diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5bc0f3395..7c8cf645a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1162,7 +1162,7 @@ jobs: # The deep submodule path (some older import sites reach this far) # must also surface the migration pointer, not a bare ModuleNotFoundError. try: - from adcp.types.generated_poc.enums.format_category import FormatCategory # noqa: F401 + from adcp.types.domains.enums.format_category import FormatCategory # noqa: F401 except ImportError as exc: assert "MIGRATION_v3_to_v4" in str(exc), exc else: @@ -1208,7 +1208,7 @@ jobs: # Blocking. Grades the COMMITTED generated tree against a regeneration # from the COMMITTED schemas, so it runs before the sync steps below - # replace `schemas/cache/`. A hand edit to `src/adcp/types/generated_poc/**`, + # replace `schemas/cache/`. A hand edit to `src/adcp/types/domains/**`, # `_generated.py` or `_ergonomic.py` fails here; without this step the # "Generate models" step overwrites the edit before anything inspects it. # `generate_types.py --check` stages the regeneration in a temp tree and diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2129987ab..66766a76a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,7 +12,7 @@ repos: args: [--line-length=100] # Version-scoped stubs are deterministically emitted and checked by # make validate-generated; reformatting them makes the checked output stale. - exclude: ^(src/adcp/types/generated_poc/|src/adcp/types/v(30|31|32)\.pyi$|src/adcp/types/versioned_bases/_?v(30|31|32)\.(py|pyi)$) + exclude: ^(src/adcp/types/domains/|src/adcp/types/v(30|31|32)\.pyi$|src/adcp/types/versioned_bases/_?v(30|31|32)\.(py|pyi)$) # Ruff linting (ignoring line-length for now - black handles it) - repo: https://github.com/astral-sh/ruff-pre-commit @@ -20,7 +20,7 @@ repos: hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix, --ignore=E501] - exclude: ^(src/adcp/types/generated_poc/|src/adcp/types/versioned_bases/_?v(30|31|32)\.(py|pyi)$) + exclude: ^(src/adcp/types/domains/|src/adcp/types/versioned_bases/_?v(30|31|32)\.(py|pyi)$) # Mypy type checking (local hook to use project dependencies) - repo: local diff --git a/AGENTS.md b/AGENTS.md index 4d7de9f4b..4d614ea07 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -234,7 +234,9 @@ from adcp.types import GetProductsRequest, CreateMediaBuyRequest, Product, Packa # Or import from a curated partial module for a narrower surface: # adcp.types.media_buy / creative / signals / protocol / buyer / seller from adcp.types.media_buy import CreateMediaBuyRequest -# Never import from adcp.types.generated_poc.* or adcp.types._generated (internal) +# adcp.types.domains.[.] is public — it is where codegen defines +# every class. Reach for it when a name several schemas declare is not bound flat. +# Never import from adcp.types._generated (internal: one winner per bare name) # Response variant types (discriminated unions) from adcp.types.aliases import CreateMediaBuySuccessResponse, CreateMediaBuyErrorResponse diff --git a/CLAUDE.md b/CLAUDE.md index 81bd434ee..547cbf4e8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,8 +12,8 @@ `Idempotency3`, `Type11`) by traversal order. Adding or reordering even one sibling schema shifts the entire numbering window — a clean re-run can produce a 600+ file diff with zero semantic change. Treat any `git diff` on -`generated_poc/` that consists of only `ClassNameN → ClassNameM` renames as -churn, not a schema delta, and discard it. `aliases.py` re-exports these +`src/adcp/types/domains/` that consists of only `ClassNameN → ClassNameM` +renames as churn, not a schema delta, and discard it. `aliases.py` re-exports these numbered classes under semantic names; accepting the renumber breaks the alias layer for nothing. @@ -21,7 +21,8 @@ alias layer for nothing. The type system has a strict layering to prevent brittleness: ``` -generated_poc/*.py (internal, auto-generated from schemas) +domains/**/*.py (auto-generated from schemas; where every class is DEFINED, + and a public address: adcp.types.domains..) ↓ _generated.py (internal consolidation) ↓ @@ -39,19 +40,25 @@ runtime `__getattr__`/`__dir__` live under `if not TYPE_CHECKING:` so type checkers see the surface only via the explicit `TYPE_CHECKING` re-export block — a typo'd import is flagged, not silently typed as `object`. -Only these modules may import from `generated_poc/` or `_generated.py` -(enforced by `tests/test_import_layering.py`): -- `_generated.py`: Consolidates exports from `generated_poc/` into a flat namespace +`domains/` is where the generator writes, and that is the whole point: there +is no private tree behind the public one and no re-export tree in front of it, +so nothing is copied and nothing can drift. A domain path is a supported +import — reach for it when the flat `adcp.types` namespace cannot bind the +name you need, because several schemas declare it. The generated class NAMES +are still generator-derived, so a numbered name can renumber on a schema bump; +`aliases.py` is what gives those a stable spelling. + +Only these modules may import from `_generated.py` — the one remaining +internal namespace, where a bare name resolves to a single winner (enforced by +`tests/test_import_layering.py`): +- `_generated.py`: Consolidates `domains/` exports into a flat namespace - `_eager.py`: Eager realization of the public surface — binds every exported name and runs the import-time patchers (`_ergonomic`, `_forward_compat`) - `aliases.py`: Creates semantic aliases for numbered discriminated union types - `capabilities.py`: Re-exports `get_adcp_capabilities_response` sub-models with disambiguated names -- `_ergonomic.py`: Applies BeforeValidator coercion for type ergonomics -- `_forward_compat.py`: Patches `Format.assets` / `RepeatableAssetGroup.assets` with open union types at import time -- `legacy.py`: Explicit facade for generated named-format wire models during the v7 migration -- `canonical_creative.py`: Canonical-first boundary models that replace generated creative lifecycle surfaces - `__init__.py`: Public API surface (thin lazy facade) -All other source code should import from `adcp.types` (the public API). +All other source code should import from `adcp.types` (the public API), or from +a domain path when the name it needs is not bound there. **Type Checking Best Practices** - Use `TYPE_CHECKING` for optional dependencies to avoid runtime import errors diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e16f7cde9..64429a4eb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -150,10 +150,16 @@ Pick the surface you are adding to: be exported from `adcp.types` — partial modules only re-curate that surface; they never import the generated layer. -Never import from `adcp.types.generated_poc.*` or `adcp.types._generated` outside -the allowlisted layering modules (`_generated.py`, `aliases.py`, `_ergonomic.py`, -`_forward_compat.py`, `capabilities.py`, `canonical_decl.py`, `_eager.py`, and -`types/__init__.py`). The generated class names are unstable across schema regen. +Never import from `adcp.types._generated` outside the allowlisted layering +modules (`_generated.py`, `aliases.py`, `capabilities.py`, `_eager.py`, and +`types/__init__.py`): it resolves a bare name that several schemas declare to a +single sort-order winner, so a schema addition can repoint it silently. + +`adcp.types.domains.[.]` is public — codegen defines every class +there and `tests/fixtures/public_api_snapshot.json` pins what each domain root +resolves to. Prefer the flat `adcp.types` surface; take a domain path for a name +the flat surface cannot bind. The generated class NAMES are still unstable across +schema regen, which is what `aliases.py` exists to absorb. After an intentional change to `adcp.__all__` or `adcp.types.__all__`, regenerate the public-API snapshot: diff --git a/LADON.md b/LADON.md index 562164a97..653d8a781 100644 --- a/LADON.md +++ b/LADON.md @@ -34,19 +34,23 @@ moment upstream adds a variant. ### Mandatory: type-system import layering -Only `_generated.py`, `_eager.py`, `aliases.py`, `capabilities.py`, -`_ergonomic.py`, `_forward_compat.py`, and `types/__init__.py` may import from -`src/adcp/types/generated_poc/**` or `src/adcp/types/_generated.py`; -`tests/test_import_layering.py` enforces the allowlist. Any other module -importing generated names directly is a `high` finding — it ships unstable -codegen identifiers to adopters. `adcp/types/__init__.py` is a PEP 562 lazy +Only `_generated.py`, `_eager.py`, `aliases.py`, `capabilities.py`, and +`types/__init__.py` may import from `src/adcp/types/_generated.py`; +`tests/test_import_layering.py` enforces the allowlist. `_generated.py` binds a +bare name several schemas declare to one sort-order winner, so importing it +outside those layers is a `high` finding. + +`src/adcp/types/domains/**` is the public definition site, not an internal +layer: importing a domain path is allowed anywhere. Prefer `adcp.types`, and +flag an import of a NUMBERED generated class (`Assets162`, `Type11`) wherever it +appears outside `aliases.py` — that identifier is what renumbers. `adcp/types/__init__.py` is a PEP 562 lazy facade with its runtime `__getattr__`/`__dir__` under `if not TYPE_CHECKING:`; reintroducing eager imports there, or moving the `__getattr__` out from under the guard, regresses both import cost and type-checker coverage. ### Mandatory: generated code is not source -A hand-edit inside `src/adcp/types/generated_poc/**` or +A hand-edit inside `src/adcp/types/domains/**` or `src/adcp/types/_generated.py` is a `high` finding. Legitimate changes there are regeneration output and pair with an upstream schema change under `schemas/` or an `src/adcp/ADCP_VERSION` bump; check that `aliases.py` and `_ergonomic.py` still @@ -106,7 +110,7 @@ public export landing without a matching entry in `AGENTS.md`'s handler table is ## Trivial Paths -- src/adcp/types/generated_poc/** +- src/adcp/types/domains/** - src/adcp/types/_generated.py - CHANGELOG.md diff --git a/README.md b/README.md index 36c73e122..2ee743324 100644 --- a/README.md +++ b/README.md @@ -282,7 +282,7 @@ Pre-configured agents (all include `.simple` accessor): See [examples/simple_api_demo.py](examples/simple_api_demo.py) for a complete comparison. -> **Tip**: Import types from the main `adcp` package (e.g., `from adcp import GetProductsRequest`), from `adcp.types`, or from a curated partial module (`adcp.types.media_buy`, `.creative`, `.signals`, `.protocol`, `.buyer`, `.seller`) — never from the internal `adcp.types.generated_poc.*` layer. `import adcp` is lightweight; the generated type graph is built only when you import a type. +> **Tip**: Import types from the main `adcp` package (e.g., `from adcp import GetProductsRequest`), from `adcp.types`, or from a curated partial module (`adcp.types.media_buy`, `.creative`, `.signals`, `.protocol`, `.buyer`, `.seller`); `adcp.types.domains.*` is public too, and is where a name several schemas declare is unambiguous. `import adcp` is lightweight; the generated type graph is built only when you import a type. ## Quick Start: Distributed Operations @@ -368,6 +368,8 @@ the SDK's supported versions in the error details. - **[API Reference](https://adcontextprotocol.github.io/adcp-client-python/)** - Complete API documentation with type signatures and examples - **[Protocol Spec](https://github.com/adcontextprotocol/adcp)** - Ad Context Protocol specification +- **[The type surface](docs/type-surface.md)** - Where a generated class lives, which spelling to import it by, and what `AdcpRequest` and `AdcpResponse` guarantee +- **[How the type tree is generated](docs/design/type-generation.md)** - The generation pipeline, name derivation, the guards, and the generator pin - **[Handler authoring](docs/handler-authoring.md)** - Building an AdCP-compliant agent on `adcp.server` - **[Production seller path](docs/production-seller.md)** - Choose the server abstraction and wire durable multi-tenant tasks and webhook delivery - **[Validation contract](docs/validation-contract.md)** - Canonical wire validation versus structural Pydantic models @@ -515,7 +517,7 @@ from adcp.types.buyer import GetProductsRequest, CpmPricingOption from adcp.types.seller import Offering, PropertyList, ContentStandards ``` -If a type you need isn't in `adcp.types`, open an issue — we'll add an alias. The `adcp.types.generated_poc.*` modules are internal; class names and module paths shift on every schema regeneration and are not a supported API. +If a type you need isn't in `adcp.types`, open an issue — we'll add an alias. The `adcp.types.domains.*` modules are public and are where codegen defines every class, so they always have it; the caveat is the class NAMES, which codegen derives and renumbers (`Assets162`) — an alias is the stable spelling. The six partial modules (`media_buy`, `creative`, `signals`, `protocol`, `buyer`, `seller`) are for curation and discoverability — they group types by domain and give you a smaller import surface. They are **not** a per-domain performance tier: the first AdCP type you touch through any of them builds the same single Pydantic graph. @@ -559,7 +561,7 @@ See `examples/type_aliases_demo.py` for more examples. **Import guidelines:** - ✅ **DO**: Import from main package: `from adcp import GetProductsRequest` - ✅ **DO**: Use semantic aliases: `from adcp import CreateMediaBuySuccessResponse` -- ⚠️ **AVOID**: Import from `adcp.types.generated_poc.*` — paths and class names (including numbered `Assets*` variants) change on every schema regeneration. +- ⚠️ **AVOID**: Import a numbered generated class (`Assets5`, `Type11`) by any path — codegen renumbers them on every schema regeneration. `adcp.types.domains..` itself is a supported import. The main package exports provide a stable API while internal generated types may change. diff --git a/SCHEMA_DELTAS.md b/SCHEMA_DELTAS.md index 0f0db1c28..5ee2bca03 100644 --- a/SCHEMA_DELTAS.md +++ b/SCHEMA_DELTAS.md @@ -1,3 +1,28 @@ # Generated-types delta -_No field-shape changes detected._ +## Field changes + +- `account/list_account_changes_request.py` + - `ListAccountChangesRequest`: `-adcp_major_version`, `-adcp_version` +- `brand/search_brands_request.py` + - `SearchBrandsRequest`: `-adcp_major_version` +- `media_buy/accept_proposal_request.py` + - `AcceptProposalRequest`: `-adcp_major_version`, `-adcp_version` +- `media_buy/buy_products_request.py` + - `BuyProductsRequest`: `-adcp_major_version`, `-adcp_version` +- `media_buy/control_media_buy_request.py` + - `ControlMediaBuyRequest`: `-adcp_major_version`, `-adcp_version` +- `media_buy/decline_proposals_request.py` + - `DeclineProposalsRequest`: `-adcp_major_version`, `-adcp_version` +- `media_buy/list_products_request.py` + - `ListProductsRequest`: `-adcp_major_version`, `-adcp_version` +- `media_buy/refine_proposals_request.py` + - `RefineProposalsRequest`: `-adcp_major_version`, `-adcp_version` +- `media_buy/request_proposals_request.py` + - `RequestProposalsRequest`: `-adcp_major_version`, `-adcp_version` +- `media_buy/sync_reporting_status_request.py` + - `SyncReportingStatusRequest`: `-adcp_major_version`, `-adcp_version` +- `trusted_match/context_match_request.py` + - `ContextMatchRequest`: `-adcp_major_version`, `-adcp_version` +- `trusted_match/identity_match_request.py` + - `IdentityMatchRequest`: `-adcp_major_version`, `-adcp_version` diff --git a/docs/design/type-generation.md b/docs/design/type-generation.md new file mode 100644 index 000000000..bfceedf49 --- /dev/null +++ b/docs/design/type-generation.md @@ -0,0 +1,571 @@ +# How the type tree is generated + +Status: current. Generator pinned at `datamodel-code-generator==0.64.0`. +Schema pin: `src/adcp/ADCP_VERSION` = `3.2.1`, which `resolve_bundle_key` collapses to the +bundle `schemas/cache/3.2`. + +This document is for whoever has to change the pipeline. It covers every step in order, how +a class gets its name, what the guards are, how to add a schema feature, and what to do +when the generator renders something wrong. + +For the resulting surface from a consumer's side, see [../type-surface.md](../type-surface.md). +For subclassing a generated type, see [../extending-types.md](../extending-types.md). + +## The shape of the thing + +1053 non-bundled schema files become 1056 generated model modules, which become one flat +namespace of 923 names and a 1084-module public mirror under `adcp.types.domains`. Nothing +in it is hand-written: the flat `__all__`, the `_generated.py` arena, the mirror, the +error-details exports and the collision table are all emitted. + +The generator is a fixed, flawed input. Three of the pipeline's layers exist because of +that: a pre-generation pass that rewrites schemas into shapes the generator renders +correctly, a post-generation pass that rewrites the rendered Python, and a consolidation +step that derives the public surface from the result. Read the pin section at the end +before proposing to remove any of them. + +## The pipeline, end to end + +`make regenerate-schemas` is the full refresh: + +```make +regenerate-schemas: ## Download latest schemas and skills from bundle, then regenerate models + $(PYTHON) scripts/sync_schemas.py + $(PYTHON) scripts/fix_schema_refs.py + $(PYTHON) scripts/bundle_schemas.py + $(PYTHON) scripts/generate_versioned_stubs.py + $(PYTHON) scripts/generate_versioned_bases.py + $(PYTHON) scripts/generate_types.py + $(PYTHON) scripts/consolidate_exports.py + $(PYTHON) scripts/generate_ergonomic_coercion.py +``` + +| Step | Consumes | Produces | Network | +|---|---|---|---| +| `sync_schemas.py` | the published schema bundle | `schemas/cache//**` | **yes — the only network step** | +| `fix_schema_refs.py` | the cache | the cache, with absolute `$ref` URLs rewritten to relative file paths | no | +| `bundle_schemas.py` | the cache | `src/adcp/_schemas/**`, the schemas the wheel ships | no | +| `generate_versioned_stubs.py` | the cache | `src/adcp/types/v30.pyi`, `v31.pyi`, `v32.pyi` | no | +| `generate_versioned_bases.py` | the cache | `src/adcp/types/versioned_bases/**` | no | +| `generate_types.py` | the cache | `domains/**`, `_generated.py`, `_ergonomic.py` | no | +| `consolidate_exports.py` | the generated tree | `_generated.py`, each `domains//__init__.py`, `domains/__init__.py`, `error_details.py`, `docs/shared-type-names.md` | no | +| `generate_ergonomic_coercion.py` | the generated tree | `_ergonomic.py` | no | + +`sync_schemas.py` is the only step that touches the network, and skipping it regenerates +against the committed cache. That is the normal inner loop: + +```bash +python scripts/generate_types.py # regenerate from the committed cache +python scripts/generate_types.py --check # verify without writing anything +``` + +Note that `generate_types.py` already runs `consolidate_exports.py` and +`generate_ergonomic_coercion.py` as subprocesses against its staging tree and installs +their output, so the Makefile target runs both of them a second time. Harmless, but do not +read the Makefile as the statement of what depends on what. + +### One tree + +`OUTPUT_DIR` is `src/adcp/types/domains` — the address adopters import from. The generator +defines every class at its public path, so there is no private tree behind the public one +and no re-export tree in front of it: nothing is copied, and nothing can drift. What +`consolidate_exports.py` writes into that tree are the namespaces DERIVED from it — each +domain root, the `DOMAINS` roster, `_generated.py` and `error_details.py` — never a second +copy of a module. + +Four domains have no package root to write: `adagents`, `brand_discovery`, `manifest` and +`manifest_schema` are single schemas at the bundle root, so the generator emits +`domains/.py` and that module IS the domain root. A post-generation pass +(`declare_root_schema_module_exports`) gives each of the four an `__all__`, because a +domain root is a public namespace and `scripts/export_resolution.py` reads `__all__` to +record what one promises. + +### Inside `generate_types.py` + +`main()` stages everything in a `TemporaryDirectory(dir=REPO_ROOT, prefix=".typegen-")` and +installs it at the end with `os.replace` plus full rollback. Nothing is written to the +checkout until every step has succeeded, which is why a failure leaves `git status` clean — +and why a crash takes the broken intermediate file with it. + +In order: + +1. Snapshot the committed tree, for the delta report. +2. `flatten_schemas` — copy the bundle into the staging tree with hyphens converted to + underscores in every path part, applying the pre-generation transforms below. +3. `_staged_source_tree` — copy `src/adcp` into the staging tree, minus the committed + generated tree. Generation targets `types/domains` inside that copy, so consolidation + can import what was just generated and write the derived namespaces beside it. +4. Run `datamodel-codegen` over the staged schema tree. +5. `generate_root_discovery_types` — generate `brand.json` a second time, standalone, as + `brand_discovery.py`. The generator is therefore invoked twice per run. +6. `fix_forward_references` — repair aliased imports. +7. `apply_post_generation_fixes` — a subprocess running `post_generate_fixes.py` against + the staged tree. +8. `prune_unused_bundled_modules` — keep only the three paths in `BUNDLED_KEEP`. +9. Run `consolidate_exports.py`, then `generate_ergonomic_coercion.py`, against the staging + tree. The first writes the domain roots into the tree generated in step 4. +10. `restore_unchanged_files` — restore byte-for-byte any file whose only change is a + timestamp header, so a no-op regeneration produces no diff. Once, over the whole tree, + after the roots are in it. +11. Compare the artifacts against the checkout — `domains/`, `_generated.py`, + `_ergonomic.py`, `error_details.py`, `docs/shared-type-names.md`. Under `--check`, + print the drift and return 1 without writing. Otherwise install and write + `SCHEMA_DELTAS.md`. + +### The pre-generation transforms + +Applied per schema file, in this order, inside `flatten_schemas`: + +```python +schema = normalize_enum_descriptions(schema) +schema = inline_structural_pointer_refs(schema, rel_path) +schema = collapse_nullable_unions(schema) +schema = normalize_version_envelope_composition(schema, rel_path) +schema = rewrite_refs(schema, rel_path) +schema = stabilize_inlined_core_refs(schema, rel_path) +schema = stabilize_nested_discriminators(schema, rel_path) +schema = flatten_root_object_validation_union(schema, rel_path) +schema = flatten_validation_oneof(schema) +``` + +Order matters in two places, and both constraints are on the same step. +`normalize_version_envelope_composition` must precede `rewrite_refs`, so the reference it +appends takes the same canonical-URL path through the rewriter as the schemas that already +compose; and it must follow `inline_structural_pointer_refs`, so the envelope reference it +appends is never itself inlined back into copied fields — which would turn inheritance into +flattened fields. + +Each of these operates on the staging copy only. The cached bundle, the schemas the wheel +ships and any direct-from-schema validation are untouched, which is what keeps the +transforms from changing what the SDK validates against. + +Two of them carry explicit ceilings rather than trusting a bump not to move the problem. +`normalize_version_envelope_composition` raises if it would rewrite more than 12 schemas: +*"A bump changed the shape of the problem: re-measure which schemas declare the version +fields without composing the envelope before raising the ceiling."* It also carries four +refusals, one of which exists because the rewrite deleted a field without it — see +*When the generator renders something wrong*, item 3. + +### `$ref` resolution never reaches the network + +`--allow-remote-refs` is passed, but so is `--http-local-ref-path` pointing at a temp +mirror of the pinned cache laid out as `adcontextprotocol.org/schemas/`. A reference +the cache does not contain fails locally with `$ref local file not found`; there is no +network fallback. `tests/test_generate_types_safety.py` grades this both ways, parametrized +on the file being present and absent. + +## The post-generation pass + +`scripts/post_generate_fixes.py` is 7367 lines holding 130 module-level functions: 72 fix +entrypoints, built as one ordered list in `main()` and run in list order, plus 58 helpers. + +Three ordering constraints are commented in the list itself and must be preserved: + +- `point_integer_fields_at_the_schema_integer_type` retypes every `type: integer`, + including an integer root's own `root` annotation, so it must precede + `rewrite_scalar_rootmodels` — run after, and an integer root is still spelled + `RootModel[StrictInt]` and keeps its wrapper. +- `rewrite_scalar_rootmodels` is last of the substantive fixes, because every earlier fixer + can emit or edit a `RootModel[]`. +- The import-hygiene passes run after everything that can add an import or a base-class + name, with `strip_extra_blank_lines_at_eof` last. + +**A fix that cannot find its target must raise, not return.** The sharpest failure mode in +this file is a pass that matches literal source text which another pass legitimately +changed. Two instances are on record. One produced a syntactically invalid module: +a regex matching only the flat single-line `from pydantic import ...` form captured the bare +`(` from a parenthesized import and wrote `from pydantic import (, model_validator`. The +other was silent — `fix_product_publisher_property_model_coercion` matched an exact import +line, failed to find it, printed a message, returned early, and never added its validator; +no syntax error and no test failure. Locate by AST, or through the shared +`ensure_pydantic_import` / `add_to_import` helpers, which read whichever names are present, +preserve the statement's form, and raise rather than mangle. Ten fixes still match an +import by exact literal text; that is recorded fragility, not a pattern to copy. + +There is no per-fix effect ledger today, so a fix whose locator goes stale stops working +silently until a test notices. PR #1374 adds one — a declared manifest of which fixes +change files, re-measured rather than hand-edited, checked on every run — and that is the +mechanism to extend rather than inventing a second one. + +## `consolidate_exports.py` + +1414 lines, and the step that turns the generated tree into a public surface. It writes +four artifacts: `_generated.py`, the flat arena every public name is re-exported from; +`src/adcp/types/domains/**`, the mirror of the schema layout; `error_details.py`; and +`docs/shared-type-names.md`, the table of every name more than one schema declares. + +**The surface is derived, and two guards make it stay that way.** Both are properties of +the tree, so a schema addition satisfies them with no edit, and both fail the build rather +than recording an exception. + +Reachability — every generated public class must be bound by some public name: + +```python +raise ValueError( + f"{len(unreachable)} generated public class(es) are reachable under no " + f"public name:\n{details}\n\n" + "Every public class in a non-bundled generated module must be importable " + "from adcp.types.domains.., the mirror of the module that " + "declares it. A class that is reachable under no name is a model an " + "adopter cannot construct, which is how the error-details family became " + "unusable (#1080).\n" +) +``` + +Unambiguous binding — no two classes may claim one public path inside a domain. Its +message is the instruction for fixing it, and the instruction is not "allow the clash": + +```python +raise ValueError( + f"{len(clashes)} public name(s) are claimed by more than one generated " + f"class inside one domain:\n{details}\n\n" + "qualified_public_name() suffixes the module stem, which is unique " + "inside a domain for every pair that needs it today. A clash means two " + "modules in one domain now share a stem — widen the suffix to the " + "domain-relative path, do not drop a binding.\n" +) +``` + +One hand-maintained table survives: `KNOWN_COLLISIONS`, 13 names handled by qualified +import. `scripts/collision_allowlist.json` is still in the tree and nothing reads it — +`grep -rl collision_allowlist scripts/ src/ tests/` matches only `.pyc` caches — so it is +an orphan of the mechanism the guards replaced, and the thing not to do is revive it. An +allowlist records which names collide without pinning which variant wins; the file's own +generated comment said the names were "knowingly resolved by first-seen / stem-preference +order", and first-seen order is a generator-traversal artifact, so a regeneration that +repointed a name to a different schema node passed the check it was supposed to fail. + +The mirror is over a thousand files, so it is written unformatted and black is invoked once +over the whole directory rather than per file. + +## How a class gets its name + +Four mechanisms. Three are live; one is written, graded, and not wired. + +### Module names: path-derived + +Each schema's relative path has every part's `-` replaced with `_`, and that becomes the +generated module path: `media-buy/create-media-buy-response.json` → +`media_buy/create_media_buy_response.py`. + +Nothing recomputes that mapping to read it back. The generator emits a `# filename:` +header per module, and `scripts/task_message_lattice.py` reads it: + +> The header is the generator's own statement of provenance, which is why it is read +> instead of recomputed: a path convention would have to re-derive the hyphen conversion, +> the directory layout and the root-discovery special cases. + +Reuse that function. Any new pass needing schema-to-module provenance should go through it +rather than re-deriving a second answer that could agree while both are wrong. + +One trap if you walk the task registry yourself: `index.json` holds **77 task entries under +76 distinct task names**, because `list-creative-formats` is declared in two domains. Key a +registry walk by `(domain, task)` — a dict keyed by task name silently loses one task, and +with it two messages. + +### Root class names: `title`-derived + +The generator names a root class from the schema's `title`. The repo reproduces that rule +only to read it back: + +```python +def mangle_schema_title(title: str) -> str: + return "".join(word.capitalize() for word in re.split(r"[^0-9a-zA-Z]+", title) if word) +``` + +Measured against all 154 task-message schemas at the pin, this reproduces the generated +root class or union-alias name for 154 of 154. Note `str.capitalize`, which lower-cases the +tail — that is the generator's behavior, and it is the part a filename-derived guess gets +wrong in both directions: `Get AdCP Capabilities Request` becomes +`GetAdcpCapabilitiesRequest`, and `list-creative-formats-request.json` does *not* become +`ListCreativeFormatsRequest` because its title names the agent variant. + +Nothing in the pipeline injects a `title` to steer this. + +### `$ref` position: classified, then eliminated + +```python +_DEFINITION_POINTER_ROOTS = frozenset({"$defs", "definitions"}) + + +def _is_structural_pointer(fragment: str) -> bool: + """Report whether a ``$ref`` fragment addresses a structural position.""" + if not fragment.startswith("/"): + return False + first_token = fragment[1:].split("/", 1)[0] + return first_token not in _DEFINITION_POINTER_ROOTS +``` + +A definition-container pointer names a reusable subschema, and the generator emits one +shared class for it. Every other pointer addresses a structural position — +`/properties/`, `/items`, `/oneOf/` — which has no name, so the generator mints a +one-arm `RootModel` wrapper per referencing site. `inline_structural_pointer_refs` replaces +such a reference with the subschema it selects, so the wrapper is never named at all. + +The predicate is on the *selected subschema*, not on the pointer token: inline only what +the generator would wrap — a `type: array` node, a constrained scalar, a bare `$ref` — and +leave a pointer whose target already generates a shared named class, so no import is +replaced by a copy. `core/targeting-input.json` is the worked case: 27 of its 37 fields are +declared as a pointer-or-null union, which otherwise produces +`targeting.AudienceExclude = RootModel[list[str]]` and becomes `list[str] | None` once +resolved. + +### Union arm names: positional today + +Root-level union arms are named by position. `restore_response_variant_aliases` mints +them off a hand-written 22-row table of `(module path, base class name)` pairs: + +```python +class_names = [f"{self.base}{index}" for index in range(1, len(arms) + 1)] +``` + +and carries a hardcoded permutation so that an upstream insertion did not move two existing +public names: + +```python +if self.base == "BuildCreativeResponse" and len(arms) == 6: + # Preserve the long-standing public numbering where Response1 is the simple + # success arm and Response2 is the error arm; append newer schema branches after those. + arms = [arms[index] for index in (0, 4, 1, 2, 3, 5)] +``` + +That permutation is the drift channel made visible: a positional name's meaning changed, +and a hand-written reorder was the only way to hold it still. + +A rule that names an arm from its schema node (`title`, then discriminator `const`, +then full `required` set, failing closed instead of numbering) is designed and measured +against the pinned bundle but not wired in; it is tracked, with the two surface-policy +decisions wiring it needs, in #1409. + +## What keeps a name's meaning stable, and where it does not + +The property wanted is: a name refers to the same schema node until the schema changes. + +1. **Schema-file order is explicit.** `flatten_schemas` walks + `sorted(SCHEMAS_DIR.rglob("*.json"))`, with a comment recording that `rglob` otherwise + follows filesystem insertion order. `test_flatten_schemas_uses_stable_path_order` grades + it. +2. **Provenance is read, not guessed.** The generator's own `filename:` header, and + `mangle_schema_title` reproducing the generator's title rule for 154 of 154 task + messages, mean a pass that renames or marks a class finds it by what produced it. +3. **The staleness check makes instability visible.** `generate_types.py --check` re-runs + the whole pipeline into a staging tree and compares all three artifacts byte-for-byte. + Any input-order sensitivity shows up as a dirty regeneration. + +**The hole.** The generator walks its input by basename rather than by full path, and +basenames are not unique here: of the 1169 prepared inputs, 1009 basenames are distinct and +**316 inputs share one with another input**. Every such pair ties, and a tie resolves in +readdir order, which differs between filesystems. PR #1374's own notes record what flipping +every tie does — it renumbers anonymous variant classes, `Result9` to `Result13`, in five +measured files, while the semantic aliases in `src/adcp/types/aliases.py` re-point correctly +so the names adopters import stay stable. The fix is to feed the generator an explicit file +list, or to sort its walk by full path. + +`--reuse-model` is the other known cross-file-order sensitivity in the flag set, and it is +**unmeasured**. Measure it before adding near-duplicate documents to the tree: generate +once with the schema file list reversed and compare the public surface map. That +measurement is a prerequisite for anything that adds schema variants, which is why the +canonical-model field strip is one declared removal rule rather than a set of variant +documents. + +## The guards + +The one authority on whether the committed tree matches the pipeline is: + +```bash +python scripts/generate_types.py --check # "✓ Generated types are up to date" +``` + +It stages a complete regeneration, compares `domains/`, `_generated.py` and +`_ergonomic.py` byte-for-byte against the checkout, and writes nothing. **Run it yourself +after any pipeline change: `make validate-generated` does not.** That target checks syntax +and the two versioned-stub generators only; PR #1374 is what wires the staleness check into +the Makefile and into CI as a blocking step. Until it lands, nothing automated fails on a +stale generated tree. + +`pytest tests/` is the rest of the gate. The modules that grade generation: + +| Module | Catches | +|---|---| +| `test_code_generation.py` | a pre-generation transform or a named fix losing its behavior | +| `test_generate_types_safety.py` | the pipeline becoming unsafe: a canonical `$ref` reaching the network, check mode writing to the checkout, a late failure leaving a half-installed tree, a partial `os.replace` not rolling back | +| `test_collision_guard.py` | the derived guards going blind: a declared class the mirror does not carry, a domain namespace splitting a name its own domain declares twice, an aggregate module exporting more than its root | +| `test_export_surface_is_derived.py` | a public name that does not resolve | +| `test_extra_policy.py` | `AdCPBaseModel` drifting off `extra='ignore'`, or an open-`additionalProperties` schema failing to get `extra='allow'` | +| `test_scalar_roots.py` | a `RootModel[str\|int\|float]` surviving, or a scalar root not behaving as its scalar | +| `test_generated_hierarchy.py` | a root `allOf`/`$ref` ceasing to render as inheritance | +| `test_generated_enum_strenum.py` | enums no longer string-comparable or string-hashable | +| `test_literal_discriminator_defaults.py` | single-value `Literal` discriminators losing their default, and the inverse | +| `test_canceled_literal_default.py` | `canceled: Literal[True] = True` returning, which makes an omitted field silently cancel a media buy | +| `test_codegen_invariants.py` | optional boolean constants inventing capabilities or response state | +| `test_codegen_deprecation_contract.py` | JSON Schema `deprecated: true` no longer reaching `Field(deprecated=True)` | +| `test_task_message_lattice.py` | the marker pass failing: a task message unmarked, a marker gaining fields, the envelope residues moving | +| `test_canonical_models_are_subclasses.py` | a canonical model built as a copy rather than a subclass, or a generated validator not reaching the public model | +| `test_canonical_validator_parity.py` | a document the generated class refuses that the public canonical name accepts | +| `test_rootmodel_proxy.py` | a retained `RootModel` union needing `.root` for attribute access | +| `test_import_layering.py` | a non-facade module importing from the generated layer | +| `test_adopter_public_paths.py` | a real downstream seller's generated imports losing their public path | + +There is a second, **dormant** staleness checker: `scripts/diff_generated_types.py check` +implements a renumbering-tolerant drift check, and nothing invokes its CLI. Only its +`snapshot` and `format_diff` library API is used, to write `SCHEMA_DELTAS.md`. + +## Adding a schema feature + +The order below is the order that works, because each step's failure is cheap and names its +cause. + +1. **Put the schema in the cache and look at what the generator does with it.** Run + `python scripts/generate_types.py` and read the new module. Do not reason about the + rendering; the generator has surprising rules and this is the step that tells you which + one you have hit. +2. **If the rendering is wrong, prefer a schema transform.** Add it to the chain in + `flatten_schemas`, operating on the staging copy. A transform is testable against a + constructed schema dict, is idempotent by obligation, and cannot affect what the SDK + validates against. Give it an explicit refusal set and a ceiling, like + `normalize_version_envelope_composition`: raise when a bump makes it do more than + measured, rather than silently changing shape. +3. **If it cannot be expressed on the schema, add a post-generation fix.** Append it to + `main()`'s list, respecting the three ordering constraints. +4. **Locate by structure, not by spelling** — see the post-generation section. A fix that + cannot find its target must raise. +5. **Give it a test in `test_code_generation.py`** that feeds the real function a + constructed input and asserts the rewrite. +6. **Break the new guard on purpose, once.** Reintroduce the defect it targets on a scratch + edit and watch it go red, then revert by editing the file back — not with + `git checkout`, which eats untracked test files. A guard that has never failed is not + known to grade anything. +7. **Regenerate and check that the tree is idempotent.** `python scripts/generate_types.py + --check` must say "Generated types are up to date" from the committed state. + +If the feature needs a *name* that adopters import, the reachability guard already supplies +one: every public class in a non-bundled generated module is bound at +`adcp.types.domains..`, and the build fails if it is not. Whether the name +also belongs in the flat namespace is the separate question, and it answers itself — if +exactly one schema declares it, it is there; if more than one does, it is in +`docs/shared-type-names.md` and the domain path is the spelling. + +## When the generator renders something wrong + +1. **Reproduce it outside the pipeline.** Build the smallest schema that shows it and run + the generator over it with the repo's exact flag set. Several supposed pipeline defects + have turned out to be two correct passes composing badly, and a minimal repro is what + separates them. +2. **Find the staging tree.** On failure `generate_types.py` deletes its `.typegen-*` + directory, so a syntax error in a generated file leaves no evidence. Run the script + under a wrapper that detaches that directory's finalizer, then `ast.parse` every staged + module to find the broken one. That is how the `from pydantic import (, model_validator` + composition defect was located at all. +3. **Ask what the generator does with the whole root, not just the node you changed.** A + root `properties` is merged into every arm the generator builds, but a root `allOf` + `$ref` becomes a base only of the arm built *from the root object*: an arm that is a bare + `$ref` to another schema is generated from that schema, and the root's bases never reach + it. Moving a field from `properties` into an `allOf` therefore **deletes** it from such an + arm rather than relocating it — measured on two schemas, where it removed `adcp_version` + from a submitted arm whose `extra` policy is `forbid`, so a seller echoing that field + would then have been rejected. `_has_bare_ref_root_arm` is the refusal that resulted. +4. **Measure the whole field set, not the field you expected to change.** That deletion was + found by comparing full `model_fields` sets before and after, in a step already reported + as safe on the strength of the one field type that had been examined. +5. **Ask whether the pass is reading a spelling or a structure.** The most common failure + shape is a pass that matched literal text which another pass legitimately changed. Both + branches pass their own tests; only the composition breaks. +6. **Check whether the thing you want already exists.** Before adding a helper, grep for + one — `ensure_pydantic_import`, `_remove_unused_imports`, the deferred-adapter + primitive, `task_message_lattice`'s provenance map. +7. **Do not reach for a newer generator.** See below. + +## The generator pin + +```toml +"datamodel-code-generator[http]==0.64.0", # pyproject.toml:152, dev extra +"datamodel-code-generator==0.64.0", # pyproject.toml:400, dependency-groups dev +``` + +Two pin sites, both exact. There is no lock file, and CI installs with +`pip install -e ".[dev]"`, so the dev extra is the pin CI uses. The comment above it +explains why the pin is *exact*: variant numbering shifts between versions, producing diff +churn and breaking generated-code imports that reference specific suffixes. + +**Why 0.64.0 specifically — two measured facts, neither sufficient alone.** + +**0.72.0 and later cannot generate this tree.** Generation aborts: + +``` +✗ Generation failed: +$ref file not found: .../.schema_temp/media_buy/enums/proposal_status.json +``` + +The trigger is a relative `$ref` inside a **multi-arm** `allOf`. +`media-buy/media-buy-commitment-response.json` declares, at +`/oneOf/0/properties/accepted_proposal`, an `allOf` of two arms: a cross-directory `$ref` to +`core/canonical-proposal.json`, and an inline `const` overlay. The referenced document then +refs `../enums/proposal-status.json`, and from 0.72.0 the generator joins that `../` +against the *referring* document's path instead of the *defining* document's directory. +Isolated to the multi-arm form: a single-arm `allOf` and a plain `$ref` both generate +cleanly. + +19 releases were probed with a three-file minimal repro. 0.64.0 through 0.71.0 all return 0; +0.72.0, 0.78.0, 0.80.0, 0.82.0 and 0.83.0 all fail. **0.72.0 is the first broken release and +0.71.0 the last working one.** 0.72.0's notes list four breaking changes, none of them this. +Past the 28 distinct mis-resolved refs, a run still fails on an independent +0.72.0-boundary defect in `JsonSchemaObject` validation. + +**0.71.0 can generate the tree and is rejected on non-equivalence.** It was actually tried, +and the output is not equivalent — not equivalent in the dangerous direction. It drops const +defaults that were correct at 0.64.0: `status: Literal['completed'] = 'completed'` becomes +`Literal['completed'] | None = None`, and the same for +`canonicalization_contract_version`, `canonicalization_media_type`, and the deprecated +postal flags. Measured on a committed regeneration that touches no schemas at all, so the +whole diff is generator-attributable: 44 removed const defaults, and three +`discriminator='type'` parameters dropped from `core/audience_evidence.py`, which silently +turns three discriminated unions into plain unions. The two `canonicalization_*` drops alone +produce seven reporting-production test failures with +`ValueError: offering must satisfy the complete public contract`. + +The direction matters. The bump's own changelog promises to fix optional boolean constants +inventing state — and that correctness win has already been obtained at 0.64.0 by hand, by a +post-generation pass that corrects 44 fields while *preserving* required boolean fields, +string discriminator defaults, and deprecation metadata. 0.71.0 removes precisely what that +pass deliberately kept. + +**The consequence for anyone designing in this repository: there is no newer-generator lever +at all**, not even for a future defect whose changelog claims a fix. Anything the type tree +needs must be reachable through a schema transform, a post-generation pass, or hand-written +source that inherits from generated output. That is not a deferred task; it is the +constraint every layer above is built around. + +## What this architecture refuses + +- **Refused: any generator bump.** 0.64.0 is the generator, indefinitely, for the two + measured reasons above. An earlier recommendation called 0.71.0 "worth taking separately" + on changelog reasoning without an equivalence measurement; that recommendation is + withdrawn, and this entry exists so it is not proposed a third time from the same + changelog. +- **Refused: a post-hoc deduplication of any emitted list.** A duplicate `__all__` entry is + a symptom of an emitter that appends instead of unioning. De-duplicating downstream + preserves the defect and hides its signal; emit sorted sets at the point of emission, + where the class of defect becomes unexpressible rather than fixed. +- **Refused: a second mechanism for reaching the public classes.** When an injected + validator failed to reach the public canonical models, the fix was to delete the runtime + copies so inheritance carries it. Carrying validators onto the copies as well would + double the mechanism that caused the gap: every future validator would need the second + carry, and the day one is forgotten the gap reopens silently. +- **Refused: a derived type stub.** Generating a `.pyi` from `model_fields` repairs a ledger + instead of deleting it — two artifacts, one derived from the other, drifting between + regenerations. Real source makes the stub unnecessary. +- **Refused: growing a hand-maintained allowlist.** The collision allowlist's reader is + gone; `KNOWN_COLLISIONS` is the one table left, and the derived guards above are what + replaced the rest. Nothing should add a third — including for arm naming, which fails + closed and takes a one-line upstream title change rather than a ledger of tolerated + numbers. +- **Refused: a positional name on the public surface.** Numbered classes may remain as + module-internal helpers reached only through a field annotation, where the name is + cosmetic. A public binding is the contract — 38 of the 923 public names are + numeric-suffixed today — and renaming all 1037 numbered internal definitions as a gate is + refused for the opposite reason: it converts a surface guarantee into a beautification + project. +- **Refused: inventing composition the schema lacks.** No `required: [status]` added to the + ten responses that do not compose the protocol envelope; no version fields added to + `validate-input-request`; no marker on a request file the task registry does not name. + Residues are pinned shrink-only and named upstream — degraded honestly, never papered. diff --git a/docs/examples/testing_patterns.py b/docs/examples/testing_patterns.py index 4c5db883e..260999b08 100644 --- a/docs/examples/testing_patterns.py +++ b/docs/examples/testing_patterns.py @@ -25,8 +25,8 @@ ) from adcp.types.core import TaskResult, TaskStatus -# ❌ WRONG: Never import from generated_poc in tests -# from adcp.types.generated_poc.product import PublisherProperties4 +# ❌ WRONG: Never import an internal numbered class in tests +# from adcp.types.domains.core.product import PublisherProperties4 # ============================================================================= @@ -399,13 +399,13 @@ class AntiPatterns: These demonstrate common mistakes that violate testing principles. """ - def test_anti_pattern_importing_generated_poc(self): - """❌ WRONG: Don't import from generated_poc in tests.""" + def test_anti_pattern_importing_numbered_variants(self): + """❌ WRONG: Don't import internal numbered classes in tests.""" # This couples tests to internal implementation # When schemas evolve, these imports break # ❌ DON'T DO THIS: - # from adcp.types.generated_poc.product import PublisherProperties4 + # from adcp.types.domains.core.product import PublisherProperties4 # prop = PublisherProperties4(...) # ✅ DO THIS INSTEAD: @@ -552,7 +552,7 @@ def sample_product_json(): 6. Write tests users can learn from ❌ DON'T: -1. Import from generated_poc +1. Import an internal numbered class 2. Test Pydantic internals 3. Test type identity (assert X is Y) 4. Test implementation details diff --git a/docs/extending-types.md b/docs/extending-types.md index 48da70d2d..11d019866 100644 --- a/docs/extending-types.md +++ b/docs/extending-types.md @@ -110,14 +110,26 @@ schema that renders as a union of arms (`AssetVariant`, than an object, which is what a pure `if`/`then` constraint schema produces. Those compose as copied fields, and `TypeAdapter` is the way to validate them. -Note that `AdcpVersionEnvelope` itself is not exported from `adcp.types`; -`ProtocolEnvelope` is. +Both envelope classes are exported from `adcp.types`, and `AdcpVersionEnvelope` +is also on the curated `adcp.types.protocol` surface, so neither check needs a +private import: + +```python +from adcp.types import AdcpVersionEnvelope, ProtocolEnvelope +``` + +Composed ancestry is also what makes a task message identifiable before you know +the tool. Every task request and response additionally descends from +`AdcpRequest` or `AdcpResponse`, which carry the cross-cutting accessors and the +envelope/payload field classifiers. See +[the type surface](type-surface.md) for what those two guarantee and for the +descent check to use when refusing a hand-written look-alike. ## Picking the Right Base Class — Context-Specific Schema Variants Several entity names (`Creative`, `Package`, `MediaBuy`, etc.) appear in multiple spec slices with **genuinely different shapes**. The bare name resolves to one specific variant — typically not the one you want when extending response types. The creative inside `ListCreativesResponse.creatives` is a different class from the creative inside `GetCreativeDeliveryResponse.creatives`, even though both are spelled `Creative` in the spec. Subclassing the wrong variant produces silent type drift: construction works, but `mypy` flags `[assignment]` when you wire your subclass into the response that expects a different variant, and runtime serialization may drop fields the consuming code expects. -**The fix is to import the variant-specific alias.** For every name that collides across slices, `adcp.types` exports a disambiguated alias whose prefix names the slice it belongs to — `ListCreativesCreative`, `SyncCreativesCreative`, `DeliveryCreative`, `CapabilitiesCreative`, and so on. Import these from the public `adcp.types` namespace. Do **not** reach into `adcp.types.generated_poc.*` or `adcp.types._generated` — those are internal modules whose class names renumber on every schema regen, so an import that resolves today can silently move tomorrow. +**The fix is to import the variant-specific alias.** For every name that collides across slices, `adcp.types` exports a disambiguated alias whose prefix names the slice it belongs to — `ListCreativesCreative`, `SyncCreativesCreative`, `DeliveryCreative`, `CapabilitiesCreative`, and so on. Import these from the public `adcp.types` namespace. Do **not** reach into `adcp.types._generated` — it resolves a bare name that several schemas declare to one sort-order winner, so an import that resolves today can silently name a different class tomorrow. A domain path (`adcp.types.domains.creative.list_creatives_response`) is public and names the declaring schema, so it cannot move that way; what it does not protect you from is a NUMBERED class name, which codegen renumbers. These prefixed aliases live in the flat `adcp.types` namespace, not in the curated partial modules (`adcp.types.creative`, `adcp.types.media_buy`, …). The partials export only the canonical, single-variant names (`Creative`, `Package`, `MediaBuy`). When you need a specific variant, import it from `adcp.types`. @@ -132,7 +144,7 @@ These prefixed aliases live in the flat `adcp.types` namespace, not in the curat | Extend the package element of `CreateMediaBuyRequest.packages` | `from adcp.types import PackageRequest` (also in `adcp.types.media_buy`) | `from adcp import Package` | | Extend the media-buy element of `GetMediaBuysResponse.media_buys` | `from adcp.types import GetMediaBuysMediaBuy` | `from adcp import MediaBuy` (resolves to the core variant; the list slice has a narrower shape) | | Extend the media-buy reported in `GetAdcpCapabilitiesResponse` | `from adcp.types import CapabilitiesMediaBuy` | `from adcp import MediaBuy` | -| Extend a `Deployment` (e.g. for `Signal.deployments`) | `from adcp.types import Deployment` (a structured union over the deployment shapes) | reaching into `generated_poc` for an internal numbered class | +| Extend a `Deployment` (e.g. for `Signal.deployments`) | `from adcp.types import Deployment` (a structured union over the deployment shapes) | reaching for an internal numbered class on a domain path | The canonical names (`Creative`, `Package`, `MediaBuy`, `Deployment`) remain available from both `adcp` and the partial modules — use those when the bare name already resolves to the variant you want. The prefixed aliases exist for the cases where it doesn't. @@ -313,7 +325,7 @@ class MyAudienceFilters(SomeLibraryFilters): excluded_countries: SchemaVariant[list[GeoCountry]] ``` -If a variant you need has neither a canonical nor a prefixed public alias, import it from its domain module (see [Every variant has a name](#every-variant-has-a-name)) and **open an issue** at [adcontextprotocol/adcp-client-python](https://github.com/adcontextprotocol/adcp-client-python/issues) asking for a semantic alias. Do not import the class from `adcp.types.generated_poc.*` as a workaround — those names renumber on schema regen, so the import is not stable. +If a variant you need has neither a canonical nor a prefixed public alias, import it from its domain module (see [Every variant has a name](#every-variant-has-a-name)) and **open an issue** at [adcontextprotocol/adcp-client-python](https://github.com/adcontextprotocol/adcp-client-python/issues) asking for a semantic alias. Do not pin a numbered class name (`Assets162`) as a workaround — codegen renumbers those on schema regen, so the import is not stable. `SchemaVariant[T]` collapses to `T` at runtime — Pydantic validates against the wrapped type unchanged. At type-check time the bundled mypy plugin (`adcp.types.mypy_plugin`) rewrites the annotation to `Any` so the LSP override check passes. **Adopters must enable the plugin in their mypy config** — add this line to `pyproject.toml`: @@ -326,7 +338,7 @@ Tradeoff: inside the override site, mypy sees the field as `Any`. If precise inf ### Tracking the spec-level fix -The cross-slice name collisions (the geo exclusion mirrors of the inclusion items, the capability-vs-response variants) are tracked upstream as a spec rename request: [adcontextprotocol/adcp#4347](https://github.com/adcontextprotocol/adcp/issues/4347). When the rename ships, some of these variants may merge — but the core principle (import the public alias that matches your intended response context, never the internal `generated_poc` class) is durable. +The cross-slice name collisions (the geo exclusion mirrors of the inclusion items, the capability-vs-response variants) are tracked upstream as a spec rename request: [adcontextprotocol/adcp#4347](https://github.com/adcontextprotocol/adcp/issues/4347). When the rename ships, some of these variants may merge — but the core principle (import the public alias that matches your intended response context, never an internal numbered class) is durable. ## Field-Level Exclusion with `Field(exclude=True)` — Recommended @@ -449,7 +461,7 @@ If your parent extends plain `pydantic.BaseModel` (not `AdCPBaseModel`), you mus > `@model_serializer` only when you need transformation logic, and file an issue at > [adcontextprotocol/adcp-client-python](https://github.com/adcontextprotocol/adcp-client-python/issues) > if the `MockValSer` error blocks you — this is an SDK-side build-ordering bug, not -> something to work around by importing from `generated_poc`. +> something to work around by importing an internal numbered class. ## Migrating from Manual `model_dump()` Dispatch Overrides diff --git a/docs/handler-authoring.md b/docs/handler-authoring.md index 385e66c31..2cac0ab56 100644 --- a/docs/handler-authoring.md +++ b/docs/handler-authoring.md @@ -234,9 +234,10 @@ For a narrower surface, six curated partial modules group types by domain: `adcp.types.media_buy`, `adcp.types.creative`, `adcp.types.signals`, `adcp.types.protocol`, `adcp.types.buyer`, and `adcp.types.seller` (e.g. `from adcp.types.buyer import GetProductsRequest`). Never import -from `adcp.types.generated_poc.*` or `adcp.types._generated` — those are -internal and their module paths and class names change on every schema -regeneration. +from `adcp.types._generated` — it binds a bare name several schemas +declare to one sort-order winner. `adcp.types.domains..` +is public (it is where codegen defines the class); what still changes on +regeneration is a numbered class NAME, which `adcp.types` aliases. **Validation errors surface as `INVALID_REQUEST`.** A Pydantic `ValidationError` at the boundary is converted to a structured AdCP @@ -1890,9 +1891,10 @@ Sellers typically need both. handler methods you override. - Don't reinvent `IdempotencyStore`, response builders, or error classification. Use the shipped helpers. -- Don't import from `adcp.types.generated_poc.*`. Everything public - lives at `adcp.types` or `adcp` — and the internal paths renumber - between releases (see `MIGRATION_v3_to_v4.md`). +- Reach for `adcp.types` or `adcp` first; `adcp.types.domains.*` is + public but is the address for a name the flat surface cannot bind. + Never import a numbered class name — those renumber between releases + (see `MIGRATION_v3_to_v4.md`). ## Troubleshooting diff --git a/docs/proposals/decisioning-platform-dispatch-design.md b/docs/proposals/decisioning-platform-dispatch-design.md index 24902fa9f..20f7362be 100644 --- a/docs/proposals/decisioning-platform-dispatch-design.md +++ b/docs/proposals/decisioning-platform-dispatch-design.md @@ -934,12 +934,12 @@ locked. | `AuthInfo` | `adcp.decisioning.context` | locked | | `WorkflowStep` | NEW in `adcp.decisioning.state` (framework-internal, not on the wire) | locked in foundation as a frozen `@dataclass` | | `WorkflowObjectType` | NEW in `adcp.decisioning.state` (framework-internal `Literal`) | locked in foundation | -| `Proposal` | `adcp.types.generated_poc.core.proposal` (already exists from spec codegen) | locked (generated) | +| `Proposal` | `adcp.types.domains.core.proposal` (already exists from spec codegen) | locked (generated) | | `GovernanceContextJWS` | NEW in `adcp.decisioning.state` (`NewType('GovernanceContextJWS', str)`) | locked in foundation | -| `PropertyList` | `adcp.types.generated_poc.core.property_list_ref` (re-export `PropertyListReference` + the resolved-list type) | locked (generated) | -| `CollectionList` | `adcp.types.generated_poc.collection.collection_list` (already exists) | locked (generated) | -| `Format` | `adcp.types.generated_poc.core.format` (already exists) | locked (generated) | -| `FormatReferenceStructuredObject` | `adcp.types.generated_poc.core.format_id` (already exists) | locked (generated) | +| `PropertyList` | `adcp.types.domains.core.property_list_ref` (re-export `PropertyListReference` + the resolved-list type) | locked (generated) | +| `CollectionList` | `adcp.types.domains.collection.collection_list` (already exists) | locked (generated) | +| `Format` | `adcp.types.domains.core.format` (already exists) | locked (generated) | +| `FormatReferenceStructuredObject` | `adcp.types.domains.core.format_id` (already exists) | locked (generated) | The framework-internal types (`WorkflowStep`, `WorkflowObjectType`, `GovernanceContextJWS`) ship as foundation-stable dataclasses / @@ -1530,7 +1530,7 @@ above for the full Protocol definitions and rationale. includes a per-type table: `Account`, `AuthInfo`, `Proposal`, `PropertyList`, `CollectionList`, `Format`, `FormatReferenceStructuredObject` are all already in - `adcp.types.generated_poc/`; `WorkflowStep`, `WorkflowObjectType`, + `adcp.types.domains/`; `WorkflowStep`, `WorkflowObjectType`, `GovernanceContextJWS` are framework-internal types defined fresh in `adcp.decisioning.state` and shipped foundation-stable. Adopter code that pattern-matches on these types doesn't refactor when v6.1 diff --git a/docs/testing-guide.md b/docs/testing-guide.md index dbd6d9ca0..f5dd22b7f 100644 --- a/docs/testing-guide.md +++ b/docs/testing-guide.md @@ -35,9 +35,9 @@ async def test_buyer_discovers_products(): ### ❌ DON'T ```python -# Don't import from internal modules +# Don't import from the internal consolidation, and never a numbered class from adcp.types._generated import Product1 # ❌ WRONG -from adcp.types.generated_poc.product import PublisherProperties4 # ❌ WRONG +from adcp.types.domains.core.product import PublisherProperties4 # ❌ WRONG (numbered) # Don't test Pydantic mechanics def test_discriminator_field_enforced(): # ❌ WRONG - testing Pydantic, not our code @@ -482,9 +482,9 @@ For parity, see JS `AgentClient.fromMCPClient()` (v5.19.0). ### ❌ Don't Import from Internal Modules ```python -# ❌ WRONG: Couples tests to internal implementation +# ❌ WRONG: Couples tests to a sort-order winner and a renumbered name from adcp.types._generated import Product1 -from adcp.types.generated_poc.product import PublisherProperties4 +from adcp.types.domains.core.product import PublisherProperties4 # ✅ CORRECT: Use public API from adcp import Product @@ -577,7 +577,7 @@ def test_semantic_alias_works_for_users(): ### ❌ DON'T -1. Import from `_generated` or `generated_poc` +1. Import from `_generated` or an internal numbered class 2. Test Pydantic internals 3. Test type identity (`assert X is Y`) 4. Test implementation details diff --git a/docs/type-surface.md b/docs/type-surface.md new file mode 100644 index 000000000..05b003710 --- /dev/null +++ b/docs/type-surface.md @@ -0,0 +1,345 @@ +# The type surface: what is where + +Every class in `adcp.types` is derived from one node of one pinned JSON Schema. This +document says where a class lives, which spelling to import it by, and what you can rely +on from a type before you know which tool produced it. + +For how to *extend* a generated type — which base to subclass, `Field(exclude=True)`, +`@model_serializer` — see [extending-types.md](extending-types.md). This document covers +layout and guarantees; that one covers subclassing. For how the tree is produced, see +[design/type-generation.md](design/type-generation.md). + +## Where to import from + +### The flat namespace + +```python +from adcp.types import CreateMediaBuyRequest, GetProductsResponse, ProtocolEnvelope +``` + +`adcp.types.__all__` carries 925 entries binding 923 distinct names. This is the spelling +to use, and it resolves for every name exactly one schema declares. + +### The domain tree, for names several schemas declare + +AdCP names an inline object after the property that holds it, so several schemas +legitimately declare a class called `Creative` or `QuerySummary`. The flat namespace binds +one of them per name; `adcp.types.domains` follows the bundle's own layout — 26 domain +roots, one module per schema file — so the import path says which one you mean: + +```python +from adcp.types.domains.creative.list_creatives_response import Creative +from adcp.types.domains.core.product import Product +``` + +[shared-type-names.md](shared-type-names.md) is the generated table of every such name and +the modules that declare it: 488 names across 1532 variants. Reach for the domain path when +that table lists your name, or when you want a spelling that cannot be repointed by a name +becoming ambiguous later. + +`adcp.types` also exports curated prefixed aliases for the variants adopters have needed — +`ListCreativesCreative`, `DeliveryCreative`, and the rest — and +[extending-types.md](extending-types.md) covers choosing between those and the domain path +when you are subclassing. This document does not repeat that guidance. + +### The curated topic modules + +```python +from adcp.types.protocol import ProtocolEnvelope, AdcpVersionEnvelope +from adcp.types.legacy import LegacyCreative +``` + +`adcp.types.core`, `creative`, `media_buy`, `signals`, `protocol` and `legacy` are +hand-picked partial views. They are stable, but they are narrower than the domain tree +and are not derived from the bundle, so a new schema does not appear in one until someone +adds it. + +### One tree: the definition site IS the public address + +`adcp.types.domains` is not a mirror of a private tree. It is where +`datamodel-code-generator` writes, so the module that DEFINES a class is the module an +adopter imports it from. There is no second tree, no copying step, and nothing that can +drift between them — `scripts/generate_types.py` points `--output` straight at +`src/adcp/types/domains/`, and the consolidation step writes only the namespaces derived +from it (`_generated.py`, each `/__init__.py`, `error_details.py`). + +Every other spelling resolves to the same class object. A public name is a re-export of +the class its generated module defines, never a second class built from the first: + +```python +from adcp.types import Product +from adcp.types.domains.core.product import Product as Defined + +assert Product is Defined +assert Product.__module__ == "adcp.types.domains.core.product" +``` + +`tests/test_export_surface_is_derived.py` holds this across the surface: every public name +must be identical to the attribute of the module that defines it, and every generated +public class must be reachable under some public name. A runtime copy cannot pass the +identity check, because `create_model` stamps the calling module onto what it builds. + +## What is deliberately not public + +`adcp.types._generated` is internal. It binds a bare type name that several generated +modules define to a single winner, chosen by module sort order — so a schema addition can +repoint a name an adopter already imports, and the only trace is a line in a regenerated +file. A domain path cannot move that way: it names the declaring schema. + +What a domain path does not protect you from is a NUMBERED class name. Codegen numbers +anonymous variant classes by traversal order, so `Assets162` can name a different shape +after the next regeneration whatever path you reach it by. `aliases.py` exists to give +those a stable spelling, and `adcp.types` is where you import it from. + +That is not hypothetical. `_generated` rebinds the whole `AuthorizedAgents*` window at +runtime — `AuthorizedAgents = AuthorizedAgents1`, `AuthorizedAgents1 = AuthorizedAgents2`, +and so on — to preserve historical variant numbering, while mypy keeps the pre-rebind +declarations. Aliasing the shifted names therefore bound each of the six authorization +aliases to the runtime class it names but to the *static type of its neighbour*, which made +every documented constructor call fail `mypy --strict`. `aliases.py` now imports the raw +variants to keep the runtime object and the static type identical, and records the whole +episode at the import. + +The ban is mechanical. `tests/test_import_layering.py` walks the AST of every non-facade +module and fails on an import whose module starts with the one forbidden prefix: + +```python +_FORBIDDEN_PREFIXES = ("adcp.types._generated",) +``` + +`ast.walk` sees `if TYPE_CHECKING:` blocks and function-local imports too, so no spelling +slips past it. `test_generated_tree_is_not_forbidden` in the same file asserts the tuple +stays that short: re-adding `adcp.types.domains` would forbid the definition site. + +## `AdcpRequest` and `AdcpResponse`: what a message is, before you know the tool + +```python +from adcp.types.base import AdcpRequest, AdcpResponse +``` + +Every request and response message of every task in the pinned bundle's task registry +descends from one of these two — 154 of 154 messages, over 77 tasks. They are the answer to +"is this an AdCP request?", which no type in this SDK used to be able to give: the name that +should mean it, `adcp.types.Request`, resolves to a body fragment of one tool. + +They are plain classes. They are not `BaseModel` subclasses, they declare no fields, and +that is deliberate: a field on the marker would put `adcp_version` on +`ValidateInputRequest`, whose schema declares no version fields, and inventing a spec field +to satisfy a type is worse than the gap it fills. So `AdcpRequest` cannot be instantiated or +validated against directly — it is an interface. Every class carrying it is a pydantic model +by construction of the pass that inserts it. + +### What you can do with one + +| Method | On | Returns | +|---|---|---| +| `get_account()` | `AdcpRequest` | the account reference this request names, or `None` | +| `get_idempotency_key()` | `AdcpRequest` | the at-most-once key, or `None` | +| `get_context()` | both | the buyer's context object, or `None` | +| `get_push_notification_config()` | `AdcpRequest` | the webhook config, or `None` | +| `get_adcp_version()` / `get_adcp_major_version()` | `AdcpRequest` | the version pins, or `None` | +| `get_status()` | `AdcpResponse` | the task status | +| `get_task_id()` | `AdcpResponse` | the async task id, or `None` | +| `get_adcp_error()` | `AdcpResponse` | a typed `Error`, or `None` | +| `get_message()` / `get_replayed()` | `AdcpResponse` | the envelope's message and replay marker | + +`None` means *this tool's schema declares no such field*, not *the field was empty*. Of the +87 request schemas, 80 declare `context`, 49 declare `account`, 43 declare +`idempotency_key` and 19 declare `push_notification_config` — the axes are genuinely +ragged, so an accessor that answers `None` is answering correctly. + +That is enough to write a transport boundary once, for all 77 tools: validate, resolve the +account, decide at-most-once, negotiate the version, echo the context. + +### Using descent as a refusal + +If you maintain a tool registry and want to refuse a hand-written model that merely looks +like a spec type, use **both** predicates: + +```python +from adcp.types import AdcpVersionEnvelope +from adcp.types.base import AdcpRequest + +if not (issubclass(model, AdcpRequest) and issubclass(model, AdcpVersionEnvelope)): + raise TypeError("a tool request model must be an SDK task-request type") +``` + +Neither alone is sufficient, and this was settled by building the forgeries rather than +reasoning about them. `AdcpRequest` is field-less, so inheriting it grants nothing — a class +can declare it as a base and invent whatever fields it likes. `AdcpVersionEnvelope` refuses +that, because inheriting it is what supplies the two spec fields; but it accepts a model +that composes the envelope without being any task's message. The conjunction refuses all +three forgeries; either one alone refuses two. + +One limit worth knowing: descent from `AdcpRequest` means the generation pass put the class +in a task's request position. It does that for all 77, and a forger can also do it for one. +The conjunction narrows that; it does not close it. + +## Envelope fields and payload fields + +The spec settles that these are not containers. `core/version-envelope.json` says the +envelope "is part of the payload itself", and `core/protocol-envelope.json` says `payload` +is "a documentary construct — NOT a required wire field": on MCP and REST, envelope fields +and body fields are siblings at the root. So fields stay flat, exactly as on the wire, and +which stratum a field belongs to is answered by **which ancestor declared it**: + +```python +wire = response.model_dump() +envelope = {k: wire[k] for k in response.protocol_fields() if k in wire} +body = {k: wire[k] for k in response.payload_fields() if k in wire} +``` + +Three classifiers, derived once from the MRO — there is no per-class table to drift: + +- `version_fields()` — the intersection with `AdcpVersionEnvelope.model_fields`, empty when + the class has no envelope ancestry. +- `protocol_fields()` — the intersection with `ProtocolEnvelope.model_fields`. +- `payload_fields()` — `model_fields` minus the other two. + +They partition `model_fields`, which is pinned by a test. The tie-breaks follow the +envelopes' own text: `context` is protocol, because the spec says the envelope declaration +is authoritative where a body-level mirror also exists; a `status` narrowed to a `Literal` +on a payload arm is still protocol; `errors` is payload and `adcp_error` protocol, because +the spec says the two must be treated as distinct fields by name. `adcp_version` reports as +version rather than payload even though the wire calls it payload, so that a transport +composing a `DataPart` or an audit record gets tool arguments only — union the two sets if +you want payload-including-version. + +### Where the ancestry is incomplete + +| | coverage over 77 tasks | +|---|---| +| requests typed `AdcpRequest` | 77 / 77 | +| responses typed `AdcpResponse` | 77 / 77 | +| requests with `AdcpVersionEnvelope` | 76 / 77 | +| responses with `AdcpVersionEnvelope` | 70 / 77 | +| responses with `ProtocolEnvelope` | 67 / 77 | + +Counted per arm: a union-rooted message counts only when *every* class it can validate into +has the ancestry, because an arm is what a buyer actually receives. + +The residues are the spec's, not the SDK's, and they are visible rather than papered over. +`creative/validate-input-request.json` declares no version fields at all, so the SDK does +not invent them; its `version_fields()` is empty while `get_adcp_version()` still answers, +because the accessor reads the instance, not the MRO. The five compact media-buy responses +compose an envelope-free shared response, and the two proposals responses carry a submitted +arm that declares no version fields. Ten task responses do not compose the protocol +envelope despite its own "REQUIRED on every task response envelope" claim. + +Each residue is pinned by equality in the SDK's guard, so a schema that gains the ancestry +upstream fails the guard until its pin is removed — the state cannot silently go stale in +either direction. `scripts/task_message_lattice.py` prints the whole table and names every +residue schema; run it against a checkout or an installed wheel. + +## Response variations + +A task response whose schema root is a union generates one class per arm and a plain +`TypeAlias` over them. There is no `RootModel` and no `.root`: + +```python +CreateMediaBuyResponse: TypeAlias = ( + CreateMediaBuyResponse1 | CreateMediaBuyResponse2 | CreateMediaBuyResponse3 +) +``` + +Dispatch by `isinstance` or structural pattern matching. mypy narrows a closed `TypeAlias` +union through `match`, so the handling is exhaustive: + +```python +match response: + case CreateMediaBuyResponse1() as ok: + persist(ok.media_buy_id, ok.packages) + case CreateMediaBuyResponse3() as submitted: + schedule_poll(submitted.task_id) + case CreateMediaBuyResponse2() as err: + raise BuyRejected(err.errors) +``` + +To validate a union root from raw bytes, use `TypeAdapter` on the alias — the alias is not +a class, so it has no `model_validate`: + +```python +from pydantic import TypeAdapter +response = TypeAdapter(CreateMediaBuyResponse).validate_python(raw) +``` + +Validation resolves arms left to right. Where the schema gives an arm a discriminating +`const`, that becomes a defaulted `Literal` field and pydantic takes it as a fast path. But +most arms have no such tag: of the 58 numbered arm classes across the 22 union-rooted +response modules, 47 declare no `status` field of their own and are distinguished by their +required sets instead — `create-media-buy-response.json`'s error arm is identified by +requiring `errors` and nothing else. A synthetic discriminator field would make every union +pydantic-discriminated, and is refused: it would be either a non-spec field on the wire or a +serialization-excluded phantom that still shows up in `model_fields`, the extra policy and +the idempotency hash. + +### The numbered names, and what replaces them + +`CreateMediaBuyResponse1` is a positional name, and a positional name's meaning can change +without the thing it names changing: insert an arm into a `oneOf` upstream and +`CreateMediaBuyResponse2` becomes a different arm. + +The schema already carries better names — `create-media-buy-response.json` titles its arms +`CreateMediaBuySuccess`, `CreateMediaBuyError` and `CreateMediaBuySubmitted` — and +`aliases.py` hand-transcribes most of them back today: + +```python +from adcp.types import CreateMediaBuySuccessResponse # == CreateMediaBuyResponse1 +``` + +A rule that derives each arm's name from its own schema node now exists in the repository +and is graded, and the old-to-new table for all 58 numbered arm classes is derivable from +it. **The renames are not applied yet**, and 38 numeric-suffixed names are still on the flat +surface. Until they are, prefer the semantic alias over the numbered class where one exists, +and expect the numbered names to be removed in a future major with a deprecation shim and a +migration table. Writing new code against `CreateMediaBuyResponse2` is writing against the +drift channel. + +See [design/type-generation.md](design/type-generation.md) for the rule itself and what +remains to wire it. + +## Scalars, unions and composition, in one paragraph each + +These are the shapes a consumer meets most often. [extending-types.md](extending-types.md) +covers each in full, including the subclassing rules. + +- **A scalar schema root is a plain scalar.** `PropertyTag` is a `str` subclass carrying the + schema's constraints, not a `RootModel` wrapper. `tag == "sports"` is `True`. +- **An object-union root is a type alias.** Construct the arm you mean; use `TypeAdapter` + when the arm is not known in advance. A root that composes rather than naming a single + scalar keeps its `RootModel`; `isinstance(x, RootModel)` distinguishes them. +- **A root `allOf` plus `$ref` is inheritance.** `GetProductsRequest` *is* an + `AdcpVersionEnvelope`; the envelope's fields are not copied onto it. So an `isinstance` + check or a generic boundary can bind to the composed base. +- **A canonical creative model is a subclass of the generated class it refines**, not a copy + of its fields. Every validator the pipeline injects into the generated class therefore + applies to the public one, and mypy sees every inherited field. One consequence to know: + a public canonical model refuses what its generated source refuses, so + `CreativeManifest` requires one of `format_id` or `format_kind`. + +## What this surface refuses to do + +Each of these has been proposed, and the reason it was refused is the reason not to propose +it again. + +- **No post-hoc `__all__` deduplication.** Duplicate entries are a symptom of an emitter + that appends instead of unioning. De-duplicating downstream preserves the defect and + hides its signal; the emitter should write sets instead. Two duplicates remain today, + `WholesaleFeedEvent` and `WholesaleFeedWebhook`, and they are the emitter's to fix. +- **No second mechanism to reach the public classes.** When an injected validator failed to + reach the public canonical models, the fix was to delete the runtime copy so inheritance + carries it — not to carry validators onto the copies as well. Doubling the mechanism that + caused a gap means every future validator needs the second carry, and the day one is + forgotten the gap reopens silently. +- **No derived type stub.** A `.pyi` generated from `model_fields` repairs a ledger instead + of deleting it: two artifacts, one derived from the other, drifting between + regenerations. Real source makes the stub unnecessary, and the stub that existed hid 715 + inherited fields across 31 classes, required four of them, and invented one. +- **No permanent numbered aliases.** One major of deprecation shim for the positional + names, then they are gone. A positional name kept alive indefinitely is the drift channel + reopened. +- **No generator version bump.** The generator is pinned, and the pin is a constraint the + whole design is built around rather than a task nobody got to. Anything the surface needs + is reachable through a schema transform, a post-generation pass, or hand-written source + that inherits from generated output. [design/type-generation.md](design/type-generation.md) + has the measurements. diff --git a/docs/types-9-migration.md b/docs/types-9-migration.md index 8c17cd930..cfc61bd00 100644 --- a/docs/types-9-migration.md +++ b/docs/types-9-migration.md @@ -12,11 +12,13 @@ pass. The pull requests are linked for the full rationale and measurements. | Structural pointer refs resolve to the type they select | #1371 | 47 per-position `RootModel` wrapper names under `adcp.types._generated` | | Generated models validate `boolean`/`integer`/`number` strictly | #1375 | Payloads that relied on `"yes"`, `"1"`, `1` coercion | | Root-level `anyOf`/`oneOf` required groups are enforced | #1368 | Documents that omit every required group of 42 request/response models, on generated and canonical names alike | +| `format_kind` is a `str`, not the closed enum, and no model refuses a value | [adcp#7929](https://github.com/adcontextprotocol/adcp/issues/7929) | `x.format_kind is CanonicalFormatKind.y` identity comparisons; any model refusing a kind this SDK version does not know | Nothing is removed from `adcp` or `adcp.types`: every name importable before -is importable after, and the ten additions (`Issue`, `AdcpVersionEnvelope`, -seven `*Details` error models and `FormatReferenceStructuredObject` under -`adcp.types.legacy`) come with #1367. Identity changes on the public surface +is importable after. The additions are `Issue`, `AdcpVersionEnvelope`, seven +`*Details` error models and `FormatReferenceStructuredObject` under +`adcp.types.legacy` with #1367, plus `is_canonical_format_kind` with the +`format_kind` override below. Identity changes on the public surface are of exactly the kinds the table names and are pinned by `tests/fixtures/public_api_snapshot.json`. @@ -101,9 +103,9 @@ The bundled schema validator additionally checks `format: uri` and 42 models enforce the root-level `anyOf`/`oneOf` their schema declares. `CreateMediaBuyRequest` needs packages, a budget, or a proposal; a document with none raises `ValidationError` naming the groups. The rule reaches the -canonical names in `adcp.types` too: `_canonical_clone` carries every validator -a generated class declares on itself, so `adcp.CreateMediaBuyRequest` and the -generated class agree. +canonical names in `adcp.types` too: a canonical model is a subclass of the +generated class, so it inherits every validator that class declares and +`adcp.CreateMediaBuyRequest` and the generated class agree. ```python CreateMediaBuyRequest.model_validate({**unconditional_fields}) @@ -114,22 +116,175 @@ CreateMediaBuyRequest.model_validate({**unconditional_fields}) Presence is what counts: an explicit `null` satisfies a group, a default the caller never sent does not. +## 6. `format_kind` is a `str`, not an enum — a deliberate override (#7929) + +`core/canonical-format-kind.json` declares a **closed** 16-member `enum` and, +in the same file, states as normative: + +> Consumer SDKs MUST treat this enum as **open** at parse time: an unknown +> `format_kind` value MUST be retained as-is on the in-memory object (not +> silently dropped or rewritten to `"custom"`) and MUST NOT cause the +> surrounding payload to fail validation. ... The producer-side enum stays +> closed ...; the consumer-side enum stays open for forward compatibility. + +The schema knows the rule is directional and then encodes it as one closed +enum, which cannot carry that. A generated model that reproduces an incoherent +schema faithfully does not inherit correctness from it — it propagates the +incoherence into every consumer. So 9.0 does not reproduce it. + +**What changed.** Every reference to that schema generates a bare `str`: + +```python +# before +CreativeManifest.model_fields["format_kind"].annotation # CanonicalFormatKind | None +# after +CreativeManifest.model_fields["format_kind"].annotation # str | None +``` + +`adcp.types.CanonicalFormatKind` is unchanged and still has its sixteen +members — it is the vocabulary, and nothing was removed from the public +surface. What changed is that it no longer types a field, so a value read off +a model is the string the seller sent rather than an enum member: + +```python +manifest.format_kind == CanonicalFormatKind.image # True, as before +manifest.format_kind is CanonicalFormatKind.image # now False +``` + +If you compared with `is`, compare with `==`. `CanonicalFormatKind` is a +`StrEnum`, so `==` holds against the member and against the plain string. + +**No model refuses a value, in either direction.** There is one type, one +field and one behaviour: `format_kind: str`, retained as sent, on +`CreativeManifest` and `CreativeAsset` as much as on `Creative` and +`DeliveryCreative`. If you were relying on a request model raising +`ValidationError` for a kind outside the sixteen, it no longer does. + +That is deliberate. A seller supports some set of format kinds, and that set +is the seller's — not this library's and not the pinned enum's. It can be +larger than the sixteen (a kind promoted in a spec newer than your pin) or +smaller (four of them). A pinned SDK cannot tell "a kind the seller invented" +from "a kind defined after my pin", so refusing the second to prevent the +first would make the SDK's version a ceiling on what the protocol permits. +"I accept your request and then tell you I cannot process this creative" is a +seller's answer, not a type error. The producer-side `MUST NOT mint ad-hoc +values` is a seller's obligation, and this library gives it the vocabulary and +the helper to meet it. + +**Checking the vocabulary yourself** is therefore the sanctioned way to be +strict, and the SDK never does it for you: + +```python +from adcp.types import CanonicalFormatKind, is_canonical_format_kind + +if not is_canonical_format_kind(creative.format_kind): + route_as_declared_but_unsupported(creative) + +# the vocabulary is a parameter, because only you know which version your +# counterpart speaks, and your support list may be larger or smaller +if not is_canonical_format_kind(manifest.format_kind, MY_SUPPORTED_KINDS): + reject_with_unsupported_format(manifest) +``` + +**What this replaced.** Five pieces of scaffolding existed only to reconcile +the closed enum with the open requirement, and all five are gone: the +`_OpenCanonicalFormatKind` alias in `adcp.types.canonical_creative`, a second +one in `adcp.types._forward_compat`, the `preserve_open_delivery_format_kind` +post-generation fix that hand-patched one generated field, +`_revalidate_subclass_instances_of_strict_base`, which flipped the wire bases +to `revalidate_instances="subclass-instances"` so a subclass instance could +not pass as validated output, and `_StrictFormatKind`, the per-direction base +that refused a non-canonical kind on a request. With one open type everywhere, +none of them has anything to do. + +**The cost, stated.** The generated models no longer agree with the bundled +schema for this one field, and #1375 established that they should. That is +declared as a single named entry with its reason in +`tests/conformance/_schema_parity.py` — +`format_kind_is_an_open_vocabulary_by_decision` — and the parity rule itself is +not relaxed: the entry is deleted automatically by +`test_every_declared_divergence_still_occurs` once it stops matching. + +The upstream ask is +[adcontextprotocol/adcp#7929](https://github.com/adcontextprotocol/adcp/issues/7929): +govern `format_kind` with a versioned registry, the way its siblings +`format_shape` and `asset_group_id` already are. If it lands, the override is +deleted and the generated models go back to agreeing with their schema. + ## Also in this batch (not breaking) * `adcp.types.domains.[.]` and `adcp.types.error_details` (#1367) give every generated class a public path, and the public API snapshot records what each name resolves to. + + The generator writes there directly now, so the private + `adcp.types.generated_poc` tree is gone. Almost every module stem moved + unchanged, which makes the migration a prefix rename: + + ```python + -from adcp.types.generated_poc.media_buy.package_request import PackageRequest + +from adcp.types.domains.media_buy.package_request import PackageRequest + ``` + + `adcp migrate v3-to-v4` rewrites those lines. The old path also still + resolves through the whole 9.x line, emitting a `DeprecationWarning` that + names the new one — #1360 measured 110 such imports in a single production + seller, and 9.0 does not break all of them at once. **It is removed in + v10.** What the old path returns is the *same module object*, so + `adcp.types.generated_poc.core.format_id.FormatReferenceStructuredObject is + adcp.types.domains.core.format_id.FormatReferenceStructuredObject` — an + `isinstance` check cannot start failing because a class was reached by its + old name. Prefer the flat `adcp.types` surface where the name you need is + bound there, as `docs/type-surface.md` describes. + + **One stem split in two, and it is the one exception to the prefix rename.** + `brand.json` shares its basename with the `brand/*.json` task schemas, so the + old `generated_poc.brand` was that discovery schema — `Brand`, + `BrandDiscovery*`, `LocalizedName` and 137 more — while the new + `domains.brand` is the generated domain aggregator over + `domains/brand/.py`. The discovery schema's classes are at + `adcp.types.domains.brand_discovery`: + + ```python + -from adcp.types.generated_poc.brand import Brand, LocalizedName + +from adcp.types.domains.brand_discovery import Brand, LocalizedName + ``` + + The deprecated name keeps serving both halves for the 9.x line, so an + unmigrated `from adcp.types.generated_poc.brand import Brand` still works and + still returns the canonical class. It is the one deprecated name that is a + compatibility module rather than the canonical module itself, so `is` against + `adcp.types.domains.brand` is False for it where every other deprecated name + compares True. Class identity is unaffected, which is what `isinstance` + reads. * `scripts/generate_types.py --check` runs in CI, grades every post-generation fix against `scripts/post_generation_manifest.json`, and the generator's input order is total, so a regeneration is byte-identical on every filesystem (#1374). -* `canonical_creative.pyi` is derived from the runtime models (#1366): every - field the canonical models carry is declared, and the synthesized - constructors demand exactly the fields the runtime demands. A program that - type-checked against the old stub while constructing `CreateMediaBuyRequest` - without `brand`, `start_time`, `end_time` or `idempotency_key` now fails - mypy, as it always failed at runtime. Enum-typed parameters keep accepting - the wire string. +* `canonical_creative.pyi` is gone (#1366). Every canonical model is now a real + subclass of the generated wire model it refines, so there is nothing left to + declare by hand: mypy reads `canonical_creative.py` and sees the actual + inherited fields. The stub is not regenerated — it was deleted, because the + thing it was transcribing is ordinary source. A program that type-checked + against the old stub while constructing `CreateMediaBuyRequest` without + `brand`, `start_time`, `end_time` or `idempotency_key` now fails mypy, as it + always failed at runtime. + + One static-typing consequence comes with that. The runtime still coerces the + wire string for an enum-typed parameter — `PackageRequest(product_id="p1", + pricing_option_id="po1", pacing="even")` returns `Pacing.even` — but mypy and + pyright now read the generated `Pacing | None` and reject the `str`. An + adopter constructing a canonical model directly passes the enum member + (`pacing=Pacing.even`) or goes through `model_validate`, which takes the wire + document unchanged. This is not a regression against the hand-written stub: + that stub did not declare `pacing` at all, so the same call was refused there + too, as an unexpected keyword rather than a wrong type. + + The legacy-identity fields the canonical models remove — `format_id`, + `format_ids`, `format_ids_pending`, `format_ids_to_provide` — are declared in + a `TYPE_CHECKING` block with `init=False`, so a type checker refuses the + keyword the runtime refuses instead of offering a constructor argument that + raises `ValidationError`. Read them and you get `None`. * A format reference's `agent_url` is carried as the wire string, validated as a URL (#1384). `ref.agent_url` is a `str`, not an `AnyUrl`, so `migrated_…` option IDs derived from a model match those derived from the diff --git a/examples/v3_reference_seller/MIGRATION.md b/examples/v3_reference_seller/MIGRATION.md index fa988eb2a..5b3d4dcb6 100644 --- a/examples/v3_reference_seller/MIGRATION.md +++ b/examples/v3_reference_seller/MIGRATION.md @@ -201,7 +201,7 @@ projection — don't relax validation. The spec shape is the contract. ### Spec error codes — what to use The canonical enum ships at -[`src/adcp/types/generated_poc/enums/error_code.py`](../../src/adcp/types/generated_poc/enums/error_code.py). +[`src/adcp/types/domains/enums/error_code.py`](../../src/adcp/types/domains/enums/error_code.py). Common codes the translator emits: | Code | When to use | `recovery` | diff --git a/llms.txt b/llms.txt index a4089bbea..30b973c3a 100644 --- a/llms.txt +++ b/llms.txt @@ -66,8 +66,10 @@ from adcp.server.responses import products_response # Response builders ``` Which import path: default to `from adcp import X` (stable common surface). Use `adcp.types.` for a curated domain grouping, or `adcp.types` for -the full surface / rarely-used types. NEVER import `adcp.types.generated_poc.*` -or `adcp.types._generated` — they renumber on every schema regen. +the full surface / rarely-used types. `adcp.types.domains..` +is public and is where codegen defines every class — use it for a name the flat +surface cannot bind. NEVER import `adcp.types._generated`, and never pin a +numbered class name (`Assets162`) — those renumber on every schema regen. `import adcp` is lightweight; the pydantic type graph builds on first type use. ## Validation @@ -78,7 +80,7 @@ mypy src/adcp/ # Type checker ``` ## Do NOT read (generated, wastes context) -- `src/adcp/types/generated_poc/` - auto-generated from schemas +- `src/adcp/types/domains/` - auto-generated from schemas (public, but 1000+ files) - `src/adcp/types/_generated.py` - consolidated generated exports - `src/adcp/types/_ergonomic.py` - auto-generated coercion - `schemas/` - raw JSON schemas diff --git a/pyproject.toml b/pyproject.toml index 1fa0dfdf8..754b58497 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -258,13 +258,13 @@ adcp = [ [tool.black] line-length = 100 target-version = ["py310", "py311", "py312"] -# Skip auto-generated files. Running black on generated_poc/ during +# Skip auto-generated files. Running black on types/domains/ during # pre-commit after `make regenerate-schemas` causes a stash conflict # (hook reformats, tries to re-apply over seller's already-modified # files, rolls back). The regen script already runs black on the -# consolidated _generated.py; individual generated_poc/ files don't +# consolidated _generated.py; individual generated files don't # need formatting at commit time. Mirrors the ruff extend-exclude. -extend-exclude = "/(tasks|_generated|registry)\\.py$|/types/generated_poc/|/types/v3[012]\\.pyi$|/types/versioned_bases/_?v3[012]\\.(py|pyi)$|/reporting/_preview/_generated_models/" +extend-exclude = "/(tasks|_generated|registry)\\.py$|/types/domains/|/types/v3[012]\\.pyi$|/types/versioned_bases/_?v3[012]\\.(py|pyi)$|/reporting/_preview/_generated_models/" [tool.ruff] line-length = 100 @@ -276,7 +276,7 @@ extend-exclude = [ "src/adcp/reporting/_preview/_generated_models/", "src/adcp/types/registry.py", "src/adcp/types/tasks.py", - "src/adcp/types/generated_poc/", + "src/adcp/types/domains/", "src/adcp/types/v30.pyi", "src/adcp/types/v31.pyi", "src/adcp/types/v32.pyi", @@ -312,13 +312,13 @@ module = "tests.*" disable_error_code = ["import-not-found", "no-untyped-def", "var-annotated", "operator"] # Generated preview models for the unreleased sync_reporting_status schemas. -# Same rationale as generated_poc: codegen output, replaced wholesale at rc.2. +# Same rationale as types/domains: codegen output, replaced wholesale at rc.2. [[tool.mypy.overrides]] module = "adcp.reporting._preview._generated_models.*" disable_error_code = ["valid-type", "assignment", "unused-ignore"] [[tool.mypy.overrides]] -module = "adcp.types.generated_poc.*" +module = "adcp.types.domains.*" # JSON Schema allOf/const intersections intentionally narrow inherited Pydantic # fields in generated models. Mypy treats those schema-valid refinements as # mutable Python attribute overrides; keep assignment checking enabled for the @@ -366,8 +366,8 @@ omit = [ "*/tests/*", "*/test_*.py", "src/adcp/types/_generated.py", - "src/adcp/types/generated_poc/*", - "src/adcp/types/generated_poc/**/*", + "src/adcp/types/domains/*", + "src/adcp/types/domains/**/*", ] branch = true diff --git a/scripts/consolidate_exports.py b/scripts/consolidate_exports.py index b2083c348..fddc793c5 100644 --- a/scripts/consolidate_exports.py +++ b/scripts/consolidate_exports.py @@ -1,17 +1,21 @@ #!/usr/bin/env python3 """ -Create the consolidated export files that re-export all types from generated_poc modules. +Create the consolidated export files derived from the generated domain tree. -This script analyzes all modules in generated_poc/ and writes three modules: +``adcp/types/domains/`` is where datamodel-code-generator defines the classes, so +this script reads the public tree and writes the namespaces derived from it: * ``_generated.py`` — every public generated type in one namespace. A bare type name defined by several generated modules resolves to one winner here. -* ``domains/.py`` — one module per schema domain, re-exporting what that - domain declares, derived from the module tree. No class is reachable under zero - names. +* ``domains//__init__.py`` — the domain root, binding the names that + domain declares exactly once. No class is reachable under zero names. +* ``domains/__init__.py`` — the ``DOMAINS`` roster. * ``error_details.py`` — the ``error-details/*.json`` model family plus the transitive closure of its field types, derived from the generated package. +It writes no per-schema module: the generated module for a schema IS its public +module, so there is nothing to mirror. + Two build guards replace the former checked-in collision allowlist: the consolidate step fails when a generated public class is reachable under no name, and when two (name, module) pairs want the same qualified name. Both are properties of the tree, @@ -37,9 +41,8 @@ from pathlib import Path from typing import get_args -GENERATED_POC_DIR = Path(__file__).parent.parent / "src" / "adcp" / "types" / "generated_poc" -OUTPUT_FILE = Path(__file__).parent.parent / "src" / "adcp" / "types" / "_generated.py" DOMAINS_DIR = Path(__file__).parent.parent / "src" / "adcp" / "types" / "domains" +OUTPUT_FILE = Path(__file__).parent.parent / "src" / "adcp" / "types" / "_generated.py" COLLISION_REPORT_FILE = Path(__file__).parent.parent / "docs" / "shared-type-names.md" ERROR_DETAILS_FILE = Path(__file__).parent.parent / "src" / "adcp" / "types" / "error_details.py" @@ -222,7 +225,7 @@ def exports_for_public_consolidation(module_path: Path) -> set[str]: source models. Re-exporting every nested helper makes those copies shadow the canonical module with the same wire type. """ - rel_path = module_path.relative_to(GENERATED_POC_DIR) + rel_path = module_path.relative_to(DOMAINS_DIR) if rel_path == Path("brand_discovery.py"): return set() if rel_path.parts[:2] == ("core", "async_response_refs"): @@ -290,7 +293,7 @@ def is_public_generated_model(cls: type) -> bool: that checks it both have to agree on that, which is why the rule is here. """ return ( - cls.__module__.startswith("adcp.types.generated_poc.") + cls.__module__.startswith("adcp.types.domains.") and not cls.__name__.startswith("_") and (hasattr(cls, "model_fields") or issubclass(cls, Enum)) ) @@ -382,8 +385,8 @@ def _enforce_every_class_is_reachable(name_to_modules: dict[str, set[str]]) -> N only what its domain declares once. """ mirrored = { - ".".join([*rel.parts[:-1], rel.stem]): extract_exports_from_module(GENERATED_POC_DIR / rel) - for rel in _mirror_relative_paths() + ".".join([*rel.parts[:-1], rel.stem]): extract_exports_from_module(DOMAINS_DIR / rel) + for rel in _schema_module_paths() } unreachable = [ (module_name, type_name) @@ -409,23 +412,23 @@ def _scan_name_to_modules() -> dict[str, set[str]]: """Map every public name to the set of non-bundled modules that define it.""" def _module_sort_key(p: Path) -> tuple[int, int, str]: - rel = p.relative_to(GENERATED_POC_DIR) + rel = p.relative_to(DOMAINS_DIR) is_enum = rel.parts[0] == "enums" if len(rel.parts) > 1 else False is_bundled = rel.parts[0] == "bundled" if len(rel.parts) > 1 else False return (0 if is_enum else 1, 1 if is_bundled else 0, str(p)) - modules = sorted(GENERATED_POC_DIR.rglob("*.py"), key=_module_sort_key) + modules = sorted(DOMAINS_DIR.rglob("*.py"), key=_module_sort_key) modules = [ m for m in modules if m.stem != "__init__" and not m.stem.startswith(".") - and m.relative_to(GENERATED_POC_DIR).parts[0] != "bundled" + and m.relative_to(DOMAINS_DIR).parts[0] != "bundled" ] name_to_modules: dict[str, set[str]] = {} for module_path in modules: - rel_path = module_path.relative_to(GENERATED_POC_DIR) + rel_path = module_path.relative_to(DOMAINS_DIR) module_name = ".".join(list(rel_path.parts[:-1]) + [rel_path.stem]) for export_name in exports_for_public_consolidation(module_path): name_to_modules.setdefault(export_name, set()).add(module_name) @@ -441,12 +444,12 @@ def generate_consolidated_exports() -> str: # as renumbered/enum duplicates — we want the canonical class definitions # from non-bundled to win the first-seen dedup. def _module_sort_key(p: Path) -> tuple[int, int, str]: - rel = p.relative_to(GENERATED_POC_DIR) + rel = p.relative_to(DOMAINS_DIR) is_enum = rel.parts[0] == "enums" if len(rel.parts) > 1 else False is_bundled = rel.parts[0] == "bundled" if len(rel.parts) > 1 else False return (0 if is_enum else 1, 1 if is_bundled else 0, str(p)) - modules = sorted(GENERATED_POC_DIR.rglob("*.py"), key=_module_sort_key) + modules = sorted(DOMAINS_DIR.rglob("*.py"), key=_module_sort_key) modules = [ m for m in modules @@ -456,7 +459,7 @@ def _module_sort_key(p: Path) -> tuple[int, int, str]: # can contain enormous inline unions that Pydantic refuses to build # when imported eagerly through _generated. Keep the files on disk, # but do not re-export bundled copies as public SDK types. - and m.relative_to(GENERATED_POC_DIR).parts[0] != "bundled" + and m.relative_to(DOMAINS_DIR).parts[0] != "bundled" ] print(f"Found {len(modules)} modules to consolidate") @@ -491,7 +494,7 @@ def _stem_matches_export(module_stem: str, export_name: str) -> bool: # elsewhere (bundled copies, enum aliases in unrelated files). module_exports: dict[str, set[str]] = {} for module_path in modules: - rel_path = module_path.relative_to(GENERATED_POC_DIR) + rel_path = module_path.relative_to(DOMAINS_DIR) module_parts = list(rel_path.parts[:-1]) + [rel_path.stem] module_name = ".".join(module_parts) display_name = rel_path.stem @@ -569,8 +572,7 @@ def _stem_matches_export(module_stem: str, export_name: str) -> bool: prefix = "" qualified_name = f"_{type_name}From{prefix}{stem}" import_str = ( - f"from adcp.types.generated_poc.{module_name}" - f" import {type_name} as {qualified_name}" + f"from adcp.types.domains.{module_name}" f" import {type_name} as {qualified_name}" ) special_imports.append(import_str) all_exports.add(qualified_name) @@ -709,9 +711,7 @@ def _stem_matches_export(module_stem: str, export_name: str) -> bool: imported.append(f"{export_name} as {private_name[export_name]}") else: imported.append(export_name) - import_lines.append( - f"from adcp.types.generated_poc.{module_name} import {', '.join(imported)}" - ) + import_lines.append(f"from adcp.types.domains.{module_name} import {', '.join(imported)}") all_exports_with_aliases = all_exports | set(aliases) @@ -734,7 +734,7 @@ def _stem_matches_export(module_stem: str, export_name: str) -> bool: "DO NOT import from this module directly.", "Use 'from adcp import Type' or 'from adcp.types import Type' instead.", "", - "This module consolidates all generated types from generated_poc/ into a single", + "This module consolidates all generated types from adcp.types.domains into a", "namespace for convenience. The leading underscore signals this is private API.", "", "A bare type name that several generated modules define resolves to one class", @@ -750,7 +750,7 @@ def _stem_matches_export(module_stem: str, export_name: str) -> bool: "# ruff: noqa: E501, I001", "from __future__ import annotations", "", - "# Import all types from generated_poc modules", + "# Import all types from the generated domain modules", ] lines.extend(import_lines) @@ -804,7 +804,7 @@ def _stem_matches_export(module_stem: str, export_name: str) -> bool: "# This must happen AFTER all imports to resolve forward reference chains", "", "# Import individual modules needed for rebuilding", - "from adcp.types import generated_poc # noqa: F401", + "from adcp.types import domains # noqa: F401", "", "# Rebuild models that reference other models via forward refs", "# Note: only call model_rebuild() on actual classes, not Union type aliases", @@ -846,18 +846,18 @@ def scan_declared_names() -> dict[str, set[str]]: Unlike :func:`_scan_name_to_modules` this applies no public-export filter. That filter exists so an aggregate schema's inlined private copies cannot shadow the canonical class they were copied from in the flat namespace, and - the domains layer does not need it twice over: a mirror path names the - defining schema and shadows nothing, and a domain root binds only the names - its domain declares exactly once — so a copy that duplicates a canonical - name is omitted from the root by that rule alone. + the domains layer does not need it twice over: a schema's own module names + the defining schema and shadows nothing, and a domain root binds only the + names its domain declares exactly once — so a copy that duplicates a + canonical name is omitted from the root by that rule alone. Using the filtered scan here is what left ``brand_discovery`` with no public path at all, since that module is suppressed in full. """ declared: dict[str, set[str]] = {} - for rel in _mirror_relative_paths(): + for rel in _schema_module_paths(): module_name = ".".join([*rel.parts[:-1], rel.stem]) - for name in extract_exports_from_module(GENERATED_POC_DIR / rel): + for name in extract_exports_from_module(DOMAINS_DIR / rel): declared.setdefault(name, set()).add(module_name) return declared @@ -869,9 +869,9 @@ def unambiguous_domain_bindings( Returns ``{domain: {type_name: module}}``. A name a domain declares twice has no unambiguous spelling at domain level and is reached through the - module that declares it — see ``generate_module_mirror``. Nothing is - renamed: a type either keeps the name codegen gave it or is imported from - its own schema's module. + module that declares it — which is the module the generator defined it in. + Nothing is renamed: a type either keeps the name codegen gave it or is + imported from its own schema's module. """ per_domain: dict[str, dict[str, list[str]]] = {} for type_name, modules in name_to_modules.items(): @@ -889,101 +889,25 @@ def unambiguous_domain_bindings( } -def _mirror_relative_paths() -> list[Path]: - """Every non-bundled generated module, as a path relative to the tree root.""" - paths = [] - for path in sorted(GENERATED_POC_DIR.rglob("*.py")): - rel = path.relative_to(GENERATED_POC_DIR) - if path.stem == "__init__" or rel.parts[0] == "bundled": - continue - paths.append(rel) - return paths - - -def generate_module_mirror( - name_to_modules: dict[str, set[str]], -) -> dict[str, str]: - """Generate a public module for every schema, mirroring the generated tree. - - Returns ``{"/": content}``, including the ``__init__`` of every - package along the way. +def _schema_module_paths() -> list[Path]: + """Every non-bundled generated module, as a path relative to the tree root. - A domain is still one namespace, so it resolves a type name several domains - declare and reproduces the collision when one domain declares it several - times — ``core`` declares nine different ``Unit`` classes, in - ``audience_evidence``, ``canvas_constraint`` and seven more. The schema - layout already distinguishes them, one level further down, and codegen - already mirrors that layout. Making it public is what gives those nine - classes nine public paths without renaming, inventing or mangling anything: + These are the modules that DEFINE the public classes. ``core`` declares nine + different ``Unit`` classes, in ``audience_evidence``, ``canvas_constraint`` + and seven more; the schema layout distinguishes them one level below the + domain and the generated tree mirrors that layout, so all nine have a public + path with nothing renamed, invented or mangled:: from adcp.types.domains.core.audience_evidence import Unit from adcp.types.domains.core.canvas_constraint import Unit - - A mirror exports what its module declares, including names - ``exports_for_public_consolidation`` keeps out of the flat namespace. That - filter exists so an aggregate schema's inlined private copies cannot shadow - the canonical class they were copied from; a path that names the defining - schema shadows nothing, so the filter has nothing to protect here. """ - generation_date = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC") - files: dict[str, str] = {} - packages: set[tuple[str, ...]] = set() - - for rel in _mirror_relative_paths(): - module_name = ".".join([*rel.parts[:-1], rel.stem]) - domain = schema_domain(module_name) - names = sorted(extract_exports_from_module(GENERATED_POC_DIR / rel)) - if not names: - continue - if module_name == domain: - # A schema at the tree root: its module path IS the domain name, so - # the domain package root is already that schema's mirror and a - # submodule would only repeat the name. Every other schema gets one, - # so ``adcp.types.domains.`` is uniform. - continue - packages.update(rel.parts[:i] for i in range(1, len(rel.parts))) - lines = [ - f'"""Types declared by the AdCP ``{module_name.replace(".", "/")}`` schema.', - "", - "One public module per schema, so a type name its own domain declares", - "more than once is still unambiguous:", - "", - f" from adcp.types.domains.{module_name} import ", - "", - "Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY.", - f"Generation date: {generation_date}", - '"""', - "# ruff: noqa: E501, I001", - "from __future__ import annotations", - "", - f"from adcp.types.generated_poc.{module_name} import {', '.join(names)}", - ] - lines.extend(_format_all_block(names)) - files[str(rel)] = "\n".join(lines) - - # ``__init__`` for every intermediate package the mirror introduces. - for parts in sorted(packages): - if len(parts) == 1 and parts[0] in {Path(r).parts[0] for r in files}: - continue # the domain root is written by generate_domain_roots - init = "/".join([*parts, "__init__.py"]) - if init in files or len(parts) == 1: + paths = [] + for path in sorted(DOMAINS_DIR.rglob("*.py")): + rel = path.relative_to(DOMAINS_DIR) + if path.stem == "__init__" or rel.parts[0] == "bundled": continue - files[init] = "\n".join( - [ - f'"""Public mirror of the AdCP ``{"/".join(parts)}`` schema directory.', - "", - "Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY.", - f"Generation date: {generation_date}", - '"""', - "", - "__all__: list[str] = []", - "", - ] - ) - - schema_count = len([f for f in files if not f.endswith("__init__.py")]) - print(f" module mirror: {schema_count} schema modules") - return files + paths.append(rel) + return paths def generate_domain_exports( @@ -995,6 +919,12 @@ def generate_domain_exports( Returns ``{"": content, "__init__": content}``. A domain root binds the names that domain declares exactly once — nothing is renamed, and a name the domain declares twice is reached through its own schema's module. + + A domain whose only schema sits at the tree root (``adagents``, + ``brand_discovery``, ``manifest``, ``manifest_schema``) gets no entry: the + generator defines that schema's classes in ``domains/.py``, so the + domain root already IS the definition module and writing one would replace + it with a re-export of itself. """ generation_date = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC") domain_modules: dict[str, set[str]] = {} @@ -1004,23 +934,14 @@ def generate_domain_exports( modules: dict[str, str] = {} for domain, rows in sorted(bindings.items()): # A schema at the tree root has no submodule of its own — its module - # path is the domain name — so the root is that schema's mirror and - # carries everything it declares. - single = domain in {m for mods in name_to_modules.values() for m in mods} - if single: - rows = dict.fromkeys( - sorted(extract_exports_from_module(GENERATED_POC_DIR / f"{domain}.py")), - domain, - ) + # path is the domain name — so the generated module at that path is + # already the domain root and carries everything it declares. + if domain in {m for mods in name_to_modules.values() for m in mods}: + continue by_module: dict[str, list[str]] = {} for type_name, module_name in sorted(rows.items()): by_module.setdefault(module_name, []).append(type_name) - detail = ( - "This domain has one schema, so every type it declares is here." - if single - else "A type this domain declares in more than one schema is not here: import" - ) lines = [ f'"""Types the AdCP ``{domain}`` schemas declare.', "", @@ -1029,20 +950,14 @@ def generate_domain_exports( "", f" from adcp.types.domains.{domain} import ", "", - detail, + "A type this domain declares in more than one schema is not here: import", + " it from its own schema's module," f" ``adcp.types.domains.{domain}.``.", + "Nothing here is renamed.", ] - if not single: - lines.extend( - [ - " it from its own schema's module," - f" ``adcp.types.domains.{domain}.``.", - "Nothing here is renamed.", - ] - ) lines.extend( [ "", - "Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY.", + "Auto-generated from the generated domain tree. DO NOT EDIT MANUALLY.", f"Generation date: {generation_date}", '"""', "# ruff: noqa: E501, I001", @@ -1052,7 +967,7 @@ def generate_domain_exports( ) for module_name in sorted(by_module): lines.append( - f"from adcp.types.generated_poc.{module_name} import " + f"from adcp.types.domains.{module_name} import " f"{', '.join(sorted(by_module[module_name]))}" ) lines.extend(_format_all_block(sorted(rows))) @@ -1062,9 +977,9 @@ def generate_domain_exports( '"""Public types grouped by the AdCP schema that declares them.', "", "The AdCP bundle is organised by domain — ``core/``, ``creative/``,", - "``media_buy/`` and the rest — and by schema within each. Codegen mirrors that", - "layout, and these modules re-export it, so a type name more than one schema", - "declares is unambiguous by path rather than by a mangled name:", + "``media_buy/`` and the rest — and by schema within each. Codegen writes this", + "package in that layout and defines every class here, so a type name more", + "than one schema declares is unambiguous by path rather than by a mangled name:", "", " from adcp.types.domains.creative import QuerySummary", " from adcp.types.domains.core.audience_evidence import Unit", @@ -1073,7 +988,7 @@ def generate_domain_exports( "A domain root carries the names that domain declares exactly once. For the", "rest, import from the schema's own module, as the last two lines do.", "", - "Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY.", + "Auto-generated from the generated domain tree. DO NOT EDIT MANUALLY.", f"Generation date: {generation_date}", '"""', "from __future__ import annotations", @@ -1146,7 +1061,7 @@ def _error_details_closure() -> dict[str, list[str]]: import like any other. Filtering on ``isclass`` dropped two error-details models the moment codegen started emitting them that way. """ - package = importlib.import_module("adcp.types.generated_poc.error_details") + package = importlib.import_module("adcp.types.domains.error_details") package_dir = Path(package.__path__[0]) roots: list[object] = [] @@ -1183,7 +1098,7 @@ def _error_details_closure() -> dict[str, list[str]]: # annotation does not make its name public. if cls.__name__.startswith("_"): continue - module_name = cls.__module__.removeprefix("adcp.types.generated_poc.") + module_name = cls.__module__.removeprefix("adcp.types.domains.") if cls.__name__ not in by_module.setdefault(module_name, []): by_module[module_name].append(cls.__name__) return {module: sorted(names) for module, names in sorted(by_module.items())} @@ -1209,9 +1124,7 @@ def generate_error_details_exports() -> str: public = name if occurrences[name] == 1 else qualified_public_name(name, module_name) imported.append(name if public == name else f"{name} as {public}") exported.append(public) - import_lines.append( - f"from adcp.types.generated_poc.{module_name} import {', '.join(imported)}" - ) + import_lines.append(f"from adcp.types.domains.{module_name} import {', '.join(imported)}") generation_date = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC") lines = [ @@ -1233,7 +1146,7 @@ def generate_error_details_exports() -> str: "A nested name that two error-details schemas both define carries the", "defining file in its name (``ScopeFromRateLimited``) instead of a bare one.", "", - "Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY.", + "Auto-generated from the generated domain tree. DO NOT EDIT MANUALLY.", f"Generation date: {generation_date}", '"""', "# ruff: noqa: E501, I001", @@ -1264,8 +1177,8 @@ def _parse_args(argv: list[str] | None = None) -> argparse.Namespace: parser.add_argument( "--input-dir", type=Path, - default=GENERATED_POC_DIR, - help="generated_poc tree to consolidate", + default=DOMAINS_DIR, + help="generated domain tree to consolidate", ) parser.add_argument( "--output-file", @@ -1291,11 +1204,7 @@ def _parse_args(argv: list[str] | None = None) -> argparse.Namespace: def _format_with_black(*targets: Path) -> None: - """Run black over ``targets`` once. - - Once, not once per file: the module mirror is over a thousand files and a - subprocess each takes minutes. - """ + """Run black over ``targets`` once, not once per file.""" print("Formatting with black...") args = [str(t) for t in targets] for command in ( @@ -1311,19 +1220,15 @@ def _format_with_black(*targets: Path) -> None: print("⚠ Could not format with black (not installed)") -def write_generated_module(path: Path, content: str, *, format_now: bool = True) -> str: +def write_generated_module(path: Path, content: str) -> str: """Write ``content`` to ``path``, black-format it, and keep a stable date. - Returns the file's previous text. With ``format_now=False`` the formatting - and the date-preservation are both left to the caller: the date can only be - preserved by comparing formatted text against formatted text, so a deferred - write has to run :func:`restore_generation_dates` after its batch format. + Returns the file's previous text. The date can only be preserved by + comparing formatted text against formatted text, so the format runs here. """ path.parent.mkdir(parents=True, exist_ok=True) previous_content = path.read_text() if path.exists() else "" path.write_text(content) - if not format_now: - return previous_content _format_with_black(path) formatted_content = path.read_text() stable_content = preserve_generation_date_if_unchanged(previous_content, formatted_content) @@ -1332,31 +1237,17 @@ def write_generated_module(path: Path, content: str, *, format_now: bool = True) return previous_content -def restore_generation_dates(previous: dict[Path, str]) -> None: - """Put each file's prior timestamp back where only the timestamp changed. - - Run after a batched format, with the texts :func:`write_generated_module` - returned. Without this a thousand mirror modules carry a fresh date on every - regeneration and the tree is never byte-stable. - """ - for path, previous_content in previous.items(): - formatted_content = path.read_text() - stable_content = preserve_generation_date_if_unchanged(previous_content, formatted_content) - if stable_content != formatted_content: - path.write_text(stable_content) - - def main(argv: list[str] | None = None): """Generate the consolidated namespace, the domain modules and error-details.""" - global GENERATED_POC_DIR, OUTPUT_FILE, DOMAINS_DIR, ERROR_DETAILS_FILE, COLLISION_REPORT_FILE + global DOMAINS_DIR, OUTPUT_FILE, ERROR_DETAILS_FILE, COLLISION_REPORT_FILE args = _parse_args(argv) - GENERATED_POC_DIR = args.input_dir.resolve() + # One tree: the input is the generated domain package, and the domain roots + # are written back into it. ``generate_types.py`` generates into its staging + # copy and passes that path, so every derived artifact lands beside the + # models and is installed (or, under ``--check``, compared) as one unit. + DOMAINS_DIR = args.input_dir.resolve() OUTPUT_FILE = args.output_file.resolve() - # The derived modules live beside ``_generated.py``: when ``generate_types.py`` - # consolidates into its staging tree, every derived artifact lands there too - # and is installed (or, under ``--check``, compared) as one unit. - DOMAINS_DIR = OUTPUT_FILE.parent / "domains" ERROR_DETAILS_FILE = OUTPUT_FILE.parent / "error_details.py" COLLISION_REPORT_FILE = args.report_file.resolve() if args.source_root is not None: @@ -1366,10 +1257,10 @@ def main(argv: list[str] | None = None): # surface is derived from the previous generation and lags one run behind. sys.path.insert(0, str(args.source_root.resolve())) - print("Generating consolidated exports from generated_poc modules...") + print("Generating consolidated exports from the generated domain tree...") - if not GENERATED_POC_DIR.exists(): - print(f"Error: {GENERATED_POC_DIR} does not exist") + if not DOMAINS_DIR.exists(): + print(f"Error: {DOMAINS_DIR} does not exist") return 1 content = generate_consolidated_exports() @@ -1386,14 +1277,6 @@ def main(argv: list[str] | None = None): for domain, module_content in generate_domain_exports(bindings, declared).items(): target = DOMAINS_DIR / ("__init__.py" if domain == "__init__" else f"{domain}/__init__.py") write_generated_module(target, module_content) - mirror = generate_module_mirror(declared) - previous_mirror = { - DOMAINS_DIR - / rel: write_generated_module(DOMAINS_DIR / rel, module_content, format_now=False) - for rel, module_content in mirror.items() - } - _format_with_black(DOMAINS_DIR) - restore_generation_dates(previous_mirror) write_generated_module(ERROR_DETAILS_FILE, generate_error_details_exports()) # Not through ``write_generated_module``: black cannot format markdown. The # date still has to be preserved or the report churns on every regeneration. diff --git a/scripts/diff_generated_types.py b/scripts/diff_generated_types.py index 8415657db..ebb237831 100644 --- a/scripts/diff_generated_types.py +++ b/scripts/diff_generated_types.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Diff Pydantic-model field shape between two generated_poc/ trees. +"""Diff Pydantic-model field shape between two generated type trees. Codegen lands as ``chore(schemas): sync …`` with hundreds of file changes, so downstream consumers (salesagent, ad servers) can't tell from release notes @@ -11,11 +11,11 @@ Usage: # Capture a snapshot of the current tree to JSON python scripts/diff_generated_types.py snapshot \\ - src/adcp/types/generated_poc/ /tmp/before.json + src/adcp/types/domains/ /tmp/before.json # After regen, write a markdown delta against the snapshot python scripts/diff_generated_types.py diff \\ - /tmp/before.json src/adcp/types/generated_poc/ \\ + /tmp/before.json src/adcp/types/domains/ \\ --output SCHEMA_DELTAS.md The library API (``snapshot``, ``format_diff``) is what ``generate_types.py`` @@ -226,12 +226,12 @@ def _cmd_check(args: argparse.Namespace) -> int: if len(drifted) > 50: sys.stderr.write(f" ... {len(drifted) - 50} more\n") sys.stderr.write( - "\nThe committed src/adcp/types/generated_poc/ does not match what\n" + "\nThe committed src/adcp/types/domains/ does not match what\n" "scripts/generate_types.py produces from schemas/cache/. Either:\n" " - re-run sync + regen and commit the result:\n" " python scripts/sync_schemas.py && python scripts/generate_types.py\n" " - or, if upstream changed, bump src/adcp/ADCP_VERSION and regen\n" - " - and never hand-edit generated_poc/ — see scripts/post_generate_fixes.py\n" + " - and never hand-edit the generated tree — see scripts/post_generate_fixes.py\n" " for the sanctioned post-regen patch path.\n" ) return 1 @@ -242,13 +242,13 @@ def main() -> int: sub = parser.add_subparsers(dest="cmd", required=True) p_snap = sub.add_parser("snapshot", help="Capture per-class field snapshot to JSON.") - p_snap.add_argument("root", help="generated_poc/ directory to walk") + p_snap.add_argument("root", help="generated type tree to walk") p_snap.add_argument("output", help="JSON file to write") p_snap.set_defaults(func=_cmd_snapshot) p_diff = sub.add_parser("diff", help="Diff a JSON snapshot against a current tree.") p_diff.add_argument("before", help="JSON snapshot from `snapshot` command") - p_diff.add_argument("after_root", help="generated_poc/ directory to walk") + p_diff.add_argument("after_root", help="generated type tree to walk") p_diff.add_argument("--output", help="Write markdown report to this path (default: stdout)") p_diff.set_defaults(func=_cmd_diff) @@ -260,7 +260,7 @@ def main() -> int: ), ) p_check.add_argument("before", help="JSON snapshot from `snapshot` command") - p_check.add_argument("after_root", help="generated_poc/ directory to walk") + p_check.add_argument("after_root", help="generated type tree to walk") p_check.set_defaults(func=_cmd_check) args = parser.parse_args() diff --git a/scripts/export_resolution.py b/scripts/export_resolution.py index 33148b020..d2c7539cc 100644 --- a/scripts/export_resolution.py +++ b/scripts/export_resolution.py @@ -47,7 +47,7 @@ def snapshot_modules() -> tuple[tuple[str, str], ...]: #: Stripped from a key for brevity. Every generated class lives under it. -_GENERATED_PREFIX = "adcp.types.generated_poc." +_GENERATED_PREFIX = "adcp.types.domains." def resolution_key(obj: object) -> str: diff --git a/scripts/generate_ergonomic_coercion.py b/scripts/generate_ergonomic_coercion.py index 8a047f992..7701a78e0 100644 --- a/scripts/generate_ergonomic_coercion.py +++ b/scripts/generate_ergonomic_coercion.py @@ -132,8 +132,8 @@ def analyze_model(model_class) -> list[dict]: coercions = [] # Import the specific types we check for - from adcp.types.generated_poc.core.context import ContextObject - from adcp.types.generated_poc.core.ext import ExtensionObject + from adcp.types.domains.core.context import ContextObject + from adcp.types.domains.core.ext import ExtensionObject for field_name, field_info in model_class.model_fields.items(): annotation = field_info.annotation @@ -211,13 +211,14 @@ def analyze_model(model_class) -> list[dict]: return coercions +_GENERATED_PREFIX = "adcp.types.domains." + + def get_import_path(cls) -> str: - """Get the import path for a class relative to generated_poc.""" + """The class's module path relative to the generated domain tree.""" module = cls.__module__ - # Convert adcp.types.generated_poc.x.y to x.y - if "generated_poc" in module: - return module.split("generated_poc.")[1] - return module + # Convert adcp.types.domains.x.y to x.y + return module.removeprefix(_GENERATED_PREFIX) def get_symbol_name(cls) -> str: @@ -241,36 +242,36 @@ def generate_code() -> str: # Import all the types we need to analyze from pydantic import BaseModel as _PydBaseModel - from adcp.types.generated_poc.creative.list_creative_formats_request import ( + from adcp.types.domains.creative.list_creative_formats_request import ( ListCreativeFormatsRequestCreativeAgent, ) - from adcp.types.generated_poc.creative.list_creatives_request import ( + from adcp.types.domains.creative.list_creatives_request import ( ListCreativesRequest, Sort, ) - from adcp.types.generated_poc.creative.list_creatives_response import ListCreativesResponse - from adcp.types.generated_poc.media_buy import ( + from adcp.types.domains.creative.list_creatives_response import ListCreativesResponse + from adcp.types.domains.media_buy import ( create_media_buy_response as _cmbr_module, ) - from adcp.types.generated_poc.media_buy import ( + from adcp.types.domains.media_buy import ( update_media_buy_response as _umbr_module, ) - from adcp.types.generated_poc.media_buy.create_media_buy_request import CreateMediaBuyRequest - from adcp.types.generated_poc.media_buy.get_media_buy_delivery_response import ( + from adcp.types.domains.media_buy.create_media_buy_request import CreateMediaBuyRequest + from adcp.types.domains.media_buy.get_media_buy_delivery_response import ( GetMediaBuyDeliveryResponse, ) - from adcp.types.generated_poc.media_buy.get_products_request import ( + from adcp.types.domains.media_buy.get_products_request import ( GetProductsRequest, ) - from adcp.types.generated_poc.media_buy.get_products_response import GetProductsResponse - from adcp.types.generated_poc.media_buy.list_creative_formats_request import ( + from adcp.types.domains.media_buy.get_products_response import GetProductsResponse + from adcp.types.domains.media_buy.list_creative_formats_request import ( ListCreativeFormatsRequest, ) - from adcp.types.generated_poc.media_buy.list_creative_formats_response import ( + from adcp.types.domains.media_buy.list_creative_formats_response import ( ListCreativeFormatsResponse, ) - from adcp.types.generated_poc.media_buy.package_request import PackageRequest - from adcp.types.generated_poc.media_buy.package_update import PackageUpdate + from adcp.types.domains.media_buy.package_request import PackageRequest + from adcp.types.domains.media_buy.package_update import PackageUpdate # Resolve success variants. Different # datamodel-codegen versions emit the variants under shifting names: @@ -412,40 +413,40 @@ def _find_media_buy_success_variant(module: Any) -> type[_PydBaseModel] | None: # Add core imports for name, path in core_imports: - lines.append(f"from adcp.types.generated_poc.{path} import {name}") + lines.append(f"from adcp.types.domains.{path} import {name}") # Add enum imports for name, path in enum_imports: - lines.append(f"from adcp.types.generated_poc.{path} import {name}") + lines.append(f"from adcp.types.domains.{path} import {name}") # Add request type imports if "CreateMediaBuyRequest" in all_coercions: - lines.append("from adcp.types.generated_poc.media_buy.create_media_buy_request import (") + lines.append("from adcp.types.domains.media_buy.create_media_buy_request import (") lines.append(" CreateMediaBuyRequest,") lines.append(")") - lines.append("from adcp.types.generated_poc.media_buy.get_products_request import (") + lines.append("from adcp.types.domains.media_buy.get_products_request import (") lines.append(" GetProductsRequest,") lines.append(")") - lines.append("from adcp.types.generated_poc.media_buy.list_creative_formats_request import (") + lines.append("from adcp.types.domains.media_buy.list_creative_formats_request import (") lines.append(" ListCreativeFormatsRequest,") lines.append(")") - lines.append("from adcp.types.generated_poc.creative.list_creative_formats_request import (") + lines.append("from adcp.types.domains.creative.list_creative_formats_request import (") lines.append(" ListCreativeFormatsRequestCreativeAgent,") lines.append(")") - lines.append("from adcp.types.generated_poc.creative.list_creatives_request import (") + lines.append("from adcp.types.domains.creative.list_creatives_request import (") lines.append(" Field1 as ListCreativesField,") lines.append(" ListCreativesRequest,") lines.append(" Sort,") lines.append(")") - lines.append("from adcp.types.generated_poc.media_buy.package_request import PackageRequest") - lines.append("from adcp.types.generated_poc.media_buy.package_update import PackageUpdate") + lines.append("from adcp.types.domains.media_buy.package_request import PackageRequest") + lines.append("from adcp.types.domains.media_buy.package_update import PackageUpdate") # Add response type imports. CreateMediaBuyResponse1's numeric suffix can # shift between codegen versions; in schema versions that collapse the # response envelope, there may be no success-specific response variant. if create_media_buy_response1 is not None: cmbr_name = create_media_buy_response1.__name__ - lines.append("from adcp.types.generated_poc.media_buy.create_media_buy_response import (") + lines.append("from adcp.types.domains.media_buy.create_media_buy_response import (") if cmbr_name == "CreateMediaBuyResponse1": lines.append(" CreateMediaBuyResponse1,") else: @@ -453,25 +454,25 @@ def _find_media_buy_success_variant(module: Any) -> type[_PydBaseModel] | None: lines.append(")") if update_media_buy_response1 is not None: umbr_name = update_media_buy_response1.__name__ - lines.append("from adcp.types.generated_poc.media_buy.update_media_buy_response import (") + lines.append("from adcp.types.domains.media_buy.update_media_buy_response import (") if umbr_name == "UpdateMediaBuyResponse1": lines.append(" UpdateMediaBuyResponse1,") else: lines.append(f" {umbr_name} as UpdateMediaBuyResponse1,") lines.append(")") - lines.append("from adcp.types.generated_poc.media_buy.get_media_buy_delivery_response import (") + lines.append("from adcp.types.domains.media_buy.get_media_buy_delivery_response import (") lines.append(" GetMediaBuyDeliveryResponse,") lines.append(" MediaBuyDelivery,") lines.append(" NotificationType,") lines.append(")") lines.append( - "from adcp.types.generated_poc.media_buy.get_products_response import GetProductsResponse" + "from adcp.types.domains.media_buy.get_products_response import GetProductsResponse" ) - lines.append("from adcp.types.generated_poc.media_buy.list_creative_formats_response import (") + lines.append("from adcp.types.domains.media_buy.list_creative_formats_response import (") lines.append(" CreativeAgent,") lines.append(" ListCreativeFormatsResponse,") lines.append(")") - lines.append("from adcp.types.generated_poc.creative.list_creatives_response import (") + lines.append("from adcp.types.domains.creative.list_creatives_response import (") lines.append(" ListCreativesResponse,") lines.append(")") @@ -502,7 +503,7 @@ def _find_media_buy_success_variant(module: Any) -> type[_PydBaseModel] | None: for name, symbol_name, path in request_imports_sorted: if name not in already_imported: imported_name = name if name == symbol_name else f"{name} as {symbol_name}" - lines.append(f"from adcp.types.generated_poc.{path} import {imported_name}") + lines.append(f"from adcp.types.domains.{path} import {imported_name}") lines.append("") lines.append("") diff --git a/scripts/generate_types.py b/scripts/generate_types.py index ca447de02..ba53c3730 100755 --- a/scripts/generate_types.py +++ b/scripts/generate_types.py @@ -10,6 +10,7 @@ import argparse import copy +import functools import json import os import posixpath @@ -19,6 +20,7 @@ import sys import tempfile from pathlib import Path +from typing import Any try: import diff_generated_types @@ -52,7 +54,12 @@ def _load_resolve_bundle_key(): _BUNDLE_KEY = resolve_bundle_key(_PINNED_VERSION) SCHEMAS_DIR = REPO_ROOT / "schemas" / "cache" / _BUNDLE_KEY -OUTPUT_DIR = REPO_ROOT / "src" / "adcp" / "types" / "generated_poc" +# The generated tree IS the public tree. datamodel-code-generator writes one +# package per schema domain and one module per schema file, at the address +# adopters import from — ``adcp.types.domains..``. There is no +# private tree behind it and no re-export tree in front of it, so there is +# nothing to copy and nothing to drift. +OUTPUT_DIR = REPO_ROOT / "src" / "adcp" / "types" / "domains" TEMP_DIR = REPO_ROOT / ".schema_temp" DELTAS_FILE = REPO_ROOT / "SCHEMA_DELTAS.md" SHARED_TYPE_NAMES_FILE = REPO_ROOT / "docs" / "shared-type-names.md" @@ -91,7 +98,7 @@ def _load_resolve_bundle_key(): # ``brand.json`` is both a discovery document and the basename of the # ``brand/`` task-schema directory. Generate it as a standalone compatibility # module after directory-mode generation so its public models remain -# available without turning ``generated_poc.brand`` into a synthetic package +# available without turning ``domains.brand`` into a synthetic package # full of colliding models. ROOT_DISCOVERY_SCHEMAS = {Path("brand.json"): Path("brand_discovery.py")} @@ -136,6 +143,70 @@ def _normalize_schema_ref_target( return source_kind, Path(normalized) +#: Schemas this SDK deliberately does NOT reproduce as a closed enum. +#: +#: ``core/canonical-format-kind.json`` declares a 16-member ``enum`` and, in the +#: same file, states as normative that "Consumer SDKs MUST treat this enum as +#: **open** at parse time: an unknown ``format_kind`` value MUST be retained +#: as-is on the in-memory object ... and MUST NOT cause the surrounding payload +#: to fail validation." A closed enum cannot retain a value it does not know, +#: so the two requirements are incompatible and the schema is wrong. Faithfully +#: generating the closed enum propagates that into every consumer, so every +#: reference to it generates a bare ``str`` instead and no model refuses a +#: value, the way its sibling vocabularies (``format_shape``, +#: ``asset_group_id``) already work. The producer-side half of the rule is a +#: seller's obligation, and a pinned SDK cannot tell a kind a seller invented +#: from a kind defined after its pin. +#: +#: The enum CLASS is still generated from this file, as the vocabulary: +#: ``adcp.types.CanonicalFormatKind`` keeps its 16 members and is the default +#: vocabulary of ``adcp.types.is_canonical_format_kind``, which is how a caller +#: checks a value. Only the REFERENCES are opened. +#: +#: Upstream ask: adcontextprotocol/adcp#7929. If it lands, delete this set, +#: delete the transform below, and the generated models go back to agreeing +#: with their schema. +OPEN_VOCABULARY_SCHEMAS = {Path("core/canonical-format-kind.json")} + +#: Keys worth keeping when a reference is opened: they document the field and +#: carry no validation, so keeping them leaves the generated annotation's +#: description intact. +_OPENED_REF_KEPT_KEYS = frozenset({"description", "title", "$comment", "examples", "deprecated"}) + + +def open_vocabulary_refs(obj, current_schema_rel_path: Path): + """Replace every ``$ref`` to an open-vocabulary schema with ``type: string``. + + One pass, one place: the reference target is resolved by + :func:`_normalize_schema_ref_target`, which is the single owner of the + canonical-URL / root-relative / file-relative spellings, so this cannot + miss a spelling that resolution understands. A ref carrying a fragment is + left alone — it names a node inside the schema rather than the schema's own + type, and no such reference exists at pin 3.2.1. + """ + if isinstance(obj, dict): + ref = obj.get("$ref") + if isinstance(ref, str): + file_part, separator, _fragment = ref.partition("#") + normalized = _normalize_schema_ref_target(file_part, current_schema_rel_path) + if ( + normalized is not None + and not separator + and normalized[1] in OPEN_VOCABULARY_SCHEMAS + ): + kept = {key: value for key, value in obj.items() if key in _OPENED_REF_KEPT_KEYS} + obj.clear() + obj.update({"type": "string", **kept}) + return obj + for value in obj.values(): + open_vocabulary_refs(value, current_schema_rel_path) + elif isinstance(obj, list): + for item in obj: + open_vocabulary_refs(item, current_schema_rel_path) + + return obj + + def _underscored_schema_path(path: Path) -> Path: return Path(*(part.replace("-", "_") for part in path.parts)) @@ -482,6 +553,197 @@ def rewrite_refs(obj, current_schema_rel_path: Path): return obj +_VERSION_ENVELOPE_SCHEMA = Path("core/version-envelope.json") +_VERSION_ENVELOPE_FIELDS = ("adcp_version", "adcp_major_version") + +# Keys that carry documentation, not validation. Two declarations that differ only in +# these describe the same wire contract, which is what makes lifting them into the +# envelope's own declaration a no-change rewrite. +_DOCUMENTATION_KEYS = frozenset({"$comment", "deprecated", "description", "examples", "title"}) + +# How many schemas the pass is allowed to rewrite, so a bump that changes the shape of +# the problem is a build failure rather than a silent behavior change. Measured at pin +# 3.2.1: 8 pointer-ref task requests and 4 wire-equivalent inline declarers (1 task +# request, 3 para-protocol). The 2 pointer-ref task RESPONSES are refused by +# ``_has_bare_ref_root_arm`` -- rewriting them deletes a field from their submitted arm. +_VERSION_ENVELOPE_NORMALIZED_CEILING = 12 + + +def _version_envelope_ref() -> str: + """The canonical URL the composing schemas already use for the version envelope.""" + return ( + f"https://adcontextprotocol.org/schemas/{_PINNED_VERSION}/" + f"{_VERSION_ENVELOPE_SCHEMA.as_posix()}" + ) + + +@functools.cache +def _version_envelope_property_definitions() -> dict[str, Any]: + """The envelope's own declaration of each version field, read from the pinned cache.""" + envelope = json.loads((SCHEMAS_DIR / _VERSION_ENVELOPE_SCHEMA).read_text()) + properties = envelope["properties"] + return {name: properties[name] for name in _VERSION_ENVELOPE_FIELDS} + + +def _without_documentation(node: Any) -> Any: + """``node`` with every documentation-only key removed, recursively.""" + if isinstance(node, dict): + return { + k: _without_documentation(v) for k, v in node.items() if k not in _DOCUMENTATION_KEYS + } + if isinstance(node, list): + return [_without_documentation(item) for item in node] + return node + + +def _composes_version_envelope(schema: dict) -> bool: + """True when the schema already pulls the envelope in through a root ``allOf``.""" + arms = schema.get("allOf") + if not isinstance(arms, list): + return False + for arm in arms: + if not isinstance(arm, dict): + continue + ref = arm.get("$ref") + if ( + isinstance(ref, str) + and "#" not in ref + and ref.endswith(_VERSION_ENVELOPE_SCHEMA.as_posix()) + ): + return True + return False + + +def _has_bare_ref_root_arm(schema: dict) -> bool: + """True when a root ``anyOf``/``oneOf`` arm is a bare ``$ref`` to another schema. + + This is the one case where the rewrite would REMOVE a field instead of relocating it. + datamodel-code-generator merges the root ``properties`` into every arm it builds, but a + root ``allOf`` ``$ref`` becomes a base only of the arm it builds FROM the root object -- + an arm that is a ``$ref`` elsewhere is generated from the referenced schema and the + root's bases never reach it. So dropping the stubs strips the field from that arm and + the composition does not give it back. + + Measured: applying the rewrite anyway cost ``RefineProposalsResponse2`` and + ``RequestProposalsResponse4`` their ``adcp_version`` outright (10 fields to 9, 16 to 15), + and both carry ``extra: forbid``, so a buyer echoing the version on the submitted arm + would have been REJECTED where it previously validated. Those two arms are + ``core/compact-task-submitted.json``, which composes no envelope of its own. + """ + for key in ("anyOf", "oneOf"): + arms = schema.get(key) + if not isinstance(arms, list): + continue + if any(isinstance(arm, dict) and "$ref" in arm for arm in arms): + return True + return False + + +def _is_version_envelope_pointer(definition: dict, field: str) -> bool: + """Predicate A: the property IS the envelope's property, by JSON pointer. + + ``{"$ref": ".../core/version-envelope.json#/properties/adcp_version"}`` makes the + field definitionally shared and the class an envelope orphan: a pointer into a + subschema confers no composition under any generator. + """ + ref = definition.get("$ref") + return ( + set(definition) == {"$ref"} + and isinstance(ref, str) + and ref.endswith(f"{_VERSION_ENVELOPE_SCHEMA.as_posix()}#/properties/{field}") + ) + + +def normalize_version_envelope_composition(schema: dict, schema_rel_path: Path) -> dict: + """Give schemas that declare the version fields real ``AdcpVersionEnvelope`` ancestry. + + Two authored conventions keep a task message out of the envelope's subtree while + still carrying its fields: a property-level pointer ``$ref`` into + ``version-envelope.json#/properties/...`` (predicate A, 10 schemas at pin 3.2.1), and + an inline copy that is wire-equivalent to the envelope's own declaration modulo + documentation (predicate B, 4 schemas). Both are the original authoring, not drift -- + the pointer form has been there since the first commit that cached these files -- + and adcontextprotocol/adcp#7892 to convert them is still open. So the SDK normalizes + them here, on the generation copy only: the cached bundle and every direct-from-schema + validation path are untouched. + + The rewrite drops the property stubs and appends the envelope ``$ref`` to the root + ``allOf``, which datamodel-code-generator 0.64 renders as a base class -- the same + path 67 request roots already take. Validation does not move: the fields come back by + inheritance with the same type, pattern and bounds, and the extra-field policy is + pydantic-side on ``AdCPBaseModel``, so draft-07 ``additionalProperties`` semantics + never enter the generated artifact. + + Idempotent in both directions. A schema upstream has already converted matches + neither predicate and is returned untouched, so the day #7892 lands -- as a clean + root ``allOf`` or in the stub-retaining form its draft-07 constraint may force -- this + pass becomes a no-op rather than a conflict. That shape is not hypothetical: + ``media-buy/sync-reporting-receipts-request.json`` ships it today (root ``allOf`` + plus retained pointer stubs) and already renders as + ``SyncReportingReceiptsRequest(AdcpVersionEnvelope)``. + + Fails closed. A third convention -- a declaration that is neither a pointer nor + wire-equivalent -- is refused and left exactly as authored, so it surfaces as the + hierarchy ratchet's residue with the schema named instead of being papered over here. + Two such schemas exist today and are correctly refused: + ``core/verification-token-claims.json`` and ``manifest.schema.json``. + """ + if schema_rel_path == _VERSION_ENVELOPE_SCHEMA: + return schema + if schema_rel_path.parts[0] == BUNDLED_DIR_NAME: + # A bundled schema inlines its whole ``$ref`` graph on purpose, so it has no + # version-envelope reference to restore and appending one would put an external + # reference back into a self-contained document. The bundled copies of these 11 + # schemas are measured separately and are not task-message types: all but + # ``protocol/get_adcp_capabilities_response`` are pruned after generation. + return schema + properties = schema.get("properties") + if not isinstance(properties, dict): + return schema + declared = { + name: properties[name] + for name in _VERSION_ENVELOPE_FIELDS + if isinstance(properties.get(name), dict) + } + if not declared or _composes_version_envelope(schema): + return schema + if _has_bare_ref_root_arm(schema): + # Relocating the stubs here would DELETE the field from the ``$ref`` arm. Refuse, + # and let the schema surface in the hierarchy ratchet's residue by name. + return schema + + required = schema.get("required") + if isinstance(required, list) and any(name in required for name in declared): + # The envelope declares both fields optional. Lifting a root-required field into + # it would relax the contract, so refuse rather than normalize. + return schema + + envelope_definitions = _version_envelope_property_definitions() + for name, definition in declared.items(): + if _is_version_envelope_pointer(definition, name): + continue + if _without_documentation(definition) == _without_documentation(envelope_definitions[name]): + continue + return schema + + for name in declared: + del properties[name] + if not properties: + del schema["properties"] + arms = schema.setdefault("allOf", []) + if not isinstance(arms, list): + raise ValueError(f"{schema_rel_path.as_posix()}: root allOf is not a list") + arms.append({"$ref": _version_envelope_ref()}) + + global _VERSION_ENVELOPE_NORMALIZED + _VERSION_ENVELOPE_NORMALIZED += 1 + print(f" composed version envelope into {schema_rel_path.as_posix()}") + return schema + + +_VERSION_ENVELOPE_NORMALIZED = 0 + + def stabilize_inlined_core_refs(schema: dict, current_schema_rel_path: Path) -> dict: """Keep inlined core schemas from rebasing sibling refs. @@ -757,11 +1019,20 @@ def flatten_schemas(temp_dir: Path): # generated convenience model omits this field. properties.pop("formats", None) - # Normalize generator-specific extensions, resolve structural pointer - # refs into the type they select, then rewrite $ref paths. + # Normalize generator-specific extensions, resolve structural pointer refs into + # the type they select, compose the version envelope, then rewrite $ref paths. The + # envelope is composed before the rewrite so the appended reference takes exactly + # the same canonical-URL path through it as the schemas that already compose, and + # after the pointer inlining so that reference is never itself inlined back into + # copied fields -- which would turn inheritance into flattened fields. schema = normalize_enum_descriptions(schema) schema = inline_structural_pointer_refs(schema, rel_path) schema = collapse_nullable_unions(schema) + schema = normalize_version_envelope_composition(schema, rel_path) + # Before the rewrite, while refs are still in the spellings + # ``_normalize_schema_ref_target`` resolves. See OPEN_VOCABULARY_SCHEMAS + # for why one enum is deliberately not reproduced. + schema = open_vocabulary_refs(schema, rel_path) schema = rewrite_refs(schema, rel_path) schema = stabilize_inlined_core_refs(schema, rel_path) schema = stabilize_nested_discriminators(schema, rel_path) @@ -776,7 +1047,17 @@ def flatten_schemas(temp_dir: Path): print(f" {rel_path}") count = len(schema_files) - print(f"\n Prepared {count} schema files\n") + if _VERSION_ENVELOPE_NORMALIZED > _VERSION_ENVELOPE_NORMALIZED_CEILING: + raise ValueError( + f"version-envelope normalization rewrote {_VERSION_ENVELOPE_NORMALIZED} schemas, " + f"above the measured ceiling of {_VERSION_ENVELOPE_NORMALIZED_CEILING}. " + "A bump changed the shape of the problem: re-measure which schemas declare the " + "version fields without composing the envelope before raising the ceiling." + ) + print( + f"\n Prepared {count} schema files " + f"({_VERSION_ENVELOPE_NORMALIZED} version-envelope compositions)\n" + ) return temp_dir @@ -1109,8 +1390,16 @@ def _parse_args(argv: list[str] | None = None) -> argparse.Namespace: return parser.parse_args(argv) -def _copy_package_for_introspection(staging_root: Path, generated_dir: Path) -> Path: - """Build an isolated source tree whose imports use the staged models.""" +def _staged_source_tree(staging_root: Path) -> Path: + """Build an isolated source tree to generate into and introspect from. + + The generated tree is the public tree, so it is generated *inside* this + copy rather than copied in afterwards: ``consolidate_exports`` then writes + the domain roots into the same directory the models were generated into, + and the whole thing installs as one artifact. The committed tree is + dropped first so a module a schema no longer declares cannot survive a + regeneration. + """ staged_source = staging_root / "source" staged_package = staged_source / "adcp" shutil.copytree( @@ -1118,10 +1407,9 @@ def _copy_package_for_introspection(staging_root: Path, generated_dir: Path) -> staged_package, ignore=shutil.ignore_patterns("__pycache__", "*.pyc", "_schemas"), ) - staged_generated = staged_package / "types" / "generated_poc" + staged_generated = staged_package / "types" / OUTPUT_DIR.name if staged_generated.exists(): shutil.rmtree(staged_generated) - shutil.copytree(generated_dir, staged_generated) # The committed ergonomic module can import generated names that no longer # exist. The isolated import graph uses this inert stub until a fresh module @@ -1213,11 +1501,20 @@ def main(argv: list[str] | None = None): with tempfile.TemporaryDirectory(dir=REPO_ROOT, prefix=".typegen-") as temp_root: staging_root = Path(temp_root) - staged_output = staging_root / "generated_poc" - # Keep the historical basename stable: datamodel-code-generator - # embeds it in every package ``__init__.py`` header. temp_schemas = flatten_schemas(staging_root / ".schema_temp") + # Generate straight into the staged copy of the package, at the + # path the tree occupies in the checkout. Consolidation writes the + # domain roots into this same directory, so the tree installs as + # one artifact with one address. + staged_source = _staged_source_tree(staging_root) + staged_types = staged_source / "adcp" / "types" + staged_output = staged_types / OUTPUT_DIR.name + staged_consolidated = staged_types / "_generated.py" + staged_ergonomic = staged_types / "_ergonomic.py" + staged_error_details = staged_types / "error_details.py" + staged_report = staging_root / "shared-type-names.md" + if not generate_types(temp_schemas, staged_output): return 1 if not generate_root_discovery_types(temp_schemas, staged_output): @@ -1227,15 +1524,6 @@ def main(argv: list[str] | None = None): if not apply_post_generation_fixes(staged_output, args.update_fix_manifest): return 1 prune_unused_bundled_modules(staged_output) - restore_unchanged_files(staged_output) - - staged_source = _copy_package_for_introspection(staging_root, staged_output) - staged_types = staged_source / "adcp" / "types" - staged_consolidated = staged_types / "_generated.py" - staged_ergonomic = staged_types / "_ergonomic.py" - staged_domains = staged_types / "domains" - staged_error_details = staged_types / "error_details.py" - staged_report = staging_root / "shared-type-names.md" consolidate_script = REPO_ROOT / "scripts" / "consolidate_exports.py" result = subprocess.run( @@ -1289,13 +1577,12 @@ def main(argv: list[str] | None = None): restore_unchanged_file(staged_ergonomic, current_types / "_ergonomic.py") restore_unchanged_file(staged_error_details, current_types / "error_details.py") restore_unchanged_file(staged_report, SHARED_TYPE_NAMES_FILE) - restore_unchanged_files(staged_domains, current_types / "domains") + restore_unchanged_files(staged_output) artifacts = [ (staged_output, OUTPUT_DIR), (staged_consolidated, current_types / "_generated.py"), (staged_ergonomic, current_types / "_ergonomic.py"), - (staged_domains, current_types / "domains"), (staged_error_details, current_types / "error_details.py"), (staged_report, SHARED_TYPE_NAMES_FILE), ] diff --git a/scripts/post_generate_fixes.py b/scripts/post_generate_fixes.py index 51d269357..19fed049f 100644 --- a/scripts/post_generate_fixes.py +++ b/scripts/post_generate_fixes.py @@ -33,12 +33,22 @@ from pathlib import Path from typing import Any, NamedTuple +try: + import consolidate_exports +except ModuleNotFoundError: # Imported as ``scripts.post_generate_fixes`` in tests. + from scripts import consolidate_exports + +try: + import task_message_lattice +except ModuleNotFoundError: # Imported as ``scripts.post_generate_fixes`` in tests. + from scripts import task_message_lattice + REPO_ROOT = Path(__file__).parent.parent # Load ``resolve_bundle_key`` from its source file rather than via the # ``adcp`` package — this script runs after datamodel-codegen produces a -# fresh ``generated_poc/`` tree, before the post-fixes that make it +# fresh generated tree, before the post-fixes that make it # importable. ``adcp/__init__.py`` would crash on the unfixed models. def _load_resolve_bundle_key(): src = REPO_ROOT / "src" / "adcp" / "validation" / "version.py" @@ -54,7 +64,7 @@ def _load_resolve_bundle_key(): # Same reason as above: load ``adcp.types._scalar`` by path so the keyword sets # the rewriter and the runtime bases share live in exactly one place. The module -# itself depends only on pydantic, so it is importable while ``generated_poc/`` +# itself depends only on pydantic, so it is importable while the generated tree # is still unfixed. def _load_scalar_module(): src = REPO_ROOT / "src" / "adcp" / "types" / "_scalar.py" @@ -155,7 +165,7 @@ def ensure_pydantic_import(source: str, *names: str) -> str: _VERSION_FILE = REPO_ROOT / "src" / "adcp" / "ADCP_VERSION" _BUNDLE_KEY = resolve_bundle_key(_VERSION_FILE.read_text().strip()) -OUTPUT_DIR = REPO_ROOT / "src" / "adcp" / "types" / "generated_poc" +OUTPUT_DIR = REPO_ROOT / "src" / "adcp" / "types" / "domains" SCHEMA_DIR = REPO_ROOT / "schemas" / "cache" / _BUNDLE_KEY _PROTOCOL_ENVELOPE_IMPORT = "from ..core.protocol_envelope import ProtocolEnvelope\n" @@ -670,9 +680,7 @@ def _first_generated_class_name(content: str) -> str | None: ( base.id if isinstance(base, ast.Name) - else base.attr - if isinstance(base, ast.Attribute) - else "" + else base.attr if isinstance(base, ast.Attribute) else "" ) for base in node.bases } @@ -2719,10 +2727,10 @@ def fix_adagents_duplicate_aliases() -> None: def restore_format_category_deprecation_shim(): """Restore the removed-type ``format_category`` module after codegen. - ``scripts/generate_types.py`` wipes ``generated_poc/`` before + ``scripts/generate_types.py`` wipes the generated tree before regenerating. The deprecation shim file for the removed ``format_category`` submodule lives inside that tree so downstream - imports of ``adcp.types.generated_poc.enums.format_category`` hit an + imports of ``adcp.types.domains.enums.format_category`` hit an ``ImportError`` with a migration pointer instead of ``ModuleNotFoundError``. This function re-writes the shim after each regen. Keep the message in sync with ``_REMOVED_IN_V4`` in @@ -2739,17 +2747,17 @@ def restore_format_category_deprecation_shim(): "Importing this module raises :class:`ImportError` with a pointer to the\n" "migration guide — so downstream import sites like::\n" "\n" - " from adcp.types.generated_poc.enums.format_category import FormatCategory\n" + " from adcp.types.domains.enums.format_category import FormatCategory\n" "\n" "get the same pointer as the top-level ``from adcp import FormatCategory``,\n" "instead of a bare ``ModuleNotFoundError``.\n" "\n" "This file is restored after every codegen run by\n" - "``scripts/post_generate_fixes.py`` (which wipes ``generated_poc/``).\n" + "``scripts/post_generate_fixes.py`` (which wipes the generated tree).\n" '"""\n' "\n" "raise ImportError(\n" - ' "adcp.types.generated_poc.enums.format_category was removed in AdCP 3.0. "\n' + ' "adcp.types.domains.enums.format_category was removed in AdCP 3.0. "\n' " \"Use free-form format-id strings (e.g. 'goog:video_responsive_ad') via \"\n" ' "adcp.types.FormatId. See MIGRATION_v3_to_v4.md#creative-format-asset-slots-formataasset-aliases "\n' ' "for details."\n' @@ -2760,6 +2768,38 @@ def restore_format_category_deprecation_shim(): print(f" ✓ Restored format_category deprecation shim at {rel}") +def declare_root_schema_module_exports() -> None: + """Give each root-level schema module an ``__all__``. + + A domain whose only schema sits at the bundle root (``adagents.json``, + ``manifest.json``, ``manifest-schema.json``, and the relocated + ``brand_discovery``) generates as ``domains/.py``, so that module + IS the domain root. Every other domain root is a package ``__init__`` that + ``consolidate_exports.py`` writes with an explicit ``__all__``, and + ``scripts/export_resolution.py`` reads ``__all__`` to record what a public + namespace promises — so without this these four would be the only public + namespaces in the tree that promise nothing. + + The names are the module's own public classes and aliases, read with the + same helper the consolidation step uses, so a domain root and a schema + module cannot disagree about what the module declares. + """ + declared = 0 + for module in sorted(OUTPUT_DIR.glob("*.py")): + if module.name == "__init__.py": + continue + names = sorted(consolidate_exports.extract_exports_from_module(module)) + if not names: + continue + source = module.read_text() + if "\n__all__" in source: + raise RuntimeError(f"{module.name}: already declares __all__") + body = "".join(f' "{name}",\n' for name in names) + module.write_text(f"{source}\n\n__all__ = [\n{body}]\n") + declared += 1 + print(f" ✓ Declared __all__ on {declared} root-level schema module(s)") + + def inject_literal_discriminator_defaults() -> None: """Inject defaults for ``Literal[]`` required fields. @@ -3822,9 +3862,7 @@ def _require_identity_match_dimensions(self) -> TmpProviderRegistration1: print(" trusted_match/identity_match_response.py response class not found") return response_class = response_class_match.group(1) - source = ( - source.rstrip() - + f""" + source = source.rstrip() + f""" @model_validator(mode='after') def _validate_tmpx_provider_ids(self) -> {response_class}: @@ -3839,7 +3877,6 @@ def _validate_tmpx_provider_ids(self) -> {response_class}: raise ValueError('tmpx_providers keys must be valid provider_id values') return self """ - ) identity_match_response.write_text(source.rstrip() + "\n") print(" trusted_match/identity_match_response.py: added runtime validators") @@ -4359,7 +4396,9 @@ def fix_mcp_webhook_operation_id_optional() -> None: def fix_signal_listing_range_subclasses() -> None: """Reuse SignalListing.Range for generated subclasses that redeclare range.""" replacements = { - OUTPUT_DIR / "signals" / "get_signals_response.py": [ + OUTPUT_DIR + / "signals" + / "get_signals_response.py": [ ( "from ..core.signal_listing import SignalListing\n", "from ..core.signal_listing import Range, SignalListing\n", @@ -4374,7 +4413,9 @@ def fix_signal_listing_range_subclasses() -> None: "", ), ], - OUTPUT_DIR / "core" / "wholesale_feed_event.py": [ + OUTPUT_DIR + / "core" + / "wholesale_feed_event.py": [ ( "from .signal_listing import SignalListing\n", "from .signal_listing import Range, SignalListing\n", @@ -4628,20 +4669,18 @@ def restore_flattened_contract_field_types() -> None: source = representation_target.read_text() expected = " format_kind: Any" + # ``str`` rather than ``CanonicalFormatKind``: the vocabulary is open at + # every reference (``OPEN_VOCABULARY_SCHEMAS`` in scripts/generate_types.py), + # and restoring the enum here would re-close it at the one site flattening + # happens to pass through. The flattening artifact being repaired is the + # ``Any``, not the openness. replacement = """ format_kind: Annotated[ - CanonicalFormatKind, + str, Field( description="Canonical 3.2 path. The canonical format name this manifest targets (e.g., `image`, `video_hosted`, `audio_vast`, `seller_rendered_stateful_display`, `coordinated_placements`). Selects the contract against which the seller validates the manifest's assets. Mutually exclusive with deprecated `format_id`." ), ]""" if expected in source: - if "from .canonical_format_kind import CanonicalFormatKind\n" not in source: - anchor = "from .creative_manifest import CreativeManifest\n" - if anchor not in source: - raise RuntimeError("creative_representation.py: missing CreativeManifest import") - source = source.replace( - anchor, "from .canonical_format_kind import CanonicalFormatKind\n" + anchor, 1 - ) source = source.replace(expected, replacement, 1) elif replacement not in source: raise RuntimeError("creative_representation.py: expected format_kind override not found") @@ -4672,9 +4711,7 @@ def restore_flattened_contract_field_types() -> None: if "@model_validator(mode='before')" not in source: source = ensure_pydantic_import(source, "model_validator") - source = ( - source.rstrip() - + """ + source = source.rstrip() + """ @model_validator(mode='before') @classmethod @@ -4689,41 +4726,10 @@ def _reject_seller_bound_manifest_fields(cls, data: Any) -> Any: ) return data """ - ) representation_target.write_text(source) print(" core/creative_representation.py: restored canonical format contract") -def preserve_open_delivery_format_kind() -> None: - """Keep the public delivery-creative consumer boundary forward-compatible.""" - target = OUTPUT_DIR / "creative" / "get_creative_delivery_response.py" - if not target.exists(): - print(" creative/get_creative_delivery_response.py not found (skipping open enum)") - return - - source = target.read_text() - generated = """ format_kind: Annotated[ - canonical_format_kind.CanonicalFormatKind | None, - Field(description='Canonical format kind delivered for this creative.'), - ] = None""" - replacement = """ format_kind: Annotated[ - canonical_format_kind.CanonicalFormatKind | str | None, - Field( - description='Canonical format kind delivered for this creative.', - union_mode='left_to_right', - ), - ] = None""" - if generated in source: - target.write_text(source.replace(generated, replacement, 1)) - print(" creative/get_creative_delivery_response.py: opened format_kind enum") - elif replacement in source: - print(" creative/get_creative_delivery_response.py: format_kind already open") - else: - raise RuntimeError( - "get_creative_delivery_response.py: expected format_kind field not found" - ) - - def enforce_transformer_output_contract() -> None: """Require a transformer to declare canonical or legacy output formats.""" target = OUTPUT_DIR / "core" / "transformer.py" @@ -4752,9 +4758,7 @@ def enforce_transformer_output_contract() -> None: if "class Transformer(" not in source: raise RuntimeError("transformer.py: Transformer class not found") source = ensure_pydantic_import(source, "model_validator") - target.write_text( - source.rstrip() - + """ + target.write_text(source.rstrip() + """ @model_validator(mode='after') def _require_output_format_declaration(self) -> Transformer: @@ -4769,8 +4773,7 @@ def _require_output_format_declaration(self) -> Transformer: 'one of output_capability_ids or deprecated output_format_ids is required' ) return self -""" - ) +""") print(" core/transformer.py: enforced output declaration requirement") @@ -5692,8 +5695,7 @@ def fix_verify_brand_claim_models() -> None: bulk_response = OUTPUT_DIR / "brand" / "verify_brand_claims_response.py" if request.exists() and "claim_type:" not in request.read_text(): - request.write_text( - """# generated by datamodel-codegen: + request.write_text("""# generated by datamodel-codegen: # filename: brand/verify_brand_claim_request.json from __future__ import annotations @@ -5725,13 +5727,11 @@ class VerifyBrandClaimRequest(AdcpVersionEnvelope): dict[str, Any], Field(description='Claim payload. Shape varies by claim_type.'), ] -""" - ) +""") print(" brand/verify_brand_claim_request.py: restored claim fields") if response.exists() and "VerifyBrandClaimSuccessResponse" not in response.read_text(): - response.write_text( - """# generated by datamodel-codegen: + response.write_text("""# generated by datamodel-codegen: # filename: brand/verify_brand_claim_response.json from __future__ import annotations @@ -5790,8 +5790,7 @@ class VerifyBrandClaimErrorResponse(AdcpVersionEnvelope, ProtocolEnvelope): VerifyBrandClaimResponse = VerifyBrandClaimSuccessResponse | VerifyBrandClaimErrorResponse -""" - ) +""") print(" brand/verify_brand_claim_response.py: restored response arms") response_schema = SCHEMA_DIR / "brand" / "verify-brand-claim-response.json" @@ -5800,8 +5799,7 @@ class VerifyBrandClaimErrorResponse(AdcpVersionEnvelope, ProtocolEnvelope): and response_schema.exists() and '"signed_response"' in response_schema.read_text() ): - response.write_text( - """# generated by datamodel-codegen: + response.write_text("""# generated by datamodel-codegen: # filename: brand/verify_brand_claim_response.json from __future__ import annotations @@ -5952,8 +5950,7 @@ class VerifyBrandClaimErrorResponse(AdcpVersionEnvelope, ProtocolEnvelope): VerifyBrandClaimResponse = VerifyBrandClaimSuccessResponse | VerifyBrandClaimErrorResponse -""" - ) +""") print(" brand/verify_brand_claim_response.py: restored signed response fields") bulk_response_schema = SCHEMA_DIR / "brand" / "verify-brand-claims-response.json" @@ -5962,8 +5959,7 @@ class VerifyBrandClaimErrorResponse(AdcpVersionEnvelope, ProtocolEnvelope): and bulk_response_schema.exists() and '"signed_response"' in bulk_response_schema.read_text() ): - bulk_response.write_text( - """# generated by datamodel-codegen: + bulk_response.write_text("""# generated by datamodel-codegen: # filename: brand/verify_brand_claims_response.json from __future__ import annotations @@ -6146,8 +6142,7 @@ class VerifyBrandClaimsErrorResponse(AdcpVersionEnvelope, ProtocolEnvelope): VerifyBrandClaimsResponse = VerifyBrandClaimsResponseBulk | VerifyBrandClaimsErrorResponse -""" - ) +""") print(" brand/verify_brand_claims_response.py: restored signed response fields") if bulk_response.exists(): @@ -6665,9 +6660,7 @@ def fix_legacy_purchase_accepted_losses() -> None: ) validator_marker = " def _accepted_losses_match_schema(" if validator_marker not in fixed and "field_validator" in fixed: - fixed = ( - fixed.rstrip() - + """ + fixed = fixed.rstrip() + """ @field_validator('selected_product_ids') @@ -6695,7 +6688,6 @@ def _accepted_losses_match_schema( raise ValueError('accepted_losses must include the required compatibility losses') return values """ - ) if fixed != source: target.write_text(fixed) print(" media_buy/legacy_purchase_continuation_input.py: narrowed accepted_losses") @@ -6826,9 +6818,7 @@ def _require_portable_bound(self) -> {class_name}: ) class_start = source.find("class MediaBuyChangeTerm(AdCPBaseModel):") if class_start >= 0 and "def _validate_constraint_action" not in source[class_start:]: - source = ( - source.rstrip() - + """ + source = source.rstrip() + """ @model_validator(mode='after') def _validate_constraint_action(self) -> MediaBuyChangeTerm: @@ -6848,9 +6838,7 @@ def _validate_constraint_action(self) -> MediaBuyChangeTerm: if action not in allowed.get(kind, set()): raise ValueError('constraint kind is incompatible with action') return self -""" - + "\n" - ) +""" + "\n" term_path.write_text(source) print(" media_buy/change_term.py: restored constraint/action compatibility") @@ -6863,9 +6851,7 @@ def _validate_constraint_action(self) -> MediaBuyChangeTerm: ) class_start = source.find("class CommercialTerms(AdCPBaseModel):") if class_start >= 0 and "def _validate_change_term_set" not in source[class_start:]: - source = ( - source.rstrip() - + """ + source = source.rstrip() + """ @model_validator(mode='after') def _validate_change_term_set(self) -> CommercialTerms: @@ -6914,9 +6900,7 @@ def _validate_change_term_set(self) -> CommercialTerms: ): raise ValueError('change-term earliest effective time exceeds latest time') return self -""" - + "\n" - ) +""" + "\n" terms_path.write_text(source) print(" media_buy/commercial_terms.py: restored change-term set invariants") @@ -7032,6 +7016,8 @@ def _write_manifest(observed: dict[str, list[str]], previous: dict[str, bool | s ) return 1 return 0 + + _REQUIRED_GROUP_VALIDATOR = "_require_schema_required_group" # Bases that declare no schema fields of their own. Any other base the @@ -7311,13 +7297,114 @@ def {_REQUIRED_GROUP_VALIDATOR}(self) -> {class_name}: print(f" skipped {note}") +_ADCP_REQUEST_MARKER = "AdcpRequest" +_ADCP_RESPONSE_MARKER = "AdcpResponse" + + +def _insert_first_base(source: str, class_name: str, marker: str) -> str: + """Put ``marker`` first in ``class_name``'s base list. + + A field-less plain class as first base changes no field, no validator and no + serialization -- pydantic's metaclass still wins metaclass resolution, because the + marker's own metaclass is ``type``. First, not last, so the marker's accessors are + what a caller sees ahead of any generated member of the same name. + """ + pattern = r"^class " + re.escape(class_name) + r"\(([^\n]*)\):$" + source, replaced = re.subn( + pattern, "class " + class_name + "(" + marker + r", \1):", source, count=1, flags=re.M + ) + if replaced == 1: + return source + pattern = r"^class " + re.escape(class_name) + r":$" + source, replaced = re.subn( + pattern, "class " + class_name + "(" + marker + "):", source, count=1, flags=re.M + ) + if replaced != 1: + raise RuntimeError(f"unable to rewrite the base list of {class_name}") + return source + + +def insert_task_message_markers() -> None: + """Give every registered task message its ``AdcpRequest`` / ``AdcpResponse`` marker. + + The marker is what lets a caller hold "a request" or "a response" as a TYPE before + knowing which tool it is -- resolve the account, decide at-most-once, echo the + context, route on task state -- and makes ``issubclass(model, AdcpRequest)`` the + registration-time proof that a model is spec-derived rather than a hand-written + parallel. The task registry in ``index.json`` decides membership, so a new task is + marked with zero edits here and a task that leaves drops its marker. + + Inheritance, not injection: an injected accessor set that skips one class fails + silently, an inherited method cannot be forgotten. And one mechanism only -- a class + that COPIES rather than inherits (the bundled clone layer) is out of reach by design, + and reaching it would mean two injection points to keep in step. + + Fails closed in four places: an unresolvable schema, a schema with no title, an + unresolvable root class, and a post-rewrite AST check that every marked class really + carries the marker as its first base. + """ + messages = task_message_lattice.task_registry_messages(SCHEMA_DIR) + modules = task_message_lattice.generated_module_by_schema(OUTPUT_DIR) + + # (module, class) -> marker. A response arm shared by several tasks is named more + # than once; the marker is the same, so the map de-duplicates it. + wanted: dict[tuple[Path, str], str] = {} + for task, kind, schema_rel in messages: + module = modules.get(task_message_lattice.underscored(schema_rel)) + if module is None: + raise RuntimeError( + f"{task} {kind}: no generated module declares filename " + f"{task_message_lattice.underscored(schema_rel).as_posix()}" + ) + title = json.loads((SCHEMA_DIR / schema_rel).read_text()).get("title") + if not isinstance(title, str) or not title.strip(): + raise RuntimeError(f"{schema_rel.as_posix()}: no title to derive a root class from") + marker = _ADCP_REQUEST_MARKER if kind == "request" else _ADCP_RESPONSE_MARKER + tree = ast.parse(module.read_text()) + for class_name in task_message_lattice.root_class_names( + schema_rel, module, tree, task_message_lattice.mangle_schema_title(title) + ): + previous = wanted.get((module, class_name)) + if previous is not None and previous != marker: + raise RuntimeError( + f"{module.name}: {class_name} is both a task request and a task response" + ) + wanted[(module, class_name)] = marker + + by_module: dict[Path, dict[str, str]] = {} + for (module, class_name), marker in wanted.items(): + by_module.setdefault(module, {})[class_name] = marker + + for module, markers in sorted(by_module.items()): + source = module.read_text() + import_line = "from adcp.types.base import " + ", ".join(sorted(set(markers.values()))) + if import_line + "\n" not in source: + future_import = "from __future__ import annotations\n\n" + if future_import not in source: + raise RuntimeError(f"{module.name}: missing future annotations import") + source = source.replace(future_import, future_import + import_line + "\n\n", 1) + for class_name, marker in sorted(markers.items()): + source = _insert_first_base(source, class_name, marker) + module.write_text(source) + + classes, _ = task_message_lattice.module_top_level(ast.parse(source)) + for class_name, marker in markers.items(): + bases = classes[class_name].bases + if not bases or not isinstance(bases[0], ast.Name) or bases[0].id != marker: + raise RuntimeError( + f"{module.name}: {class_name} does not carry {marker} as its first base" + ) + + print(f" marked {len(wanted)} task-message classes across {len(by_module)} modules") + + def _parse_args(argv: list[str] | None = None) -> argparse.Namespace: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( "--output-dir", type=Path, default=OUTPUT_DIR, - help="generated_poc tree to modify", + help="generated domain tree to modify", ) parser.add_argument( "--update-manifest", @@ -7362,7 +7449,6 @@ def main(argv: list[str] | None = None) -> int: restore_principal_result_aliases, disambiguate_comply_response_arm, restore_flattened_contract_field_types, - preserve_open_delivery_format_kind, enforce_transformer_output_contract, restore_constructible_response_bases, restore_response_variant_aliases, @@ -7406,9 +7492,13 @@ def main(argv: list[str] | None = None) -> int: rewrite_generated_enums_to_strenum, annotate_registry_track_verdict, preserve_format_reference_agent_url_wire_string, + insert_task_message_markers, point_integer_fields_at_the_schema_integer_type, remove_imports_shadowed_by_a_local_class, remove_unused_pydantic_field_imports, + # After every pass that can add, rename or remove a class: the + # ``__all__`` it writes has to name the final set. + declare_root_schema_module_exports, strip_extra_blank_lines_at_eof, ] observed: dict[str, list[str]] = {} diff --git a/scripts/post_generation_manifest.json b/scripts/post_generation_manifest.json index d2176d7a9..37aaf274c 100644 --- a/scripts/post_generation_manifest.json +++ b/scripts/post_generation_manifest.json @@ -27,7 +27,6 @@ "restore_principal_result_aliases": true, "disambiguate_comply_response_arm": true, "restore_flattened_contract_field_types": true, - "preserve_open_delivery_format_kind": true, "enforce_transformer_output_contract": true, "restore_constructible_response_bases": true, "restore_response_variant_aliases": true, @@ -69,9 +68,11 @@ "rewrite_generated_enums_to_strenum": true, "annotate_registry_track_verdict": true, "preserve_format_reference_agent_url_wire_string": true, + "insert_task_message_markers": true, "point_integer_fields_at_the_schema_integer_type": true, "remove_imports_shadowed_by_a_local_class": true, "remove_unused_pydantic_field_imports": true, + "declare_root_schema_module_exports": true, "strip_extra_blank_lines_at_eof": true } } diff --git a/scripts/task_message_lattice.py b/scripts/task_message_lattice.py new file mode 100644 index 000000000..e78a47fe4 --- /dev/null +++ b/scripts/task_message_lattice.py @@ -0,0 +1,327 @@ +#!/usr/bin/env python3 +"""Resolve the pinned bundle's task messages to their generated root classes. + +ONE home for that resolution, with three consumers: the marker-insertion pass in +``post_generate_fixes.py``, the ratchet in ``tests/test_task_message_lattice.py``, and the +``--package`` measurement below, which the adoption run uses to re-measure the ancestry +counts against an installed wheel instead of trusting a number in a report. + +Run it against whatever ``adcp`` is importable: + + python scripts/task_message_lattice.py --package + +or against a repo checkout's generated tree: + + python scripts/task_message_lattice.py + +It needs only ``adcp``'s shipped ``_schemas//index.json`` and the generated +modules, both of which are in the wheel -- so copying this one file next to a venv that has +the wheel installed is enough to reproduce the table. +""" + +from __future__ import annotations + +import argparse +import ast +import importlib +import json +import re +import sys +import typing +from pathlib import Path + +_CANONICAL_REF = re.compile(r"^https://adcontextprotocol\.org/schemas/[^/]+/(.+)$") + + +def registry_ref_to_path(ref: str) -> Path: + """The schema path a task-registry ``$ref`` names. + + Narrower than ``post_generate_fixes._resolve_schema_ref`` on purpose: registry refs are + always absolute, so a relative one means the registry's shape changed and that should + stop the build rather than be resolved against a guessed base. + """ + file_ref = ref.split("#", 1)[0] + canonical = _CANONICAL_REF.match(file_ref) + if canonical: + return Path(canonical.group(1)) + if file_ref.startswith("/schemas/"): + return Path(file_ref.removeprefix("/schemas/")) + raise RuntimeError(f"task registry $ref is not absolute: {ref!r}") + + +def mangle_schema_title(title: str) -> str: + """The class name datamodel-code-generator derives from a schema ``title``. + + Measured against all 154 task-message schemas at pin 3.2.1: this reproduces the + generated root class (or union alias) name for 154 of 154. The acronym collapse is the + part a filename-derived guess gets wrong in both directions -- "Get AdCP Capabilities + Request" becomes ``GetAdcpCapabilitiesRequest``, and + ``list-creative-formats-request.json`` does NOT become ``ListCreativeFormatsRequest`` + because its title names the agent variant. + """ + return "".join(word.capitalize() for word in re.split(r"[^0-9a-zA-Z]+", title) if word) + + +def task_registry_messages(schema_dir: Path) -> list[tuple[str, str, Path]]: + """Every task message in the pinned bundle: (task, "request"|"response", schema path). + + The registry in ``index.json`` is the authority for what IS a task message. A filename + pattern is not: 10 of the 87 ``*-request.json`` files are components or para-protocol + schemas that no task names, and treating those as task messages would claim registry + membership the bundle does not grant. + """ + index = json.loads((schema_dir / "index.json").read_text()) + messages: list[tuple[str, str, Path]] = [] + for domain, entry in index["schemas"].items(): + for task, refs in (entry.get("tasks") or {}).items(): + for kind in ("request", "response"): + messages.append( + (f"{domain}.{task}", kind, registry_ref_to_path(refs[kind]["$ref"])) + ) + return messages + + +def generated_module_by_schema(output_dir: Path) -> dict[Path, Path]: + """Map each schema to the module generated from it, by the codegen ``filename:`` header. + + The header is the generator's own statement of provenance, which is why it is read + instead of recomputed: a path convention would have to re-derive the hyphen conversion, + the directory layout and the root-discovery special cases. + """ + modules: dict[Path, Path] = {} + for path in sorted(output_dir.rglob("*.py")): + if "bundled" in path.parts: + continue + for line in path.read_text().splitlines()[:6]: + if line.startswith("# filename:"): + modules[Path(line.split(":", 1)[1].strip())] = path + break + return modules + + +def underscored(path: Path) -> Path: + return Path(*(part.replace("-", "_") for part in path.parts)) + + +def module_top_level(tree: ast.Module) -> tuple[dict[str, ast.ClassDef], dict[str, ast.expr]]: + """Top-level classes and top-level assignments (the union aliases) of one module.""" + classes = {n.name: n for n in tree.body if isinstance(n, ast.ClassDef)} + aliases: dict[str, ast.expr] = {} + for node in tree.body: + names: list[str] = [] + if isinstance(node, ast.Assign): + names = [t.id for t in node.targets if isinstance(t, ast.Name)] + elif isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name): + names = [node.target.id] + if names and node.value is not None: + for name in names: + aliases[name] = node.value + return classes, aliases + + +def _alias_arms_expr(node: ast.expr) -> ast.expr: + """The type an alias names, with any ``Annotated[...]`` metadata dropped. + + A composing root is emitted as ``Annotated[A | B, Field(...)]``, so the arms are + the first subscript element and nothing else. Walking the whole expression reads + ``Annotated`` -- and the ``Field`` call beside it -- as union arms, and then fails + closed on the first one because no module defines a class by that name. + """ + while ( + isinstance(node, ast.Subscript) + and ( + (isinstance(node.value, ast.Name) and node.value.id == "Annotated") + or (isinstance(node.value, ast.Attribute) and node.value.attr == "Annotated") + ) + and isinstance(node.slice, ast.Tuple) + and node.slice.elts + ): + node = node.slice.elts[0] + return node + + +def root_class_names(schema_rel: Path, module: Path, tree: ast.Module, root: str) -> list[str]: + """The class(es) a task message can validate into, in the module that defines them. + + A plain root is one class. A union root is a type alias, and the alias is not a class -- + so the marker lands on each member arm, which is what makes + ``isinstance(resp, AdcpResponse)`` hold for every value the task can answer with. + Nested aliases are followed. Fails closed: a root that resolves to neither a class nor + an alias, or an alias naming something this module does not define, is an error with + the schema named. + """ + classes, aliases = module_top_level(tree) + if root in classes: + return [root] + if root not in aliases: + raise RuntimeError( + f"{schema_rel.as_posix()}: generated module {module.name} defines neither a " + f"class nor a type alias named {root!r}" + ) + pending = [ + node.id for node in ast.walk(_alias_arms_expr(aliases[root])) if isinstance(node, ast.Name) + ] + resolved: list[str] = [] + seen: set[str] = set() + while pending: + name = pending.pop(0) + if name in seen: + continue + seen.add(name) + if name in classes: + resolved.append(name) + elif name in aliases: + pending.extend( + node.id + for node in ast.walk(_alias_arms_expr(aliases[name])) + if isinstance(node, ast.Name) + ) + else: + raise RuntimeError( + f"{schema_rel.as_posix()}: union arm {name!r} of {root} is not defined in " + f"{module.name}; the marker pass cannot reach it (one mechanism only)" + ) + if not resolved: + raise RuntimeError(f"{schema_rel.as_posix()}: {root} resolved to no arm classes") + return resolved + + +def task_message_roots( + schema_dir: Path, output_dir: Path +) -> list[tuple[str, str, Path, Path, list[str]]]: + """(task, kind, schema path, module path, root class names) for every task message.""" + modules = generated_module_by_schema(output_dir) + rows = [] + for task, kind, schema_rel in task_registry_messages(schema_dir): + module = modules.get(underscored(schema_rel)) + if module is None: + raise RuntimeError( + f"{task} {kind}: no generated module declares filename " + f"{underscored(schema_rel).as_posix()}" + ) + title = json.loads((schema_dir / schema_rel).read_text()).get("title") + if not isinstance(title, str) or not title.strip(): + raise RuntimeError(f"{schema_rel.as_posix()}: no title to derive a root class from") + tree = ast.parse(module.read_text()) + rows.append( + ( + task, + kind, + schema_rel, + module, + root_class_names(schema_rel, module, tree, mangle_schema_title(title)), + ) + ) + return rows + + +# -------------------------------------------------------------------------------------- +# Measurement (``--package``): import the classes and report the ancestry at task scope. +# -------------------------------------------------------------------------------------- + + +def _arms(obj: object) -> list[type]: + """A union alias's member classes, or the single class itself.""" + members = list(typing.get_args(obj)) or [obj] + return [m for m in members if isinstance(m, type)] + + +def measure(schema_dir: Path, output_dir: Path, package_root: Path) -> int: + """Print the ancestry table and return a process exit code. + + Ancestry means EVERY root class the message can validate into descends from the + ancestor -- all arms, not any arm. A union whose submitted arm lacks the envelope + cannot answer the envelope question for every value the task returns, so counting it as + composed overstates what a caller may rely on. + """ + from adcp.types.domains.core.protocol_envelope import ProtocolEnvelope + from adcp.types.domains.core.version_envelope import AdcpVersionEnvelope + + try: + from adcp.types.base import AdcpRequest, AdcpResponse + + markers: dict[str, type | None] = {"request": AdcpRequest, "response": AdcpResponse} + except ImportError: + print("adcp.types.base exposes no AdcpRequest/AdcpResponse (pre-lattice wheel)") + markers = {"request": None, "response": None} + + rows = task_message_roots(schema_dir, output_dir) + resolved: list[tuple[str, str, str, list[type]]] = [] + for task, kind, schema_rel, module, names in rows: + dotted = ".".join(module.relative_to(package_root).with_suffix("").parts) + imported = importlib.import_module(dotted) + classes = [c for name in names for c in _arms(getattr(imported, name))] + resolved.append((task, kind, schema_rel.as_posix(), classes)) + + failures = 0 + print(f"task messages resolved: {len(resolved)} (77 tasks x request + response)\n") + for kind in ("request", "response"): + subset = [r for r in resolved if r[1] == kind] + print(f"=== task {kind}s, n={len(subset)} ===") + checks = [ + (f"Adcp{kind.capitalize()}", markers[kind]), + ("AdcpVersionEnvelope", AdcpVersionEnvelope), + ] + if kind == "response": + # The protocol envelope wraps responses. On a request it is expected absent, + # and listing 77 "residue" lines for that would bury the two real numbers. + checks.append(("ProtocolEnvelope", ProtocolEnvelope)) + for label, ancestor in checks: + if ancestor is None: + print(f" {label:<22} n/a (absent from this wheel)") + continue + residue = sorted( + {s for _, _, s, cs in subset if not all(issubclass(c, ancestor) for c in cs)} + ) + print(f" {label:<22} {len(subset) - len(residue)}/{len(subset)}") + for schema in residue: + print(f" residue: {schema}") + unmarked = [ + s + for _, kind, s, cs in resolved + if markers[kind] is not None and not all(issubclass(c, markers[kind]) for c in cs) + ] + if unmarked: + failures += len(unmarked) + print(f"\nFAIL: {len(unmarked)} task message(s) carry no marker") + return 1 if failures else 0 + + +def _package_roots() -> tuple[Path, Path, Path]: + """Schema dir, generated tree and package parent, read off the IMPORTED adcp package.""" + import adcp + from adcp.validation.version import resolve_bundle_key + + package = Path(adcp.__file__).parent + bundle = resolve_bundle_key((package / "ADCP_VERSION").read_text().strip()) + return package / "_schemas" / bundle, package / "types" / "domains", package.parent + + +def _repo_roots() -> tuple[Path, Path, Path]: + repo = Path(__file__).resolve().parent.parent + sys.path.insert(0, str(repo / "src")) + from adcp.validation.version import resolve_bundle_key + + bundle = resolve_bundle_key((repo / "src" / "adcp" / "ADCP_VERSION").read_text().strip()) + return ( + repo / "schemas" / "cache" / bundle, + repo / "src" / "adcp" / "types" / "domains", + repo / "src", + ) + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--package", + action="store_true", + help="measure the installed adcp package (a wheel) instead of a repo checkout", + ) + args = parser.parse_args(argv) + schema_dir, output_dir, package_root = _package_roots() if args.package else _repo_roots() + print(f"schemas: {schema_dir}\ngenerated: {output_dir}\n") + return measure(schema_dir, output_dir, package_root) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/update_canonical_stub_ledger.py b/scripts/update_canonical_stub_ledger.py deleted file mode 100644 index 13273e839..000000000 --- a/scripts/update_canonical_stub_ledger.py +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env python3 -"""Rewrite the canonical-stub field-gap ledger from the current gap. - -``canonical_creative.pyi`` omits runtime fields that its models carry, and -``tests/test_protocol_envelope_inheritance.py`` holds the omissions to a ledger -that may only shrink. Declare a field in the stub, then run this to drop it from -the ledger. The ledger cannot grow: a field the stub stops declaring fails the -test, and this script refuses to record it. -""" - -from __future__ import annotations - -import json -import sys -from pathlib import Path - -REPO_ROOT = Path(__file__).parent.parent -sys.path.insert(0, str(REPO_ROOT / "src")) -sys.path.insert(0, str(REPO_ROOT)) - -from tests.canonical_stub_gap import LEDGER_FILE, stub_field_gap # noqa: E402 - - -def main() -> int: - recorded: dict[str, list[str]] = json.loads(LEDGER_FILE.read_text(encoding="utf-8"))["gap"] - measured = stub_field_gap() - - grown = { - name: sorted(set(fields) - set(recorded.get(name, ()))) - for name, fields in measured.items() - if set(fields) - set(recorded.get(name, ())) - } - if grown: - print("✗ The stub stopped declaring fields it used to declare:", file=sys.stderr) - for name, fields in grown.items(): - print(f" {name}: {', '.join(fields)}", file=sys.stderr) - print(" Declare them again rather than recording the loss.", file=sys.stderr) - return 1 - - before = sum(len(fields) for fields in recorded.values()) - after = sum(len(fields) for fields in measured.values()) - LEDGER_FILE.write_text( - json.dumps({"_comment": _COMMENT, "gap": measured}, indent=2) + "\n", - encoding="utf-8", - ) - print(f"Wrote {LEDGER_FILE.name}: {after} undeclared field(s) across {len(measured)} class(es)") - print(f" {before - after} closed since the last update") - return 0 - - -_COMMENT = ( - "Runtime fields that src/adcp/types/canonical_creative.pyi does not declare, so a " - "type checker rejects reading them and rejects passing them to the constructor. " - "Shrink-only: declare the field in the stub, then run " - "`python scripts/update_canonical_stub_ledger.py`. Graded by " - "tests/test_protocol_envelope_inheritance.py::test_canonical_stub_declares_every_runtime_field." -) - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/skills/build-generative-seller-agent/SKILL.md b/skills/build-generative-seller-agent/SKILL.md index f352e327e..3e71f0561 100644 --- a/skills/build-generative-seller-agent/SKILL.md +++ b/skills/build-generative-seller-agent/SKILL.md @@ -146,7 +146,7 @@ All seller response builders apply. The generative delta is in `list_creative_fo `ADCPHandler` advertises `build_creative` and `preview_creative` by default and returns `not_supported` unless you override them. A generative seller MUST implement both, or the storyboard fails at the generation step. -**`build_creative`** — render a creative manifest from a brief. `idempotency_key` is a REQUIRED request field (pattern `^[A-Za-z0-9_.:-]{16,255}$`, see `src/adcp/types/generated_poc/media_buy/build_creative_request.py:157-165`). Use `adcp.server.idempotency.IdempotencyStore` to dedupe retries: +**`build_creative`** — render a creative manifest from a brief. `idempotency_key` is a REQUIRED request field (pattern `^[A-Za-z0-9_.:-]{16,255}$`, see `src/adcp/types/domains/media_buy/build_creative_request.py:157-165`). Use `adcp.server.idempotency.IdempotencyStore` to dedupe retries: ```python from adcp.server.responses import build_creative_response diff --git a/src/adcp/__init__.py b/src/adcp/__init__.py index 37f70af4c..4903ec4d6 100644 --- a/src/adcp/__init__.py +++ b/src/adcp/__init__.py @@ -935,7 +935,7 @@ def get_adcp_spec_version() -> str: Pinned at build time from the ``ADCP_VERSION`` file packaged with the SDK. The version determines which AdCP schemas - (``adcp.types.generated_poc``) ship with this release. + (``adcp.types.domains``) ship with this release. Use this when you need to surface spec version to clients (agent cards, capability responses, debug endpoints) or validate diff --git a/src/adcp/canonical_formats/narrowing.py b/src/adcp/canonical_formats/narrowing.py index f8a647489..bd5f02daa 100644 --- a/src/adcp/canonical_formats/narrowing.py +++ b/src/adcp/canonical_formats/narrowing.py @@ -335,13 +335,13 @@ def narrowing_advisory( return make_sdk_advisory( code="FORMAT_DECLARATION_DIVERGENT", message=( - f"v2 declaration (format_kind={declaration.format_kind.value!r}) " + f"v2 declaration (format_kind={declaration.format_kind!r}) " f"params do not narrow v1 format {safe_id!r} requirements: " f"{len(divs)} divergence(s)." ), field=field_path, details={ - "format_kind": declaration.format_kind.value, + "format_kind": declaration.format_kind, "v1_format_id": safe_id, "divergences": [d.to_dict() for d in divs], }, diff --git a/src/adcp/canonical_formats/projection.py b/src/adcp/canonical_formats/projection.py index fdd65bf44..3bf9d382e 100644 --- a/src/adcp/canonical_formats/projection.py +++ b/src/adcp/canonical_formats/projection.py @@ -758,7 +758,7 @@ def visit(item: Any, field_path: str) -> Any: ) declaration = projected.declaration retain_routes([declaration], result.get("product_id")) - result["format_kind"] = declaration.format_kind.value + result["format_kind"] = declaration.format_kind if declaration.format_option_id: result["format_option_ref"] = { "scope": "product", @@ -1243,7 +1243,7 @@ def resolve(context: CanonicalFormatLegacyResolutionContext) -> Sequence[LegacyF ( _normalized_publisher_domain(declaration.publisher_domain) or None, declaration.format_option_id, - declaration.format_kind.value, + declaration.format_kind, ) ) if ranked is not None and ranked[1] is None: diff --git a/src/adcp/canonical_formats/v1_to_v2.py b/src/adcp/canonical_formats/v1_to_v2.py index 3fd71d481..99b20fdd9 100644 --- a/src/adcp/canonical_formats/v1_to_v2.py +++ b/src/adcp/canonical_formats/v1_to_v2.py @@ -58,7 +58,6 @@ structural_match, ) from adcp.types import ( - CanonicalFormatKind, CanonicalProjectionReference, Error, ProductFormatDeclaration, @@ -207,7 +206,7 @@ def _v1_version_constraints( def _build_declaration( *, - kind: CanonicalFormatKind, + kind: str, v1_format_id: FormatId, params: dict[str, Any] | None = None, canonical_ref: CanonicalProjectionReference | None = None, @@ -280,7 +279,7 @@ def project_v1_format_to_declaration( # declarations must not inherit registry slot or pixel-density # contracts. registry_params: dict[str, Any] = {} - registry_kind: CanonicalFormatKind | None = None + registry_kind: str | None = None for mapping in registry.mappings: pattern = mapping.v1_pattern glob = getattr(pattern, "format_id_glob", None) @@ -356,7 +355,7 @@ def project_v1_format_to_declaration( code="FORMAT_DECLARATION_V1_AMBIGUOUS", message=( f"v1 format {fid.id!r} structurally matched the " - f"{first.v2.canonical.value!r} family but the registry " + f"{first.v2.canonical!r} family but the registry " f"entry is pure-structural — the projection is a " f"family-level guess. Seller SHOULD add an explicit " f"``canonical`` annotation on the v1 format." @@ -364,7 +363,7 @@ def project_v1_format_to_declaration( field=field_path, details={ "v1_format_id": _echo_identifier(fid.id), - "matched_canonical": first.v2.canonical.value, + "matched_canonical": first.v2.canonical, "match_kind": "structural_family", "candidate_count": len(structural_hits), }, diff --git a/src/adcp/canonical_formats/v2_to_v1.py b/src/adcp/canonical_formats/v2_to_v1.py index 73ac39ae0..10f1ed6ad 100644 --- a/src/adcp/canonical_formats/v2_to_v1.py +++ b/src/adcp/canonical_formats/v2_to_v1.py @@ -57,23 +57,27 @@ # format with no seller-asserted ``v1_format_ref[]`` has no projection # target — but a custom declaration MAY carry ``v1_format_ref`` and # project via step 2. -V1_TRANSLATABLE: dict[CanonicalFormatKind, bool] = { - CanonicalFormatKind.image: True, - CanonicalFormatKind.html5: True, - CanonicalFormatKind.display_tag: True, - CanonicalFormatKind.image_carousel: False, - CanonicalFormatKind.video_hosted: True, - CanonicalFormatKind.video_vast: True, - CanonicalFormatKind.audio_hosted: True, - CanonicalFormatKind.audio_vast: True, - CanonicalFormatKind.audio_daast: True, - CanonicalFormatKind.sponsored_placement: False, - CanonicalFormatKind.native_in_feed: True, - CanonicalFormatKind.responsive_creative: False, - CanonicalFormatKind.agent_placement: False, - CanonicalFormatKind.seller_rendered_stateful_display: False, - CanonicalFormatKind.coordinated_placements: False, - CanonicalFormatKind.custom: False, +# Keyed by the vocabulary's STRING values: ``format_kind`` is a ``str`` at +# every reference (see adcp.types.canonical_creative), and a declaration +# may legitimately carry a kind promoted after this SDK's release — for +# which ``.get(kind, True)`` is the right answer. +V1_TRANSLATABLE: dict[str, bool] = { + CanonicalFormatKind.image.value: True, + CanonicalFormatKind.html5.value: True, + CanonicalFormatKind.display_tag.value: True, + CanonicalFormatKind.image_carousel.value: False, + CanonicalFormatKind.video_hosted.value: True, + CanonicalFormatKind.video_vast.value: True, + CanonicalFormatKind.audio_hosted.value: True, + CanonicalFormatKind.audio_vast.value: True, + CanonicalFormatKind.audio_daast.value: True, + CanonicalFormatKind.sponsored_placement.value: False, + CanonicalFormatKind.native_in_feed.value: True, + CanonicalFormatKind.responsive_creative.value: False, + CanonicalFormatKind.agent_placement.value: False, + CanonicalFormatKind.seller_rendered_stateful_display.value: False, + CanonicalFormatKind.coordinated_placements.value: False, + CanonicalFormatKind.custom.value: False, } @@ -154,7 +158,7 @@ def project_declaration_to_v1( sizes_n = _params_sizes_count(declaration) if sizes_n > len(refs): details: dict[str, Any] = { - "format_kind": kind.value, + "format_kind": kind, "v1_format_ref_count": len(refs), "sizes_count": sizes_n, } @@ -185,7 +189,7 @@ def project_declaration_to_v1( # Step 4: canonical IS v1-translatable but seller didn't author refs. details = { - "format_kind": kind.value, + "format_kind": kind, "reason": "no_v1_format_ref", } if product_id is not None: @@ -195,7 +199,7 @@ def project_declaration_to_v1( make_sdk_advisory( code="FORMAT_DECLARATION_V1_AMBIGUOUS", message=( - f"Canonical '{kind.value}' is normally v1-translatable but the " + f"Canonical '{kind}' is normally v1-translatable but the " f"declaration carries no v1_format_ref[] — SDK cannot synthesize " f"a v1 format_id without seller assertion." ), diff --git a/src/adcp/capabilities.py b/src/adcp/capabilities.py index 3fb44aa9c..d981893b6 100644 --- a/src/adcp/capabilities.py +++ b/src/adcp/capabilities.py @@ -7,7 +7,7 @@ from __future__ import annotations # GetAdcpCapabilitiesResponse is under TYPE_CHECKING to avoid a circular import -# (adcp.types imports from generated_poc which imports from adcp.types.base). +# (adcp.types imports from adcp.types.domains which imports from adcp.types.base). # This is safe because `from __future__ import annotations` makes all annotations # strings that are never evaluated at runtime. from typing import TYPE_CHECKING, Any @@ -17,7 +17,7 @@ from adcp.exceptions import ADCPFeatureUnsupportedError if TYPE_CHECKING: - from adcp.types.generated_poc.protocol.get_adcp_capabilities_response import ( + from adcp.types.domains.protocol.get_adcp_capabilities_response import ( GetAdcpCapabilitiesResponse, ) @@ -204,7 +204,7 @@ def __init__(self, capabilities: GetAdcpCapabilitiesResponse) -> None: # Pre-compute the set of valid protocol names so supports() doesn't # need a runtime import on every call. - from adcp.types.generated_poc.protocol.get_adcp_capabilities_response import ( + from adcp.types.domains.protocol.get_adcp_capabilities_response import ( SupportedProtocol, ) diff --git a/src/adcp/client.py b/src/adcp/client.py index 81757d1dc..855e2319d 100644 --- a/src/adcp/client.py +++ b/src/adcp/client.py @@ -175,231 +175,231 @@ ) # V3 Governance (Sync Governance) types -from adcp.types.generated_poc.account.sync_governance_request import ( +from adcp.types.domains.account.sync_governance_request import ( SyncGovernanceRequest, ) -from adcp.types.generated_poc.account.sync_governance_response import ( +from adcp.types.domains.account.sync_governance_response import ( SyncGovernanceResponse, ) -from adcp.types.generated_poc.brand.acquire_rights_request import AcquireRightsRequest -from adcp.types.generated_poc.brand.acquire_rights_response import ( +from adcp.types.domains.brand.acquire_rights_request import AcquireRightsRequest +from adcp.types.domains.brand.acquire_rights_response import ( AcquireRightsResponse, ) -from adcp.types.generated_poc.brand.get_brand_identity_request import ( +from adcp.types.domains.brand.get_brand_identity_request import ( GetBrandIdentityRequest, ) -from adcp.types.generated_poc.brand.get_brand_identity_response import ( +from adcp.types.domains.brand.get_brand_identity_response import ( GetBrandIdentityResponse, ) -from adcp.types.generated_poc.brand.get_rights_request import GetRightsRequest -from adcp.types.generated_poc.brand.get_rights_response import GetRightsResponse -from adcp.types.generated_poc.brand.update_rights_request import UpdateRightsRequest -from adcp.types.generated_poc.brand.update_rights_response import ( +from adcp.types.domains.brand.get_rights_request import GetRightsRequest +from adcp.types.domains.brand.get_rights_response import GetRightsResponse +from adcp.types.domains.brand.update_rights_request import UpdateRightsRequest +from adcp.types.domains.brand.update_rights_response import ( UpdateRightsResponse, ) # V3 Governance (Collection Lists) types -from adcp.types.generated_poc.collection.create_collection_list_request import ( +from adcp.types.domains.collection.create_collection_list_request import ( CreateCollectionListRequest, ) -from adcp.types.generated_poc.collection.create_collection_list_response import ( +from adcp.types.domains.collection.create_collection_list_response import ( CreateCollectionListResponse, ) -from adcp.types.generated_poc.collection.delete_collection_list_request import ( +from adcp.types.domains.collection.delete_collection_list_request import ( DeleteCollectionListRequest, ) -from adcp.types.generated_poc.collection.delete_collection_list_response import ( +from adcp.types.domains.collection.delete_collection_list_response import ( DeleteCollectionListResponse, ) -from adcp.types.generated_poc.collection.get_collection_list_request import ( +from adcp.types.domains.collection.get_collection_list_request import ( GetCollectionListRequest, ) -from adcp.types.generated_poc.collection.get_collection_list_response import ( +from adcp.types.domains.collection.get_collection_list_response import ( GetCollectionListResponse, ) -from adcp.types.generated_poc.collection.list_collection_lists_request import ( +from adcp.types.domains.collection.list_collection_lists_request import ( ListCollectionListsRequest, ) -from adcp.types.generated_poc.collection.list_collection_lists_response import ( +from adcp.types.domains.collection.list_collection_lists_response import ( ListCollectionListsResponse, ) -from adcp.types.generated_poc.collection.update_collection_list_request import ( +from adcp.types.domains.collection.update_collection_list_request import ( UpdateCollectionListRequest, ) -from adcp.types.generated_poc.collection.update_collection_list_response import ( +from adcp.types.domains.collection.update_collection_list_response import ( UpdateCollectionListResponse, ) -from adcp.types.generated_poc.compliance.comply_test_controller_request import ( +from adcp.types.domains.compliance.comply_test_controller_request import ( ComplyTestControllerRequest, ) -from adcp.types.generated_poc.compliance.comply_test_controller_response import ( +from adcp.types.domains.compliance.comply_test_controller_response import ( ComplyTestControllerResponse, ) -from adcp.types.generated_poc.content_standards.calibrate_content_request import ( +from adcp.types.domains.content_standards.calibrate_content_request import ( CalibrateContentRequest, ) -from adcp.types.generated_poc.content_standards.calibrate_content_response import ( +from adcp.types.domains.content_standards.calibrate_content_response import ( CalibrateContentResponse, ) # V3 Content Standards types -from adcp.types.generated_poc.content_standards.create_content_standards_request import ( +from adcp.types.domains.content_standards.create_content_standards_request import ( CreateContentStandardsRequest, ) -from adcp.types.generated_poc.content_standards.create_content_standards_response import ( +from adcp.types.domains.content_standards.create_content_standards_response import ( CreateContentStandardsResponse, ) -from adcp.types.generated_poc.content_standards.get_content_standards_request import ( +from adcp.types.domains.content_standards.get_content_standards_request import ( GetContentStandardsRequest, ) -from adcp.types.generated_poc.content_standards.get_content_standards_response import ( +from adcp.types.domains.content_standards.get_content_standards_response import ( GetContentStandardsResponse, ) -from adcp.types.generated_poc.content_standards.get_media_buy_artifacts_request import ( +from adcp.types.domains.content_standards.get_media_buy_artifacts_request import ( GetMediaBuyArtifactsRequest, ) -from adcp.types.generated_poc.content_standards.get_media_buy_artifacts_response import ( +from adcp.types.domains.content_standards.get_media_buy_artifacts_response import ( GetMediaBuyArtifactsResponse, ) -from adcp.types.generated_poc.content_standards.list_content_standards_request import ( +from adcp.types.domains.content_standards.list_content_standards_request import ( ListContentStandardsRequest, ) -from adcp.types.generated_poc.content_standards.list_content_standards_response import ( +from adcp.types.domains.content_standards.list_content_standards_response import ( ListContentStandardsResponse, ) -from adcp.types.generated_poc.content_standards.update_content_standards_request import ( +from adcp.types.domains.content_standards.update_content_standards_request import ( UpdateContentStandardsRequest, ) -from adcp.types.generated_poc.content_standards.update_content_standards_response import ( +from adcp.types.domains.content_standards.update_content_standards_response import ( UpdateContentStandardsResponse, ) -from adcp.types.generated_poc.content_standards.validate_content_delivery_request import ( +from adcp.types.domains.content_standards.validate_content_delivery_request import ( ValidateContentDeliveryRequest, ) -from adcp.types.generated_poc.content_standards.validate_content_delivery_response import ( +from adcp.types.domains.content_standards.validate_content_delivery_response import ( ValidateContentDeliveryResponse, ) -from adcp.types.generated_poc.core.async_response_data import AdcpAsyncResponseData -from adcp.types.generated_poc.creative.get_creative_features_request import ( +from adcp.types.domains.core.async_response_data import AdcpAsyncResponseData +from adcp.types.domains.creative.get_creative_features_request import ( GetCreativeFeaturesRequest, ) -from adcp.types.generated_poc.creative.get_creative_features_response import ( +from adcp.types.domains.creative.get_creative_features_response import ( GetCreativeFeaturesResponse, ) -from adcp.types.generated_poc.creative.list_transformers_request import ( +from adcp.types.domains.creative.list_transformers_request import ( ListTransformersRequestCreativeAgent as ListTransformersRequest, ) -from adcp.types.generated_poc.creative.list_transformers_response import ( +from adcp.types.domains.creative.list_transformers_response import ( ListTransformersResponseCreativeAgent as ListTransformersResponse, ) # V3 Governance (Property Lists) types -from adcp.types.generated_poc.governance.check_governance_request import ( +from adcp.types.domains.governance.check_governance_request import ( CheckGovernanceRequest, ) -from adcp.types.generated_poc.governance.check_governance_response import ( +from adcp.types.domains.governance.check_governance_response import ( CheckGovernanceResponse, ) -from adcp.types.generated_poc.governance.get_plan_audit_logs_request import ( +from adcp.types.domains.governance.get_plan_audit_logs_request import ( GetPlanAuditLogsRequest, ) -from adcp.types.generated_poc.governance.get_plan_audit_logs_response import ( +from adcp.types.domains.governance.get_plan_audit_logs_response import ( GetPlanAuditLogsResponse, ) -from adcp.types.generated_poc.governance.report_plan_adjustment_request import ( +from adcp.types.domains.governance.report_plan_adjustment_request import ( ReportPlanAdjustmentRequest, ) -from adcp.types.generated_poc.governance.report_plan_adjustment_response import ( +from adcp.types.domains.governance.report_plan_adjustment_response import ( ReportPlanAdjustmentResponse, ) -from adcp.types.generated_poc.governance.report_plan_outcome_request import ( +from adcp.types.domains.governance.report_plan_outcome_request import ( ReportPlanOutcomeRequest, ) -from adcp.types.generated_poc.governance.report_plan_outcome_response import ( +from adcp.types.domains.governance.report_plan_outcome_response import ( ReportPlanOutcomeResponse, ) -from adcp.types.generated_poc.governance.sync_plans_request import SyncPlansRequest -from adcp.types.generated_poc.governance.sync_plans_response import SyncPlansResponse -from adcp.types.generated_poc.property.create_property_list_request import ( +from adcp.types.domains.governance.sync_plans_request import SyncPlansRequest +from adcp.types.domains.governance.sync_plans_response import SyncPlansResponse +from adcp.types.domains.property.create_property_list_request import ( CreatePropertyListRequest, ) -from adcp.types.generated_poc.property.create_property_list_response import ( +from adcp.types.domains.property.create_property_list_response import ( CreatePropertyListResponse, ) -from adcp.types.generated_poc.property.delete_property_list_request import ( +from adcp.types.domains.property.delete_property_list_request import ( DeletePropertyListRequest, ) -from adcp.types.generated_poc.property.delete_property_list_response import ( +from adcp.types.domains.property.delete_property_list_response import ( DeletePropertyListResponse, ) -from adcp.types.generated_poc.property.get_property_list_request import ( +from adcp.types.domains.property.get_property_list_request import ( GetPropertyListRequest, ) -from adcp.types.generated_poc.property.get_property_list_response import ( +from adcp.types.domains.property.get_property_list_response import ( GetPropertyListResponse, ) -from adcp.types.generated_poc.property.list_property_lists_request import ( +from adcp.types.domains.property.list_property_lists_request import ( ListPropertyListsRequest, ) -from adcp.types.generated_poc.property.list_property_lists_response import ( +from adcp.types.domains.property.list_property_lists_response import ( ListPropertyListsResponse, ) -from adcp.types.generated_poc.property.update_property_list_request import ( +from adcp.types.domains.property.update_property_list_request import ( UpdatePropertyListRequest, ) -from adcp.types.generated_poc.property.update_property_list_response import ( +from adcp.types.domains.property.update_property_list_response import ( UpdatePropertyListResponse, ) # V3 Protocol Discovery types -from adcp.types.generated_poc.protocol.get_adcp_capabilities_request import ( +from adcp.types.domains.protocol.get_adcp_capabilities_request import ( GetAdcpCapabilitiesRequest, ) -from adcp.types.generated_poc.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.protocol.get_adcp_capabilities_response import ( GetAdcpCapabilitiesResponse, ) -from adcp.types.generated_poc.protocol.get_task_status_request import GetTaskStatusRequest -from adcp.types.generated_poc.protocol.get_task_status_response import GetTaskStatusResponse -from adcp.types.generated_poc.protocol.list_tasks_request import ListTasksRequest -from adcp.types.generated_poc.protocol.list_tasks_response import ListTasksResponse -from adcp.types.generated_poc.protocol.sync_agent_notification_configs_request import ( +from adcp.types.domains.protocol.get_task_status_request import GetTaskStatusRequest +from adcp.types.domains.protocol.get_task_status_response import GetTaskStatusResponse +from adcp.types.domains.protocol.list_tasks_request import ListTasksRequest +from adcp.types.domains.protocol.list_tasks_response import ListTasksResponse +from adcp.types.domains.protocol.sync_agent_notification_configs_request import ( SyncAgentNotificationConfigsRequest, ) -from adcp.types.generated_poc.protocol.sync_agent_notification_configs_response import ( +from adcp.types.domains.protocol.sync_agent_notification_configs_response import ( SyncAgentNotificationConfigsResponse, ) # V3 Sponsored Intelligence types -from adcp.types.generated_poc.sponsored_intelligence.si_get_offering_request import ( +from adcp.types.domains.sponsored_intelligence.si_get_offering_request import ( SiGetOfferingRequest, ) -from adcp.types.generated_poc.sponsored_intelligence.si_get_offering_response import ( +from adcp.types.domains.sponsored_intelligence.si_get_offering_response import ( SiGetOfferingResponse, ) -from adcp.types.generated_poc.sponsored_intelligence.si_initiate_session_request import ( +from adcp.types.domains.sponsored_intelligence.si_initiate_session_request import ( SiInitiateSessionRequest, ) -from adcp.types.generated_poc.sponsored_intelligence.si_initiate_session_response import ( +from adcp.types.domains.sponsored_intelligence.si_initiate_session_response import ( SiInitiateSessionResponse, ) -from adcp.types.generated_poc.sponsored_intelligence.si_send_message_request import ( +from adcp.types.domains.sponsored_intelligence.si_send_message_request import ( SiSendMessageRequest, ) -from adcp.types.generated_poc.sponsored_intelligence.si_send_message_response import ( +from adcp.types.domains.sponsored_intelligence.si_send_message_response import ( SiSendMessageResponse, ) -from adcp.types.generated_poc.sponsored_intelligence.si_terminate_session_request import ( +from adcp.types.domains.sponsored_intelligence.si_terminate_session_request import ( SiTerminateSessionRequest, ) -from adcp.types.generated_poc.sponsored_intelligence.si_terminate_session_response import ( +from adcp.types.domains.sponsored_intelligence.si_terminate_session_response import ( SiTerminateSessionResponse, ) -from adcp.types.generated_poc.trusted_match.context_match_request import ContextMatchRequest -from adcp.types.generated_poc.trusted_match.context_match_response import ( +from adcp.types.domains.trusted_match.context_match_request import ContextMatchRequest +from adcp.types.domains.trusted_match.context_match_response import ( ContextMatchResponseRouterPublisher as ContextMatchResponse, ) -from adcp.types.generated_poc.trusted_match.identity_match_request import IdentityMatchRequest -from adcp.types.generated_poc.trusted_match.identity_match_response import IdentityMatchResponse +from adcp.types.domains.trusted_match.identity_match_request import IdentityMatchRequest +from adcp.types.domains.trusted_match.identity_match_response import IdentityMatchResponse from adcp.types.legacy import ( LegacyBuildCreativeRequest, LegacyBuildCreativeResponse, @@ -2091,7 +2091,7 @@ def _canonicalize_format_read_result( legacy_format_converter=self.legacy_format_converter, ) if projection.declaration is not None: - item["format_kind"] = projection.declaration.format_kind.value + item["format_kind"] = projection.declaration.format_kind elif projection.diagnostic is not None: diagnostic = projection.diagnostic.model_dump() diagnostics.append(diagnostic) @@ -6192,7 +6192,7 @@ async def _handle_mcp_webhook( ADCPWebhookSignatureError: If signature verification fails ValidationError: If payload doesn't match McpWebhookPayload schema """ - from adcp.types.generated_poc.core.mcp_webhook_payload import McpWebhookPayload + from adcp.types.domains.core.mcp_webhook_payload import McpWebhookPayload # Signed MCP webhooks are the secure default. Receiving without a # verifier requires an explicit compatibility opt-in so a missing diff --git a/src/adcp/decisioning/_get_products_helpers.py b/src/adcp/decisioning/_get_products_helpers.py index a582635ef..1eedf9fcb 100644 --- a/src/adcp/decisioning/_get_products_helpers.py +++ b/src/adcp/decisioning/_get_products_helpers.py @@ -34,11 +34,21 @@ from __future__ import annotations import logging +from collections.abc import Sequence from adcp.types import GetProductsField, GetProductsResponse, Product +from adcp.types.domains.media_buy.get_products_request import Fields as _RequestOnlyField +from adcp.types.domains.media_buy.product_fields import ProductResponseField logger = logging.getLogger(__name__) +#: What ``GetProductsRequest.fields`` actually declares: a union of two +#: generated enums (38 + 17 members). The public ``GetProductsField`` alias +#: resolves to a third, 53-member enum that the request never holds — one of +#: the names #1360 measured as typed a variant off its referent. Accept every +#: spelling: the projection reads only ``.value``, which all three carry. +_FieldSelection = GetProductsField | ProductResponseField | _RequestOnlyField + # --------------------------------------------------------------------------- # Module-level constants derived from the live model so they stay in sync # with schema regenerations. @@ -65,7 +75,7 @@ def _project_product_fields( response: GetProductsResponse, - fields: list[GetProductsField], + fields: Sequence[_FieldSelection], ) -> GetProductsResponse: """Drop unrequested product fields from *response*. diff --git a/src/adcp/decisioning/refine.py b/src/adcp/decisioning/refine.py index de44c2ac0..587fdbc54 100644 --- a/src/adcp/decisioning/refine.py +++ b/src/adcp/decisioning/refine.py @@ -54,7 +54,7 @@ class RefinementOutcome: NOT echo ``scope``, ``product_id``, or ``proposal_id`` manually. :param status: ``'applied'``, ``'partial'``, or ``'unable'`` per the - wire enum (see :class:`adcp.types.generated_poc.bundled.media_buy`'s + wire enum (see :class:`adcp.types.domains.bundled.media_buy`'s ``RefinementApplied.status``). :param notes: Adopter's explanation; recommended when status is ``'partial'`` or ``'unable'``. diff --git a/src/adcp/decisioning/resolve.py b/src/adcp/decisioning/resolve.py index fc21e7cb2..056494e67 100644 --- a/src/adcp/decisioning/resolve.py +++ b/src/adcp/decisioning/resolve.py @@ -14,7 +14,7 @@ paper over. See ``docs/proposals/decisioning-platform-dispatch-design.md#d15``. The :class:`Format` and :class:`PropertyListReference` types are -re-exported from :mod:`adcp.types.generated_poc` so adopters import +re-exported from :mod:`adcp.types.domains` so adopters import once from :mod:`adcp.decisioning`. :class:`PropertyList` and :class:`CollectionList` use the spec-defined wire shapes; the resolver returns the same Pydantic models adopters would construct @@ -28,7 +28,7 @@ # Wire types — already exported from adcp.types. Re-export for # one-stop import from adcp.decisioning. Per CLAUDE.md import # architecture rules, only adcp.types/{stable,aliases,_ergonomic} may -# import from generated_poc/; everywhere else uses the public +# import from the generated tree; everywhere else uses the public # adcp.types surface. from adcp.types import ( CollectionList, diff --git a/src/adcp/decisioning/state.py b/src/adcp/decisioning/state.py index d499f331d..c32b0957e 100644 --- a/src/adcp/decisioning/state.py +++ b/src/adcp/decisioning/state.py @@ -8,7 +8,7 @@ * :class:`WorkflowStep`, :class:`WorkflowObjectType`, :data:`GovernanceContextJWS` — framework-internal types referenced by :class:`StateReader` methods. Defined here (not in - ``adcp.types.generated_poc/``) because they're framework-only — + ``adcp.types.domains/``) because they're framework-only — not on the wire. * :class:`_NotYetWiredStateReader` — v6.0 stub. Returns type-correct empty values; emits a one-time :class:`UserWarning` per method on @@ -36,7 +36,7 @@ # Wire types referenced through the StateReader. ``Proposal`` is # exported from adcp.types; importing from there keeps the layering # rule in CLAUDE.md happy (only adcp.types/{stable,aliases,_ergonomic} -# may import from generated_poc/). +# may import from the generated tree). from adcp.types import Proposal #: Object types a workflow step can touch. Framework-internal — not on diff --git a/src/adcp/decisioning/types.py b/src/adcp/decisioning/types.py index d541d704a..3eee00efd 100644 --- a/src/adcp/decisioning/types.py +++ b/src/adcp/decisioning/types.py @@ -41,7 +41,7 @@ # TYPE_CHECKING because the generated types pull in the heavy # adcp.types dependency tree; the framework runs without forcing that # import at decisioning module load time. Imports go through the public -# adcp.types surface (not adcp.types.generated_poc) per the type-import +# adcp.types surface (not adcp.types.domains) per the type-import # layering rule documented in CLAUDE.md. if TYPE_CHECKING: from adcp.types import ( diff --git a/src/adcp/migrate/v3_to_v4.py b/src/adcp/migrate/v3_to_v4.py index 3e55fd08e..1817ac3f3 100644 --- a/src/adcp/migrate/v3_to_v4.py +++ b/src/adcp/migrate/v3_to_v4.py @@ -77,7 +77,7 @@ # jump straight to the replacement pattern. REMOVED_TYPES: dict[str, tuple[str, str]] = { "BrandManifest": ( - "use BrandReference(domain=...) on requests; " "read ResolvedBrand.brand from the registry", + "use BrandReference(domain=...) on requests; read ResolvedBrand.brand from the registry", "brandmanifest--brandreference", ), "FormatCategory": ( @@ -97,7 +97,7 @@ "promotedproducts--promotedofferings--offerings", ), "Pricing": ( - "use the discriminated *PricingOption classes " "(e.g. CpmFixedRatePricingOption)", + "use the discriminated *PricingOption classes (e.g. CpmFixedRatePricingOption)", "pricing--discriminated-pricingoption", ), "PackageStatus": ( @@ -231,11 +231,28 @@ def _proposed_generated_symbol_replacement(module: str, symbol: str) -> str | No def _replacement_is_identical(module: str, symbol: str, replacement: str) -> bool: - """Verify that a private class and its proposed public target are identical.""" + """Verify that a private class and its proposed public target are identical. + + The spelling this codemod DETECTS is the v3 one, + ``adcp.types.generated_poc.``. The module it IMPORTS is where those + classes live now: the generated tree moved to ``adcp.types.domains`` and + almost every module stem came with it unchanged, so the same ```` + resolves. Importing the v3 path here would raise ``ModuleNotFoundError``, + the except clause would swallow it, and every per-symbol rewrite would + silently degrade to a flag. + + The one stem that split is ``brand``: ``brand.json``'s classes are at + ``adcp.types.domains.brand_discovery`` while ``adcp.types.domains.brand`` + is the domain aggregator. Neither symbol map holds one of those classes + today, so nothing degrades — but a symbol added from that schema would + take the ``AttributeError`` branch and be flagged rather than rewritten, + and the fix is to resolve it against the discovery half, not to widen the + except clause. + """ if not module or not replacement.startswith("adcp.types."): return False try: - source_module = importlib.import_module(f"adcp.types.generated_poc.{module}") + source_module = importlib.import_module(f"adcp.types.domains.{module}") public_module = importlib.import_module("adcp.types") source = getattr(source_module, symbol) public = getattr(public_module, replacement.removeprefix("adcp.types.")) @@ -253,7 +270,7 @@ def _generated_symbol_replacement(module: str, symbol: str) -> str | None: def _unsafe_replacement_hint(module: str, symbol: str, replacement: str) -> str: source = f"adcp.types.generated_poc.{module}.{symbol}" - return f"SKIP: source {source} is not identical to {replacement} — " "manual rewrite required" + return f"SKIP: source {source} is not identical to {replacement} — manual rewrite required" # Regex for numbered Assets direct imports (``Assets5``, ``Assets14``, etc). diff --git a/src/adcp/signing/autosign.py b/src/adcp/signing/autosign.py index b1d9b8c8d..3999c8e98 100644 --- a/src/adcp/signing/autosign.py +++ b/src/adcp/signing/autosign.py @@ -23,7 +23,7 @@ from adcp.signing.crypto import ALG_ED25519, ALLOWED_ALGS, PrivateKey if TYPE_CHECKING: - from adcp.types.generated_poc.protocol.get_adcp_capabilities_response import ( + from adcp.types.domains.protocol.get_adcp_capabilities_response import ( RequestSigning, ) diff --git a/src/adcp/signing/client.py b/src/adcp/signing/client.py index 0f5101e26..bdfa3d8bb 100644 --- a/src/adcp/signing/client.py +++ b/src/adcp/signing/client.py @@ -68,7 +68,7 @@ if TYPE_CHECKING: import httpx - from adcp.types.generated_poc.protocol.get_adcp_capabilities_response import ( + from adcp.types.domains.protocol.get_adcp_capabilities_response import ( RequestSigning, ) diff --git a/src/adcp/types/__init__.py b/src/adcp/types/__init__.py index 9990ad51a..d7cc3cde9 100644 --- a/src/adcp/types/__init__.py +++ b/src/adcp/types/__init__.py @@ -21,7 +21,7 @@ from adcp.types.buyer import GetProductsRequest from adcp.types.seller import Offering, PropertyList -IMPORTANT: Never import directly from adcp.types.generated_poc or +IMPORTANT: Never import directly from adcp.types.domains or adcp.types._generated. These are internal modules regenerated from upstream schemas. Only import from adcp.types (this module), one of the partial modules above, or adcp. @@ -47,10 +47,19 @@ import importlib.util from typing import TYPE_CHECKING +from adcp.types import _generated_poc_alias + +# Serves the pre-9.0 ``adcp.types.generated_poc.*`` paths from the modules they +# moved to, as the SAME module objects, with a ``DeprecationWarning``. Removed +# in v10. Installing a finder imports no generated module, so this costs the +# lazy surface nothing. +_generated_poc_alias.install() + __pdoc__ = { "generated_poc": False, "mypy_plugin": False, "_eager": False, + "_generated_poc_alias": False, } __all__ = [ @@ -183,7 +192,6 @@ "ActivateSignalResponse", "ActivateSignalResponse1", "AdcpProtocol", - "AdcpVersionEnvelope", "AuthorizationRequiredDetails", "CreativeAction", "AggregatedTotals", @@ -204,6 +212,8 @@ "ListCreativesRequest", "ListCreativesResponse", "MediaBuyDelivery", + "AdcpVersionEnvelope", + "is_canonical_format_kind", "ProtocolEnvelope", "ProtocolResponse", "ProvidePerformanceFeedbackRequest", @@ -506,7 +516,10 @@ "Input", "KellerType", "LandingPageRequirement", + "LegacyCreateMediaBuyErrorResponse", "LegacyCreateMediaBuyRequest", + "LegacyCreateMediaBuySubmittedResponse", + "LegacyCreateMediaBuySuccessResponse", "LegacyBuildCreativeErrorResponse", "LegacyBuildCreativeRequest", "LegacyBuildCreativeResponse", @@ -542,7 +555,9 @@ "LegacyPreviewCreativeResponse1", "LegacyPreviewCreativeResponse2", "LegacyPreviewCreativeResponse3", + "LegacyPreviewCreativeResponse4", "LegacyPreviewCreativeSingleResponse", + "LegacyPreviewCreativeSubmittedResponse", "LegacyPreviewCreativeVariantResponse", "LegacyProduct", "LegacyProductFormatDeclaration", @@ -834,6 +849,7 @@ "UpdateContentStandardsSuccessResponse", "UpdateMediaBuyErrorResponse", "LegacyUpdateMediaBuyErrorResponse", + "LegacyUpdateMediaBuySubmittedResponse", "LegacyUpdateMediaBuySuccessResponse", "UpdateMediaBuyResponse1", "UpdateMediaBuyResponse3", @@ -1606,7 +1622,10 @@ def __dir__() -> list[str]: LegacyBuildCreativeResponse6, LegacyBuildCreativeSubmittedResponse, LegacyBuildCreativeSuccessResponse, + LegacyCreateMediaBuyErrorResponse, LegacyCreateMediaBuyRequest, + LegacyCreateMediaBuySubmittedResponse, + LegacyCreateMediaBuySuccessResponse, LegacyCreativeAsset, LegacyCreativeFilters, LegacyFormat, @@ -1631,7 +1650,9 @@ def __dir__() -> list[str]: LegacyPreviewCreativeResponse1, LegacyPreviewCreativeResponse2, LegacyPreviewCreativeResponse3, + LegacyPreviewCreativeResponse4, LegacyPreviewCreativeSingleResponse, + LegacyPreviewCreativeSubmittedResponse, LegacyPreviewCreativeVariantResponse, LegacyProduct, LegacyProductFilters, @@ -1640,6 +1661,7 @@ def __dir__() -> list[str]: LegacySyncCreativesRequest, LegacyUpdateMediaBuyErrorResponse, LegacyUpdateMediaBuyRequest, + LegacyUpdateMediaBuySubmittedResponse, LegacyUpdateMediaBuySuccessResponse, ListAccountChangesRequest, ListAccountChangesResponse, @@ -2111,6 +2133,7 @@ def __dir__() -> list[str]: WholesaleFeedSignal, WholesaleFeedWebhook, ZipAsset, + is_canonical_format_kind, project_geo_postal_areas, to_account_response, ) diff --git a/src/adcp/types/_eager.py b/src/adcp/types/_eager.py index 156e3bb3a..d1f785126 100644 --- a/src/adcp/types/_eager.py +++ b/src/adcp/types/_eager.py @@ -22,7 +22,7 @@ # Imported via ``import_module`` — a call, not a name binding — so they read as # the intentional side-effect imports they are rather than tripping # unused-import analysers. (The removed ``format_category`` submodule has a real -# shim at ``generated_poc/enums/format_category.py``, restored after codegen by +# shim at ``domains/enums/format_category.py``, restored after codegen by # ``scripts/post_generate_fixes.py``.) importlib.import_module("adcp.types._ergonomic") importlib.import_module("adcp.types._forward_compat") @@ -706,6 +706,7 @@ KeyValueActivationKey, LegacyProposalTotalBudgetGuidance, LegacyUpdateMediaBuyErrorResponse, + LegacyUpdateMediaBuySubmittedResponse, LegacyUpdateMediaBuySuccessResponse, ListContentStandardsErrorResponse, ListContentStandardsResponse1, @@ -873,7 +874,10 @@ LegacyBuildCreativeResponse6, LegacyBuildCreativeSubmittedResponse, LegacyBuildCreativeSuccessResponse, + LegacyCreateMediaBuyErrorResponse, LegacyCreateMediaBuyRequest, + LegacyCreateMediaBuySubmittedResponse, + LegacyCreateMediaBuySuccessResponse, LegacyCreativeAsset, LegacyCreativeFilters, LegacyFormat, @@ -898,7 +902,9 @@ LegacyPreviewCreativeResponse1, LegacyPreviewCreativeResponse2, LegacyPreviewCreativeResponse3, + LegacyPreviewCreativeResponse4, LegacyPreviewCreativeSingleResponse, + LegacyPreviewCreativeSubmittedResponse, LegacyPreviewCreativeVariantResponse, LegacyProduct, LegacyProductFilters, @@ -907,6 +913,7 @@ LegacyUpdateMediaBuyRequest, ) +is_canonical_format_kind = _canonical_creative.is_canonical_format_kind CreateMediaBuyResponse = _canonical_creative.CreateMediaBuyResponse CreateMediaBuyResponse1 = _canonical_creative.CreateMediaBuyResponse1 CreateMediaBuySuccessResponse = _canonical_creative.CreateMediaBuyResponse1 @@ -974,21 +981,21 @@ # type of the obsolete aggregate ``RootModel`` — documented constructor kwargs # get rejected. Importing the variant class by its generated name keeps the # runtime object and the static type identical. -from adcp.types.generated_poc.adagents import AuthorizedAgents1 as AuthorizedAgents +from adcp.types.domains.adagents import AuthorizedAgents1 as AuthorizedAgents # Deprecated compatibility types from the root brand discovery document. The # document collides with the ``brand/`` task-schema package, so codegen emits # it into a dedicated module. -from adcp.types.generated_poc.brand_discovery import Asset, Disclaimer, Fonts, ProductCatalog -from adcp.types.generated_poc.core.catalog_item_delivery_metrics import ( +from adcp.types.domains.brand_discovery import Asset, Disclaimer, Fonts, ProductCatalog +from adcp.types.domains.core.catalog_item_delivery_metrics import ( CatalogItemDeliveryMetrics as ByCatalogItemItem, ) -from adcp.types.generated_poc.core.outcome_measurement import OutcomeMeasurement -from adcp.types.generated_poc.enums.metric_type import MetricType +from adcp.types.domains.core.outcome_measurement import OutcomeMeasurement +from adcp.types.domains.enums.metric_type import MetricType # Status: _generated picks invoice status (get_account_financials_response) due to # alphabetical module sort. Import the delivery status variant directly for backward compat. -from adcp.types.generated_poc.media_buy.get_media_buy_delivery_response import ( # noqa: E501 +from adcp.types.domains.media_buy.get_media_buy_delivery_response import ( # noqa: E501 Status, ) @@ -1954,7 +1961,13 @@ def __init__(self, *args: object, **kwargs: object) -> None: "UpdateContentStandardsSuccessResponse", "UpdateFrequency", "UpdateMediaBuyErrorResponse", + "LegacyCreateMediaBuyErrorResponse", + "LegacyCreateMediaBuySubmittedResponse", + "LegacyCreateMediaBuySuccessResponse", + "LegacyPreviewCreativeResponse4", + "LegacyPreviewCreativeSubmittedResponse", "LegacyUpdateMediaBuyErrorResponse", + "LegacyUpdateMediaBuySubmittedResponse", "LegacyUpdateMediaBuySuccessResponse", "UpdateMediaBuyPackagesRequest", "UpdateMediaBuyPropertiesRequest", @@ -2073,6 +2086,7 @@ def __init__(self, *args: object, **kwargs: object) -> None: "is_sync_creatives_success", "is_update_media_buy_error", "is_update_media_buy_success", + "is_canonical_format_kind", "is_validate_content_delivery_success", "project_geo_postal_areas", "to_account_response", diff --git a/src/adcp/types/_ergonomic.py b/src/adcp/types/_ergonomic.py index b2832172b..76c054cb4 100644 --- a/src/adcp/types/_ergonomic.py +++ b/src/adcp/types/_ergonomic.py @@ -46,71 +46,70 @@ coerce_to_model, ) -from adcp.types.generated_poc.core.canonical_format_kind import CanonicalFormatKind -from adcp.types.generated_poc.core.context import ContextObject -from adcp.types.generated_poc.core.creative_asset import CreativeAsset -from adcp.types.generated_poc.core.creative_assignment import CreativeAssignment -from adcp.types.generated_poc.core.error import Error -from adcp.types.generated_poc.core.ext import ExtensionObject -from adcp.types.generated_poc.core.format import Format -from adcp.types.generated_poc.core.package import Package -from adcp.types.generated_poc.core.product import Product -from adcp.types.generated_poc.enums.advertiser_industry import AdvertiserIndustry -from adcp.types.generated_poc.enums.asset_content_type import AssetContentType -from adcp.types.generated_poc.enums.creative_sort_field import CreativeSortField -from adcp.types.generated_poc.enums.delivery_type import DeliveryType -from adcp.types.generated_poc.enums.disclosure_persistence import DisclosurePersistence -from adcp.types.generated_poc.enums.disclosure_position import DisclosurePosition -from adcp.types.generated_poc.enums.media_buy_status import MediaBuyStatus -from adcp.types.generated_poc.enums.media_buy_valid_action import MediaBuyValidAction -from adcp.types.generated_poc.enums.pacing import Pacing -from adcp.types.generated_poc.enums.sort_direction import SortDirection -from adcp.types.generated_poc.enums.task_status import TaskStatus -from adcp.types.generated_poc.enums.wcag_level import WcagLevel -from adcp.types.generated_poc.media_buy.create_media_buy_request import ( +from adcp.types.domains.core.context import ContextObject +from adcp.types.domains.core.creative_asset import CreativeAsset +from adcp.types.domains.core.creative_assignment import CreativeAssignment +from adcp.types.domains.core.error import Error +from adcp.types.domains.core.ext import ExtensionObject +from adcp.types.domains.core.format import Format +from adcp.types.domains.core.package import Package +from adcp.types.domains.core.product import Product +from adcp.types.domains.enums.advertiser_industry import AdvertiserIndustry +from adcp.types.domains.enums.asset_content_type import AssetContentType +from adcp.types.domains.enums.creative_sort_field import CreativeSortField +from adcp.types.domains.enums.delivery_type import DeliveryType +from adcp.types.domains.enums.disclosure_persistence import DisclosurePersistence +from adcp.types.domains.enums.disclosure_position import DisclosurePosition +from adcp.types.domains.enums.media_buy_status import MediaBuyStatus +from adcp.types.domains.enums.media_buy_valid_action import MediaBuyValidAction +from adcp.types.domains.enums.pacing import Pacing +from adcp.types.domains.enums.sort_direction import SortDirection +from adcp.types.domains.enums.task_status import TaskStatus +from adcp.types.domains.enums.wcag_level import WcagLevel +from adcp.types.domains.media_buy.create_media_buy_request import ( CreateMediaBuyRequest, ) -from adcp.types.generated_poc.media_buy.get_products_request import ( +from adcp.types.domains.media_buy.get_products_request import ( GetProductsRequest, ) -from adcp.types.generated_poc.media_buy.list_creative_formats_request import ( +from adcp.types.domains.media_buy.list_creative_formats_request import ( ListCreativeFormatsRequest, ) -from adcp.types.generated_poc.creative.list_creative_formats_request import ( +from adcp.types.domains.creative.list_creative_formats_request import ( ListCreativeFormatsRequestCreativeAgent, ) -from adcp.types.generated_poc.creative.list_creatives_request import ( +from adcp.types.domains.creative.list_creatives_request import ( Field1 as ListCreativesField, ListCreativesRequest, Sort, ) -from adcp.types.generated_poc.media_buy.package_request import PackageRequest -from adcp.types.generated_poc.media_buy.package_update import PackageUpdate -from adcp.types.generated_poc.media_buy.create_media_buy_response import ( +from adcp.types.domains.media_buy.package_request import PackageRequest +from adcp.types.domains.media_buy.package_update import PackageUpdate +from adcp.types.domains.media_buy.create_media_buy_response import ( CreateMediaBuyResponse1, ) -from adcp.types.generated_poc.media_buy.update_media_buy_response import ( +from adcp.types.domains.media_buy.update_media_buy_response import ( UpdateMediaBuyResponse1, ) -from adcp.types.generated_poc.media_buy.get_media_buy_delivery_response import ( +from adcp.types.domains.media_buy.get_media_buy_delivery_response import ( GetMediaBuyDeliveryResponse, MediaBuyDelivery, NotificationType, ) -from adcp.types.generated_poc.media_buy.get_products_response import GetProductsResponse -from adcp.types.generated_poc.media_buy.list_creative_formats_response import ( +from adcp.types.domains.media_buy.get_products_response import GetProductsResponse +from adcp.types.domains.media_buy.list_creative_formats_response import ( CreativeAgent, ListCreativeFormatsResponse, ) -from adcp.types.generated_poc.creative.list_creatives_response import ( +from adcp.types.domains.creative.list_creatives_response import ( ListCreativesResponse, ) -from adcp.types.generated_poc.creative.list_creatives_request import AssignmentProjection -from adcp.types.generated_poc.media_buy.get_products_request import BuyingMode -from adcp.types.generated_poc.media_buy.get_products_response import CacheScope -from adcp.types.generated_poc.creative.list_creatives_response import Creative -from adcp.types.generated_poc.media_buy.list_creative_formats_response import Source -from adcp.types.generated_poc.creative.list_creative_formats_request import Type +from adcp.types.domains.creative.list_creatives_request import AssignmentProjection +from adcp.types.domains.media_buy.get_products_request import BuyingMode +from adcp.types.domains.media_buy.get_products_response import CacheScope +from adcp.types.domains.creative.list_creatives_response import Creative +from adcp.types.domains.media_buy.list_creative_formats_response import Source +from adcp.types.domains.creative.list_creative_formats_request import Type def _apply_coercion() -> None: @@ -287,17 +286,11 @@ def _apply_coercion() -> None: GetProductsRequest.model_rebuild(force=True) # Apply coercion to PackageRequest - # - format_kind: CanonicalFormatKind | str | None # - pacing: Pacing | str | None # - creative_assignments: list[CreativeAssignment] (accepts subclass instances) # - creatives: Sequence[CreativeAsset] (accepts subclass instances) # - context: ContextObject | dict | None # - ext: ExtensionObject | dict | None - _patch_field_annotation( - PackageRequest, - "format_kind", - Annotated[CanonicalFormatKind | None, BeforeValidator(coerce_to_enum(CanonicalFormatKind))], - ) _patch_field_annotation( PackageRequest, "pacing", diff --git a/src/adcp/types/_forward_compat.py b/src/adcp/types/_forward_compat.py index 5b33c2f82..092635350 100644 --- a/src/adcp/types/_forward_compat.py +++ b/src/adcp/types/_forward_compat.py @@ -6,8 +6,8 @@ of ValidationErrors that zero out the entire list_creative_formats response. This module is intentionally NOT auto-generated. It lives outside -generated_poc/ and is preserved across codegen runs (generate_types.py only -wipes src/adcp/types/generated_poc/). +the generated tree and is preserved across codegen runs (generate_types.py only +wipes src/adcp/types/domains/). It also replaces identity-distinct bundled clones at public capability boundaries with their canonical public model classes. This keeps independently @@ -22,7 +22,7 @@ so there is no circular-import risk and no strict ordering requirement against aliases in __init__.py. -Import layering: this module imports directly from generated_poc (like +Import layering: this module imports directly from the generated tree (like aliases.py and _ergonomic.py) because it must patch the generated classes in-place. It is therefore listed in ALLOWED_FILES in test_import_layering.py. """ @@ -58,89 +58,82 @@ from adcp.types.canonical_creative import PackageRequest as PublicPackageRequest from adcp.types.canonical_creative import PackageUpdate as PublicPackageUpdate from adcp.types.canonical_creative import UpdateMediaBuyRequest as _PublicUpdateMediaBuyRequest -from adcp.types.generated_poc.bundled.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.bundled.protocol.get_adcp_capabilities_response import ( AcceptancePolicyDiscovery as BundledAcceptancePolicyDiscovery, ) -from adcp.types.generated_poc.bundled.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.bundled.protocol.get_adcp_capabilities_response import ( MediaBuy as BundledCapabilitiesMediaBuy, ) -from adcp.types.generated_poc.bundled.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.bundled.protocol.get_adcp_capabilities_response import ( Portfolio as BundledCapabilitiesPortfolio, ) -from adcp.types.generated_poc.bundled.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.bundled.protocol.get_adcp_capabilities_response import ( PrimaryCountry as BundledPrimaryCountry, ) -from adcp.types.generated_poc.bundled.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.bundled.protocol.get_adcp_capabilities_response import ( PublisherDomain as BundledPublisherDomain, ) -from adcp.types.generated_poc.core.canonical_format_kind import CanonicalFormatKind -from adcp.types.generated_poc.core.canonical_product import PublisherDomain -from adcp.types.generated_poc.core.creative_manifest import CreativeManifest -from adcp.types.generated_poc.core.creative_variant import CreativeVariant -from adcp.types.generated_poc.core.format import Format -from adcp.types.generated_poc.core.mcp_webhook_payload import McpWebhookPayload -from adcp.types.generated_poc.core.media_buy_features import MediaBuyFeatures -from adcp.types.generated_poc.core.reporting_webhook import ( +from adcp.types.domains.core.canonical_product import PublisherDomain +from adcp.types.domains.core.creative_manifest import CreativeManifest +from adcp.types.domains.core.creative_variant import CreativeVariant +from adcp.types.domains.core.format import Format +from adcp.types.domains.core.mcp_webhook_payload import McpWebhookPayload +from adcp.types.domains.core.media_buy_features import MediaBuyFeatures +from adcp.types.domains.core.reporting_webhook import ( ReportingWebhook as _GeneratedReportingWebhook, ) -from adcp.types.generated_poc.core.targeting import TargetingOverlay -from adcp.types.generated_poc.core.targeting_input import TargetingOverlayInput -from adcp.types.generated_poc.core.version_envelope import AdcpVersionEnvelope -from adcp.types.generated_poc.creative.get_creative_delivery_response import ( +from adcp.types.domains.core.targeting import TargetingOverlay +from adcp.types.domains.core.targeting_input import TargetingOverlayInput +from adcp.types.domains.core.version_envelope import AdcpVersionEnvelope +from adcp.types.domains.creative.get_creative_delivery_response import ( Creative as DeliveryCreative, ) -from adcp.types.generated_poc.creative.get_creative_delivery_response import ( +from adcp.types.domains.creative.get_creative_delivery_response import ( GetCreativeDeliveryResponse, ) -from adcp.types.generated_poc.creative.preview_creative_response import PreviewCreativeResponse3 -from adcp.types.generated_poc.media_buy.accept_proposal_request import ( +from adcp.types.domains.creative.preview_creative_response import PreviewCreativeResponse3 +from adcp.types.domains.media_buy.accept_proposal_request import ( AcceptProposalRequest as _GeneratedAcceptProposalRequest, ) -from adcp.types.generated_poc.media_buy.build_creative_response import ( +from adcp.types.domains.media_buy.build_creative_response import ( BuildCreativeResponse1, BuildCreativeResponse3, BuildCreativeResponse4, ) -from adcp.types.generated_poc.media_buy.build_creative_response import ( +from adcp.types.domains.media_buy.build_creative_response import ( Creative as BuildCreative, ) -from adcp.types.generated_poc.media_buy.build_creative_response import ( +from adcp.types.domains.media_buy.build_creative_response import ( Variant as BuildCreativeVariant, ) -from adcp.types.generated_poc.media_buy.buy_products_request import ( +from adcp.types.domains.media_buy.buy_products_request import ( BuyProductsRequest as _GeneratedBuyProductsRequest, ) -from adcp.types.generated_poc.media_buy.control_media_buy_request import ( +from adcp.types.domains.media_buy.control_media_buy_request import ( ControlMediaBuyRequest as _GeneratedControlMediaBuyRequest, ) -from adcp.types.generated_poc.media_buy.create_media_buy_request import ( +from adcp.types.domains.media_buy.create_media_buy_request import ( CreateMediaBuyRequest as _GeneratedCreateMediaBuyRequest, ) -from adcp.types.generated_poc.media_buy.package_control import PackageControl -from adcp.types.generated_poc.media_buy.package_request import PackageRequest -from adcp.types.generated_poc.media_buy.package_update import PackageUpdate -from adcp.types.generated_poc.media_buy.product_purchase_input import ProductPurchaseInput -from adcp.types.generated_poc.media_buy.update_media_buy_request import ( +from adcp.types.domains.media_buy.package_control import PackageControl +from adcp.types.domains.media_buy.package_request import PackageRequest +from adcp.types.domains.media_buy.package_update import PackageUpdate +from adcp.types.domains.media_buy.product_purchase_input import ProductPurchaseInput +from adcp.types.domains.media_buy.update_media_buy_request import ( UpdateMediaBuyRequest as _GeneratedUpdateMediaBuyRequest, ) -from adcp.types.generated_poc.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.protocol.get_adcp_capabilities_response import ( AcceptancePolicyDiscovery, PrimaryCountry, ) -from adcp.types.generated_poc.trusted_match.context_match_response import ( +from adcp.types.domains.trusted_match.context_match_response import ( ContextMatchResponseRouterPublisher, ) -from adcp.types.generated_poc.trusted_match.offer import Offer -from adcp.types.generated_poc.trusted_match.provider_context_match_response import ( +from adcp.types.domains.trusted_match.offer import Offer +from adcp.types.domains.trusted_match.provider_context_match_response import ( ContextMatchResponseProviderRouter, ) -_OpenCanonicalFormatKind = Annotated[ - CanonicalFormatKind | str, - Field(union_mode="left_to_right"), -] - - _ReportingOperationId = Annotated[ str | None, Field( @@ -240,10 +233,15 @@ def _normalize_readback_manifest(data: Any) -> Any: _ReadbackCreativeManifest = _manifest_readback_clone( "_ReadbackCreativeManifest", CreativeManifest, - {"format_kind": _OpenCanonicalFormatKind | None}, + # No field override: ``format_kind`` is generated as ``str`` (see + # OPEN_VOCABULARY_SCHEMAS in scripts/generate_types.py), so there is nothing + # left to widen. What this clone still carries is instance normalization — + # a readback node accepts a manifest INSTANCE of a sibling class, which a + # plain pydantic model field does not. + {}, validators={ # Preserve the generated manifest's standalone-asset normalization, - # without widening that input model or inheriting from it. + # without inheriting from the input model. "_coerce_standalone_assets": model_validator(mode="before")(_normalize_readback_manifest), }, ) @@ -467,15 +465,15 @@ def _apply_forward_compat() -> None: ): request.model_rebuild(force=True) - # All response manifests retain unknown future kinds. Patch the generated - # response classes themselves so public aliases and indirect wrappers agree; - # public/generated input manifests and direct Creative/CreativeAsset fields - # stay strict. Private readback nodes cannot bypass strict input validation. - _patch_model_field( - DeliveryCreative, - "format_kind", - _OpenCanonicalFormatKind | None, - ) + # Response manifests retain unknown future kinds because the field is + # ``str`` everywhere. These patches thread the readback NODES through the + # generated response classes, which is what lets a response accept a + # manifest instance of a sibling class. Nothing here is about strictness: + # no model refuses a format kind in either direction, and checking the + # vocabulary is the caller's through ``adcp.types.is_canonical_format_kind`` + # (see adcp.types.canonical_creative). + # ``format_kind`` needs no patch: it is generated as ``str``, so a response + # retains an unknown kind without one. _patch_model_field(DeliveryCreative, "variants", GenericAlias(list, _DeliveryVariant)) DeliveryCreative.model_rebuild(force=True) GetCreativeDeliveryResponse.model_rebuild(force=True) diff --git a/src/adcp/types/_generated.py b/src/adcp/types/_generated.py index af7c7ed6f..b49051c4f 100644 --- a/src/adcp/types/_generated.py +++ b/src/adcp/types/_generated.py @@ -3,7 +3,7 @@ DO NOT import from this module directly. Use 'from adcp import Type' or 'from adcp.types import Type' instead. -This module consolidates all generated types from generated_poc/ into a single +This module consolidates all generated types from adcp.types.domains into a namespace for convenience. The leading underscore signals this is private API. A bare type name that several generated modules define resolves to one class @@ -14,231 +14,226 @@ DO NOT EDIT MANUALLY. Generated from: https://github.com/adcontextprotocol/adcp/tree/main/schemas -Generation date: 2026-10-04 01:57:48 UTC +Generation date: 2026-10-04 18:45:09 UTC """ # ruff: noqa: E501, I001 from __future__ import annotations -# Import all types from generated_poc modules -from adcp.types.generated_poc.enums.account_currency_mode import AccountCurrencyMode -from adcp.types.generated_poc.enums.account_scope import AccountScope -from adcp.types.generated_poc.enums.account_status import AccountStatus -from adcp.types.generated_poc.enums.action_not_allowed_reason import ActionNotAllowedReason -from adcp.types.generated_poc.enums.action_source import ActionSource -from adcp.types.generated_poc.enums.activation_method import CreativeActivationMethod -from adcp.types.generated_poc.enums.adcp_protocol import AdcpProtocol -from adcp.types.generated_poc.enums.adjustment_kind import PriceAdjustmentKind -from adcp.types.generated_poc.enums.advertiser_industry import AdvertiserIndustry -from adcp.types.generated_poc.enums.age_determination_basis import AgeDeterminationBasis -from adcp.types.generated_poc.enums.age_verification_method import AgeVerificationMethod -from adcp.types.generated_poc.enums.assessment_status import AssessmentStatus -from adcp.types.generated_poc.enums.asset_content_type import AssetContentType -from adcp.types.generated_poc.enums.attestation_claim import AttestationClaim -from adcp.types.generated_poc.enums.attribution_methodology import AttributionMethodology -from adcp.types.generated_poc.enums.attribution_model import AttributionModel -from adcp.types.generated_poc.enums.audience_evidence_methodology import AudienceEvidenceMethodology -from adcp.types.generated_poc.enums.audience_resolution_method import AudienceResolutionMethod -from adcp.types.generated_poc.enums.audience_source import AudienceSource -from adcp.types.generated_poc.enums.audience_status import AudienceStatus -from adcp.types.generated_poc.enums.audience_subject_type import AudienceSubjectType -from adcp.types.generated_poc.enums.audio_channel_layout import AudioChannelLayout -from adcp.types.generated_poc.enums.audio_distribution_type import AudioDistributionType -from adcp.types.generated_poc.enums.auth_scheme import AuthenticationScheme -from adcp.types.generated_poc.enums.availability_status import AvailabilityStatus -from adcp.types.generated_poc.enums.available_metric import AvailableMetric -from adcp.types.generated_poc.enums.billing_party import BillingParty -from adcp.types.generated_poc.enums.binary_verdict import BinaryVerdict -from adcp.types.generated_poc.enums.brand_agent_type import BrandAgentType -from adcp.types.generated_poc.enums.browser_family import BrowserFamily -from adcp.types.generated_poc.enums.c2pa_watermark_action import C2PaWatermarkAction -from adcp.types.generated_poc.enums.canceled_by import CanceledBy -from adcp.types.generated_poc.enums.canonical_media_buy_action import CanonicalMediaBuyActionName -from adcp.types.generated_poc.enums.canonical_media_buy_action_mode import ( - CanonicalMediaBuyActionMode, -) -from adcp.types.generated_poc.enums.catalog_action import CatalogAction -from adcp.types.generated_poc.enums.catalog_item_status import CatalogItemStatus -from adcp.types.generated_poc.enums.catalog_type import CatalogType -from adcp.types.generated_poc.enums.channels import MediaChannel -from adcp.types.generated_poc.enums.cloud_storage_protocol import CloudStorageProtocol -from adcp.types.generated_poc.enums.co_branding_requirement import CoBrandingRequirement -from adcp.types.generated_poc.enums.collection_cadence import CollectionCadence -from adcp.types.generated_poc.enums.collection_kind import CollectionKind -from adcp.types.generated_poc.enums.collection_relationship import CollectionRelationship -from adcp.types.generated_poc.enums.collection_status import CollectionStatus -from adcp.types.generated_poc.enums.completion_source import CompletionSource -from adcp.types.generated_poc.enums.consent_basis import ConsentBasis -from adcp.types.generated_poc.enums.content_id_type import ContentIdType -from adcp.types.generated_poc.enums.content_rating_system import ContentRatingSystem -from adcp.types.generated_poc.enums.creative_action import CreativeAction -from adcp.types.generated_poc.enums.creative_agent_capability import CreativeAgentCapability -from adcp.types.generated_poc.enums.creative_approval_status import CreativeApprovalStatus -from adcp.types.generated_poc.enums.creative_event_reason_code import CreativeEventReasonCode -from adcp.types.generated_poc.enums.creative_identifier_type import CreativeIdentifierType -from adcp.types.generated_poc.enums.creative_quality import CreativeQuality -from adcp.types.generated_poc.enums.creative_selection_strategy import CreativeSelectionStrategy -from adcp.types.generated_poc.enums.creative_sort_field import CreativeSortField -from adcp.types.generated_poc.enums.creative_status import CreativeStatus -from adcp.types.generated_poc.enums.ctv_ad_experience import CtvAdExperience -from adcp.types.generated_poc.enums.daast_tracking_event import DaastTrackingEvent -from adcp.types.generated_poc.enums.daast_version import DaastVersion -from adcp.types.generated_poc.enums.day_of_week import DayOfWeek -from adcp.types.generated_poc.enums.daypart_timezone_mode import DaypartTimezoneMode -from adcp.types.generated_poc.enums.delegation_authority import DelegationAuthority -from adcp.types.generated_poc.enums.delivery_recipient_cloud import DeliveryRecipientCloud -from adcp.types.generated_poc.enums.delivery_status import DeliveryStatus -from adcp.types.generated_poc.enums.delivery_type import DeliveryType -from adcp.types.generated_poc.enums.demographic_system import DemographicSystem -from adcp.types.generated_poc.enums.derivative_type import DerivativeType -from adcp.types.generated_poc.enums.device_platform import DevicePlatform -from adcp.types.generated_poc.enums.device_type import DeviceType -from adcp.types.generated_poc.enums.digital_source_type import DigitalSourceType -from adcp.types.generated_poc.enums.dimension_unit import DimensionUnit -from adcp.types.generated_poc.enums.disclosure_persistence import DisclosurePersistence -from adcp.types.generated_poc.enums.disclosure_position import DisclosurePosition -from adcp.types.generated_poc.enums.distance_unit import DistanceUnit -from adcp.types.generated_poc.enums.distribution_identifier_type import DistributionIdentifierType -from adcp.types.generated_poc.enums.dooh_motion_type import DoohMotionType -from adcp.types.generated_poc.enums.embedded_provenance_method import EmbeddedProvenanceMethod -from adcp.types.generated_poc.enums.error_code import ErrorCode -from adcp.types.generated_poc.enums.error_scope import ErrorScope -from adcp.types.generated_poc.enums.escalation_severity import EscalationSeverity -from adcp.types.generated_poc.enums.event_type import EventType -from adcp.types.generated_poc.enums.exclusivity import Exclusivity -from adcp.types.generated_poc.enums.feature_check_status import FeatureCheckStatus -from adcp.types.generated_poc.enums.feed_format import FeedFormat -from adcp.types.generated_poc.enums.feedback_source import FeedbackSource -from adcp.types.generated_poc.enums.forecast_method import ForecastMethod -from adcp.types.generated_poc.enums.forecast_range_unit import ForecastRangeUnit -from adcp.types.generated_poc.enums.forecastable_metric import ForecastableMetric -from adcp.types.generated_poc.enums.format_id_parameter import FormatIdParameter -from adcp.types.generated_poc.enums.frame_rate_type import FrameRateType -from adcp.types.generated_poc.enums.frequency_cap_control_mode import FrequencyCapControlMode -from adcp.types.generated_poc.enums.frequency_cap_mutable_field import FrequencyCapMutableField -from adcp.types.generated_poc.enums.frequency_cap_scope import FrequencyCapScope -from adcp.types.generated_poc.enums.genre_taxonomy import GenreTaxonomy -from adcp.types.generated_poc.enums.geo_level import GeographicTargetingLevel -from adcp.types.generated_poc.enums.gop_type import GopType -from adcp.types.generated_poc.enums.governance_decision import GovernanceDecision -from adcp.types.generated_poc.enums.governance_domain import GovernanceDomain -from adcp.types.generated_poc.enums.governance_mode import GovernanceMode -from adcp.types.generated_poc.enums.governance_phase import GovernancePhase -from adcp.types.generated_poc.enums.history_entry_type import HistoryEntryType -from adcp.types.generated_poc.enums.http_method import HttpMethod -from adcp.types.generated_poc.enums.identifier_types import PropertyIdentifierTypes -from adcp.types.generated_poc.enums.impairment_offline_state import ImpairmentOfflineState -from adcp.types.generated_poc.enums.impairment_reason_code import ImpairmentReasonCode -from adcp.types.generated_poc.enums.indicator_type import IndicatorType -from adcp.types.generated_poc.enums.installment_status import InstallmentStatus -from adcp.types.generated_poc.enums.javascript_module_type import JavascriptModuleType -from adcp.types.generated_poc.enums.landing_page_requirement import LandingPageRequirement -from adcp.types.generated_poc.enums.legacy_postal_system import CountryFusedPostalCodeSystem -from adcp.types.generated_poc.enums.lift_dimension import LiftDimension -from adcp.types.generated_poc.enums.logo_slot import LogoSlot -from adcp.types.generated_poc.enums.macro_dialect import MacroDialectFamily -from adcp.types.generated_poc.enums.macro_mapping_status import MacroMappingStatus -from adcp.types.generated_poc.enums.macro_processing_operation import MacroProcessingOperation -from adcp.types.generated_poc.enums.macro_resolution_reason import MacroResolutionReason -from adcp.types.generated_poc.enums.macro_resolver import MacroProcessingActor -from adcp.types.generated_poc.enums.macro_value_context import MacroValueContext -from adcp.types.generated_poc.enums.makegood_remedy import MakegoodRemedy -from adcp.types.generated_poc.enums.markdown_flavor import MarkdownFlavor -from adcp.types.generated_poc.enums.match_id_type import MatchIdType -from adcp.types.generated_poc.enums.match_type import MatchType -from adcp.types.generated_poc.enums.media_buy_action_mode import MediaBuyActionMode -from adcp.types.generated_poc.enums.media_buy_frequency_cap_control_mode import ( +# Import all types from the generated domain modules +from adcp.types.domains.enums.account_currency_mode import AccountCurrencyMode +from adcp.types.domains.enums.account_scope import AccountScope +from adcp.types.domains.enums.account_status import AccountStatus +from adcp.types.domains.enums.action_not_allowed_reason import ActionNotAllowedReason +from adcp.types.domains.enums.action_source import ActionSource +from adcp.types.domains.enums.activation_method import CreativeActivationMethod +from adcp.types.domains.enums.adcp_protocol import AdcpProtocol +from adcp.types.domains.enums.adjustment_kind import PriceAdjustmentKind +from adcp.types.domains.enums.advertiser_industry import AdvertiserIndustry +from adcp.types.domains.enums.age_determination_basis import AgeDeterminationBasis +from adcp.types.domains.enums.age_verification_method import AgeVerificationMethod +from adcp.types.domains.enums.assessment_status import AssessmentStatus +from adcp.types.domains.enums.asset_content_type import AssetContentType +from adcp.types.domains.enums.attestation_claim import AttestationClaim +from adcp.types.domains.enums.attribution_methodology import AttributionMethodology +from adcp.types.domains.enums.attribution_model import AttributionModel +from adcp.types.domains.enums.audience_evidence_methodology import AudienceEvidenceMethodology +from adcp.types.domains.enums.audience_resolution_method import AudienceResolutionMethod +from adcp.types.domains.enums.audience_source import AudienceSource +from adcp.types.domains.enums.audience_status import AudienceStatus +from adcp.types.domains.enums.audience_subject_type import AudienceSubjectType +from adcp.types.domains.enums.audio_channel_layout import AudioChannelLayout +from adcp.types.domains.enums.audio_distribution_type import AudioDistributionType +from adcp.types.domains.enums.auth_scheme import AuthenticationScheme +from adcp.types.domains.enums.availability_status import AvailabilityStatus +from adcp.types.domains.enums.available_metric import AvailableMetric +from adcp.types.domains.enums.billing_party import BillingParty +from adcp.types.domains.enums.binary_verdict import BinaryVerdict +from adcp.types.domains.enums.brand_agent_type import BrandAgentType +from adcp.types.domains.enums.browser_family import BrowserFamily +from adcp.types.domains.enums.c2pa_watermark_action import C2PaWatermarkAction +from adcp.types.domains.enums.canceled_by import CanceledBy +from adcp.types.domains.enums.canonical_media_buy_action import CanonicalMediaBuyActionName +from adcp.types.domains.enums.canonical_media_buy_action_mode import CanonicalMediaBuyActionMode +from adcp.types.domains.enums.catalog_action import CatalogAction +from adcp.types.domains.enums.catalog_item_status import CatalogItemStatus +from adcp.types.domains.enums.catalog_type import CatalogType +from adcp.types.domains.enums.channels import MediaChannel +from adcp.types.domains.enums.cloud_storage_protocol import CloudStorageProtocol +from adcp.types.domains.enums.co_branding_requirement import CoBrandingRequirement +from adcp.types.domains.enums.collection_cadence import CollectionCadence +from adcp.types.domains.enums.collection_kind import CollectionKind +from adcp.types.domains.enums.collection_relationship import CollectionRelationship +from adcp.types.domains.enums.collection_status import CollectionStatus +from adcp.types.domains.enums.completion_source import CompletionSource +from adcp.types.domains.enums.consent_basis import ConsentBasis +from adcp.types.domains.enums.content_id_type import ContentIdType +from adcp.types.domains.enums.content_rating_system import ContentRatingSystem +from adcp.types.domains.enums.creative_action import CreativeAction +from adcp.types.domains.enums.creative_agent_capability import CreativeAgentCapability +from adcp.types.domains.enums.creative_approval_status import CreativeApprovalStatus +from adcp.types.domains.enums.creative_event_reason_code import CreativeEventReasonCode +from adcp.types.domains.enums.creative_identifier_type import CreativeIdentifierType +from adcp.types.domains.enums.creative_quality import CreativeQuality +from adcp.types.domains.enums.creative_selection_strategy import CreativeSelectionStrategy +from adcp.types.domains.enums.creative_sort_field import CreativeSortField +from adcp.types.domains.enums.creative_status import CreativeStatus +from adcp.types.domains.enums.ctv_ad_experience import CtvAdExperience +from adcp.types.domains.enums.daast_tracking_event import DaastTrackingEvent +from adcp.types.domains.enums.daast_version import DaastVersion +from adcp.types.domains.enums.day_of_week import DayOfWeek +from adcp.types.domains.enums.daypart_timezone_mode import DaypartTimezoneMode +from adcp.types.domains.enums.delegation_authority import DelegationAuthority +from adcp.types.domains.enums.delivery_recipient_cloud import DeliveryRecipientCloud +from adcp.types.domains.enums.delivery_status import DeliveryStatus +from adcp.types.domains.enums.delivery_type import DeliveryType +from adcp.types.domains.enums.demographic_system import DemographicSystem +from adcp.types.domains.enums.derivative_type import DerivativeType +from adcp.types.domains.enums.device_platform import DevicePlatform +from adcp.types.domains.enums.device_type import DeviceType +from adcp.types.domains.enums.digital_source_type import DigitalSourceType +from adcp.types.domains.enums.dimension_unit import DimensionUnit +from adcp.types.domains.enums.disclosure_persistence import DisclosurePersistence +from adcp.types.domains.enums.disclosure_position import DisclosurePosition +from adcp.types.domains.enums.distance_unit import DistanceUnit +from adcp.types.domains.enums.distribution_identifier_type import DistributionIdentifierType +from adcp.types.domains.enums.dooh_motion_type import DoohMotionType +from adcp.types.domains.enums.embedded_provenance_method import EmbeddedProvenanceMethod +from adcp.types.domains.enums.error_code import ErrorCode +from adcp.types.domains.enums.error_scope import ErrorScope +from adcp.types.domains.enums.escalation_severity import EscalationSeverity +from adcp.types.domains.enums.event_type import EventType +from adcp.types.domains.enums.exclusivity import Exclusivity +from adcp.types.domains.enums.feature_check_status import FeatureCheckStatus +from adcp.types.domains.enums.feed_format import FeedFormat +from adcp.types.domains.enums.feedback_source import FeedbackSource +from adcp.types.domains.enums.forecast_method import ForecastMethod +from adcp.types.domains.enums.forecast_range_unit import ForecastRangeUnit +from adcp.types.domains.enums.forecastable_metric import ForecastableMetric +from adcp.types.domains.enums.format_id_parameter import FormatIdParameter +from adcp.types.domains.enums.frame_rate_type import FrameRateType +from adcp.types.domains.enums.frequency_cap_control_mode import FrequencyCapControlMode +from adcp.types.domains.enums.frequency_cap_mutable_field import FrequencyCapMutableField +from adcp.types.domains.enums.frequency_cap_scope import FrequencyCapScope +from adcp.types.domains.enums.genre_taxonomy import GenreTaxonomy +from adcp.types.domains.enums.geo_level import GeographicTargetingLevel +from adcp.types.domains.enums.gop_type import GopType +from adcp.types.domains.enums.governance_decision import GovernanceDecision +from adcp.types.domains.enums.governance_domain import GovernanceDomain +from adcp.types.domains.enums.governance_mode import GovernanceMode +from adcp.types.domains.enums.governance_phase import GovernancePhase +from adcp.types.domains.enums.history_entry_type import HistoryEntryType +from adcp.types.domains.enums.http_method import HttpMethod +from adcp.types.domains.enums.identifier_types import PropertyIdentifierTypes +from adcp.types.domains.enums.impairment_offline_state import ImpairmentOfflineState +from adcp.types.domains.enums.impairment_reason_code import ImpairmentReasonCode +from adcp.types.domains.enums.indicator_type import IndicatorType +from adcp.types.domains.enums.installment_status import InstallmentStatus +from adcp.types.domains.enums.javascript_module_type import JavascriptModuleType +from adcp.types.domains.enums.landing_page_requirement import LandingPageRequirement +from adcp.types.domains.enums.legacy_postal_system import CountryFusedPostalCodeSystem +from adcp.types.domains.enums.lift_dimension import LiftDimension +from adcp.types.domains.enums.logo_slot import LogoSlot +from adcp.types.domains.enums.macro_dialect import MacroDialectFamily +from adcp.types.domains.enums.macro_mapping_status import MacroMappingStatus +from adcp.types.domains.enums.macro_processing_operation import MacroProcessingOperation +from adcp.types.domains.enums.macro_resolution_reason import MacroResolutionReason +from adcp.types.domains.enums.macro_resolver import MacroProcessingActor +from adcp.types.domains.enums.macro_value_context import MacroValueContext +from adcp.types.domains.enums.makegood_remedy import MakegoodRemedy +from adcp.types.domains.enums.markdown_flavor import MarkdownFlavor +from adcp.types.domains.enums.match_id_type import MatchIdType +from adcp.types.domains.enums.match_type import MatchType +from adcp.types.domains.enums.media_buy_action_mode import MediaBuyActionMode +from adcp.types.domains.enums.media_buy_frequency_cap_control_mode import ( MediaBuyFrequencyCapControlMode, ) -from adcp.types.generated_poc.enums.media_buy_health import MediaBuyHealth -from adcp.types.generated_poc.enums.media_buy_status import MediaBuyStatus -from adcp.types.generated_poc.enums.media_buy_valid_action import MediaBuyValidAction -from adcp.types.generated_poc.enums.metric_scope import MetricScope -from adcp.types.generated_poc.enums.metric_type import MetricType -from adcp.types.generated_poc.enums.metro_system import MetroAreaSystem -from adcp.types.generated_poc.enums.moov_atom_position import MoovAtomPosition -from adcp.types.generated_poc.enums.motion_level import CreativeMotionLevel -from adcp.types.generated_poc.enums.notification_type import NotificationType -from adcp.types.generated_poc.enums.offering_availability_status import OfferingAvailabilityStatus -from adcp.types.generated_poc.enums.outcome_target_cost_strength import OutcomeTargetCostStrength -from adcp.types.generated_poc.enums.outcome_type import OutcomeType -from adcp.types.generated_poc.enums.pacing import Pacing -from adcp.types.generated_poc.enums.payment_terms import PaymentTerms -from adcp.types.generated_poc.enums.performance_baseline import PerformanceBaseline -from adcp.types.generated_poc.enums.performance_standard_metric import PerformanceStandardMetric -from adcp.types.generated_poc.enums.pixel_tracking_event import PixelTrackingEvent -from adcp.types.generated_poc.enums.policy_category import PolicyCategory -from adcp.types.generated_poc.enums.policy_enforcement import PolicyEnforcementLevel -from adcp.types.generated_poc.enums.postal_system import PostalCodeSystem -from adcp.types.generated_poc.enums.preview_output_format import PreviewOutputFormat -from adcp.types.generated_poc.enums.pricing_model import PricingModel -from adcp.types.generated_poc.enums.pricing_structure import PricingStructure -from adcp.types.generated_poc.enums.principal_kind import PrincipalKind -from adcp.types.generated_poc.enums.production_quality import ProductionQuality -from adcp.types.generated_poc.enums.property_type import PropertyType -from adcp.types.generated_poc.enums.proposal_decline_reason import ProposalDeclineReason -from adcp.types.generated_poc.enums.proposal_refinement_reason import ProposalRefinementReason -from adcp.types.generated_poc.enums.proposal_status import ProposalStatus -from adcp.types.generated_poc.enums.publisher_identifier_types import PublisherIdentifierTypes -from adcp.types.generated_poc.enums.purchase_type import PurchaseType -from adcp.types.generated_poc.enums.reach_aggregation import ReachAggregation -from adcp.types.generated_poc.enums.reach_unit import ReachUnit -from adcp.types.generated_poc.enums.reporting_destination_setup_state import ( +from adcp.types.domains.enums.media_buy_health import MediaBuyHealth +from adcp.types.domains.enums.media_buy_status import MediaBuyStatus +from adcp.types.domains.enums.media_buy_valid_action import MediaBuyValidAction +from adcp.types.domains.enums.metric_scope import MetricScope +from adcp.types.domains.enums.metric_type import MetricType +from adcp.types.domains.enums.metro_system import MetroAreaSystem +from adcp.types.domains.enums.moov_atom_position import MoovAtomPosition +from adcp.types.domains.enums.motion_level import CreativeMotionLevel +from adcp.types.domains.enums.notification_type import NotificationType +from adcp.types.domains.enums.offering_availability_status import OfferingAvailabilityStatus +from adcp.types.domains.enums.outcome_target_cost_strength import OutcomeTargetCostStrength +from adcp.types.domains.enums.outcome_type import OutcomeType +from adcp.types.domains.enums.pacing import Pacing +from adcp.types.domains.enums.payment_terms import PaymentTerms +from adcp.types.domains.enums.performance_baseline import PerformanceBaseline +from adcp.types.domains.enums.performance_standard_metric import PerformanceStandardMetric +from adcp.types.domains.enums.pixel_tracking_event import PixelTrackingEvent +from adcp.types.domains.enums.policy_category import PolicyCategory +from adcp.types.domains.enums.policy_enforcement import PolicyEnforcementLevel +from adcp.types.domains.enums.postal_system import PostalCodeSystem +from adcp.types.domains.enums.preview_output_format import PreviewOutputFormat +from adcp.types.domains.enums.pricing_model import PricingModel +from adcp.types.domains.enums.pricing_structure import PricingStructure +from adcp.types.domains.enums.principal_kind import PrincipalKind +from adcp.types.domains.enums.production_quality import ProductionQuality +from adcp.types.domains.enums.property_type import PropertyType +from adcp.types.domains.enums.proposal_decline_reason import ProposalDeclineReason +from adcp.types.domains.enums.proposal_refinement_reason import ProposalRefinementReason +from adcp.types.domains.enums.proposal_status import ProposalStatus +from adcp.types.domains.enums.publisher_identifier_types import PublisherIdentifierTypes +from adcp.types.domains.enums.purchase_type import PurchaseType +from adcp.types.domains.enums.reach_aggregation import ReachAggregation +from adcp.types.domains.enums.reach_unit import ReachUnit +from adcp.types.domains.enums.reporting_destination_setup_state import ( ReportingDestinationSetupState, ) -from adcp.types.generated_poc.enums.reporting_finality import ReportingFinality -from adcp.types.generated_poc.enums.reporting_frequency import ReportingFrequency -from adcp.types.generated_poc.enums.reporting_health import ReportingHealth -from adcp.types.generated_poc.enums.representation_selection_strategy import ( +from adcp.types.domains.enums.reporting_finality import ReportingFinality +from adcp.types.domains.enums.reporting_frequency import ReportingFrequency +from adcp.types.domains.enums.reporting_health import ReportingHealth +from adcp.types.domains.enums.representation_selection_strategy import ( RepresentationSelectionStrategy, ) -from adcp.types.generated_poc.enums.request_signing_error_code import RequestSigningErrorCode -from adcp.types.generated_poc.enums.response_type import TmpResponseType -from adcp.types.generated_poc.enums.restricted_attribute import RestrictedAttribute -from adcp.types.generated_poc.enums.right_type import RightType -from adcp.types.generated_poc.enums.right_use import RightUse -from adcp.types.generated_poc.enums.rights_billing_period import RightsBillingPeriod -from adcp.types.generated_poc.enums.scan_type import ScanType -from adcp.types.generated_poc.enums.seller_policy_decline_reason import SellerPolicyDeclineReason -from adcp.types.generated_poc.enums.si_session_status import SiSessionStatus -from adcp.types.generated_poc.enums.signal_catalog_type import ( - SignalAvailabilityType, - SignalCatalogType, -) -from adcp.types.generated_poc.enums.signal_source import SignalSource -from adcp.types.generated_poc.enums.signal_value_type import SignalValueType -from adcp.types.generated_poc.enums.snapshot_unavailable_reason import SnapshotUnavailableReason -from adcp.types.generated_poc.enums.social_placement_surface import SocialPlacementSurface -from adcp.types.generated_poc.enums.sort_direction import SortDirection -from adcp.types.generated_poc.enums.sort_metric import SortMetric -from adcp.types.generated_poc.enums.special_category import SpecialCategory -from adcp.types.generated_poc.enums.specialism import AdcpSpecialism -from adcp.types.generated_poc.enums.sponsored_placement_type import SponsoredPlacementType -from adcp.types.generated_poc.enums.talent_role import TalentRole -from adcp.types.generated_poc.enums.task_status import TaskStatus -from adcp.types.generated_poc.enums.task_type import TaskType -from adcp.types.generated_poc.enums.tracker_execution_actor import TrackerExecutionActor -from adcp.types.generated_poc.enums.tracker_firing_path import TrackerFiringPath -from adcp.types.generated_poc.enums.transport_mode import TransportMode -from adcp.types.generated_poc.enums.travel_time_unit import TravelTimeUnit -from adcp.types.generated_poc.enums.uid_type import UidType -from adcp.types.generated_poc.enums.universal_macro import UniversalMacro -from adcp.types.generated_poc.enums.update_frequency import UpdateFrequency -from adcp.types.generated_poc.enums.url_asset_type import UrlAssetType -from adcp.types.generated_poc.enums.validation_mode import ValidationMode -from adcp.types.generated_poc.enums.vast_media_delivery_method import VastMediaDeliveryMethod -from adcp.types.generated_poc.enums.vast_tracking_event import VastTrackingEvent -from adcp.types.generated_poc.enums.vast_version import VastVersion -from adcp.types.generated_poc.enums.vendor_relationship import VendorRelationship -from adcp.types.generated_poc.enums.video_placement_type import VideoPlacementType -from adcp.types.generated_poc.enums.view_threshold_basis import ViewThresholdBasis -from adcp.types.generated_poc.enums.viewability_standard import ViewabilityStandard -from adcp.types.generated_poc.enums.warning_code import WarningCode -from adcp.types.generated_poc.enums.watermark_media_type import WatermarkMediaType -from adcp.types.generated_poc.enums.wcag_level import WcagLevel -from adcp.types.generated_poc.enums.webhook_response_type import WebhookResponseType -from adcp.types.generated_poc.enums.webhook_security_method import WebhookSecurityMethod -from adcp.types.generated_poc.a2ui.bound_value import ( +from adcp.types.domains.enums.request_signing_error_code import RequestSigningErrorCode +from adcp.types.domains.enums.response_type import TmpResponseType +from adcp.types.domains.enums.restricted_attribute import RestrictedAttribute +from adcp.types.domains.enums.right_type import RightType +from adcp.types.domains.enums.right_use import RightUse +from adcp.types.domains.enums.rights_billing_period import RightsBillingPeriod +from adcp.types.domains.enums.scan_type import ScanType +from adcp.types.domains.enums.seller_policy_decline_reason import SellerPolicyDeclineReason +from adcp.types.domains.enums.si_session_status import SiSessionStatus +from adcp.types.domains.enums.signal_catalog_type import SignalAvailabilityType, SignalCatalogType +from adcp.types.domains.enums.signal_source import SignalSource +from adcp.types.domains.enums.signal_value_type import SignalValueType +from adcp.types.domains.enums.snapshot_unavailable_reason import SnapshotUnavailableReason +from adcp.types.domains.enums.social_placement_surface import SocialPlacementSurface +from adcp.types.domains.enums.sort_direction import SortDirection +from adcp.types.domains.enums.sort_metric import SortMetric +from adcp.types.domains.enums.special_category import SpecialCategory +from adcp.types.domains.enums.specialism import AdcpSpecialism +from adcp.types.domains.enums.sponsored_placement_type import SponsoredPlacementType +from adcp.types.domains.enums.talent_role import TalentRole +from adcp.types.domains.enums.task_status import TaskStatus +from adcp.types.domains.enums.task_type import TaskType +from adcp.types.domains.enums.tracker_execution_actor import TrackerExecutionActor +from adcp.types.domains.enums.tracker_firing_path import TrackerFiringPath +from adcp.types.domains.enums.transport_mode import TransportMode +from adcp.types.domains.enums.travel_time_unit import TravelTimeUnit +from adcp.types.domains.enums.uid_type import UidType +from adcp.types.domains.enums.universal_macro import UniversalMacro +from adcp.types.domains.enums.update_frequency import UpdateFrequency +from adcp.types.domains.enums.url_asset_type import UrlAssetType +from adcp.types.domains.enums.validation_mode import ValidationMode +from adcp.types.domains.enums.vast_media_delivery_method import VastMediaDeliveryMethod +from adcp.types.domains.enums.vast_tracking_event import VastTrackingEvent +from adcp.types.domains.enums.vast_version import VastVersion +from adcp.types.domains.enums.vendor_relationship import VendorRelationship +from adcp.types.domains.enums.video_placement_type import VideoPlacementType +from adcp.types.domains.enums.view_threshold_basis import ViewThresholdBasis +from adcp.types.domains.enums.viewability_standard import ViewabilityStandard +from adcp.types.domains.enums.warning_code import WarningCode +from adcp.types.domains.enums.watermark_media_type import WatermarkMediaType +from adcp.types.domains.enums.wcag_level import WcagLevel +from adcp.types.domains.enums.webhook_response_type import WebhookResponseType +from adcp.types.domains.enums.webhook_security_method import WebhookSecurityMethod +from adcp.types.domains.a2ui.bound_value import ( A2UiBoundValue, A2UiBoundValue1, A2UiBoundValue2, @@ -246,8 +241,8 @@ A2UiBoundValue4, A2UiBoundValue5, ) -from adcp.types.generated_poc.a2ui.component import A2UiComponent -from adcp.types.generated_poc.a2ui.si_catalog import ( +from adcp.types.domains.a2ui.component import A2UiComponent +from adcp.types.domains.a2ui.si_catalog import ( Action, Action23, Align, @@ -272,18 +267,16 @@ Variant, Variant4, ) -from adcp.types.generated_poc.a2ui.surface import A2UiSurface -from adcp.types.generated_poc.a2ui.user_action import A2UiUserAction -from adcp.types.generated_poc.aao.agent_publishers import ( +from adcp.types.domains.a2ui.surface import A2UiSurface +from adcp.types.domains.a2ui.user_action import A2UiUserAction +from adcp.types.domains.aao.agent_publishers import ( AaoDirectoryAgentPublishersInverseLookupResponse, DiscoveryMethod, PublisherEntry, Status, ) -from adcp.types.generated_poc.account.get_account_financials_request import ( - GetAccountFinancialsRequest, -) -from adcp.types.generated_poc.account.get_account_financials_response import ( +from adcp.types.domains.account.get_account_financials_request import GetAccountFinancialsRequest +from adcp.types.domains.account.get_account_financials_response import ( Balance, Credit, GetAccountFinancialsResponse, @@ -293,44 +286,44 @@ LastTopUp, Spend, ) -from adcp.types.generated_poc.account.list_account_changes_request import ( +from adcp.types.domains.account.list_account_changes_request import ( ListAccountChangesRequest, ResourceType, StartingPosition, ) -from adcp.types.generated_poc.account.list_account_changes_response import ( +from adcp.types.domains.account.list_account_changes_response import ( Kind, ListAccountChangesResponse, SourceCoverageItem, Status22, ) -from adcp.types.generated_poc.account.list_accounts_request import ListAccountsRequest -from adcp.types.generated_poc.account.list_accounts_response import ListAccountsResponse -from adcp.types.generated_poc.account.report_usage_request import ReportUsageRequest, UsageItem -from adcp.types.generated_poc.account.report_usage_response import ReportUsageResponse -from adcp.types.generated_poc.account.sync_accounts_request import ( +from adcp.types.domains.account.list_accounts_request import ListAccountsRequest +from adcp.types.domains.account.list_accounts_response import ListAccountsResponse +from adcp.types.domains.account.report_usage_request import ReportUsageRequest, UsageItem +from adcp.types.domains.account.report_usage_response import ReportUsageResponse +from adcp.types.domains.account.sync_accounts_request import ( Accounts, Accounts1, SyncAccountsRequest, ) -from adcp.types.generated_poc.account.sync_accounts_response import ( +from adcp.types.domains.account.sync_accounts_response import ( CreditLimit, Setup, SyncAccountsResponse, SyncAccountsResponse1, SyncAccountsResponse2, ) -from adcp.types.generated_poc.account.sync_governance_request import ( +from adcp.types.domains.account.sync_governance_request import ( Authentication, GovernanceAgent, SyncGovernanceRequest, ) -from adcp.types.generated_poc.account.sync_governance_response import ( +from adcp.types.domains.account.sync_governance_response import ( SyncGovernanceResponse, SyncGovernanceResponse1, SyncGovernanceResponse2, ) -from adcp.types.generated_poc.adagents import ( +from adcp.types.domains.adagents import ( AdcpAgentsAuthorization, AdcpAgentsAuthorization1, AdcpAgentsAuthorization2, @@ -412,8 +405,8 @@ SignalTags, Tags, ) -from adcp.types.generated_poc.brand.acquire_rights_request import AcquireRightsRequest, Campaign -from adcp.types.generated_poc.brand.acquire_rights_response import ( +from adcp.types.domains.brand.acquire_rights_request import AcquireRightsRequest, Campaign +from adcp.types.domains.brand.acquire_rights_response import ( AcquireRightsResponse, AcquireRightsResponse1, AcquireRightsResponse2, @@ -421,19 +414,16 @@ AcquireRightsResponse4, Disclosure, ) -from adcp.types.generated_poc.brand.creative_approval_request import CreativeApprovalRequest -from adcp.types.generated_poc.brand.creative_approval_response import ( +from adcp.types.domains.brand.creative_approval_request import CreativeApprovalRequest +from adcp.types.domains.brand.creative_approval_response import ( CreativeApprovalResponse, CreativeApprovalResponse1, CreativeApprovalResponse2, CreativeApprovalResponse3, CreativeApprovalResponse4, ) -from adcp.types.generated_poc.brand.get_brand_identity_request import ( - Field1, - GetBrandIdentityRequest, -) -from adcp.types.generated_poc.brand.get_brand_identity_response import ( +from adcp.types.domains.brand.get_brand_identity_request import Field1, GetBrandIdentityRequest +from adcp.types.domains.brand.get_brand_identity_response import ( Asset, Colors, File, @@ -448,8 +438,8 @@ Tone, VoiceSynthesis, ) -from adcp.types.generated_poc.brand.get_rights_request import GetRightsRequest -from adcp.types.generated_poc.brand.get_rights_response import ( +from adcp.types.domains.brand.get_rights_request import GetRightsRequest +from adcp.types.domains.brand.get_rights_response import ( Excluded, ExclusivityStatus, GetRightsResponse, @@ -458,11 +448,11 @@ PreviewAsset, Right, ) -from adcp.types.generated_poc.brand.revocation_notification import RevocationNotification -from adcp.types.generated_poc.brand.rights_pricing_option import RightsPricingOption -from adcp.types.generated_poc.brand.rights_terms import RightsTerms -from adcp.types.generated_poc.brand.search_brands_request import SearchBrandsRequest -from adcp.types.generated_poc.brand.search_brands_response import ( +from adcp.types.domains.brand.revocation_notification import RevocationNotification +from adcp.types.domains.brand.rights_pricing_option import RightsPricingOption +from adcp.types.domains.brand.rights_terms import RightsTerms +from adcp.types.domains.brand.search_brands_request import SearchBrandsRequest +from adcp.types.domains.brand.search_brands_response import ( Background, ExcludedCountry, KellerType, @@ -471,18 +461,15 @@ SearchBrandResult, SearchBrandsResponse, ) -from adcp.types.generated_poc.brand.update_rights_request import UpdateRightsRequest -from adcp.types.generated_poc.brand.update_rights_response import ( +from adcp.types.domains.brand.update_rights_request import UpdateRightsRequest +from adcp.types.domains.brand.update_rights_response import ( UpdateRightsResponse, UpdateRightsResponse1, UpdateRightsResponse2, ) -from adcp.types.generated_poc.brand.verification_status import VerificationStatus -from adcp.types.generated_poc.brand.verify_brand_claim_request import ( - ClaimType, - VerifyBrandClaimRequest, -) -from adcp.types.generated_poc.brand.verify_brand_claim_response import ( +from adcp.types.domains.brand.verification_status import VerificationStatus +from adcp.types.domains.brand.verify_brand_claim_request import ClaimType, VerifyBrandClaimRequest +from adcp.types.domains.brand.verify_brand_claim_response import ( VerifyBrandClaimErrorResponse, VerifyBrandClaimPayload, VerifyBrandClaimResponse, @@ -490,7 +477,7 @@ VerifyBrandClaimSignedSuccessPayload, VerifyBrandClaimSuccessResponse, ) -from adcp.types.generated_poc.brand.verify_brand_claims_request import ( +from adcp.types.domains.brand.verify_brand_claims_request import ( Claim, Claim1, Claim2, @@ -504,7 +491,7 @@ VerifyBrandClaimsRequest, VerifyBrandClaimsRequestBulk, ) -from adcp.types.generated_poc.brand.verify_brand_claims_response import ( +from adcp.types.domains.brand.verify_brand_claims_response import ( ResultEntry, ResultEntry1, ResultEntry2, @@ -515,55 +502,45 @@ VerifyBrandClaimsSignedResponse, VerifyBrandClaimsSignedSuccessPayload, ) -from adcp.types.generated_poc.collection.base_collection_source import ( +from adcp.types.domains.collection.base_collection_source import ( BaseCollectionSource, BaseCollectionSource1, BaseCollectionSource2, BaseCollectionSource3, ) -from adcp.types.generated_poc.collection.collection_list import CollectionList -from adcp.types.generated_poc.collection.collection_list_changed_webhook import ( +from adcp.types.domains.collection.collection_list import CollectionList +from adcp.types.domains.collection.collection_list_changed_webhook import ( ChangeSummary, CollectionListChangedWebhook, ) -from adcp.types.generated_poc.collection.collection_list_filters import ( +from adcp.types.domains.collection.collection_list_filters import ( CollectionListFilters, ExcludeDistributionId, ) -from adcp.types.generated_poc.collection.create_collection_list_request import ( - CreateCollectionListRequest, -) -from adcp.types.generated_poc.collection.create_collection_list_response import ( +from adcp.types.domains.collection.create_collection_list_request import CreateCollectionListRequest +from adcp.types.domains.collection.create_collection_list_response import ( CreateCollectionListResponse, ) -from adcp.types.generated_poc.collection.delete_collection_list_request import ( - DeleteCollectionListRequest, -) -from adcp.types.generated_poc.collection.delete_collection_list_response import ( +from adcp.types.domains.collection.delete_collection_list_request import DeleteCollectionListRequest +from adcp.types.domains.collection.delete_collection_list_response import ( DeleteCollectionListResponse, ) -from adcp.types.generated_poc.collection.get_collection_list_request import ( +from adcp.types.domains.collection.get_collection_list_request import ( GetCollectionListRequest, Pagination, ) -from adcp.types.generated_poc.collection.get_collection_list_response import ( +from adcp.types.domains.collection.get_collection_list_response import ( CoverageGap, DistributionId, GetCollectionListResponse, ) -from adcp.types.generated_poc.collection.list_collection_lists_request import ( - ListCollectionListsRequest, -) -from adcp.types.generated_poc.collection.list_collection_lists_response import ( - ListCollectionListsResponse, -) -from adcp.types.generated_poc.collection.update_collection_list_request import ( - UpdateCollectionListRequest, -) -from adcp.types.generated_poc.collection.update_collection_list_response import ( +from adcp.types.domains.collection.list_collection_lists_request import ListCollectionListsRequest +from adcp.types.domains.collection.list_collection_lists_response import ListCollectionListsResponse +from adcp.types.domains.collection.update_collection_list_request import UpdateCollectionListRequest +from adcp.types.domains.collection.update_collection_list_response import ( UpdateCollectionListResponse, ) -from adcp.types.generated_poc.compliance.comply_test_controller_request import ( +from adcp.types.domains.compliance.comply_test_controller_request import ( AdvanceTo, Arm, ComplyTestControllerRequest, @@ -578,7 +555,7 @@ Suggestion, TargetHealth, ) -from adcp.types.generated_poc.compliance.comply_test_controller_response import ( +from adcp.types.domains.compliance.comply_test_controller_response import ( AttestationMode, ComplyResponseArm, ComplyTestControllerResponse, @@ -598,11 +575,11 @@ RecordedCalls1, RecordedCalls2, ) -from adcp.types.generated_poc.compliance.get_creative_features_completion import ( +from adcp.types.domains.compliance.get_creative_features_completion import ( GetCreativeFeaturesComplianceCompletion, ) -from adcp.types.generated_poc.compliance.task_completion_data import ComplianceTaskCompletionData -from adcp.types.generated_poc.content_standards.artifact import ( +from adcp.types.domains.compliance.task_completion_data import ComplianceTaskCompletionData +from adcp.types.domains.content_standards.artifact import ( Artifact, AssetAccess, AssetAccess1, @@ -622,90 +599,86 @@ TranscriptSource, TranscriptSource1, ) -from adcp.types.generated_poc.content_standards.artifact_webhook_payload import ( - ArtifactWebhookPayload, -) -from adcp.types.generated_poc.content_standards.calibrate_content_request import ( - CalibrateContentRequest, -) -from adcp.types.generated_poc.content_standards.calibrate_content_response import ( +from adcp.types.domains.content_standards.artifact_webhook_payload import ArtifactWebhookPayload +from adcp.types.domains.content_standards.calibrate_content_request import CalibrateContentRequest +from adcp.types.domains.content_standards.calibrate_content_response import ( CalibrateContentResponse, CalibrateContentResponse1, CalibrateContentResponse2, Feature, ) -from adcp.types.generated_poc.content_standards.content_standards import ( +from adcp.types.domains.content_standards.content_standards import ( CalibrationExemplars, ContentStandards, ) -from adcp.types.generated_poc.content_standards.create_content_standards_request import ( +from adcp.types.domains.content_standards.create_content_standards_request import ( CreateContentStandardsRequest, Fail, Pass, Scope, ) -from adcp.types.generated_poc.content_standards.create_content_standards_response import ( +from adcp.types.domains.content_standards.create_content_standards_response import ( CreateContentStandardsResponse, CreateContentStandardsResponse1, CreateContentStandardsResponse2, ) -from adcp.types.generated_poc.content_standards.get_content_standards_request import ( +from adcp.types.domains.content_standards.get_content_standards_request import ( GetContentStandardsRequest, ) -from adcp.types.generated_poc.content_standards.get_content_standards_response import ( +from adcp.types.domains.content_standards.get_content_standards_response import ( GetContentStandardsResponse, GetContentStandardsResponse1, GetContentStandardsResponse2, ) -from adcp.types.generated_poc.content_standards.get_media_buy_artifacts_request import ( +from adcp.types.domains.content_standards.get_media_buy_artifacts_request import ( GetMediaBuyArtifactsRequest, TimeRange, ) -from adcp.types.generated_poc.content_standards.get_media_buy_artifacts_response import ( +from adcp.types.domains.content_standards.get_media_buy_artifacts_response import ( BrandContext, CollectionInfo, GetMediaBuyArtifactsResponse, GetMediaBuyArtifactsResponse1, GetMediaBuyArtifactsResponse2, ) -from adcp.types.generated_poc.content_standards.list_content_standards_request import ( +from adcp.types.domains.content_standards.list_content_standards_request import ( ListContentStandardsRequest, ) -from adcp.types.generated_poc.content_standards.list_content_standards_response import ( +from adcp.types.domains.content_standards.list_content_standards_response import ( ListContentStandardsResponse, ListContentStandardsResponse1, ListContentStandardsResponse2, ) -from adcp.types.generated_poc.content_standards.update_content_standards_request import ( +from adcp.types.domains.content_standards.update_content_standards_request import ( UpdateContentStandardsRequest, ) -from adcp.types.generated_poc.content_standards.update_content_standards_response import ( +from adcp.types.domains.content_standards.update_content_standards_response import ( UpdateContentStandardsResponse, UpdateContentStandardsResponse1, UpdateContentStandardsResponse2, ) -from adcp.types.generated_poc.content_standards.validate_content_delivery_request import ( +from adcp.types.domains.content_standards.validate_content_delivery_request import ( Record, ValidateContentDeliveryRequest, ) -from adcp.types.generated_poc.content_standards.validate_content_delivery_response import ( +from adcp.types.domains.content_standards.validate_content_delivery_response import ( Result, Summary, ValidateContentDeliveryResponse, ValidateContentDeliveryResponse1, ValidateContentDeliveryResponse2, ) -from adcp.types.generated_poc.core.acceptance_policy_profile_ids import ( +from adcp.types.domains.core.acceptance_policy_profile_ids import ( AcceptancePolicyProfileId, AcceptancePolicyProfileIds, ) -from adcp.types.generated_poc.core.account import Account, Compression, ReportingBucket -from adcp.types.generated_poc.core.account_authorization import ( +from adcp.types.domains.core.account import Account, Compression, ReportingBucket +from adcp.types.domains.core.account_authorization import ( AccountAuthorization, AllowedTask, ScopeName, ) -from adcp.types.generated_poc.core.account_change import ( +from adcp.types.domains.core.account_change import ( AccountChange, Actor, ChangedPath, @@ -715,15 +688,13 @@ Task, UnavailableReason, ) -from adcp.types.generated_poc.core.account_change_recorded_webhook import ( - AccountChangeRecordedWebhook, -) -from adcp.types.generated_poc.core.account_identity_change import ( +from adcp.types.domains.core.account_change_recorded_webhook import AccountChangeRecordedWebhook +from adcp.types.domains.core.account_identity_change import ( AccountIdentityChange, AccountIdentityChange1, AccountIdentityChange2, ) -from adcp.types.generated_poc.core.account_identity_change_preview import ( +from adcp.types.domains.core.account_identity_change_preview import ( AccountIdentityChangePreview, AccountIdentityChangePreview1, AccountIdentityChangePreview2, @@ -737,59 +708,50 @@ NonblockingImpact, NonblockingImpacts, ) -from adcp.types.generated_poc.core.account_ref import ( +from adcp.types.domains.core.account_ref import ( AccountReference, AccountReference1, AccountReference2, ) -from adcp.types.generated_poc.core.account_status_changed_webhook import ( +from adcp.types.domains.core.account_status_changed_webhook import ( AccountStatusChangedWebhook, ReasonCode, ) -from adcp.types.generated_poc.core.account_timezone_capability import ( +from adcp.types.domains.core.account_timezone_capability import ( AccountSelection, AccountTimezoneCapability, Mode, SupportedTimezone, ) -from adcp.types.generated_poc.core.account_with_authorization import AccountWithAuthorization -from adcp.types.generated_poc.core.activation_key import ( - ActivationKey, - ActivationKey1, - ActivationKey2, -) -from adcp.types.generated_poc.core.ad_inventory_config import AdInventoryConfiguration -from adcp.types.generated_poc.core.agent_encryption_key import AgentEncryptionKey -from adcp.types.generated_poc.core.agent_notification_config import AgentNotificationConfig -from adcp.types.generated_poc.core.agent_notification_config_state import ( - AgentNotificationConfigState, -) -from adcp.types.generated_poc.core.agent_reporting_destination import ( +from adcp.types.domains.core.account_with_authorization import AccountWithAuthorization +from adcp.types.domains.core.activation_key import ActivationKey, ActivationKey1, ActivationKey2 +from adcp.types.domains.core.ad_inventory_config import AdInventoryConfiguration +from adcp.types.domains.core.agent_encryption_key import AgentEncryptionKey +from adcp.types.domains.core.agent_notification_config import AgentNotificationConfig +from adcp.types.domains.core.agent_notification_config_state import AgentNotificationConfigState +from adcp.types.domains.core.agent_reporting_destination import ( AcceptedFormat, AgentReportingDestination, AgentReportingDestination1, AgentReportingDestination2, AgentReportingDestination3, ) -from adcp.types.generated_poc.core.agent_reporting_destination_state import ( +from adcp.types.domains.core.agent_reporting_destination_state import ( AgentReportingDestinationState, PriorDestinationRef, ) -from adcp.types.generated_poc.core.agent_signing_key import AgentSigningKey -from adcp.types.generated_poc.core.agent_webhook_challenge import ( - AgentWebhookChallenge, - DeliveryAuth, -) -from adcp.types.generated_poc.core.app_item import AppItem, Platform -from adcp.types.generated_poc.core.applicable_package_id import ApplicablePackageId -from adcp.types.generated_poc.core.asset_group_vocabulary import AdcpAssetGroupVocabularyRegistry -from adcp.types.generated_poc.core.assets.asset_union import AssetVariant -from adcp.types.generated_poc.core.assets.audio_asset import AudioAsset, BitDepth -from adcp.types.generated_poc.core.assets.brief_asset import BriefAsset -from adcp.types.generated_poc.core.assets.card_asset import CardAsset -from adcp.types.generated_poc.core.assets.catalog_asset import CatalogAsset -from adcp.types.generated_poc.core.assets.css_asset import CssAsset -from adcp.types.generated_poc.core.assets.daast_asset import ( +from adcp.types.domains.core.agent_signing_key import AgentSigningKey +from adcp.types.domains.core.agent_webhook_challenge import AgentWebhookChallenge, DeliveryAuth +from adcp.types.domains.core.app_item import AppItem, Platform +from adcp.types.domains.core.applicable_package_id import ApplicablePackageId +from adcp.types.domains.core.asset_group_vocabulary import AdcpAssetGroupVocabularyRegistry +from adcp.types.domains.core.assets.asset_union import AssetVariant +from adcp.types.domains.core.assets.audio_asset import AudioAsset, BitDepth +from adcp.types.domains.core.assets.brief_asset import BriefAsset +from adcp.types.domains.core.assets.card_asset import CardAsset +from adcp.types.domains.core.assets.catalog_asset import CatalogAsset +from adcp.types.domains.core.assets.css_asset import CssAsset +from adcp.types.domains.core.assets.daast_asset import ( DaastAsset, DaastAsset3, DaastAsset4, @@ -798,8 +760,8 @@ MacroDeclaration12, UnavailableBehavior, ) -from adcp.types.generated_poc.core.assets.daast_tracker_asset import DaastTrackerAsset, Target -from adcp.types.generated_poc.core.assets.display_tag_asset import ( +from adcp.types.domains.core.assets.daast_tracker_asset import DaastTrackerAsset, Target +from adcp.types.domains.core.assets.display_tag_asset import ( DisplayTagAsset, DisplayTagAsset4, DisplayTagAsset5, @@ -810,27 +772,27 @@ MacroDeclaration16, MarkupType, ) -from adcp.types.generated_poc.core.assets.html_asset import Accessibility, HtmlAsset -from adcp.types.generated_poc.core.assets.image_asset import FocalPointItem, ImageAsset -from adcp.types.generated_poc.core.assets.javascript_asset import JavascriptAsset -from adcp.types.generated_poc.core.assets.markdown_asset import MarkdownAsset -from adcp.types.generated_poc.core.assets.pixel_tracker_asset import PixelTrackerAsset -from adcp.types.generated_poc.core.assets.published_post_asset import ( +from adcp.types.domains.core.assets.html_asset import Accessibility, HtmlAsset +from adcp.types.domains.core.assets.image_asset import FocalPointItem, ImageAsset +from adcp.types.domains.core.assets.javascript_asset import JavascriptAsset +from adcp.types.domains.core.assets.markdown_asset import MarkdownAsset +from adcp.types.domains.core.assets.pixel_tracker_asset import PixelTrackerAsset +from adcp.types.domains.core.assets.published_post_asset import ( IdentityRef, PublishedPostAsset, ReferenceAuthorization, ) -from adcp.types.generated_poc.core.assets.text_asset import TextAsset -from adcp.types.generated_poc.core.assets.url_asset import UrlAsset -from adcp.types.generated_poc.core.assets.vast_asset import ( +from adcp.types.domains.core.assets.text_asset import TextAsset +from adcp.types.domains.core.assets.url_asset import UrlAsset +from adcp.types.domains.core.assets.vast_asset import ( Location26, MacroDeclaration20, VastAsset, VastAsset3, VastAsset4, ) -from adcp.types.generated_poc.core.assets.vast_tracker_asset import VastTrackerAsset -from adcp.types.generated_poc.core.assets.video_asset import ( +from adcp.types.domains.core.assets.vast_tracker_asset import VastTrackerAsset +from adcp.types.domains.core.assets.video_asset import ( AudioBitDepth, ChromaSubsampling, ColorSpace, @@ -838,10 +800,10 @@ VideoAsset, VideoBitDepth, ) -from adcp.types.generated_poc.core.assets.webhook_asset import Security, WebhookAsset -from adcp.types.generated_poc.core.assets.zip_asset import ZipAsset -from adcp.types.generated_poc.core.async_response_data import AdcpAsyncResponseData -from adcp.types.generated_poc.core.attestation_capabilities import ( +from adcp.types.domains.core.assets.webhook_asset import Security, WebhookAsset +from adcp.types.domains.core.assets.zip_asset import ZipAsset +from adcp.types.domains.core.async_response_data import AdcpAsyncResponseData +from adcp.types.domains.core.attestation_capabilities import ( AcceptedIssuer, AcceptedVerifier, AttestationCapabilities, @@ -849,18 +811,18 @@ Resolver, SupportedDeliveryMethod, ) -from adcp.types.generated_poc.core.attestation_evaluation import ( +from adcp.types.domains.core.attestation_evaluation import ( ActionBinding, AttestationEvaluation, Outcome, ) -from adcp.types.generated_poc.core.attestation_issuer import ( +from adcp.types.domains.core.attestation_issuer import ( AttestationIssuer, AttestationIssuer1, AttestationIssuer2, AttestationIssuer3, ) -from adcp.types.generated_poc.core.attestation_reference import ( +from adcp.types.domains.core.attestation_reference import ( AttestationReference, EmbeddedCredential, Locator, @@ -868,14 +830,14 @@ ValidityHint, VerifyAgent, ) -from adcp.types.generated_poc.core.attestation_subject import ( +from adcp.types.domains.core.attestation_subject import ( AttestationSubject, AttestationSubject1, AttestationSubject2, AttestationSubject3, ) -from adcp.types.generated_poc.core.attribution_window import AttributionWindow -from adcp.types.generated_poc.core.audience_activation_method import ( +from adcp.types.domains.core.attribution_window import AttributionWindow +from adcp.types.domains.core.audience_activation_method import ( AudienceActivationMethod, AudienceActivationMethod1, AudienceActivationMethod2, @@ -888,14 +850,14 @@ ConsumerIdentity, Direction, ) -from adcp.types.generated_poc.core.audience_characteristic import ( +from adcp.types.domains.core.audience_characteristic import ( AudienceCharacteristic, Dimension, Range, Taxonomy, Value, ) -from adcp.types.generated_poc.core.audience_evidence import ( +from adcp.types.domains.core.audience_evidence import ( AttestationRef, AudienceEvidence, Baseline, @@ -927,37 +889,31 @@ Subject37, Unit, ) -from adcp.types.generated_poc.core.audience_evidence_pin import AudienceEvidencePin -from adcp.types.generated_poc.core.audience_evidence_requirements import ( +from adcp.types.domains.core.audience_evidence_pin import AudienceEvidencePin +from adcp.types.domains.core.audience_evidence_requirements import ( AcceptedEvidenceType, AudienceEvidenceRequirements, EvidencePresence, MaximumAge, RequirementMode, ) -from adcp.types.generated_poc.core.audience_evidence_selection import ( +from adcp.types.domains.core.audience_evidence_selection import ( AudienceEvidenceSelection, DecisionUse, Evaluation, ) -from adcp.types.generated_poc.core.audience_member import AudienceMember, Uid -from adcp.types.generated_poc.core.audience_selector import ( +from adcp.types.domains.core.audience_member import AudienceMember, Uid +from adcp.types.domains.core.audience_selector import ( AudienceSelector, AudienceSelector1, AudienceSelector2, AudienceSelector3, AudienceSelector4, ) -from adcp.types.generated_poc.core.audience_source import AudienceSource1, AudienceSource2 -from adcp.types.generated_poc.core.authorized_agent_base import AuthorizedAgentBaseFields -from adcp.types.generated_poc.core.bidding_policy import ( - BiddingPolicy, - CostPer, - Roas, - Strength, - Strength1, -) -from adcp.types.generated_poc.core.bidding_policy_capability import ( +from adcp.types.domains.core.audience_source import AudienceSource1, AudienceSource2 +from adcp.types.domains.core.authorized_agent_base import AuthorizedAgentBaseFields +from adcp.types.domains.core.bidding_policy import BiddingPolicy, CostPer, Roas, Strength, Strength1 +from adcp.types.domains.core.bidding_policy_capability import ( BiddingPolicyCapability, CostPerStrength, MaxBidWithCostPer, @@ -966,20 +922,20 @@ RoasStrength, ScopeCapability, ) -from adcp.types.generated_poc.core.brand_id import BrandId -from adcp.types.generated_poc.core.brand_key import BrandKey -from adcp.types.generated_poc.core.brand_ref import ( +from adcp.types.domains.core.brand_id import BrandId +from adcp.types.domains.core.brand_key import BrandKey +from adcp.types.domains.core.brand_ref import ( BrandKitOverride, BrandReference, DataSubjectContestation, ) -from adcp.types.generated_poc.core.brand_response_authorization_result import ( +from adcp.types.domains.core.brand_response_authorization_result import ( BrandResponseAuthorizationResult, BrandResponseAuthorizationResult1, BrandResponseAuthorizationResult2, Trust, ) -from adcp.types.generated_poc.core.budget_allocation import ( +from adcp.types.domains.core.budget_allocation import ( BudgetAllocation, BudgetAllocation1, BudgetAllocation2, @@ -999,48 +955,48 @@ Target8, TargetFrequency, ) -from adcp.types.generated_poc.core.budget_range import BudgetRange -from adcp.types.generated_poc.core.business_entity import Address, Bank, BusinessEntity -from adcp.types.generated_poc.core.cancellation_policy import CancellationFee, CancellationPolicy -from adcp.types.generated_poc.core.canonical_account_ref import ( +from adcp.types.domains.core.budget_range import BudgetRange +from adcp.types.domains.core.business_entity import Address, Bank, BusinessEntity +from adcp.types.domains.core.cancellation_policy import CancellationFee, CancellationPolicy +from adcp.types.domains.core.canonical_account_ref import ( CanonicalAccountReference, CanonicalAccountReference1, CanonicalAccountReference2, ) -from adcp.types.generated_poc.core.canonical_audience_evidence import ( +from adcp.types.domains.core.canonical_audience_evidence import ( AttestationDigest, CanonicalAudienceEvidence, ) -from adcp.types.generated_poc.core.canonical_audience_evidence_selection import ( +from adcp.types.domains.core.canonical_audience_evidence_selection import ( CanonicalAudienceEvidenceSelection, VerifiedAttestationDigest, ) -from adcp.types.generated_poc.core.canonical_budget_allocation import ( +from adcp.types.domains.core.canonical_budget_allocation import ( CanonicalBudgetAllocation, CanonicalBudgetAllocation1, CanonicalBudgetAllocation2, ) -from adcp.types.generated_poc.core.canonical_delivery_forecast import CanonicalDeliveryForecast -from adcp.types.generated_poc.core.canonical_forecast_point import ( +from adcp.types.domains.core.canonical_delivery_forecast import CanonicalDeliveryForecast +from adcp.types.domains.core.canonical_forecast_point import ( CanonicalForecastPoint, Metrics, Viewability, ) -from adcp.types.generated_poc.core.canonical_forecast_vendor_metric_value import ( +from adcp.types.domains.core.canonical_forecast_vendor_metric_value import ( CanonicalForecastVendorMetricValue, ) -from adcp.types.generated_poc.core.canonical_format_kind import CanonicalFormatKind -from adcp.types.generated_poc.core.canonical_format_option import ( +from adcp.types.domains.core.canonical_format_kind import CanonicalFormatKind +from adcp.types.domains.core.canonical_format_option import ( CanonicalFormatOption, FormatKind, SellerPreference, ) -from adcp.types.generated_poc.core.canonical_measurement_terms import ( +from adcp.types.domains.core.canonical_measurement_terms import ( BillingMeasurement, CanonicalMeasurementTerms, MakegoodPolicy, ) -from adcp.types.generated_poc.core.canonical_media_buy_action import ( +from adcp.types.domains.core.canonical_media_buy_action import ( Action3, Action4, CanonicalMediaBuyAction, @@ -1048,12 +1004,10 @@ CanonicalMediaBuyAction2, CanonicalMediaBuyAction3, ) -from adcp.types.generated_poc.core.canonical_media_buy_action_fields import ( - CanonicalMediaBuyActionFields, -) -from adcp.types.generated_poc.core.canonical_media_buy_features import CanonicalMediaBuyFeatures -from adcp.types.generated_poc.core.canonical_metric_qualifier import CanonicalMetricQualifier -from adcp.types.generated_poc.core.canonical_optimization_goal import ( +from adcp.types.domains.core.canonical_media_buy_action_fields import CanonicalMediaBuyActionFields +from adcp.types.domains.core.canonical_media_buy_features import CanonicalMediaBuyFeatures +from adcp.types.domains.core.canonical_metric_qualifier import CanonicalMetricQualifier +from adcp.types.domains.core.canonical_optimization_goal import ( CanonicalOptimizationGoal, CanonicalOptimizationGoal1, CanonicalOptimizationGoal2, @@ -1061,18 +1015,16 @@ Target10, Target11, ) -from adcp.types.generated_poc.core.canonical_performance_standard import ( - CanonicalPerformanceStandard, -) -from adcp.types.generated_poc.core.canonical_placement import ( +from adcp.types.domains.core.canonical_performance_standard import CanonicalPerformanceStandard +from adcp.types.domains.core.canonical_placement import ( CanonicalDoohPlacementAttributes, CanonicalDoohScreenResolution, CanonicalProductPlacement, CanonicalProductPlacement1, CanonicalProductPlacement2, ) -from adcp.types.generated_poc.core.canonical_pricing_option import CanonicalPricingOption -from adcp.types.generated_poc.core.canonical_product import ( +from adcp.types.domains.core.canonical_pricing_option import CanonicalPricingOption +from adcp.types.domains.core.canonical_product import ( CanonicalProduct, CatalogMatch, MatchedGtin, @@ -1086,66 +1038,60 @@ PublisherProperty6, PublisherProperty7, ) -from adcp.types.generated_poc.core.canonical_product_action import CanonicalProductAction -from adcp.types.generated_poc.core.canonical_projection_ref import ( +from adcp.types.domains.core.canonical_product_action import CanonicalProductAction +from adcp.types.domains.core.canonical_projection_ref import ( AssetSource, CanonicalProjectionReference, ) -from adcp.types.generated_poc.core.canonical_projection_slot_override import ( +from adcp.types.domains.core.canonical_projection_slot_override import ( CanonicalProjectionSlotOverride, ) -from adcp.types.generated_poc.core.canonical_proposal import CanonicalProposal, ProposalKind -from adcp.types.generated_poc.core.canonical_reporting_capabilities import ( +from adcp.types.domains.core.canonical_proposal import CanonicalProposal, ProposalKind +from adcp.types.domains.core.canonical_reporting_capabilities import ( CanonicalReportingCapabilities, DateRangeSupport, VendorMetric, ) -from adcp.types.generated_poc.core.canonical_reporting_commitment import ( +from adcp.types.domains.core.canonical_reporting_commitment import ( CanonicalReportingCommitment, CanonicalReportingCommitment1, CanonicalReportingCommitment2, ) -from adcp.types.generated_poc.core.canvas_constraint import CanvasConstraint, Constraint, Region -from adcp.types.generated_poc.core.capabilities_changed_webhook import CapabilitiesChangedWebhook -from adcp.types.generated_poc.core.catalog import Catalog, Gtin -from adcp.types.generated_poc.core.catalog_field_mapping import CatalogFieldMapping, Transform -from adcp.types.generated_poc.core.catalog_item_availability_error import ( - CatalogItemAvailabilityError, -) -from adcp.types.generated_poc.core.catalog_item_availability_ref import ( - CatalogItemAvailabilityReference, -) -from adcp.types.generated_poc.core.catalog_item_availability_state import ( +from adcp.types.domains.core.canvas_constraint import CanvasConstraint, Constraint, Region +from adcp.types.domains.core.capabilities_changed_webhook import CapabilitiesChangedWebhook +from adcp.types.domains.core.catalog import Catalog, Gtin +from adcp.types.domains.core.catalog_field_mapping import CatalogFieldMapping, Transform +from adcp.types.domains.core.catalog_item_availability_error import CatalogItemAvailabilityError +from adcp.types.domains.core.catalog_item_availability_ref import CatalogItemAvailabilityReference +from adcp.types.domains.core.catalog_item_availability_state import ( Availability, CatalogItemAvailabilityState, ) -from adcp.types.generated_poc.core.catalog_item_availability_update import ( - CatalogItemAvailabilityUpdate, -) -from adcp.types.generated_poc.core.catalog_item_availability_update_result import ( +from adcp.types.domains.core.catalog_item_availability_update import CatalogItemAvailabilityUpdate +from adcp.types.domains.core.catalog_item_availability_update_result import ( CatalogItemAvailabilityUpdateResult, ) -from adcp.types.generated_poc.core.catalog_item_delivery_metrics import CatalogItemDeliveryMetrics -from adcp.types.generated_poc.core.catalog_item_reference_not_found_error import ( +from adcp.types.domains.core.catalog_item_delivery_metrics import CatalogItemDeliveryMetrics +from adcp.types.domains.core.catalog_item_reference_not_found_error import ( CatalogItemReferenceNotFoundError, ) -from adcp.types.generated_poc.core.catalog_selection import CatalogSelection -from adcp.types.generated_poc.core.catchment import Catchment, Geometry, Radius, TravelTime -from adcp.types.generated_poc.core.collection import Collection, RelatedCollection -from adcp.types.generated_poc.core.collection_delivery_metrics import CollectionDeliveryMetrics -from adcp.types.generated_poc.core.collection_distribution import CollectionDistribution -from adcp.types.generated_poc.core.collection_list_ref import CollectionListReference -from adcp.types.generated_poc.core.collection_property_delivery_metrics import ( +from adcp.types.domains.core.catalog_selection import CatalogSelection +from adcp.types.domains.core.catchment import Catchment, Geometry, Radius, TravelTime +from adcp.types.domains.core.collection import Collection, RelatedCollection +from adcp.types.domains.core.collection_delivery_metrics import CollectionDeliveryMetrics +from adcp.types.domains.core.collection_distribution import CollectionDistribution +from adcp.types.domains.core.collection_list_ref import CollectionListReference +from adcp.types.domains.core.collection_property_delivery_metrics import ( CollectionPropertyDeliveryMetrics, ) -from adcp.types.generated_poc.core.collection_ref import CollectionReference -from adcp.types.generated_poc.core.collection_selection import ( +from adcp.types.domains.core.collection_ref import CollectionReference +from adcp.types.domains.core.collection_selection import ( CollectionSelection, CollectionSelection1, CollectionSelection2, ) -from adcp.types.generated_poc.core.collection_selector import CollectionSelector -from adcp.types.generated_poc.core.committed_metric import ( +from adcp.types.domains.core.collection_selector import CollectionSelector +from adcp.types.domains.core.committed_metric import ( CommittedMetric, CommittedMetric1, CommittedMetric2, @@ -1153,16 +1099,16 @@ Qualifier1, QualifierModel, ) -from adcp.types.generated_poc.core.compact_task_input_required import CompactTaskInputRequired -from adcp.types.generated_poc.core.compact_task_submitted import CompactTaskSubmitted -from adcp.types.generated_poc.core.compact_task_working import CompactTaskWorking -from adcp.types.generated_poc.core.content_rating import ContentRating -from adcp.types.generated_poc.core.context import ContextObject -from adcp.types.generated_poc.core.creative_approval_scope import ScopedCreativeApproval -from adcp.types.generated_poc.core.creative_asset import CreativeAsset, Input -from adcp.types.generated_poc.core.creative_assets import CreativeAssets, CreativeAssets1 -from adcp.types.generated_poc.core.creative_assignment import CreativeAssignment, RotationMode -from adcp.types.generated_poc.core.creative_brief import ( +from adcp.types.domains.core.compact_task_input_required import CompactTaskInputRequired +from adcp.types.domains.core.compact_task_submitted import CompactTaskSubmitted +from adcp.types.domains.core.compact_task_working import CompactTaskWorking +from adcp.types.domains.core.content_rating import ContentRating +from adcp.types.domains.core.context import ContextObject +from adcp.types.domains.core.creative_approval_scope import ScopedCreativeApproval +from adcp.types.domains.core.creative_asset import CreativeAsset, Input +from adcp.types.domains.core.creative_assets import CreativeAssets, CreativeAssets1 +from adcp.types.domains.core.creative_assignment import CreativeAssignment, RotationMode +from adcp.types.domains.core.creative_brief import ( Compliance, CreativeBrief, Jurisdiction, @@ -1170,19 +1116,19 @@ Objective, RequiredDisclosure, ) -from adcp.types.generated_poc.core.creative_consumption import CreativeConsumption -from adcp.types.generated_poc.core.creative_delivery_metrics import CreativeDeliveryMetrics -from adcp.types.generated_poc.core.creative_filters import CreativeFilters -from adcp.types.generated_poc.core.creative_item import CreativeItem, CreativeItem1, CreativeItem2 -from adcp.types.generated_poc.core.creative_locale_policy import CreativeLocalePolicy -from adcp.types.generated_poc.core.creative_localization import ( +from adcp.types.domains.core.creative_consumption import CreativeConsumption +from adcp.types.domains.core.creative_delivery_metrics import CreativeDeliveryMetrics +from adcp.types.domains.core.creative_filters import CreativeFilters +from adcp.types.domains.core.creative_item import CreativeItem, CreativeItem1, CreativeItem2 +from adcp.types.domains.core.creative_locale_policy import CreativeLocalePolicy +from adcp.types.domains.core.creative_localization import ( CreativeLocalization, LocaleFallback, Source, TargetVariant, UnmatchedLocaleAction, ) -from adcp.types.generated_poc.core.creative_localization_readback import ( +from adcp.types.domains.core.creative_localization_readback import ( CreativeLocalizationReadback, ResolvedAssets, ResolvedAssets1, @@ -1190,43 +1136,43 @@ Variants1, Variants2, ) -from adcp.types.generated_poc.core.creative_manifest import CreativeManifest -from adcp.types.generated_poc.core.creative_operation_format_declaration import ( +from adcp.types.domains.core.creative_manifest import CreativeManifest +from adcp.types.domains.core.creative_operation_format_declaration import ( CreativeOperationFormatDeclaration, ) -from adcp.types.generated_poc.core.creative_policy import CreativePolicy, ProvenanceRequirements -from adcp.types.generated_poc.core.creative_representation import CreativeRepresentation -from adcp.types.generated_poc.core.creative_representation_set import CreativeRepresentationSet -from adcp.types.generated_poc.core.creative_revision_id import CreativeRevisionId -from adcp.types.generated_poc.core.creative_variable import CreativeVariable, VariableType -from adcp.types.generated_poc.core.creative_variant import CreativeVariant, GenerationContext -from adcp.types.generated_poc.core.daast_tracker_constraints import ( +from adcp.types.domains.core.creative_policy import CreativePolicy, ProvenanceRequirements +from adcp.types.domains.core.creative_representation import CreativeRepresentation +from adcp.types.domains.core.creative_representation_set import CreativeRepresentationSet +from adcp.types.domains.core.creative_revision_id import CreativeRevisionId +from adcp.types.domains.core.creative_variable import CreativeVariable, VariableType +from adcp.types.domains.core.creative_variant import CreativeVariant, GenerationContext +from adcp.types.domains.core.daast_tracker_constraints import ( DaastEvent, DaastOffset, DaastTarget, DaastTrackerConstraints, DaastVersions, ) -from adcp.types.generated_poc.core.data_provider_signal_selector import ( +from adcp.types.domains.core.data_provider_signal_selector import ( DataProviderSignalSelector, DataProviderSignalSelector1, DataProviderSignalSelector2, DataProviderSignalSelector3, ) -from adcp.types.generated_poc.core.date_range import DateRange -from adcp.types.generated_poc.core.datetime_range import DatetimeRange -from adcp.types.generated_poc.core.daypart_target import DaypartTarget -from adcp.types.generated_poc.core.deadline_policy import DeadlinePolicy, MaterialStage -from adcp.types.generated_poc.core.delivery_breakdown_controls import DeliveryBreakdownControls -from adcp.types.generated_poc.core.delivery_forecast import DeliveryForecast -from adcp.types.generated_poc.core.delivery_metric_aggregate import ( +from adcp.types.domains.core.date_range import DateRange +from adcp.types.domains.core.datetime_range import DatetimeRange +from adcp.types.domains.core.daypart_target import DaypartTarget +from adcp.types.domains.core.deadline_policy import DeadlinePolicy, MaterialStage +from adcp.types.domains.core.delivery_breakdown_controls import DeliveryBreakdownControls +from adcp.types.domains.core.delivery_forecast import DeliveryForecast +from adcp.types.domains.core.delivery_metric_aggregate import ( DeliveryMetricAggregate, DeliveryMetricAggregate1, DeliveryMetricAggregate2, Field0, Qualifier3, ) -from adcp.types.generated_poc.core.delivery_metrics import ( +from adcp.types.domains.core.delivery_metrics import ( ByActionSourceItem, ByEventTypeItem, DeliveryMetrics, @@ -1244,42 +1190,42 @@ ViewedSecondsHistogramItem, ViewedSecondsPercentiles, ) -from adcp.types.generated_poc.core.delivery_provider import DeliveryProvider -from adcp.types.generated_poc.core.delivery_recipient import DeliveryRecipient -from adcp.types.generated_poc.core.demographic_age_range import DemographicAgeRange -from adcp.types.generated_poc.core.demographic_predicate import DemographicPredicate -from adcp.types.generated_poc.core.demographic_reporting_capability import ( +from adcp.types.domains.core.delivery_provider import DeliveryProvider +from adcp.types.domains.core.delivery_recipient import DeliveryRecipient +from adcp.types.domains.core.demographic_age_range import DemographicAgeRange +from adcp.types.domains.core.demographic_predicate import DemographicPredicate +from adcp.types.domains.core.demographic_reporting_capability import ( Age, DemographicReportingCapability, Interval, ReportingMode, ) -from adcp.types.generated_poc.core.demographic_targeting_capability import ( +from adcp.types.domains.core.demographic_targeting_capability import ( DemographicTargetingCapability, ExecutionMode, UnknownHandling, ) -from adcp.types.generated_poc.core.demographic_targeting_intent import DemographicTargetingIntent -from adcp.types.generated_poc.core.demographic_targeting_resolution import ( +from adcp.types.domains.core.demographic_targeting_intent import DemographicTargetingIntent +from adcp.types.domains.core.demographic_targeting_resolution import ( DemographicTargetingResolution, Execution, Execution1, Execution2, IntervalId, ) -from adcp.types.generated_poc.core.deployment import Deployment, Deployment1, Deployment2 -from adcp.types.generated_poc.core.destination import Destination, Destination1, Destination2 -from adcp.types.generated_poc.core.destination_item import DestinationItem, DestinationType -from adcp.types.generated_poc.core.diagnostic_issue import DiagnosticIssue, Severity -from adcp.types.generated_poc.core.downstream_connection_requirement import ( +from adcp.types.domains.core.deployment import Deployment, Deployment1, Deployment2 +from adcp.types.domains.core.destination import Destination, Destination1, Destination2 +from adcp.types.domains.core.destination_item import DestinationItem, DestinationType +from adcp.types.domains.core.diagnostic_issue import DiagnosticIssue, Severity +from adcp.types.domains.core.downstream_connection_requirement import ( ConnectionType, DownstreamConnectionRequirement, ResourceRef, ) -from adcp.types.generated_poc.core.duration import Duration -from adcp.types.generated_poc.core.education_item import DegreeType, EducationItem, Level, Modality -from adcp.types.generated_poc.core.error import BuyerReason, DiscriminatorItem, Issue, Recovery -from adcp.types.generated_poc.core.evaluator_spec import ( +from adcp.types.domains.core.duration import Duration +from adcp.types.domains.core.education_item import DegreeType, EducationItem, Level, Modality +from adcp.types.domains.core.error import BuyerReason, DiscriminatorItem, Issue, Recovery +from adcp.types.domains.core.evaluator_spec import ( EvalBudget, EvaluatorSpec, EvaluatorSpec1, @@ -1291,32 +1237,29 @@ RankByItem1, RankByItem2, ) -from adcp.types.generated_poc.core.event import Event -from adcp.types.generated_poc.core.event_custom_data import Content, EventCustomData -from adcp.types.generated_poc.core.event_source_health import Detail, EventSourceHealth -from adcp.types.generated_poc.core.event_surface import Category, EventSurface -from adcp.types.generated_poc.core.experimental_feature_id import ExperimentalFeatureId -from adcp.types.generated_poc.core.ext import ExtensionObject -from adcp.types.generated_poc.core.feature_requirement import FeatureRequirement, IfNotCovered -from adcp.types.generated_poc.core.flight_item import FlightItem -from adcp.types.generated_poc.core.forecast_dimension_audience import AudienceForecastDimension -from adcp.types.generated_poc.core.forecast_dimension_device_platform import ( +from adcp.types.domains.core.event import Event +from adcp.types.domains.core.event_custom_data import Content, EventCustomData +from adcp.types.domains.core.event_source_health import Detail, EventSourceHealth +from adcp.types.domains.core.event_surface import Category, EventSurface +from adcp.types.domains.core.experimental_feature_id import ExperimentalFeatureId +from adcp.types.domains.core.ext import ExtensionObject +from adcp.types.domains.core.feature_requirement import FeatureRequirement, IfNotCovered +from adcp.types.domains.core.flight_item import FlightItem +from adcp.types.domains.core.forecast_dimension_audience import AudienceForecastDimension +from adcp.types.domains.core.forecast_dimension_device_platform import ( DevicePlatformForecastDimension, ) -from adcp.types.generated_poc.core.forecast_dimension_device_type import DeviceTypeForecastDimension -from adcp.types.generated_poc.core.forecast_dimension_geo import GeoForecastDimension -from adcp.types.generated_poc.core.forecast_dimension_placement import PlacementForecastDimension -from adcp.types.generated_poc.core.forecast_dimension_signal import ( - Presence, - SignalForecastDimension, -) -from adcp.types.generated_poc.core.forecast_dimension_time import TimeForecastDimension -from adcp.types.generated_poc.core.forecast_point import CoverageRate, ForecastPoint, ViewableRate -from adcp.types.generated_poc.core.forecast_point_dimensions import ForecastPointDimensions -from adcp.types.generated_poc.core.forecast_range import ForecastRange -from adcp.types.generated_poc.core.forecast_rate_range import ForecastRateRange -from adcp.types.generated_poc.core.forecast_vendor_metric_value import ForecastVendorMetricValue -from adcp.types.generated_poc.core.format import ( +from adcp.types.domains.core.forecast_dimension_device_type import DeviceTypeForecastDimension +from adcp.types.domains.core.forecast_dimension_geo import GeoForecastDimension +from adcp.types.domains.core.forecast_dimension_placement import PlacementForecastDimension +from adcp.types.domains.core.forecast_dimension_signal import Presence, SignalForecastDimension +from adcp.types.domains.core.forecast_dimension_time import TimeForecastDimension +from adcp.types.domains.core.forecast_point import CoverageRate, ForecastPoint, ViewableRate +from adcp.types.domains.core.forecast_point_dimensions import ForecastPointDimensions +from adcp.types.domains.core.forecast_range import ForecastRange +from adcp.types.domains.core.forecast_rate_range import ForecastRateRange +from adcp.types.domains.core.forecast_vendor_metric_value import ForecastVendorMetricValue +from adcp.types.domains.core.format import ( Assets10, Assets11, Assets12, @@ -1408,107 +1351,107 @@ Responsive, SelectionMode, ) -from adcp.types.generated_poc.core.format_id import FormatReferenceStructuredObject -from adcp.types.generated_poc.core.format_option_ref import ( +from adcp.types.domains.core.format_id import FormatReferenceStructuredObject +from adcp.types.domains.core.format_option_ref import ( FormatOptionReference, FormatOptionReference1, FormatOptionReference2, ) -from adcp.types.generated_poc.core.format_shape_vocabulary import AdcpFormatShapeVocabularyRegistry -from adcp.types.generated_poc.core.frequency_cap import FrequencyCap -from adcp.types.generated_poc.core.frequency_cap_constraints import FrequencyCapConstraints -from adcp.types.generated_poc.core.frequency_cap_duration_unit import FrequencyCapDurationUnit -from adcp.types.generated_poc.core.frequency_cap_impression_constraints import ( +from adcp.types.domains.core.format_shape_vocabulary import AdcpFormatShapeVocabularyRegistry +from adcp.types.domains.core.frequency_cap import FrequencyCap +from adcp.types.domains.core.frequency_cap_constraints import FrequencyCapConstraints +from adcp.types.domains.core.frequency_cap_duration_unit import FrequencyCapDurationUnit +from adcp.types.domains.core.frequency_cap_impression_constraints import ( AllowedValue, FrequencyCapImpressionConstraints, ) -from adcp.types.generated_poc.core.frequency_cap_interval_constraints import ( +from adcp.types.domains.core.frequency_cap_interval_constraints import ( AllowedInterval, FrequencyCapIntervalConstraints, ) -from adcp.types.generated_poc.core.frequency_cap_requirements import FrequencyCapRequirements -from adcp.types.generated_poc.core.generation_credential import GenerationCredential -from adcp.types.generated_poc.core.geo_breakdown_support import GeographicBreakdownSupport -from adcp.types.generated_poc.core.geo_delivery_metrics import GeoDeliveryMetrics -from adcp.types.generated_poc.core.geo_metro import GeoMetro -from adcp.types.generated_poc.core.geo_place_area import GeographicPlaceArea -from adcp.types.generated_poc.core.geo_place_catalog_capability import ( +from adcp.types.domains.core.frequency_cap_requirements import FrequencyCapRequirements +from adcp.types.domains.core.generation_credential import GenerationCredential +from adcp.types.domains.core.geo_breakdown_support import GeographicBreakdownSupport +from adcp.types.domains.core.geo_delivery_metrics import GeoDeliveryMetrics +from adcp.types.domains.core.geo_metro import GeoMetro +from adcp.types.domains.core.geo_place_area import GeographicPlaceArea +from adcp.types.domains.core.geo_place_catalog_capability import ( GeographicPlaceCatalogCapability, SupportedVersion, ) -from adcp.types.generated_poc.core.geo_place_catalog_entry import ( +from adcp.types.domains.core.geo_place_catalog_entry import ( GeographicPlaceCatalogEntry, ParentLabel, ReplacedByValue, ) -from adcp.types.generated_poc.core.geo_place_requirement import ( +from adcp.types.domains.core.geo_place_requirement import ( CatalogRequirement, GeographicPlaceRequirement, SystemVersion, ) -from adcp.types.generated_poc.core.geo_place_resolver import Auth, GeographicPlaceResolver -from adcp.types.generated_poc.core.geo_place_support import GeographicPlaceSystemSupport -from adcp.types.generated_poc.core.geo_place_system import ( +from adcp.types.domains.core.geo_place_resolver import Auth, GeographicPlaceResolver +from adcp.types.domains.core.geo_place_support import GeographicPlaceSystemSupport +from adcp.types.domains.core.geo_place_system import ( GeographicPlaceIdentifierSystem, GeographicPlaceIdentifierSystem1, GeographicPlaceIdentifierSystem2, ) -from adcp.types.generated_poc.core.geo_place_type import ( +from adcp.types.domains.core.geo_place_type import ( GeographicPlaceType, GeographicPlaceType1, GeographicPlaceType2, ) -from adcp.types.generated_poc.core.geo_region_requirement import ( +from adcp.types.domains.core.geo_region_requirement import ( Countries, Countries1, GeographicRegionRequirement, ) -from adcp.types.generated_poc.core.geo_region_support import Countries3, GeographicRegionSupport -from adcp.types.generated_poc.core.get_geo_place_resolution_request import ( +from adcp.types.domains.core.geo_region_support import Countries3, GeographicRegionSupport +from adcp.types.domains.core.get_geo_place_resolution_request import ( GetGeographicPlaceResolutionRequest, ) -from adcp.types.generated_poc.core.get_geo_place_resolution_response import ( +from adcp.types.domains.core.get_geo_place_resolution_response import ( GetGeographicPlaceResolutionResponse, ) -from adcp.types.generated_poc.core.hotel_item import HotelItem -from adcp.types.generated_poc.core.iana_timezone import IanaTimezoneIdentifier -from adcp.types.generated_poc.core.identifier import Identifier -from adcp.types.generated_poc.core.impairment import Impairment, Transition -from adcp.types.generated_poc.core.indicator import Indicator -from adcp.types.generated_poc.core.indicator_bearing import IndicatorBearingResourceState -from adcp.types.generated_poc.core.indicator_scope import IndicatorScope -from adcp.types.generated_poc.core.indicators_changed_webhook import ( +from adcp.types.domains.core.hotel_item import HotelItem +from adcp.types.domains.core.iana_timezone import IanaTimezoneIdentifier +from adcp.types.domains.core.identifier import Identifier +from adcp.types.domains.core.impairment import Impairment, Transition +from adcp.types.domains.core.indicator import Indicator +from adcp.types.domains.core.indicator_bearing import IndicatorBearingResourceState +from adcp.types.domains.core.indicator_scope import IndicatorScope +from adcp.types.domains.core.indicators_changed_webhook import ( ChangeKind, IndicatorsChangedWebhook, RelationshipKind, ) -from adcp.types.generated_poc.core.industry_identifier import IndustryIdentifier -from adcp.types.generated_poc.core.insertion_order import InsertionOrder, Terms, TotalBudget -from adcp.types.generated_poc.core.installment import DerivativeOf, Installment -from adcp.types.generated_poc.core.installment_deadlines import InstallmentDeadlines -from adcp.types.generated_poc.core.installment_delivery_metrics import InstallmentDeliveryMetrics -from adcp.types.generated_poc.core.installment_property_delivery_metrics import ( +from adcp.types.domains.core.industry_identifier import IndustryIdentifier +from adcp.types.domains.core.insertion_order import InsertionOrder, Terms, TotalBudget +from adcp.types.domains.core.installment import DerivativeOf, Installment +from adcp.types.domains.core.installment_deadlines import InstallmentDeadlines +from adcp.types.domains.core.installment_delivery_metrics import InstallmentDeliveryMetrics +from adcp.types.domains.core.installment_property_delivery_metrics import ( InstallmentPropertyDeliveryMetrics, ) -from adcp.types.generated_poc.core.installment_ref import InstallmentReference -from adcp.types.generated_poc.core.inventory_list_application import ( +from adcp.types.domains.core.installment_ref import InstallmentReference +from adcp.types.domains.core.inventory_list_application import ( InventoryListApplication, InventoryListApplication1, InventoryListApplication2, Summary2, ) -from adcp.types.generated_poc.core.job_item import ( +from adcp.types.domains.core.job_item import ( EmploymentType, ExperienceLevel, JobItem, Period, Salary, ) -from adcp.types.generated_poc.core.keyword_delivery_metrics import KeywordDeliveryMetrics -from adcp.types.generated_poc.core.keyword_target import KeywordTarget -from adcp.types.generated_poc.core.limited_series import LimitedSeries -from adcp.types.generated_poc.core.locale_tag import LanguageTag -from adcp.types.generated_poc.core.localized_creative_asset import ( +from adcp.types.domains.core.keyword_delivery_metrics import KeywordDeliveryMetrics +from adcp.types.domains.core.keyword_target import KeywordTarget +from adcp.types.domains.core.limited_series import LimitedSeries +from adcp.types.domains.core.locale_tag import LanguageTag +from adcp.types.domains.core.localized_creative_asset import ( LocalizedCreativeAsset, LocalizedCreativeAsset1, LocalizedCreativeAsset10, @@ -1532,62 +1475,56 @@ LocalizedCreativeAsset8, LocalizedCreativeAsset9, ) -from adcp.types.generated_poc.core.macro_bearing_url import ( +from adcp.types.domains.core.macro_bearing_url import ( MacroBearingUrl, MacroBearingUrl3, MacroBearingUrl4, ) -from adcp.types.generated_poc.core.macro_declaration import MacroDeclaration -from adcp.types.generated_poc.core.macro_encoding import MacroEncoding -from adcp.types.generated_poc.core.macro_resolution_capability import ( +from adcp.types.domains.core.macro_declaration import MacroDeclaration +from adcp.types.domains.core.macro_encoding import MacroEncoding +from adcp.types.domains.core.macro_resolution_capability import ( MacroProcessingCapability, MappingStatus, ) -from adcp.types.generated_poc.core.macro_resolution_result import MacroResolutionResult -from adcp.types.generated_poc.core.macro_translation_target import MacroTranslationTarget -from adcp.types.generated_poc.core.material_deadline import MaterialDeadline -from adcp.types.generated_poc.core.mcp_webhook_payload import McpWebhookPayload -from adcp.types.generated_poc.core.measurement_readiness import MeasurementReadiness -from adcp.types.generated_poc.core.measurement_terms import MeasurementTerms -from adcp.types.generated_poc.core.measurement_window import MeasurementWindow -from adcp.types.generated_poc.core.media_buy import Cancellation, MediaBuy -from adcp.types.generated_poc.core.media_buy_available_action import MediaBuyAvailableAction -from adcp.types.generated_poc.core.media_buy_available_action_id import MediaBuyAvailableActionId -from adcp.types.generated_poc.core.media_buy_change_term_id import MediaBuyChangeTermId -from adcp.types.generated_poc.core.media_buy_features import MediaBuyFeatures -from adcp.types.generated_poc.core.media_buy_frequency_cap import MediaBuyFrequencyCap -from adcp.types.generated_poc.core.media_buy_frequency_cap_capability import ( +from adcp.types.domains.core.macro_resolution_result import MacroResolutionResult +from adcp.types.domains.core.macro_translation_target import MacroTranslationTarget +from adcp.types.domains.core.material_deadline import MaterialDeadline +from adcp.types.domains.core.mcp_webhook_payload import McpWebhookPayload +from adcp.types.domains.core.measurement_readiness import MeasurementReadiness +from adcp.types.domains.core.measurement_terms import MeasurementTerms +from adcp.types.domains.core.measurement_window import MeasurementWindow +from adcp.types.domains.core.media_buy import Cancellation, MediaBuy +from adcp.types.domains.core.media_buy_available_action import MediaBuyAvailableAction +from adcp.types.domains.core.media_buy_available_action_id import MediaBuyAvailableActionId +from adcp.types.domains.core.media_buy_change_term_id import MediaBuyChangeTermId +from adcp.types.domains.core.media_buy_features import MediaBuyFeatures +from adcp.types.domains.core.media_buy_frequency_cap import MediaBuyFrequencyCap +from adcp.types.domains.core.media_buy_frequency_cap_capability import ( MediaBuyFrequencyCapCapability, ) -from adcp.types.generated_poc.core.media_buy_frequency_cap_requirement import ( +from adcp.types.domains.core.media_buy_frequency_cap_requirement import ( MediaBuyFrequencyCapRequirement, ) -from adcp.types.generated_poc.core.media_buy_frequency_cap_support import ( - MediaBuyFrequencyCapSupport, -) -from adcp.types.generated_poc.core.media_buy_legacy_terms_ref import MediaBuyTermsReference -from adcp.types.generated_poc.core.media_buy_support import ProductMediaBuySupport -from adcp.types.generated_poc.core.media_buy_support_requirements import ( +from adcp.types.domains.core.media_buy_frequency_cap_support import MediaBuyFrequencyCapSupport +from adcp.types.domains.core.media_buy_legacy_terms_ref import MediaBuyTermsReference +from adcp.types.domains.core.media_buy_support import ProductMediaBuySupport +from adcp.types.domains.core.media_buy_support_requirements import ( ProductMediaBuySupportRequirements, ) -from adcp.types.generated_poc.core.missing_metric import ( - MissingMetric, - MissingMetric1, - MissingMetric2, -) -from adcp.types.generated_poc.core.negative_keyword import NegativeKeyword -from adcp.types.generated_poc.core.notification_config import NotificationConfig, ProductPayloadView -from adcp.types.generated_poc.core.offering import GeoTargets, Metro, Offering -from adcp.types.generated_poc.core.offering_asset_group import Items, OfferingAssetGroup -from adcp.types.generated_poc.core.operator_identity import OperatorIdentity -from adcp.types.generated_poc.core.operator_unit import OperatorUnit -from adcp.types.generated_poc.core.opportunity_context import ( +from adcp.types.domains.core.missing_metric import MissingMetric, MissingMetric1, MissingMetric2 +from adcp.types.domains.core.negative_keyword import NegativeKeyword +from adcp.types.domains.core.notification_config import NotificationConfig, ProductPayloadView +from adcp.types.domains.core.offering import GeoTargets, Metro, Offering +from adcp.types.domains.core.offering_asset_group import Items, OfferingAssetGroup +from adcp.types.domains.core.operator_identity import OperatorIdentity +from adcp.types.domains.core.operator_unit import OperatorUnit +from adcp.types.domains.core.opportunity_context import ( CloseReason, Intent, OpportunityContext, Phase, ) -from adcp.types.generated_poc.core.optimization_goal import ( +from adcp.types.domains.core.optimization_goal import ( OptimizationGoal, OptimizationGoal10, OptimizationGoal8, @@ -1599,11 +1536,11 @@ Target18, Target19, ) -from adcp.types.generated_poc.core.outcome_measurement import OutcomeMeasurement -from adcp.types.generated_poc.core.outcome_target_cost_per import OutcomeTargetCostPer -from adcp.types.generated_poc.core.overlay import Bounds, Overlay, Visual -from adcp.types.generated_poc.core.package_delivery_metric_value import PackageDeliveryMetricValue -from adcp.types.generated_poc.core.package_format_snapshot import ( +from adcp.types.domains.core.outcome_measurement import OutcomeMeasurement +from adcp.types.domains.core.outcome_target_cost_per import OutcomeTargetCostPer +from adcp.types.domains.core.overlay import Bounds, Overlay, Visual +from adcp.types.domains.core.package_delivery_metric_value import PackageDeliveryMetricValue +from adcp.types.domains.core.package_format_snapshot import ( PackageFormatSnapshot, PackageFormatSnapshot1, PackageFormatSnapshot10, @@ -1639,7 +1576,7 @@ PackageFormatSnapshot8, PackageFormatSnapshot9, ) -from adcp.types.generated_poc.core.package_signal_targeting import ( +from adcp.types.domains.core.package_signal_targeting import ( PackageSignalTargeting, PackageSignalTargeting1, PackageSignalTargeting2, @@ -1649,51 +1586,49 @@ PackageSignalTargeting6, PackageSignalTargeting7, ) -from adcp.types.generated_poc.core.package_signal_targeting_group import ( +from adcp.types.domains.core.package_signal_targeting_group import ( Operator, PackageSignalTargetingGroup, ) -from adcp.types.generated_poc.core.package_signal_targeting_groups import ( - PackageSignalTargetingGroups, -) -from adcp.types.generated_poc.core.package_targeting_resolution import PackageTargetingResolution -from adcp.types.generated_poc.core.pagination_request import PaginationRequest -from adcp.types.generated_poc.core.pagination_response import PaginationResponse -from adcp.types.generated_poc.core.performance_feedback import ( +from adcp.types.domains.core.package_signal_targeting_groups import PackageSignalTargetingGroups +from adcp.types.domains.core.package_targeting_resolution import PackageTargetingResolution +from adcp.types.domains.core.pagination_request import PaginationRequest +from adcp.types.domains.core.pagination_response import PaginationResponse +from adcp.types.domains.core.performance_feedback import ( MeasurementPeriod, Metric7, PerformanceFeedback, ) -from adcp.types.generated_poc.core.performance_feedback_assertion import ( +from adcp.types.domains.core.performance_feedback_assertion import ( ConfidenceInterval, Evidence, PerformanceFeedbackAssertion, ) -from adcp.types.generated_poc.core.performance_feedback_metric import ( +from adcp.types.domains.core.performance_feedback_metric import ( PerformanceFeedbackMetric, PerformanceFeedbackMetric1, PerformanceFeedbackMetric2, ) -from adcp.types.generated_poc.core.performance_standard import PerformanceStandard -from adcp.types.generated_poc.core.placement import ( +from adcp.types.domains.core.performance_standard import PerformanceStandard +from adcp.types.domains.core.placement import ( Placement, ProductDoohPlacementAttributes, ProductDoohScreenResolution, ) -from adcp.types.generated_poc.core.placement_definition import ( +from adcp.types.domains.core.placement_definition import ( FormatOptions, PlacementDefinition, PublisherDoohPlacementAttributes, PublisherDoohScreenResolution, ) -from adcp.types.generated_poc.core.placement_delivery_metrics import PlacementDeliveryMetrics -from adcp.types.generated_poc.core.placement_evidence import PlacementEvidence -from adcp.types.generated_poc.core.placement_identity import ( +from adcp.types.domains.core.placement_delivery_metrics import PlacementDeliveryMetrics +from adcp.types.domains.core.placement_evidence import PlacementEvidence +from adcp.types.domains.core.placement_identity import ( PlacementIdentity, PlacementIdentity1, PlacementIdentity2, ) -from adcp.types.generated_poc.core.placement_presentation import ( +from adcp.types.domains.core.placement_presentation import ( BoxDecoration, Canvas, Color, @@ -1706,19 +1641,19 @@ Rectangle, TextDecoration, ) -from adcp.types.generated_poc.core.placement_property_delivery_metrics import ( +from adcp.types.domains.core.placement_property_delivery_metrics import ( PlacementPropertyDeliveryMetrics, ) -from adcp.types.generated_poc.core.placement_ref import PlacementReference -from adcp.types.generated_poc.core.placement_selection import ( +from adcp.types.domains.core.placement_ref import PlacementReference +from adcp.types.domains.core.placement_selection import ( PlacementSelection, PlacementSelection1, PlacementSelection2, ) -from adcp.types.generated_poc.core.planned_delivery import Geo, PlannedDelivery -from adcp.types.generated_poc.core.platform_extension_ref import PlatformExtensionReference -from adcp.types.generated_poc.core.positive_postal_area_support import PositivePostalAreaSupport -from adcp.types.generated_poc.core.postal_area import ( +from adcp.types.domains.core.planned_delivery import Geo, PlannedDelivery +from adcp.types.domains.core.platform_extension_ref import PlatformExtensionReference +from adcp.types.domains.core.positive_postal_area_support import PositivePostalAreaSupport +from adcp.types.domains.core.postal_area import ( PostalArea, PostalArea1, PostalArea11, @@ -1749,14 +1684,14 @@ System3, System9, ) -from adcp.types.generated_poc.core.postal_area_support import ( +from adcp.types.domains.core.postal_area_support import ( CAEnum, GBEnum, ME, PostalAreaSupport, PostalAreaSupportAdditionalPropertyEnum, ) -from adcp.types.generated_poc.core.postal_country_system import ( +from adcp.types.domains.core.postal_country_system import ( PostalCountrySystem, PostalCountrySystem1, PostalCountrySystem10, @@ -1773,28 +1708,28 @@ System13, System19, ) -from adcp.types.generated_poc.core.presentation_ref import PlacementPresentationReference -from adcp.types.generated_poc.core.preview_provider import PublisherDesignatedPreviewProvider -from adcp.types.generated_poc.core.preview_renderer_metadata import PreviewRendererMetadata -from adcp.types.generated_poc.core.price import Price -from adcp.types.generated_poc.core.pricing_option import PricingOption -from adcp.types.generated_poc.core.principal_changed_webhook import PrincipalChangedWebhook -from adcp.types.generated_poc.core.principal_declarations import ( +from adcp.types.domains.core.presentation_ref import PlacementPresentationReference +from adcp.types.domains.core.preview_provider import PublisherDesignatedPreviewProvider +from adcp.types.domains.core.preview_renderer_metadata import PreviewRendererMetadata +from adcp.types.domains.core.price import Price +from adcp.types.domains.core.pricing_option import PricingOption +from adcp.types.domains.core.principal_changed_webhook import PrincipalChangedWebhook +from adcp.types.domains.core.principal_declarations import ( AgentDeclarations, AsyncAdcpVersion, WebhookSigningAlgorithm, ) -from adcp.types.generated_poc.core.principal_declarations_state import ( +from adcp.types.domains.core.principal_declarations_state import ( Axis, Exclusion, PrincipalDeclarationsState, ) -from adcp.types.generated_poc.core.principal_state import ( +from adcp.types.domains.core.principal_state import ( DestinationRef, PrincipalState, RetiredDestination, ) -from adcp.types.generated_poc.core.product import ( +from adcp.types.domains.core.product import ( AudienceActivation, ConversionTracking, DeliveryMeasurement, @@ -1816,25 +1751,25 @@ SupportedViewDuration, TrustedMatch, ) -from adcp.types.generated_poc.core.product_allocation import ProductAllocation -from adcp.types.generated_poc.core.product_allowed_action import ProductAllowedAction -from adcp.types.generated_poc.core.product_audience_evidence_requirements import ( +from adcp.types.domains.core.product_allocation import ProductAllocation +from adcp.types.domains.core.product_allowed_action import ProductAllowedAction +from adcp.types.domains.core.product_audience_evidence_requirements import ( AcceptedAttestationIssuers, AcceptedAttestationIssuers1, AcceptedAttestationIssuers2, AcceptedAttestationIssuers3, ProductAudienceEvidenceRequirements, ) -from adcp.types.generated_poc.core.product_card_reference_asset import ProductCardReferenceAsset -from adcp.types.generated_poc.core.product_change_map import ProductChangeMap, ProductChangeMap1 -from adcp.types.generated_poc.core.product_execution_requirement import ( +from adcp.types.domains.core.product_card_reference_asset import ProductCardReferenceAsset +from adcp.types.domains.core.product_change_map import ProductChangeMap, ProductChangeMap1 +from adcp.types.domains.core.product_execution_requirement import ( Connection, ProductExecutionRequirement, ProductExecutionRequirement1, ProductExecutionRequirement2, ProductExecutionRequirement3, ) -from adcp.types.generated_poc.core.product_filters import ( +from adcp.types.domains.core.product_filters import ( AudienceActivationMethods, AudienceActivationMethods1, AudienceActivationMethods2, @@ -1857,7 +1792,7 @@ SignalTargetingItem7, TargetingMode, ) -from adcp.types.generated_poc.core.product_format_declaration import ( +from adcp.types.domains.core.product_format_declaration import ( ProductFormatDeclaration, ProductFormatDeclaration1, ProductFormatDeclaration10, @@ -1876,27 +1811,27 @@ ProductFormatDeclaration8, ProductFormatDeclaration9, ) -from adcp.types.generated_poc.core.product_identity import ProductIdentity -from adcp.types.generated_poc.core.product_offer_filters import ( +from adcp.types.domains.core.product_identity import ProductIdentity +from adcp.types.domains.core.product_offer_filters import ( AvailabilityHorizon, ProductOfferFilters, RequiredPerformanceStandard, ) -from adcp.types.generated_poc.core.product_signal_targeting_option import ( +from adcp.types.domains.core.product_signal_targeting_option import ( ActivationStatus, AllowedTargetingMode, ProductSignalTargetingOption, ) -from adcp.types.generated_poc.core.product_targeting_resolution import ProductTargetingResolution -from adcp.types.generated_poc.core.property import Property -from adcp.types.generated_poc.core.property_delivery_metrics import PropertyDeliveryMetrics -from adcp.types.generated_poc.core.property_id import PropertyId -from adcp.types.generated_poc.core.property_list_ref import PropertyListReference -from adcp.types.generated_poc.core.property_ref import PropertyReference -from adcp.types.generated_poc.core.property_tag import PropertyTag -from adcp.types.generated_poc.core.proposal import Proposal -from adcp.types.generated_poc.core.protocol_envelope import ProtocolEnvelope -from adcp.types.generated_poc.core.provenance import ( +from adcp.types.domains.core.product_targeting_resolution import ProductTargetingResolution +from adcp.types.domains.core.property import Property +from adcp.types.domains.core.property_delivery_metrics import PropertyDeliveryMetrics +from adcp.types.domains.core.property_id import PropertyId +from adcp.types.domains.core.property_list_ref import PropertyListReference +from adcp.types.domains.core.property_ref import PropertyReference +from adcp.types.domains.core.property_tag import PropertyTag +from adcp.types.domains.core.proposal import Proposal +from adcp.types.domains.core.protocol_envelope import ProtocolEnvelope +from adcp.types.domains.core.provenance import ( AiTool, C2pa, EmbeddedProvenanceItem, @@ -1906,17 +1841,17 @@ VerifyAgent18, Watermark, ) -from adcp.types.generated_poc.core.publisher_property_selector import ( +from adcp.types.domains.core.publisher_property_selector import ( PublisherPropertySelector, PublisherPropertySelector1, PublisherPropertySelector2, PublisherPropertySelector3, ) -from adcp.types.generated_poc.core.push_notification_config import PushNotificationConfig -from adcp.types.generated_poc.core.real_estate_item import ListingType, RealEstateItem -from adcp.types.generated_poc.core.reference_asset import ReferenceAsset -from adcp.types.generated_poc.core.reference_renderer import ReferenceRenderer -from adcp.types.generated_poc.core.registry_event import ( +from adcp.types.domains.core.push_notification_config import PushNotificationConfig +from adcp.types.domains.core.real_estate_item import ListingType, RealEstateItem +from adcp.types.domains.core.reference_asset import ReferenceAsset +from adcp.types.domains.core.reference_renderer import ReferenceRenderer +from adcp.types.domains.core.registry_event import ( AgentProfilePayload, AuthorizationPayload, AuthorizationType, @@ -1975,16 +1910,16 @@ StringArray, Tracks, ) -from adcp.types.generated_poc.core.registry_feed_response import Freshness, RegistryFeedResponse -from adcp.types.generated_poc.core.reporting_adjustment import AccountingPeriod, ReportingAdjustment -from adcp.types.generated_poc.core.reporting_adjustment_receipt import ( +from adcp.types.domains.core.registry_feed_response import Freshness, RegistryFeedResponse +from adcp.types.domains.core.reporting_adjustment import AccountingPeriod, ReportingAdjustment +from adcp.types.domains.core.reporting_adjustment_receipt import ( ReportingAdjustmentReceipt, ReportingAdjustmentRejectionCode, ) -from adcp.types.generated_poc.core.reporting_canonical_content_digest import ( +from adcp.types.domains.core.reporting_canonical_content_digest import ( ReportingCanonicalContentDigest, ) -from adcp.types.generated_poc.core.reporting_canonicalization_contract import ( +from adcp.types.domains.core.reporting_canonicalization_contract import ( AdditionalItem, EmptyReport, GoldenVectors, @@ -1992,52 +1927,52 @@ ReportingCanonicalizationContract, ReportingPrimaryKey, ) -from adcp.types.generated_poc.core.reporting_capabilities import ReportingCapabilities -from adcp.types.generated_poc.core.reporting_consumer_status import ( +from adcp.types.domains.core.reporting_capabilities import ReportingCapabilities +from adcp.types.domains.core.reporting_consumer_status import ( ConsumerStatus, FailureCode, MismatchCode, ReportingConsumerStatus, ) -from adcp.types.generated_poc.core.reporting_control_total import ( +from adcp.types.domains.core.reporting_control_total import ( ReportingControlTotal, ReportingControlTotal1, ReportingControlTotal2, ) -from adcp.types.generated_poc.core.reporting_coverage import ( +from adcp.types.domains.core.reporting_coverage import ( Limitation, ReportingCoverage, ReportingMediaBuyId, ReportingPackageId, ) -from adcp.types.generated_poc.core.reporting_dataset_share_destination import ( +from adcp.types.domains.core.reporting_dataset_share_destination import ( Recipient, ReportingCloud, ReportingDatasetShareDestination, ReportingDatasetShareDestination1, ReportingDatasetShareDestination2, ) -from adcp.types.generated_poc.core.reporting_delivery_capabilities import ( +from adcp.types.domains.core.reporting_delivery_capabilities import ( OperationsContact, ReportingDeliveryCapabilities, ) -from adcp.types.generated_poc.core.reporting_delivery_config import ( +from adcp.types.domains.core.reporting_delivery_config import ( AuthoritativeParty, CoverageRequirement, ReportingDeliveryConfiguration, ) -from adcp.types.generated_poc.core.reporting_delivery_config_state import ( +from adcp.types.domains.core.reporting_delivery_config_state import ( ReportingDeliveryConfigLifecycleState, ReportingDeliveryConfigurationState, ) -from adcp.types.generated_poc.core.reporting_delivery_method import ( +from adcp.types.domains.core.reporting_delivery_method import ( ReportingDeliveryMethod, ReportingDeliveryMethod1, ReportingDeliveryMethod2, ReportingDeliveryMethod3, ReportingOrchestration, ) -from adcp.types.generated_poc.core.reporting_delivery_offering import ( +from adcp.types.domains.core.reporting_delivery_offering import ( DestinationMode, ProducerIdentity, ReportingDeliveryOffering, @@ -2045,37 +1980,33 @@ ReportingFeedPurpose, ReportingProfile, ) -from adcp.types.generated_poc.core.reporting_delivery_offering_id import ReportingDeliveryOfferingId -from adcp.types.generated_poc.core.reporting_delivery_ready_webhook import ( +from adcp.types.domains.core.reporting_delivery_offering_id import ReportingDeliveryOfferingId +from adcp.types.domains.core.reporting_delivery_ready_webhook import ( Readiness, ReportingDeliveryReadyWebhook, ) -from adcp.types.generated_poc.core.reporting_file_compression import ReportingFileCompression -from adcp.types.generated_poc.core.reporting_file_entry import ReportingFileEntry -from adcp.types.generated_poc.core.reporting_file_manifest import ReportingFileManifest -from adcp.types.generated_poc.core.reporting_file_object_ref import ReportingFileObjectReference -from adcp.types.generated_poc.core.reporting_ledger_changed_webhook import ( - ReportingLedgerChangedWebhook, -) -from adcp.types.generated_poc.core.reporting_materialization import ReportingMaterialization -from adcp.types.generated_poc.core.reporting_native_version_ref import ( - ReportingNativeVersionReference, -) -from adcp.types.generated_poc.core.reporting_obligation import ( +from adcp.types.domains.core.reporting_file_compression import ReportingFileCompression +from adcp.types.domains.core.reporting_file_entry import ReportingFileEntry +from adcp.types.domains.core.reporting_file_manifest import ReportingFileManifest +from adcp.types.domains.core.reporting_file_object_ref import ReportingFileObjectReference +from adcp.types.domains.core.reporting_ledger_changed_webhook import ReportingLedgerChangedWebhook +from adcp.types.domains.core.reporting_materialization import ReportingMaterialization +from adcp.types.domains.core.reporting_native_version_ref import ReportingNativeVersionReference +from adcp.types.domains.core.reporting_obligation import ( ProductionStatus, ReconciliationStatus, ReportingObligation, ) -from adcp.types.generated_poc.core.reporting_receipt import RejectionCode, ReportingReceipt -from adcp.types.generated_poc.core.reporting_reconciliation_mode import ReportingReconciliationMode -from adcp.types.generated_poc.core.reporting_reliability_statistics import ( +from adcp.types.domains.core.reporting_receipt import RejectionCode, ReportingReceipt +from adcp.types.domains.core.reporting_reconciliation_mode import ReportingReconciliationMode +from adcp.types.domains.core.reporting_reliability_statistics import ( AdjustmentMagnitudeItem, Basis, LatencyPercentiles, ReportingReliabilityMeasurementPeriod, ReportingReliabilityStatistics, ) -from adcp.types.generated_poc.core.reporting_report_definition import ( +from adcp.types.domains.core.reporting_report_definition import ( Aggregation, Calendar, ContractVersion, @@ -2086,30 +2017,27 @@ ReportingReportDefinition, RestatementPolicy, ) -from adcp.types.generated_poc.core.reporting_resource import ( +from adcp.types.domains.core.reporting_resource import ( Immutability, ReportingReaderCompatibilityItem, ReportingResource, ) -from adcp.types.generated_poc.core.reporting_revision import ( +from adcp.types.domains.core.reporting_revision import ( DataThroughPrecision, FinalityBasis, ReportingRevision, ) -from adcp.types.generated_poc.core.reporting_schedule import ( - ReportingSchedule, - ReportingScheduleAlignment, -) -from adcp.types.generated_poc.core.reporting_schedule_offering import ( +from adcp.types.domains.core.reporting_schedule import ReportingSchedule, ReportingScheduleAlignment +from adcp.types.domains.core.reporting_schedule_offering import ( PeriodAnchorPolicy, PeriodTimezonePolicy, ReportingScheduleOffering, ) -from adcp.types.generated_poc.core.reporting_status_changed_webhook import ( +from adcp.types.domains.core.reporting_status_changed_webhook import ( IssueId, ReportingStatusChangedWebhook, ) -from adcp.types.generated_poc.core.reporting_status_issue import ( +from adcp.types.domains.core.reporting_status_issue import ( Code, IssueState, RecommendedAction, @@ -2117,7 +2045,7 @@ ReportingStatusSeverity, ResponsibleParty, ) -from adcp.types.generated_poc.core.reporting_verification import ( +from adcp.types.domains.core.reporting_verification import ( NativeCommitEvidence, ObservedThrough, PhysicalChecksums, @@ -2125,70 +2053,61 @@ ReportingVerification, VerificationPath, ) -from adcp.types.generated_poc.core.reporting_verification_profile import ( - ReportingVerificationProfile, -) -from adcp.types.generated_poc.core.reporting_verification_profile_set import ( +from adcp.types.domains.core.reporting_verification_profile import ReportingVerificationProfile +from adcp.types.domains.core.reporting_verification_profile_set import ( ReportingVerificationProfileSet, ReportingVerificationProfileSetEnum, ) -from adcp.types.generated_poc.core.reporting_webhook import ReportingWebhook -from adcp.types.generated_poc.core.reporting_write_destination import ( +from adcp.types.domains.core.reporting_webhook import ReportingWebhook +from adcp.types.domains.core.reporting_write_destination import ( ReportingWriteDestination, ReportingWriteDestination1, ReportingWriteDestination2, ) -from adcp.types.generated_poc.core.representation_destination import RepresentationDestination -from adcp.types.generated_poc.core.representation_rejection import RepresentationRejection -from adcp.types.generated_poc.core.representation_selection import ( - RepresentationSelection, - ResolvedBy, -) -from adcp.types.generated_poc.core.requirements.asset_requirements import AssetRequirements -from adcp.types.generated_poc.core.requirements.audio_asset_requirements import ( +from adcp.types.domains.core.representation_destination import RepresentationDestination +from adcp.types.domains.core.representation_rejection import RepresentationRejection +from adcp.types.domains.core.representation_selection import RepresentationSelection, ResolvedBy +from adcp.types.domains.core.requirements.asset_requirements import AssetRequirements +from adcp.types.domains.core.requirements.audio_asset_requirements import ( AudioAssetRequirements, Channel, SampleRate, ) -from adcp.types.generated_poc.core.requirements.catalog_field_binding import ( +from adcp.types.domains.core.requirements.catalog_field_binding import ( AssetPoolBinding, CatalogFieldBinding, CatalogFieldBinding1, PerItemBindings, ScalarBinding, ) -from adcp.types.generated_poc.core.requirements.catalog_requirements import CatalogRequirements -from adcp.types.generated_poc.core.requirements.css_asset_requirements import CssAssetRequirements -from adcp.types.generated_poc.core.requirements.daast_asset_requirements import ( - DaastAssetRequirements, -) -from adcp.types.generated_poc.core.requirements.html_asset_requirements import ( +from adcp.types.domains.core.requirements.catalog_requirements import CatalogRequirements +from adcp.types.domains.core.requirements.css_asset_requirements import CssAssetRequirements +from adcp.types.domains.core.requirements.daast_asset_requirements import DaastAssetRequirements +from adcp.types.domains.core.requirements.html_asset_requirements import ( HtmlAssetRequirements, Sandbox, ) -from adcp.types.generated_poc.core.requirements.image_asset_requirements import ( +from adcp.types.domains.core.requirements.image_asset_requirements import ( Bleed, Bleed1, ImageAssetRequirements, PixelRatio, ) -from adcp.types.generated_poc.core.requirements.javascript_asset_requirements import ( +from adcp.types.domains.core.requirements.javascript_asset_requirements import ( JavascriptAssetRequirements, ModuleType, ) -from adcp.types.generated_poc.core.requirements.markdown_asset_requirements import ( +from adcp.types.domains.core.requirements.markdown_asset_requirements import ( MarkdownAssetRequirements, ) -from adcp.types.generated_poc.core.requirements.offering_asset_constraint import ( - OfferingAssetConstraint, -) -from adcp.types.generated_poc.core.requirements.text_asset_requirements import TextAssetRequirements -from adcp.types.generated_poc.core.requirements.url_asset_requirements import ( +from adcp.types.domains.core.requirements.offering_asset_constraint import OfferingAssetConstraint +from adcp.types.domains.core.requirements.text_asset_requirements import TextAssetRequirements +from adcp.types.domains.core.requirements.url_asset_requirements import ( Protocol, UrlAssetRequirements, ) -from adcp.types.generated_poc.core.requirements.vast_asset_requirements import VastAssetRequirements -from adcp.types.generated_poc.core.requirements.video_asset_requirements import ( +from adcp.types.domains.core.requirements.vast_asset_requirements import VastAssetRequirements +from adcp.types.domains.core.requirements.video_asset_requirements import ( AudioCodec, AudioSampleRate, Codec, @@ -2196,15 +2115,13 @@ FrameRate, VideoAssetRequirements, ) -from adcp.types.generated_poc.core.requirements.webhook_asset_requirements import ( - WebhookAssetRequirements, -) -from adcp.types.generated_poc.core.response import ProtocolResponse -from adcp.types.generated_poc.core.response_payload_jws_envelope import ( +from adcp.types.domains.core.requirements.webhook_asset_requirements import WebhookAssetRequirements +from adcp.types.domains.core.response import ProtocolResponse +from adcp.types.domains.core.response_payload_jws_envelope import ( ResponsePayload, ResponsePayloadJwsEnvelope, ) -from adcp.types.generated_poc.core.rights_attestation_evaluation import ( +from adcp.types.domains.core.rights_attestation_evaluation import ( Issuer, Issuer5, Issuer6, @@ -2213,7 +2130,7 @@ Subject10, Subject19, ) -from adcp.types.generated_poc.core.rights_constraint import ( +from adcp.types.domains.core.rights_constraint import ( ApprovalStatus, GrantStatus, Issuer8, @@ -2224,14 +2141,14 @@ Subject20, Subject29, ) -from adcp.types.generated_poc.core.seller_agent_ref import SellerAgentReference -from adcp.types.generated_poc.core.signal_coverage_forecast import ( +from adcp.types.domains.core.seller_agent_ref import SellerAgentReference +from adcp.types.domains.core.signal_coverage_forecast import ( BucketCompleteness, BucketSemantics, Point, SignalCoverageForecast, ) -from adcp.types.generated_poc.core.signal_definition import ( +from adcp.types.domains.core.signal_definition import ( AiActRiskClass, Art9Basis, AudienceScope, @@ -2250,12 +2167,12 @@ TrainingDataJurisdiction, ValueMapping, ) -from adcp.types.generated_poc.core.signal_definition_enrichment import SignalDefinitionEnrichment -from adcp.types.generated_poc.core.signal_filters import SignalFilters -from adcp.types.generated_poc.core.signal_id import SignalId, SignalId8, SignalId9 -from adcp.types.generated_poc.core.signal_listing import SignalListing -from adcp.types.generated_poc.core.signal_modeling_disclosure import SignalModelingDisclosure -from adcp.types.generated_poc.core.signal_pricing import ( +from adcp.types.domains.core.signal_definition_enrichment import SignalDefinitionEnrichment +from adcp.types.domains.core.signal_filters import SignalFilters +from adcp.types.domains.core.signal_id import SignalId, SignalId8, SignalId9 +from adcp.types.domains.core.signal_listing import SignalListing +from adcp.types.domains.core.signal_modeling_disclosure import SignalModelingDisclosure +from adcp.types.domains.core.signal_pricing import ( VendorPricing, VendorPricing1, VendorPricing2, @@ -2263,32 +2180,29 @@ VendorPricing4, VendorPricing5, ) -from adcp.types.generated_poc.core.signal_pricing_option import SignalPricingOption -from adcp.types.generated_poc.core.signal_ref import SignalRef, SignalRef1, SignalRef2, SignalRef3 -from adcp.types.generated_poc.core.signal_selection_group_rule import SignalSelectionGroupRule -from adcp.types.generated_poc.core.signal_targeting import ( +from adcp.types.domains.core.signal_pricing_option import SignalPricingOption +from adcp.types.domains.core.signal_ref import SignalRef, SignalRef1, SignalRef2, SignalRef3 +from adcp.types.domains.core.signal_selection_group_rule import SignalSelectionGroupRule +from adcp.types.domains.core.signal_targeting import ( SignalTargeting, SignalTargeting1, SignalTargeting2, SignalTargeting3, ) -from adcp.types.generated_poc.core.signal_targeting_expression import ( +from adcp.types.domains.core.signal_targeting_expression import ( SignalTargetingExpression, SignalTargetingExpression1, SignalTargetingExpression2, SignalTargetingExpression3, ) -from adcp.types.generated_poc.core.signal_targeting_rules import ( - ResolutionModel, - SignalTargetingRules, -) -from adcp.types.generated_poc.core.sla_window import SlaWindow -from adcp.types.generated_poc.core.special import Special -from adcp.types.generated_poc.core.spot_reporting_capability import SpotReportingCapability -from adcp.types.generated_poc.core.start_timing import StartTiming -from adcp.types.generated_poc.core.store_item import StoreItem -from adcp.types.generated_poc.core.talent import Talent -from adcp.types.generated_poc.core.targeting import ( +from adcp.types.domains.core.signal_targeting_rules import ResolutionModel, SignalTargetingRules +from adcp.types.domains.core.sla_window import SlaWindow +from adcp.types.domains.core.special import Special +from adcp.types.domains.core.spot_reporting_capability import SpotReportingCapability +from adcp.types.domains.core.start_timing import StartTiming +from adcp.types.domains.core.store_item import StoreItem +from adcp.types.domains.core.talent import Talent +from adcp.types.domains.core.targeting import ( AgeRestriction, GeoCountriesExcludeItem, GeoCountry, @@ -2298,15 +2212,15 @@ StoreCatchment, TargetingOverlay, ) -from adcp.types.generated_poc.core.targeting_input import TargetingOverlayInput -from adcp.types.generated_poc.core.targeting_modification import ( +from adcp.types.domains.core.targeting_input import TargetingOverlayInput +from adcp.types.domains.core.targeting_modification import ( Path, Selector, TargetingModification, TargetingModification1, TargetingModification2, ) -from adcp.types.generated_poc.core.targeting_overlay_requirements import ( +from adcp.types.domains.core.targeting_overlay_requirements import ( BrowserRequirement, BrowserRequirement1, DaypartRequirement, @@ -2320,7 +2234,7 @@ Required, TargetingOverlayRequirements, ) -from adcp.types.generated_poc.core.targeting_overlay_support import ( +from adcp.types.domains.core.targeting_overlay_support import ( BrowserSupport, BrowserSupport1, CountrySupport, @@ -2337,34 +2251,34 @@ Supported, TargetingOverlaySupport, ) -from adcp.types.generated_poc.core.targeting_unknown_age_eligibility_constraint import ( +from adcp.types.domains.core.targeting_unknown_age_eligibility_constraint import ( TargetingUnknownAgeEligibilityConstraint, ) -from adcp.types.generated_poc.core.targeting_verified_age_basis_constraint import ( +from adcp.types.domains.core.targeting_verified_age_basis_constraint import ( TargetingVerifiedAgeBasisConstraint, ) -from adcp.types.generated_poc.core.tasks_get_request import TasksGetRequest -from adcp.types.generated_poc.core.tasks_get_response import ( +from adcp.types.domains.core.tasks_get_request import TasksGetRequest +from adcp.types.domains.core.tasks_get_response import ( Details, HistoryItem, Progress, TasksGetResponse, ) -from adcp.types.generated_poc.core.tasks_list_request import Filters, Sort, TasksListRequest -from adcp.types.generated_poc.core.tasks_list_response import ( +from adcp.types.domains.core.tasks_list_request import Filters, Sort, TasksListRequest +from adcp.types.domains.core.tasks_list_response import ( DomainBreakdown, QuerySummary, SortApplied, TasksListResponse, ) -from adcp.types.generated_poc.core.tracker_execution_contract import TrackerExecutionContract -from adcp.types.generated_poc.core.tracker_execution_selector import ( +from adcp.types.domains.core.tracker_execution_contract import TrackerExecutionContract +from adcp.types.domains.core.tracker_execution_selector import ( TrackerExecutionSelector, TrackerExecutionSelector1, TrackerExecutionSelector2, TrackerExecutionSelector3, ) -from adcp.types.generated_poc.core.transformer import ( +from adcp.types.domains.core.transformer import ( BrandAgent, InputFormat, InputFormat1, @@ -2406,21 +2320,18 @@ VariantDimension, VoiceSynthesisRefItem, ) -from adcp.types.generated_poc.core.transformer_param import Option, TransformerParam, ValueSource -from adcp.types.generated_poc.core.truncation_sentinel import FieldTruncation, TruncationSentinel -from adcp.types.generated_poc.core.user_match import UserMatch -from adcp.types.generated_poc.core.vast_media_file_requirements import ( - MimeType, - VastMediafileRequirements, -) -from adcp.types.generated_poc.core.vast_tracker_constraints import ( +from adcp.types.domains.core.transformer_param import Option, TransformerParam, ValueSource +from adcp.types.domains.core.truncation_sentinel import FieldTruncation, TruncationSentinel +from adcp.types.domains.core.user_match import UserMatch +from adcp.types.domains.core.vast_media_file_requirements import MimeType, VastMediafileRequirements +from adcp.types.domains.core.vast_tracker_constraints import ( VastEvent, VastOffset, VastTarget, VastTrackerConstraints, VastVersions, ) -from adcp.types.generated_poc.core.vehicle_item import ( +from adcp.types.domains.core.vehicle_item import ( BodyStyle, Condition, FuelType, @@ -2428,13 +2339,13 @@ Transmission, VehicleItem, ) -from adcp.types.generated_poc.core.vendor_metric_id import VendorMetricId -from adcp.types.generated_poc.core.vendor_metric_optimization import VendorMetricOptimization -from adcp.types.generated_poc.core.vendor_metric_optimization_supported_metric import ( +from adcp.types.domains.core.vendor_metric_id import VendorMetricId +from adcp.types.domains.core.vendor_metric_optimization import VendorMetricOptimization +from adcp.types.domains.core.vendor_metric_optimization_supported_metric import ( VendorMetricOptimizationSupportedMetric, ) -from adcp.types.generated_poc.core.vendor_metric_value import VendorMetricValue -from adcp.types.generated_poc.core.vendor_pricing_option import ( +from adcp.types.domains.core.vendor_metric_value import VendorMetricValue +from adcp.types.domains.core.vendor_pricing_option import ( AppliesToOutputCapabilityId, VendorPricingOption, VendorPricingOption1, @@ -2449,18 +2360,18 @@ VendorPricingOption8, VendorPricingOption9, ) -from adcp.types.generated_poc.core.verification_token_claims import ( +from adcp.types.domains.core.verification_token_claims import ( AgenticadvertisingOrgVerificationTokenClaims, VerificationTokenGradingProfile, VerificationTokenMode, ) -from adcp.types.generated_poc.core.version_envelope import AdcpVersionEnvelope -from adcp.types.generated_poc.core.warning import Warning -from adcp.types.generated_poc.core.warning_resource import WarningAffectedResource -from adcp.types.generated_poc.core.webhook_activity_record import WebhookActivityRecord -from adcp.types.generated_poc.core.webhook_challenge import WebhookChallenge -from adcp.types.generated_poc.core.webhook_challenge_response import WebhookChallengeResponse -from adcp.types.generated_poc.core.wholesale_feed_event import ( +from adcp.types.domains.core.version_envelope import AdcpVersionEnvelope +from adcp.types.domains.core.warning import Warning +from adcp.types.domains.core.warning_resource import WarningAffectedResource +from adcp.types.domains.core.webhook_activity_record import WebhookActivityRecord +from adcp.types.domains.core.webhook_challenge import WebhookChallenge +from adcp.types.domains.core.webhook_challenge_response import WebhookChallengeResponse +from adcp.types.domains.core.wholesale_feed_event import ( AffectedEntityType, AppliesTo, AppliesTo1, @@ -2486,59 +2397,49 @@ WholesaleFeedEvent8, WholesaleFeedEvent9, ) -from adcp.types.generated_poc.core.wholesale_feed_webhook import CacheScope, WholesaleFeedWebhook -from adcp.types.generated_poc.core.x_entity_types import XEntityTypes -from adcp.types.generated_poc.creative.audit_observation import ( - ClaimedValue, - CreativeAuditObservation, -) -from adcp.types.generated_poc.creative.creative_assignment_changed_webhook import ( +from adcp.types.domains.core.wholesale_feed_webhook import CacheScope, WholesaleFeedWebhook +from adcp.types.domains.core.x_entity_types import XEntityTypes +from adcp.types.domains.creative.audit_observation import ClaimedValue, CreativeAuditObservation +from adcp.types.domains.creative.creative_assignment_changed_webhook import ( CreativeAssignmentChangedWebhook, ) -from adcp.types.generated_poc.creative.creative_feature_result import CreativeFeatureResult -from adcp.types.generated_poc.creative.creative_purged_webhook import ( - CreativePurgedWebhook, - Initiator, -) -from adcp.types.generated_poc.creative.creative_status_changed_webhook import ( +from adcp.types.domains.creative.creative_feature_result import CreativeFeatureResult +from adcp.types.domains.creative.creative_purged_webhook import CreativePurgedWebhook, Initiator +from adcp.types.domains.creative.creative_status_changed_webhook import ( CreativeStatusChangedWebhook, From, ) -from adcp.types.generated_poc.creative.get_creative_delivery_request import ( - GetCreativeDeliveryRequest, -) -from adcp.types.generated_poc.creative.get_creative_delivery_response import ( +from adcp.types.domains.creative.get_creative_delivery_request import GetCreativeDeliveryRequest +from adcp.types.domains.creative.get_creative_delivery_response import ( Creative, GetCreativeDeliveryResponse, ReportingPeriod, ) -from adcp.types.generated_poc.creative.get_creative_features_async_response_submitted import ( +from adcp.types.domains.creative.get_creative_features_async_response_submitted import ( GetCreativeFeaturesSubmitted, ) -from adcp.types.generated_poc.creative.get_creative_features_request import ( - GetCreativeFeaturesRequest, -) -from adcp.types.generated_poc.creative.get_creative_features_response import ( +from adcp.types.domains.creative.get_creative_features_request import GetCreativeFeaturesRequest +from adcp.types.domains.creative.get_creative_features_response import ( GetCreativeFeaturesResponse, GetCreativeFeaturesResponse1, GetCreativeFeaturesResponse2, GetCreativeFeaturesResponse3, ) -from adcp.types.generated_poc.creative.get_creative_features_terminal_success import ( +from adcp.types.domains.creative.get_creative_features_terminal_success import ( GetCreativeFeaturesSuccess, ) -from adcp.types.generated_poc.creative.list_creative_formats_request import ( +from adcp.types.domains.creative.list_creative_formats_request import ( ListCreativeFormatsRequestCreativeAgent, ) -from adcp.types.generated_poc.creative.list_creative_formats_response import ( +from adcp.types.domains.creative.list_creative_formats_response import ( CreativeAgent, ListCreativeFormatsResponseCreativeAgent, ) -from adcp.types.generated_poc.creative.list_creatives_request import ( +from adcp.types.domains.creative.list_creatives_request import ( AssignmentProjection, ListCreativesRequest, ) -from adcp.types.generated_poc.creative.list_creatives_response import ( +from adcp.types.domains.creative.list_creatives_response import ( AssignedPackage, Assignments, Creatives, @@ -2550,20 +2451,20 @@ Snapshot, StatusSummary, ) -from adcp.types.generated_poc.creative.list_transformers_request import ( +from adcp.types.domains.creative.list_transformers_request import ( ExpandPaginationItem, ListTransformersRequestCreativeAgent, ) -from adcp.types.generated_poc.creative.list_transformers_response import ( +from adcp.types.domains.creative.list_transformers_response import ( ListTransformersResponseCreativeAgent, ) -from adcp.types.generated_poc.creative.preview_creative_request import ( +from adcp.types.domains.creative.preview_creative_request import ( Input10, PreviewCreativeRequest, Request, RequestType, ) -from adcp.types.generated_poc.creative.preview_creative_response import ( +from adcp.types.domains.creative.preview_creative_response import ( Input2, Preview, Preview2, @@ -2575,23 +2476,21 @@ PreviewCreativeResponse4, Response, ) -from adcp.types.generated_poc.creative.preview_render import ( +from adcp.types.domains.creative.preview_render import ( Embedding, PreviewRender, PreviewRender1, PreviewRender2, PreviewRender3, ) -from adcp.types.generated_poc.creative.sync_creatives_async_response_input_required import ( +from adcp.types.domains.creative.sync_creatives_async_response_input_required import ( SyncCreativesInputRequired, ) -from adcp.types.generated_poc.creative.sync_creatives_async_response_submitted import ( +from adcp.types.domains.creative.sync_creatives_async_response_submitted import ( SyncCreativesSubmitted, ) -from adcp.types.generated_poc.creative.sync_creatives_async_response_working import ( - SyncCreativesWorking, -) -from adcp.types.generated_poc.creative.sync_creatives_request import ( +from adcp.types.domains.creative.sync_creatives_async_response_working import SyncCreativesWorking +from adcp.types.domains.creative.sync_creatives_request import ( Assignment, AssignmentOperations, AssignmentOperations1, @@ -2600,13 +2499,13 @@ PlacementId, SyncCreativesRequest, ) -from adcp.types.generated_poc.creative.sync_creatives_response import ( +from adcp.types.domains.creative.sync_creatives_response import ( SyncCreativesResponse, SyncCreativesResponse1, SyncCreativesResponse2, SyncCreativesResponse3, ) -from adcp.types.generated_poc.creative.validate_input_request import ( +from adcp.types.domains.creative.validate_input_request import ( Targets, Targets1, Targets2, @@ -2614,73 +2513,65 @@ Targets4, ValidateInputRequest, ) -from adcp.types.generated_poc.creative.validate_input_response import ValidateInputResponse -from adcp.types.generated_poc.creative.validate_input_result import ( +from adcp.types.domains.creative.validate_input_response import ValidateInputResponse +from adcp.types.domains.creative.validate_input_result import ( ResultKind, ValidateInputResult, Violation, ) -from adcp.types.generated_poc.creative.video_brief import Segment, VideoBrief -from adcp.types.generated_poc.error_details.accessibility_violation import ( +from adcp.types.domains.creative.video_brief import Segment, VideoBrief +from adcp.types.domains.error_details.accessibility_violation import ( AccessibilityViolationDetails, FailureKind, FailureKind1, ) -from adcp.types.generated_poc.error_details.account_moved import AccountMovedDetails -from adcp.types.generated_poc.error_details.account_setup_required import ( - AccountSetupRequiredDetails, -) -from adcp.types.generated_poc.error_details.action_not_allowed import ActionNotAllowedDetails -from adcp.types.generated_poc.error_details.agent_permission_denied import ( - AgentPermissionDeniedDetails, -) -from adcp.types.generated_poc.error_details.audience_too_small import AudienceTooSmallDetails -from adcp.types.generated_poc.error_details.authorization_required import ( - AuthorizationRequiredDetails, -) -from adcp.types.generated_poc.error_details.billing_not_permitted_for_agent import ( +from adcp.types.domains.error_details.account_moved import AccountMovedDetails +from adcp.types.domains.error_details.account_setup_required import AccountSetupRequiredDetails +from adcp.types.domains.error_details.action_not_allowed import ActionNotAllowedDetails +from adcp.types.domains.error_details.agent_permission_denied import AgentPermissionDeniedDetails +from adcp.types.domains.error_details.audience_too_small import AudienceTooSmallDetails +from adcp.types.domains.error_details.authorization_required import AuthorizationRequiredDetails +from adcp.types.domains.error_details.billing_not_permitted_for_agent import ( BillingNotPermittedForAgentDetails, ) -from adcp.types.generated_poc.error_details.billing_not_supported import BillingNotSupportedDetails -from adcp.types.generated_poc.error_details.budget_too_low import BudgetTooLowDetails -from adcp.types.generated_poc.error_details.conflict import ConflictDetails -from adcp.types.generated_poc.error_details.creative_rejected import CreativeRejectedDetails -from adcp.types.generated_poc.error_details.creative_representation_unresolved import ( +from adcp.types.domains.error_details.billing_not_supported import BillingNotSupportedDetails +from adcp.types.domains.error_details.budget_too_low import BudgetTooLowDetails +from adcp.types.domains.error_details.conflict import ConflictDetails +from adcp.types.domains.error_details.creative_rejected import CreativeRejectedDetails +from adcp.types.domains.error_details.creative_representation_unresolved import ( CreativeRepresentationUnresolvedDetails, ) -from adcp.types.generated_poc.error_details.creative_revision_content_mismatch import ( +from adcp.types.domains.error_details.creative_revision_content_mismatch import ( CreativeRevisionContentMismatchDetails, ) -from adcp.types.generated_poc.error_details.execution_requirement_unmet import ( +from adcp.types.domains.error_details.execution_requirement_unmet import ( ExecutionRequirementUnmetDetails, UnmetRequirement, ) -from adcp.types.generated_poc.error_details.governance_agent_not_accepted import ( +from adcp.types.domains.error_details.governance_agent_not_accepted import ( GovernanceAgentNotAcceptedDetails, GovernanceAgentNotAcceptedDetails1, GovernanceAgentNotAcceptedDetails2, ) -from adcp.types.generated_poc.error_details.macro_resolution_failed import ( - MacroResolutionFailedDetails, -) -from adcp.types.generated_poc.error_details.policy_violation import PolicyViolationDetails -from adcp.types.generated_poc.error_details.rate_limited import RateLimitedDetails -from adcp.types.generated_poc.error_details.requote_required import ( +from adcp.types.domains.error_details.macro_resolution_failed import MacroResolutionFailedDetails +from adcp.types.domains.error_details.policy_violation import PolicyViolationDetails +from adcp.types.domains.error_details.rate_limited import RateLimitedDetails +from adcp.types.domains.error_details.requote_required import ( EnvelopeField, EnvelopeField1, EnvelopeField1Item, RequoteRequiredDetails, ) -from adcp.types.generated_poc.error_details.stale_response import ( +from adcp.types.domains.error_details.stale_response import ( OriginalError, StaleResponseDetails, Upstream, ) -from adcp.types.generated_poc.error_details.unsupported_refinement_dimension import ( +from adcp.types.domains.error_details.unsupported_refinement_dimension import ( SupportedDimension, UnsupportedRefinementDimensionDetails, ) -from adcp.types.generated_poc.error_details.vast_version_mismatch import ( +from adcp.types.domains.error_details.vast_version_mismatch import ( DocumentRole, MismatchReason, VastVersionMismatchDetails, @@ -2688,17 +2579,17 @@ VastVersionMismatchDetails2, VastVersionMismatchDetails3, ) -from adcp.types.generated_poc.error_details.vendor_error_codes import ( +from adcp.types.domains.error_details.vendor_error_codes import ( Codes, VendorErrorCodeRegistry, Vendors, ) -from adcp.types.generated_poc.error_details.version_unsupported import ( +from adcp.types.domains.error_details.version_unsupported import ( SupportedMajor, VersionUnsupportedDetails, ) -from adcp.types.generated_poc.extensions.extension_meta import AdcpExtensionFileSchema -from adcp.types.generated_poc.formats.canonical._base import ( +from adcp.types.domains.extensions.extension_meta import AdcpExtensionFileSchema +from adcp.types.domains.formats.canonical._base import ( AssetType, CanonicalFormatBase, CompositionModel, @@ -2706,55 +2597,55 @@ RequiredPixelRatio, Slot, ) -from adcp.types.generated_poc.formats.canonical.agent_placement import ( +from adcp.types.domains.formats.canonical.agent_placement import ( CanonicalFormatAgentPlacementAiSurfaceSponsoredPlacement, OutputModality, ) -from adcp.types.generated_poc.formats.canonical.audio_daast import ( +from adcp.types.domains.formats.canonical.audio_daast import ( CanonicalFormatDaastAudio, DurationMsRangeItem, ) -from adcp.types.generated_poc.formats.canonical.audio_hosted import ( +from adcp.types.domains.formats.canonical.audio_hosted import ( AudioChannel, BuyerAssetAcceptance, CanonicalFormatHostedAudio, DurationMsRange, ) -from adcp.types.generated_poc.formats.canonical.audio_vast import CanonicalFormatVastAudio -from adcp.types.generated_poc.formats.canonical.coordinated_placements import ( +from adcp.types.domains.formats.canonical.audio_vast import CanonicalFormatVastAudio +from adcp.types.domains.formats.canonical.coordinated_placements import ( CanonicalFormatCoordinatedPlacements, ) -from adcp.types.generated_poc.formats.canonical.display_tag import ( +from adcp.types.domains.formats.canonical.display_tag import ( CanonicalFormatDisplayTag, Size, SupportedDeliveryType, SupportedTagType, ) -from adcp.types.generated_poc.formats.canonical.html5 import ( +from adcp.types.domains.formats.canonical.html5 import ( CanonicalFormatHtml5Banner, ClicktagMacro, MraidVersion, ) -from adcp.types.generated_poc.formats.canonical.image import ( +from adcp.types.domains.formats.canonical.image import ( CanonicalFormatImage, ImageFormat, MotionLevel, ) -from adcp.types.generated_poc.formats.canonical.image_carousel import ( +from adcp.types.domains.formats.canonical.image_carousel import ( AllowedCardMediaAssetType, CanonicalFormatImageCarousel, ) -from adcp.types.generated_poc.formats.canonical.native_in_feed import ( +from adcp.types.domains.formats.canonical.native_in_feed import ( CanonicalFormatNativeInFeed, FocusBehavior, IconSize, MainImageSize, MenuPlacement, ) -from adcp.types.generated_poc.formats.canonical.responsive_creative import ( +from adcp.types.domains.formats.canonical.responsive_creative import ( CanonicalFormatResponsiveCreative, ) -from adcp.types.generated_poc.formats.canonical.seller_rendered_stateful_display import ( +from adcp.types.domains.formats.canonical.seller_rendered_stateful_display import ( Anchoring, Breakpoint, CanonicalFormatSellerRenderedStatefulDisplay, @@ -2781,37 +2672,34 @@ WidthMode, WidthRangeItem, ) -from adcp.types.generated_poc.formats.canonical.sponsored_placement import ( +from adcp.types.domains.formats.canonical.sponsored_placement import ( CanonicalFormatSponsoredPlacementRetailMediaCatalogDriven, FanoutMode, ItemProductionModel, SupportedIdType, ) -from adcp.types.generated_poc.formats.canonical.video_hosted import ( +from adcp.types.domains.formats.canonical.video_hosted import ( CanonicalFormatHostedVideo, Captions, CompanionBannerHeight, CompanionBannerWidth, VideoCodec, ) -from adcp.types.generated_poc.formats.canonical.video_vast import ( +from adcp.types.domains.formats.canonical.video_vast import ( CanonicalFormatVastVideo, CreativeType, VpaidVersion, ) -from adcp.types.generated_poc.governance.accepted_governance_agents import ( +from adcp.types.domains.governance.accepted_governance_agents import ( AcceptedGovernanceAgents, AnyOf, AnyOf1, AnyOf2, VerificationMode, ) -from adcp.types.generated_poc.governance.attribute_definition import ( - AttributeDefinition, - RegulatoryBasi, -) -from adcp.types.generated_poc.governance.audience_constraints import AudienceConstraints -from adcp.types.generated_poc.governance.check_governance_request import ( +from adcp.types.domains.governance.attribute_definition import AttributeDefinition, RegulatoryBasi +from adcp.types.domains.governance.audience_constraints import AudienceConstraints +from adcp.types.domains.governance.check_governance_request import ( AudienceDistribution, AudienceDistribution1, CheckGovernanceRequest, @@ -2828,7 +2716,7 @@ Subject8, Subject9, ) -from adcp.types.generated_poc.governance.check_governance_response import ( +from adcp.types.domains.governance.check_governance_response import ( CanonicalPayload, CheckGovernanceResponse, CheckType, @@ -2836,8 +2724,8 @@ Finding, RuntimeAttestationEvaluation, ) -from adcp.types.generated_poc.governance.get_plan_audit_logs_request import GetPlanAuditLogsRequest -from adcp.types.generated_poc.governance.get_plan_audit_logs_response import ( +from adcp.types.domains.governance.get_plan_audit_logs_request import GetPlanAuditLogsRequest +from adcp.types.domains.governance.get_plan_audit_logs_response import ( AccountingMode, AdjustmentState, AdjustmentType, @@ -2858,30 +2746,30 @@ Statuses, Thresholds, ) -from adcp.types.generated_poc.governance.policy_category_definition import ( +from adcp.types.domains.governance.policy_category_definition import ( Facet, PolicyCategoryDefinition, RegulatoryFramework, ) -from adcp.types.generated_poc.governance.policy_entry import Exemplar, PolicyEntry -from adcp.types.generated_poc.governance.policy_ref import PolicyReference -from adcp.types.generated_poc.governance.report_plan_adjustment_request import ( +from adcp.types.domains.governance.policy_entry import Exemplar, PolicyEntry +from adcp.types.domains.governance.policy_ref import PolicyReference +from adcp.types.domains.governance.report_plan_adjustment_request import ( Decision, ReportPlanAdjustmentRequest, ) -from adcp.types.generated_poc.governance.report_plan_adjustment_response import ( +from adcp.types.domains.governance.report_plan_adjustment_response import ( PlanSummary, ReportPlanAdjustmentResponse, ) -from adcp.types.generated_poc.governance.report_plan_outcome_request import ( +from adcp.types.domains.governance.report_plan_outcome_request import ( ReportPlanOutcomeRequest, SellerResponse, ) -from adcp.types.generated_poc.governance.report_plan_outcome_response import ( +from adcp.types.domains.governance.report_plan_outcome_response import ( OutcomeState, ReportPlanOutcomeResponse, ) -from adcp.types.generated_poc.governance.reported_outcome_error import ( +from adcp.types.domains.governance.reported_outcome_error import ( BoundedObject, BoundedScalar, BoundedScalar1, @@ -2894,7 +2782,7 @@ ClassificationSource, ReportedOutcomeError, ) -from adcp.types.generated_poc.governance.sync_plans_request import ( +from adcp.types.domains.governance.sync_plans_request import ( Allocations, Budget2, BudgetLimit, @@ -2906,13 +2794,13 @@ SyncPlansRequest, TotalBudgetCap, ) -from adcp.types.generated_poc.governance.sync_plans_response import ( +from adcp.types.domains.governance.sync_plans_response import ( ResolvedPolicy, Status51, SyncPlansResponse, ) -from adcp.types.generated_poc.manifest import Model -from adcp.types.generated_poc.manifest_schema import ( +from adcp.types.domains.manifest import Model +from adcp.types.domains.manifest_schema import ( AdcpManifest, DefaultUnknownRecovery, ErrorCodePolicy, @@ -2927,21 +2815,21 @@ ToolName, Tools, ) -from adcp.types.generated_poc.media_buy.accept_proposal_async_response_input_required import ( +from adcp.types.domains.media_buy.accept_proposal_async_response_input_required import ( AcceptProposalInputRequired, ) -from adcp.types.generated_poc.media_buy.accept_proposal_async_response_submitted import ( +from adcp.types.domains.media_buy.accept_proposal_async_response_submitted import ( AcceptProposalSubmitted, ) -from adcp.types.generated_poc.media_buy.accept_proposal_async_response_working import ( +from adcp.types.domains.media_buy.accept_proposal_async_response_working import ( AcceptProposalWorking, ) -from adcp.types.generated_poc.media_buy.accept_proposal_request import ( +from adcp.types.domains.media_buy.accept_proposal_request import ( AcceptProposalRequest, IoAcceptance, Opportunity, ) -from adcp.types.generated_poc.media_buy.accept_proposal_response import ( +from adcp.types.domains.media_buy.accept_proposal_response import ( AcceptProposalResponse, AcceptProposalResponse1, AcceptProposalResponse2, @@ -2953,15 +2841,15 @@ AcceptedProposal, PurchaseBinding, ) -from adcp.types.generated_poc.media_buy.acceptance_context import ( +from adcp.types.domains.media_buy.acceptance_context import ( AcceptanceContext, AdvertiserJurisdiction, AdvertiserRole, DeliveryJurisdiction, SubjectFacet, ) -from adcp.types.generated_poc.media_buy.acceptance_policy_catalog import AcceptancePolicyCatalog -from adcp.types.generated_poc.media_buy.acceptance_policy_profile import ( +from adcp.types.domains.media_buy.acceptance_policy_catalog import AcceptancePolicyCatalog +from adcp.types.domains.media_buy.acceptance_policy_profile import ( AcceptancePolicyProfile, AppliesToEnum, Coverage, @@ -2970,10 +2858,10 @@ RegionAliase, SubjectCategory, ) -from adcp.types.generated_poc.media_buy.acceptance_policy_profile_ref import ( +from adcp.types.domains.media_buy.acceptance_policy_profile_ref import ( RegistryAcceptancePolicyProfileReference, ) -from adcp.types.generated_poc.media_buy.acceptance_policy_requirement import ( +from adcp.types.domains.media_buy.acceptance_policy_requirement import ( AcceptancePolicyRequirement, AcceptancePolicyRequirement1, AcceptancePolicyRequirement10, @@ -2995,21 +2883,19 @@ Criterion, FormatId, ) -from adcp.types.generated_poc.media_buy.acceptance_policy_rule import ( +from adcp.types.domains.media_buy.acceptance_policy_rule import ( AcceptancePolicyRule, Disposition, PolicyId, ) -from adcp.types.generated_poc.media_buy.build_creative_async_response_input_required import ( +from adcp.types.domains.media_buy.build_creative_async_response_input_required import ( BuildCreativeInputRequired, ) -from adcp.types.generated_poc.media_buy.build_creative_async_response_submitted import ( +from adcp.types.domains.media_buy.build_creative_async_response_submitted import ( BuildCreativeSubmitted, ) -from adcp.types.generated_poc.media_buy.build_creative_async_response_working import ( - BuildCreativeWorking, -) -from adcp.types.generated_poc.media_buy.build_creative_request import ( +from adcp.types.domains.media_buy.build_creative_async_response_working import BuildCreativeWorking +from adcp.types.domains.media_buy.build_creative_request import ( BuildCreativeRequest, KeepMode, MaxSpend, @@ -3025,7 +2911,7 @@ TargetCapabilityId, VariantAxis, ) -from adcp.types.generated_poc.media_buy.build_creative_response import ( +from adcp.types.domains.media_buy.build_creative_response import ( BuildCreativeResponse, BuildCreativeResponse1, BuildCreativeResponse2, @@ -3039,17 +2925,13 @@ PerLeaf, Preview4, ) -from adcp.types.generated_poc.media_buy.buy_products_async_response_input_required import ( +from adcp.types.domains.media_buy.buy_products_async_response_input_required import ( BuyProductsInputRequired, ) -from adcp.types.generated_poc.media_buy.buy_products_async_response_submitted import ( - BuyProductsSubmitted, -) -from adcp.types.generated_poc.media_buy.buy_products_async_response_working import ( - BuyProductsWorking, -) -from adcp.types.generated_poc.media_buy.buy_products_request import BuyProductsRequest -from adcp.types.generated_poc.media_buy.buy_products_response import ( +from adcp.types.domains.media_buy.buy_products_async_response_submitted import BuyProductsSubmitted +from adcp.types.domains.media_buy.buy_products_async_response_working import BuyProductsWorking +from adcp.types.domains.media_buy.buy_products_request import BuyProductsRequest +from adcp.types.domains.media_buy.buy_products_response import ( BuyProductsResponse, BuyProductsResponse1, BuyProductsResponse2, @@ -3059,8 +2941,8 @@ BuyProductsResponse6, BuyProductsResponse7, ) -from adcp.types.generated_poc.media_buy.change_term import AllowedStatus, MediaBuyChangeTerm -from adcp.types.generated_poc.media_buy.change_term_constraints import ( +from adcp.types.domains.media_buy.change_term import AllowedStatus, MediaBuyChangeTerm +from adcp.types.domains.media_buy.change_term_constraints import ( MediaBuyChangeTermConstraints, MediaBuyChangeTermConstraints1, MediaBuyChangeTermConstraints2, @@ -3068,61 +2950,61 @@ MediaBuyChangeTermConstraints4, Money, ) -from adcp.types.generated_poc.media_buy.commercial_terms import ( +from adcp.types.domains.media_buy.commercial_terms import ( CancellationTerms, CommercialTerms, Fee, ReportingCommitment, ) -from adcp.types.generated_poc.media_buy.control_media_buy_async_response_input_required import ( +from adcp.types.domains.media_buy.control_media_buy_async_response_input_required import ( ControlMediaBuyInputRequired, ) -from adcp.types.generated_poc.media_buy.control_media_buy_async_response_submitted import ( +from adcp.types.domains.media_buy.control_media_buy_async_response_submitted import ( ControlMediaBuySubmitted, ) -from adcp.types.generated_poc.media_buy.control_media_buy_async_response_working import ( +from adcp.types.domains.media_buy.control_media_buy_async_response_working import ( ControlMediaBuyWorking, ) -from adcp.types.generated_poc.media_buy.control_media_buy_request import ControlMediaBuyRequest -from adcp.types.generated_poc.media_buy.control_media_buy_response import ( +from adcp.types.domains.media_buy.control_media_buy_request import ControlMediaBuyRequest +from adcp.types.domains.media_buy.control_media_buy_response import ( AffectedPackageId, ControlMediaBuyResponse, ControlMediaBuyResponse1, ControlMediaBuyResponse2, ControlMediaBuyResponse3, ) -from adcp.types.generated_poc.media_buy.create_media_buy_async_response_input_required import ( +from adcp.types.domains.media_buy.create_media_buy_async_response_input_required import ( CreateMediaBuyInputRequired, ) -from adcp.types.generated_poc.media_buy.create_media_buy_async_response_submitted import ( +from adcp.types.domains.media_buy.create_media_buy_async_response_submitted import ( CreateMediaBuySubmitted, ) -from adcp.types.generated_poc.media_buy.create_media_buy_async_response_working import ( +from adcp.types.domains.media_buy.create_media_buy_async_response_working import ( CreateMediaBuyWorking, ) -from adcp.types.generated_poc.media_buy.create_media_buy_request import ( +from adcp.types.domains.media_buy.create_media_buy_request import ( ArtifactWebhook, BatchFrequency, CreateMediaBuyRequest, DeliveryMode, ) -from adcp.types.generated_poc.media_buy.create_media_buy_response import ( +from adcp.types.domains.media_buy.create_media_buy_response import ( CreateMediaBuyResponse, CreateMediaBuyResponse1, CreateMediaBuyResponse2, CreateMediaBuyResponse3, ) -from adcp.types.generated_poc.media_buy.decline_proposals_async_response_input_required import ( +from adcp.types.domains.media_buy.decline_proposals_async_response_input_required import ( DeclineProposalsInputRequired, ) -from adcp.types.generated_poc.media_buy.decline_proposals_async_response_submitted import ( +from adcp.types.domains.media_buy.decline_proposals_async_response_submitted import ( DeclineProposalsSubmitted, ) -from adcp.types.generated_poc.media_buy.decline_proposals_async_response_working import ( +from adcp.types.domains.media_buy.decline_proposals_async_response_working import ( DeclineProposalsWorking, ) -from adcp.types.generated_poc.media_buy.decline_proposals_request import DeclineProposalsRequest -from adcp.types.generated_poc.media_buy.decline_proposals_response import ( +from adcp.types.domains.media_buy.decline_proposals_request import DeclineProposalsRequest +from adcp.types.domains.media_buy.decline_proposals_response import ( DeclineProposalsResponse, DeclineProposalsResponse1, DeclineProposalsResponse2, @@ -3133,7 +3015,7 @@ Results4, Results5, ) -from adcp.types.generated_poc.media_buy.get_media_buy_delivery_request import ( +from adcp.types.domains.media_buy.get_media_buy_delivery_request import ( CatalogItem, Demographic, GetMediaBuyDeliveryRequest, @@ -3141,7 +3023,7 @@ Spot, StatusFilter, ) -from adcp.types.generated_poc.media_buy.get_media_buy_delivery_response import ( +from adcp.types.domains.media_buy.get_media_buy_delivery_response import ( AggregatedTotals, ByAudienceItem, ByDemographicItem, @@ -3159,8 +3041,8 @@ Totals, Window, ) -from adcp.types.generated_poc.media_buy.get_media_buys_request import GetMediaBuysRequest -from adcp.types.generated_poc.media_buy.get_media_buys_response import ( +from adcp.types.domains.media_buy.get_media_buys_request import GetMediaBuysRequest +from adcp.types.domains.media_buy.get_media_buys_response import ( Cancellation1, CreativeApproval, GetMediaBuysResponse, @@ -3169,17 +3051,13 @@ IndicatorTypesEvaluatedEnum1, IndicatorTypesEvaluatedEnum2, ) -from adcp.types.generated_poc.media_buy.get_products_async_response_input_required import ( +from adcp.types.domains.media_buy.get_products_async_response_input_required import ( GetProductsInputRequired, ) -from adcp.types.generated_poc.media_buy.get_products_async_response_submitted import ( - GetProductsSubmitted, -) -from adcp.types.generated_poc.media_buy.get_products_async_response_working import ( - GetProductsWorking, -) -from adcp.types.generated_poc.media_buy.get_products_rejected import GetProductsRejected -from adcp.types.generated_poc.media_buy.get_products_request import ( +from adcp.types.domains.media_buy.get_products_async_response_submitted import GetProductsSubmitted +from adcp.types.domains.media_buy.get_products_async_response_working import GetProductsWorking +from adcp.types.domains.media_buy.get_products_rejected import GetProductsRejected +from adcp.types.domains.media_buy.get_products_request import ( Action9, BuyingMode, Fields, @@ -3189,7 +3067,7 @@ Refine2, Refine3, ) -from adcp.types.generated_poc.media_buy.get_products_response import ( +from adcp.types.domains.media_buy.get_products_response import ( ExcludedBy, Extensions, FilterDiagnostics, @@ -3201,76 +3079,69 @@ RefinementApplied3, Semantics, ) -from adcp.types.generated_poc.media_buy.get_products_targeting_resolution import ( +from adcp.types.domains.media_buy.get_products_targeting_resolution import ( ProductDiscoveryTargetingResolution, ) -from adcp.types.generated_poc.media_buy.get_reporting_status_request import ( +from adcp.types.domains.media_buy.get_reporting_status_request import ( DeliveryConfigId, GetReportingStatusRequest, ReportingStatusView, ) -from adcp.types.generated_poc.media_buy.get_reporting_status_response import ( +from adcp.types.domains.media_buy.get_reporting_status_response import ( DeliveryConfigGeneration, GetReportingStatusResponse, ObligationCounts, ) -from adcp.types.generated_poc.media_buy.legacy_purchase_continuation_input import ( +from adcp.types.domains.media_buy.legacy_purchase_continuation_input import ( AcceptedLoss, CompatibilityPurchaseCoordinatorInput, SelectedProductId, ) -from adcp.types.generated_poc.media_buy.list_creative_formats_request import ( - ListCreativeFormatsRequest, -) -from adcp.types.generated_poc.media_buy.list_creative_formats_response import ( - ListCreativeFormatsResponse, -) -from adcp.types.generated_poc.media_buy.list_products_request import ListProductsRequest -from adcp.types.generated_poc.media_buy.list_products_response import ( +from adcp.types.domains.media_buy.list_creative_formats_request import ListCreativeFormatsRequest +from adcp.types.domains.media_buy.list_creative_formats_response import ListCreativeFormatsResponse +from adcp.types.domains.media_buy.list_products_request import ListProductsRequest +from adcp.types.domains.media_buy.list_products_response import ( IncompleteItem3, ListProductsResponse, ListProductsResponse1, ListProductsResponse2, ) -from adcp.types.generated_poc.media_buy.log_event_request import LogEventRequest -from adcp.types.generated_poc.media_buy.log_event_response import ( +from adcp.types.domains.media_buy.log_event_request import LogEventRequest +from adcp.types.domains.media_buy.log_event_response import ( LogEventResponse, LogEventResponse1, LogEventResponse2, PartialFailure, ) -from adcp.types.generated_poc.media_buy.media_buy_commitment_response import ( +from adcp.types.domains.media_buy.media_buy_commitment_response import ( MediaBuyCommitmentResponse, MediaBuyCommitmentResponse1, MediaBuyCommitmentResponse2, MediaBuyCommitmentResponse3, ) -from adcp.types.generated_poc.media_buy.media_buy_delivery_webhook_result import ( +from adcp.types.domains.media_buy.media_buy_delivery_webhook_result import ( MediaBuyDeliveryWebhookResult, ) -from adcp.types.generated_poc.media_buy.outcome_target import Goal, Goal1, OutcomeTarget -from adcp.types.generated_poc.media_buy.package_control import CatalogId, PackageControl -from adcp.types.generated_poc.media_buy.package_request import ( +from adcp.types.domains.media_buy.outcome_target import Goal, Goal1, OutcomeTarget +from adcp.types.domains.media_buy.package_control import CatalogId, PackageControl +from adcp.types.domains.media_buy.package_request import ( CommittedMetrics, CommittedMetrics1, CommittedMetrics2, PackageRequest, ) -from adcp.types.generated_poc.media_buy.package_update import ( +from adcp.types.domains.media_buy.package_update import ( KeywordTargetsAddItem, KeywordTargetsRemoveItem, NegativeKeywordsAddItem, NegativeKeywordsRemoveItem, PackageUpdate, ) -from adcp.types.generated_poc.media_buy.product_discovery_criteria import ProductDiscoveryCriteria -from adcp.types.generated_poc.media_buy.product_fields import ( - ProductResponseField, - ProductResponseFields, -) -from adcp.types.generated_poc.media_buy.product_purchase import ProductPurchase -from adcp.types.generated_poc.media_buy.product_purchase_input import ProductPurchaseInput -from adcp.types.generated_poc.media_buy.product_refinement import ( +from adcp.types.domains.media_buy.product_discovery_criteria import ProductDiscoveryCriteria +from adcp.types.domains.media_buy.product_fields import ProductResponseField, ProductResponseFields +from adcp.types.domains.media_buy.product_purchase import ProductPurchase +from adcp.types.domains.media_buy.product_purchase_input import ProductPurchaseInput +from adcp.types.domains.media_buy.product_refinement import ( Action11, ProductRefinementRequests, ProductRefinementRequests1, @@ -3278,9 +3149,9 @@ ProductRefinementRequests3, ProductRefinementRequests4, ) -from adcp.types.generated_poc.media_buy.proposal_budget_constraint import ProposalBudgetConstraint -from adcp.types.generated_poc.media_buy.proposal_decline import ProposalDecline -from adcp.types.generated_poc.media_buy.proposal_refinement import ( +from adcp.types.domains.media_buy.proposal_budget_constraint import ProposalBudgetConstraint +from adcp.types.domains.media_buy.proposal_decline import ProposalDecline +from adcp.types.domains.media_buy.proposal_refinement import ( Alternatives, Constraints, Constraints1, @@ -3302,28 +3173,28 @@ ProposalRefinement8, ProposalRefinement9, ) -from adcp.types.generated_poc.media_buy.provide_performance_feedback_request import ( +from adcp.types.domains.media_buy.provide_performance_feedback_request import ( ProvidePerformanceFeedbackRequest, ) -from adcp.types.generated_poc.media_buy.provide_performance_feedback_response import ( +from adcp.types.domains.media_buy.provide_performance_feedback_response import ( ProvidePerformanceFeedbackResponse, ProvidePerformanceFeedbackResponse1, ProvidePerformanceFeedbackResponse2, ) -from adcp.types.generated_poc.media_buy.refine_proposals_async_response_input_required import ( +from adcp.types.domains.media_buy.refine_proposals_async_response_input_required import ( RefineProposalsInputRequired, ) -from adcp.types.generated_poc.media_buy.refine_proposals_async_response_submitted import ( +from adcp.types.domains.media_buy.refine_proposals_async_response_submitted import ( RefineProposalsSubmitted, ) -from adcp.types.generated_poc.media_buy.refine_proposals_async_response_working import ( +from adcp.types.domains.media_buy.refine_proposals_async_response_working import ( RefineProposalsWorking, ) -from adcp.types.generated_poc.media_buy.refine_proposals_request import ( +from adcp.types.domains.media_buy.refine_proposals_request import ( RefineProposalsRequest, Refinements, ) -from adcp.types.generated_poc.media_buy.refine_proposals_response import ( +from adcp.types.domains.media_buy.refine_proposals_response import ( Proposal2, Proposal3, Proposal4, @@ -3344,17 +3215,17 @@ Results9, UnsatisfiedConstraint, ) -from adcp.types.generated_poc.media_buy.request_proposals_async_response_input_required import ( +from adcp.types.domains.media_buy.request_proposals_async_response_input_required import ( RequestProposalsInputRequired, ) -from adcp.types.generated_poc.media_buy.request_proposals_async_response_submitted import ( +from adcp.types.domains.media_buy.request_proposals_async_response_submitted import ( RequestProposalsSubmitted, ) -from adcp.types.generated_poc.media_buy.request_proposals_async_response_working import ( +from adcp.types.domains.media_buy.request_proposals_async_response_working import ( RequestProposalsWorking, ) -from adcp.types.generated_poc.media_buy.request_proposals_request import RequestProposalsRequest -from adcp.types.generated_poc.media_buy.request_proposals_response import ( +from adcp.types.domains.media_buy.request_proposals_request import RequestProposalsRequest +from adcp.types.domains.media_buy.request_proposals_response import ( IncompleteItem5, IncompleteItem6, IncompleteItem7, @@ -3375,146 +3246,126 @@ RequestProposalsResponse4, SourceAdcpVersion, ) -from adcp.types.generated_poc.media_buy.sync_audiences_request import ( - AudienceType, - SyncAudiencesRequest, -) -from adcp.types.generated_poc.media_buy.sync_audiences_response import ( +from adcp.types.domains.media_buy.sync_audiences_request import AudienceType, SyncAudiencesRequest +from adcp.types.domains.media_buy.sync_audiences_response import ( MatchBreakdown, SyncAudiencesResponse, SyncAudiencesResponse1, SyncAudiencesResponse2, SyncAudiencesResponse3, ) -from adcp.types.generated_poc.media_buy.sync_catalogs_async_response_input_required import ( +from adcp.types.domains.media_buy.sync_catalogs_async_response_input_required import ( SyncCatalogsInputRequired, ) -from adcp.types.generated_poc.media_buy.sync_catalogs_async_response_submitted import ( +from adcp.types.domains.media_buy.sync_catalogs_async_response_submitted import ( SyncCatalogsSubmitted, ) -from adcp.types.generated_poc.media_buy.sync_catalogs_async_response_working import ( - SyncCatalogsWorking, -) -from adcp.types.generated_poc.media_buy.sync_catalogs_request import SyncCatalogsRequest -from adcp.types.generated_poc.media_buy.sync_catalogs_response import ( +from adcp.types.domains.media_buy.sync_catalogs_async_response_working import SyncCatalogsWorking +from adcp.types.domains.media_buy.sync_catalogs_request import SyncCatalogsRequest +from adcp.types.domains.media_buy.sync_catalogs_response import ( ItemIssue, SyncCatalogsResponse, SyncCatalogsResponse1, SyncCatalogsResponse2, SyncCatalogsResponse3, ) -from adcp.types.generated_poc.media_buy.sync_event_sources_request import ( +from adcp.types.domains.media_buy.sync_event_sources_request import ( SyncEventSourcesRequest, ValueCurrency, ) -from adcp.types.generated_poc.media_buy.sync_event_sources_response import ( +from adcp.types.domains.media_buy.sync_event_sources_response import ( SyncEventSourcesResponse, SyncEventSourcesResponse1, SyncEventSourcesResponse2, ) -from adcp.types.generated_poc.media_buy.sync_reporting_receipts_request import ( +from adcp.types.domains.media_buy.sync_reporting_receipts_request import ( SyncReportingReceiptsRequest, ) -from adcp.types.generated_poc.media_buy.sync_reporting_receipts_response import ( +from adcp.types.domains.media_buy.sync_reporting_receipts_response import ( Results20, Results21, Results22, Results23, SyncReportingReceiptsResponse, ) -from adcp.types.generated_poc.media_buy.sync_reporting_status_request import ( - SyncReportingStatusRequest, -) -from adcp.types.generated_poc.media_buy.sync_reporting_status_response import ( +from adcp.types.domains.media_buy.sync_reporting_status_request import SyncReportingStatusRequest +from adcp.types.domains.media_buy.sync_reporting_status_response import ( Results26, Results27, SyncReportingStatusResponse, ) -from adcp.types.generated_poc.media_buy.update_media_buy_async_response_input_required import ( +from adcp.types.domains.media_buy.update_media_buy_async_response_input_required import ( UpdateMediaBuyInputRequired, ) -from adcp.types.generated_poc.media_buy.update_media_buy_async_response_submitted import ( +from adcp.types.domains.media_buy.update_media_buy_async_response_submitted import ( UpdateMediaBuySubmitted, ) -from adcp.types.generated_poc.media_buy.update_media_buy_async_response_working import ( +from adcp.types.domains.media_buy.update_media_buy_async_response_working import ( UpdateMediaBuyWorking, ) -from adcp.types.generated_poc.media_buy.update_media_buy_request import UpdateMediaBuyRequest -from adcp.types.generated_poc.media_buy.update_media_buy_response import ( +from adcp.types.domains.media_buy.update_media_buy_request import UpdateMediaBuyRequest +from adcp.types.domains.media_buy.update_media_buy_response import ( UpdateMediaBuyResponse, UpdateMediaBuyResponse1, UpdateMediaBuyResponse2, UpdateMediaBuyResponse3, ) -from adcp.types.generated_poc.pricing_options.cpa_option import CpaPricingOption -from adcp.types.generated_poc.pricing_options.cpc_option import CpcPricingOption -from adcp.types.generated_poc.pricing_options.cpcv_option import CpcvPricingOption -from adcp.types.generated_poc.pricing_options.cpm_option import CpmPricingOption -from adcp.types.generated_poc.pricing_options.cpp_option import CppPricingOption, Parameters -from adcp.types.generated_poc.pricing_options.cpv_option import ( +from adcp.types.domains.pricing_options.cpa_option import CpaPricingOption +from adcp.types.domains.pricing_options.cpc_option import CpcPricingOption +from adcp.types.domains.pricing_options.cpcv_option import CpcvPricingOption +from adcp.types.domains.pricing_options.cpm_option import CpmPricingOption +from adcp.types.domains.pricing_options.cpp_option import CppPricingOption, Parameters +from adcp.types.domains.pricing_options.cpv_option import ( CpvPricingOption, ViewThreshold, ViewThreshold1, ) -from adcp.types.generated_poc.pricing_options.flat_rate_option import FlatRatePricingOption -from adcp.types.generated_poc.pricing_options.price_breakdown import Adjustment, PriceBreakdown -from adcp.types.generated_poc.pricing_options.price_guidance import PriceGuidance -from adcp.types.generated_poc.pricing_options.revenue_share_option import RevenueSharePricingOption -from adcp.types.generated_poc.pricing_options.time_option import TimeBasedPricingOption, TimeUnit -from adcp.types.generated_poc.pricing_options.vcpm_option import VcpmPricingOption -from adcp.types.generated_poc.property.authorization_result import AuthorizationResult -from adcp.types.generated_poc.property.base_property_source import ( +from adcp.types.domains.pricing_options.flat_rate_option import FlatRatePricingOption +from adcp.types.domains.pricing_options.price_breakdown import Adjustment, PriceBreakdown +from adcp.types.domains.pricing_options.price_guidance import PriceGuidance +from adcp.types.domains.pricing_options.revenue_share_option import RevenueSharePricingOption +from adcp.types.domains.pricing_options.time_option import TimeBasedPricingOption, TimeUnit +from adcp.types.domains.pricing_options.vcpm_option import VcpmPricingOption +from adcp.types.domains.property.authorization_result import AuthorizationResult +from adcp.types.domains.property.base_property_source import ( BasePropertySource, BasePropertySource1, BasePropertySource2, BasePropertySource3, ) -from adcp.types.generated_poc.property.create_property_list_request import CreatePropertyListRequest -from adcp.types.generated_poc.property.create_property_list_response import ( - CreatePropertyListResponse, -) -from adcp.types.generated_poc.property.delete_property_list_request import DeletePropertyListRequest -from adcp.types.generated_poc.property.delete_property_list_response import ( - DeletePropertyListResponse, -) -from adcp.types.generated_poc.property.delivery_record import DeliveryRecord -from adcp.types.generated_poc.property.get_property_list_request import GetPropertyListRequest -from adcp.types.generated_poc.property.get_property_list_response import GetPropertyListResponse -from adcp.types.generated_poc.property.list_property_lists_request import ListPropertyListsRequest -from adcp.types.generated_poc.property.list_property_lists_response import ListPropertyListsResponse -from adcp.types.generated_poc.property.property_error import PropertyError -from adcp.types.generated_poc.property.property_feature import PropertyFeature -from adcp.types.generated_poc.property.property_feature_definition import PropertyFeatureDefinition -from adcp.types.generated_poc.property.property_feature_result import ( +from adcp.types.domains.property.create_property_list_request import CreatePropertyListRequest +from adcp.types.domains.property.create_property_list_response import CreatePropertyListResponse +from adcp.types.domains.property.delete_property_list_request import DeletePropertyListRequest +from adcp.types.domains.property.delete_property_list_response import DeletePropertyListResponse +from adcp.types.domains.property.delivery_record import DeliveryRecord +from adcp.types.domains.property.get_property_list_request import GetPropertyListRequest +from adcp.types.domains.property.get_property_list_response import GetPropertyListResponse +from adcp.types.domains.property.list_property_lists_request import ListPropertyListsRequest +from adcp.types.domains.property.list_property_lists_response import ListPropertyListsResponse +from adcp.types.domains.property.property_error import PropertyError +from adcp.types.domains.property.property_feature import PropertyFeature +from adcp.types.domains.property.property_feature_definition import PropertyFeatureDefinition +from adcp.types.domains.property.property_feature_result import ( CoverageStatus, PropertyFeatureResult, ) -from adcp.types.generated_poc.property.property_feature_value import PropertyFeatureValue -from adcp.types.generated_poc.property.property_list import PropertyList -from adcp.types.generated_poc.property.property_list_changed_webhook import ( - PropertyListChangedWebhook, -) -from adcp.types.generated_poc.property.property_list_filters import ( - CountriesAllItem, - PropertyListFilters, -) -from adcp.types.generated_poc.property.update_property_list_request import UpdatePropertyListRequest -from adcp.types.generated_poc.property.update_property_list_response import ( - UpdatePropertyListResponse, -) -from adcp.types.generated_poc.property.validate_property_delivery_request import ( +from adcp.types.domains.property.property_feature_value import PropertyFeatureValue +from adcp.types.domains.property.property_list import PropertyList +from adcp.types.domains.property.property_list_changed_webhook import PropertyListChangedWebhook +from adcp.types.domains.property.property_list_filters import CountriesAllItem, PropertyListFilters +from adcp.types.domains.property.update_property_list_request import UpdatePropertyListRequest +from adcp.types.domains.property.update_property_list_response import UpdatePropertyListResponse +from adcp.types.domains.property.validate_property_delivery_request import ( ValidatePropertyDeliveryRequest, ) -from adcp.types.generated_poc.property.validate_property_delivery_response import ( +from adcp.types.domains.property.validate_property_delivery_response import ( Aggregate, AuthorizationSummary, ValidatePropertyDeliveryResponse, ) -from adcp.types.generated_poc.property.validation_result import Requirement, ValidationResult -from adcp.types.generated_poc.protocol.get_adcp_capabilities_request import ( - GetAdcpCapabilitiesRequest, -) -from adcp.types.generated_poc.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.property.validation_result import Requirement, ValidationResult +from adcp.types.domains.protocol.get_adcp_capabilities_request import GetAdcpCapabilitiesRequest +from adcp.types.domains.protocol.get_adcp_capabilities_response import ( AcceptancePolicyDiscovery, AccessMode, Accreditation, @@ -3614,8 +3465,8 @@ WholesaleFeedVersioning, WholesaleFeedWebhooks, ) -from adcp.types.generated_poc.protocol.get_principal_request import GetPrincipalRequest -from adcp.types.generated_poc.protocol.get_principal_response import ( +from adcp.types.domains.protocol.get_principal_request import GetPrincipalRequest +from adcp.types.domains.protocol.get_principal_response import ( GetPrincipalResponse, PrincipalCurrentResult, PrincipalReadFailedResult, @@ -3625,21 +3476,18 @@ Result7, Result9, ) -from adcp.types.generated_poc.protocol.get_task_status_request import GetTaskStatusRequest -from adcp.types.generated_poc.protocol.get_task_status_response import GetTaskStatusResponse -from adcp.types.generated_poc.protocol.list_tasks_request import ListTasksRequest -from adcp.types.generated_poc.protocol.list_tasks_response import ListTasksResponse -from adcp.types.generated_poc.protocol.sync_agent_notification_configs_request import ( +from adcp.types.domains.protocol.get_task_status_request import GetTaskStatusRequest +from adcp.types.domains.protocol.get_task_status_response import GetTaskStatusResponse +from adcp.types.domains.protocol.list_tasks_request import ListTasksRequest +from adcp.types.domains.protocol.list_tasks_response import ListTasksResponse +from adcp.types.domains.protocol.sync_agent_notification_configs_request import ( SyncAgentNotificationConfigsRequest, ) -from adcp.types.generated_poc.protocol.sync_agent_notification_configs_response import ( +from adcp.types.domains.protocol.sync_agent_notification_configs_response import ( SyncAgentNotificationConfigsResponse, ) -from adcp.types.generated_poc.protocol.sync_principal_request import ( - Configuration, - SyncPrincipalRequest, -) -from adcp.types.generated_poc.protocol.sync_principal_response import ( +from adcp.types.domains.protocol.sync_principal_request import Configuration, SyncPrincipalRequest +from adcp.types.domains.protocol.sync_principal_response import ( Action33, PrincipalAppliedResult, PrincipalSyncFailedResult, @@ -3648,7 +3496,7 @@ Result19, SyncPrincipalResponse, ) -from adcp.types.generated_poc.registries.v1_canonical_mapping import ( +from adcp.types.domains.registries.v1_canonical_mapping import ( Mapping, ParameterMapping, Structural, @@ -3657,19 +3505,17 @@ V1V2CanonicalFormatMappingRegistry, V2, ) -from adcp.types.generated_poc.signals.activate_signal_request import ActivateSignalRequest -from adcp.types.generated_poc.signals.activate_signal_response import ( +from adcp.types.domains.signals.activate_signal_request import ActivateSignalRequest +from adcp.types.domains.signals.activate_signal_response import ( ActivateSignalResponse, ActivateSignalResponse1, ActivateSignalResponse2, ) -from adcp.types.generated_poc.signals.get_signals_async_response_submitted import ( - GetSignalsSubmitted, -) -from adcp.types.generated_poc.signals.get_signals_async_response_working import GetSignalsWorking -from adcp.types.generated_poc.signals.get_signals_request import GetSignalsRequest -from adcp.types.generated_poc.signals.get_signals_response import GetSignalsResponse -from adcp.types.generated_poc.sponsored_intelligence.si_capabilities import ( +from adcp.types.domains.signals.get_signals_async_response_submitted import GetSignalsSubmitted +from adcp.types.domains.signals.get_signals_async_response_working import GetSignalsWorking +from adcp.types.domains.signals.get_signals_request import GetSignalsRequest +from adcp.types.domains.signals.get_signals_response import GetSignalsResponse +from adcp.types.domains.sponsored_intelligence.si_capabilities import ( A2ui, Avatar, Commerce, @@ -3680,167 +3526,163 @@ Video, Voice, ) -from adcp.types.generated_poc.sponsored_intelligence.si_context_use import SiContextUse -from adcp.types.generated_poc.sponsored_intelligence.si_get_offering_request import ( - SiGetOfferingRequest, -) -from adcp.types.generated_poc.sponsored_intelligence.si_get_offering_response import ( +from adcp.types.domains.sponsored_intelligence.si_context_use import SiContextUse +from adcp.types.domains.sponsored_intelligence.si_get_offering_request import SiGetOfferingRequest +from adcp.types.domains.sponsored_intelligence.si_get_offering_response import ( MatchingProduct, SiGetOfferingResponse, ) -from adcp.types.generated_poc.sponsored_intelligence.si_identity import ( +from adcp.types.domains.sponsored_intelligence.si_identity import ( ConsentScopeEnum, PrivacyPolicyAcknowledged, ShippingAddress, SiIdentity, User, ) -from adcp.types.generated_poc.sponsored_intelligence.si_initiate_session_request import ( +from adcp.types.domains.sponsored_intelligence.si_initiate_session_request import ( SiInitiateSessionRequest, ) -from adcp.types.generated_poc.sponsored_intelligence.si_initiate_session_response import ( +from adcp.types.domains.sponsored_intelligence.si_initiate_session_response import ( SiInitiateSessionResponse, ) -from adcp.types.generated_poc.sponsored_intelligence.si_send_message_request import ( +from adcp.types.domains.sponsored_intelligence.si_send_message_request import ( ActionResponse, SiSendMessageRequest, ) -from adcp.types.generated_poc.sponsored_intelligence.si_send_message_response import ( +from adcp.types.domains.sponsored_intelligence.si_send_message_response import ( ContextForCheckout, Handoff, SiSendMessageResponse, ) -from adcp.types.generated_poc.sponsored_intelligence.si_sponsored_context import ( +from adcp.types.domains.sponsored_intelligence.si_sponsored_context import ( DisclosureObligation, PayingPrincipal, Proximity, SiSponsoredContext, Timing, ) -from adcp.types.generated_poc.sponsored_intelligence.si_sponsored_context_receipt import ( +from adcp.types.domains.sponsored_intelligence.si_sponsored_context_receipt import ( DisclosureCommitment, HostReceipt, SiSponsoredContextReceipt, Status46, ) -from adcp.types.generated_poc.sponsored_intelligence.si_terminate_session_request import ( +from adcp.types.domains.sponsored_intelligence.si_terminate_session_request import ( SiTerminateSessionRequest, TerminationContext, TransactionIntent, ) -from adcp.types.generated_poc.sponsored_intelligence.si_terminate_session_response import ( +from adcp.types.domains.sponsored_intelligence.si_terminate_session_response import ( AcpHandoff, FollowUp, SiTerminateSessionResponse, ) -from adcp.types.generated_poc.sponsored_intelligence.si_ui_element import SiUiElement -from adcp.types.generated_poc.trusted_match.available_package import AvailablePackage -from adcp.types.generated_poc.trusted_match.context_match_request import ( +from adcp.types.domains.sponsored_intelligence.si_ui_element import SiUiElement +from adcp.types.domains.trusted_match.available_package import AvailablePackage +from adcp.types.domains.trusted_match.context_match_request import ( ArtifactRef, ContextMatchRequest, ContextSignals, Sentiment, ) -from adcp.types.generated_poc.trusted_match.context_match_response import ( +from adcp.types.domains.trusted_match.context_match_response import ( ContextMatchResponseRouterPublisher, SignalsByProvider, TargetingKv, TargetingKvs, ) -from adcp.types.generated_poc.trusted_match.error import TmpError -from adcp.types.generated_poc.trusted_match.identity_match_request import ( +from adcp.types.domains.trusted_match.error import TmpError +from adcp.types.domains.trusted_match.identity_match_request import ( Attestation, Consent, IdentityMatchRequest, SealedCredential, VerificationLevel, ) -from adcp.types.generated_poc.trusted_match.identity_match_response import ( +from adcp.types.domains.trusted_match.identity_match_response import ( IdentityMatchResponse, IdentityMatchResponseRouterPublisher, TmpxProviders, ) -from adcp.types.generated_poc.trusted_match.offer import Offer -from adcp.types.generated_poc.trusted_match.offer_price import OfferPrice -from adcp.types.generated_poc.trusted_match.provider_context_match_response import ( +from adcp.types.domains.trusted_match.offer import Offer +from adcp.types.domains.trusted_match.offer_price import OfferPrice +from adcp.types.domains.trusted_match.provider_context_match_response import ( ContextMatchResponseProviderRouter, ) -from adcp.types.generated_poc.trusted_match.provider_identity_match_response import ( +from adcp.types.domains.trusted_match.provider_identity_match_response import ( IdentityMatchResponseProviderRouter, ) -from adcp.types.generated_poc.trusted_match.provider_registration import ( +from adcp.types.domains.trusted_match.provider_registration import ( TmpProviderRegistration, TmpProviderRegistration1, TmpProviderRegistration2, TmpxSlot, ) -from adcp.types.generated_poc.trusted_match.publisher_targeting_kv_config import ( +from adcp.types.domains.trusted_match.publisher_targeting_kv_config import ( PublisherTargetingKvMapping, ) -from adcp.types.generated_poc.trusted_match.publisher_tmpx_config import PublisherTmpxMacroMapping -from adcp.types.generated_poc.trusted_match.tmpx_chunk import TmpxChunk +from adcp.types.domains.trusted_match.publisher_tmpx_config import PublisherTmpxMacroMapping +from adcp.types.domains.trusted_match.tmpx_chunk import TmpxChunk # Special imports for name collisions (qualified names for types defined in multiple modules) -from adcp.types.generated_poc.core.package import Package as _PackageFromPackage -from adcp.types.generated_poc.media_buy.get_media_buys_response import ( +from adcp.types.domains.core.package import Package as _PackageFromPackage +from adcp.types.domains.media_buy.get_media_buys_response import ( Package as _PackageFromGetMediaBuysResponse, ) -from adcp.types.generated_poc.media_buy.get_media_buy_delivery_request import ( +from adcp.types.domains.media_buy.get_media_buy_delivery_request import ( Audience as _AudienceFromGetMediaBuyDeliveryRequest, ) -from adcp.types.generated_poc.media_buy.sync_audiences_request import ( +from adcp.types.domains.media_buy.sync_audiences_request import ( Audience as _AudienceFromSyncAudiencesRequest, ) -from adcp.types.generated_poc.media_buy.sync_audiences_response import ( +from adcp.types.domains.media_buy.sync_audiences_response import ( Audience as _AudienceFromSyncAudiencesResponse, ) -from adcp.types.generated_poc.compliance.comply_test_controller_response import ( +from adcp.types.domains.compliance.comply_test_controller_response import ( Error as _ErrorFromComplyTestControllerResponse, ) -from adcp.types.generated_poc.core.error import Error as _ErrorFromError -from adcp.types.generated_poc.core.provenance import DeclaredBy as _DeclaredByFromProvenance -from adcp.types.generated_poc.sponsored_intelligence.si_sponsored_context import ( +from adcp.types.domains.core.error import Error as _ErrorFromError +from adcp.types.domains.core.provenance import DeclaredBy as _DeclaredByFromProvenance +from adcp.types.domains.sponsored_intelligence.si_sponsored_context import ( DeclaredBy as _DeclaredByFromSiSponsoredContext, ) -from adcp.types.generated_poc.trusted_match.identity_match_response import ( +from adcp.types.domains.trusted_match.identity_match_response import ( TmpxMacro as _TmpxMacroFromIdentityMatchResponse, ) -from adcp.types.generated_poc.trusted_match.provider_registration import ( +from adcp.types.domains.trusted_match.provider_registration import ( TmpxMacro as _TmpxMacroFromProviderRegistration, ) -from adcp.types.generated_poc.core.provenance import Provenance as _ProvenanceFromProvenance -from adcp.types.generated_poc.core.reference_renderer import ( +from adcp.types.domains.core.provenance import Provenance as _ProvenanceFromProvenance +from adcp.types.domains.core.reference_renderer import ( Provenance as _ProvenanceFromReferenceRenderer, ) -from adcp.types.generated_poc.core.preview_renderer_metadata import ( +from adcp.types.domains.core.preview_renderer_metadata import ( RenderingOrigin as _RenderingOriginFromPreviewRendererMetadata, ) -from adcp.types.generated_poc.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.protocol.get_adcp_capabilities_response import ( RenderingOrigin as _RenderingOriginFromGetAdcpCapabilitiesResponse, ) -from adcp.types.generated_poc.core.preview_provider import Route as _RouteFromPreviewProvider -from adcp.types.generated_poc.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.core.preview_provider import Route as _RouteFromPreviewProvider +from adcp.types.domains.protocol.get_adcp_capabilities_response import ( Route as _RouteFromGetAdcpCapabilitiesResponse, ) -from adcp.types.generated_poc.media_buy.product_discovery_criteria import ( +from adcp.types.domains.media_buy.product_discovery_criteria import ( ProductId as _ProductIdFromProductDiscoveryCriteria, ) -from adcp.types.generated_poc.media_buy.request_proposals_response import ( +from adcp.types.domains.media_buy.request_proposals_response import ( ProductId as _ProductIdFromRequestProposalsResponse, ) -from adcp.types.generated_poc.core.canonical_proposal import ( +from adcp.types.domains.core.canonical_proposal import ( TotalBudgetGuidance as _TotalBudgetGuidanceFromCanonicalProposal, ) -from adcp.types.generated_poc.core.proposal import ( - TotalBudgetGuidance as _TotalBudgetGuidanceFromProposal, -) -from adcp.types.generated_poc.media_buy.refine_proposals_response import ( +from adcp.types.domains.core.proposal import TotalBudgetGuidance as _TotalBudgetGuidanceFromProposal +from adcp.types.domains.media_buy.refine_proposals_response import ( TotalBudgetGuidance as _TotalBudgetGuidanceFromRefineProposalsResponse, ) -from adcp.types.generated_poc.core.downstream_connection_requirement import ( +from adcp.types.domains.core.downstream_connection_requirement import ( RequiredForItem as _RequiredForItemFromDownstreamConnectionRequirement, ) -from adcp.types.generated_poc.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.protocol.get_adcp_capabilities_response import ( RequiredForItem as _RequiredForItemFromGetAdcpCapabilitiesResponse, ) @@ -6830,7 +6672,7 @@ # This must happen AFTER all imports to resolve forward reference chains # Import individual modules needed for rebuilding -from adcp.types import generated_poc # noqa: F401 +from adcp.types import domains # noqa: F401 # Rebuild models that reference other models via forward refs # Note: only call model_rebuild() on actual classes, not Union type aliases diff --git a/src/adcp/types/_generated_poc_alias.py b/src/adcp/types/_generated_poc_alias.py new file mode 100644 index 000000000..7be3a3981 --- /dev/null +++ b/src/adcp/types/_generated_poc_alias.py @@ -0,0 +1,289 @@ +"""The pre-9.0 generated tree's import path, aliased to the module it moved to. + +The generator used to write its models to ``adcp.types.generated_poc`` and the +public surface re-exported them. It now writes them to ``adcp.types.domains`` +directly: the module that defines a class is the module adopters import it +from, so there is no private tree left to re-export. + +Almost every module stem moved unchanged, so the migration is mostly a prefix +rename:: + + -from adcp.types.generated_poc.media_buy.package_request import PackageRequest + +from adcp.types.domains.media_buy.package_request import PackageRequest + +``adcp migrate v3-to-v4`` rewrites those lines; ``adcp.types`` remains the +first choice, and a domain path is for a name the flat namespace cannot bind, +as ``docs/type-surface.md`` describes. + +**A root discovery schema is the exception, and a prefix rename loses it.** +``scripts/generate_types.py``'s ``ROOT_DISCOVERY_SCHEMAS`` names every schema +whose basename collides with a task-schema DIRECTORY: ``brand.json`` against +``brand/*.json``. Pre-9.0 the generator wrote such a schema's models into the +colliding package's ``__init__`` — ``generated_poc/brand/__init__.py`` WAS +``brand.json``, 140 classes including ``Brand`` — and 9.0 writes them to +``domains/_discovery.py`` instead, leaving ``domains//__init__.py`` +to the generated domain aggregator. So one deprecated name has two canonical +halves, and ``generated_poc.brand`` resolving to ``domains.brand`` alone raises +``ImportError: cannot import name 'Brand'`` — an error naming a module the +adopter never wrote. ``_SplitAliasModule`` below serves both halves under the +one deprecated name, discovery half first because that is the surface the +pre-9.0 module had. The ``_discovery`` suffix is the generator's own naming +(``ROOT_DISCOVERY_SCHEMAS``' value for ``brand.json`` is ``brand_discovery.py``) +rather than a second list here, and +``tests/test_generated_poc_alias.py::test_a_root_discovery_module_is_named_the_way_the_alias_derives_it`` +fails the build if the generator ever names one some other way. + +#1360 measured 110 such imports in ONE production seller, so 9.0 keeps the old +path working for the 9.x line and warns, instead of breaking every one of them +at once. **It is removed in v10** — and +``tests/test_generated_poc_alias.py::test_the_alias_does_not_outlive_its_removal_major`` +fails the build if this module is still here then. + +The one property that makes this an alias rather than a second copy of the +tree: ``sys.modules`` holds ONE module object under both names. + + >>> import adcp.types.generated_poc.core.format_id as old + >>> import adcp.types.domains.core.format_id as new + >>> old is new + True + >>> old.FormatReferenceStructuredObject is new.FormatReferenceStructuredObject + True + +A split module is the one place that cannot hold: two canonical modules cannot +be one object. The property that MATTERS is the second one, and the shim keeps +it — it binds no class of its own, it reads the two canonical modules. + + >>> from adcp.types.generated_poc.brand import Brand + >>> from adcp.types.domains.brand_discovery import Brand as Canonical + >>> Brand is Canonical + True + +Redirecting a package's ``__path__`` instead would make the import machinery +LOAD each module a second time under the old name, giving two module objects +and two class objects per class — and `isinstance` against the wrong one fails +for no visible reason. ``tests/test_export_surface_is_derived.py`` exists to +refuse exactly that, by asserting every generated class is DEFINED at the +address it is imported from. So the loader here never executes anything: it +imports the canonical module and hands that object back. A split name gets a +shim instead of one of its two halves, and the shim executes nothing either — +it binds no class, it reads the halves. + +The one write the import machinery makes to a module handed back from +``create_module`` is ``__spec__`` (``importlib._bootstrap._init_module_attrs`` +sets ``__name__``, ``__loader__``, ``__package__``, ``__path__`` and +``__file__`` only when they are absent, and they are not). ``exec_module`` +puts the canonical spec back, so the shared object carries no trace of having +been reached through the old name. +""" + +from __future__ import annotations + +import importlib +import importlib.util +import sys +import warnings +from collections.abc import Sequence +from importlib.machinery import ModuleSpec +from types import ModuleType +from typing import TYPE_CHECKING, cast + +if TYPE_CHECKING: + # For the one ``cast`` below, and nothing else. ``importlib.abc`` costs + # ~9ms to import because it pulls ``importlib.resources`` -> + # ``inspect``/``tempfile``/``typing``, and this module sits on + # ``adcp.types``' import path — the same path whose cost ``adcp.types``' + # PEP 562 ``__getattr__`` and ``adcp``'s deferred ``importlib.metadata`` + # exist to keep down. The import system reaches a loader and a finder + # structurally, so neither class needs the ABC at runtime. + from importlib.abc import Loader + +#: The pre-9.0 path, and the path it moved to. Every name under the first is +#: served by the identically-stemmed module under the second, plus — for a +#: root discovery schema — that module's ``_discovery`` sibling. +DEPRECATED_ROOT = "adcp.types.generated_poc" +CANONICAL_ROOT = "adcp.types.domains" + +#: The major version that removes this module. Stated here because a +#: deprecation with no expiry is a permanent second surface. +REMOVED_IN_MAJOR = 10 + +#: The suffix ``scripts/generate_types.py`` gives a root discovery schema's +#: module when its basename collides with a task-schema directory. Derived from +#: there rather than restated as a list of stems, so a schema added to +#: ``ROOT_DISCOVERY_SCHEMAS`` tomorrow is served without touching this file. +ROOT_DISCOVERY_SUFFIX = "_discovery" + + +def canonical_name(deprecated: str) -> str: + """``adcp.types.generated_poc.core.x`` -> ``adcp.types.domains.core.x``.""" + + return CANONICAL_ROOT + deprecated[len(DEPRECATED_ROOT) :] + + +def discovery_half(canonical: str) -> str | None: + """The second canonical module a split deprecated name also has to serve. + + ``adcp.types.domains.brand`` -> ``adcp.types.domains.brand_discovery``, and + ``None`` for every name that did not split. A root discovery schema sits at + the schema root, so only a DOMAIN-level package can be the colliding half; + anything deeper, and any domain with no ``_discovery`` sibling, is an + ordinary prefix rename. + """ + + stem = canonical[len(CANONICAL_ROOT) + 1 :] + if not stem or "." in stem: + return None + candidate = f"{canonical}{ROOT_DISCOVERY_SUFFIX}" + try: + if importlib.util.find_spec(candidate) is None: + return None + except (ImportError, AttributeError, ValueError): + return None + return candidate + + +class _SplitAliasModule(ModuleType): + """One deprecated name over the two canonical halves of a split module. + + Reads both; defines nothing. ``__getattr__`` fires only after normal + instance lookup misses, so the two halves live in the instance dict and the + attributes the import system writes (``__name__``, ``__spec__``, ``__path__`` + and friends) resolve without reaching it. + + ``__path__`` is the aggregator package's, which is what keeps + ``generated_poc.brand.acquire_rights_request`` resolving: ``_find_and_load`` + reads the parent's ``__path__`` before any meta-path finder is consulted, so + a shim that could not answer for it would raise + "'adcp.types.generated_poc.brand' is not a package" for all 20 of that + domain's leaf modules. Bound here rather than left to ``__getattr__``, which + would reach the same object by the same fallback: a package attribute the + import system reads on every submodule import is not a compatibility + lookup, and ``_init_module_attrs`` skips it only because it is already set. + + The discovery half is tried first because it is the surface the pre-9.0 + module HAD — ``generated_poc/brand/__init__.py`` was ``brand.json`` and + nothing else. The two halves share 12 names (``Logo``, ``Colors``, + ``Fonts``, ...), and for those the pre-9.0 import got the discovery class. + """ + + def __init__(self, name: str, discovery: ModuleType, aggregator: ModuleType) -> None: + super().__init__(name) + self.__path__ = aggregator.__path__ + # Set through ``__dict__`` so ``__getattr__`` can read it without the + # risk of recursing through a half-initialized instance. + self.__dict__["_halves"] = (discovery, aggregator) + + def __getattr__(self, attr: str) -> object: + for half in self.__dict__["_halves"]: + try: + return getattr(half, attr) + except AttributeError: + continue + halves = ", ".join(half.__name__ for half in self.__dict__["_halves"]) + raise AttributeError( + f"module {self.__name__!r} has no attribute {attr!r} (served from {halves})" + ) + + +class _AliasLoader: + """Hands back the canonical module object instead of loading anything.""" + + def __init__(self, canonical: str) -> None: + self._canonical = canonical + self._canonical_spec: ModuleSpec | None = None + + def create_module(self, spec: ModuleSpec) -> ModuleType: + module = importlib.import_module(self._canonical) + discovery = discovery_half(self._canonical) + if discovery is not None: + # A split name is the one case that gets its own module object: two + # canonical modules cannot be one. It binds no class, so every class + # reached through it is still the canonical class. + return _SplitAliasModule(spec.name, importlib.import_module(discovery), module) + # Captured before ``_init_module_attrs`` overwrites it with *spec*. + self._canonical_spec = module.__spec__ + return module + + def exec_module(self, module: ModuleType) -> None: + if isinstance(module, _SplitAliasModule): + # The shim IS its own module; it keeps the spec naming itself, so a + # reload re-runs this loader rather than one canonical half. + return + module.__spec__ = self._canonical_spec + + +class _AliasFinder: + """Serves every ``adcp.types.generated_poc`` name from its new address. + + Installed at the front of ``sys.meta_path`` so it is consulted before + ``PathFinder``. That ordering is the whole mechanism for the submodules: a + deprecated package resolves to the canonical package, whose ``__path__`` + points at the real directory, and ``PathFinder`` would happily load a + second copy of every module on it under the deprecated name. + + The warning fires once per deprecated MODULE, tracked here rather than + left to the import machinery's own caching: ``find_spec`` is called again + for a name already in ``sys.modules`` whenever something probes it ( + ``adcp.types.__getattr__`` does, for its submodule passthrough), and a + rename is one fact however many times it is looked up. + """ + + def __init__(self) -> None: + self._warned: set[str] = set() + + def find_spec( + self, + fullname: str, + path: Sequence[str] | None = None, + target: ModuleType | None = None, + ) -> ModuleSpec | None: + if fullname != DEPRECATED_ROOT and not fullname.startswith(DEPRECATED_ROOT + "."): + return None + canonical = canonical_name(fullname) + try: + if importlib.util.find_spec(canonical) is None: + return None + except (ImportError, AttributeError, ValueError): + # No such module under the new root either. Returning None lets the + # ModuleNotFoundError name what the caller actually asked for. + return None + if fullname not in self._warned: + self._warned.add(fullname) + # A split name's advice is not the prefix rename, because the + # classes this module used to hold are under the other half. Saying + # "import adcp.types.domains.brand instead" and nothing more sends + # the reader to a module that does not bind what they imported. + discovery = discovery_half(canonical) + if discovery is None: + advice = ( + f"import {canonical} instead. The module stems are " + f"unchanged, so this is a prefix rename, and " + f"`adcp migrate v3-to-v4` rewrites it." + ) + else: + advice = ( + f"import {discovery} for the classes this module declared " + f"and {canonical} for the ones its domain aggregates — this " + f"stem split in two, so it is the one case that is not a " + f"prefix rename. See docs/types-9-migration.md." + ) + warnings.warn( + f"{fullname} is deprecated since adcp 9.0 and is removed in " + f"v{REMOVED_IN_MAJOR}; {advice}", + DeprecationWarning, + stacklevel=2, + ) + # ``ModuleSpec`` declares its loader as ``importlib.abc.Loader``, which + # is a registration ABC: what the import system actually calls is + # ``create_module`` / ``exec_module``, and ``_AliasLoader`` defines + # both. typeshed's structural alternative, ``LoaderProtocol``, declares + # only ``load_module`` — removed from ``Loader`` in 3.12 — so + # implementing it would be the less honest of the two options. + return ModuleSpec(fullname, cast("Loader", _AliasLoader(canonical))) + + +def install() -> None: + """Put the finder at the front of ``sys.meta_path``, at most once.""" + + if not any(isinstance(finder, _AliasFinder) for finder in sys.meta_path): + sys.meta_path.insert(0, _AliasFinder()) diff --git a/src/adcp/types/aliases.py b/src/adcp/types/aliases.py index 81693c64d..779b73c5e 100644 --- a/src/adcp/types/aliases.py +++ b/src/adcp/types/aliases.py @@ -87,7 +87,7 @@ UpdateMediaBuyRequest, VcpmPricingOption, ) -from adcp.types.generated_poc.core.assets.asset_union import ( +from adcp.types.domains.core.assets.asset_union import ( DaastAsset1, DaastAsset2, VastAsset1, @@ -114,80 +114,80 @@ LegacyPreviewCreativeSingleResponse, LegacyPreviewCreativeVariantResponse, ) -from adcp.types.generated_poc.core.async_response_refs.media_buy.get_products_async_response_input_required import ( # noqa: E501 +from adcp.types.domains.core.async_response_refs.media_buy.get_products_async_response_input_required import ( # noqa: E501 GetProductsInputRequired, ) -from adcp.types.generated_poc.core.async_response_refs.media_buy.get_products_async_response_submitted import ( # noqa: E501 +from adcp.types.domains.core.async_response_refs.media_buy.get_products_async_response_submitted import ( # noqa: E501 GetProductsSubmitted, ) -from adcp.types.generated_poc.core.async_response_refs.media_buy.get_products_async_response_working import ( # noqa: E501 +from adcp.types.domains.core.async_response_refs.media_buy.get_products_async_response_working import ( # noqa: E501 GetProductsWorking, ) -from adcp.types.generated_poc.core.async_response_refs.signals.get_signals_async_response_submitted import ( # noqa: E501 +from adcp.types.domains.core.async_response_refs.signals.get_signals_async_response_submitted import ( # noqa: E501 GetSignalsSubmitted, ) -from adcp.types.generated_poc.core.async_response_refs.signals.get_signals_async_response_working import ( # noqa: E501 +from adcp.types.domains.core.async_response_refs.signals.get_signals_async_response_working import ( # noqa: E501 GetSignalsWorking, ) -from adcp.types.generated_poc.core.error import ( +from adcp.types.domains.core.error import ( Recovery, Source, ) -from adcp.types.generated_poc.core.missing_metric import MissingMetric -from adcp.types.generated_poc.core.product import TrustedMatch -from adcp.types.generated_poc.core.product_allocation import ProductAllocation -from adcp.types.generated_poc.core.signal_coverage_forecast import SignalCoverageForecast -from adcp.types.generated_poc.protocol.get_principal_response import ( +from adcp.types.domains.core.missing_metric import MissingMetric +from adcp.types.domains.core.product import TrustedMatch +from adcp.types.domains.core.product_allocation import ProductAllocation +from adcp.types.domains.core.signal_coverage_forecast import SignalCoverageForecast +from adcp.types.domains.protocol.get_principal_response import ( PrincipalCurrentResult, PrincipalReadFailedResult, PrincipalRecognizedResult, PrincipalUnconfiguredResult, ) -from adcp.types.generated_poc.protocol.sync_principal_request import ( +from adcp.types.domains.protocol.sync_principal_request import ( Configuration as PrincipalConfiguration, ) -from adcp.types.generated_poc.protocol.sync_principal_response import ( +from adcp.types.domains.protocol.sync_principal_response import ( PrincipalAppliedResult, PrincipalSyncFailedResult, PrincipalValidatedResult, ) -from adcp.types.generated_poc.core.vendor_pricing_option import ( +from adcp.types.domains.core.vendor_pricing_option import ( VendorPricingOption as VendorPricingOptionUnion, ) -from adcp.types.generated_poc.core.vendor_pricing_option import ( +from adcp.types.domains.core.vendor_pricing_option import ( VendorPricingOption1 as CpmVendorPricingOption, ) -from adcp.types.generated_poc.core.vendor_pricing_option import ( +from adcp.types.domains.core.vendor_pricing_option import ( VendorPricingOption2 as PercentOfMediaVendorPricingOption, ) -from adcp.types.generated_poc.core.vendor_pricing_option import ( +from adcp.types.domains.core.vendor_pricing_option import ( VendorPricingOption3 as FlatFeeVendorPricingOption, ) -from adcp.types.generated_poc.core.vendor_pricing_option import ( +from adcp.types.domains.core.vendor_pricing_option import ( VendorPricingOption4 as PerUnitVendorPricingOption, ) -from adcp.types.generated_poc.core.vendor_pricing_option import ( +from adcp.types.domains.core.vendor_pricing_option import ( VendorPricingOption5 as CustomVendorPricingOption, ) -from adcp.types.generated_poc.signals.get_signals_response import ( +from adcp.types.domains.signals.get_signals_response import ( Range as SignalCoverageRange, ) -from adcp.types.generated_poc.media_buy.create_media_buy_response import ( +from adcp.types.domains.media_buy.create_media_buy_response import ( CreateMediaBuyResponse1, CreateMediaBuyResponse2, CreateMediaBuyResponse3, ) -from adcp.types.generated_poc.media_buy.get_products_response import ( +from adcp.types.domains.media_buy.get_products_response import ( GetProductsResponse as _GetProductsSuccessResponse, ) -from adcp.types.generated_poc.signals.get_signals_response import ( +from adcp.types.domains.signals.get_signals_response import ( GetSignalsResponse as _GetSignalsSuccessResponse, ) -from adcp.types.generated_poc.brand_discovery import Brand as BrandIdentity +from adcp.types.domains.brand_discovery import Brand as BrandIdentity """Semantic public model for a brand entry in ``brand.json``. -This replaces the removed private ``adcp.types.generated_poc.brand.Brand`` +This replaces the removed private ``adcp.types.domains.brand.Brand`` import without colliding with the unrelated capabilities ``Brand`` model. """ @@ -400,13 +400,13 @@ def _generated_alias(name: str, fallback_name: str) -> Any: ] # ``ProductFormatDeclaration`` comes from ``adcp.types.canonical_decl`` -# (a hand-rolled class) rather than ``generated_poc`` because the codegen +# (a hand-rolled class) rather than the generated tree because the codegen # can't represent the discriminated oneOf — see canonical_decl.py. from adcp.types.canonical_decl import ProductFormatDeclaration -from adcp.types.generated_poc.core.assets.pixel_tracker_asset import ( +from adcp.types.domains.core.assets.pixel_tracker_asset import ( Method as PixelTrackerMethod, ) -from adcp.types.generated_poc.enums.pixel_tracking_event import ( +from adcp.types.domains.enums.pixel_tracking_event import ( PixelTrackingEvent as PixelTrackerEvent, ) @@ -414,9 +414,9 @@ def _generated_alias(name: str, fallback_name: str) -> Any: # Canonical-formats public surface (AdCP 3.1) # ---------------------------------------------------------------------------- # The v2 catalog-side canonical-formats vocabulary lives across several -# generated_poc paths. Re-export the public-facing classes under clean names +# generated module paths. Re-export the public-facing classes under clean names # so adopters import them from ``adcp.types`` without having to reach into -# ``generated_poc/``. Two renames clean up codegen-derived class names that +# the generated tree. Two renames clean up codegen-derived class names that # include the schema title's parenthetical descriptor: # # * ``CanonicalFormatAgentPlacementAiSurfaceSponsoredPlacement`` → @@ -427,88 +427,88 @@ def _generated_alias(name: str, fallback_name: str) -> Any: # All other canonical format classes keep their generated names. Registry # types are renamed from the generic codegen forms (``Mapping``, ``V1Pattern``, # ``V2``) to scoped names that make sense once imported into ``adcp.types``. -from adcp.types.generated_poc.core.canonical_format_kind import ( +from adcp.types.domains.core.canonical_format_kind import ( CanonicalFormatKind, ) -from adcp.types.generated_poc.core.canonical_projection_ref import ( +from adcp.types.domains.core.canonical_projection_ref import ( AssetSource as CanonicalAssetSource, ) -from adcp.types.generated_poc.core.canonical_projection_ref import ( +from adcp.types.domains.core.canonical_projection_ref import ( CanonicalProjectionReference, ) -from adcp.types.generated_poc.core.canonical_projection_slot_override import ( +from adcp.types.domains.core.canonical_projection_slot_override import ( CanonicalProjectionSlotOverride as CanonicalSlotOverride, ) -from adcp.types.generated_poc.core.product_format_declaration import ( +from adcp.types.domains.core.product_format_declaration import ( SellerPreference as ProductFormatSellerPreference, ) -from adcp.types.generated_poc.formats.canonical._base import ( +from adcp.types.domains.formats.canonical._base import ( CanonicalFormatBase, ) -from adcp.types.generated_poc.formats.canonical._base import ( +from adcp.types.domains.formats.canonical._base import ( CompositionModel as CanonicalCompositionModel, ) -from adcp.types.generated_poc.formats.canonical.agent_placement import ( +from adcp.types.domains.formats.canonical.agent_placement import ( CanonicalFormatAgentPlacementAiSurfaceSponsoredPlacement as CanonicalFormatAgentPlacement, ) -from adcp.types.generated_poc.formats.canonical.audio_daast import ( +from adcp.types.domains.formats.canonical.audio_daast import ( CanonicalFormatDaastAudio, ) -from adcp.types.generated_poc.formats.canonical.audio_hosted import ( +from adcp.types.domains.formats.canonical.audio_hosted import ( CanonicalFormatHostedAudio, ) -from adcp.types.generated_poc.formats.canonical.display_tag import ( +from adcp.types.domains.formats.canonical.display_tag import ( CanonicalFormatDisplayTag, ) -from adcp.types.generated_poc.formats.canonical.html5 import ( +from adcp.types.domains.formats.canonical.html5 import ( CanonicalFormatHtml5Banner, ) -from adcp.types.generated_poc.formats.canonical.image import ( +from adcp.types.domains.formats.canonical.image import ( CanonicalFormatImage, ) -from adcp.types.generated_poc.formats.canonical.image_carousel import ( +from adcp.types.domains.formats.canonical.image_carousel import ( CanonicalFormatImageCarousel, ) -from adcp.types.generated_poc.formats.canonical.native_in_feed import ( +from adcp.types.domains.formats.canonical.native_in_feed import ( CanonicalFormatNativeInFeed, ) -from adcp.types.generated_poc.formats.canonical.responsive_creative import ( +from adcp.types.domains.formats.canonical.responsive_creative import ( CanonicalFormatResponsiveCreative, ) -from adcp.types.generated_poc.formats.canonical.sponsored_placement import ( +from adcp.types.domains.formats.canonical.sponsored_placement import ( CanonicalFormatSponsoredPlacementRetailMediaCatalogDriven as CanonicalFormatSponsoredPlacement, ) -from adcp.types.generated_poc.formats.canonical.video_hosted import ( +from adcp.types.domains.formats.canonical.video_hosted import ( CanonicalFormatHostedVideo, ) -from adcp.types.generated_poc.formats.canonical.video_vast import ( +from adcp.types.domains.formats.canonical.video_vast import ( CanonicalFormatVastVideo, ) -from adcp.types.generated_poc.registries.v1_canonical_mapping import ( +from adcp.types.domains.registries.v1_canonical_mapping import ( V2 as V1CanonicalV2Projection, # noqa: N811 — codegen class ``V2``; rename here ) -from adcp.types.generated_poc.registries.v1_canonical_mapping import ( +from adcp.types.domains.registries.v1_canonical_mapping import ( Dimensions as V1CanonicalDimensions, ) -from adcp.types.generated_poc.registries.v1_canonical_mapping import ( +from adcp.types.domains.registries.v1_canonical_mapping import ( Mapping as V1CanonicalMapping, ) -from adcp.types.generated_poc.registries.v1_canonical_mapping import ( +from adcp.types.domains.registries.v1_canonical_mapping import ( Structural as V1CanonicalStructural, ) -from adcp.types.generated_poc.registries.v1_canonical_mapping import ( +from adcp.types.domains.registries.v1_canonical_mapping import ( V1Pattern as V1CanonicalGlobPattern, ) -from adcp.types.generated_poc.registries.v1_canonical_mapping import ( +from adcp.types.domains.registries.v1_canonical_mapping import ( V1Pattern1 as V1CanonicalStructuralPattern, ) -from adcp.types.generated_poc.registries.v1_canonical_mapping import ( +from adcp.types.domains.registries.v1_canonical_mapping import ( V1V2CanonicalFormatMappingRegistry, ) try: - from adcp.types.generated_poc.creative.sync_creatives_response import ( + from adcp.types.domains.creative.sync_creatives_response import ( Creative as SyncCreativeResultInternal, ) except ImportError: @@ -516,18 +516,18 @@ def _generated_alias(name: str, fallback_name: str) -> Any: # Status name collides across many modules. Preserve backward compat by importing # the specific variant that was exported on main (media buy delivery status). -from adcp.types.generated_poc.media_buy.get_media_buy_delivery_response import ( +from adcp.types.domains.media_buy.get_media_buy_delivery_response import ( Status as MediaBuyDeliveryStatus, ) # Audience name collides in _generated (delivery breakdown wins over sync request) -from adcp.types.generated_poc.media_buy.sync_audiences_request import ( +from adcp.types.domains.media_buy.sync_audiences_request import ( Audience as SyncAudiencesAudienceInternal, ) # Import nested types that aren't exported by _generated but are useful for type hints try: - from adcp.types.generated_poc.media_buy.sync_catalogs_response import ( + from adcp.types.domains.media_buy.sync_catalogs_response import ( Catalog as SyncCatalogResultInternal, ) except ImportError: @@ -846,11 +846,21 @@ def process_result(result: SyncCatalogResult) -> None: UpdateMediaBuyErrorResponse: TypeAlias = UpdateMediaBuyResponse2 """Error response - media buy update failed, no changes applied.""" +UpdateMediaBuySubmittedResponse: TypeAlias = UpdateMediaBuyResponse3 +"""Submitted (async) envelope - media buy update accepted for async processing.""" + +# One Legacy name per arm of this union, not two of the three (#1399). The +# generated arm is what a seller holds after reading a response off the wire; +# the bare names above are the canonical SUBCLASSES, so without these the only +# public spelling for the arm itself was an ``adcp.types.domains`` path. +LegacyUpdateMediaBuySuccessResponse: TypeAlias = UpdateMediaBuyResponse1 +"""Exact legacy-generated update success shape for private-import migration.""" + LegacyUpdateMediaBuyErrorResponse: TypeAlias = UpdateMediaBuyResponse2 """Exact legacy-generated update error shape for private-import migration.""" -UpdateMediaBuySubmittedResponse: TypeAlias = UpdateMediaBuyResponse3 -"""Submitted (async) envelope - media buy update accepted for async processing.""" +LegacyUpdateMediaBuySubmittedResponse: TypeAlias = UpdateMediaBuyResponse3 +"""Exact legacy-generated update submitted shape for private-import migration.""" # Get Account Financials Response Variants GetAccountFinancialsSuccessResponse: TypeAlias = GetAccountFinancialsResponse1 @@ -1265,22 +1275,22 @@ def process_result(result: SyncCatalogResult) -> None: # static type of its neighbour — an off-by-one that made every documented # constructor call fail ``mypy --strict``. Binding the raw variants keeps the # runtime object and the static type identical. -from adcp.types.generated_poc.adagents import ( +from adcp.types.domains.adagents import ( AuthorizedAgents1 as _AuthorizedAgentsPropertyIds, ) -from adcp.types.generated_poc.adagents import ( +from adcp.types.domains.adagents import ( AuthorizedAgents2 as _AuthorizedAgentsPropertyTags, ) -from adcp.types.generated_poc.adagents import ( +from adcp.types.domains.adagents import ( AuthorizedAgents3 as _AuthorizedAgentsInlineProperties, ) -from adcp.types.generated_poc.adagents import ( +from adcp.types.domains.adagents import ( AuthorizedAgents4 as _AuthorizedAgentsPublisherProperties, ) -from adcp.types.generated_poc.adagents import ( +from adcp.types.domains.adagents import ( AuthorizedAgents5 as _AuthorizedAgentsSignalIds, ) -from adcp.types.generated_poc.adagents import ( +from adcp.types.domains.adagents import ( AuthorizedAgents6 as _AuthorizedAgentsSignalTags, ) @@ -1546,7 +1556,7 @@ def filter_products(props: PublisherProperties) -> None: # (alphabetical wins), so the get_products version is not reachable via # _generated at all. Import it directly from its source module. -from adcp.types.generated_poc.media_buy.get_products_request import ( +from adcp.types.domains.media_buy.get_products_request import ( Field1 as GetProductsFieldInternal, ) @@ -1556,7 +1566,7 @@ def filter_products(props: PublisherProperties) -> None: Values include product_id, name, description, pricing_options, placements, etc. """ -from adcp.types.generated_poc.brand.get_brand_identity_request import ( +from adcp.types.domains.brand.get_brand_identity_request import ( Field1 as GetBrandIdentityFieldInternal, ) @@ -1670,9 +1680,9 @@ def get_pricing(options: list[PricingOption]) -> None: # to a class whose ``asset_type`` literal default matches the expected # value. Generator renumbering is caught there, not in downstream code. -from adcp.types.generated_poc.core import format as _format_module -from adcp.types.generated_poc.core.format import BaseGroupAsset as _BaseGroupAsset -from adcp.types.generated_poc.core.format import BaseIndividualAsset as _BaseIndividualAsset +from adcp.types.domains.core import format as _format_module +from adcp.types.domains.core.format import BaseGroupAsset as _BaseGroupAsset +from adcp.types.domains.core.format import BaseIndividualAsset as _BaseIndividualAsset def _format_asset_class(asset_type: str, *, group: bool = False) -> type: @@ -1877,7 +1887,7 @@ def _repeatable_asset_group_class() -> type: # OPEN UNION TYPES — forward-compat fallback arms for Format.assets # ============================================================================ # AdCP enums grow additively by design. When a new asset_type arrives before -# the SDK is updated, the closed discriminated union in generated_poc raises a +# the SDK is updated, the closed discriminated union in the generated tree raises a # cascade of ValidationErrors (one per arm per slot) and zeroes out the entire # list_creative_formats catalog. These open union types add an unknown fallback # arm so callers receive unrecognized assets as typed-unknown rather than @@ -2052,7 +2062,7 @@ class UnknownGroupAsset(_BaseGroupAsset): # # Each alias imports the variant directly from its source module, so it resolves # to the correct per-module class regardless of which one wins the bare-name -# slot in _generated.py. These do NOT remove the underlying generated_poc +# slot in _generated.py. These do NOT remove the underlying generated # collisions; what keeps every variant reachable is the derived export set in # the ``domains/`` modules and the reachability guard in the consolidate step. # @@ -2073,208 +2083,205 @@ class UnknownGroupAsset(_BaseGroupAsset): # enums measure different dimensions. ``DurationUnit`` keeps its public # identity when the schema moves the duration vocabulary to a shared enum. # ``adcp.types.DimensionUnit`` is a separate, already-unambiguous enum. -from adcp.types.generated_poc.account.sync_accounts_response import ( +from adcp.types.domains.account.sync_accounts_response import ( Account as SyncAccountsAccount, ) -from adcp.types.generated_poc.account.sync_accounts_response import ( +from adcp.types.domains.account.sync_accounts_response import ( CreditLimit as SyncAccountsCreditLimit, ) -from adcp.types.generated_poc.account.sync_accounts_response import ( +from adcp.types.domains.account.sync_accounts_response import ( Setup as SyncAccountsSetup, ) -from adcp.types.generated_poc.account.sync_governance_request import ( +from adcp.types.domains.account.sync_governance_request import ( Account as SyncGovernanceAccount, ) -from adcp.types.generated_poc.account.sync_governance_request import ( +from adcp.types.domains.account.sync_governance_request import ( Authentication as GovernanceAuthentication, ) -from adcp.types.generated_poc.account.sync_governance_request import ( +from adcp.types.domains.account.sync_governance_request import ( GovernanceAgent as SyncGovernanceGovernanceAgent, ) -from adcp.types.generated_poc.core.account import ( +from adcp.types.domains.core.account import ( Account as CoreAccount, ) -from adcp.types.generated_poc.core.account import ( +from adcp.types.domains.core.account import ( CreditLimit as CoreCreditLimit, ) -from adcp.types.generated_poc.core.account import ( +from adcp.types.domains.core.account import ( GovernanceAgent as CoreGovernanceAgent, ) -from adcp.types.generated_poc.core.account import ( +from adcp.types.domains.core.account import ( Setup as CoreSetup, ) -from adcp.types.generated_poc.core.canonical_proposal import ( +from adcp.types.domains.core.canonical_proposal import ( TotalBudgetGuidance as CanonicalProposalTotalBudgetGuidance, ) try: - from adcp.types.generated_poc.enums.duration_unit import DurationUnit # type: ignore[import-not-found,unused-ignore] + from adcp.types.domains.enums.duration_unit import DurationUnit # type: ignore[import-not-found,unused-ignore] except ModuleNotFoundError as exc: - if exc.name != "adcp.types.generated_poc.enums.duration_unit": + if exc.name != "adcp.types.domains.enums.duration_unit": raise - from adcp.types.generated_poc.core.duration import ( + from adcp.types.domains.core.duration import ( Unit as DurationUnit, ) -from adcp.types.generated_poc.media_buy.change_term_constraints import ( +from adcp.types.domains.media_buy.change_term_constraints import ( MediaBuyChangeTermConstraints1 as BudgetChangeConstraints, ) -from adcp.types.generated_poc.media_buy.change_term_constraints import ( +from adcp.types.domains.media_buy.change_term_constraints import ( MediaBuyChangeTermConstraints2 as FlightChangeConstraints, ) -from adcp.types.generated_poc.media_buy.change_term_constraints import ( +from adcp.types.domains.media_buy.change_term_constraints import ( MediaBuyChangeTermConstraints3 as PackageCountChangeConstraints, ) -from adcp.types.generated_poc.media_buy.change_term_constraints import ( +from adcp.types.domains.media_buy.change_term_constraints import ( MediaBuyChangeTermConstraints4 as EffectiveTimingChangeConstraints, ) -from adcp.types.generated_poc.core.media_buy import ( +from adcp.types.domains.core.media_buy import ( MediaBuy as CoreMediaBuy, ) -from adcp.types.generated_poc.core.notification_config import ( +from adcp.types.domains.core.notification_config import ( Authentication as NotificationAuthentication, ) -from adcp.types.generated_poc.core.overlay import ( +from adcp.types.domains.core.overlay import ( Unit as OverlayUnit, ) -from adcp.types.generated_poc.core.product_filters import ( +from adcp.types.domains.core.product_filters import ( Country as ProductFilterCountry, ) -from adcp.types.generated_poc.core.property import ( +from adcp.types.domains.core.property import ( Identifier as PropertyIdentifier, ) -from adcp.types.generated_poc.core.proposal import ( +from adcp.types.domains.core.proposal import ( TotalBudgetGuidance as LegacyProposalTotalBudgetGuidance, ) -from adcp.types.generated_poc.media_buy.product_discovery_criteria import ( +from adcp.types.domains.media_buy.product_discovery_criteria import ( ProductId as ProductDiscoveryProductId, ) -from adcp.types.generated_poc.media_buy.request_proposals_response import ( +from adcp.types.domains.media_buy.request_proposals_response import ( ProductId as RequestProposalsProductId, ) -from adcp.types.generated_poc.media_buy.refine_proposals_response import ( +from adcp.types.domains.media_buy.refine_proposals_response import ( TotalBudgetGuidance as RefineProposalsTotalBudgetGuidance, ) -from adcp.types.generated_poc.core.provenance import ( +from adcp.types.domains.core.provenance import ( DeclaredBy as ProvenanceDeclaredBy, ) -from adcp.types.generated_poc.core.provenance import ( +from adcp.types.domains.core.provenance import ( Provenance, ) -from adcp.types.generated_poc.core.downstream_connection_requirement import ( +from adcp.types.domains.core.downstream_connection_requirement import ( RequiredForItem as DownstreamConnectionRequiredForItem, ) -from adcp.types.generated_poc.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.protocol.get_adcp_capabilities_response import ( RequiredForItem as RequestSigningRequiredForItem, ) -from adcp.types.generated_poc.core.preview_provider import ( +from adcp.types.domains.core.preview_provider import ( Route as PublisherPreviewRoute, ) -from adcp.types.generated_poc.core.preview_renderer_metadata import ( +from adcp.types.domains.core.preview_renderer_metadata import ( RenderingOrigin as PreviewRenderingOrigin, ) -from adcp.types.generated_poc.core.push_notification_config import ( +from adcp.types.domains.core.push_notification_config import ( Authentication as PushNotificationAuthentication, ) -from adcp.types.generated_poc.core.reference_renderer import ( +from adcp.types.domains.core.reference_renderer import ( Provenance as ReferenceRendererProvenance, ) -from adcp.types.generated_poc.core.real_estate_item import ( +from adcp.types.domains.core.real_estate_item import ( Unit as RealEstateUnit, ) -from adcp.types.generated_poc.core.reporting_consumer_status import ( +from adcp.types.domains.core.reporting_consumer_status import ( ConsumerStatus as ReportingConsumerStatusValue, ) -from adcp.types.generated_poc.core.reporting_consumer_status import ( +from adcp.types.domains.core.reporting_consumer_status import ( FailureCode as ReportingConsumerFailureCode, ) -from adcp.types.generated_poc.core.reporting_consumer_status import ( +from adcp.types.domains.core.reporting_consumer_status import ( MismatchCode as ReportingConsumerMismatchCode, ) -from adcp.types.generated_poc.core.reporting_delivery_capabilities import ( +from adcp.types.domains.core.reporting_delivery_capabilities import ( OperationsContact as ReportingOperationsContact, ) -from adcp.types.generated_poc.core.reporting_delivery_config import ( +from adcp.types.domains.core.reporting_delivery_config import ( AuthoritativeParty as ReportingAuthoritativeParty, ) -from adcp.types.generated_poc.core.reporting_status_issue import ( +from adcp.types.domains.core.reporting_status_issue import ( IssueState as ReportingIssueState, ) -from adcp.types.generated_poc.core.reporting_status_issue import ( +from adcp.types.domains.core.reporting_status_issue import ( RecommendedAction as ReportingIssueRecommendedAction, ) -from adcp.types.generated_poc.core.reporting_webhook import ( +from adcp.types.domains.core.reporting_webhook import ( Authentication as ReportingWebhookAuthentication, ) -from adcp.types.generated_poc.core.tasks_list_request import ( +from adcp.types.domains.core.tasks_list_request import ( Sort as TasksListSort, ) -from adcp.types.generated_poc.core.vehicle_item import ( +from adcp.types.domains.core.vehicle_item import ( Unit as VehicleUnit, ) -from adcp.types.generated_poc.core.wholesale_feed_event import ( +from adcp.types.domains.core.wholesale_feed_event import ( Signal as WholesaleFeedSignal, ) -from adcp.types.generated_poc.media_buy.get_reporting_status_response import ( +from adcp.types.domains.media_buy.get_reporting_status_response import ( ObligationCounts as ReportingObligationCounts, ) -from adcp.types.generated_poc.creative.get_creative_delivery_response import ( +from adcp.types.domains.creative.get_creative_delivery_response import ( Creative as DeliveryCreative, ) -from adcp.types.generated_poc.creative.list_creatives_request import ( +from adcp.types.domains.creative.list_creatives_request import ( Sort as ListCreativesSort, ) -from adcp.types.generated_poc.creative.list_creatives_response import ( +from adcp.types.domains.creative.list_creatives_response import ( Creatives as ListCreativesLegacyCreative, ) -from adcp.types.generated_poc.creative.list_creatives_response import ( +from adcp.types.domains.creative.list_creatives_response import ( Creatives1 as ListCreativesCanonicalCreative, ) -from adcp.types.generated_poc.creative.sync_creatives_response import ( +from adcp.types.domains.creative.sync_creatives_response import ( Creative as SyncCreativesCreative, ) -from adcp.types.generated_poc.media_buy.build_creative_response import ( +from adcp.types.domains.media_buy.build_creative_response import ( Creative as BuildCreativeCreative, ) -from adcp.types.generated_poc.media_buy.create_media_buy_request import ( +from adcp.types.domains.media_buy.create_media_buy_request import ( Authentication as CreateMediaBuyAuthentication, ) -from adcp.types.generated_poc.media_buy.get_media_buys_response import ( +from adcp.types.domains.media_buy.get_media_buys_response import ( MediaBuy as GetMediaBuysMediaBuy, ) -from adcp.types.generated_poc.media_buy.sync_event_sources_response import ( +from adcp.types.domains.media_buy.sync_event_sources_response import ( Setup as SyncEventSourcesSetup, ) -from adcp.types.generated_poc.media_buy.update_media_buy_response import ( - UpdateMediaBuyResponse1 as LegacyUpdateMediaBuySuccessResponse, -) -from adcp.types.generated_poc.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.protocol.get_adcp_capabilities_response import ( Account as CapabilitiesAccount, ) -from adcp.types.generated_poc.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.protocol.get_adcp_capabilities_response import ( Creative as CapabilitiesCreative, ) -from adcp.types.generated_poc.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.protocol.get_adcp_capabilities_response import ( MediaBuy as CapabilitiesMediaBuy, ) -from adcp.types.generated_poc.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.protocol.get_adcp_capabilities_response import ( RenderingOrigin as CapabilitiesPreviewRenderingOrigin, ) -from adcp.types.generated_poc.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.protocol.get_adcp_capabilities_response import ( Route as CapabilitiesPreviewRoute, ) -from adcp.types.generated_poc.protocol.list_tasks_request import ( +from adcp.types.domains.protocol.list_tasks_request import ( Sort as ListTasksSort, ) -from adcp.types.generated_poc.signals.get_signals_response import ( +from adcp.types.domains.signals.get_signals_response import ( Signal as GetSignalsSignal, ) -from adcp.types.generated_poc.sponsored_intelligence.si_sponsored_context import ( +from adcp.types.domains.sponsored_intelligence.si_sponsored_context import ( DeclaredBy as SiSponsoredContextDeclaredBy, ) -from adcp.types.generated_poc.trusted_match.identity_match_response import ( +from adcp.types.domains.trusted_match.identity_match_response import ( TmpxMacro as IdentityMatchTmpxMacro, ) -from adcp.types.generated_poc.trusted_match.provider_registration import ( +from adcp.types.domains.trusted_match.provider_registration import ( TmpxMacro as ProviderRegistrationTmpxMacro, ) @@ -2599,6 +2606,7 @@ class UnknownGroupAsset(_BaseGroupAsset): "UpdateMediaBuySuccessResponse", "UpdateMediaBuyErrorResponse", "LegacyUpdateMediaBuyErrorResponse", + "LegacyUpdateMediaBuySubmittedResponse", "LegacyUpdateMediaBuySuccessResponse", "UpdateMediaBuyResponse3", "UpdateMediaBuySubmittedResponse", diff --git a/src/adcp/types/base.py b/src/adcp/types/base.py index 8cae09211..5683c3da9 100644 --- a/src/adcp/types/base.py +++ b/src/adcp/types/base.py @@ -11,7 +11,7 @@ import os from collections.abc import Callable -from typing import Annotated, Any, Literal +from typing import TYPE_CHECKING, Annotated, Any, Literal, cast from pydantic import ( AfterValidator, @@ -31,6 +31,22 @@ from adcp._deferred_adapters import deferred_adapter from adcp.types._scalar import as_json_schema_integer +if TYPE_CHECKING: + from typing_extensions import Self + + # Annotation-only: resolving these at runtime would import the generated tree, which + # imports this module. ``from __future__ import annotations`` keeps them as strings. + # Taken by their public domain path rather than out of ``_generated``: the domain + # modules mirror the schema tree, so these names do not renumber on a regeneration + # the way the generated module's do. A ``TYPE_CHECKING`` block never executes, so + # the layering rule costs nothing here. + from adcp.types.domains.core.account_ref import AccountReference1, AccountReference2 + from adcp.types.domains.core.canonical_account_ref import CanonicalAccountReference + from adcp.types.domains.core.context import ContextObject + from adcp.types.domains.core.error import Error + from adcp.types.domains.core.push_notification_config import PushNotificationConfig + from adcp.types.domains.enums.task_status import TaskStatus + # Type alias to shorten long type annotations MessageFormatter = Callable[[Any], str] @@ -408,3 +424,208 @@ class RegistryBaseModel(BaseModel): """ model_config = ConfigDict(extra="allow") + + +def _version_envelope() -> type[BaseModel]: + """The generated ``AdcpVersionEnvelope`` class, imported on first use. + + Deferred because every generated module imports this one: a module-level import + of the generated tree here would be a cycle. + """ + from adcp.types.domains.core.version_envelope import AdcpVersionEnvelope + + return AdcpVersionEnvelope + + +def _protocol_envelope() -> type[BaseModel]: + """The generated ``ProtocolEnvelope`` class, imported on first use (see above).""" + from adcp.types.domains.core.protocol_envelope import ProtocolEnvelope + + return ProtocolEnvelope + + +class _AdcpMessage: + """What an AdCP task request and an AdCP task response have in common. + + A MARKER: it declares no fields and is not a ``BaseModel``. A field here would land + on every task message's wire type -- ``adcp_version`` would appear on + ``ValidateInputRequest``, whose schema declares no version fields at all, inventing + a spec field. So the version pins are read through accessors, which answer from the + instance dict and return ``None`` for a tool that declares nothing. + + METHODS, not properties: pydantic does not let a model field shadow a property of + the same name. The value is stored and ``model_dump`` shows it, but attribute access + returns the property -- so every tool that declares a ``context`` would read ``None``, + silently. The production precedent for the whole shape is the Prebid Sales Agent's + ``BuyerRequest`` mixin (``src/core/schemas/_base.py``). + + The three field classifiers answer "which stratum does this field belong to" from + the MRO rather than a per-class table: fields stay flat, exactly as on the wire, and + provenance is which ancestor declared them. A class with no envelope ancestry + answers the empty set -- degraded, but it cannot be wrong, and the answer changes by + itself when the schema starts composing the envelope. + """ + + if TYPE_CHECKING: + # DECLARED, not inherited. Every concrete AdCP message class is a pydantic model + # by construction of the marker-insertion pass, so these members always exist -- + # and declaring them is what makes ``type[AdcpRequest]`` usable in a typed + # registry without a cast. Inheriting ``BaseModel`` to say the same thing would + # make the marker a model in its own right, which is exactly what it must not be. + @classmethod + def model_validate(cls, obj: Any, **kwargs: Any) -> Self: ... + + def model_dump(self, **kwargs: Any) -> dict[str, Any]: ... + + def model_dump_json(self, **kwargs: Any) -> str: ... + + @classmethod + def _stratum_fields(cls, ancestor: type[BaseModel]) -> frozenset[str]: + """The fields this class declares that ``ancestor`` also declares. + + Empty unless this class actually descends from ``ancestor``: a schema that + inlines ``adcp_version`` rather than composing the envelope shares the name but + not the stratum, and the intersection alone would read it as composed. + """ + if not issubclass(cls, ancestor): + return frozenset() + own = cast("type[BaseModel]", cls) + return frozenset(own.model_fields) & frozenset(ancestor.model_fields) + + @classmethod + def version_fields(cls) -> frozenset[str]: + """The version-envelope fields on this message (empty without the ancestry).""" + return cls._stratum_fields(_version_envelope()) + + @classmethod + def protocol_fields(cls) -> frozenset[str]: + """The protocol-envelope fields on this message (empty without the ancestry). + + A ``status`` an arm narrows to a ``Literal`` is still the envelope's ``status``, + and a body-level ``context`` is still the envelope's ``context`` -- the spec says + the envelope declaration is authoritative -- so matching by name is the tie-break, + not an approximation of one. ``errors[]`` is a different field from ``adcp_error`` + by design and stays payload. + """ + return cls._stratum_fields(_protocol_envelope()) + + @classmethod + def payload_fields(cls) -> frozenset[str]: + """The tool's own fields: everything neither envelope declared. + + Excludes the version pins, which ARE payload on the wire ("this envelope is part + of the payload itself"), because a caller composing a DataPart or an audit record + wants tool arguments only. A caller who wants both unions the two sets. + """ + own = cast("type[BaseModel]", cls) + return frozenset(own.model_fields) - cls.version_fields() - cls.protocol_fields() + + +class AdcpRequest(_AdcpMessage): + """The request message of a task in the pinned bundle's task registry. + + A consumer holding one can resolve its account, decide at-most-once, echo its + context and negotiate version -- the whole transport-boundary job -- before knowing + which tool it is. ``issubclass(model, AdcpRequest)`` is the registration-time proof + that a model is spec-derived rather than a hand-written parallel: a field test passes + for a forged model, descent does not. + + Each accessor returns the field's value, or ``None`` when this tool's schema declares + no such field. Only 49 of the 87 request schemas declare an ``account`` and only 43 an + ``idempotency_key``, so asking the request is what replaces + ``getattr(req, "account", None)`` against ``Any`` at the boundary. + """ + + def get_account( + self, + ) -> AccountReference1 | AccountReference2 | CanonicalAccountReference | None: + """The account this request names, or None when its schema declares none. + + The union is what the generated ``account`` fields actually hold, measured: 44 + request classes type it as the two ``core/account-ref.json`` arms directly (the + ``AccountReference`` RootModel is unwrapped at the field by + ``expose_account_reference_union_fields``, so naming the wrapper here would be a + type no value ever has), and 8 as the ``CanonicalAccountReference`` root. The one + inline declarer, ``compliance/comply-test-controller-request.json``, answers with + its own generated ``Account`` through the same instance-dict read. + """ + return self.__dict__.get("account") + + def get_idempotency_key(self) -> str | None: + """The at-most-once key this request carries, or None when its schema declares none. + + Presence is the honest structural signal for write-versus-read: a read is + idempotent by construction and takes no key. ``x-mutates-state`` is not a + substitute -- it covers 44 of 87 schemas and contradicts the key in both + directions on 2 schemas each way. + """ + return self.__dict__.get("idempotency_key") + + def get_context(self) -> ContextObject | None: + """The buyer's opaque ``context``, echoed unchanged onto whatever leaves.""" + return self.__dict__.get("context") + + def get_push_notification_config(self) -> PushNotificationConfig | None: + """The webhook configuration this request asks for, or None (19 of 87 declare it).""" + return self.__dict__.get("push_notification_config") + + def get_adcp_version(self) -> str | None: + """The release this buyer pins, or None when it pinned none. + + Answers from the instance dict, so it still answers for a schema that inlines the + field without composing the envelope -- the one case ``version_fields()`` reports + empty. + """ + return self.__dict__.get("adcp_version") + + def get_adcp_major_version(self) -> int | None: + """The major this buyer pins (deprecated through 3.x), or None.""" + return self.__dict__.get("adcp_major_version") + + +class AdcpResponse(_AdcpMessage): + """The response message of a task in the pinned bundle's task registry. + + A consumer holding one can route on task state, pick up an async ``task_id``, split + envelope from payload and log uniformly, before knowing which tool answered. Which + makes one generic poll-to-terminal loop possible for all 77 tasks, where today each + arm has no common type at all. + """ + + def get_status(self) -> TaskStatus | None: + """The AdCP task state the seller asserted, or None. + + None only for the 10 task responses whose schemas do not compose + ``core/protocol-envelope.json``, against the envelope's own "REQUIRED on every + task response envelope" -- a residue this SDK cannot invent its way out of. + """ + return self.__dict__.get("status") + + def get_task_id(self) -> str | None: + """The async operation identifier, present when the task needs polling.""" + return self.__dict__.get("task_id") + + def get_context(self) -> ContextObject | None: + """The caller's ``context``, echoed back byte-for-byte.""" + return self.__dict__.get("context") + + def get_adcp_error(self) -> Error | None: + """The envelope-level typed error for a fatal task failure, or None. + + The payload's ``errors[]`` array is a different field by design and is read from + the concrete arm; the two MUST be treated as distinct by name. + """ + return self.__dict__.get("adcp_error") + + def get_message(self) -> str | None: + """The human-readable summary of the result, or None.""" + return self.__dict__.get("message") + + def get_replayed(self) -> bool | None: + """True when this answer came from the idempotency cache. + + ``False`` when the response declares the field and was executed fresh, ``None`` + when the tool's schema does not declare it -- absence stays distinguishable from + a negative answer. + """ + return self.__dict__.get("replayed") diff --git a/src/adcp/types/canonical_creative.py b/src/adcp/types/canonical_creative.py index 1716cd9ef..4a6c51d38 100644 --- a/src/adcp/types/canonical_creative.py +++ b/src/adcp/types/canonical_creative.py @@ -5,6 +5,61 @@ are exposed from :mod:`adcp.types.legacy`. This module provides the primary application-facing models: legacy identity is absent from their declared fields, JSON Schema, and serialized output at every nesting depth. + +``format_kind`` is a ``str``, not the generated enum — a deliberate override +------------------------------------------------------------------------- + +``core/canonical-format-kind.json`` declares a **closed** 16-member ``enum`` +and, in the same file, states as normative: + + Consumer SDKs MUST treat this enum as **open** at parse time: an unknown + ``format_kind`` value MUST be retained as-is on the in-memory object (not + silently dropped or rewritten to ``"custom"``) and MUST NOT cause the + surrounding payload to fail validation. ... The producer-side enum stays + closed (sellers MUST NOT mint ad-hoc ``format_kind`` values ...); the + consumer-side enum stays open for forward compatibility. + +**The schema already knows the rule is directional, and then encodes it as a +single closed enum, which cannot carry that.** So this override implements what +the schema says rather than contradicting it: every reference to that schema +generates a bare ``str`` — ``OPEN_VOCABULARY_SCHEMAS`` in +``scripts/generate_types.py``, one schema-level transform rather than a +widening at each call site. + +**The SDK refuses nothing, in either direction, and that is deliberate.** A +seller supports some set of format kinds; that set is the seller's, not this +library's and not the pinned enum's. It can be larger — the seller handles a +kind promoted in a spec newer than the pin — or smaller, four of the sixteen. +A pinned SDK cannot tell "a kind the seller invented" from "a kind defined +after my pin": both are simply "not in my 16". Refusing the second to prevent +the first would make this SDK's version a ceiling on what the protocol permits, +which is the same defect as the closed enum with the enforcement moved into a +validator. The producer-side MUST is a seller's obligation; this library gives +it the vocabulary and :func:`is_canonical_format_kind` to meet it, and leaves +the decision where the knowledge is. + +So the vocabulary is not discarded, it is **relocated**: +:class:`CanonicalFormatKind` stays a first-class export, used for comparison +(``creative.format_kind == CanonicalFormatKind.image`` — it is a ``StrEnum``, +so that holds against a plain string field and no adopter writes a literal) and +for membership through :func:`is_canonical_format_kind`, whose vocabulary is a +parameter. What changed is only that the field no longer refuses the +seventeenth value a newer server sends. That is the registry pattern the +sibling fields already use: ``format_shape`` and ``asset_group_id`` are plain +strings governed by versioned registries whose own governance says +non-canonical values stay valid and validators may warn. + +The one cost is that the generated models no longer agree with the bundled +schema for this one field. That is declared, by name and with this reason, as a +single entry in ``tests/conformance/_schema_parity.py``'s ``DECLARED`` — +``format_kind_is_an_open_vocabulary_by_decision``. The parity rule itself is +not relaxed. + +Upstream ask: `adcontextprotocol/adcp#7929 +`_. If it lands — the +schema stating the directional rule in a form that can carry it, the way +``format_shape`` is governed — delete the transform, delete this section, and +the generated models go back to agreeing with their schema. """ from __future__ import annotations @@ -12,9 +67,8 @@ import copy import json import re -from collections.abc import Collection, Sequence -from enum import Enum -from typing import Annotated, Any, ClassVar, Literal, TypeVar +from collections.abc import Callable, Iterable, Sequence +from typing import TYPE_CHECKING, Annotated, Any, ClassVar, Protocol, TypeVar, cast from pydantic import ( ConfigDict, @@ -23,92 +77,91 @@ PrivateAttr, SerializerFunctionWrapHandler, WithJsonSchema, - create_model, field_validator, model_serializer, model_validator, ) +from pydantic.json_schema import GenerateJsonSchema from pydantic_core import CoreSchema -from adcp.types._str_enum import StrEnum from adcp.types.base import AdCPBaseModel -from adcp.types.generated_poc.core.canonical_format_kind import CanonicalFormatKind -from adcp.types.generated_poc.core.creative_asset import CreativeAsset as _CanonicalCreativeWire -from adcp.types.generated_poc.core.creative_filters import CreativeFilters as _LegacyCreativeFilters -from adcp.types.generated_poc.core.creative_manifest import ( +from adcp.types.domains.core.canonical_format_kind import CanonicalFormatKind +from adcp.types.domains.core.creative_asset import CreativeAsset as _CanonicalCreativeWire +from adcp.types.domains.core.creative_filters import CreativeFilters as _LegacyCreativeFilters +from adcp.types.domains.core.creative_manifest import ( CreativeManifest as _CanonicalCreativeManifestWire, ) -from adcp.types.generated_poc.core.creative_variant import CreativeVariant as _LegacyCreativeVariant -from adcp.types.generated_poc.core.package import Package as _LegacyPackage -from adcp.types.generated_poc.core.placement import Placement as _LegacyPlacement -from adcp.types.generated_poc.core.platform_extension_ref import PlatformExtensionReference -from adcp.types.generated_poc.core.pricing_option import PricingOption as _LegacyPricingOption -from adcp.types.generated_poc.core.product import Product as _LegacyProduct -from adcp.types.generated_poc.core.product_filters import ProductFilters as _LegacyProductFilters -from adcp.types.generated_poc.core.product_format_declaration import SellerPreference -from adcp.types.generated_poc.creative.get_creative_delivery_response import ( +from adcp.types.domains.core.creative_variant import CreativeVariant as _LegacyCreativeVariant +from adcp.types.domains.core.package import Package as _LegacyPackage +from adcp.types.domains.core.placement import Placement as _LegacyPlacement +from adcp.types.domains.core.platform_extension_ref import PlatformExtensionReference +from adcp.types.domains.core.pricing_option import PricingOption as _LegacyPricingOption +from adcp.types.domains.core.product import Product as _LegacyProduct +from adcp.types.domains.core.product_filters import ProductFilters as _LegacyProductFilters +from adcp.types.domains.core.product_format_declaration import SellerPreference +from adcp.types.domains.creative.get_creative_delivery_response import ( Creative as _LegacyDeliveryCreative, ) -from adcp.types.generated_poc.creative.get_creative_delivery_response import ( +from adcp.types.domains.creative.get_creative_delivery_response import ( GetCreativeDeliveryResponse as _LegacyGetCreativeDeliveryResponse, ) -from adcp.types.generated_poc.creative.list_creatives_request import ( +from adcp.types.domains.creative.list_creatives_request import ( ListCreativesRequest as _LegacyListCreativesRequest, ) -from adcp.types.generated_poc.creative.list_creatives_response import ( +from adcp.types.domains.creative.list_creatives_response import ( Creatives1 as _CanonicalListedCreative, ) -from adcp.types.generated_poc.creative.list_creatives_response import ( +from adcp.types.domains.creative.list_creatives_response import ( ListCreativesResponse as _LegacyListCreativesResponse, ) -from adcp.types.generated_poc.creative.sync_creatives_request import ( +from adcp.types.domains.creative.sync_creatives_request import ( SyncCreativesRequest as _LegacySyncCreativesRequest, ) -from adcp.types.generated_poc.enums.channels import MediaChannel -from adcp.types.generated_poc.media_buy.create_media_buy_request import ( +from adcp.types.domains.enums.channels import MediaChannel +from adcp.types.domains.media_buy.create_media_buy_request import ( CreateMediaBuyRequest as _LegacyCreateMediaBuyRequest, ) -from adcp.types.generated_poc.media_buy.create_media_buy_response import ( +from adcp.types.domains.media_buy.create_media_buy_response import ( CreateMediaBuyResponse1 as _LegacyCreateMediaBuyResponse1, ) -from adcp.types.generated_poc.media_buy.create_media_buy_response import ( +from adcp.types.domains.media_buy.create_media_buy_response import ( CreateMediaBuyResponse2 as _LegacyCreateMediaBuyResponse2, ) -from adcp.types.generated_poc.media_buy.create_media_buy_response import ( +from adcp.types.domains.media_buy.create_media_buy_response import ( CreateMediaBuyResponse3 as _LegacyCreateMediaBuyResponse3, ) -from adcp.types.generated_poc.media_buy.get_media_buy_delivery_response import ( +from adcp.types.domains.media_buy.get_media_buy_delivery_response import ( GetMediaBuyDeliveryResponse as _LegacyGetMediaBuyDeliveryResponse, ) -from adcp.types.generated_poc.media_buy.get_media_buys_response import ( +from adcp.types.domains.media_buy.get_media_buys_response import ( GetMediaBuysResponse as _LegacyGetMediaBuysResponse, ) -from adcp.types.generated_poc.media_buy.get_media_buys_response import ( +from adcp.types.domains.media_buy.get_media_buys_response import ( MediaBuy as _LegacyMediaBuy, ) -from adcp.types.generated_poc.media_buy.get_media_buys_response import ( +from adcp.types.domains.media_buy.get_media_buys_response import ( Package as _LegacyMediaBuyPackage, ) -from adcp.types.generated_poc.media_buy.get_products_request import ( +from adcp.types.domains.media_buy.get_products_request import ( GetProductsRequest as _LegacyGetProductsRequest, ) -from adcp.types.generated_poc.media_buy.get_products_response import ( +from adcp.types.domains.media_buy.get_products_response import ( GetProductsResponse as _LegacyGetProductsResponse, ) -from adcp.types.generated_poc.media_buy.package_request import ( +from adcp.types.domains.media_buy.package_request import ( PackageRequest as _LegacyPackageRequest, ) -from adcp.types.generated_poc.media_buy.package_update import PackageUpdate as _LegacyPackageUpdate -from adcp.types.generated_poc.media_buy.update_media_buy_request import ( +from adcp.types.domains.media_buy.package_update import PackageUpdate as _LegacyPackageUpdate +from adcp.types.domains.media_buy.update_media_buy_request import ( UpdateMediaBuyRequest as _LegacyUpdateMediaBuyRequest, ) -from adcp.types.generated_poc.media_buy.update_media_buy_response import ( +from adcp.types.domains.media_buy.update_media_buy_response import ( UpdateMediaBuyResponse1 as _LegacyUpdateMediaBuyResponse1, ) -from adcp.types.generated_poc.media_buy.update_media_buy_response import ( +from adcp.types.domains.media_buy.update_media_buy_response import ( UpdateMediaBuyResponse2 as _LegacyUpdateMediaBuyResponse2, ) -from adcp.types.generated_poc.media_buy.update_media_buy_response import ( +from adcp.types.domains.media_buy.update_media_buy_response import ( UpdateMediaBuyResponse3 as _LegacyUpdateMediaBuyResponse3, ) from adcp.types.legacy import LegacyFormatId @@ -116,11 +169,47 @@ MEDIA_BUY_LEGACY_STATUS_VALUES, unwrap_enum_value, ) +from adcp.types.variants import SchemaVariant + + +def is_canonical_format_kind( + value: object, + vocabulary: Iterable[str] = CanonicalFormatKind, +) -> bool: + """Is *value* one of *vocabulary*'s format kinds? + + The vocabulary defaults to :class:`CanonicalFormatKind`, the sixteen kinds + the pinned AdCP bundle declares — but it is a PARAMETER, because the set + that matters is the seller's, not this SDK's. A seller may support a kind + promoted in a spec newer than the pin, or only four of the sixteen, and + neither is expressible by anything this library knows. + + **The SDK never calls this for you.** ``format_kind`` is a ``str`` + everywhere, on the way out and on the way back, and no model refuses a + value. That is deliberate: a pinned library cannot tell "a kind the seller + invented" from "a kind defined after my pin", so refusing the second to + prevent the first would make this SDK's version a ceiling on what the + protocol permits. "I accept the request and then tell you I cannot process + this creative" is a seller's answer, not a type error. + + This function is the sanctioned way to be strict, where the caller knows + which spec version its counterpart speaks:: + + from adcp.types import is_canonical_format_kind + + if not is_canonical_format_kind(creative.format_kind): + route_as_declared_but_unsupported(creative) + + if not is_canonical_format_kind(manifest.format_kind, MY_SUPPORTED_KINDS): + reject_with_unsupported_format(manifest) + + Comparison needs no helper: ``CanonicalFormatKind`` is a ``StrEnum``, so + ``creative.format_kind == CanonicalFormatKind.image`` holds against a plain + string field and an adopter never writes a literal. + """ + + return isinstance(value, str) and any(value == kind for kind in vocabulary) -_OpenCanonicalFormatKind = Annotated[ - CanonicalFormatKind | str, - Field(union_mode="left_to_right"), -] _LEGACY_IDENTITY_KEY = re.compile(r"(^|_)(?:format_ids?|v1_format_ref)($|_)") _CREDENTIAL_SHAPED_KEY_SUFFIXES = ( @@ -134,6 +223,23 @@ "bearer", ) +if TYPE_CHECKING: + from adcp.types.domains.core.format_id import FormatReferenceStructuredObject + + # The three shapes the generated parents give legacy creative identity. + # Each canonical model that inherits one of those fields redeclares it in a + # ``TYPE_CHECKING`` block, so a type checker's synthesized ``__init__`` + # agrees with the runtime: ``__pydantic_init_subclass__`` removes the field + # and ``_reject_legacy_creative_identity`` refuses the key, and ``init=False`` + # is what makes the keyword unavailable statically as well. The annotations + # mirror the generated declaration exactly — a drift in either direction + # makes the redeclaration an incompatible override, which mypy and pyright + # refuse by name. ``test_canonical_removed_fields.py`` pins that every + # removed field has such a redeclaration. + _RemovedFormatId = FormatReferenceStructuredObject | None + _RemovedFormatIds = list[FormatReferenceStructuredObject] | None + _RemovedFormatIdSequence = Sequence[FormatReferenceStructuredObject] | None + def _walk_for_credential_keys(value: Any, *, path: str = "") -> str | None: """Return the first credential-shaped key path under ``value``.""" @@ -158,6 +264,17 @@ def _walk_for_credential_keys(value: Any, *, path: str = "") -> str | None: return None +class _JsonSchemaHandlerWithGenerator(Protocol): + """The concrete handler pydantic passes in carries the active generator. + + ``GetJsonSchemaHandler`` is the documented protocol and does not declare + ``generate_json_schema``; the object pydantic actually supplies does, and + reaching it is how a nested definition registry gets sanitized. + """ + + generate_json_schema: GenerateJsonSchema + + def is_legacy_creative_identity_key(key: object) -> bool: """Return whether *key* names legacy creative routing identity.""" @@ -316,20 +433,80 @@ def _sanitize_schema_node(value: Any) -> Any: def sanitize_canonical_schema(schema: dict[str, Any]) -> dict[str, Any]: """Return a defensive deep copy with legacy identity removed.""" - return _sanitize_schema_node(copy.deepcopy(schema)) + sanitized: dict[str, Any] = _sanitize_schema_node(copy.deepcopy(schema)) + return sanitized + + +def _serialize_canonical_model( + self: CanonicalBoundaryModel, + handler: SerializerFunctionWrapHandler, +) -> Any: + """Enforce the boundary for nested and TypeAdapter serialization too.""" + + return strip_legacy_creative_identity( + handler(self), + _format_scope=self.__class__.__name__ == "Format", + ) class CanonicalBoundaryModel(AdCPBaseModel): - """Base class enforcing the primary canonical runtime boundary.""" + """Base class enforcing the primary canonical runtime boundary. + + Every canonical model is a real subclass of the generated wire model it + refines, so each one inherits the generated model's fields, its injected + ``model_validator``s, and its envelope ancestry. Two concerns that used to + be re-attached per class by ``create_model`` are therefore declared once, + here, and inherited: + + * ``_serialize_canonical`` — the wrap serializer that strips legacy creative + identity from nested and ``TypeAdapter`` serialization. + * ``__pydantic_init_subclass__`` — the one declared field removal. Legacy + creative identity must be absent from a canonical model's *declared* + fields, which is the single thing inheritance alone cannot express; the + rule reads the same :func:`is_legacy_creative_identity_key` predicate that + governs the input validator, the schema sanitizer and the serializer, so + there is one strip predicate for all four. + + ``CanonicalBoundaryModel`` is listed LAST among a canonical model's bases. + Pydantic merges ``model_config`` across bases left to right, so the + right-most base wins; the generated wire model inherits + :class:`AdCPBaseModel`'s ``extra`` policy and would otherwise override this + class's ``extra="allow"`` and start dropping caller-supplied extension keys. + """ model_config = ConfigDict(extra="allow", defer_build=True) __adcp_canonical_creative_model__: ClassVar[bool] = True - #: The generated class a canonical clone stands in for; ``None`` on a model - #: declared directly. Set by :func:`_canonical_clone`. - __adcp_canonical_source__: ClassVar[type[AdCPBaseModel] | None] = None - #: Source validators a clone left behind, each with the dropped field names - #: it touches. Empty on a model declared directly. - __adcp_canonical_validators_left_behind__: ClassVar[dict[str, set[str]]] = {} + + _serialize_canonical = model_serializer(mode="wrap")(_serialize_canonical_model) + + @classmethod + def __pydantic_init_subclass__(cls, **kwargs: Any) -> None: + """Remove inherited legacy creative identity from the declared fields. + + Each removed name is then bound as a plain class attribute holding + ``None`` — which is the removal's own truth, and which the inherited + validators need. A generated model can carry an injected validator that + READS the removed field: ``list-creatives-response.json``'s + ``_validate_format_reference_xor`` evaluates + ``(self.format_id is None) == (self.format_kind is None)``. Inheritance + is the point of this layer, so that validator now runs on the canonical + model, and with the field merely deleted it raised ``AttributeError``. + With the name reading ``None`` the XOR reduces to exactly the invariant + the canonical model should hold — ``format_kind`` must be set — which is + the same thing the canonical declaration states by making it required. + + Binding happens AFTER class creation, so pydantic never considers the + name a field candidate; ``model_fields``, the JSON schema and the wire + are all unaffected. Measured scope: one such validator, on one model. + """ + + removed = [name for name in cls.model_fields if is_legacy_creative_identity_key(name)] + for name in removed: + del cls.model_fields[name] + cls.__annotations__.pop(name, None) + setattr(cls, name, None) + if removed: + cls.model_rebuild(force=True) @model_validator(mode="before") @classmethod @@ -347,10 +524,11 @@ def _reject_legacy_creative_identity(cls, value: Any) -> Any: def model_dump(self, **kwargs: Any) -> dict[str, Any]: kwargs.setdefault("serialize_as_any", False) - return strip_legacy_creative_identity( + stripped: dict[str, Any] = strip_legacy_creative_identity( super().model_dump(**kwargs), _format_scope=self.__class__.__name__ == "Format", ) + return stripped def model_dump_json(self, **kwargs: Any) -> str: kwargs.setdefault("serialize_as_any", False) @@ -384,182 +562,24 @@ def __get_pydantic_json_schema__( # node. Mutate the active generator's definition registry as well so # unreachable generated legacy definitions cannot leak into the final # recursive schema document. - generator = handler.generate_json_schema + generator = cast(_JsonSchemaHandlerWithGenerator, handler).generate_json_schema for key, definition in list(generator.definitions.items()): generator.definitions[key] = sanitize_canonical_schema(definition) return schema -def _field_definitions( - source: type[AdCPBaseModel], - *, - exclude: frozenset[str] = frozenset(), - overrides: dict[str, tuple[Any, Any]] | None = None, -) -> dict[str, tuple[Any, Any]]: - fields: dict[str, tuple[Any, Any]] = {} - for name, info in source.model_fields.items(): - if name in exclude or is_legacy_creative_identity_key(name): - continue - fields[name] = (info.annotation, copy.deepcopy(info)) - fields.update(overrides or {}) - return fields - - -def _serialize_canonical_model( - self: CanonicalBoundaryModel, - handler: SerializerFunctionWrapHandler, -) -> Any: - """Enforce the boundary for nested and TypeAdapter serialization too.""" - - return strip_legacy_creative_identity( - handler(self), - _format_scope=self.__class__.__name__ == "Format", - ) - +def _inherit(source: type[AdCPBaseModel], name: str) -> Any: + """Return a copy of ``source``'s ``name`` field for a retyped redeclaration. -def _canonical_clone_bases(source: type[AdCPBaseModel]) -> tuple[type[AdCPBaseModel], ...]: - """Return the clone bases for ``source``: what it composes, then the boundary. - - A canonical clone copies the composed *fields*, but a clone built on - ``CanonicalBoundaryModel`` alone would drop the composed *ancestry* — so - ``issubclass(GetProductsResponse, ProtocolEnvelope)`` would be ``False`` - even though the response carries ``status``/``replayed``/``task_id``. The - bases ``source`` already has are what its schema composes at its root, so - carrying them over keeps the canonical surface's ancestry identical to the - generated surface it replaces. Reading them off ``source`` rather than - naming the envelopes is what makes that true for every composed base: - naming them covered ``AdcpVersionEnvelope`` and ``ProtocolEnvelope`` and - silently dropped ``DeliveryMetrics`` from ``CreativeVariant`` and - ``IndicatorBearingResourceState`` from ``MediaBuy``. - - ``CanonicalBoundaryModel`` comes last on purpose. Pydantic merges - ``model_config`` across bases left to right, so the right-most base wins; - a composed base inherits :class:`AdCPBaseModel`'s ``extra`` policy and - would otherwise override the boundary's ``extra="allow"`` and start - dropping caller-supplied extension keys. Method resolution is unaffected — - the composed bases override nothing, so ``CanonicalBoundaryModel`` still - supplies ``model_dump``/``model_json_schema`` ahead of - :class:`AdCPBaseModel`. - - ``source`` is sometimes a clone itself — ``_DeliveryCreativeVariantBase`` - and the guard fixtures in ``tests/test_code_generation.py`` clone one — so - the two bases this function supplies are dropped before it supplies them - again. Without that, re-cloning a clone is ``TypeError: duplicate base - class CanonicalBoundaryModel``. + A canonical model that narrows an inherited field's *annotation* still wants + the generated field's constraints, description and default. Redeclaring with + this as the assigned value keeps every one of them, where a bare ``Field()`` + would silently drop ``min_length`` and friends. The return type is ``Any`` + because a ``FieldInfo`` is what pydantic expects on the right-hand side of an + annotated field declaration — the same reason ``Field()`` itself is ``Any``. """ - supplied = (AdCPBaseModel, CanonicalBoundaryModel) - composed = tuple(base for base in source.__bases__ if base not in supplied) - return (*composed, CanonicalBoundaryModel) - - -def _validator_references(function: Any) -> set[str]: - """Names a validator's code touches: attributes read and string constants. - - A generated validator reaches a field as ``self.`` (``co_names``) or - names it in a literal — the root required-group validator carries its - groups as a tuple of field-name strings. Nested code objects (comprehensions) - are walked too. - """ - code = getattr(function, "__func__", function).__code__ - names: set[str] = set() - pending = [code] - while pending: - current = pending.pop() - names.update(current.co_names) - constants: list[Any] = list(current.co_consts) - while constants: - constant = constants.pop() - if isinstance(constant, str): - names.add(constant) - elif isinstance(constant, (tuple, frozenset)): - constants.extend(constant) - elif hasattr(constant, "co_code"): - pending.append(constant) - return names - - -def _own_validators( - source: type[AdCPBaseModel], fields: Collection[str] -) -> tuple[dict[str, Any], dict[str, set[str]]]: - """Return the validators ``source`` declares on itself, re-decorated for a clone. - - A canonical clone is built with ``create_model`` over ``source``'s *bases* - (:func:`_canonical_clone_bases`) and a copy of its *fields* - (:func:`_field_definitions`), so ``source`` itself is not in the clone's - MRO and nothing declared on its class body crosses over: the validators - the generator writes onto a request class — the root-level required - groups of ``create-media-buy-request.json`` (#1361), the uniqueness checks - on reporting selectors, the publisher-property coercion on ``Product`` — - would silently stop applying to the canonical name. Inherited validators do - cross, through the bases, so only the ones ``source`` declares on its own - body are carried here. - - A validator that touches a field the clone does not carry is left behind, - and the second mapping names which field for each: the clone drops the - legacy creative identity fields on purpose, so a rule written against - ``format_id`` — the ``format_id | format_kind`` root group on a manifest, - the format-reference XOR on a listed creative — has nothing to enforce on - the canonical boundary and would raise ``AttributeError`` on the read. - - The class namespace, not ``Decorator.func``, is what gets re-decorated: - pydantic stores a ``before`` validator's ``classmethod`` there, and the - unwrapped ``func`` has already lost its ``cls`` binding. - """ - own = vars(source) - decorators = source.__pydantic_decorators__ - dropped = set(source.model_fields) - set(fields) - carried: dict[str, Any] = {} - left_behind: dict[str, set[str]] = {} - for attribute, decorator in decorators.model_validators.items(): - if attribute not in own: - continue - touched = _validator_references(own[attribute]) & dropped - if touched: - left_behind[attribute] = touched - continue - carried[attribute] = model_validator(mode=decorator.info.mode)(own[attribute]) - for attribute, decorator in decorators.field_validators.items(): - if attribute not in own: - continue - info = decorator.info - touched = (set(info.fields) | _validator_references(own[attribute])) & dropped - if touched: - left_behind[attribute] = touched - continue - carried[attribute] = field_validator( - *info.fields, - mode=info.mode, - check_fields=info.check_fields, - )(own[attribute]) - return carried, left_behind - - -def _canonical_clone( - name: str, - source: type[AdCPBaseModel], - *, - exclude: frozenset[str] = frozenset(), - overrides: dict[str, tuple[Any, Any]] | None = None, -) -> type[CanonicalBoundaryModel]: - fields = _field_definitions(source, exclude=exclude, overrides=overrides) - carried, left_behind = _own_validators(source, fields) - model = create_model( # type: ignore[call-overload] - name, - __base__=_canonical_clone_bases(source), - __module__=__name__, - __validators__={ - **carried, - "_serialize_canonical": model_serializer(mode="wrap")(_serialize_canonical_model), - }, - **fields, - ) - # The generated class a clone stands in for, and the validators it could - # not carry. ``tests/test_canonical_validator_parity.py`` reads both to - # assert the clone refuses every document its source refuses. - model.__adcp_canonical_source__ = source - model.__adcp_canonical_validators_left_behind__ = left_behind - return model + return copy.deepcopy(source.model_fields[name]) _CanonicalParamsT = TypeVar("_CanonicalParamsT", bound=AdCPBaseModel) @@ -592,13 +612,11 @@ class Format(CanonicalBoundaryModel): experimental: bool | None = None format_shape: str | None = None format_schema: PlatformExtensionReference | None = None - format_kind: CanonicalFormatKind + format_kind: str params: dict[str, Any] _legacy_format_refs: list[LegacyFormatId] = PrivateAttr(default_factory=list) - _serialize_canonical = model_serializer(mode="wrap")(_serialize_canonical_model) - @model_validator(mode="before") @classmethod def _reject_legacy_conflicts_and_credentials(cls, data: Any) -> Any: @@ -652,7 +670,7 @@ def params_as(self, canonical_type: type[_CanonicalParamsT]) -> _CanonicalParams @model_validator(mode="after") def _validate_custom_shape(self) -> Format: - if self.format_kind is CanonicalFormatKind.custom: + if self.format_kind == CanonicalFormatKind.custom.value: if not self.format_shape: raise ValueError("custom formats require format_shape") if self.format_schema is None: @@ -665,49 +683,59 @@ def _validate_custom_shape(self) -> Format: ProductFormatDeclaration = Format -Placement = _canonical_clone( - "Placement", - _LegacyPlacement, - overrides={ - "format_options": ( - list[Format] | None, - Field(default=None, min_length=1), - ) - }, -) +class Placement(_LegacyPlacement, CanonicalBoundaryModel): + """Canonical placement; ``format_options`` are canonical declarations.""" -Product = _canonical_clone( - "Product", - _LegacyProduct, - overrides={ - "format_options": ( - list[Format], - Field(min_length=1, description="Canonical creative formats accepted by this product."), - ), - "placements": (list[Placement] | None, Field(default=None, min_length=1)), - "pricing_options": (list[CanonicalPricingOption], Field(min_length=1)), - }, -) + if TYPE_CHECKING: # the removed field, hidden from the constructor too + format_ids: _RemovedFormatIdSequence = Field(default=None, init=False) -CreativeAsset = _canonical_clone( - "CreativeAsset", - _CanonicalCreativeWire, - overrides={"format_kind": (CanonicalFormatKind, Field())}, -) + format_options: SchemaVariant[list[Format] | None] = Field(default=None, min_length=1) -Creative = _canonical_clone( - "Creative", - _CanonicalListedCreative, - overrides={"format_kind": (CanonicalFormatKind, Field())}, -) -_CreativeManifestBase = _canonical_clone( - "_CreativeManifestBase", - _CanonicalCreativeManifestWire, -) +class Product(_LegacyProduct, CanonicalBoundaryModel): + """Canonical product; formats, placements and pricing are canonical.""" + + if TYPE_CHECKING: # the removed field, hidden from the constructor too + format_ids: _RemovedFormatIds = Field(default=None, init=False) + format_options: SchemaVariant[list[Format]] = Field( + min_length=1, description="Canonical creative formats accepted by this product." + ) + placements: SchemaVariant[list[Placement] | None] = Field(default=None, min_length=1) + pricing_options: list[CanonicalPricingOption] = Field(min_length=1) + + +class CreativeAsset(_CanonicalCreativeWire, CanonicalBoundaryModel): + """Canonical creative asset; the format kind is required, not optional. + + The kind is narrowed to required and nothing else: it stays ``str`` and the + model refuses no value. A buyer SENDS a creative asset, and the + producer-side "sellers MUST NOT mint ad-hoc kinds" rule is the sender's + obligation, not something a pinned library can tell from a kind defined + after its pin — :func:`is_canonical_format_kind` is how a caller meets it. + """ -class CreativeManifest(_CreativeManifestBase): + if TYPE_CHECKING: # the removed field, hidden from the constructor too + format_id: _RemovedFormatId = Field(default=None, init=False) + + format_kind: str + + +class Creative(_CanonicalListedCreative, CanonicalBoundaryModel): + """Canonical listed creative; the format kind is required, not optional. + + A listed creative is a row a seller RETURNS, so the kind is required but + never confined: a kind a newer seller emits is retained as-is, which is + what ``core/canonical-format-kind.json`` requires of a consumer. + """ + + if TYPE_CHECKING: # the removed field, hidden from the constructor too + format_id: _RemovedFormatId = Field(default=None, init=False) + + format_kind: str + + +class CreativeManifest(_CanonicalCreativeManifestWire, CanonicalBoundaryModel): """Canonical manifest accepting the SDK's public standalone asset models. The 3.2 aggregate asset-union schema currently generates structurally @@ -717,6 +745,9 @@ class CreativeManifest(_CreativeManifestBase): relaxing the on-wire discriminator checks. """ + if TYPE_CHECKING: # the removed field, hidden from the constructor too + format_id: _RemovedFormatId = Field(default=None, init=False) + @model_validator(mode="before") @classmethod def _normalize_standalone_assets(cls, data: Any) -> Any: @@ -736,50 +767,66 @@ def wire_value(value: Any) -> Any: } -CreativeVariant = _canonical_clone( - "CreativeVariant", - _LegacyCreativeVariant, - overrides={"manifest": (CreativeManifest | None, Field(default=None))}, -) +class CreativeVariant(_LegacyCreativeVariant, CanonicalBoundaryModel): + """Canonical creative variant whose manifest is the canonical manifest.""" + manifest: CreativeManifest | None = None -_DeliveryCreativeManifestBase = _canonical_clone( - "_DeliveryCreativeManifestBase", - _CanonicalCreativeManifestWire, - overrides={ - "format_kind": ( - _OpenCanonicalFormatKind | None, - copy.deepcopy(_CanonicalCreativeManifestWire.model_fields["format_kind"]), - ) - }, -) +class _DeliveryCreativeManifest(_CanonicalCreativeManifestWire, CanonicalBoundaryModel): + """The buyer's manifest, read back off a delivery row. + + It redeclares nothing about the format-kind vocabulary. ``format_kind`` is + ``str`` here and on :class:`CreativeManifest`, neither refuses a value, and + the ``_StrictFormatKind`` mixin that used to be the only difference between + the two is deleted. **This class is not a tolerant variant of anything.** + + **The one reason it survives** is ``_normalize_readback`` below, and + specifically its first line: the canonical models are real SUBCLASSES of + the generated wire models they refine, so a delivery row carrying a + manifest INSTANCE of the generated wire class is a PARENT instance, which + pydantic refuses for a field typed as the subclass. Dumping it first is + what lets a caller compose a delivery response out of the models it already + holds. -class _DeliveryCreativeManifest(_DeliveryCreativeManifestBase): - """Tolerant served output, deliberately not a subtype of the strict input.""" + Measured, not reasoned: deleting both delivery classes and typing + ``DeliveryCreative.variants`` as ``list[CreativeVariant]`` turns the + readback suites 6 red, of which exactly ONE is lost behavior — + + Input should be a valid dictionary or instance of CreativeManifest + [type=model_type, input_type=CreativeManifest] + + on ``test_delivery_accepts_known_input_models[manifest-CreativeManifest1-canonical]`` + and its ``variant`` twin. The other four grade the two-class split itself + (``test_delivery_manifest_cannot_bypass_strict_input[instance-...]``, its + variant twin, ``test_delivery_only_types_are_not_top_level_exports`` and + ``test_unknown_nested_manifest_kind_round_trips_in_delivery_readback``) and + would be restated, not lost. Keeping one type here therefore costs + parent-instance acceptance at the delivery boundary, which is a readback + concern and has nothing to do with the vocabulary. + """ @model_validator(mode="before") @classmethod def _normalize_readback(cls, data: Any) -> Any: if isinstance(data, AdCPBaseModel) and not isinstance(data, cls): data = data.model_dump(mode="python") - return CreativeManifest._normalize_standalone_assets(data) + # Pydantic binds the validator to a descriptor proxy on the class. + normalize = cast(Callable[[Any], Any], CreativeManifest._normalize_standalone_assets) + return normalize(data) -_DeliveryCreativeVariantBase = _canonical_clone( - "_DeliveryCreativeVariantBase", - _LegacyCreativeVariant, - overrides={ - "manifest": ( - _DeliveryCreativeManifest | None, - copy.deepcopy(_LegacyCreativeVariant.model_fields["manifest"]), - ) - }, -) +class _DeliveryCreativeVariant(_LegacyCreativeVariant, CanonicalBoundaryModel): + """A delivery row carrying the read-back manifest. + Survives for the same measured reason as + :class:`_DeliveryCreativeManifest`: ``_normalize_readback`` accepts a + PARENT instance — a variant of the generated wire class — which a field + typed as the canonical subclass refuses. Nothing here is about the + format-kind vocabulary. + """ -class _DeliveryCreativeVariant(_DeliveryCreativeVariantBase): - """A delivery row whose rendered manifest may use a future format kind.""" + manifest: _DeliveryCreativeManifest | None = _inherit(_LegacyCreativeVariant, "manifest") @model_validator(mode="before") @classmethod @@ -789,31 +836,39 @@ def _normalize_readback(cls, data: Any) -> Any: return data -DeliveryCreative = _canonical_clone( - "DeliveryCreative", - _LegacyDeliveryCreative, - overrides={ - "format_kind": (_OpenCanonicalFormatKind | None, Field(default=None)), - "variants": ( - list[_DeliveryCreativeVariant], - copy.deepcopy(_LegacyDeliveryCreative.model_fields["variants"]), - ), - }, -) +class DeliveryCreative(_LegacyDeliveryCreative, CanonicalBoundaryModel): + """Canonical served creative; variants are the read-back delivery rows.""" -CreativeFilters = _canonical_clone("CreativeFilters", _LegacyCreativeFilters) -ProductFilters = _canonical_clone("ProductFilters", _LegacyProductFilters) + if TYPE_CHECKING: # the removed field, hidden from the constructor too + format_id: _RemovedFormatId = Field(default=None, init=False) -_PackageRequestBase = _canonical_clone( - "PackageRequest", - _LegacyPackageRequest, - overrides={"creatives": (list[CreativeAsset] | None, Field(default=None, min_length=1))}, -) + variants: SchemaVariant[list[_DeliveryCreativeVariant]] = _inherit( + _LegacyDeliveryCreative, "variants" + ) + + +class CreativeFilters(_LegacyCreativeFilters, CanonicalBoundaryModel): + """Canonical creative filters; legacy identity selection is unavailable.""" + + if TYPE_CHECKING: # the removed field, hidden from the constructor too + format_ids: _RemovedFormatIds = Field(default=None, init=False) + + +class ProductFilters(_LegacyProductFilters, CanonicalBoundaryModel): + """Canonical product filters; legacy identity selection is unavailable.""" + if TYPE_CHECKING: # the removed field, hidden from the constructor too + format_ids: _RemovedFormatIds = Field(default=None, init=False) -class PackageRequest(_PackageRequestBase): + +class PackageRequest(_LegacyPackageRequest, CanonicalBoundaryModel): """Canonical package request preserving beta.3 selector constraints.""" + if TYPE_CHECKING: # the removed field, hidden from the constructor too + format_ids: _RemovedFormatIds = Field(default=None, init=False) + + creatives: list[CreativeAsset] | None = Field(default=None, min_length=1) + @model_validator(mode="after") def _validate_format_params(self) -> PackageRequest: if self.params is not None and self.format_kind is None: @@ -824,34 +879,26 @@ def _validate_format_params(self) -> PackageRequest: return self -PackageUpdate = _canonical_clone( - "PackageUpdate", - _LegacyPackageUpdate, - overrides={"creatives": (list[CreativeAsset] | None, Field(default=None, min_length=1))}, -) +class PackageUpdate(_LegacyPackageUpdate, CanonicalBoundaryModel): + """Canonical package update; creatives are canonical assets.""" -Package = _canonical_clone("Package", _LegacyPackage) + creatives: SchemaVariant[list[CreativeAsset] | None] = Field(default=None, min_length=1) -def _canonical_enum(name: str, source: type[Enum]) -> type[StrEnum]: - members = { - member.name: member.value - for member in source - if not is_legacy_creative_identity_key(member.value) - } - return StrEnum(name, members, module=__name__) # type: ignore[call-overload,return-value] +class Package(_LegacyPackage, CanonicalBoundaryModel): + """Canonical package; legacy format identity is absent.""" + if TYPE_CHECKING: # the removed fields, hidden from the constructor too + format_ids: _RemovedFormatIds = Field(default=None, init=False) + format_ids_pending: _RemovedFormatIds = Field(default=None, init=False) + format_ids_to_provide: _RemovedFormatIds = Field(default=None, init=False) -_GetProductsRequestBase = _canonical_clone( - "_GetProductsRequestBase", - _LegacyGetProductsRequest, - overrides={"filters": (ProductFilters | None, Field(default=None))}, -) - -class GetProductsRequest(_GetProductsRequestBase): +class GetProductsRequest(_LegacyGetProductsRequest, CanonicalBoundaryModel): """Canonical discovery request with legacy response-field selection rejected.""" + filters: ProductFilters | None = None + @field_validator("fields") @classmethod def _reject_legacy_fields(cls, value: Any) -> Any: @@ -864,44 +911,30 @@ def _reject_legacy_fields(cls, value: Any) -> Any: return value -GetProductsResponse = _canonical_clone( - "GetProductsResponse", - _LegacyGetProductsResponse, - overrides={"products": (list[Product] | None, Field(default=None))}, -) +class GetProductsResponse(_LegacyGetProductsResponse, CanonicalBoundaryModel): + """Canonical discovery response; products are canonical products.""" -CreateMediaBuyRequest = _canonical_clone( - "CreateMediaBuyRequest", - _LegacyCreateMediaBuyRequest, - overrides={"packages": (list[PackageRequest] | None, Field(default=None))}, -) + products: SchemaVariant[list[Product] | None] = None -UpdateMediaBuyRequest = _canonical_clone( - "UpdateMediaBuyRequest", - _LegacyUpdateMediaBuyRequest, - overrides={ - "packages": (list[PackageUpdate] | None, Field(default=None)), - "new_packages": (list[PackageRequest] | None, Field(default=None)), - }, -) -_CreateMediaBuyResponse1Base = _canonical_clone( - "_CreateMediaBuyResponse1Base", - _LegacyCreateMediaBuyResponse1, - overrides={ - "packages": (list[Package], Field()), - # AdCP 3.2 removes the synchronous task-envelope status from this - # schema arm. Keep it as a declared compatibility field so the - # normalizer does not inject an unknown extra into adopter subclasses - # that choose ``extra='forbid'``. - "status": (Literal["completed"], Field(default="completed")), - }, -) +class CreateMediaBuyRequest(_LegacyCreateMediaBuyRequest, CanonicalBoundaryModel): + """Canonical create request; packages are canonical package requests.""" + + packages: list[PackageRequest] | None = None -class CreateMediaBuyResponse1(_CreateMediaBuyResponse1Base): +class UpdateMediaBuyRequest(_LegacyUpdateMediaBuyRequest, CanonicalBoundaryModel): + """Canonical update request; both package lists are canonical.""" + + packages: list[PackageUpdate] | None = None + new_packages: SchemaVariant[list[PackageRequest] | None] = None + + +class CreateMediaBuyResponse1(_LegacyCreateMediaBuyResponse1, CanonicalBoundaryModel): """Canonical create response preserving the 3.x legacy-status normalizer.""" + packages: SchemaVariant[list[Package]] + @model_validator(mode="before") @classmethod def _normalize_legacy_status(cls, data: Any) -> Any: @@ -918,27 +951,22 @@ def _normalize_legacy_status(cls, data: Any) -> Any: return data -CreateMediaBuyResponse2 = _canonical_clone( - "CreateMediaBuyResponse2", _LegacyCreateMediaBuyResponse2 -) -CreateMediaBuyResponse3 = _canonical_clone( - "CreateMediaBuyResponse3", _LegacyCreateMediaBuyResponse3 -) -CreateMediaBuyResponse = CreateMediaBuyResponse1 | CreateMediaBuyResponse2 | CreateMediaBuyResponse3 +class CreateMediaBuyResponse2(_LegacyCreateMediaBuyResponse2, CanonicalBoundaryModel): + """Canonical create-media-buy error arm.""" -_UpdateMediaBuyResponse1Base = _canonical_clone( - "_UpdateMediaBuyResponse1Base", - _LegacyUpdateMediaBuyResponse1, - overrides={ - "affected_packages": (Sequence[Package] | None, Field(default=None)), - "status": (Literal["completed"], Field(default="completed")), - }, -) +class CreateMediaBuyResponse3(_LegacyCreateMediaBuyResponse3, CanonicalBoundaryModel): + """Canonical create-media-buy submitted arm.""" + + +CreateMediaBuyResponse = CreateMediaBuyResponse1 | CreateMediaBuyResponse2 | CreateMediaBuyResponse3 -class UpdateMediaBuyResponse1(_UpdateMediaBuyResponse1Base): + +class UpdateMediaBuyResponse1(_LegacyUpdateMediaBuyResponse1, CanonicalBoundaryModel): """Canonical update response preserving the 3.x legacy-status normalizer.""" + affected_packages: Sequence[Package] | None = None + @model_validator(mode="before") @classmethod def _normalize_legacy_status(cls, data: Any) -> Any: @@ -955,30 +983,28 @@ def _normalize_legacy_status(cls, data: Any) -> Any: return data -UpdateMediaBuyResponse2 = _canonical_clone( - "UpdateMediaBuyResponse2", _LegacyUpdateMediaBuyResponse2 -) -UpdateMediaBuyResponse3 = _canonical_clone( - "UpdateMediaBuyResponse3", _LegacyUpdateMediaBuyResponse3 -) +class UpdateMediaBuyResponse2(_LegacyUpdateMediaBuyResponse2, CanonicalBoundaryModel): + """Canonical update-media-buy error arm.""" + + +class UpdateMediaBuyResponse3(_LegacyUpdateMediaBuyResponse3, CanonicalBoundaryModel): + """Canonical update-media-buy submitted arm.""" + + UpdateMediaBuyResponse = UpdateMediaBuyResponse1 | UpdateMediaBuyResponse2 | UpdateMediaBuyResponse3 -SyncCreativesRequest = _canonical_clone( - "SyncCreativesRequest", - _LegacySyncCreativesRequest, - overrides={"creatives": (list[CreativeAsset], Field(min_length=1))}, -) -_ListCreativesRequestBase = _canonical_clone( - "_ListCreativesRequestBase", - _LegacyListCreativesRequest, - overrides={"filters": (CreativeFilters | None, Field(default=None))}, -) +class SyncCreativesRequest(_LegacySyncCreativesRequest, CanonicalBoundaryModel): + """Canonical creative sync request; creatives are canonical assets.""" + + creatives: SchemaVariant[list[CreativeAsset]] = Field(min_length=1) -class ListCreativesRequest(_ListCreativesRequestBase): +class ListCreativesRequest(_LegacyListCreativesRequest, CanonicalBoundaryModel): """Canonical creative read request with legacy field selection rejected.""" + filters: CreativeFilters | None = None + @field_validator("fields") @classmethod def _reject_legacy_fields(cls, value: Any) -> Any: @@ -991,31 +1017,41 @@ def _reject_legacy_fields(cls, value: Any) -> Any: return value -ListCreativesResponse = _canonical_clone( - "ListCreativesResponse", - _LegacyListCreativesResponse, - overrides={"creatives": (list[Creative], Field())}, -) +class ListCreativesResponse(_LegacyListCreativesResponse, CanonicalBoundaryModel): + """Canonical creative listing; rows are canonical listed creatives.""" -MediaBuyPackage = _canonical_clone("MediaBuyPackage", _LegacyMediaBuyPackage) -MediaBuy = _canonical_clone( - "MediaBuy", - _LegacyMediaBuy, - overrides={"packages": (Sequence[MediaBuyPackage], Field())}, -) -GetMediaBuysResponse = _canonical_clone( - "GetMediaBuysResponse", - _LegacyGetMediaBuysResponse, - overrides={"media_buys": (Sequence[MediaBuy], Field())}, -) -GetMediaBuyDeliveryResponse = _canonical_clone( - "GetMediaBuyDeliveryResponse", _LegacyGetMediaBuyDeliveryResponse -) -GetCreativeDeliveryResponse = _canonical_clone( - "GetCreativeDeliveryResponse", - _LegacyGetCreativeDeliveryResponse, - overrides={"creatives": (Sequence[DeliveryCreative], Field())}, -) + creatives: SchemaVariant[list[Creative]] + + +class MediaBuyPackage(_LegacyMediaBuyPackage, CanonicalBoundaryModel): + """Canonical media-buy package row; legacy format identity is absent.""" + + if TYPE_CHECKING: # the removed fields, hidden from the constructor too + format_ids: _RemovedFormatIds = Field(default=None, init=False) + format_ids_pending: _RemovedFormatIds = Field(default=None, init=False) + format_ids_to_provide: _RemovedFormatIds = Field(default=None, init=False) + + +class MediaBuy(_LegacyMediaBuy, CanonicalBoundaryModel): + """Canonical media buy; packages are canonical package rows.""" + + packages: Sequence[MediaBuyPackage] + + +class GetMediaBuysResponse(_LegacyGetMediaBuysResponse, CanonicalBoundaryModel): + """Canonical media-buy listing; rows are canonical media buys.""" + + media_buys: Sequence[MediaBuy] + + +class GetMediaBuyDeliveryResponse(_LegacyGetMediaBuyDeliveryResponse, CanonicalBoundaryModel): + """Canonical media-buy delivery response.""" + + +class GetCreativeDeliveryResponse(_LegacyGetCreativeDeliveryResponse, CanonicalBoundaryModel): + """Canonical creative delivery response; rows are the read-back delivery creatives.""" + + creatives: Sequence[DeliveryCreative] PRIMARY_CANONICAL_MODELS: tuple[type[CanonicalBoundaryModel], ...] = ( @@ -1090,6 +1126,7 @@ def _reject_legacy_fields(cls, value: Any) -> Any: "UpdateMediaBuyResponse1", "UpdateMediaBuyResponse2", "UpdateMediaBuyResponse3", + "is_canonical_format_kind", "is_legacy_creative_identity_key", "sanitize_canonical_schema", "strip_legacy_creative_identity", diff --git a/src/adcp/types/canonical_creative.pyi b/src/adcp/types/canonical_creative.pyi deleted file mode 100644 index 9acfececb..000000000 --- a/src/adcp/types/canonical_creative.pyi +++ /dev/null @@ -1,255 +0,0 @@ -from collections.abc import Sequence -from datetime import datetime -from typing import Any, ClassVar, Literal, TypeAlias, TypeVar - -from adcp.types.base import AdCPBaseModel -from adcp.types.generated_poc.core.canonical_format_kind import CanonicalFormatKind -from adcp.types.generated_poc.core.protocol_envelope import ProtocolEnvelope -from adcp.types.generated_poc.core.targeting import TargetingOverlay -from adcp.types.generated_poc.core.targeting_input import TargetingOverlayInput -from adcp.types.generated_poc.core.version_envelope import AdcpVersionEnvelope -from adcp.types.generated_poc.enums.task_status import TaskStatus -from adcp.types.legacy import LegacyFormatId - -_T = TypeVar("_T", bound=AdCPBaseModel) - -class CanonicalBoundaryModel(AdCPBaseModel): - __adcp_canonical_creative_model__: ClassVar[bool] - __adcp_canonical_source__: ClassVar[type[AdCPBaseModel] | None] - __adcp_canonical_validators_left_behind__: ClassVar[dict[str, set[str]]] - -class _CanonicalResponseEnvelope(AdcpVersionEnvelope, ProtocolEnvelope, CanonicalBoundaryModel): - """Stub-only Liskov bridge for canonical responses; not a runtime class. - - The canonical response clones inherit :class:`AdcpVersionEnvelope`, - :class:`ProtocolEnvelope` and :class:`CanonicalBoundaryModel` directly at - runtime, so ``isinstance``/``issubclass`` agree with this stub. Collapsing - them into one private ancestor exists purely for the type checker: several - schema arms pin an envelope field to a ``Literal``, and narrowing an - inherited mutable attribute is a Liskov violation. Relaxing the pinned - fields to ``Any`` once here lets each arm declare its precise literal with - no per-field suppression, and keeps plain-string construction - (``status="completed"``) working for adopters. - - This is a bridge, not a public type. Every concrete response below - re-declares ``status`` with the exact annotation its runtime model carries, - so no adopter ever reads ``Any`` off one of them — enforced by - ``test_canonical_response_stub_status_matches_runtime``. The ``= ...`` - matters as much as the type: ``status`` is defaulted on every runtime - response, so a bare ``status: Any`` would make the synthesized ``__init__`` - demand it and reject a plain ``GetMediaBuysResponse(media_buys=[])``. - """ - - status: Any = ... - -class Format(CanonicalBoundaryModel): - format_option_id: str | None - publisher_domain: str | None - format_kind: CanonicalFormatKind - params: dict[str, Any] - canonical_formats_only: bool | None - def __init__( - self, - *, - format_kind: CanonicalFormatKind | str, - params: dict[str, Any], - format_option_id: str | None = ..., - publisher_domain: str | None = ..., - canonical_formats_only: bool | None = ..., - v1_format_ref: Sequence[LegacyFormatId | dict[str, Any]] | None = ..., - **data: Any, - ) -> None: ... - @property - def legacy_format_refs(self) -> tuple[LegacyFormatId, ...]: ... - def params_as(self, canonical_type: type[_T]) -> _T: ... - -ProductFormatDeclaration: TypeAlias = Format - -class Placement(CanonicalBoundaryModel): - format_options: list[Format] | None - -class Product(CanonicalBoundaryModel): - product_id: str - name: str - description: str - format_options: list[Format] - placements: list[Placement] | None - pricing_options: list[Any] - -class CreativeAsset(CanonicalBoundaryModel): - creative_id: str - format_kind: CanonicalFormatKind - format_option_ref: Any - -class Creative(CanonicalBoundaryModel): - creative_id: str - format_kind: CanonicalFormatKind - format_option_ref: Any - -class CreativeManifest(CanonicalBoundaryModel): - format_kind: CanonicalFormatKind | None = ... - assets: dict[str, Any] - -class CreativeVariant(CanonicalBoundaryModel): - manifest: CreativeManifest | None - -class _DeliveryCreativeManifest(CanonicalBoundaryModel): - format_kind: CanonicalFormatKind | str | None = ... - assets: dict[str, Any] - -class _DeliveryCreativeVariant(CanonicalBoundaryModel): - manifest: _DeliveryCreativeManifest | None - -class DeliveryCreative(CanonicalBoundaryModel): - creative_id: str - format_kind: CanonicalFormatKind | str | None - variants: list[_DeliveryCreativeVariant] - -class CreativeFilters(CanonicalBoundaryModel): ... -class ProductFilters(CanonicalBoundaryModel): ... - -class PackageRequest(AdcpVersionEnvelope, CanonicalBoundaryModel): - product_id: str - format_option_refs: list[Any] | None - creatives: list[CreativeAsset] | None - targeting_overlay: TargetingOverlayInput | TargetingOverlay | None = ... - -class PackageUpdate(CanonicalBoundaryModel): - package_id: str - format_option_refs: list[Any] | None - creatives: list[CreativeAsset] | None - targeting_overlay: TargetingOverlayInput | TargetingOverlay | None = ... - -class Package(CanonicalBoundaryModel): - package_id: str - product_id: str | None = ... - format_option_refs: list[Any] | None = ... - def __init__( - self, - *, - package_id: str, - product_id: str | None = ..., - format_option_refs: list[Any] | None = ..., - **data: Any, - ) -> None: ... - -class GetProductsRequest(AdcpVersionEnvelope, CanonicalBoundaryModel): - account: Any - filters: ProductFilters | None - fields: Any - refine: Any - time_budget: Any - pagination: Any - -class GetProductsResponse(_CanonicalResponseEnvelope): - status: TaskStatus = ... - products: list[Product] | None - proposals: Any - refinement_applied: Any - def __init__( - self, - *, - products: list[Product] | None = ..., - proposals: Any = ..., - refinement_applied: Any = ..., - **data: Any, - ) -> None: ... - -class CreateMediaBuyRequest(AdcpVersionEnvelope, CanonicalBoundaryModel): - account: Any - packages: list[PackageRequest] | None - -class UpdateMediaBuyRequest(AdcpVersionEnvelope, CanonicalBoundaryModel): - account: Any - media_buy_id: str - packages: list[PackageUpdate] | None - new_packages: list[PackageRequest] | None - -class CreateMediaBuyResponse1(_CanonicalResponseEnvelope): - media_buy_id: str - packages: list[Package] - # AdCP 3.2 drops the synchronous task-envelope status from this arm, so the - # runtime model pins it to the single outcome and defaults it. Mirror both - # halves: the literal and the default. - status: Literal["completed"] = ... - # Required *and* nullable: the schema lists confirmed_at in the success - # branch's ``required`` while typing it ``["string", "null"]``. A buy - # awaiting seller commitment carries the key with a null value. - confirmed_at: datetime | None - def __init__( - self, - *, - media_buy_id: str, - confirmed_at: datetime | None, - revision: int, - packages: list[Package], - status: Literal["completed"] = ..., - media_buy_status: Any = ..., - **data: Any, - ) -> None: ... - -class CreateMediaBuyResponse2(_CanonicalResponseEnvelope): - status: TaskStatus = ... - -class CreateMediaBuyResponse3(_CanonicalResponseEnvelope): - status: Literal[TaskStatus.submitted] = ... - -CreateMediaBuyResponse: TypeAlias = ( - CreateMediaBuyResponse1 | CreateMediaBuyResponse2 | CreateMediaBuyResponse3 -) - -class UpdateMediaBuyResponse1(_CanonicalResponseEnvelope): - media_buy_id: str - # The 3.x schema arm pins ``status`` to the single synchronous outcome. - # ``_CanonicalResponseEnvelope`` is what makes this precise literal legal - # without a per-field suppression, and it keeps ``status="completed"`` - # constructible — see tests/type_checks/extend_response_with_sequence.py. - status: Literal["completed"] = ... - revision: int - media_buy_status: Any = ... - affected_packages: Sequence[Package] | None = ... - -class UpdateMediaBuyResponse2(_CanonicalResponseEnvelope): - status: TaskStatus = ... - -class UpdateMediaBuyResponse3(_CanonicalResponseEnvelope): - status: Literal[TaskStatus.submitted] = ... - -UpdateMediaBuyResponse: TypeAlias = ( - UpdateMediaBuyResponse1 | UpdateMediaBuyResponse2 | UpdateMediaBuyResponse3 -) - -class SyncCreativesRequest(AdcpVersionEnvelope, CanonicalBoundaryModel): - account: Any - creatives: list[CreativeAsset] - -class ListCreativesRequest(AdcpVersionEnvelope, CanonicalBoundaryModel): - account: Any - filters: CreativeFilters | None - fields: Any - -class ListCreativesResponse(_CanonicalResponseEnvelope): - status: TaskStatus = ... - creatives: list[Creative] - -class MediaBuyPackage(CanonicalBoundaryModel): ... - -class MediaBuy(CanonicalBoundaryModel): - packages: Sequence[MediaBuyPackage] - -class GetMediaBuysResponse(_CanonicalResponseEnvelope): - status: TaskStatus = ... - media_buys: Sequence[MediaBuy] - -class GetMediaBuyDeliveryResponse(_CanonicalResponseEnvelope): - status: TaskStatus = ... - -class GetCreativeDeliveryResponse(_CanonicalResponseEnvelope): - status: TaskStatus = ... - creatives: Sequence[DeliveryCreative] - -PRIMARY_CANONICAL_MODELS: tuple[type[CanonicalBoundaryModel], ...] - -def is_legacy_creative_identity_key(key: object) -> bool: ... -def sanitize_canonical_schema(schema: dict[str, Any]) -> dict[str, Any]: ... -def strip_legacy_creative_identity(value: Any) -> Any: ... diff --git a/src/adcp/types/canonical_decl.py b/src/adcp/types/canonical_decl.py index 81dd484f0..59ca9dda8 100644 --- a/src/adcp/types/canonical_decl.py +++ b/src/adcp/types/canonical_decl.py @@ -44,22 +44,22 @@ from pydantic import BaseModel, ConfigDict, Field, model_validator from adcp.types.base import AdCPBaseModel -from adcp.types.generated_poc.core.canonical_format_kind import ( +from adcp.types.domains.core.canonical_format_kind import ( CanonicalFormatKind, ) -from adcp.types.generated_poc.core.format_id import ( +from adcp.types.domains.core.format_id import ( FormatReferenceStructuredObject, ) -from adcp.types.generated_poc.core.platform_extension_ref import ( +from adcp.types.domains.core.platform_extension_ref import ( PlatformExtensionReference, ) -from adcp.types.generated_poc.core.product_format_declaration import ( +from adcp.types.domains.core.product_format_declaration import ( ProductFormatDeclaration as _GeneratedProductFormatDeclaration, ) -from adcp.types.generated_poc.core.product_format_declaration import ( +from adcp.types.domains.core.product_format_declaration import ( SellerPreference, ) -from adcp.types.generated_poc.enums.channels import MediaChannel +from adcp.types.domains.enums.channels import MediaChannel if TYPE_CHECKING: from typing_extensions import Self diff --git a/src/adcp/types/capabilities.py b/src/adcp/types/capabilities.py index 903c998f9..1c4a2b487 100644 --- a/src/adcp/types/capabilities.py +++ b/src/adcp/types/capabilities.py @@ -6,12 +6,12 @@ typed capability sub-models — ``Account``, ``MediaBuy``, ``Targeting``, ``GeoMetros``, ``Idempotency`` etc. The generated Pydantic classes already exist in -``adcp.types.generated_poc.bundled.protocol.get_adcp_capabilities_response``, +``adcp.types.domains.bundled.protocol.get_adcp_capabilities_response``, but three of them (``Account``, ``MediaBuy``, ``Creative``) collide on name with unrelated wire types already exported from :mod:`adcp.types`. This module sits in the import-architecture whitelist for direct -``generated_poc`` imports, alongside the other re-export layers +generated-tree imports, alongside the other re-export layers (``aliases.py``, ``_ergonomic.py``, ``_forward_compat.py``, ``_generated.py``, ``error_details.py`` and the ``domains/`` modules); see ``ALLOWED_FILES`` in ``tests/test_import_layering.py`` for the current set. It pulls the capabilities sub-models out @@ -23,7 +23,7 @@ Layering:: - generated_poc/bundled/protocol/get_adcp_capabilities_response.py + domains/bundled/protocol/get_adcp_capabilities_response.py ↓ (this module — disambiguates colliding names) adcp.types.capabilities ↓ (re-exported from) @@ -41,7 +41,7 @@ from adcp.types.base import AdCPBaseModel from adcp.types._generated import CountryFusedPostalCodeSystem as LegacyPostalCodeSystem -from adcp.types.generated_poc.bundled.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.bundled.protocol.get_adcp_capabilities_response import ( A2ui, Accreditation, Adcp, @@ -96,10 +96,10 @@ # / ``MediaBuy`` / ``Creative`` within its own namespace, so adopters # writing the ``DecisioningCapabilities`` declaration get the wire-spec # names. -from adcp.types.generated_poc.bundled.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.bundled.protocol.get_adcp_capabilities_response import ( Account as CapabilitiesAccount, ) -from adcp.types.generated_poc.bundled.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.bundled.protocol.get_adcp_capabilities_response import ( Adcp as _Adcp, ) @@ -108,7 +108,7 @@ # / a2ui / mcp_apps. Re-aliased here as ``SiCapabilities`` to disambiguate # from :class:`adcp.decisioning.DecisioningCapabilities` and to make the # import site self-documenting. -from adcp.types.generated_poc.bundled.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.bundled.protocol.get_adcp_capabilities_response import ( Capabilities as SiCapabilities, ) @@ -117,34 +117,34 @@ # disambiguation pattern as ``Account`` / ``MediaBuy`` / ``Creative`` — # imported here under a ``Capabilities*`` alias and re-aliased back to # the wire-spec name within :mod:`adcp.decisioning.capabilities`. -from adcp.types.generated_poc.bundled.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.bundled.protocol.get_adcp_capabilities_response import ( ContentStandards as CapabilitiesContentStandards, ) -from adcp.types.generated_poc.bundled.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.bundled.protocol.get_adcp_capabilities_response import ( Creative as CapabilitiesCreative, ) -from adcp.types.generated_poc.bundled.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.bundled.protocol.get_adcp_capabilities_response import ( GetAdcpCapabilitiesResponse as _CapabilitiesResponse, ) -from adcp.types.generated_poc.bundled.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.bundled.protocol.get_adcp_capabilities_response import ( Idempotency as IdempotencySupported, ) -from adcp.types.generated_poc.bundled.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.bundled.protocol.get_adcp_capabilities_response import ( MediaBuy as _MediaBuy, ) -from adcp.types.generated_poc.bundled.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.bundled.protocol.get_adcp_capabilities_response import ( Preview as CapabilitiesPreview, ) -from adcp.types.generated_poc.bundled.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.bundled.protocol.get_adcp_capabilities_response import ( RenderingOrigin as CapabilitiesPreviewRenderingOrigin, ) -from adcp.types.generated_poc.bundled.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.bundled.protocol.get_adcp_capabilities_response import ( Route as CapabilitiesPreviewRoute, ) -from adcp.types.generated_poc.bundled.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.bundled.protocol.get_adcp_capabilities_response import ( Signals as _Signals, ) -from adcp.types.generated_poc.core.reporting_delivery_capabilities import ( +from adcp.types.domains.core.reporting_delivery_capabilities import ( ReportingDeliveryCapabilities, ) diff --git a/src/adcp/types/domains/__init__.py b/src/adcp/types/domains/__init__.py index 63e32fe31..5ef5efde3 100644 --- a/src/adcp/types/domains/__init__.py +++ b/src/adcp/types/domains/__init__.py @@ -1,9 +1,9 @@ """Public types grouped by the AdCP schema that declares them. The AdCP bundle is organised by domain — ``core/``, ``creative/``, -``media_buy/`` and the rest — and by schema within each. Codegen mirrors that -layout, and these modules re-export it, so a type name more than one schema -declares is unambiguous by path rather than by a mangled name: +``media_buy/`` and the rest — and by schema within each. Codegen writes this +package in that layout and defines every class here, so a type name more +than one schema declares is unambiguous by path rather than by a mangled name: from adcp.types.domains.creative import QuerySummary from adcp.types.domains.core.audience_evidence import Unit @@ -12,8 +12,8 @@ A domain root carries the names that domain declares exactly once. For the rest, import from the schema's own module, as the last two lines do. -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:33:59 UTC +Auto-generated from the generated domain tree. DO NOT EDIT MANUALLY. +Generation date: 2026-10-04 18:45:11 UTC """ from __future__ import annotations diff --git a/src/adcp/types/domains/a2ui/__init__.py b/src/adcp/types/domains/a2ui/__init__.py index 4f470ed88..6645b8324 100644 --- a/src/adcp/types/domains/a2ui/__init__.py +++ b/src/adcp/types/domains/a2ui/__init__.py @@ -9,14 +9,14 @@ it from its own schema's module, ``adcp.types.domains.a2ui.``. Nothing here is renamed. -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:33:59 UTC +Auto-generated from the generated domain tree. DO NOT EDIT MANUALLY. +Generation date: 2026-10-04 18:45:11 UTC """ # ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.a2ui.bound_value import ( +from adcp.types.domains.a2ui.bound_value import ( A2UiBoundValue, A2UiBoundValue1, A2UiBoundValue2, @@ -24,8 +24,8 @@ A2UiBoundValue4, A2UiBoundValue5, ) -from adcp.types.generated_poc.a2ui.component import A2UiComponent -from adcp.types.generated_poc.a2ui.si_catalog import ( +from adcp.types.domains.a2ui.component import A2UiComponent +from adcp.types.domains.a2ui.si_catalog import ( Action23, Align, AppHandoff, @@ -49,8 +49,8 @@ Variant, Variant4, ) -from adcp.types.generated_poc.a2ui.surface import A2UiSurface -from adcp.types.generated_poc.a2ui.user_action import A2UiUserAction +from adcp.types.domains.a2ui.surface import A2UiSurface +from adcp.types.domains.a2ui.user_action import A2UiUserAction # Explicit exports __all__ = [ diff --git a/src/adcp/types/domains/a2ui/bound_value.py b/src/adcp/types/domains/a2ui/bound_value.py index ae425d301..2afc76ba3 100644 --- a/src/adcp/types/domains/a2ui/bound_value.py +++ b/src/adcp/types/domains/a2ui/bound_value.py @@ -1,32 +1,58 @@ -"""Types declared by the AdCP ``a2ui/bound_value`` schema. +# generated by datamodel-codegen: +# filename: a2ui/bound_value.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.a2ui.bound_value import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictBool, StrictFloat + + +class A2UiBoundValue1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + literalString: Annotated[str, Field(description='Static string value')] + + +class A2UiBoundValue2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + literalNumber: Annotated[StrictFloat, Field(description='Static number value')] + + +class A2UiBoundValue3(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + literalBoolean: Annotated[StrictBool, Field(description='Static boolean value')] + + +class A2UiBoundValue4(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + path: Annotated[ + str, + Field(description="JSON pointer path to value in data model (e.g., '/products/0/title')"), + ] + + +class A2UiBoundValue5(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + literalString: str + path: str -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.a2ui.bound_value import ( - A2UiBoundValue, - A2UiBoundValue1, - A2UiBoundValue2, - A2UiBoundValue3, - A2UiBoundValue4, - A2UiBoundValue5, -) - -# Explicit exports -__all__ = [ - "A2UiBoundValue", - "A2UiBoundValue1", - "A2UiBoundValue2", - "A2UiBoundValue3", - "A2UiBoundValue4", - "A2UiBoundValue5", +A2UiBoundValue = Annotated[ + A2UiBoundValue1 | A2UiBoundValue2 | A2UiBoundValue3 | A2UiBoundValue4 | A2UiBoundValue5, + Field( + description='A value that can be a literal or bound to a path in the data model', + title='A2UI Bound Value', + ), ] diff --git a/src/adcp/types/domains/a2ui/component.py b/src/adcp/types/domains/a2ui/component.py index 473223894..6b4065cd3 100644 --- a/src/adcp/types/domains/a2ui/component.py +++ b/src/adcp/types/domains/a2ui/component.py @@ -1,18 +1,26 @@ -"""Types declared by the AdCP ``a2ui/component`` schema. +# generated by datamodel-codegen: +# filename: a2ui/component.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.a2ui.component import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Any -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -from adcp.types.generated_poc.a2ui.component import A2UiComponent -# Explicit exports -__all__ = ["A2UiComponent"] +class A2UiComponent(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + id: Annotated[str, Field(description='Unique identifier for this component within the surface')] + parentId: Annotated[ + str | None, Field(description='ID of the parent component (null for root)') + ] = None + component: Annotated[ + dict[str, dict[str, Any]], + Field( + description='Component definition (keyed by component type)', max_length=1, min_length=1 + ), + ] diff --git a/src/adcp/types/domains/a2ui/si_catalog.py b/src/adcp/types/domains/a2ui/si_catalog.py index c5ca4132c..096662c7e 100644 --- a/src/adcp/types/domains/a2ui/si_catalog.py +++ b/src/adcp/types/domains/a2ui/si_catalog.py @@ -1,66 +1,191 @@ -"""Types declared by the AdCP ``a2ui/si_catalog`` schema. +# generated by datamodel-codegen: +# filename: a2ui/si_catalog.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.a2ui.si_catalog import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AnyUrl, Field, StrictBool + +from . import bound_value + + +class Component(StrEnum): + Text = 'Text' + Button = 'Button' + Link = 'Link' + Image = 'Image' + Card = 'Card' + ProductCard = 'ProductCard' + List = 'List' + Row = 'Row' + Column = 'Column' + IntegrationAction = 'IntegrationAction' + AppHandoff = 'AppHandoff' + + +class SiComponentCatalog(AdCPBaseModel): + catalogId: Annotated[ + Literal['si-standard'], Field(description='SI standard component catalog') + ] = 'si-standard' + components: Annotated[ + list[Component] | None, Field(description='Available component types') + ] = None + + +class Variant(StrEnum): + body = 'body' + heading = 'heading' + caption = 'caption' + label = 'label' + + +class Text(AdCPBaseModel): + text: Annotated[bound_value.A2UiBoundValue, Field(description='Text content to display')] + variant: Variant | None = Variant.body + + +class Action(AdCPBaseModel): + name: Annotated[str, Field(description='Action identifier')] + context: Annotated[ + dict[str, bound_value.A2UiBoundValue] | None, + Field(description='Context data to include with action'), + ] = None + + +class Variant4(StrEnum): + primary = 'primary' + secondary = 'secondary' + text = 'text' + + +class Button(AdCPBaseModel): + label: Annotated[bound_value.A2UiBoundValue, Field(description='Button label text')] + action: Annotated[Action, Field(description='Action to trigger on click')] + variant: Variant4 | None = Variant4.primary + disabled: bound_value.A2UiBoundValue | None = None + + +class Link(AdCPBaseModel): + label: Annotated[bound_value.A2UiBoundValue, Field(description='Link text')] + url: Annotated[bound_value.A2UiBoundValue, Field(description='URL to navigate to')] + external: Annotated[StrictBool | None, Field(description='Opens in new tab if true')] = True + + +class Image(AdCPBaseModel): + src: Annotated[bound_value.A2UiBoundValue, Field(description='Image source URL')] + alt: Annotated[bound_value.A2UiBoundValue, Field(description='Alt text for accessibility')] + width: Annotated[SchemaInt | None, Field(description='Image width in pixels')] = None + height: Annotated[SchemaInt | None, Field(description='Image height in pixels')] = None + + +class Action23(AdCPBaseModel): + name: str + context: dict[str, bound_value.A2UiBoundValue] | None = None + + +class Card(AdCPBaseModel): + title: Annotated[bound_value.A2UiBoundValue, Field(description='Card title')] + subtitle: Annotated[bound_value.A2UiBoundValue | None, Field(description='Card subtitle')] = ( + None + ) + image: Annotated[bound_value.A2UiBoundValue | None, Field(description='Card image URL')] = None + badge: Annotated[ + bound_value.A2UiBoundValue | None, Field(description="Badge text (e.g., 'New', 'Sale')") + ] = None + action: Annotated[Action23 | None, Field(description='Action to trigger on card click')] = None + children: Annotated[list[str] | None, Field(description='Child component IDs')] = None + + +class ProductCard(AdCPBaseModel): + title: Annotated[bound_value.A2UiBoundValue, Field(description='Product name')] + price: Annotated[bound_value.A2UiBoundValue, Field(description='Price display string')] + image: Annotated[bound_value.A2UiBoundValue | None, Field(description='Product image URL')] = ( + None + ) + description: Annotated[ + bound_value.A2UiBoundValue | None, Field(description='Product description') + ] = None + badge: Annotated[ + bound_value.A2UiBoundValue | None, Field(description="Badge text (e.g., 'Best Seller')") + ] = None + ctaLabel: Annotated[ + bound_value.A2UiBoundValue | None, Field(description='CTA button label') + ] = None + action: Action23 | None = None + + +class Template(AdCPBaseModel): + componentId: Annotated[str, Field(description='ID of component to use as template')] + + +class Layout(StrEnum): + vertical = 'vertical' + horizontal = 'horizontal' + grid = 'grid' + + +class List(AdCPBaseModel): + items: Annotated[bound_value.A2UiBoundValue, Field(description='Path to array in data model')] + template: Annotated[Template, Field(description='Template for list items')] + emptyMessage: Annotated[ + bound_value.A2UiBoundValue | None, Field(description='Message to show when list is empty') + ] = None + layout: Layout | None = Layout.vertical + + +class Align(StrEnum): + start = 'start' + center = 'center' # type: ignore[assignment] + end = 'end' + stretch = 'stretch' + + +class Justify(StrEnum): + start = 'start' + center = 'center' # type: ignore[assignment] + end = 'end' + between = 'between' + around = 'around' + + +class Row(AdCPBaseModel): + children: Annotated[list[str], Field(description='Child component IDs')] + gap: Annotated[str | None, Field(description='Gap between children')] = '8px' + align: Align | None = Align.center + justify: Justify | None = Justify.start + + +class Column(AdCPBaseModel): + children: Annotated[list[str], Field(description='Child component IDs')] + gap: Annotated[str | None, Field(description='Gap between children')] = '8px' + align: Align | None = Align.stretch + + +class Type(StrEnum): + mcp = 'mcp' + a2a = 'a2a' + + +class IntegrationAction(AdCPBaseModel): + type: Annotated[Type, Field(description='Integration type')] + label: Annotated[bound_value.A2UiBoundValue, Field(description='Button label')] + url: Annotated[ + bound_value.A2UiBoundValue | None, Field(description='Integration endpoint URL') + ] = None + highlighted: Annotated[ + StrictBool | None, Field(description='Visually emphasize this action') + ] = False + + +class Apps(AdCPBaseModel): + appId: str | None = None + deepLink: str | None = None + url: AnyUrl | None = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.a2ui.si_catalog import ( - Action, - Action23, - Align, - AppHandoff, - Apps, - Button, - Card, - Column, - Component, - Image, - IntegrationAction, - Justify, - Layout, - Link, - List, - ProductCard, - Row, - SiComponentCatalog, - Template, - Text, - Type, - Variant, - Variant4, -) - -# Explicit exports -__all__ = [ - "Action", - "Action23", - "Align", - "AppHandoff", - "Apps", - "Button", - "Card", - "Column", - "Component", - "Image", - "IntegrationAction", - "Justify", - "Layout", - "Link", - "List", - "ProductCard", - "Row", - "SiComponentCatalog", - "Template", - "Text", - "Type", - "Variant", - "Variant4", -] +class AppHandoff(AdCPBaseModel): + apps: Annotated[dict[str, Apps], Field(description='Platform-specific app configurations')] diff --git a/src/adcp/types/domains/a2ui/surface.py b/src/adcp/types/domains/a2ui/surface.py index ef378e2d0..2bac4e883 100644 --- a/src/adcp/types/domains/a2ui/surface.py +++ b/src/adcp/types/domains/a2ui/surface.py @@ -1,18 +1,33 @@ -"""Types declared by the AdCP ``a2ui/surface`` schema. +# generated by datamodel-codegen: +# filename: a2ui/surface.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.a2ui.surface import +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import component -from adcp.types.generated_poc.a2ui.surface import A2UiSurface -# Explicit exports -__all__ = ["A2UiSurface"] +class A2UiSurface(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + surfaceId: Annotated[str, Field(description='Unique identifier for this surface')] + catalogId: Annotated[ + str | None, Field(description='Component catalog to use for rendering') + ] = 'standard' + components: Annotated[ + list[component.A2UiComponent], + Field(description='Flat list of components (adjacency list structure)'), + ] + rootId: Annotated[ + str | None, + Field(description='ID of the root component (if not specified, first component is root)'), + ] = None + dataModel: Annotated[ + dict[str, Any] | None, Field(description='Application data that components can bind to') + ] = None diff --git a/src/adcp/types/domains/a2ui/user_action.py b/src/adcp/types/domains/a2ui/user_action.py index c60e9054b..44c5cf991 100644 --- a/src/adcp/types/domains/a2ui/user_action.py +++ b/src/adcp/types/domains/a2ui/user_action.py @@ -1,18 +1,32 @@ -"""Types declared by the AdCP ``a2ui/user_action`` schema. +# generated by datamodel-codegen: +# filename: a2ui/user_action.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.a2ui.user_action import +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.a2ui.user_action import A2UiUserAction, Action +class Action(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + name: Annotated[ + str, Field(description="Action identifier (e.g., 'view_product', 'add_to_cart')") + ] + context: Annotated[ + dict[str, Any] | None, Field(description='Context data resolved from data model bindings') + ] = None + -# Explicit exports -__all__ = ["A2UiUserAction", "Action"] +class A2UiUserAction(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + surfaceId: Annotated[str, Field(description='ID of the surface where the action occurred')] + componentId: Annotated[str, Field(description='ID of the component that triggered the action')] + action: Annotated[Action, Field(description='The action that was triggered')] + timestamp: Annotated[AwareDatetime | None, Field(description='When the action occurred')] = None diff --git a/src/adcp/types/domains/aao/__init__.py b/src/adcp/types/domains/aao/__init__.py index 992848439..d08486588 100644 --- a/src/adcp/types/domains/aao/__init__.py +++ b/src/adcp/types/domains/aao/__init__.py @@ -9,14 +9,14 @@ it from its own schema's module, ``adcp.types.domains.aao.``. Nothing here is renamed. -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:33:59 UTC +Auto-generated from the generated domain tree. DO NOT EDIT MANUALLY. +Generation date: 2026-10-04 18:45:11 UTC """ # ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.aao.agent_publishers import ( +from adcp.types.domains.aao.agent_publishers import ( AaoDirectoryAgentPublishersInverseLookupResponse, DiscoveryMethod, PublisherEntry, diff --git a/src/adcp/types/domains/aao/agent_publishers.py b/src/adcp/types/domains/aao/agent_publishers.py index 11fbe9083..da5d89b10 100644 --- a/src/adcp/types/domains/aao/agent_publishers.py +++ b/src/adcp/types/domains/aao/agent_publishers.py @@ -1,28 +1,117 @@ -"""Types declared by the AdCP ``aao/agent_publishers`` schema. +# generated by datamodel-codegen: +# filename: aao/agent_publishers.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.aao.agent_publishers import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictBool + + +class DiscoveryMethod(StrEnum): + direct = 'direct' + authoritative_location = 'authoritative_location' + adagents_authoritative = 'adagents_authoritative' + ads_txt_managerdomain = 'ads_txt_managerdomain' + + +class Status(StrEnum): + authorized = 'authorized' + revoked = 'revoked' + + +class PublisherEntry(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + publisher_domain: Annotated[ + str, + Field( + description='Publisher whose adagents.json authorizes the agent.', + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] + discovery_method: Annotated[ + DiscoveryMethod, + Field( + description="How the directory discovered this authorization. `direct`: agent listed in publisher's own /.well-known/adagents.json. `authoritative_location`: publisher's /.well-known/adagents.json declared `authoritative_location` pointing to a manager file that lists the agent. `adagents_authoritative`: discovered via the manager file (publisher-declared in the manager's own properties[]). `ads_txt_managerdomain`: discovered via the publisher's ads.txt `MANAGERDOMAIN=` directive pointing to the manager file. The last three paths converge on the same manager file but have different trust profiles — the `managerdomain` path is the weakest because the manager file's `publisher_domain` anchor (the `managerdomain` fallback safety rule) is the only positive cross-check." + ), + ] + manager_domain: Annotated[ + str | None, + Field( + description='Domain of the manager file that authorizes this publisher → agent edge. Required when `discovery_method` is `authoritative_location`, `adagents_authoritative`, or `ads_txt_managerdomain`. Null or absent when `discovery_method` is `direct`.', + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] = None + properties_authorized: Annotated[ + SchemaInt, + Field( + description="Count of properties under THIS `publisher_domain` only that the agent's selectors resolve to. Never a network-wide count. The directory computes this by applying the publisher's `adagents.json` selector predicates against the publisher's own properties (federated) OR against the parent file's inline properties carrying matching `publisher_domain` (inline, per adcp#4825 resolution rule).", + ge=0, + ), + ] + properties_total: Annotated[ + SchemaInt, + Field( + description="Count of properties under THIS `publisher_domain` only — total inventory the publisher's file declares. Never a network-wide count. On managed-network-shape parent files (per adcp#4825 inline resolution), this is the count of inline `properties[]` entries whose `publisher_domain` field matches this row's domain.", + ge=0, + ), + ] + property_ids: Annotated[ + list[str] | None, + Field( + description="Canonical list of `property_id`s under THIS `publisher_domain` that the agent's selectors resolve to. Present iff the request included `?include=properties`; absent otherwise. The set is the same population `properties_authorized` counts, surfaced as IDs so consumers can run full set-diff against a federated fetch (count-equality is not set-equality — a publisher rotating N properties leaves the count unchanged but the set entirely different). Per-publisher scope; never network-wide. Order is unspecified; consumers should treat as a set." + ), + ] = None + signing_keys_pinned: Annotated[ + StrictBool | None, + Field( + description="Whether the publisher's adagents.json entry for this agent pins `signing_keys[]`. When true, the agent's signed responses MUST verify against the pinned key set regardless of the agent's own JWKS. Operators should treat true as a signal that their published JWKS must match the publisher's pin." + ), + ] = None + status: Annotated[ + Status, + Field( + description="Lifecycle state for this publisher → agent edge. v1: `authorized` (selector resolves to ≥ 1 property under this publisher) and `revoked` (this publisher_domain newly appears in a parent file's `revoked_publisher_domains[]`; emitted as a tombstone on the next sync after revocation lands, then dropped). Future states (`unbound`, `pending`) deferred — the directory does not have the crawler state to emit them honestly." + ), + ] + last_verified_at: Annotated[ + AwareDatetime, + Field( + description="When the directory last fetched and validated this publisher's adagents.json. Distinct from the envelope's `directory_indexed_at` — `last_verified_at` is per-publisher freshness, the envelope value is the most recent refresh in the result set." + ), + ] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.aao.agent_publishers import ( - AaoDirectoryAgentPublishersInverseLookupResponse, - DiscoveryMethod, - PublisherEntry, - Status, -) - -# Explicit exports -__all__ = [ - "AaoDirectoryAgentPublishersInverseLookupResponse", - "DiscoveryMethod", - "PublisherEntry", - "Status", -] +class AaoDirectoryAgentPublishersInverseLookupResponse(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + agent_url: Annotated[ + AnyUrl, + Field( + description='Canonicalized echo of the agent_url that was looked up. Lowercase host, default port stripped, trailing slash on path component normalized — matches the canonicalization the SDK applies in `verify_agent_authorization`.' + ), + ] + directory_indexed_at: Annotated[ + AwareDatetime | None, + Field( + description="When the directory last completed a refresh of any publisher in this result. Provenance anchor for the consumer's own cache. NULL on empty pages — there's no per-publisher anchor to report; consumers SHOULD treat a null value as 'no freshness assertion for this response' and not advance their own cache freshness." + ), + ] + publishers: Annotated[ + list[PublisherEntry], + Field( + description='Publishers whose adagents.json authorizes this agent. Empty array is a valid response (the directory has indexed this agent but no current authorizations resolve).' + ), + ] + next_cursor: Annotated[ + str | None, + Field( + description="Opaque pagination cursor. Absent or null on the terminal page. Stable across the directory's refresh cycle for the lifetime of the cursor." + ), + ] = None diff --git a/src/adcp/types/domains/account/__init__.py b/src/adcp/types/domains/account/__init__.py index afb95976f..0d3483af0 100644 --- a/src/adcp/types/domains/account/__init__.py +++ b/src/adcp/types/domains/account/__init__.py @@ -9,17 +9,15 @@ it from its own schema's module, ``adcp.types.domains.account.``. Nothing here is renamed. -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:33:59 UTC +Auto-generated from the generated domain tree. DO NOT EDIT MANUALLY. +Generation date: 2026-10-04 18:45:11 UTC """ # ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.account.get_account_financials_request import ( - GetAccountFinancialsRequest, -) -from adcp.types.generated_poc.account.get_account_financials_response import ( +from adcp.types.domains.account.get_account_financials_request import GetAccountFinancialsRequest +from adcp.types.domains.account.get_account_financials_response import ( Balance, Credit, GetAccountFinancialsResponse, @@ -29,37 +27,34 @@ LastTopUp, Spend, ) -from adcp.types.generated_poc.account.list_account_changes_request import ( +from adcp.types.domains.account.list_account_changes_request import ( ListAccountChangesRequest, StartingPosition, ) -from adcp.types.generated_poc.account.list_account_changes_response import ( +from adcp.types.domains.account.list_account_changes_response import ( Kind, ListAccountChangesResponse, SourceCoverageItem, Status22, ) -from adcp.types.generated_poc.account.list_accounts_request import ListAccountsRequest -from adcp.types.generated_poc.account.list_accounts_response import ListAccountsResponse -from adcp.types.generated_poc.account.report_usage_request import ReportUsageRequest, UsageItem -from adcp.types.generated_poc.account.report_usage_response import ReportUsageResponse -from adcp.types.generated_poc.account.sync_accounts_request import ( +from adcp.types.domains.account.list_accounts_request import ListAccountsRequest +from adcp.types.domains.account.list_accounts_response import ListAccountsResponse +from adcp.types.domains.account.report_usage_request import ReportUsageRequest, UsageItem +from adcp.types.domains.account.report_usage_response import ReportUsageResponse +from adcp.types.domains.account.sync_accounts_request import ( Accounts, Accounts1, SyncAccountsRequest, ) -from adcp.types.generated_poc.account.sync_accounts_response import ( +from adcp.types.domains.account.sync_accounts_response import ( CreditLimit, Setup, SyncAccountsResponse, SyncAccountsResponse1, SyncAccountsResponse2, ) -from adcp.types.generated_poc.account.sync_governance_request import ( - Authentication, - SyncGovernanceRequest, -) -from adcp.types.generated_poc.account.sync_governance_response import ( +from adcp.types.domains.account.sync_governance_request import Authentication, SyncGovernanceRequest +from adcp.types.domains.account.sync_governance_response import ( SyncGovernanceResponse, SyncGovernanceResponse1, SyncGovernanceResponse2, diff --git a/src/adcp/types/domains/account/get_account_financials_request.py b/src/adcp/types/domains/account/get_account_financials_request.py index 2744568f5..772d2141a 100644 --- a/src/adcp/types/domains/account/get_account_financials_request.py +++ b/src/adcp/types/domains/account/get_account_financials_request.py @@ -1,20 +1,35 @@ -"""Types declared by the AdCP ``account/get_account_financials_request`` schema. +# generated by datamodel-codegen: +# filename: account/get_account_financials_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.account.get_account_financials_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field + +from ..core import account_ref +from ..core import context as context_1 +from ..core import date_range +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope -from adcp.types.generated_poc.account.get_account_financials_request import ( - GetAccountFinancialsRequest, -) -# Explicit exports -__all__ = ["GetAccountFinancialsRequest"] +class GetAccountFinancialsRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + account: Annotated[ + account_ref.AccountReference, + Field(description='Account to query financials for. Must be an operator-billed account.'), + ] + period: Annotated[ + date_range.DateRange | None, + Field( + description='Date range for the spend summary. Defaults to the current billing cycle if omitted.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/account/get_account_financials_response.py b/src/adcp/types/domains/account/get_account_financials_response.py index 23b0b411a..f83f31546 100644 --- a/src/adcp/types/domains/account/get_account_financials_response.py +++ b/src/adcp/types/domains/account/get_account_financials_response.py @@ -1,36 +1,95 @@ -"""Types declared by the AdCP ``account/get_account_financials_response`` schema. +# generated by datamodel-codegen: +# filename: account/get_account_financials_response.json +# timestamp: preserved-by-post-generate-fixes -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.account.get_account_financials_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from datetime import date + +from typing import Annotated, Literal, TypeAlias + +from pydantic import ConfigDict, Field, StringConstraints + +from ..core.version_envelope import AdcpVersionEnvelope +from ..core import account_ref as account_ref_1 +from ..core import context as context_1 +from ..core import date_range as date_range_1 +from ..core import error as error_1 +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..enums import payment_terms as payment_terms_1 + + +class Spend(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + total_spend: Annotated[float, Field(ge=0)] + media_buy_count: Annotated[int, Field(ge=0)] | None = None + + +class Credit(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + credit_limit: Annotated[float, Field(ge=0)] + available_credit: float + utilization_percent: Annotated[float, Field(ge=0, le=100)] | None = None + + +class LastTopUp(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + amount: Annotated[float, Field(ge=0)] + date: date + + +class Balance(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + available: Annotated[float, Field(ge=0)] + last_top_up: LastTopUp | None = None + + +class Invoice(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + invoice_id: str + period: date_range_1.DateRange | None = None + amount: Annotated[float, Field(ge=0)] + status: Literal['draft', 'issued', 'paid', 'past_due', 'void'] + due_date: date | None = None + paid_date: date | None = None + + +class GetAccountFinancialsResponse1(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + account: account_ref_1.AccountReference + currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] + period: date_range_1.DateRange + timezone: str + spend: Spend | None = None + credit: Credit | None = None + balance: Balance | None = None + payment_status: Literal['current', 'past_due', 'suspended'] | None = None + payment_terms: payment_terms_1.PaymentTerms | None = None + invoices: list[Invoice] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class GetAccountFinancialsResponse2(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + errors: Annotated[list[error_1.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +GetAccountFinancialsResponse: TypeAlias = GetAccountFinancialsResponse1 | GetAccountFinancialsResponse2 -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.account.get_account_financials_response import ( - Balance, - Credit, - GetAccountFinancialsResponse, - GetAccountFinancialsResponse1, - GetAccountFinancialsResponse2, - Invoice, - LastTopUp, - Spend, -) - -# Explicit exports __all__ = [ - "Balance", - "Credit", - "GetAccountFinancialsResponse", - "GetAccountFinancialsResponse1", - "GetAccountFinancialsResponse2", - "Invoice", - "LastTopUp", - "Spend", + 'GetAccountFinancialsResponse', + 'GetAccountFinancialsResponse1', + 'GetAccountFinancialsResponse2', + 'Balance', + 'Credit', + 'Invoice', + 'LastTopUp', + 'Spend', ] diff --git a/src/adcp/types/domains/account/list_account_changes_request.py b/src/adcp/types/domains/account/list_account_changes_request.py index bee84b9a2..d5cacf444 100644 --- a/src/adcp/types/domains/account/list_account_changes_request.py +++ b/src/adcp/types/domains/account/list_account_changes_request.py @@ -1,22 +1,73 @@ -"""Types declared by the AdCP ``account/list_account_changes_request`` schema. +# generated by datamodel-codegen: +# filename: account/list_account_changes_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.account.list_account_changes_request import +from adcp.types.base import AdcpRequest, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._scalar import ScalarStr -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types._str_enum import StrEnum +from typing import Annotated + +from pydantic import ConfigDict, Field + +from ..core import account_ref +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope + + +class StartingPosition(StrEnum): + earliest = 'earliest' + latest = 'latest' + + +class ResourceType(ScalarStr): + __slots__ = () + _constraints = {'max_length': 100, 'min_length': 1, 'pattern': '^[a-z][a-z0-9_.-]{0,99}$'} -from adcp.types.generated_poc.account.list_account_changes_request import ( - ListAccountChangesRequest, - ResourceType, - StartingPosition, -) -# Explicit exports -__all__ = ["ListAccountChangesRequest", "ResourceType", "StartingPosition"] +class ListAccountChangesRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + account: Annotated[ + account_ref.AccountReference, + Field( + description='Account whose change feed to read. The resolved account and returned cursor are bound to the authenticated principal.' + ), + ] + cursor: Annotated[ + str | None, + Field( + description='Opaque checkpoint returned by a prior call using the same authenticated principal, authorization scope epoch, account, and normalized filters. Returns changes strictly after the scanned high-water represented by this value. Sellers MUST reject reuse under a different principal, account, or filter set with INVALID_REQUEST at field cursor; an authorization-scope epoch change returns CURSOR_EXPIRED and requires snapshot rebootstrap.', + max_length=4096, + min_length=1, + ), + ] = None + starting_position: Annotated[ + StartingPosition | None, + Field( + description='Initial position when cursor is absent. Mutually exclusive with cursor; sellers enforce this semantic rule at runtime so the emitted MCP input schema can remain a plain root object. earliest intentionally begins at the oldest retained change. latest returns a checkpoint at the current seller-ingestion high-water and is used before a race-free snapshot bootstrap.' + ), + ] = StartingPosition.earliest + resource_types: Annotated[ + list[ResourceType] | None, + Field( + description='Optional exact resource-type filter. The cursor is bound to the normalized filter. Unknown resource types are allowed for forward compatibility.', + max_length=50, + min_length=1, + ), + ] = None + max_results: Annotated[ + SchemaInt | None, + Field( + description='Maximum changes to return. Sellers still scan through nonmatching records and advance the returned cursor.', + ge=1, + le=100, + ), + ] = 50 + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/account/list_account_changes_response.py b/src/adcp/types/domains/account/list_account_changes_response.py index 07f650210..760eaf0ef 100644 --- a/src/adcp/types/domains/account/list_account_changes_response.py +++ b/src/adcp/types/domains/account/list_account_changes_response.py @@ -1,32 +1,142 @@ -"""Types declared by the AdCP ``account/list_account_changes_response`` schema. +# generated by datamodel-codegen: +# filename: account/list_account_changes_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.account.list_account_changes_response import +from adcp.types.base import AdcpResponse, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import AwareDatetime, ConfigDict, Field, StrictBool, model_validator + +from ..core import account_change +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope + + +class Kind(StrEnum): + seller = 'seller' + connected_platform = 'connected_platform' + + +class Status(StrEnum): + current = 'current' + delayed = 'delayed' + unavailable = 'unavailable' + not_configured = 'not_configured' + + +class ResourceType(ScalarStr): + __slots__ = () + _constraints = {'max_length': 100, 'min_length': 1, 'pattern': '^[a-z][a-z0-9_.-]{0,99}$'} + + +class SourceCoverageItem(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + source_id: Annotated[ + str, Field(description='Opaque seller or connection reference.', max_length=255) + ] + kind: Kind + status: Annotated[ + Status, + Field( + description='Connector freshness classification. For connected_platform, current means last_successful_sync_at is no older than stale_after_seconds at generated_at and the seller knows of no ingestion gap; delayed means that threshold is exceeded or a gap is known.' + ), + ] + coverage_start: Annotated[ + AwareDatetime | None, + Field( + description='Earliest upstream time covered by this source after connection or retention limits.' + ), + ] = None + observed_through: Annotated[ + AwareDatetime | None, + Field(description='Latest upstream point the seller has successfully observed.'), + ] = None + last_successful_sync_at: AwareDatetime | None = None + stale_after_seconds: Annotated[ + SchemaInt | None, + Field( + description='Maximum age of last_successful_sync_at at generated_at for this connected source to self-classify as current. Required by the protocol contract whenever a connected source reports current.', + ge=1, + le=2592000, + ), + ] = None + resource_types: Annotated[list[ResourceType], Field(max_length=50)] + + +class Status22(StrEnum): + completed = 'completed' + failed = 'failed' + + +class ListAccountChangesResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + changes: Annotated[ + list[account_change.AccountChange] | None, + Field( + description='Matching changes in oldest-first total account order. Timestamps are descriptive and do not define this order.', + max_length=100, + ), + ] = None + cursor: Annotated[ + str | None, + Field( + description='Opaque checkpoint strictly after the high-water scanned by this page. Always persist this value, including when changes is empty or has_more is false. A filtered empty page still advances past scanned nonmatching records.', + max_length=4096, + min_length=1, + ), + ] = None + has_more: Annotated[ + StrictBool | None, + Field( + description='True when more retained matching changes were available at generation time. False means caught up to seller ingestion, not necessarily to an unavailable or delayed connected source.' + ), + ] = None + available_since: Annotated[ + AwareDatetime | None, + Field( + description='Oldest time for which the seller currently retains change records for this account and caller. The capability guarantees at least 90 days after adoption; pre-adoption history is not fabricated.' + ), + ] = None + generated_at: Annotated[ + AwareDatetime | None, + Field( + description='Seller time when this page and its source-coverage watermarks were generated.' + ), + ] = None + source_coverage: Annotated[ + list[SourceCoverageItem] | None, + Field( + description='Account-specific feed and connector coverage. Buyers use this to distinguish feed catch-up from upstream freshness. Omission means no additional connected-source coverage is declared.', + max_length=50, + ), + ] = None + errors: list[error.Error] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + status: Status22 -from adcp.types.generated_poc.account.list_account_changes_response import ( - Kind, - ListAccountChangesResponse, - ResourceType, - SourceCoverageItem, - Status, - Status22, -) - -# Explicit exports -__all__ = [ - "Kind", - "ListAccountChangesResponse", - "ResourceType", - "SourceCoverageItem", - "Status", - "Status22", -] + @model_validator(mode='after') + def _require_schema_required_group(self) -> ListAccountChangesResponse: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('status', 'changes', 'cursor', 'has_more', 'available_since', 'generated_at'), ('status', 'adcp_error', 'errors'),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'ListAccountChangesResponse requires at least one of these field groups: status+changes+cursor+has_more+available_since+generated_at | status+adcp_error+errors' + ) diff --git a/src/adcp/types/domains/account/list_accounts_request.py b/src/adcp/types/domains/account/list_accounts_request.py index 4d20f0e11..ab82c8a2f 100644 --- a/src/adcp/types/domains/account/list_accounts_request.py +++ b/src/adcp/types/domains/account/list_accounts_request.py @@ -1,18 +1,66 @@ -"""Types declared by the AdCP ``account/list_accounts_request`` schema. +# generated by datamodel-codegen: +# filename: account/list_accounts_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.account.list_accounts_request import +from adcp.types.base import AdcpRequest, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field, StrictBool + +from ..core import account_ref +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core import pagination_request +from ..core.version_envelope import AdcpVersionEnvelope + + +class Status(StrEnum): + active = 'active' + pending_approval = 'pending_approval' + rejected = 'rejected' + payment_required = 'payment_required' + suspended = 'suspended' + closed = 'closed' -from adcp.types.generated_poc.account.list_accounts_request import ListAccountsRequest, Status -# Explicit exports -__all__ = ["ListAccountsRequest", "Status"] +class ListAccountsRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + account: Annotated[ + account_ref.AccountReference | None, + Field( + description='Optional exact account filter. Use `account_id` to retrieve one known seller/storefront account, or the complete natural key (`brand` + `operator` + optional `operator_unit`, fixed `currency`, buyer-selected account `timezone`, and `sandbox`) for buyer-declared accounts. When present, the seller returns only matching accounts visible to the authenticated caller.' + ), + ] = None + status: Annotated[ + Status | None, + Field(description='Filter accounts by status. Omit to return accounts in all statuses.'), + ] = None + pagination: pagination_request.PaginationRequest | None = None + sandbox: Annotated[ + StrictBool | None, + Field( + description='Filter by sandbox status. true returns only sandbox accounts, false returns only production accounts. Omit to return all accounts. Primarily used with account-id namespaces where sandbox accounts are pre-existing test accounts on the platform.' + ), + ] = None + include_webhook_activity: Annotated[ + StrictBool | None, + Field( + description='When true, request recent webhook delivery attempts for each returned account in account.webhook_activity[]. Sellers MAY omit webhook_activity if they do not expose this debug log; when present, three-state semantics match the shared webhook_activity[] contract.' + ), + ] = False + webhook_activity_limit: Annotated[ + SchemaInt | None, + Field( + description='Maximum number of webhook_activity[] records to return per account when include_webhook_activity is true.', + ge=1, + le=200, + ), + ] = 50 + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/account/list_accounts_response.py b/src/adcp/types/domains/account/list_accounts_response.py index 7b102ea93..f0194d25e 100644 --- a/src/adcp/types/domains/account/list_accounts_response.py +++ b/src/adcp/types/domains/account/list_accounts_response.py @@ -1,18 +1,37 @@ -"""Types declared by the AdCP ``account/list_accounts_response`` schema. +# generated by datamodel-codegen: +# filename: account/list_accounts_response.json +# timestamp: 2026-10-04T02:05:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.account.list_accounts_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field + +from ..core import account_with_authorization +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 +from ..core import pagination_response +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope -from adcp.types.generated_poc.account.list_accounts_response import ListAccountsResponse -# Explicit exports -__all__ = ["ListAccountsResponse"] +class ListAccountsResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + accounts: Annotated[ + list[account_with_authorization.AccountWithAuthorization], + Field( + description='Array of accounts accessible to the authenticated agent. Each entry is the full Account object plus optional authorization. Buyer-declared entries include brand, operator, and any operator_unit, fixed currency, buyer-selected account timezone, and sandbox qualifiers so the natural AccountRef round-trips after a cold start.' + ), + ] + errors: Annotated[ + list[error.Error] | None, Field(description='Task-specific errors and warnings') + ] = None + pagination: pagination_response.PaginationResponse | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/account/report_usage_request.py b/src/adcp/types/domains/account/report_usage_request.py index db2cba417..cb6fa0e5c 100644 --- a/src/adcp/types/domains/account/report_usage_request.py +++ b/src/adcp/types/domains/account/report_usage_request.py @@ -1,18 +1,160 @@ -"""Types declared by the AdCP ``account/report_usage_request`` schema. +# generated by datamodel-codegen: +# filename: account/report_usage_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.account.report_usage_request import +from adcp.types.base import AdcpRequest, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat + +from ..core import account_ref +from ..core import context as context_1 +from ..core import datetime_range +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope + + +class UsageItem(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + account: Annotated[ + account_ref.AccountReference, Field(description='Account for this usage record.') + ] + media_buy_id: Annotated[ + str | None, + Field( + description='Seller-assigned media buy identifier. Links this usage record to a specific media buy.' + ), + ] = None + vendor_cost: Annotated[ + StrictFloat, + Field( + description='Amount owed to the vendor for this record, denominated in currency.', + ge=0.0, + ), + ] + currency: Annotated[str, Field(description='ISO 4217 currency code.', pattern='^[A-Z]{3}$')] + pricing_option_id: Annotated[ + str | None, + Field( + description="Pricing option identifier from the vendor's discovery response (e.g., get_signals, list_content_standards). The vendor uses this to verify the correct rate was applied." + ), + ] = None + impressions: Annotated[ + SchemaInt | None, + Field(description='Impressions delivered using this vendor service.', ge=0), + ] = None + media_spend: Annotated[ + StrictFloat | None, + Field( + description='Media spend in currency for the period. Required when a percent_of_media pricing model was used, so the vendor can verify the applied rate.', + ge=0.0, + ), + ] = None + conversions: Annotated[ + StrictFloat | None, + Field( + description='Number of attributed conversion events for the reporting period. Optional analytics context for revenue_share reconciliation.', + ge=0.0, + ), + ] = None + conversion_value: Annotated[ + StrictFloat | None, + Field( + description='Total monetary value of attributed conversions for the reporting period, in currency. Optional analytics context for revenue_share reconciliation; it is not the billing basis.', + ge=0.0, + ), + ] = None + commissionable_value: Annotated[ + StrictFloat | None, + Field( + description='Settled attributed value eligible for commission, in currency. Required when pricing_option_id selects a revenue_share option. The receiver verifies vendor_cost = round_currency(commissionable_value × the selected commission_rate).', + ge=0.0, + ), + ] = None + signal_agent_segment_id: Annotated[ + str | None, + Field(description='Signal identifier from get_signals. Required for signals agents.'), + ] = None + standards_id: Annotated[ + str | None, + Field( + description='Content standards configuration identifier. Required for governance agents.' + ), + ] = None + rights_id: Annotated[ + str | None, + Field( + description='Rights grant identifier from acquire_rights. Required for brand/rights agents. Links usage records to specific rights grants for cap tracking, billing verification, and overage calculation.' + ), + ] = None + creative_id: Annotated[ + str | None, + Field( + description='Creative identifier from build_creative or list_creatives. Required for creative agents. Links usage records to specific creatives for billing verification.' + ), + ] = None + build_variant_id: Annotated[ + str | None, + Field( + description='Optional. When the reported creative_id was promoted from a specific build_creative variant leaf but the creative_id differs from the source build_variant_id, carry that source build_variant_id so billing reconciliation can link this usage record back to the exact produced leaf for audit (pricing_option_id alone is not unique across leaves). On the canonical path where creative_id is the build_variant_id, omit this field and use creative_id as the join key. Omit for creatives with no build-variant lineage.' + ), + ] = None + property_list_id: Annotated[ + str | None, + Field( + description='Property list identifier from list_property_lists. Required for property list agents. Links usage records to specific property lists for billing verification.' + ), + ] = None + final: Annotated[ + StrictBool | None, + Field( + description="Whether this usage record represents the reporter's final, billing-authoritative numbers for the reporting period. **Absent means unknown** — the reporter has not declared finality on this record. Set `true` only when the reporter has actually settled the numbers (e.g., 3PAS month-end close after SIVT scrubbing, conversion dedup, and view-through windows have closed; vendor file post-C7 for broadcast). Set `false` when pushing preliminary measurements (daily pacing pushes, intra-period progress) that are still settling. Receivers MUST NOT invoice on `final: false` records, and MUST NOT invoice on records where `final` is absent for buys whose `measurement_terms.billing_measurement` names this reporter as authoritative — request a final record first. Receivers MAY invoice on absent for buys with no `measurement_terms.billing_measurement` (3.0-style usage where the receiver treats reports as authoritative on receipt) and for non-media-buy variants (signals, governance, creative, brand — domains with no provisional state concept). When the same `(account, media_buy_id, reporting_period)` is later reported with `final: true`, that record supersedes any prior records for the period." + ), + ] = None + finalized_at: Annotated[ + AwareDatetime | None, + Field( + description="ISO 8601 timestamp at which the reporter considered these numbers final. Present only when `final: true`. Anchors any deadline declared in the buy's `measurement_terms.billing_measurement.finalization_deadline_hours`." + ), + ] = None + measurement_window: Annotated[ + str | None, + Field( + description="Which measurement window this record represents, referencing a window_id from the product's reporting_capabilities.measurement_windows or from `measurement_terms.billing_measurement.measurement_window`. Examples: 'c7' for broadcast TV, 'post_sivt' for digital post-IVT, 'downloads_30d' for podcast. When absent, the record is not windowed (standard digital reporting). When the buy's `measurement_terms.billing_measurement.measurement_window` is set, reporters SHOULD include `measurement_window` so the receiver can reconcile against the correct stage.", + examples=['live', 'c3', 'c7', 'post_ivt', 'post_sivt', 'downloads_30d'], + max_length=50, + ), + ] = None -from adcp.types.generated_poc.account.report_usage_request import ReportUsageRequest, UsageItem -# Explicit exports -__all__ = ["ReportUsageRequest", "UsageItem"] +class ReportUsageRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + idempotency_key: Annotated[ + str, + Field( + description='Client-generated unique key for this request. If a request with the same key has already been accepted, the server returns the original response without re-processing. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request. Prevents duplicate billing on retries.' + ), + ] + reporting_period: Annotated[ + datetime_range.DatetimeRange, + Field( + description='The time range covered by this usage report. Applies to all records in the request.' + ), + ] + usage: Annotated[ + list[UsageItem], + Field( + description='One or more usage records. Each record is self-contained: it carries its own account, allowing a single request to span multiple accounts.', + min_length=1, + ), + ] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/account/report_usage_response.py b/src/adcp/types/domains/account/report_usage_response.py index 212a7c776..97da5b656 100644 --- a/src/adcp/types/domains/account/report_usage_response.py +++ b/src/adcp/types/domains/account/report_usage_response.py @@ -1,18 +1,38 @@ -"""Types declared by the AdCP ``account/report_usage_response`` schema. +# generated by datamodel-codegen: +# filename: account/report_usage_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.account.report_usage_response import +from adcp.types.base import AdcpResponse, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field, StrictBool + +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope -from adcp.types.generated_poc.account.report_usage_response import ReportUsageResponse -# Explicit exports -__all__ = ["ReportUsageResponse"] +class ReportUsageResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + accepted: Annotated[ + SchemaInt, Field(description='Number of usage records successfully stored.', ge=0) + ] + errors: Annotated[ + list[error.Error] | None, + Field( + description="Validation errors for individual records. The field property identifies which record failed (e.g., 'usage[1].pricing_option_id')." + ), + ] = None + sandbox: Annotated[ + StrictBool | None, + Field(description='When true, the account is a sandbox account and no billing occurred.'), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/account/sync_accounts_request.py b/src/adcp/types/domains/account/sync_accounts_request.py index f6c83b415..4e077a0b5 100644 --- a/src/adcp/types/domains/account/sync_accounts_request.py +++ b/src/adcp/types/domains/account/sync_accounts_request.py @@ -1,22 +1,282 @@ -"""Types declared by the AdCP ``account/sync_accounts_request`` schema. +# generated by datamodel-codegen: +# filename: account/sync_accounts_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.account.sync_accounts_request import +from adcp.types.base import AdcpRequest, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictBool + +from ..core import account_ref, brand_ref, business_entity +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core import notification_config +from ..core import operator_identity as operator_identity_1 +from ..core import operator_unit as operator_unit_1 +from ..core import push_notification_config as push_notification_config_1 +from ..core import reporting_delivery_config +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import billing_party, cloud_storage_protocol +from ..enums import payment_terms as payment_terms_1 + + +class Accounts(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + account: Annotated[ + account_ref.AccountReference | None, + Field( + description='Settings-update key. When present, this entry targets an existing account by `account_id` (seller-owned account namespace) or natural key (buyer-declared account settings-update against a previously-provisioned account). Mutually exclusive with the flat `brand` + `operator` + `billing` provisioning trio. When `account` is present, the seller MUST NOT create a new account — entries that would otherwise trigger provisioning are rejected with `UNSUPPORTED_PROVISIONING`.' + ), + ] = None + revision: Annotated[ + SchemaInt | None, + Field( + description='Expected current account revision for optimistic concurrency in settings-update mode. Required whenever operator_identity is present; optional for existing non-identity settings updates. The seller MUST compare it atomically with the write, reject a mismatch with CONFLICT, and leave the account unchanged. Obtain it from list_accounts or the most recent sync_accounts result. Reads, dry runs, validation failures, and exact idempotency replays do not increment revision; every persisted settings or identity-change state transition does. MUST be absent in provisioning mode.', + ge=1, + ), + ] = None + operator_identity: Annotated[ + operator_identity_1.OperatorIdentity | None, + Field( + description='Complete desired operator identity for settings-update mode. Omit this field to leave operator identity unchanged. When present, omission of operator_unit within the object removes the existing unit. Changing only operator_unit_1.name updates display metadata; changing operator_unit_1.id or adding/removing a unit rekeys the same account within the current operator. Changing operator requests an inter-entity handoff and MUST enter pending_approval until the seller verifies the current account authority, verified brand authorization, destination-operator acceptance, and any operator-scoped billing and grant transition. The seller MUST preserve account_id and account-scoped historical resources, MUST reject collisions without merging, and MUST apply no identity change if continuity cannot be preserved. MUST be accompanied by revision and MUST be absent in provisioning mode.' + ), + ] = None + destination_billing_entity: Annotated[ + business_entity.BusinessEntity | None, + Field( + description="Complete staged billing identity for the requested destination operator during an operator-domain handoff on an account whose billing party is operator. This value is write-only while approval is pending and MUST NOT replace or be echoed as the account's canonical billing_entity until the handoff applies atomically. Required by the protocol when an operator-billed account changes operator; otherwise MUST be absent. Requires operator_identity and revision and MUST be absent in provisioning mode." + ), + ] = None + brand: Annotated[ + brand_ref.BrandReference, + Field( + description='Brand reference identifying the advertiser. Required for **provisioning mode**; MUST be absent in settings-update mode. Only the BrandKey projection — `domain`, `brand_id`, and the canonicalized `countries[]` set — participates in account identity. Mutable or per-call BrandRef fields such as `industries`, `data_subject_contestation`, and `brand_kit_override` MUST NOT affect lookup, idempotency, or account creation. New 3.2 producers SHOULD send only the BrandKey fields; the broader BrandRef remains accepted on this existing 3.x task for compatibility.' + ), + ] + operator: Annotated[ + str, + Field( + description="Domain of the entity operating on the brand's behalf (e.g., 'pinnacle-media.com'). When the brand operates directly, this is the brand's domain. Verified against the brand's authorized_operators in brand.json. Required for **provisioning mode**; MUST be absent in settings-update mode.", + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] + operator_unit: Annotated[ + operator_unit_1.OperatorUnit | None, + Field( + description='Optional operator-owned business unit, agency seat, or platform account for provisioning mode. operator_unit_1.id participates in the natural key; name is mapping/display metadata. MUST be absent in settings-update mode.' + ), + ] = None + currency: Annotated[ + str | None, + Field( + description='Optional immutable ISO 4217 transaction currency for a currency-bound advertiser object. Consult `account.supported_account_currency_modes` before provisioning. When supplied, it participates in the natural key and all media buys on the account use it. Omit for per-media-buy currency selection. MUST be absent in settings-update mode.', + pattern='^[A-Z]{3}$', + ), + ] = None + timezone: Annotated[ + str | None, + Field( + description='Immutable operational timezone selected for an account_fixed advertiser object. Required in provisioning mode when get_adcp_capabilities.account.timezone declares account_selection: buyer_selected, and the value MUST be one of supported_timezones. Omit for seller_fixed or seller_assigned modes. When supplied, it participates in the natural key. MUST be absent in settings-update mode.', + min_length=1, + ), + ] = None + billing: Annotated[ + billing_party.BillingParty, + Field( + description='Who the seller invoices for this buyer–storefront account relationship. Required for **provisioning mode**; MUST be absent in settings-update mode (the invoiced party is fixed at provisioning time and cannot be changed via settings-update). This field does not select a payment rail, clearing intermediary, or per-media-buy settlement route.' + ), + ] + billing_entity: Annotated[ + business_entity.BusinessEntity | None, + Field( + description='Business entity details for the party responsible for payment. The agent provides this so the seller has the legal name, tax IDs, address, and bank details needed for formal B2B invoicing. Permitted in both modes — sellers MAY accept refinements in settings-update mode (e.g., updated bank details).' + ), + ] = None + payment_terms: Annotated[ + payment_terms_1.PaymentTerms | None, + Field( + description='Payment terms for this account. The seller must either accept these terms or reject the account — terms are never silently remapped. When omitted, the seller applies its default terms. Permitted in both modes.' + ), + ] = None + sandbox: Annotated[ + StrictBool | None, + Field( + description='When true, provision this as a sandbox account with no real platform calls or billing. Only applicable to buyer-declared accounts (require_operator_auth: false) in provisioning mode. For account-id namespaces, sandbox accounts are pre-existing test accounts discovered via list_accounts or supplied out-of-band.' + ), + ] = None + preferred_reporting_protocol: Annotated[ + cloud_storage_protocol.CloudStorageProtocol | None, + Field( + description="Buyer's preferred cloud storage protocol for offline reporting delivery. The seller provisions the account's reporting_bucket using this protocol if supported. When omitted, the seller chooses from its supported offline_delivery_protocols. Only meaningful when the seller's reporting_delivery_methods includes 'offline'." + ), + ] = None + reporting_delivery_configs: Annotated[ + list[reporting_delivery_config.ReportingDeliveryConfiguration] | None, + Field( + description="Caller-owned desired state for durable reporting delivery on this account. Declarative replacement is scoped to (authenticated caller, resolved account): omission leaves that caller's set unchanged; [] deactivates that caller's set and starts grant revocation; another caller's entries MUST NOT be read, replaced, or deleted. Entries are keyed by immutable (delivery_config_id, delivery_config_version); duplicate tuples MUST reject the entire account entry, and reusing a tuple with changed content MUST be rejected. Each generation binds the exact report_definition_id advertised by its offering. destination.mode provision asks the seller to verify caller disclosure authority and destination/recipient control from non-secret provider coordinates; destination.mode existing reuses a caller-scoped immutable destination-generation reference, including one registered through sync_agent_configuration. The account configuration independently authorizes disclosure for this feed and scope, so possession of a reusable reference is never account authority. Unknown, unauthorized, and cross-caller refs MUST be indistinguishable. Credentials never transit AdCP, including nested extension fields. Permitted in both provisioning and settings-update modes. Sellers accepting this field MUST advertise media_buy.reporting_delivery in experimental_features and echo resolved secret-free state on sync_accounts and list_accounts.", + max_length=16, + ), + ] = None + notification_configs: Annotated[ + list[notification_config.NotificationConfig] | None, + Field( + description='Account-level webhook subscriptions for notifications whose lifecycle outlives any single media buy (`creative.status_changed`, optional `creative.assignment_changed`, `indicators.changed`, `creative.purged`, `account.status_changed`, wholesale feed change payloads, and future account-anchored resource events after those event types are added to `notification-config.json`). Indicator and assignment registrations are prospective: activation does not replay current conditions, so buyers establish a complete baseline through `get_media_buys` by enumerating known IDs or requesting every status and exhausting pagination, without an indicator filter. Durable account lifecycle transitions such as later `payment_required`, `suspended`, `closed`, or recovery to `active` use `account.status_changed` on this surface; the one-shot `sync_accounts.push_notification_config` channel remains scoped to the async result of the original provisioning task. Declarative replace semantics: when this field is present, the buyer sends the full desired array and the seller replaces the account\'s current set with that array, keyed by account-scoped `subscriber_id`. Omit this field to leave existing subscribers unchanged; send `[]` to remove all subscribers. Re-sending an existing `subscriber_id` for the account replaces that subscriber\'s config rather than creating a duplicate; persisted entries whose `subscriber_id` does not appear in the sent array are removed, so the seller MUST NOT merge the new array with persisted state. Paused entries (`active: false`) use the same replacement semantics; a buyer that wants to preserve a paused subscriber MUST re-include it with `active: false`. Duplicate `subscriber_id` values within one submitted array are rejected. Permitted in both provisioning and settings-update modes. Each entry registers a URL, the event types the subscriber wants, and optional legacy auth — see [`notification-config.json`](/schemas/core/notification-config.json). The seller MUST echo applied state on the response and on `list_accounts` reads, with `authentication.credentials` omitted (write-only). Sellers MUST reject entries whose `event_types` include any type whose contract anchors at a media buy or below (today: `scheduled`, `final`, `delayed`, `adjusted`, `window_update`, `impairment`) or at the agent (today: `capabilities.changed`) as per-account validation failures with `INVALID_REQUEST` or `VALIDATION_ERROR` and `error.field` pointing at the invalid `event_types` entry — those events do not belong on this surface. Wholesale feed webhook registrations carry the actual change payload in `/schemas/core/wholesale-feed-webhook.json`; canonical product subscribers repair through `list_products(if_feed_version)`, legacy product subscribers through `get_products(if_wholesale_feed_version)`, and signal subscribers through `get_signals(if_wholesale_feed_version)`. Account status change registrations carry the invalidation payload in `/schemas/core/account-status-changed-webhook.json`; receivers use `list_accounts` to repair or reconcile. This is distinct from sync_catalogs, which manages buyer-provided campaign input feeds on a seller account.\n\nActivation proof: before activating a new or changed active subscriber, the seller MUST validate the URL, complete the account-level webhook proof-of-control challenge, and only then persist or expose the subscriber as `active: true`. For `account.status_changed`, sellers MUST assign `account_id` before completing proof so subsequent status transitions can identify the account and be repaired through `list_accounts`, even when external approval remains pending. A valid existing proof for the same `(account_id, subscriber_id, normalized url, authentication mode/credential binding, normalized event_types)` tuple MAY be reused; changing any element of that tuple requires fresh proof. The challenge POST itself MUST be signed with the seller\'s RFC 9421 webhook profile key and MUST include seller_agent_url, delivery_auth, and event_types so the receiver can verify the pending registration before echoing the challenge. New signers use `adcp_use: "request-signing"`; deprecated `webhook-signing` keys remain accepted during the compatibility window. Entries sent with `active: false` may skip only the outbound proof challenge while inactive; sellers MUST still enforce URL parsing, HTTPS, hostname normalization, and reserved-range rejection at write time, and those entries MUST NOT receive fires until reactivated. If proof fails or times out, the seller rejects the account entry with `action: "failed"`, leaves the prior notification_configs[] set unchanged, and reports `VALIDATION_ERROR` (or `INVALID_REQUEST` for malformed URLs) at the failing `notification_configs[j].url` field.\n\n**Cap rationale:** `maxItems: 16` is a practical fan-out cap (governance + buyer ingestion + audit bus + dx team + a few partner hooks). The cap exists to prevent unbounded subscriber arrays in storage and to bound the seller\'s per-event fan-out work. Sellers that hit the cap with legitimate subscribers should surface this on the protocol roadmap rather than work around it.', + max_length=16, + ), + ] = None + + +class Accounts1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + account: Annotated[ + account_ref.AccountReference, + Field( + description='Settings-update key. When present, this entry targets an existing account by `account_id` (seller-owned account namespace) or natural key (buyer-declared account settings-update against a previously-provisioned account). Mutually exclusive with the flat `brand` + `operator` + `billing` provisioning trio. When `account` is present, the seller MUST NOT create a new account — entries that would otherwise trigger provisioning are rejected with `UNSUPPORTED_PROVISIONING`.' + ), + ] + revision: Annotated[ + SchemaInt | None, + Field( + description='Expected current account revision for optimistic concurrency in settings-update mode. Required whenever operator_identity is present; optional for existing non-identity settings updates. The seller MUST compare it atomically with the write, reject a mismatch with CONFLICT, and leave the account unchanged. Obtain it from list_accounts or the most recent sync_accounts result. Reads, dry runs, validation failures, and exact idempotency replays do not increment revision; every persisted settings or identity-change state transition does. MUST be absent in provisioning mode.', + ge=1, + ), + ] = None + operator_identity: Annotated[ + operator_identity_1.OperatorIdentity | None, + Field( + description='Complete desired operator identity for settings-update mode. Omit this field to leave operator identity unchanged. When present, omission of operator_unit within the object removes the existing unit. Changing only operator_unit_1.name updates display metadata; changing operator_unit_1.id or adding/removing a unit rekeys the same account within the current operator. Changing operator requests an inter-entity handoff and MUST enter pending_approval until the seller verifies the current account authority, verified brand authorization, destination-operator acceptance, and any operator-scoped billing and grant transition. The seller MUST preserve account_id and account-scoped historical resources, MUST reject collisions without merging, and MUST apply no identity change if continuity cannot be preserved. MUST be accompanied by revision and MUST be absent in provisioning mode.' + ), + ] = None + destination_billing_entity: Annotated[ + business_entity.BusinessEntity | None, + Field( + description="Complete staged billing identity for the requested destination operator during an operator-domain handoff on an account whose billing party is operator. This value is write-only while approval is pending and MUST NOT replace or be echoed as the account's canonical billing_entity until the handoff applies atomically. Required by the protocol when an operator-billed account changes operator; otherwise MUST be absent. Requires operator_identity and revision and MUST be absent in provisioning mode." + ), + ] = None + brand: Annotated[ + brand_ref.BrandReference | None, + Field( + description='Brand reference identifying the advertiser. Required for **provisioning mode**; MUST be absent in settings-update mode. Only the BrandKey projection — `domain`, `brand_id`, and the canonicalized `countries[]` set — participates in account identity. Mutable or per-call BrandRef fields such as `industries`, `data_subject_contestation`, and `brand_kit_override` MUST NOT affect lookup, idempotency, or account creation. New 3.2 producers SHOULD send only the BrandKey fields; the broader BrandRef remains accepted on this existing 3.x task for compatibility.' + ), + ] = None + operator: Annotated[ + str | None, + Field( + description="Domain of the entity operating on the brand's behalf (e.g., 'pinnacle-media.com'). When the brand operates directly, this is the brand's domain. Verified against the brand's authorized_operators in brand.json. Required for **provisioning mode**; MUST be absent in settings-update mode.", + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] = None + operator_unit: Annotated[ + operator_unit_1.OperatorUnit | None, + Field( + description='Optional operator-owned business unit, agency seat, or platform account for provisioning mode. operator_unit_1.id participates in the natural key; name is mapping/display metadata. MUST be absent in settings-update mode.' + ), + ] = None + currency: Annotated[ + str | None, + Field( + description='Optional immutable ISO 4217 transaction currency for a currency-bound advertiser object. Consult `account.supported_account_currency_modes` before provisioning. When supplied, it participates in the natural key and all media buys on the account use it. Omit for per-media-buy currency selection. MUST be absent in settings-update mode.', + pattern='^[A-Z]{3}$', + ), + ] = None + timezone: Annotated[ + str | None, + Field( + description='Immutable operational timezone selected for an account_fixed advertiser object. Required in provisioning mode when get_adcp_capabilities.account.timezone declares account_selection: buyer_selected, and the value MUST be one of supported_timezones. Omit for seller_fixed or seller_assigned modes. When supplied, it participates in the natural key. MUST be absent in settings-update mode.', + min_length=1, + ), + ] = None + billing: Annotated[ + billing_party.BillingParty | None, + Field( + description='Who the seller invoices for this buyer–storefront account relationship. Required for **provisioning mode**; MUST be absent in settings-update mode (the invoiced party is fixed at provisioning time and cannot be changed via settings-update). This field does not select a payment rail, clearing intermediary, or per-media-buy settlement route.' + ), + ] = None + billing_entity: Annotated[ + business_entity.BusinessEntity | None, + Field( + description='Business entity details for the party responsible for payment. The agent provides this so the seller has the legal name, tax IDs, address, and bank details needed for formal B2B invoicing. Permitted in both modes — sellers MAY accept refinements in settings-update mode (e.g., updated bank details).' + ), + ] = None + payment_terms: Annotated[ + payment_terms_1.PaymentTerms | None, + Field( + description='Payment terms for this account. The seller must either accept these terms or reject the account — terms are never silently remapped. When omitted, the seller applies its default terms. Permitted in both modes.' + ), + ] = None + sandbox: Annotated[ + StrictBool | None, + Field( + description='When true, provision this as a sandbox account with no real platform calls or billing. Only applicable to buyer-declared accounts (require_operator_auth: false) in provisioning mode. For account-id namespaces, sandbox accounts are pre-existing test accounts discovered via list_accounts or supplied out-of-band.' + ), + ] = None + preferred_reporting_protocol: Annotated[ + cloud_storage_protocol.CloudStorageProtocol | None, + Field( + description="Buyer's preferred cloud storage protocol for offline reporting delivery. The seller provisions the account's reporting_bucket using this protocol if supported. When omitted, the seller chooses from its supported offline_delivery_protocols. Only meaningful when the seller's reporting_delivery_methods includes 'offline'." + ), + ] = None + reporting_delivery_configs: Annotated[ + list[reporting_delivery_config.ReportingDeliveryConfiguration] | None, + Field( + description="Caller-owned desired state for durable reporting delivery on this account. Declarative replacement is scoped to (authenticated caller, resolved account): omission leaves that caller's set unchanged; [] deactivates that caller's set and starts grant revocation; another caller's entries MUST NOT be read, replaced, or deleted. Entries are keyed by immutable (delivery_config_id, delivery_config_version); duplicate tuples MUST reject the entire account entry, and reusing a tuple with changed content MUST be rejected. Each generation binds the exact report_definition_id advertised by its offering. destination.mode provision asks the seller to verify caller disclosure authority and destination/recipient control from non-secret provider coordinates; destination.mode existing reuses a caller-scoped immutable destination-generation reference, including one registered through sync_agent_configuration. The account configuration independently authorizes disclosure for this feed and scope, so possession of a reusable reference is never account authority. Unknown, unauthorized, and cross-caller refs MUST be indistinguishable. Credentials never transit AdCP, including nested extension fields. Permitted in both provisioning and settings-update modes. Sellers accepting this field MUST advertise media_buy.reporting_delivery in experimental_features and echo resolved secret-free state on sync_accounts and list_accounts.", + max_length=16, + ), + ] = None + notification_configs: Annotated[ + list[notification_config.NotificationConfig] | None, + Field( + description='Account-level webhook subscriptions for notifications whose lifecycle outlives any single media buy (`creative.status_changed`, optional `creative.assignment_changed`, `indicators.changed`, `creative.purged`, `account.status_changed`, wholesale feed change payloads, and future account-anchored resource events after those event types are added to `notification-config.json`). Indicator and assignment registrations are prospective: activation does not replay current conditions, so buyers establish a complete baseline through `get_media_buys` by enumerating known IDs or requesting every status and exhausting pagination, without an indicator filter. Durable account lifecycle transitions such as later `payment_required`, `suspended`, `closed`, or recovery to `active` use `account.status_changed` on this surface; the one-shot `sync_accounts.push_notification_config` channel remains scoped to the async result of the original provisioning task. Declarative replace semantics: when this field is present, the buyer sends the full desired array and the seller replaces the account\'s current set with that array, keyed by account-scoped `subscriber_id`. Omit this field to leave existing subscribers unchanged; send `[]` to remove all subscribers. Re-sending an existing `subscriber_id` for the account replaces that subscriber\'s config rather than creating a duplicate; persisted entries whose `subscriber_id` does not appear in the sent array are removed, so the seller MUST NOT merge the new array with persisted state. Paused entries (`active: false`) use the same replacement semantics; a buyer that wants to preserve a paused subscriber MUST re-include it with `active: false`. Duplicate `subscriber_id` values within one submitted array are rejected. Permitted in both provisioning and settings-update modes. Each entry registers a URL, the event types the subscriber wants, and optional legacy auth — see [`notification-config.json`](/schemas/core/notification-config.json). The seller MUST echo applied state on the response and on `list_accounts` reads, with `authentication.credentials` omitted (write-only). Sellers MUST reject entries whose `event_types` include any type whose contract anchors at a media buy or below (today: `scheduled`, `final`, `delayed`, `adjusted`, `window_update`, `impairment`) or at the agent (today: `capabilities.changed`) as per-account validation failures with `INVALID_REQUEST` or `VALIDATION_ERROR` and `error.field` pointing at the invalid `event_types` entry — those events do not belong on this surface. Wholesale feed webhook registrations carry the actual change payload in `/schemas/core/wholesale-feed-webhook.json`; canonical product subscribers repair through `list_products(if_feed_version)`, legacy product subscribers through `get_products(if_wholesale_feed_version)`, and signal subscribers through `get_signals(if_wholesale_feed_version)`. Account status change registrations carry the invalidation payload in `/schemas/core/account-status-changed-webhook.json`; receivers use `list_accounts` to repair or reconcile. This is distinct from sync_catalogs, which manages buyer-provided campaign input feeds on a seller account.\n\nActivation proof: before activating a new or changed active subscriber, the seller MUST validate the URL, complete the account-level webhook proof-of-control challenge, and only then persist or expose the subscriber as `active: true`. For `account.status_changed`, sellers MUST assign `account_id` before completing proof so subsequent status transitions can identify the account and be repaired through `list_accounts`, even when external approval remains pending. A valid existing proof for the same `(account_id, subscriber_id, normalized url, authentication mode/credential binding, normalized event_types)` tuple MAY be reused; changing any element of that tuple requires fresh proof. The challenge POST itself MUST be signed with the seller\'s RFC 9421 webhook profile key and MUST include seller_agent_url, delivery_auth, and event_types so the receiver can verify the pending registration before echoing the challenge. New signers use `adcp_use: "request-signing"`; deprecated `webhook-signing` keys remain accepted during the compatibility window. Entries sent with `active: false` may skip only the outbound proof challenge while inactive; sellers MUST still enforce URL parsing, HTTPS, hostname normalization, and reserved-range rejection at write time, and those entries MUST NOT receive fires until reactivated. If proof fails or times out, the seller rejects the account entry with `action: "failed"`, leaves the prior notification_configs[] set unchanged, and reports `VALIDATION_ERROR` (or `INVALID_REQUEST` for malformed URLs) at the failing `notification_configs[j].url` field.\n\n**Cap rationale:** `maxItems: 16` is a practical fan-out cap (governance + buyer ingestion + audit bus + dx team + a few partner hooks). The cap exists to prevent unbounded subscriber arrays in storage and to bound the seller\'s per-event fan-out work. Sellers that hit the cap with legitimate subscribers should surface this on the protocol roadmap rather than work around it.', + max_length=16, + ), + ] = None -from adcp.types.generated_poc.account.sync_accounts_request import ( - Accounts, - Accounts1, - SyncAccountsRequest, -) -# Explicit exports -__all__ = ["Accounts", "Accounts1", "SyncAccountsRequest"] +class SyncAccountsRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + idempotency_key: Annotated[ + str, + Field( + description='Client-generated unique key for at-most-once execution. Natural per-account upsert keys handle resource-level dedup, but the envelope triggers onboarding webhooks, billing setup, and audit events — this key prevents those side effects from firing twice on retry. MUST be unique per (seller, request) pair. Use a fresh UUID v4 for each request.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + accounts: Annotated[ + list[Accounts | Accounts1], + Field( + description='Per-account sync entries. Each entry uses one of two key shapes: the `account` field (AccountRef) for settings-update mode, or the flat `brand` + `operator` + `billing` trio for provisioning mode. An operator_identity settings update MUST carry the latest account revision.', + max_length=1000, + ), + ] + delete_missing: Annotated[ + StrictBool | None, + Field( + description='When true, accounts previously synced by this agent but not included in this request will be deactivated. Scoped to the authenticated agent — does not affect accounts managed by other agents. Use with caution.' + ), + ] = False + dry_run: Annotated[ + StrictBool | None, + Field( + description='When true, preview what would change without applying. Returns what would be created/updated/deactivated.' + ), + ] = False + push_notification_config: Annotated[ + push_notification_config_1.PushNotificationConfig | None, + Field( + description='Webhook for async notifications when account status changes (e.g., pending_approval transitions to active).' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/account/sync_accounts_response.py b/src/adcp/types/domains/account/sync_accounts_response.py index c9213a7cd..3431a8f9c 100644 --- a/src/adcp/types/domains/account/sync_accounts_response.py +++ b/src/adcp/types/domains/account/sync_accounts_response.py @@ -1,32 +1,101 @@ -"""Types declared by the AdCP ``account/sync_accounts_response`` schema. +# generated by datamodel-codegen: +# filename: account/sync_accounts_response.json +# timestamp: preserved-by-post-generate-fixes -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.account.sync_accounts_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Any, Literal, TypeAlias -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StringConstraints + +from ..core.version_envelope import AdcpVersionEnvelope +from ..core import account_authorization as account_authorization_1 +from ..core import account_identity_change as account_identity_change_1 +from ..core import account_identity_change_preview as account_identity_change_preview_1 +from ..core import account_ref as account_ref_1 +from ..core import brand_ref as brand_ref_1 +from ..core import business_entity as business_entity_1 +from ..core import context as context_1 +from ..core import error as error_1 +from ..core import ext as ext_1 +from ..core import notification_config as notification_config_1 +from ..core import operator_unit as operator_unit_1 +from ..core import reporting_delivery_config_state as reporting_delivery_config_state_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..enums import account_scope as account_scope_1 +from ..enums import billing_party as billing_party_1 +from ..enums import payment_terms as payment_terms_1 + + +class Setup(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + url: AnyUrl | None = None + message: str + expires_at: AwareDatetime | None = None + + +class CreditLimit(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + amount: Annotated[float, Field(ge=0)] + currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] + + +class Account(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + account_id: str | None = None + account: account_ref_1.AccountReference | None = None + brand: brand_ref_1.BrandReference | None = None + operator: str | None = None + operator_unit: operator_unit_1.OperatorUnit | None = None + revision: Annotated[int, Field(ge=1)] | None = None + identity_change: account_identity_change_1.AccountIdentityChange | None = None + identity_change_preview: account_identity_change_preview_1.AccountIdentityChangePreview | None = None + currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] | None = None + timezone: Annotated[str, StringConstraints(min_length=1)] | None = None + name: str | None = None + action: Literal['created', 'updated', 'unchanged', 'failed'] + status: Literal['active', 'pending_approval', 'rejected', 'payment_required', 'suspended', 'closed'] | None = None + billing: billing_party_1.BillingParty | None = None + billing_entity: business_entity_1.BusinessEntity | None = None + destination_billing_entity: Any | None = None + account_scope: account_scope_1.AccountScope | None = None + setup: Setup | None = None + rate_card: str | None = None + payment_terms: payment_terms_1.PaymentTerms | None = None + credit_limit: CreditLimit | None = None + errors: Annotated[list[error_1.Error], Field(min_length=1)] | None = None + warnings: list[str] | None = None + sandbox: bool | None = None + notification_configs: Annotated[list[notification_config_1.NotificationConfig], Field(max_length=16)] | None = None + reporting_delivery_configs: Annotated[list[reporting_delivery_config_state_1.ReportingDeliveryConfigurationState], Field(max_length=16)] | None = None + authorization: account_authorization_1.AccountAuthorization | None = None + + +class SyncAccountsResponse1(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + dry_run: bool | None = None + accounts: list[Account] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class SyncAccountsResponse2(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + errors: Annotated[list[error_1.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +SyncAccountsResponse: TypeAlias = SyncAccountsResponse1 | SyncAccountsResponse2 -from adcp.types.generated_poc.account.sync_accounts_response import ( - Account, - CreditLimit, - Setup, - SyncAccountsResponse, - SyncAccountsResponse1, - SyncAccountsResponse2, -) -# Explicit exports __all__ = [ - "Account", - "CreditLimit", - "Setup", - "SyncAccountsResponse", - "SyncAccountsResponse1", - "SyncAccountsResponse2", + 'SyncAccountsResponse', + 'SyncAccountsResponse1', + 'SyncAccountsResponse2', + 'Account', + 'CreditLimit', + 'Setup', ] diff --git a/src/adcp/types/domains/account/sync_governance_request.py b/src/adcp/types/domains/account/sync_governance_request.py index 85e5f2045..fc4eb9da3 100644 --- a/src/adcp/types/domains/account/sync_governance_request.py +++ b/src/adcp/types/domains/account/sync_governance_request.py @@ -1,23 +1,90 @@ -"""Types declared by the AdCP ``account/sync_governance_request`` schema. +# generated by datamodel-codegen: +# filename: account/sync_governance_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.account.sync_governance_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field + +from ..core import account_ref +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope + + +class Authentication(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + schemes: Annotated[ + list[Literal['Bearer']], + Field( + description='The seller authenticates outbound check_governance calls with the registered Bearer credential. Other shared webhook authentication schemes are not valid for this agent-to-agent call.', + max_length=1, + min_length=1, + ), + ] + credentials: Annotated[ + str, Field(description='Authentication credential (e.g., Bearer token).', min_length=32) + ] + + +class GovernanceAgent(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + url: Annotated[AnyUrl, Field(description='Governance agent endpoint URL. Must use HTTPS.')] + authentication: Annotated[ + Authentication, + Field(description='Authentication the seller presents when calling this governance agent.'), + ] + + +class Account(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + account: Annotated[ + account_ref.AccountReference, + Field( + description='Account to sync governance agents for. Use account_id for account-id namespaces or brand + operator for buyer-declared accounts.' + ), + ] + governance_agents: Annotated[ + list[GovernanceAgent], + Field( + description="Governance agent endpoint for this account. Exactly one entry — the single agent that owns the account's full governance lifecycle. The seller calls this agent via check_governance during media buy lifecycle events. The array shape is preserved for wire compatibility with 3.0 senders; `maxItems: 1` is load-bearing and mirrors the singular `governance_context` on the protocol envelope.", + max_length=1, + min_length=1, + ), + ] -from adcp.types.generated_poc.account.sync_governance_request import ( - Account, - Authentication, - GovernanceAgent, - SyncGovernanceRequest, -) -# Explicit exports -__all__ = ["Account", "Authentication", "GovernanceAgent", "SyncGovernanceRequest"] +class SyncGovernanceRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + idempotency_key: Annotated[ + str, + Field( + description='Client-generated unique key for at-most-once execution. `account` gives resource-level dedup, but governance changes emit audit events and can trigger reapproval flows — this key prevents those side effects from firing twice on retry. MUST be unique per (seller, request) pair. Use a fresh UUID v4 for each request.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + accounts: Annotated[ + list[Account], + Field( + description='Per-account governance agent configuration. Each entry pairs an account reference with the governance agents for that account.', + max_length=100, + min_length=1, + ), + ] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/account/sync_governance_response.py b/src/adcp/types/domains/account/sync_governance_response.py index bf679b9e9..01c891b6a 100644 --- a/src/adcp/types/domains/account/sync_governance_response.py +++ b/src/adcp/types/domains/account/sync_governance_response.py @@ -1,32 +1,98 @@ -"""Types declared by the AdCP ``account/sync_governance_response`` schema. +# generated by datamodel-codegen: +# filename: account/sync_governance_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.account.sync_governance_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.response_dispatch import ResponseArmDispatchMixin + +from adcp.types._str_enum import StrEnum +from typing import Annotated + +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field + +from ..core import account_ref +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope + + +class Status(StrEnum): + synced = 'synced' + failed = 'failed' + + +class GovernanceAgent(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + url: Annotated[AnyUrl, Field(description='Governance agent endpoint URL.')] + + +class SyncGovernanceResponse(AdcpResponse, ResponseArmDispatchMixin, AdcpVersionEnvelope, ProtocolEnvelope): + """Constructible compatibility base for generated response arms.""" + + @classmethod + def _response_arm_models(cls) -> tuple[type[SyncGovernanceResponse], ...]: + return ( + SyncGovernanceResponse1, + SyncGovernanceResponse2, + ) + + +class SyncGovernanceResponse2(SyncGovernanceResponse): + model_config = ConfigDict( + extra='allow', + ) + errors: Annotated[ + list[error.Error], + Field( + description='Operation-level errors (e.g., authentication failure, service unavailable)', + min_length=1, + ), + ] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class Account(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + account: Annotated[ + account_ref.AccountReference, Field(description='Account reference, echoed from request') + ] + status: Annotated[ + Status, + Field( + description='Sync result. synced: governance agents persisted. failed: could not complete (see errors).' + ), + ] + governance_agents: Annotated[ + list[GovernanceAgent] | None, + Field( + description='Governance agent now synced on this account. Reflects the persisted state after sync. Exactly one entry; the array shape mirrors the request schema and the one-agent-per-account invariant. See sync_governance request schema.', + max_length=1, + min_length=1, + ), + ] = None + errors: Annotated[ + list[error.Error] | None, + Field( + description="Per-account errors (only present when status is 'failed')", min_length=1 + ), + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.account.sync_governance_response import ( - Account, - GovernanceAgent, - Status, - SyncGovernanceResponse, - SyncGovernanceResponse1, - SyncGovernanceResponse2, -) - -# Explicit exports -__all__ = [ - "Account", - "GovernanceAgent", - "Status", - "SyncGovernanceResponse", - "SyncGovernanceResponse1", - "SyncGovernanceResponse2", -] +class SyncGovernanceResponse1(SyncGovernanceResponse): + model_config = ConfigDict( + extra='allow', + ) + accounts: Annotated[list[Account], Field(description='Per-account sync results')] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/adagents.py b/src/adcp/types/domains/adagents.py similarity index 97% rename from src/adcp/types/generated_poc/adagents.py rename to src/adcp/types/domains/adagents.py index d79ec9406..df45c446b 100644 --- a/src/adcp/types/generated_poc/adagents.py +++ b/src/adcp/types/domains/adagents.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: adagents.json -# timestamp: 2026-10-04T00:59:59+00:00 +# timestamp: 2026-10-04T18:54:18+00:00 from __future__ import annotations @@ -1759,3 +1759,90 @@ def __getattr__(self, name: str) -> Any: if name.startswith('_'): raise AttributeError(name) return getattr(self.root, name) + + + +__all__ = [ + "AdcpAgentsAuthorization", + "AdcpAgentsAuthorization1", + "AdcpAgentsAuthorization2", + "AdcpAgentsAuthorization21", + "AdcpAgentsAuthorization210", + "AdcpAgentsAuthorization211", + "AdcpAgentsAuthorization212", + "AdcpAgentsAuthorization213", + "AdcpAgentsAuthorization22", + "AdcpAgentsAuthorization23", + "AdcpAgentsAuthorization24", + "AdcpAgentsAuthorization25", + "AdcpAgentsAuthorization26", + "AdcpAgentsAuthorization27", + "AdcpAgentsAuthorization28", + "AdcpAgentsAuthorization29", + "AuthorizedAgents", + "AuthorizedAgents1", + "AuthorizedAgents10", + "AuthorizedAgents11", + "AuthorizedAgents12", + "AuthorizedAgents13", + "AuthorizedAgents14", + "AuthorizedAgents15", + "AuthorizedAgents16", + "AuthorizedAgents17", + "AuthorizedAgents18", + "AuthorizedAgents19", + "AuthorizedAgents2", + "AuthorizedAgents20", + "AuthorizedAgents21", + "AuthorizedAgents22", + "AuthorizedAgents23", + "AuthorizedAgents24", + "AuthorizedAgents25", + "AuthorizedAgents26", + "AuthorizedAgents27", + "AuthorizedAgents28", + "AuthorizedAgents29", + "AuthorizedAgents3", + "AuthorizedAgents30", + "AuthorizedAgents31", + "AuthorizedAgents32", + "AuthorizedAgents33", + "AuthorizedAgents34", + "AuthorizedAgents35", + "AuthorizedAgents36", + "AuthorizedAgents37", + "AuthorizedAgents38", + "AuthorizedAgents39", + "AuthorizedAgents4", + "AuthorizedAgents40", + "AuthorizedAgents41", + "AuthorizedAgents42", + "AuthorizedAgents43", + "AuthorizedAgents44", + "AuthorizedAgents45", + "AuthorizedAgents46", + "AuthorizedAgents47", + "AuthorizedAgents48", + "AuthorizedAgents5", + "AuthorizedAgents6", + "AuthorizedAgents7", + "AuthorizedAgents8", + "AuthorizedAgents9", + "Contact", + "Country", + "DelegationType", + "PlacementTags", + "PropertyFeature", + "Reason", + "RevokedPublisherDomain", + "RevokedPublisherDomain1", + "RevokedPublisherDomain2", + "RevokedPublisherDomain3", + "RevokedPublisherDomain4", + "RevokedPublisherDomain5", + "RevokedPublisherDomain6", + "SignalId", + "SignalTag", + "SignalTags", + "Tags", +] diff --git a/src/adcp/types/domains/adagents/__init__.py b/src/adcp/types/domains/adagents/__init__.py deleted file mode 100644 index 34cc33bde..000000000 --- a/src/adcp/types/domains/adagents/__init__.py +++ /dev/null @@ -1,186 +0,0 @@ -"""Types the AdCP ``adagents`` schemas declare. - -Importing from the domain says which variant you mean, where the flat -``adcp.types`` namespace can only bind one class per name: - - from adcp.types.domains.adagents import - -This domain has one schema, so every type it declares is here. - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:33:59 UTC -""" - -# ruff: noqa: E501, I001 -from __future__ import annotations - -from adcp.types.generated_poc.adagents import ( - AdcpAgentsAuthorization, - AdcpAgentsAuthorization1, - AdcpAgentsAuthorization2, - AdcpAgentsAuthorization21, - AdcpAgentsAuthorization210, - AdcpAgentsAuthorization211, - AdcpAgentsAuthorization212, - AdcpAgentsAuthorization213, - AdcpAgentsAuthorization22, - AdcpAgentsAuthorization23, - AdcpAgentsAuthorization24, - AdcpAgentsAuthorization25, - AdcpAgentsAuthorization26, - AdcpAgentsAuthorization27, - AdcpAgentsAuthorization28, - AdcpAgentsAuthorization29, - AuthorizedAgents, - AuthorizedAgents1, - AuthorizedAgents10, - AuthorizedAgents11, - AuthorizedAgents12, - AuthorizedAgents13, - AuthorizedAgents14, - AuthorizedAgents15, - AuthorizedAgents16, - AuthorizedAgents17, - AuthorizedAgents18, - AuthorizedAgents19, - AuthorizedAgents2, - AuthorizedAgents20, - AuthorizedAgents21, - AuthorizedAgents22, - AuthorizedAgents23, - AuthorizedAgents24, - AuthorizedAgents25, - AuthorizedAgents26, - AuthorizedAgents27, - AuthorizedAgents28, - AuthorizedAgents29, - AuthorizedAgents3, - AuthorizedAgents30, - AuthorizedAgents31, - AuthorizedAgents32, - AuthorizedAgents33, - AuthorizedAgents34, - AuthorizedAgents35, - AuthorizedAgents36, - AuthorizedAgents37, - AuthorizedAgents38, - AuthorizedAgents39, - AuthorizedAgents4, - AuthorizedAgents40, - AuthorizedAgents41, - AuthorizedAgents42, - AuthorizedAgents43, - AuthorizedAgents44, - AuthorizedAgents45, - AuthorizedAgents46, - AuthorizedAgents47, - AuthorizedAgents48, - AuthorizedAgents5, - AuthorizedAgents6, - AuthorizedAgents7, - AuthorizedAgents8, - AuthorizedAgents9, - Contact, - Country, - DelegationType, - PlacementTags, - PropertyFeature, - Reason, - RevokedPublisherDomain, - RevokedPublisherDomain1, - RevokedPublisherDomain2, - RevokedPublisherDomain3, - RevokedPublisherDomain4, - RevokedPublisherDomain5, - RevokedPublisherDomain6, - SignalId, - SignalTag, - SignalTags, - Tags, -) - -# Explicit exports -__all__ = [ - "AdcpAgentsAuthorization", - "AdcpAgentsAuthorization1", - "AdcpAgentsAuthorization2", - "AdcpAgentsAuthorization21", - "AdcpAgentsAuthorization210", - "AdcpAgentsAuthorization211", - "AdcpAgentsAuthorization212", - "AdcpAgentsAuthorization213", - "AdcpAgentsAuthorization22", - "AdcpAgentsAuthorization23", - "AdcpAgentsAuthorization24", - "AdcpAgentsAuthorization25", - "AdcpAgentsAuthorization26", - "AdcpAgentsAuthorization27", - "AdcpAgentsAuthorization28", - "AdcpAgentsAuthorization29", - "AuthorizedAgents", - "AuthorizedAgents1", - "AuthorizedAgents10", - "AuthorizedAgents11", - "AuthorizedAgents12", - "AuthorizedAgents13", - "AuthorizedAgents14", - "AuthorizedAgents15", - "AuthorizedAgents16", - "AuthorizedAgents17", - "AuthorizedAgents18", - "AuthorizedAgents19", - "AuthorizedAgents2", - "AuthorizedAgents20", - "AuthorizedAgents21", - "AuthorizedAgents22", - "AuthorizedAgents23", - "AuthorizedAgents24", - "AuthorizedAgents25", - "AuthorizedAgents26", - "AuthorizedAgents27", - "AuthorizedAgents28", - "AuthorizedAgents29", - "AuthorizedAgents3", - "AuthorizedAgents30", - "AuthorizedAgents31", - "AuthorizedAgents32", - "AuthorizedAgents33", - "AuthorizedAgents34", - "AuthorizedAgents35", - "AuthorizedAgents36", - "AuthorizedAgents37", - "AuthorizedAgents38", - "AuthorizedAgents39", - "AuthorizedAgents4", - "AuthorizedAgents40", - "AuthorizedAgents41", - "AuthorizedAgents42", - "AuthorizedAgents43", - "AuthorizedAgents44", - "AuthorizedAgents45", - "AuthorizedAgents46", - "AuthorizedAgents47", - "AuthorizedAgents48", - "AuthorizedAgents5", - "AuthorizedAgents6", - "AuthorizedAgents7", - "AuthorizedAgents8", - "AuthorizedAgents9", - "Contact", - "Country", - "DelegationType", - "PlacementTags", - "PropertyFeature", - "Reason", - "RevokedPublisherDomain", - "RevokedPublisherDomain1", - "RevokedPublisherDomain2", - "RevokedPublisherDomain3", - "RevokedPublisherDomain4", - "RevokedPublisherDomain5", - "RevokedPublisherDomain6", - "SignalId", - "SignalTag", - "SignalTags", - "Tags", -] diff --git a/src/adcp/types/domains/brand/__init__.py b/src/adcp/types/domains/brand/__init__.py index 1700b4097..a37d39fdc 100644 --- a/src/adcp/types/domains/brand/__init__.py +++ b/src/adcp/types/domains/brand/__init__.py @@ -9,15 +9,15 @@ it from its own schema's module, ``adcp.types.domains.brand.``. Nothing here is renamed. -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:33:59 UTC +Auto-generated from the generated domain tree. DO NOT EDIT MANUALLY. +Generation date: 2026-10-04 18:45:11 UTC """ # ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.brand.acquire_rights_request import AcquireRightsRequest, Campaign -from adcp.types.generated_poc.brand.acquire_rights_response import ( +from adcp.types.domains.brand.acquire_rights_request import AcquireRightsRequest, Campaign +from adcp.types.domains.brand.acquire_rights_response import ( AcquireRightsResponse, AcquireRightsResponse1, AcquireRightsResponse2, @@ -25,19 +25,16 @@ AcquireRightsResponse4, Disclosure, ) -from adcp.types.generated_poc.brand.creative_approval_request import CreativeApprovalRequest -from adcp.types.generated_poc.brand.creative_approval_response import ( +from adcp.types.domains.brand.creative_approval_request import CreativeApprovalRequest +from adcp.types.domains.brand.creative_approval_response import ( CreativeApprovalResponse, CreativeApprovalResponse1, CreativeApprovalResponse2, CreativeApprovalResponse3, CreativeApprovalResponse4, ) -from adcp.types.generated_poc.brand.get_brand_identity_request import ( - Field1, - GetBrandIdentityRequest, -) -from adcp.types.generated_poc.brand.get_brand_identity_response import ( +from adcp.types.domains.brand.get_brand_identity_request import Field1, GetBrandIdentityRequest +from adcp.types.domains.brand.get_brand_identity_response import ( Asset, Colors, File, @@ -49,8 +46,8 @@ Tone, VoiceSynthesis, ) -from adcp.types.generated_poc.brand.get_rights_request import GetRightsRequest -from adcp.types.generated_poc.brand.get_rights_response import ( +from adcp.types.domains.brand.get_rights_request import GetRightsRequest +from adcp.types.domains.brand.get_rights_response import ( Excluded, ExclusivityStatus, GetRightsResponse, @@ -59,11 +56,11 @@ PreviewAsset, Right, ) -from adcp.types.generated_poc.brand.revocation_notification import RevocationNotification -from adcp.types.generated_poc.brand.rights_pricing_option import RightsPricingOption -from adcp.types.generated_poc.brand.rights_terms import Exclusivity, RightsTerms -from adcp.types.generated_poc.brand.search_brands_request import SearchBrandsRequest -from adcp.types.generated_poc.brand.search_brands_response import ( +from adcp.types.domains.brand.revocation_notification import RevocationNotification +from adcp.types.domains.brand.rights_pricing_option import RightsPricingOption +from adcp.types.domains.brand.rights_terms import Exclusivity, RightsTerms +from adcp.types.domains.brand.search_brands_request import SearchBrandsRequest +from adcp.types.domains.brand.search_brands_response import ( Background, ExcludedCountry, KellerType, @@ -73,15 +70,15 @@ SearchBrandsResponse, Variant, ) -from adcp.types.generated_poc.brand.update_rights_request import UpdateRightsRequest -from adcp.types.generated_poc.brand.update_rights_response import ( +from adcp.types.domains.brand.update_rights_request import UpdateRightsRequest +from adcp.types.domains.brand.update_rights_response import ( UpdateRightsResponse, UpdateRightsResponse1, UpdateRightsResponse2, ) -from adcp.types.generated_poc.brand.verification_status import VerificationStatus -from adcp.types.generated_poc.brand.verify_brand_claim_request import VerifyBrandClaimRequest -from adcp.types.generated_poc.brand.verify_brand_claim_response import ( +from adcp.types.domains.brand.verification_status import VerificationStatus +from adcp.types.domains.brand.verify_brand_claim_request import VerifyBrandClaimRequest +from adcp.types.domains.brand.verify_brand_claim_response import ( VerifyBrandClaimErrorResponse, VerifyBrandClaimPayload, VerifyBrandClaimResponse, @@ -89,7 +86,7 @@ VerifyBrandClaimSignedSuccessPayload, VerifyBrandClaimSuccessResponse, ) -from adcp.types.generated_poc.brand.verify_brand_claims_request import ( +from adcp.types.domains.brand.verify_brand_claims_request import ( Claim, Claim1, Claim2, @@ -104,7 +101,7 @@ VerifyBrandClaimsRequest, VerifyBrandClaimsRequestBulk, ) -from adcp.types.generated_poc.brand.verify_brand_claims_response import ( +from adcp.types.domains.brand.verify_brand_claims_response import ( ResultEntry, ResultEntry1, ResultEntry2, diff --git a/src/adcp/types/domains/brand/acquire_rights_request.py b/src/adcp/types/domains/brand/acquire_rights_request.py index 14aa0b824..fe4ad0724 100644 --- a/src/adcp/types/domains/brand/acquire_rights_request.py +++ b/src/adcp/types/domains/brand/acquire_rights_request.py @@ -1,22 +1,129 @@ -"""Types declared by the AdCP ``brand/acquire_rights_request`` schema. +# generated by datamodel-codegen: +# filename: brand/acquire_rights_request.json +# timestamp: 2026-10-05T13:49:50+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.brand.acquire_rights_request import +from adcp.types.base import AdcpRequest, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from datetime import date +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field + +from ..core import account_ref, brand_ref +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core import format_id, format_option_ref +from ..core import push_notification_config as push_notification_config_1 +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import right_use + + +class Country(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{2}$'} + + +class Campaign(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + description: Annotated[str, Field(description='Description of how the rights will be used')] + uses: Annotated[ + list[right_use.RightUse], + Field(description='Specific rights uses for this campaign', min_length=1), + ] + countries: Annotated[ + list[Country] | None, + Field(description='Countries where the campaign will run (ISO 3166-1 alpha-2)'), + ] = None + format_ids: Annotated[ + list[format_id.FormatReferenceStructuredObject] | None, + Field( + deprecated=True, + description='Deprecated in AdCP 3.2; removed in AdCP 4.0. Legacy named formats that will be produced. Use `format_kinds` and optional `format_option_refs`.', + ), + ] = None + format_kinds: Annotated[ + list[str] | None, + Field( + description='Canonical creative format kinds that will be produced under the rights grant.', + min_length=1, + ), + ] = None + format_option_refs: Annotated[ + list[format_option_ref.FormatOptionReference] | None, + Field( + description='Exact canonical publisher or product format options covered by the planned rights use, when kind alone is insufficient.', + min_length=1, + ), + ] = None + estimated_impressions: Annotated[ + SchemaInt | None, + Field( + description="Estimated total impressions for the campaign. Required when the brand agent will project commitment against a governance plan AND the selected pricing_option has model: 'cpm' — projection equals (pricing_option.price / 1000) × estimated_impressions evaluated in pricing_option.currency. The brand agent will project commitment whenever the request is governance-aware via either (a) an intent-phase governance_context token on the protocol envelope, or (b) `account` resolving to an account that has a governance agent previously bound via sync_governance. Brand agents MUST reject with INVALID_REQUEST (field: campaign.estimated_impressions) in either path when CPM-priced rights are requested and this field is omitted or zero; implementer-chosen defaults are non-conformant. See the acquire_rights task reference for the full validation contract including currency-mismatch handling.", + ge=0, + ), + ] = None + start_date: Annotated[date | None, Field(description='Campaign start date (ISO 8601)')] = None + end_date: Annotated[ + date | None, + Field( + description='Campaign end date (ISO 8601). Brand agents MUST reject with INVALID_REQUEST (field: campaign.end_date) when end_date is in the past at the time of the request — acquiring rights for an elapsed window produces a zero-duration grant and is almost always a buyer-side bug.' + ), + ] = None -from adcp.types.generated_poc.brand.acquire_rights_request import ( - AcquireRightsRequest, - Campaign, - Country, -) -# Explicit exports -__all__ = ["AcquireRightsRequest", "Campaign", "Country"] +class AcquireRightsRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + governance_context: Annotated[ + str | None, + Field( + description='Opaque intent authorization for this rights commitment. Required when governance applies to the resolved account.', + max_length=4096, + min_length=1, + pattern='^[\\x20-\\x7E]+$', + ), + ] = None + rights_id: Annotated[ + str, Field(description='Rights offering identifier from get_rights response') + ] + pricing_option_id: Annotated[ + str, Field(description='Selected pricing option from the rights offering') + ] + buyer: Annotated[brand_ref.BrandReference, Field(description="The buyer's brand identity")] + account: Annotated[ + account_ref.AccountReference | None, + Field( + description='Account context for this acquisition. Used by the brand agent to resolve any governance agent previously bound for this brand+operator pair via sync_governance. When both an inline governance_context token and a bound governance agent are present, the token is verified against that resolved relationship. An agent advertising adcp.governance_enforcement for acquire_rights MUST resolve an account and returns ACCOUNT_REQUIRED when neither the request nor an existing resource supplies one; it MUST NOT infer that a missing token means ungoverned. Legacy non-claiming agents may continue to treat omission of both fields as ungoverned during 3.x. Pass a natural key (brand, operator, optional sandbox) or a seller-assigned account_id from list_accounts.' + ), + ] = None + campaign: Annotated[Campaign, Field(description='Campaign details for rights clearance')] + revocation_webhook: Annotated[ + push_notification_config_1.PushNotificationConfig, + Field( + description='Webhook for rights revocation notifications. If the rights holder needs to revoke rights (talent scandal, contract violation, etc.), they POST a revocation-notification to this URL. The buyer is responsible for stopping creative delivery upon receipt.' + ), + ] + push_notification_config: Annotated[ + push_notification_config_1.PushNotificationConfig | None, + Field( + description='Webhook for async status updates if the acquisition requires approval. The rights agent sends a webhook notification when the status transitions to acquired or rejected.' + ), + ] = None + idempotency_key: Annotated[ + str, + Field( + description='Client-generated key for safe retries. Resubmitting with the same key returns the original response rather than creating a duplicate acquisition. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/brand/acquire_rights_response.py b/src/adcp/types/domains/brand/acquire_rights_response.py index 120b5ca04..c49b22355 100644 --- a/src/adcp/types/domains/brand/acquire_rights_response.py +++ b/src/adcp/types/domains/brand/acquire_rights_response.py @@ -1,32 +1,84 @@ -"""Types declared by the AdCP ``brand/acquire_rights_response`` schema. +# generated by datamodel-codegen: +# filename: brand/acquire_rights_response.json +# timestamp: preserved-by-post-generate-fixes -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.brand.acquire_rights_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Any, Literal, TypeAlias -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import AnyUrl, ConfigDict, Field + +from ..core.version_envelope import AdcpVersionEnvelope +from . import rights_terms as rights_terms_1 +from ..core import context as context_1 +from ..core import error as error_1 +from ..core import ext as ext_1 +from ..core import generation_credential as generation_credential_1 +from ..core import push_notification_config as push_notification_config_1 +from ..core.protocol_envelope import ProtocolEnvelope + + +class Disclosure(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + required: bool + text: str | None = None + + +class AcquireRightsResponse1(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + rights_id: str + rights_status: Literal['acquired'] = 'acquired' + brand_id: str + terms: rights_terms_1.RightsTerms + generation_credentials: list[generation_credential_1.GenerationCredential] + restrictions: list[str] | None = None + disclosure: Disclosure | None = None + approval_webhook: push_notification_config_1.PushNotificationConfig | None = None + usage_reporting_url: AnyUrl | None = None + rights_constraint: Any + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class AcquireRightsResponse2(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + rights_id: str + rights_status: Literal['pending_approval'] = 'pending_approval' + brand_id: str + detail: str | None = None + estimated_response_time: str | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class AcquireRightsResponse3(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + rights_id: str + rights_status: Literal['rejected'] = 'rejected' + brand_id: str + reason: str + suggestions: list[str] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class AcquireRightsResponse4(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + errors: Annotated[list[error_1.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +AcquireRightsResponse: TypeAlias = AcquireRightsResponse1 | AcquireRightsResponse2 | AcquireRightsResponse3 | AcquireRightsResponse4 -from adcp.types.generated_poc.brand.acquire_rights_response import ( - AcquireRightsResponse, - AcquireRightsResponse1, - AcquireRightsResponse2, - AcquireRightsResponse3, - AcquireRightsResponse4, - Disclosure, -) -# Explicit exports __all__ = [ - "AcquireRightsResponse", - "AcquireRightsResponse1", - "AcquireRightsResponse2", - "AcquireRightsResponse3", - "AcquireRightsResponse4", - "Disclosure", + 'AcquireRightsResponse', + 'AcquireRightsResponse1', + 'AcquireRightsResponse2', + 'AcquireRightsResponse3', + 'AcquireRightsResponse4', + 'Disclosure', ] diff --git a/src/adcp/types/domains/brand/creative_approval_request.py b/src/adcp/types/domains/brand/creative_approval_request.py index 0ea231a0f..1b5e5c17e 100644 --- a/src/adcp/types/domains/brand/creative_approval_request.py +++ b/src/adcp/types/domains/brand/creative_approval_request.py @@ -1,18 +1,54 @@ -"""Types declared by the AdCP ``brand/creative_approval_request`` schema. +# generated by datamodel-codegen: +# filename: brand/creative_approval_request.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.brand.creative_approval_request import +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import AnyUrl, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core import format_id +from ..core.version_envelope import AdcpVersionEnvelope -from adcp.types.generated_poc.brand.creative_approval_request import CreativeApprovalRequest -# Explicit exports -__all__ = ["CreativeApprovalRequest"] +class CreativeApprovalRequest(AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + rights_id: Annotated[str, Field(description='Rights grant this creative was produced under')] + creative_id: Annotated[ + str | None, + Field( + description='Buyer-assigned creative identifier. Equivalent to OpenRTB crid. Used to track approval status across resubmissions.' + ), + ] = None + creative_url: Annotated[ + AnyUrl, Field(description='URL where the creative asset can be retrieved for review') + ] + creative_format: Annotated[ + format_id.FormatReferenceStructuredObject | None, + Field(description='Format of the creative being submitted'), + ] = None + description: Annotated[ + str | None, Field(description='Description of the creative for reviewer context') + ] = None + metadata: Annotated[ + dict[str, Any] | None, + Field( + description='Additional creative metadata (duration, dimensions, target audience, etc.)' + ), + ] = None + idempotency_key: Annotated[ + str, + Field( + description='Client-generated key for safe retries. Resubmitting with the same key returns the original response. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/brand/creative_approval_response.py b/src/adcp/types/domains/brand/creative_approval_response.py index 587301635..7593c775e 100644 --- a/src/adcp/types/domains/brand/creative_approval_response.py +++ b/src/adcp/types/domains/brand/creative_approval_response.py @@ -1,30 +1,106 @@ -"""Types declared by the AdCP ``brand/creative_approval_response`` schema. +# generated by datamodel-codegen: +# filename: brand/creative_approval_response.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.brand.creative_approval_response import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field + +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope + + +class CreativeApprovalResponse1(AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + approval_status: Annotated[ + Literal['approved'], + Field( + description='Creative has been approved for distribution. Renamed from `status` in 3.1 to free the top-level `status` key for the envelope task-status (TaskStatus) under MCP flat-on-the-wire serialization (#4878).' + ), + ] = 'approved' + rights_id: str + creative_id: Annotated[ + str | None, Field(description="Echo of the buyer's creative identifier") + ] = None + creative_url: AnyUrl | None = None + approved_at: AwareDatetime | None = None + conditions: Annotated[ + list[str] | None, + Field(description="Conditions on the approval (e.g., 'approved for NL market only')"), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class CreativeApprovalResponse2(AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + approval_status: Annotated[Literal['rejected'], Field(description='Creative was rejected')] = 'rejected' + rights_id: str + creative_id: Annotated[ + str | None, Field(description="Echo of the buyer's creative identifier") + ] = None + creative_url: AnyUrl | None = None + reason: Annotated[str, Field(description='Why the creative was rejected')] + suggestions: Annotated[ + list[str] | None, + Field( + description='Actionable feedback for revision. If present, the buyer can revise and resubmit the creative. If absent, the rejection is final for this creative concept.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class CreativeApprovalResponse3(AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + approval_status: Annotated[ + Literal['pending_review'], Field(description='Creative is queued for review') + ] = 'pending_review' + rights_id: str + creative_id: Annotated[ + str | None, Field(description="Echo of the buyer's creative identifier") + ] = None + creative_url: AnyUrl | None = None + estimated_response_time: Annotated[ + str | None, Field(description="Expected time for review (e.g., '24h', '2 business days')") + ] = None + status_url: Annotated[ + AnyUrl | None, + Field( + description='URL to poll for updated approval status. GET this URL to receive a creative-approval-response. Poll at reasonable intervals (suggested: every 5 minutes, back off after 1 hour to every 30 minutes). Stop polling after estimated_response_time has elapsed and the approval_status is still pending_review.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class CreativeApprovalResponse4(AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + errors: Annotated[list[error.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.brand.creative_approval_response import ( - CreativeApprovalResponse, - CreativeApprovalResponse1, - CreativeApprovalResponse2, - CreativeApprovalResponse3, - CreativeApprovalResponse4, -) - -# Explicit exports -__all__ = [ - "CreativeApprovalResponse", - "CreativeApprovalResponse1", - "CreativeApprovalResponse2", - "CreativeApprovalResponse3", - "CreativeApprovalResponse4", +CreativeApprovalResponse = Annotated[ + CreativeApprovalResponse1 + | CreativeApprovalResponse2 + | CreativeApprovalResponse3 + | CreativeApprovalResponse4, + Field( + description='Response from the approval_webhook after reviewing a submitted creative. Uses discriminated union on `approval_status` field: approved, rejected, or pending_review.', + title='Creative Approval Response', + ), ] diff --git a/src/adcp/types/domains/brand/get_brand_identity_request.py b/src/adcp/types/domains/brand/get_brand_identity_request.py index 6b1c24d74..564a740e9 100644 --- a/src/adcp/types/domains/brand/get_brand_identity_request.py +++ b/src/adcp/types/domains/brand/get_brand_identity_request.py @@ -1,21 +1,53 @@ -"""Types declared by the AdCP ``brand/get_brand_identity_request`` schema. +# generated by datamodel-codegen: +# filename: brand/get_brand_identity_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.brand.get_brand_identity_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field + +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope + + +class Field1(StrEnum): + description = 'description' + industries = 'industries' + keller_type = 'keller_type' + logos = 'logos' + colors = 'colors' + fonts = 'fonts' + visual_guidelines = 'visual_guidelines' + tone = 'tone' + tagline = 'tagline' + voice_synthesis = 'voice_synthesis' + assets = 'assets' + rights = 'rights' -from adcp.types.generated_poc.brand.get_brand_identity_request import ( - Field1, - GetBrandIdentityRequest, -) -# Explicit exports -__all__ = ["Field1", "GetBrandIdentityRequest"] +class GetBrandIdentityRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + brand_id: Annotated[str, Field(description='Brand identifier from brand.json brands array')] + fields: Annotated[ + list[Field1] | None, + Field( + description='Optional identity sections to include in the response. When omitted, all sections the caller is authorized to see are returned. Core fields (brand_id, house, names) are always returned and do not need to be requested.', + min_length=1, + ), + ] = None + use_case: Annotated[ + str | None, + Field( + description="Intended use case, so the agent can tailor the response. A 'voice_synthesis' use case returns voice configs; a 'likeness' use case returns high-res photos and appearance guidelines." + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/brand/get_brand_identity_response.py b/src/adcp/types/domains/brand/get_brand_identity_response.py index 188b627e6..0af6a4ff8 100644 --- a/src/adcp/types/domains/brand/get_brand_identity_response.py +++ b/src/adcp/types/domains/brand/get_brand_identity_response.py @@ -1,46 +1,157 @@ -"""Types declared by the AdCP ``brand/get_brand_identity_response`` schema. +# generated by datamodel-codegen: +# filename: brand/get_brand_identity_response.json +# timestamp: preserved-by-post-generate-fixes -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.brand.get_brand_identity_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Any, Literal, TypeAlias + +from pydantic import AnyUrl, ConfigDict, Field, StringConstraints + +from ..core.version_envelope import AdcpVersionEnvelope +from ..core import context as context_1 +from ..core import error as error_1 +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..enums import asset_content_type as asset_content_type_1 +from ..enums import right_use as right_use_1 + + +class House(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + domain: str + name: str + + +class Logo(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + url: AnyUrl + orientation: Literal['square', 'horizontal', 'vertical', 'stacked'] | None = None + background: Literal['dark-bg', 'light-bg', 'transparent-bg'] | None = None + variant: Literal['primary', 'secondary', 'icon', 'wordmark', 'full-lockup'] | None = None + tags: list[str] | None = None + usage: str | None = None + width: int | None = None + height: int | None = None + + +class Colors(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + primary: Annotated[str, StringConstraints(pattern='^#[0-9A-Fa-f]{6}$')] | Annotated[list[Annotated[str, StringConstraints(pattern='^#[0-9A-Fa-f]{6}$')]], Field(min_length=1)] | None = None + secondary: Annotated[str, StringConstraints(pattern='^#[0-9A-Fa-f]{6}$')] | Annotated[list[Annotated[str, StringConstraints(pattern='^#[0-9A-Fa-f]{6}$')]], Field(min_length=1)] | None = None + accent: Annotated[str, StringConstraints(pattern='^#[0-9A-Fa-f]{6}$')] | Annotated[list[Annotated[str, StringConstraints(pattern='^#[0-9A-Fa-f]{6}$')]], Field(min_length=1)] | None = None + background: Annotated[str, StringConstraints(pattern='^#[0-9A-Fa-f]{6}$')] | Annotated[list[Annotated[str, StringConstraints(pattern='^#[0-9A-Fa-f]{6}$')]], Field(min_length=1)] | None = None + text: Annotated[str, StringConstraints(pattern='^#[0-9A-Fa-f]{6}$')] | Annotated[list[Annotated[str, StringConstraints(pattern='^#[0-9A-Fa-f]{6}$')]], Field(min_length=1)] | None = None + + +class File(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + url: Annotated[str, StringConstraints(pattern='^https://')] + weight: Annotated[int, Field(ge=100, le=900)] | None = None + weight_range: Annotated[list[Annotated[int, Field(ge=100, le=900)]], Field(min_length=2, max_length=2)] | None = None + style: Literal['normal', 'italic', 'oblique'] | None = None + + +class FontRole2(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + family: str + files: Annotated[list[File], Field(max_length=36)] | None = None + opentype_features: Annotated[list[Annotated[str, StringConstraints(pattern='^[a-z0-9]{4}$')]], Field(max_length=20)] | None = None + fallbacks: Annotated[list[Annotated[str, StringConstraints(max_length=100)]], Field(max_length=10)] | None = None + + +class Fonts(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + primary: str | FontRole2 | None = None + secondary: str | FontRole2 | None = None + + +class Tone(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + voice: str | None = None + attributes: list[str] | None = None + dos: list[str] | None = None + donts: list[str] | None = None + + +class VoiceSynthesis(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + provider: str | None = None + voice_id: str | None = None + rights_offering_id: str | None = None + settings: dict[str, Any] | None = None + + +class Asset(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + asset_id: str + asset_type: asset_content_type_1.AssetContentType + url: AnyUrl + tags: list[str] | None = None + name: str | None = None + description: str | None = None + width: int | None = None + height: int | None = None + duration_seconds: float | None = None + file_size_bytes: int | None = None + format: str | None = None + + +class Rights(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + available_uses: list[right_use_1.RightUse] | None = None + countries: list[Annotated[str, StringConstraints(pattern='^[A-Z]{2}$')]] | None = None + excluded_countries: list[Annotated[str, StringConstraints(pattern='^[A-Z]{2}$')]] | None = None + exclusivity_model: str | None = None + content_restrictions: list[str] | None = None + + +class GetBrandIdentityResponse1(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + brand_id: str + house: House + names: list[dict[str, str]] + description: str | None = None + industries: Annotated[list[str], Field(min_length=1)] | None = None + keller_type: Literal['master', 'sub_brand', 'endorsed', 'independent'] | None = None + logos: list[Logo] | None = None + colors: Colors | None = None + fonts: Fonts | None = None + visual_guidelines: dict[str, Any] | None = None + tone: Tone | None = None + tagline: str | Annotated[list[dict[str, Annotated[str, StringConstraints(min_length=1)]]], Field(min_length=1)] | None = None + voice_synthesis: VoiceSynthesis | None = None + assets: list[Asset] | None = None + rights: Rights | None = None + available_fields: list[Literal['description', 'industries', 'keller_type', 'logos', 'colors', 'fonts', 'visual_guidelines', 'tone', 'tagline', 'voice_synthesis', 'assets', 'rights']] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class GetBrandIdentityResponse2(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + errors: Annotated[list[error_1.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +GetBrandIdentityResponse: TypeAlias = GetBrandIdentityResponse1 | GetBrandIdentityResponse2 -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.brand.get_brand_identity_response import ( - Asset, - Colors, - File, - FontRole2, - Fonts, - GetBrandIdentityResponse, - GetBrandIdentityResponse1, - GetBrandIdentityResponse2, - House, - Logo, - Rights, - Tone, - VoiceSynthesis, -) - -# Explicit exports __all__ = [ - "Asset", - "Colors", - "File", - "FontRole2", - "Fonts", - "GetBrandIdentityResponse", - "GetBrandIdentityResponse1", - "GetBrandIdentityResponse2", - "House", - "Logo", - "Rights", - "Tone", - "VoiceSynthesis", + 'GetBrandIdentityResponse', + 'GetBrandIdentityResponse1', + 'GetBrandIdentityResponse2', + 'Asset', + 'Colors', + 'File', + 'FontRole2', + 'Fonts', + 'House', + 'Logo', + 'Rights', + 'Tone', + 'VoiceSynthesis', ] diff --git a/src/adcp/types/domains/brand/get_rights_request.py b/src/adcp/types/domains/brand/get_rights_request.py index 78c77548c..aac1f77b2 100644 --- a/src/adcp/types/domains/brand/get_rights_request.py +++ b/src/adcp/types/domains/brand/get_rights_request.py @@ -1,18 +1,80 @@ -"""Types declared by the AdCP ``brand/get_rights_request`` schema. +# generated by datamodel-codegen: +# filename: brand/get_rights_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.brand.get_rights_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._scalar import ScalarStr -# ruff: noqa: E501, I001 -from __future__ import annotations +from typing import Annotated + +from pydantic import ConfigDict, Field, StrictBool + +from ..core import brand_ref +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core import pagination_request +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import right_type as right_type_1 +from ..enums import right_use + + +class Country(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{2}$'} -from adcp.types.generated_poc.brand.get_rights_request import Country, GetRightsRequest -# Explicit exports -__all__ = ["Country", "GetRightsRequest"] +class GetRightsRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + query: Annotated[ + str, + Field( + description='Natural language description of desired rights. The agent interprets intent, budget signals, and compatibility from this text.', + max_length=2000, + ), + ] + uses: Annotated[ + list[right_use.RightUse], + Field( + description='Rights uses being requested. The agent returns options covering these uses, potentially bundled into composite pricing.', + min_length=1, + ), + ] + buyer_brand: Annotated[ + brand_ref.BrandReference | None, + Field( + description="The buyer's brand. The agent fetches the buyer's brand.json for compatibility filtering (e.g., dietary conflicts, competitor exclusions)." + ), + ] = None + countries: Annotated[ + list[Country] | None, + Field( + description='Countries where rights are needed (ISO 3166-1 alpha-2). Filters to rights available in these markets.' + ), + ] = None + brand_id: Annotated[ + str | None, + Field( + description="Search within a specific brand's rights. If omitted, searches across the agent's full roster." + ), + ] = None + right_type: Annotated[ + right_type_1.RightType | None, + Field(description='Filter by type of rights (talent, music, stock_media, etc.)'), + ] = None + include_excluded: Annotated[ + StrictBool | None, + Field( + description='Include filtered-out results in the excluded array with reasons. Defaults to false.' + ), + ] = False + pagination: Annotated[ + pagination_request.PaginationRequest | None, + Field(description='Pagination parameters for large result sets'), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/brand/get_rights_response.py b/src/adcp/types/domains/brand/get_rights_response.py index 507f18f20..65bc299fc 100644 --- a/src/adcp/types/domains/brand/get_rights_response.py +++ b/src/adcp/types/domains/brand/get_rights_response.py @@ -1,34 +1,87 @@ -"""Types declared by the AdCP ``brand/get_rights_response`` schema. +# generated by datamodel-codegen: +# filename: brand/get_rights_response.json +# timestamp: preserved-by-post-generate-fixes -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.brand.get_rights_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, TypeAlias + +from pydantic import AnyUrl, ConfigDict, Field, StringConstraints + +from ..core.version_envelope import AdcpVersionEnvelope +from . import rights_pricing_option as rights_pricing_option_1 +from ..core import context as context_1 +from ..core import error as error_1 +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..enums import right_type as right_type_1 +from ..enums import right_use as right_use_1 + + +class ExclusivityStatus(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + available: bool | None = None + existing_exclusives: list[str] | None = None + + +class PreviewAsset(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + url: AnyUrl + usage: str | None = None + + +class Right(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + rights_id: str + brand_id: str + name: str + description: str | None = None + right_type: right_type_1.RightType | None = None + match_score: Annotated[float, Field(ge=0, le=1)] | None = None + match_reasons: list[str] | None = None + available_uses: list[right_use_1.RightUse] + countries: list[Annotated[str, StringConstraints(pattern='^[A-Z]{2}$')]] | None = None + excluded_countries: list[Annotated[str, StringConstraints(pattern='^[A-Z]{2}$')]] | None = None + exclusivity_status: ExclusivityStatus | None = None + pricing_options: Annotated[list[rights_pricing_option_1.RightsPricingOption], Field(min_length=1)] + content_restrictions: list[str] | None = None + preview_assets: list[PreviewAsset] | None = None + + +class Excluded(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + brand_id: str + name: str | None = None + reason: str + suggestions: list[str] | None = None + + +class GetRightsResponse1(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + rights: list[Right] + excluded: list[Excluded] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class GetRightsResponse2(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + errors: Annotated[list[error_1.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +GetRightsResponse: TypeAlias = GetRightsResponse1 | GetRightsResponse2 -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.brand.get_rights_response import ( - Excluded, - ExclusivityStatus, - GetRightsResponse, - GetRightsResponse1, - GetRightsResponse2, - PreviewAsset, - Right, -) - -# Explicit exports __all__ = [ - "Excluded", - "ExclusivityStatus", - "GetRightsResponse", - "GetRightsResponse1", - "GetRightsResponse2", - "PreviewAsset", - "Right", + 'GetRightsResponse', + 'GetRightsResponse1', + 'GetRightsResponse2', + 'Excluded', + 'ExclusivityStatus', + 'PreviewAsset', + 'Right', ] diff --git a/src/adcp/types/domains/brand/revocation_notification.py b/src/adcp/types/domains/brand/revocation_notification.py index 623496ffc..e6424cfac 100644 --- a/src/adcp/types/domains/brand/revocation_notification.py +++ b/src/adcp/types/domains/brand/revocation_notification.py @@ -1,18 +1,47 @@ -"""Types declared by the AdCP ``brand/revocation_notification`` schema. +# generated by datamodel-codegen: +# filename: brand/revocation_notification.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.brand.revocation_notification import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..enums import right_use -from adcp.types.generated_poc.brand.revocation_notification import RevocationNotification -# Explicit exports -__all__ = ["RevocationNotification"] +class RevocationNotification(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + idempotency_key: Annotated[ + str, + Field( + description='Sender-generated key stable across retries of the same revocation notification. Rights holders MUST generate a cryptographically random value (UUID v4 recommended) per distinct revocation event and reuse the same key when retrying delivery. Buyers MUST dedupe by this key, scoped to the authenticated sender identity (HMAC secret or Bearer credential); keys from different senders are independent.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + rights_id: Annotated[str, Field(description='The revoked rights grant identifier')] + brand_id: Annotated[str, Field(description='Brand identifier of the rights subject')] + reason: Annotated[str, Field(description='Human-readable reason for revocation')] + effective_at: Annotated[ + AwareDatetime, + Field( + description='When the revocation takes effect. Immediate revocations use current time. Grace periods use a future time. The buyer must stop serving creative using these rights by this time.' + ), + ] + revoked_uses: Annotated[ + list[right_use.RightUse] | None, + Field( + description='If present, only these uses are revoked (partial revocation). If absent, all uses under the grant are revoked.', + min_length=1, + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/brand/rights_pricing_option.py b/src/adcp/types/domains/brand/rights_pricing_option.py index 09e9a71b2..13e7695ff 100644 --- a/src/adcp/types/domains/brand/rights_pricing_option.py +++ b/src/adcp/types/domains/brand/rights_pricing_option.py @@ -1,18 +1,59 @@ -"""Types declared by the AdCP ``brand/rights_pricing_option`` schema. +# generated by datamodel-codegen: +# filename: brand/rights_pricing_option.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.brand.rights_pricing_option import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import ext as ext_1 +from ..enums import pricing_model, right_use, rights_billing_period -from adcp.types.generated_poc.brand.rights_pricing_option import RightsPricingOption -# Explicit exports -__all__ = ["RightsPricingOption"] +class RightsPricingOption(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + pricing_option_id: Annotated[ + str, + Field( + description='Unique identifier for this pricing option. Referenced in acquire_rights and report_usage.' + ), + ] + model: Annotated[ + pricing_model.PricingModel, Field(description='Pricing model (cpm, flat_rate, etc.)') + ] + price: Annotated[ + StrictFloat, + Field( + description='Price amount. Interpretation depends on model: CPM = cost per 1,000 impressions, flat_rate = fixed cost per period.', + ge=0.0, + ), + ] + currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] + uses: Annotated[ + list[right_use.RightUse], + Field( + description='Which rights uses this pricing option covers. A single option can bundle multiple uses (e.g., likeness + voice).', + min_length=1, + ), + ] + period: Annotated[ + rights_billing_period.RightsBillingPeriod | None, + Field(description='Billing period for flat_rate and time-based models'), + ] = None + impression_cap: Annotated[ + SchemaInt | None, + Field(description='Maximum impressions included in this pricing option per period', ge=1), + ] = None + overage_cpm: Annotated[ + StrictFloat | None, + Field(description='CPM rate applied to impressions exceeding the impression_cap', ge=0.0), + ] = None + description: Annotated[ + str | None, Field(description='Human-readable description of this pricing option') + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/brand/rights_terms.py b/src/adcp/types/domains/brand/rights_terms.py index 94f5847f4..7e9bc7637 100644 --- a/src/adcp/types/domains/brand/rights_terms.py +++ b/src/adcp/types/domains/brand/rights_terms.py @@ -1,18 +1,45 @@ -"""Types declared by the AdCP ``brand/rights_terms`` schema. +# generated by datamodel-codegen: +# filename: brand/rights_terms.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.brand.rights_terms import +from datetime import date +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import right_use, rights_billing_period + + +class Country(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{2}$'} + + +class Exclusivity(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + scope: str | None = None + countries: list[Country] | None = None -from adcp.types.generated_poc.brand.rights_terms import Country, Exclusivity, RightsTerms -# Explicit exports -__all__ = ["Country", "Exclusivity", "RightsTerms"] +class RightsTerms(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + pricing_option_id: str + amount: Annotated[StrictFloat, Field(ge=0.0)] + currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] + period: rights_billing_period.RightsBillingPeriod | None = None + uses: list[right_use.RightUse] + impression_cap: Annotated[SchemaInt | None, Field(ge=1)] = None + overage_cpm: Annotated[StrictFloat | None, Field(ge=0.0)] = None + start_date: date | None = None + end_date: date | None = None + exclusivity: Annotated[ + Exclusivity | None, Field(description='Exclusivity terms if applicable') + ] = None diff --git a/src/adcp/types/domains/brand/search_brands_request.py b/src/adcp/types/domains/brand/search_brands_request.py index 6191bba08..605de3dc2 100644 --- a/src/adcp/types/domains/brand/search_brands_request.py +++ b/src/adcp/types/domains/brand/search_brands_request.py @@ -1,18 +1,62 @@ -"""Types declared by the AdCP ``brand/search_brands_request`` schema. +# generated by datamodel-codegen: +# filename: brand/search_brands_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.brand.search_brands_request import +from adcp.types._scalar import ScalarStr -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field + +from ..core import brand_ref +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core import pagination_request +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import advertiser_industry + + +class Country(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{2}$'} -from adcp.types.generated_poc.brand.search_brands_request import Country, SearchBrandsRequest -# Explicit exports -__all__ = ["Country", "SearchBrandsRequest"] +class SearchBrandsRequest(AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + query: Annotated[ + str, + Field( + description='Natural language description of the brands to find. The agent interprets intent, category, and roster coverage from this text.', + max_length=2000, + ), + ] + industries: Annotated[ + list[advertiser_industry.AdvertiserIndustry] | None, + Field( + description='Optional industry filter. The agent restricts results to brands operating in these industries. Values follow the advertiser-industry taxonomy; the agent SHOULD accept unknown values gracefully per the advertiser-industry extension policy.', + min_length=1, + ), + ] = None + countries: Annotated[ + list[Country] | None, + Field( + description='Optional market filter. Returns brands with rights availability in at least one of the specified countries (ISO 3166-1 alpha-2).', + min_length=1, + ), + ] = None + buyer_brand: Annotated[ + brand_ref.BrandReference | None, + Field( + description="The buyer's brand. When provided, the agent SHOULD apply the same compatibility filtering as get_rights — filtering out brands with category conflicts, competitor exclusions, or dietary restrictions that would prevent licensing. When omitted, results are returned without compatibility filtering." + ), + ] = None + pagination: Annotated[ + pagination_request.PaginationRequest | None, + Field(description='Pagination parameters for large result sets'), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/brand/search_brands_response.py b/src/adcp/types/domains/brand/search_brands_response.py index 0c0ae6e10..763918ffe 100644 --- a/src/adcp/types/domains/brand/search_brands_response.py +++ b/src/adcp/types/domains/brand/search_brands_response.py @@ -1,44 +1,204 @@ -"""Types declared by the AdCP ``brand/search_brands_response`` schema. +# generated by datamodel-codegen: +# filename: brand/search_brands_response.json +# timestamp: 2026-10-04T01:14:11+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.brand.search_brands_response import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, model_validator -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 +from ..core import pagination_response +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import advertiser_industry, right_use + + +class House(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + domain: Annotated[ + str, + Field( + description='House domain (e.g., acme-corp.com). For mutually confirmed relationships (relationship_trust = mutual or inline) this domain is trust-extending. For one-sided or unconfirmed claims see also claimed_house_domain.' + ), + ] + name: Annotated[str, Field(description='House display name')] + + +class RelationshipTrust(StrEnum): + inline = 'inline' + mutual = 'mutual' + leaf_only = 'leaf_only' + house_only = 'house_only' + standalone = 'standalone' + unverifiable = 'unverifiable' + + +class KellerType(StrEnum): + master = 'master' + sub_brand = 'sub_brand' + endorsed = 'endorsed' + independent = 'independent' + + +class Orientation(StrEnum): + square = 'square' + horizontal = 'horizontal' + vertical = 'vertical' + stacked = 'stacked' + + +class Background(StrEnum): + dark_bg = 'dark-bg' + light_bg = 'light-bg' + transparent_bg = 'transparent-bg' + + +class Variant(StrEnum): + primary = 'primary' + secondary = 'secondary' + icon = 'icon' + wordmark = 'wordmark' + full_lockup = 'full-lockup' + + +class Logo(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + url: Annotated[AnyUrl, Field(description='URL to the logo asset')] + orientation: Annotated[ + Orientation | None, Field(description='Logo aspect ratio orientation') + ] = None + background: Annotated[Background | None, Field(description='Background compatibility')] = None + variant: Annotated[Variant | None, Field(description='Logo variant type')] = None + + +class Country(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{2}$'} + + +class ExcludedCountry(Country): + pass + + +class Rights(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + available_uses: list[right_use.RightUse] | None = None + countries: Annotated[ + list[Country] | None, + Field( + description='Countries where rights are available (ISO 3166-1 alpha-2). If omitted, rights are available worldwide.' + ), + ] = None + excluded_countries: Annotated[ + list[ExcludedCountry] | None, + Field(description='Countries excluded from availability (ISO 3166-1 alpha-2)'), + ] = None + + +class SearchBrandResult(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + brand_id: Annotated[ + str, + Field( + description='Brand identifier. Pass to get_brand_identity or get_rights for the next step.' + ), + ] + house: Annotated[ + House | None, + Field( + description='The house (corporate entity) associated with this brand. Present when a house affiliation is on record. Use relationship_trust to determine whether this association has been reciprocally confirmed by the house.' + ), + ] = None + relationship_trust: Annotated[ + RelationshipTrust | None, + Field( + description="Trust verdict for the brand-to-house relationship, computed from the mutual-assertion model. inline = brand defined inside the house's own document; mutual = both sides publish the relationship (trust-extending); leaf_only = brand claims house but house has not reciprocated or declaration is expired; house_only = house claims brand, brand has not reciprocated; standalone = brand claims no house; unverifiable = claimed house could not be checked. When absent, trust has not yet been computed — absent MUST NOT be interpreted as standalone. Use get_brand_identity or verify_brand_claim for authoritative trust resolution." + ), + ] = None + relationship_verified_at: Annotated[ + AwareDatetime | None, + Field( + description='When the mutual-assertion edge was last confirmed by both sides. Present only when relationship_trust is mutual.' + ), + ] = None + claimed_house_domain: Annotated[ + str | None, + Field( + description='Canonical domain of the house this brand self-declares as its parent. Not trust-extending on its own. Present for leaf_only and unverifiable trust states where the brand has made a claim that could not be or has not been reciprocated.' + ), + ] = None + names: Annotated[ + list[dict[str, str]], + Field( + description="Localized brand names with BCP 47 locale code keys (e.g., 'en_US', 'fr_CA')." + ), + ] + description: Annotated[str | None, Field(description='Brand description')] = None + industries: Annotated[ + list[advertiser_industry.AdvertiserIndustry] | None, + Field( + description='Brand industries, following the advertiser-industry taxonomy', min_length=1 + ), + ] = None + keller_type: Annotated[ + KellerType | None, + Field( + description='Brand architecture type: master (primary brand of house), sub_brand (carries parent name), endorsed (independent identity backed by parent), independent (operates separately)' + ), + ] = None + logos: Annotated[ + list[Logo] | None, + Field( + description='Standard-resolution logos for display during discovery. High-res variants are gated behind get_brand_identity with account authorization.' + ), + ] = None + rights: Annotated[ + Rights | None, + Field( + description='Rights availability summary. Signals whether this brand has licensable rights and in which markets. For detailed pricing and rights records, use get_rights.' + ), + ] = None + + +class SearchBrandsResponse(AdcpVersionEnvelope, ProtocolEnvelope): + brands: Annotated[ + list[SearchBrandResult] | None, + Field(description='Brand stubs matching the query, ranked by relevance'), + ] = None + pagination: Annotated[ + pagination_response.PaginationResponse | None, + Field( + description='Pagination metadata. Present when has_more is true or total_count is known.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + errors: Annotated[list[error.Error] | None, Field(min_length=1)] = None -from adcp.types.generated_poc.brand.search_brands_response import ( - Background, - Country, - ExcludedCountry, - House, - KellerType, - Logo, - Orientation, - RelationshipTrust, - Rights, - SearchBrandResult, - SearchBrandsResponse, - Variant, -) - -# Explicit exports -__all__ = [ - "Background", - "Country", - "ExcludedCountry", - "House", - "KellerType", - "Logo", - "Orientation", - "RelationshipTrust", - "Rights", - "SearchBrandResult", - "SearchBrandsResponse", - "Variant", -] + @model_validator(mode='after') + def _require_schema_required_group(self) -> SearchBrandsResponse: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('brands',), ('errors',),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'SearchBrandsResponse requires at least one of these field groups: brands | errors' + ) diff --git a/src/adcp/types/domains/brand/update_rights_request.py b/src/adcp/types/domains/brand/update_rights_request.py index aeafb91ba..f9a11a174 100644 --- a/src/adcp/types/domains/brand/update_rights_request.py +++ b/src/adcp/types/domains/brand/update_rights_request.py @@ -1,18 +1,82 @@ -"""Types declared by the AdCP ``brand/update_rights_request`` schema. +# generated by datamodel-codegen: +# filename: brand/update_rights_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.brand.update_rights_request import +from adcp.types.base import AdcpRequest, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from datetime import date +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field, StrictBool + +from ..core import account_ref +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core import push_notification_config as push_notification_config_1 +from ..core.version_envelope import AdcpVersionEnvelope -from adcp.types.generated_poc.brand.update_rights_request import UpdateRightsRequest -# Explicit exports -__all__ = ["UpdateRightsRequest"] +class UpdateRightsRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + governance_context: Annotated[ + str | None, + Field( + description='Opaque intent authorization for a commitment-increasing rights update.', + max_length=4096, + min_length=1, + pattern='^[\\x20-\\x7E]+$', + ), + ] = None + rights_id: Annotated[ + str, Field(description='Rights grant identifier from acquire_rights response') + ] + account: Annotated[ + account_ref.AccountReference | None, + Field( + description='Account context for this update. Used by the brand agent to resolve any governance agent previously bound for this brand+operator pair via sync_governance — update_rights is a modification-phase governance trigger (per `/docs/governance/campaign/specification#spend-commit-invocation`) and the brand agent consults the bound agent when computing the incremental commit delta. When both an inline governance_context token (on the protocol envelope) and a bound governance agent are present, the inline token wins. Pass a natural key (brand, operator, optional sandbox) or a seller-assigned account_id from list_accounts. The estimated_impressions / commit-delta projection rule for governance-aware updates is tracked separately and not yet normative on this task.' + ), + ] = None + end_date: Annotated[ + date | None, + Field( + description='New end date for the rights grant (must be >= current end_date). Extending the grant may re-issue generation credentials with updated expiration.' + ), + ] = None + impression_cap: Annotated[ + SchemaInt | None, + Field( + description='New impression cap for the grant. Must be >= impressions already delivered.', + ge=1, + ), + ] = None + pricing_option_id: Annotated[ + str | None, + Field( + description="Switch to a different pricing option from the original get_rights offering. The new option must be compatible with the existing grant's uses and countries." + ), + ] = None + paused: Annotated[ + StrictBool | None, + Field( + description='Pause or resume the rights grant. When paused, generation credentials are suspended and creative delivery should stop. When resumed, credentials are re-activated.' + ), + ] = None + push_notification_config: Annotated[ + push_notification_config_1.PushNotificationConfig | None, + Field(description='Webhook for async update notifications if the update requires approval'), + ] = None + idempotency_key: Annotated[ + str, + Field( + description='Client-generated idempotency key for safe retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/brand/update_rights_response.py b/src/adcp/types/domains/brand/update_rights_response.py index 52840735f..f4dad3910 100644 --- a/src/adcp/types/domains/brand/update_rights_response.py +++ b/src/adcp/types/domains/brand/update_rights_response.py @@ -1,22 +1,50 @@ -"""Types declared by the AdCP ``brand/update_rights_response`` schema. +# generated by datamodel-codegen: +# filename: brand/update_rights_response.json +# timestamp: preserved-by-post-generate-fixes -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.brand.update_rights_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Any, TypeAlias -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import AwareDatetime, ConfigDict, Field + +from ..core.version_envelope import AdcpVersionEnvelope +from . import rights_terms as rights_terms_1 +from ..core import context as context_1 +from ..core import error as error_1 +from ..core import ext as ext_1 +from ..core import generation_credential as generation_credential_1 +from ..core.protocol_envelope import ProtocolEnvelope + + +class UpdateRightsResponse1(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + rights_id: str + terms: rights_terms_1.RightsTerms + generation_credentials: list[generation_credential_1.GenerationCredential] = Field( + default_factory=list + ) + rights_constraint: Any | None = None + paused: bool | None = None + implementation_date: AwareDatetime | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class UpdateRightsResponse2(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + errors: Annotated[list[error_1.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +UpdateRightsResponse: TypeAlias = UpdateRightsResponse1 | UpdateRightsResponse2 -from adcp.types.generated_poc.brand.update_rights_response import ( - UpdateRightsResponse, - UpdateRightsResponse1, - UpdateRightsResponse2, -) -# Explicit exports -__all__ = ["UpdateRightsResponse", "UpdateRightsResponse1", "UpdateRightsResponse2"] +__all__ = [ + 'UpdateRightsResponse', + 'UpdateRightsResponse1', + 'UpdateRightsResponse2', +] diff --git a/src/adcp/types/domains/brand/verification_status.py b/src/adcp/types/domains/brand/verification_status.py index 2f3c41a0b..0317fa409 100644 --- a/src/adcp/types/domains/brand/verification_status.py +++ b/src/adcp/types/domains/brand/verification_status.py @@ -1,18 +1,19 @@ -"""Types declared by the AdCP ``brand/verification_status`` schema. +# generated by datamodel-codegen: +# filename: brand/verification_status.json +# timestamp: 2026-05-22T13:15:12+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.brand.verification_status import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.brand.verification_status import VerificationStatus +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["VerificationStatus"] +class VerificationStatus(StrEnum): + owned = 'owned' + pending_review = 'pending_review' + transferring = 'transferring' + disputed = 'disputed' + not_ours = 'not_ours' + archived = 'archived' + licensed_in = 'licensed_in' + licensed_out = 'licensed_out' + unknown = 'unknown' diff --git a/src/adcp/types/domains/brand/verify_brand_claim_request.py b/src/adcp/types/domains/brand/verify_brand_claim_request.py index e28218e8a..327475a3c 100644 --- a/src/adcp/types/domains/brand/verify_brand_claim_request.py +++ b/src/adcp/types/domains/brand/verify_brand_claim_request.py @@ -1,21 +1,40 @@ -"""Types declared by the AdCP ``brand/verify_brand_claim_request`` schema. +# generated by datamodel-codegen: +# filename: brand/verify_brand_claim_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.brand.verify_brand_claim_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Any + +from ..core.version_envelope import AdcpVersionEnvelope + + +from pydantic import model_validator + + +class ClaimType(StrEnum): + subsidiary = 'subsidiary' + parent = 'parent' + property = 'property' + trademark = 'trademark' -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.brand.verify_brand_claim_request import ( - ClaimType, - VerifyBrandClaimRequest, -) +class VerifyBrandClaimRequest(AdcpRequest, AdcpVersionEnvelope): + claim_type: ClaimType + claim: dict[str, Any] -# Explicit exports -__all__ = ["ClaimType", "VerifyBrandClaimRequest"] + @model_validator(mode='after') + def _require_schema_required_group(self) -> VerifyBrandClaimRequest: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('claim_type', 'claim'),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'VerifyBrandClaimRequest requires at least one of these field groups: claim_type+claim' + ) diff --git a/src/adcp/types/domains/brand/verify_brand_claim_response.py b/src/adcp/types/domains/brand/verify_brand_claim_response.py index cbae64528..1037943f6 100644 --- a/src/adcp/types/domains/brand/verify_brand_claim_response.py +++ b/src/adcp/types/domains/brand/verify_brand_claim_response.py @@ -1,34 +1,153 @@ -"""Types declared by the AdCP ``brand/verify_brand_claim_response`` schema. +# generated by datamodel-codegen: +# filename: brand/verify_brand_claim_response.json -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.brand.verify_brand_claim_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Any, Annotated, Literal + +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field + +from ..core import context as context_1 +from ..core import error as error_1 +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from . import verification_status + + +class ClaimType(StrEnum): + subsidiary = 'subsidiary' + parent = 'parent' + property = 'property' + trademark = 'trademark' + + +class VerifyBrandClaimSignedSuccessPayload(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + claim_type: ClaimType + verification_status: verification_status.VerificationStatus + details: dict[str, Any] | None = None + context_note: Annotated[str | None, Field(max_length=500)] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class VerifyBrandClaimPayload(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + typ: Annotated[ + Literal['adcp-response-payload+jws'], + Field(description='Type discriminator preventing cross-profile replay.'), + ] + task: Annotated[ + Literal['verify_brand_claim'], + Field(description='Designated task whose response payload is signed.'), + ] + brand_domain: Annotated[ + str, + Field( + description='Brand tenant whose policy store produced the answer. The signer MUST derive this from server-side tenant resolution, not caller-supplied request fields.', + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] + agent_url: Annotated[ + AnyUrl, + Field( + description='Canonical URL of the responding brand agent entry whose response-signing key verifies this envelope.' + ), + ] + request_hash: Annotated[ + str, + Field( + description='sha256: prefix plus unpadded base64url SHA-256 of the canonical request-binding object for this call.', + pattern='^sha256:[A-Za-z0-9_-]{43}$', + ), + ] + iat: Annotated[int, Field(description='Issued-at time as Unix epoch seconds.', ge=0)] + exp: Annotated[ + int, + Field( + description='Expiration time as Unix epoch seconds. Online verifiers reject envelopes after this time, allowing only implementation-defined clock skew.', + ge=0, + ), + ] + response: VerifyBrandClaimSignedSuccessPayload + + +class VerifyBrandClaimSignedResponse(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + protected: Annotated[ + str, + Field( + description='Base64url-encoded JWS protected header. The decoded header MUST include alg, kid, and typ: adcp-response-payload+jws, and MUST NOT include the RFC 7797 b64 header. Verifiers enforce the key purpose by resolving kid to a JWK with adcp_use: response-signing.', + pattern='^[A-Za-z0-9_-]+$', + ), + ] + payload: Annotated[ + VerifyBrandClaimPayload, + Field( + description='Decoded signed payload. Signers compute the JWS payload bytes from the RFC 8785/JCS canonicalization of this object.' + ), + ] + signature: Annotated[ + str, + Field( + description='Base64url-encoded JWS signature over the protected header and canonicalized payload.', + pattern='^[A-Za-z0-9_-]+$', + ), + ] + + +class VerifyBrandClaimSuccessResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + claim_type: Annotated[ + ClaimType, + Field(description="Echoes the request's claim_type for caller-side routing."), + ] + verification_status: Annotated[ + verification_status.VerificationStatus, + Field( + description='Verification status. Not every status applies to every claim_type - see the task page for the applicable subset. Renamed from `status` in 3.1 to free the top-level `status` key for the envelope task-status (TaskStatus) under MCP flat-on-the-wire serialization (#4878).' + ), + ] + signed_response: Annotated[ + VerifyBrandClaimSignedResponse, + Field( + description='Payload-envelope JWS attesting the canonical success response for verify_brand_claim. The signed payload response MUST match the unsigned task-body fields on this response, excluding signed_response and protocol/version envelope fields.' + ), + ] + details: Annotated[ + dict[str, Any] | None, + Field( + description="Per-claim-type response fields. Shape varies - see the task page for each claim_type's expected fields." + ), + ] = None + context_note: Annotated[ + str | None, + Field(description='Public - free-text context the brand chooses to surface.', max_length=500), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class VerifyBrandClaimErrorResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + errors: Annotated[list[error_1.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.brand.verify_brand_claim_response import ( - ClaimType, - VerifyBrandClaimErrorResponse, - VerifyBrandClaimPayload, - VerifyBrandClaimResponse, - VerifyBrandClaimSignedResponse, - VerifyBrandClaimSignedSuccessPayload, - VerifyBrandClaimSuccessResponse, -) - -# Explicit exports -__all__ = [ - "ClaimType", - "VerifyBrandClaimErrorResponse", - "VerifyBrandClaimPayload", - "VerifyBrandClaimResponse", - "VerifyBrandClaimSignedResponse", - "VerifyBrandClaimSignedSuccessPayload", - "VerifyBrandClaimSuccessResponse", -] +VerifyBrandClaimResponse = VerifyBrandClaimSuccessResponse | VerifyBrandClaimErrorResponse diff --git a/src/adcp/types/domains/brand/verify_brand_claims_request.py b/src/adcp/types/domains/brand/verify_brand_claims_request.py index 996006797..487612174 100644 --- a/src/adcp/types/domains/brand/verify_brand_claims_request.py +++ b/src/adcp/types/domains/brand/verify_brand_claims_request.py @@ -1,48 +1,164 @@ -"""Types declared by the AdCP ``brand/verify_brand_claims_request`` schema. +# generated by datamodel-codegen: +# filename: brand/verify_brand_claims_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.brand.verify_brand_claims_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import AwareDatetime, ConfigDict, Field + +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import property_type + + +class Claim(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + subsidiary_domain: Annotated[ + str, + Field( + description='Domain of the leaf brand whose `house_domain` claim is being verified.', + pattern='^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]{0,61}[A-Za-z0-9])$', + ), + ] + subsidiary_brand_id: Annotated[ + str | None, + Field( + description='Stable brand identifier the leaf uses for itself. Optional but recommended.', + pattern='^[a-z0-9_]+$', + ), + ] = None + observed_at: Annotated[ + AwareDatetime | None, Field(description="When the caller observed the leaf's claim.") + ] = None + + +class ClaimEntry1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + claim_type: Literal['subsidiary'] = 'subsidiary' + claim: Claim + + +class Claim1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + parent_domain: Annotated[ + str, + Field( + description="Domain of the house claimed as this brand's parent.", + pattern='^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]{0,61}[A-Za-z0-9])$', + ), + ] + claimant_says: Annotated[ + str | None, Field(description='Optional context — what the claimant published or said.') + ] = None + observed_at: AwareDatetime | None = None + + +class ClaimEntry2(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + claim_type: Literal['parent'] = 'parent' + claim: Claim1 + + +class Store(StrEnum): + apple = 'apple' + google = 'google' + amazon = 'amazon' + roku = 'roku' + fire_tv = 'fire_tv' + samsung = 'samsung' + lg = 'lg' + vizio = 'vizio' + other = 'other' + + +class Property(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + type: property_type.PropertyType + identifier: str + store: Store | None = None + region: str | None = None -from adcp.types.generated_poc.brand.verify_brand_claims_request import ( - Claim, - Claim1, - Claim2, - Claim3, - ClaimEntry, - ClaimEntry1, - ClaimEntry2, - ClaimEntry3, - ClaimEntry4, - Country, - Property, - Store, - VerifyBrandClaimsRequest, - VerifyBrandClaimsRequestBulk, -) - -# Explicit exports -__all__ = [ - "Claim", - "Claim1", - "Claim2", - "Claim3", - "ClaimEntry", - "ClaimEntry1", - "ClaimEntry2", - "ClaimEntry3", - "ClaimEntry4", - "Country", - "Property", - "Store", - "VerifyBrandClaimsRequest", - "VerifyBrandClaimsRequestBulk", + +class Claim2(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + property: Annotated[Property, Field(description='Shape matches brand.json properties[] entry.')] + use_case: Annotated[ + str | None, Field(description='Optional caller-declared use case.', max_length=100) + ] = None + + +class ClaimEntry3(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + claim_type: Literal['property'] = 'property' + claim: Claim2 + + +class Country(ScalarStr): + __slots__ = () + _constraints = {'max_length': 2, 'min_length': 2} + + +class Claim3(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + mark: Annotated[str, Field(max_length=200, min_length=1)] + registry: Annotated[str | None, Field(max_length=50)] = None + number: Annotated[str | None, Field(max_length=100)] = None + countries: list[Country] | None = None + use_case: Annotated[str | None, Field(max_length=100)] = None + + +class ClaimEntry4(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + claim_type: Literal['trademark'] = 'trademark' + claim: Claim3 + + +ClaimEntry = Annotated[ + ClaimEntry1 | ClaimEntry2 | ClaimEntry3 | ClaimEntry4, + Field( + description='One verification claim. Shape mirrors the single-target `verify_brand_claim` request body (minus the version envelope, which lives on the batch).', + discriminator='claim_type', + ), ] + + +class VerifyBrandClaimsRequestBulk(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + claims: Annotated[ + list[ClaimEntry], + Field( + description='Ordered list of verification claims. The agent MUST return `results[]` in the same order (positional zip-by-index). Maximum batch size is 100 per call; agents MAY enforce a lower limit and SHOULD advertise it via `get_adcp_capabilities` (see the task page).', + max_length=100, + min_length=1, + ), + ] + + +class VerifyBrandClaimsRequest(VerifyBrandClaimsRequestBulk): + pass diff --git a/src/adcp/types/domains/brand/verify_brand_claims_response.py b/src/adcp/types/domains/brand/verify_brand_claims_response.py index fac86ce43..31e8b68e2 100644 --- a/src/adcp/types/domains/brand/verify_brand_claims_response.py +++ b/src/adcp/types/domains/brand/verify_brand_claims_response.py @@ -1,40 +1,185 @@ -"""Types declared by the AdCP ``brand/verify_brand_claims_response`` schema. +# generated by datamodel-codegen: +# filename: brand/verify_brand_claims_response.json -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.brand.verify_brand_claims_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any, Literal + +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field, RootModel + +from ..core import context as context_1 +from ..core import error as error_1 +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from . import verification_status + + +class ClaimType(StrEnum): + subsidiary = 'subsidiary' + parent = 'parent' + property = 'property' + trademark = 'trademark' + + +class ResultEntry1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + claim_type: Annotated[ + ClaimType, + Field(description="Echoes the request item's claim_type for caller-side routing."), + ] + status: Annotated[ + verification_status.VerificationStatus, + Field( + description='Verification status for this claim. Not every status applies to every claim_type - see the single-target task page for the applicable subset.' + ), + ] + details: Annotated[ + dict[str, Any] | None, + Field( + description="Per-claim-type response fields. Shape varies - see the single-target task page for each claim_type's expected fields." + ), + ] = None + context_note: Annotated[ + str | None, + Field(description='Public - free-text context the brand chooses to surface.', max_length=500), + ] = None + + +class ResultEntry2(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + error: error_1.Error + + +class ResultEntry(RootModel[ResultEntry1 | ResultEntry2]): + root: Annotated[ + ResultEntry1 | ResultEntry2, + Field( + description='One entry in `results[]`. Either a per-claim success (claim_type + status + optional details/context_note) or a per-claim error (error field only). Mirrors the single-target `verify_brand_claim` response success arm shape.' + ), + ] + + def __getattr__(self, name: str) -> Any: + """Proxy attribute access to the wrapped type.""" + if name.startswith('_'): + raise AttributeError(name) + return getattr(self.root, name) + + +class VerifyBrandClaimsSignedSuccessPayload(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + results: Annotated[list[ResultEntry], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class VerifyBrandClaimsPayload(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + typ: Annotated[ + Literal['adcp-response-payload+jws'], + Field(description='Type discriminator preventing cross-profile replay.'), + ] + task: Annotated[ + Literal['verify_brand_claims'], + Field(description='Designated task whose response payload is signed.'), + ] + brand_domain: Annotated[ + str, + Field( + description='Brand tenant whose policy store produced the answer. The signer MUST derive this from server-side tenant resolution, not caller-supplied request fields.', + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] + agent_url: Annotated[ + AnyUrl, + Field( + description='Canonical URL of the responding brand agent entry whose response-signing key verifies this envelope.' + ), + ] + request_hash: Annotated[ + str, + Field( + description='sha256: prefix plus unpadded base64url SHA-256 of the canonical request-binding object for this call.', + pattern='^sha256:[A-Za-z0-9_-]{43}$', + ), + ] + iat: Annotated[int, Field(description='Issued-at time as Unix epoch seconds.', ge=0)] + exp: Annotated[ + int, + Field( + description='Expiration time as Unix epoch seconds. Online verifiers reject envelopes after this time, allowing only implementation-defined clock skew.', + ge=0, + ), + ] + response: VerifyBrandClaimsSignedSuccessPayload + + +class VerifyBrandClaimsSignedResponse(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + protected: Annotated[ + str, + Field( + description='Base64url-encoded JWS protected header. The decoded header MUST include alg, kid, and typ: adcp-response-payload+jws, and MUST NOT include the RFC 7797 b64 header. Verifiers enforce the key purpose by resolving kid to a JWK with adcp_use: response-signing.', + pattern='^[A-Za-z0-9_-]+$', + ), + ] + payload: Annotated[ + VerifyBrandClaimsPayload, + Field( + description='Decoded signed payload. Signers compute the JWS payload bytes from the RFC 8785/JCS canonicalization of this object.' + ), + ] + signature: Annotated[ + str, + Field( + description='Base64url-encoded JWS signature over the protected header and canonicalized payload.', + pattern='^[A-Za-z0-9_-]+$', + ), + ] + + +class VerifyBrandClaimsResponseBulk(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + results: Annotated[ + list[ResultEntry], + Field( + description="Per-claim results, positionally aligned with the request's claims.", + min_length=1, + ), + ] + signed_response: Annotated[ + VerifyBrandClaimsSignedResponse, + Field( + description='Payload-envelope JWS attesting the canonical bulk success response for verify_brand_claims. The signed payload response MUST match the unsigned task-body fields on this response, excluding signed_response and protocol/version envelope fields.' + ), + ] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class VerifyBrandClaimsErrorResponse(AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + errors: Annotated[list[error_1.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.brand.verify_brand_claims_response import ( - ClaimType, - ResultEntry, - ResultEntry1, - ResultEntry2, - VerifyBrandClaimsErrorResponse, - VerifyBrandClaimsPayload, - VerifyBrandClaimsResponse, - VerifyBrandClaimsResponseBulk, - VerifyBrandClaimsSignedResponse, - VerifyBrandClaimsSignedSuccessPayload, -) - -# Explicit exports -__all__ = [ - "ClaimType", - "ResultEntry", - "ResultEntry1", - "ResultEntry2", - "VerifyBrandClaimsErrorResponse", - "VerifyBrandClaimsPayload", - "VerifyBrandClaimsResponse", - "VerifyBrandClaimsResponseBulk", - "VerifyBrandClaimsSignedResponse", - "VerifyBrandClaimsSignedSuccessPayload", -] +VerifyBrandClaimsResponse = VerifyBrandClaimsResponseBulk | VerifyBrandClaimsErrorResponse diff --git a/src/adcp/types/generated_poc/brand_discovery.py b/src/adcp/types/domains/brand_discovery.py similarity index 97% rename from src/adcp/types/generated_poc/brand_discovery.py rename to src/adcp/types/domains/brand_discovery.py index 3323dad01..667f4beef 100644 --- a/src/adcp/types/generated_poc/brand_discovery.py +++ b/src/adcp/types/domains/brand_discovery.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: _brand_discovery.json -# timestamp: 2026-10-04T01:00:03+00:00 +# timestamp: 2026-10-04T18:54:20+00:00 from __future__ import annotations @@ -3230,3 +3230,181 @@ class BrandDiscovery5(Brand): title='Brand Discovery', ), ] + + +__all__ = [ + "AgenticCheckout", + "Agents", + "AiTool", + "AnimationSpeed", + "Architecture", + "Asset", + "AssetContentType", + "AssetLibrary", + "AuthorizedOperator", + "Avatar", + "Background", + "BackgroundContrast", + "Backgrounds", + "BorderRadius", + "Brand", + "Brand1", + "BrandAgent", + "BrandAgentEntry", + "BrandAgentType", + "BrandDiscovery", + "BrandDiscovery1", + "BrandDiscovery2", + "BrandDiscovery3", + "BrandDiscovery4", + "BrandDiscovery5", + "BrandId", + "BrandIdModel", + "BrandKitOverride", + "BrandRef", + "BrandShapes", + "C2pa", + "C2paWatermarkAction", + "Collection", + "ColorConstraint", + "ColorGuide", + "ColorRef", + "ColorRef1", + "ColorRef2", + "ColorRef3", + "ColorTemperature", + "ColorValue", + "ColorValue1", + "ColorValue2", + "ColorValue2Item", + "Colors", + "Colors1", + "Colorway", + "CompositionRules", + "Contact", + "Contact1", + "Contrast", + "CornerStyle", + "Country", + "Country1", + "DataSubjectContestation", + "DataSubjectContestation1", + "DeclaredBy", + "DefaultLanguage", + "DepthOfField", + "DigitalSourceType", + "Disclaimer", + "Disclosure", + "DisclosurePersistence", + "DisclosurePosition", + "Domain", + "Elevation", + "EmbeddedProvenanceItem", + "EmbeddedProvenanceMethod", + "Ext", + "Fallback", + "FeedFormat", + "FocalPointItem", + "FontFile", + "FontRole", + "FontRole1", + "Fonts", + "Framing", + "GradientStyle", + "GraphicElement", + "GraphicStyle", + "GuidelineSeverity", + "HexColor", + "House", + "HumanOversight", + "Iconography", + "IdentityRelyingParty", + "ImageAsset", + "Jurisdiction", + "KellerType", + "Kind", + "LicenseType", + "LocaleTag", + "LocalizedName", + "LocalizedScalar", + "LocalizedScalar1", + "LocalizedStringList", + "LocalizedStringList1", + "LocalizedStringListEntry", + "LockupType", + "Logo", + "LogoId", + "LogoPlacement", + "LogoSlot", + "LogoUsageRule", + "MarkLockup", + "MinimumSize", + "MotionGuidelines", + "NiceClass", + "OpentypeFeature", + "Ordering", + "Orientation", + "Orientation1", + "Overlays", + "Pacing", + "Palette", + "People", + "PhotographyStyle", + "PortfolioEntry", + "PreferredAspectRatio", + "PreferredPosition", + "ProductCatalog", + "ProductFocus", + "Property", + "PropertyType", + "Provenance", + "Realism", + "RedirectReason", + "Relationship", + "RenderGuidance", + "Result", + "RightType", + "RightUse", + "RightsAgent", + "Role", + "Scale", + "Scope", + "Scope1", + "Separator", + "Spacing", + "Status", + "Store", + "StrokeStyle", + "Style", + "Style1", + "Style2", + "StyleType", + "Subject", + "Surface", + "Tagline", + "TalentRole", + "TextEntrance", + "TextTransform", + "Texture", + "Tone", + "Trademark", + "TransitionStyle", + "Type", + "Type1", + "Type2", + "TypeScale", + "TypeScaleEntry", + "TypesAllowedEnum", + "UpdateFrequency", + "Usage", + "Usage1", + "Variant", + "VerificationItem", + "VerifyAgent", + "VerifyAgent1", + "VisualGuidelines", + "VoiceSynthesis", + "Watermark", + "WatermarkMediaType", + "WeightRangeItem", +] diff --git a/src/adcp/types/domains/brand_discovery/__init__.py b/src/adcp/types/domains/brand_discovery/__init__.py deleted file mode 100644 index 3cacbd3a6..000000000 --- a/src/adcp/types/domains/brand_discovery/__init__.py +++ /dev/null @@ -1,370 +0,0 @@ -"""Types the AdCP ``brand_discovery`` schemas declare. - -Importing from the domain says which variant you mean, where the flat -``adcp.types`` namespace can only bind one class per name: - - from adcp.types.domains.brand_discovery import - -This domain has one schema, so every type it declares is here. - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:33:59 UTC -""" - -# ruff: noqa: E501, I001 -from __future__ import annotations - -from adcp.types.generated_poc.brand_discovery import ( - AgenticCheckout, - Agents, - AiTool, - AnimationSpeed, - Architecture, - Asset, - AssetContentType, - AssetLibrary, - AuthorizedOperator, - Avatar, - Background, - BackgroundContrast, - Backgrounds, - BorderRadius, - Brand, - Brand1, - BrandAgent, - BrandAgentEntry, - BrandAgentType, - BrandDiscovery, - BrandDiscovery1, - BrandDiscovery2, - BrandDiscovery3, - BrandDiscovery4, - BrandDiscovery5, - BrandId, - BrandIdModel, - BrandKitOverride, - BrandRef, - BrandShapes, - C2pa, - C2paWatermarkAction, - Collection, - ColorConstraint, - ColorGuide, - ColorRef, - ColorRef1, - ColorRef2, - ColorRef3, - ColorTemperature, - ColorValue, - ColorValue1, - ColorValue2, - ColorValue2Item, - Colors, - Colors1, - Colorway, - CompositionRules, - Contact, - Contact1, - Contrast, - CornerStyle, - Country, - Country1, - DataSubjectContestation, - DataSubjectContestation1, - DeclaredBy, - DefaultLanguage, - DepthOfField, - DigitalSourceType, - Disclaimer, - Disclosure, - DisclosurePersistence, - DisclosurePosition, - Domain, - Elevation, - EmbeddedProvenanceItem, - EmbeddedProvenanceMethod, - Ext, - Fallback, - FeedFormat, - FocalPointItem, - FontFile, - FontRole, - FontRole1, - Fonts, - Framing, - GradientStyle, - GraphicElement, - GraphicStyle, - GuidelineSeverity, - HexColor, - House, - HumanOversight, - Iconography, - IdentityRelyingParty, - ImageAsset, - Jurisdiction, - KellerType, - Kind, - LicenseType, - LocaleTag, - LocalizedName, - LocalizedScalar, - LocalizedScalar1, - LocalizedStringList, - LocalizedStringList1, - LocalizedStringListEntry, - LockupType, - Logo, - LogoId, - LogoPlacement, - LogoSlot, - LogoUsageRule, - MarkLockup, - MinimumSize, - MotionGuidelines, - NiceClass, - OpentypeFeature, - Ordering, - Orientation, - Orientation1, - Overlays, - Pacing, - Palette, - People, - PhotographyStyle, - PortfolioEntry, - PreferredAspectRatio, - PreferredPosition, - ProductCatalog, - ProductFocus, - Property, - PropertyType, - Provenance, - Realism, - RedirectReason, - Relationship, - RenderGuidance, - Result, - RightType, - RightUse, - RightsAgent, - Role, - Scale, - Scope, - Scope1, - Separator, - Spacing, - Status, - Store, - StrokeStyle, - Style, - Style1, - Style2, - StyleType, - Subject, - Surface, - Tagline, - TalentRole, - TextEntrance, - TextTransform, - Texture, - Tone, - Trademark, - TransitionStyle, - Type, - Type1, - Type2, - TypeScale, - TypeScaleEntry, - TypesAllowedEnum, - UpdateFrequency, - Usage, - Usage1, - Variant, - VerificationItem, - VerifyAgent, - VerifyAgent1, - VisualGuidelines, - VoiceSynthesis, - Watermark, - WatermarkMediaType, - WeightRangeItem, -) - -# Explicit exports -__all__ = [ - "AgenticCheckout", - "Agents", - "AiTool", - "AnimationSpeed", - "Architecture", - "Asset", - "AssetContentType", - "AssetLibrary", - "AuthorizedOperator", - "Avatar", - "Background", - "BackgroundContrast", - "Backgrounds", - "BorderRadius", - "Brand", - "Brand1", - "BrandAgent", - "BrandAgentEntry", - "BrandAgentType", - "BrandDiscovery", - "BrandDiscovery1", - "BrandDiscovery2", - "BrandDiscovery3", - "BrandDiscovery4", - "BrandDiscovery5", - "BrandId", - "BrandIdModel", - "BrandKitOverride", - "BrandRef", - "BrandShapes", - "C2pa", - "C2paWatermarkAction", - "Collection", - "ColorConstraint", - "ColorGuide", - "ColorRef", - "ColorRef1", - "ColorRef2", - "ColorRef3", - "ColorTemperature", - "ColorValue", - "ColorValue1", - "ColorValue2", - "ColorValue2Item", - "Colors", - "Colors1", - "Colorway", - "CompositionRules", - "Contact", - "Contact1", - "Contrast", - "CornerStyle", - "Country", - "Country1", - "DataSubjectContestation", - "DataSubjectContestation1", - "DeclaredBy", - "DefaultLanguage", - "DepthOfField", - "DigitalSourceType", - "Disclaimer", - "Disclosure", - "DisclosurePersistence", - "DisclosurePosition", - "Domain", - "Elevation", - "EmbeddedProvenanceItem", - "EmbeddedProvenanceMethod", - "Ext", - "Fallback", - "FeedFormat", - "FocalPointItem", - "FontFile", - "FontRole", - "FontRole1", - "Fonts", - "Framing", - "GradientStyle", - "GraphicElement", - "GraphicStyle", - "GuidelineSeverity", - "HexColor", - "House", - "HumanOversight", - "Iconography", - "IdentityRelyingParty", - "ImageAsset", - "Jurisdiction", - "KellerType", - "Kind", - "LicenseType", - "LocaleTag", - "LocalizedName", - "LocalizedScalar", - "LocalizedScalar1", - "LocalizedStringList", - "LocalizedStringList1", - "LocalizedStringListEntry", - "LockupType", - "Logo", - "LogoId", - "LogoPlacement", - "LogoSlot", - "LogoUsageRule", - "MarkLockup", - "MinimumSize", - "MotionGuidelines", - "NiceClass", - "OpentypeFeature", - "Ordering", - "Orientation", - "Orientation1", - "Overlays", - "Pacing", - "Palette", - "People", - "PhotographyStyle", - "PortfolioEntry", - "PreferredAspectRatio", - "PreferredPosition", - "ProductCatalog", - "ProductFocus", - "Property", - "PropertyType", - "Provenance", - "Realism", - "RedirectReason", - "Relationship", - "RenderGuidance", - "Result", - "RightType", - "RightUse", - "RightsAgent", - "Role", - "Scale", - "Scope", - "Scope1", - "Separator", - "Spacing", - "Status", - "Store", - "StrokeStyle", - "Style", - "Style1", - "Style2", - "StyleType", - "Subject", - "Surface", - "Tagline", - "TalentRole", - "TextEntrance", - "TextTransform", - "Texture", - "Tone", - "Trademark", - "TransitionStyle", - "Type", - "Type1", - "Type2", - "TypeScale", - "TypeScaleEntry", - "TypesAllowedEnum", - "UpdateFrequency", - "Usage", - "Usage1", - "Variant", - "VerificationItem", - "VerifyAgent", - "VerifyAgent1", - "VisualGuidelines", - "VoiceSynthesis", - "Watermark", - "WatermarkMediaType", - "WeightRangeItem", -] diff --git a/src/adcp/types/generated_poc/bundled/__init__.py b/src/adcp/types/domains/bundled/__init__.py similarity index 100% rename from src/adcp/types/generated_poc/bundled/__init__.py rename to src/adcp/types/domains/bundled/__init__.py diff --git a/src/adcp/types/generated_poc/bundled/protocol/__init__.py b/src/adcp/types/domains/bundled/protocol/__init__.py similarity index 100% rename from src/adcp/types/generated_poc/bundled/protocol/__init__.py rename to src/adcp/types/domains/bundled/protocol/__init__.py diff --git a/src/adcp/types/generated_poc/bundled/protocol/get_adcp_capabilities_response.py b/src/adcp/types/domains/bundled/protocol/get_adcp_capabilities_response.py similarity index 100% rename from src/adcp/types/generated_poc/bundled/protocol/get_adcp_capabilities_response.py rename to src/adcp/types/domains/bundled/protocol/get_adcp_capabilities_response.py diff --git a/src/adcp/types/domains/collection/__init__.py b/src/adcp/types/domains/collection/__init__.py index 9a131a6e1..cde3db016 100644 --- a/src/adcp/types/domains/collection/__init__.py +++ b/src/adcp/types/domains/collection/__init__.py @@ -9,61 +9,51 @@ it from its own schema's module, ``adcp.types.domains.collection.``. Nothing here is renamed. -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:33:59 UTC +Auto-generated from the generated domain tree. DO NOT EDIT MANUALLY. +Generation date: 2026-10-04 18:45:11 UTC """ # ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.collection.base_collection_source import ( +from adcp.types.domains.collection.base_collection_source import ( BaseCollectionSource, BaseCollectionSource1, BaseCollectionSource2, BaseCollectionSource3, Identifier, ) -from adcp.types.generated_poc.collection.collection_list import CollectionList -from adcp.types.generated_poc.collection.collection_list_changed_webhook import ( +from adcp.types.domains.collection.collection_list import CollectionList +from adcp.types.domains.collection.collection_list_changed_webhook import ( ChangeSummary, CollectionListChangedWebhook, ) -from adcp.types.generated_poc.collection.collection_list_filters import ( +from adcp.types.domains.collection.collection_list_filters import ( CollectionListFilters, ExcludeDistributionId, ) -from adcp.types.generated_poc.collection.create_collection_list_request import ( - CreateCollectionListRequest, -) -from adcp.types.generated_poc.collection.create_collection_list_response import ( +from adcp.types.domains.collection.create_collection_list_request import CreateCollectionListRequest +from adcp.types.domains.collection.create_collection_list_response import ( CreateCollectionListResponse, ) -from adcp.types.generated_poc.collection.delete_collection_list_request import ( - DeleteCollectionListRequest, -) -from adcp.types.generated_poc.collection.delete_collection_list_response import ( +from adcp.types.domains.collection.delete_collection_list_request import DeleteCollectionListRequest +from adcp.types.domains.collection.delete_collection_list_response import ( DeleteCollectionListResponse, ) -from adcp.types.generated_poc.collection.get_collection_list_request import ( +from adcp.types.domains.collection.get_collection_list_request import ( GetCollectionListRequest, Pagination, ) -from adcp.types.generated_poc.collection.get_collection_list_response import ( +from adcp.types.domains.collection.get_collection_list_response import ( Collection, CoverageGap, DistributionId, GetCollectionListResponse, ) -from adcp.types.generated_poc.collection.list_collection_lists_request import ( - ListCollectionListsRequest, -) -from adcp.types.generated_poc.collection.list_collection_lists_response import ( - ListCollectionListsResponse, -) -from adcp.types.generated_poc.collection.update_collection_list_request import ( - UpdateCollectionListRequest, -) -from adcp.types.generated_poc.collection.update_collection_list_response import ( +from adcp.types.domains.collection.list_collection_lists_request import ListCollectionListsRequest +from adcp.types.domains.collection.list_collection_lists_response import ListCollectionListsResponse +from adcp.types.domains.collection.update_collection_list_request import UpdateCollectionListRequest +from adcp.types.domains.collection.update_collection_list_response import ( UpdateCollectionListResponse, ) diff --git a/src/adcp/types/domains/collection/base_collection_source.py b/src/adcp/types/domains/collection/base_collection_source.py index 7ac3232f9..fbc4c3806 100644 --- a/src/adcp/types/domains/collection/base_collection_source.py +++ b/src/adcp/types/domains/collection/base_collection_source.py @@ -1,30 +1,107 @@ -"""Types declared by the AdCP ``collection/base_collection_source`` schema. +# generated by datamodel-codegen: +# filename: collection/base_collection_source.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.collection.base_collection_source import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field + +from ..enums import distribution_identifier_type +from ..enums import genre_taxonomy as genre_taxonomy_1 + + +class BaseCollectionSource2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + selection_type: Annotated[ + Literal['publisher_collections'], + Field( + description='Discriminator indicating selection by specific collection IDs within a publisher' + ), + ] = 'publisher_collections' + publisher_domain: Annotated[ + str, + Field( + description="Domain where publisher's adagents.json is hosted", + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] + collection_ids: Annotated[ + list[str], + Field( + description="Specific collection IDs from the publisher's adagents.json", min_length=1 + ), + ] + + +class Identifier(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Annotated[ + distribution_identifier_type.DistributionIdentifierType, + Field(description='Type of distribution identifier'), + ] + value: Annotated[str, Field(description='The identifier value')] + + +class BaseCollectionSource1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + selection_type: Annotated[ + Literal['distribution_ids'], + Field( + description='Discriminator indicating selection by platform-independent distribution identifiers' + ), + ] = 'distribution_ids' + identifiers: Annotated[ + list[Identifier], + Field( + description='Platform-independent identifiers (imdb_id, gracenote_id, eidr_id, etc.). Each identifier uniquely identifies a collection across all publishers. platform_channel_id is not allowed here: its identity is the tuple (publisher_domain, value), which this bare {type, value} shape cannot carry.', + min_length=1, + ), + ] + + +class BaseCollectionSource3(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + selection_type: Annotated[ + Literal['publisher_genres'], + Field(description='Discriminator indicating selection by genre within a publisher'), + ] = 'publisher_genres' + publisher_domain: Annotated[ + str, + Field( + description="Domain where publisher's adagents.json is hosted", + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] + genres: Annotated[ + list[str], + Field( + description="Genre values to match against the publisher's collections", min_length=1 + ), + ] + genre_taxonomy: Annotated[ + genre_taxonomy_1.GenreTaxonomy, + Field( + description="Taxonomy for the genre values. Required so sellers can interpret genre strings unambiguously. Use 'custom' for free-form values negotiated out of band." + ), + ] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.collection.base_collection_source import ( - BaseCollectionSource, - BaseCollectionSource1, - BaseCollectionSource2, - BaseCollectionSource3, - Identifier, -) - -# Explicit exports -__all__ = [ - "BaseCollectionSource", - "BaseCollectionSource1", - "BaseCollectionSource2", - "BaseCollectionSource3", - "Identifier", +BaseCollectionSource = Annotated[ + BaseCollectionSource1 | BaseCollectionSource2 | BaseCollectionSource3, + Field( + description='A source of collections for a collection list. Supports three selection patterns: distribution identifiers (cross-publisher), publisher-specific collection IDs, or publisher-specific genres.', + discriminator='selection_type', + title='Base Collection Source', + ), ] diff --git a/src/adcp/types/domains/collection/collection_list.py b/src/adcp/types/domains/collection/collection_list.py index 5295fefdb..cc082bc3b 100644 --- a/src/adcp/types/domains/collection/collection_list.py +++ b/src/adcp/types/domains/collection/collection_list.py @@ -1,18 +1,69 @@ -"""Types declared by the AdCP ``collection/collection_list`` schema. +# generated by datamodel-codegen: +# filename: collection/collection_list.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.collection.collection_list import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import account_ref, brand_ref +from . import base_collection_source, collection_list_filters -from adcp.types.generated_poc.collection.collection_list import CollectionList -# Explicit exports -__all__ = ["CollectionList"] +class CollectionList(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + list_id: Annotated[str, Field(description='Unique identifier for this collection list')] + name: Annotated[str, Field(description='Human-readable name for the list')] + description: Annotated[str | None, Field(description="Description of the list's purpose")] = ( + None + ) + account: Annotated[ + account_ref.AccountReference | None, + Field( + description='Account that owns this list. Returned as account_id form (seller-assigned identifier).' + ), + ] = None + base_collections: Annotated[ + list[base_collection_source.BaseCollectionSource] | None, + Field( + description="Array of collection sources to evaluate. Each entry is a discriminated union: distribution_ids (platform-independent identifiers), publisher_collections (publisher_domain + collection_ids), or publisher_genres (publisher_domain + genres). If omitted, queries the agent's entire collection database." + ), + ] = None + filters: Annotated[ + collection_list_filters.CollectionListFilters | None, + Field(description='Dynamic filters applied when resolving the list'), + ] = None + brand: Annotated[ + brand_ref.BrandReference | None, + Field( + description='Brand reference used to automatically apply appropriate rules. Resolved to full brand identity at execution time.' + ), + ] = None + webhook_url: Annotated[ + AnyUrl | None, + Field(description='URL to receive notifications when the resolved list changes'), + ] = None + cache_duration_hours: Annotated[ + SchemaInt | None, + Field( + description='Recommended cache duration for resolved list. Consumers should re-fetch after this period. Defaults to 168 (one week) because collection metadata changes less frequently than property metadata.', + ge=1, + ), + ] = 168 + created_at: Annotated[AwareDatetime | None, Field(description='When the list was created')] = ( + None + ) + updated_at: Annotated[ + AwareDatetime | None, Field(description='When the list was last modified') + ] = None + collection_count: Annotated[ + SchemaInt | None, + Field( + description='Number of collections in the resolved list (at time of last resolution)' + ), + ] = None diff --git a/src/adcp/types/domains/collection/collection_list_changed_webhook.py b/src/adcp/types/domains/collection/collection_list_changed_webhook.py index d06bec8f2..763b93045 100644 --- a/src/adcp/types/domains/collection/collection_list_changed_webhook.py +++ b/src/adcp/types/domains/collection/collection_list_changed_webhook.py @@ -1,21 +1,60 @@ -"""Types declared by the AdCP ``collection/collection_list_changed_webhook`` schema. +# generated by datamodel-codegen: +# filename: collection/collection_list_changed_webhook.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.collection.collection_list_changed_webhook import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import ext as ext_1 + + +class ChangeSummary(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + collections_added: Annotated[ + SchemaInt | None, Field(description='Number of collections added since last resolution') + ] = None + collections_removed: Annotated[ + SchemaInt | None, Field(description='Number of collections removed since last resolution') + ] = None + total_collections: Annotated[ + SchemaInt | None, Field(description='Total collections in the resolved list') + ] = None -from adcp.types.generated_poc.collection.collection_list_changed_webhook import ( - ChangeSummary, - CollectionListChangedWebhook, -) -# Explicit exports -__all__ = ["ChangeSummary", "CollectionListChangedWebhook"] +class CollectionListChangedWebhook(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + idempotency_key: Annotated[ + str, + Field( + description='Sender-generated key stable across retries of the same webhook event. Governance agents MUST generate a cryptographically random value (UUID v4 recommended) per distinct list-change event and reuse the same key on every retry. Recipients MUST dedupe by this key, scoped to the authenticated sender identity (HMAC secret or Bearer credential) — keys from different governance agents are independent.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + event: Annotated[Literal['collection_list_changed'], Field(description='The event type')] = 'collection_list_changed' + list_id: Annotated[str, Field(description='ID of the collection list that changed')] + list_name: Annotated[str | None, Field(description='Name of the collection list')] = None + change_summary: Annotated[ + ChangeSummary | None, Field(description='Summary of changes to the resolved list') + ] = None + resolved_at: Annotated[AwareDatetime, Field(description='When the list was re-resolved')] + cache_valid_until: Annotated[ + AwareDatetime | None, + Field(description='When the consumer should refresh from the governance agent'), + ] = None + signature: Annotated[ + str, + Field( + description='HMAC-SHA256 webhook signature over {unix_timestamp}.{raw_http_body_bytes} using the secret exchanged out-of-band when the seller registered with the governance agent. Recipients MUST verify against the X-ADCP-Signature and X-ADCP-Timestamp headers using timing-safe comparison and MUST reject requests where |now - timestamp| > 300 seconds. The body copy of this field is a convenience only — the headers are authoritative. See docs/building/implementation/security#webhook-security.' + ), + ] + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/collection/collection_list_filters.py b/src/adcp/types/domains/collection/collection_list_filters.py index e4281f640..a56006edd 100644 --- a/src/adcp/types/domains/collection/collection_list_filters.py +++ b/src/adcp/types/domains/collection/collection_list_filters.py @@ -1,21 +1,81 @@ -"""Types declared by the AdCP ``collection/collection_list_filters`` schema. +# generated by datamodel-codegen: +# filename: collection/collection_list_filters.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.collection.collection_list_filters import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import content_rating +from ..enums import collection_kind, distribution_identifier_type +from ..enums import genre_taxonomy as genre_taxonomy_1 +from ..enums import production_quality as production_quality_1 + + +class ExcludeDistributionId(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Annotated[ + distribution_identifier_type.DistributionIdentifierType, + Field(description='Type of distribution identifier'), + ] + value: Annotated[str, Field(description='The identifier value')] -from adcp.types.generated_poc.collection.collection_list_filters import ( - CollectionListFilters, - ExcludeDistributionId, -) -# Explicit exports -__all__ = ["CollectionListFilters", "ExcludeDistributionId"] +class CollectionListFilters(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + content_ratings_exclude: Annotated[ + list[content_rating.ContentRating] | None, + Field( + description="Exclude collections with any of these content ratings (OR logic). This is a metadata filter on the collection's declared content_rating field — it does not evaluate episode content.", + min_length=1, + ), + ] = None + content_ratings_include: Annotated[ + list[content_rating.ContentRating] | None, + Field( + description='Include only collections with any of these content ratings (OR logic). Collections without a declared content_rating are excluded.', + min_length=1, + ), + ] = None + genres_exclude: Annotated[ + list[str] | None, + Field( + description='Exclude collections tagged with any of these genres (OR logic). Values are interpreted against genre_taxonomy when present.', + min_length=1, + ), + ] = None + genres_include: Annotated[ + list[str] | None, + Field( + description='Include only collections with any of these genres (OR logic). Collections without genre metadata are excluded. Values are interpreted against genre_taxonomy when present.', + min_length=1, + ), + ] = None + genre_taxonomy: Annotated[ + genre_taxonomy_1.GenreTaxonomy | None, + Field( + description='Taxonomy for genre filter values. When present, genres_include and genres_exclude values are interpreted as taxonomy IDs.' + ), + ] = None + kinds: Annotated[ + list[collection_kind.CollectionKind] | None, + Field(description='Filter to these collection kinds', min_length=1), + ] = None + exclude_distribution_ids: Annotated[ + list[ExcludeDistributionId] | None, + Field( + description='Always exclude collections with these distribution identifiers', + min_length=1, + ), + ] = None + production_quality: Annotated[ + list[production_quality_1.ProductionQuality] | None, + Field(description='Filter by production quality tier', min_length=1), + ] = None diff --git a/src/adcp/types/domains/collection/create_collection_list_request.py b/src/adcp/types/domains/collection/create_collection_list_request.py index 63213c70e..c0807e20d 100644 --- a/src/adcp/types/domains/collection/create_collection_list_request.py +++ b/src/adcp/types/domains/collection/create_collection_list_request.py @@ -1,20 +1,61 @@ -"""Types declared by the AdCP ``collection/create_collection_list_request`` schema. +# generated by datamodel-codegen: +# filename: collection/create_collection_list_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.collection.create_collection_list_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field + +from ..core import account_ref, brand_ref +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope +from . import base_collection_source, collection_list_filters -from adcp.types.generated_poc.collection.create_collection_list_request import ( - CreateCollectionListRequest, -) -# Explicit exports -__all__ = ["CreateCollectionListRequest"] +class CreateCollectionListRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + account: Annotated[ + account_ref.AccountReference | None, + Field( + description='Account that will own the list. Pass a natural key (brand, operator, optional sandbox) or a seller-assigned account_id from list_accounts. When omitted, this task applies its task-local single-account shortcut: if exactly one account is accessible to the authenticated caller, the seller may assign the list to that account; otherwise it MUST return an account-required or ambiguous-account error. Omission MUST NOT mean an undocumented credential-local default account.' + ), + ] = None + name: Annotated[str, Field(description='Human-readable name for the list')] + description: Annotated[str | None, Field(description="Description of the list's purpose")] = ( + None + ) + base_collections: Annotated[ + list[base_collection_source.BaseCollectionSource] | None, + Field( + description="Array of collection sources to evaluate. Each entry is a discriminated union: distribution_ids (platform-independent identifiers), publisher_collections (publisher_domain + collection_ids), or publisher_genres (publisher_domain + genres). If omitted, queries the agent's entire collection database.", + min_length=1, + ), + ] = None + filters: Annotated[ + collection_list_filters.CollectionListFilters | None, + Field(description='Dynamic filters to apply when resolving the list'), + ] = None + brand: Annotated[ + brand_ref.BrandReference | None, + Field( + description='Brand reference. When provided, the agent automatically applies appropriate rules based on brand characteristics (industry, target_audience, etc.). Resolved at execution time.' + ), + ] = None + idempotency_key: Annotated[ + str, + Field( + description='Client-generated unique key for this request. Prevents duplicate collection list creation on retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/collection/create_collection_list_response.py b/src/adcp/types/domains/collection/create_collection_list_response.py index dfd1f6a25..2c6cd87aa 100644 --- a/src/adcp/types/domains/collection/create_collection_list_response.py +++ b/src/adcp/types/domains/collection/create_collection_list_response.py @@ -1,20 +1,40 @@ -"""Types declared by the AdCP ``collection/create_collection_list_response`` schema. +# generated by datamodel-codegen: +# filename: collection/create_collection_list_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.collection.create_collection_list_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field, StrictBool + +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from . import collection_list -from adcp.types.generated_poc.collection.create_collection_list_response import ( - CreateCollectionListResponse, -) -# Explicit exports -__all__ = ["CreateCollectionListResponse"] +class CreateCollectionListResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + list: Annotated[ + collection_list.CollectionList, Field(description='The created collection list') + ] + auth_token: Annotated[ + str, + Field( + description='Token that authorizes sellers to fetch this list via get_collection_list. Only returned at creation time — buyers MUST store it in a secret manager. Scoped to this one list_id; MUST NOT be reused across lists. Governance agents MUST issue a distinct token per seller so per-relationship revocation is possible. Tokens MUST NOT be logged, appear in cache keys, or echo in error responses. delete_collection_list MUST revoke the token immediately; compromise-driven revocation MUST also signal cache invalidation to sellers (reduced cache_valid_until or a list-changed webhook). See Security considerations in docs/governance/collection/tasks/collection_lists.' + ), + ] + replayed: Annotated[ + StrictBool | None, + Field( + description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. `replayed` appears only when the request actually resolved through the idempotency cache. Pure reads may ignore an optional `idempotency_key`; when a seller voluntarily caches keyed reads, those responses use the same replay indicator and full cache contract." + ), + ] = False + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/collection/delete_collection_list_request.py b/src/adcp/types/domains/collection/delete_collection_list_request.py index b008e9797..bba2ff419 100644 --- a/src/adcp/types/domains/collection/delete_collection_list_request.py +++ b/src/adcp/types/domains/collection/delete_collection_list_request.py @@ -1,20 +1,40 @@ -"""Types declared by the AdCP ``collection/delete_collection_list_request`` schema. +# generated by datamodel-codegen: +# filename: collection/delete_collection_list_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.collection.delete_collection_list_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field + +from ..core import account_ref +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope -from adcp.types.generated_poc.collection.delete_collection_list_request import ( - DeleteCollectionListRequest, -) -# Explicit exports -__all__ = ["DeleteCollectionListRequest"] +class DeleteCollectionListRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + list_id: Annotated[str, Field(description='ID of the collection list to delete')] + account: Annotated[ + account_ref.AccountReference | None, + Field( + description='Account that owns the list. Required when the authenticated agent has access to multiple accounts; optional otherwise.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + idempotency_key: Annotated[ + str, + Field( + description='Client-generated unique key for at-most-once execution. If a request with the same key has already been processed, the server returns the original response without re-processing. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] diff --git a/src/adcp/types/domains/collection/delete_collection_list_response.py b/src/adcp/types/domains/collection/delete_collection_list_response.py index d1cabf57c..ebb56a177 100644 --- a/src/adcp/types/domains/collection/delete_collection_list_response.py +++ b/src/adcp/types/domains/collection/delete_collection_list_response.py @@ -1,20 +1,32 @@ -"""Types declared by the AdCP ``collection/delete_collection_list_response`` schema. +# generated by datamodel-codegen: +# filename: collection/delete_collection_list_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.collection.delete_collection_list_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field, StrictBool + +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope -from adcp.types.generated_poc.collection.delete_collection_list_response import ( - DeleteCollectionListResponse, -) -# Explicit exports -__all__ = ["DeleteCollectionListResponse"] +class DeleteCollectionListResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + deleted: Annotated[StrictBool, Field(description='Whether the list was successfully deleted')] + list_id: Annotated[str, Field(description='ID of the deleted list')] + replayed: Annotated[ + StrictBool | None, + Field( + description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. `replayed` appears only when the request actually resolved through the idempotency cache. Pure reads may ignore an optional `idempotency_key`; when a seller voluntarily caches keyed reads, those responses use the same replay indicator and full cache contract." + ), + ] = False + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/collection/get_collection_list_request.py b/src/adcp/types/domains/collection/get_collection_list_request.py index d345a80ca..bebff05ef 100644 --- a/src/adcp/types/domains/collection/get_collection_list_request.py +++ b/src/adcp/types/domains/collection/get_collection_list_request.py @@ -1,21 +1,58 @@ -"""Types declared by the AdCP ``collection/get_collection_list_request`` schema. +# generated by datamodel-codegen: +# filename: collection/get_collection_list_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.collection.get_collection_list_request import +from adcp.types.base import AdcpRequest, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictBool + +from ..core import account_ref +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope + + +class Pagination(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + max_results: Annotated[ + SchemaInt | None, + Field(description='Maximum number of collections to return per page', ge=1, le=10000), + ] = 1000 + cursor: Annotated[ + str | None, + Field(description='Opaque cursor from a previous response to fetch the next page'), + ] = None -from adcp.types.generated_poc.collection.get_collection_list_request import ( - GetCollectionListRequest, - Pagination, -) -# Explicit exports -__all__ = ["GetCollectionListRequest", "Pagination"] +class GetCollectionListRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + list_id: Annotated[str, Field(description='ID of the collection list to retrieve')] + account: Annotated[ + account_ref.AccountReference | None, + Field( + description='Account that owns the list. Required when the authenticated agent has access to multiple accounts and the list_id is not globally unique within that scope; optional otherwise.' + ), + ] = None + resolve: Annotated[ + StrictBool | None, + Field( + description='Whether to apply filters and return resolved collections (default: true)' + ), + ] = True + pagination: Annotated[ + Pagination | None, + Field( + description='Pagination parameters. Uses higher limits than standard pagination because collection lists can contain thousands of entries.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/collection/get_collection_list_response.py b/src/adcp/types/domains/collection/get_collection_list_response.py index 2f14525e1..8dc7c23c4 100644 --- a/src/adcp/types/domains/collection/get_collection_list_response.py +++ b/src/adcp/types/domains/collection/get_collection_list_response.py @@ -1,23 +1,109 @@ -"""Types declared by the AdCP ``collection/get_collection_list_response`` schema. +# generated by datamodel-codegen: +# filename: collection/get_collection_list_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.collection.get_collection_list_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field + +from ..core import content_rating as content_rating_1 +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core import pagination_response +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import collection_kind, distribution_identifier_type +from ..enums import genre_taxonomy as genre_taxonomy_1 +from . import collection_list + + +class DistributionId(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Annotated[ + distribution_identifier_type.DistributionIdentifierType, + Field(description='Type of distribution identifier'), + ] + value: Annotated[str, Field(description='The identifier value')] + + +class CoverageGap(DistributionId): + pass + + +class Collection(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + collection_rid: Annotated[ + str | None, + Field( + description='Registry-assigned stable identifier for this collection. Present when the collection has been registered in the collection registry.' + ), + ] = None + publisher_domain: Annotated[ + str | None, + Field( + description='Domain of the adagents.json that declares this collection — the canonical namespace for collection_id. SHOULD be returned whenever known: together with collection_id it is the domain-qualified identity that makes a resolved row matchable when no platform-independent distribution identifier exists (e.g. a channel known only by a platform_channel_id).' + ), + ] = None + collection_id: Annotated[ + str | None, + Field( + description="Publisher-assigned collection ID within publisher_domain's adagents.json. SHOULD be returned whenever known; identity is the tuple (publisher_domain, collection_id)." + ), + ] = None + name: Annotated[str, Field(description='Human-readable collection name')] + distribution_ids: Annotated[ + list[DistributionId] | None, + Field(description='Platform-independent identifiers for cross-publisher matching'), + ] = None + content_rating: Annotated[ + content_rating_1.ContentRating | None, + Field(description='Baseline content rating for this collection'), + ] = None + genre: Annotated[list[str] | None, Field(description='Genre tags for this collection')] = None + genre_taxonomy: Annotated[ + genre_taxonomy_1.GenreTaxonomy | None, Field(description='Taxonomy system for genre values') + ] = None + kind: collection_kind.CollectionKind | None = None -from adcp.types.generated_poc.collection.get_collection_list_response import ( - Collection, - CoverageGap, - DistributionId, - GetCollectionListResponse, -) -# Explicit exports -__all__ = ["Collection", "CoverageGap", "DistributionId", "GetCollectionListResponse"] +class GetCollectionListResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + list: Annotated[ + collection_list.CollectionList, + Field(description='The collection list metadata (always returned)'), + ] + collections: Annotated[ + list[Collection] | None, + Field( + description='Resolved collections that passed filters (if resolve=true). Each entry contains identification and key metadata for seller matching.' + ), + ] = None + pagination: pagination_response.PaginationResponse | None = None + resolved_at: Annotated[ + AwareDatetime | None, Field(description='When the list was resolved') + ] = None + cache_valid_until: Annotated[ + AwareDatetime | None, + Field( + description='Cache expiration timestamp. Re-fetch the list after this time to get updated collections.' + ), + ] = None + coverage_gaps: Annotated[ + dict[str, list[CoverageGap]] | None, + Field( + description="Collections included in the list despite missing metadata for a filtered dimension. Maps dimension name (e.g., 'genre', 'content_rating') to arrays of distribution identifiers for collections not covered. Only present when filters are applied and some collections lack the filtered metadata." + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/collection/list_collection_lists_request.py b/src/adcp/types/domains/collection/list_collection_lists_request.py index 271a481ed..24f14a365 100644 --- a/src/adcp/types/domains/collection/list_collection_lists_request.py +++ b/src/adcp/types/domains/collection/list_collection_lists_request.py @@ -1,20 +1,35 @@ -"""Types declared by the AdCP ``collection/list_collection_lists_request`` schema. +# generated by datamodel-codegen: +# filename: collection/list_collection_lists_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.collection.list_collection_lists_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field + +from ..core import account_ref +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core import pagination_request +from ..core.version_envelope import AdcpVersionEnvelope -from adcp.types.generated_poc.collection.list_collection_lists_request import ( - ListCollectionListsRequest, -) -# Explicit exports -__all__ = ["ListCollectionListsRequest"] +class ListCollectionListsRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + account: Annotated[ + account_ref.AccountReference | None, + Field( + description='Filter to lists owned by this account. When omitted, returns lists across all accounts accessible to the authenticated agent.' + ), + ] = None + name_contains: Annotated[ + str | None, Field(description='Filter to lists whose name contains this string') + ] = None + pagination: pagination_request.PaginationRequest | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/collection/list_collection_lists_response.py b/src/adcp/types/domains/collection/list_collection_lists_response.py index 3b5cb4c47..908fa19bc 100644 --- a/src/adcp/types/domains/collection/list_collection_lists_response.py +++ b/src/adcp/types/domains/collection/list_collection_lists_response.py @@ -1,20 +1,31 @@ -"""Types declared by the AdCP ``collection/list_collection_lists_response`` schema. +# generated by datamodel-codegen: +# filename: collection/list_collection_lists_response.json +# timestamp: 2026-10-04T02:05:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.collection.list_collection_lists_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field + +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core import pagination_response +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from . import collection_list -from adcp.types.generated_poc.collection.list_collection_lists_response import ( - ListCollectionListsResponse, -) -# Explicit exports -__all__ = ["ListCollectionListsResponse"] +class ListCollectionListsResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + lists: Annotated[ + list[collection_list.CollectionList], + Field(description='Array of collection lists (metadata only, not resolved collections)'), + ] + pagination: pagination_response.PaginationResponse | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/collection/update_collection_list_request.py b/src/adcp/types/domains/collection/update_collection_list_request.py index 9f78f5f85..f341fde00 100644 --- a/src/adcp/types/domains/collection/update_collection_list_request.py +++ b/src/adcp/types/domains/collection/update_collection_list_request.py @@ -1,20 +1,65 @@ -"""Types declared by the AdCP ``collection/update_collection_list_request`` schema. +# generated by datamodel-codegen: +# filename: collection/update_collection_list_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.collection.update_collection_list_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import AnyUrl, ConfigDict, Field + +from ..core import account_ref, brand_ref +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope +from . import base_collection_source, collection_list_filters -from adcp.types.generated_poc.collection.update_collection_list_request import ( - UpdateCollectionListRequest, -) -# Explicit exports -__all__ = ["UpdateCollectionListRequest"] +class UpdateCollectionListRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + list_id: Annotated[str, Field(description='ID of the collection list to update')] + account: Annotated[ + account_ref.AccountReference | None, + Field( + description='Account that owns the list. Required when the authenticated agent has access to multiple accounts; optional otherwise.' + ), + ] = None + name: Annotated[str | None, Field(description='New name for the list')] = None + description: Annotated[str | None, Field(description='New description')] = None + base_collections: Annotated[ + list[base_collection_source.BaseCollectionSource] | None, + Field( + description='Complete replacement for the base collections list (not a patch). Each entry is a discriminated union: distribution_ids (platform-independent identifiers), publisher_collections (publisher_domain + collection_ids), or publisher_genres (publisher_domain + genres).' + ), + ] = None + filters: Annotated[ + collection_list_filters.CollectionListFilters | None, + Field(description='Complete replacement for the filters (not a patch)'), + ] = None + brand: Annotated[ + brand_ref.BrandReference | None, + Field( + description='Update brand reference. Resolved to full brand identity at execution time.' + ), + ] = None + webhook_url: Annotated[ + AnyUrl | None, + Field( + description='Update the webhook URL for list change notifications (set to empty string to remove). Governance agents MUST validate this URL against SSRF per docs/building/implementation/security#webhook-url-validation-ssrf.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + idempotency_key: Annotated[ + str, + Field( + description='Client-generated unique key for at-most-once execution. If a request with the same key has already been processed, the server returns the original response without re-processing. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] diff --git a/src/adcp/types/domains/collection/update_collection_list_response.py b/src/adcp/types/domains/collection/update_collection_list_response.py index 3df9e8ffc..5f3545445 100644 --- a/src/adcp/types/domains/collection/update_collection_list_response.py +++ b/src/adcp/types/domains/collection/update_collection_list_response.py @@ -1,20 +1,34 @@ -"""Types declared by the AdCP ``collection/update_collection_list_response`` schema. +# generated by datamodel-codegen: +# filename: collection/update_collection_list_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.collection.update_collection_list_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field, StrictBool + +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from . import collection_list -from adcp.types.generated_poc.collection.update_collection_list_response import ( - UpdateCollectionListResponse, -) -# Explicit exports -__all__ = ["UpdateCollectionListResponse"] +class UpdateCollectionListResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + list: Annotated[ + collection_list.CollectionList, Field(description='The updated collection list') + ] + replayed: Annotated[ + StrictBool | None, + Field( + description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. `replayed` appears only when the request actually resolved through the idempotency cache. Pure reads may ignore an optional `idempotency_key`; when a seller voluntarily caches keyed reads, those responses use the same replay indicator and full cache contract." + ), + ] = False + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/compliance/__init__.py b/src/adcp/types/domains/compliance/__init__.py index 95e278209..63727ff15 100644 --- a/src/adcp/types/domains/compliance/__init__.py +++ b/src/adcp/types/domains/compliance/__init__.py @@ -9,14 +9,14 @@ it from its own schema's module, ``adcp.types.domains.compliance.``. Nothing here is renamed. -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:33:59 UTC +Auto-generated from the generated domain tree. DO NOT EDIT MANUALLY. +Generation date: 2026-10-04 18:45:11 UTC """ # ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.compliance.comply_test_controller_request import ( +from adcp.types.domains.compliance.comply_test_controller_request import ( Account, AdvanceTo, Arm, @@ -32,7 +32,7 @@ ReportedSpend, TargetHealth, ) -from adcp.types.generated_poc.compliance.comply_test_controller_response import ( +from adcp.types.domains.compliance.comply_test_controller_response import ( AttestationMode, ComplyResponseArm, ComplyTestControllerResponse, @@ -53,10 +53,10 @@ RecordedCalls1, RecordedCalls2, ) -from adcp.types.generated_poc.compliance.get_creative_features_completion import ( +from adcp.types.domains.compliance.get_creative_features_completion import ( GetCreativeFeaturesComplianceCompletion, ) -from adcp.types.generated_poc.compliance.task_completion_data import ComplianceTaskCompletionData +from adcp.types.domains.compliance.task_completion_data import ComplianceTaskCompletionData # Explicit exports __all__ = [ diff --git a/src/adcp/types/domains/compliance/comply_test_controller_request.py b/src/adcp/types/domains/compliance/comply_test_controller_request.py index 9ce0fa973..e5dc81d9f 100644 --- a/src/adcp/types/domains/compliance/comply_test_controller_request.py +++ b/src/adcp/types/domains/compliance/comply_test_controller_request.py @@ -1,50 +1,518 @@ -"""Types declared by the AdCP ``compliance/comply_test_controller_request`` schema. +# generated by datamodel-codegen: +# filename: compliance/comply_test_controller_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.compliance.comply_test_controller_request import +from adcp.types.base import AdcpRequest, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from datetime import date +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any, Literal + +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat + +from ..core import brand_ref, committed_metric +from ..core import context as context_1 +from ..core import delivery_metrics, duration +from ..core import ext as ext_1 +from ..core import vendor_metric_id, vendor_metric_value +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import creative_event_reason_code +from ..enums import reach_unit as reach_unit_1 +from . import task_completion_data + + +class Operation(StrEnum): + seed_inaccessible_item = 'seed_inaccessible_item' + query_eligibility = 'query_eligibility' + advance_time = 'advance_time' + recreate_catalog = 'recreate_catalog' + prepare = 'prepare' + expire_proposal = 'expire_proposal' + publish_zero_row = 'publish_zero_row' + publish_nonempty = 'publish_nonempty' + restate_snapshot = 'restate_snapshot' + restate_after_received = 'restate_after_received' + omit_obligation = 'omit_obligation' + advance_past_status_deadline = 'advance_past_status_deadline' + advance_past_escalation = 'advance_past_escalation' + publish_official_adjustment = 'publish_official_adjustment' + probe_scheduler_dst = 'probe_scheduler_dst' + suppress_readiness = 'suppress_readiness' + advance_within_retention = 'advance_within_retention' + revoke_access = 'revoke_access' + publish_adjustment = 'publish_adjustment' + + +class TargetHealth(StrEnum): + delayed = 'delayed' + action_required = 'action_required' + + +class AdvanceTo(StrEnum): + within_grace = 'within_grace' + past_grace = 'past_grace' + + +class PurgeKind(StrEnum): + soft = 'soft' + hard = 'hard' + + +class ReportedSpend(AdCPBaseModel): + amount: Annotated[StrictFloat, Field(ge=0.0)] + currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] + + +class Kind(StrEnum): + cumulative = 'cumulative' + period = 'period' + rolling = 'rolling' + + +class Arm(StrEnum): + submitted = 'submitted' + completed = 'completed' + input_required = 'input-required' + rejected = 'rejected' + + +class Suggestion(ScalarStr): + __slots__ = () + _constraints = {'max_length': 1000, 'min_length': 1} + + +class Account(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + sandbox: Annotated[ + Literal[True], + Field( + description='MUST be true. The seller MUST verify the targeted account is sandbox by looking up the persisted account record, not by trusting this field. A request asserting sandbox: false schema-rejects before reaching the seller — defense-in-depth on top of the per-request gate.' + ), + ] + + +class ReachWindow(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + kind: Annotated[ + Kind, + Field( + description='Window kind. `cumulative` — no period field required. `period` and `rolling` — period field REQUIRED.' + ), + ] + period: Annotated[ + duration.Duration | None, + Field( + description="Duration of the measurement window. REQUIRED when kind is `period` or `rolling`. Matches the `period` field in delivery-metrics.json's reach_window block." + ), + ] = None + + +class Metric(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + metric_id: vendor_metric_id.VendorMetricId + + +class NotYetMeasurableVendorMetric(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + vendor: brand_ref.BrandReference + metric_id: vendor_metric_id.VendorMetricId + qualifier: Annotated[ + committed_metric.Qualifier | None, + Field( + description='Qualifier for the exact committed vendor-metric row whose measurement is deferred.' + ), + ] = None + + +class NotYetMeasurableVendorMetricsByPackageItem(NotYetMeasurableVendorMetric): + pass + + +class Params(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + creative_id: Annotated[ + str | None, + Field( + description='Creative to transition (force_creative_status) or seed (seed_creative).' + ), + ] = None + account_id: Annotated[ + str | None, + Field( + description='Account to transition, simulate, or seed. Used by force_account_status, simulate_budget_spend, and seed_account.' + ), + ] = None + media_buy_id: Annotated[ + str | None, + Field( + description='Media buy to transition or purge (force_media_buy_status, force_media_buy_purge), simulate (simulate_delivery, simulate_budget_spend), or seed (seed_media_buy).' + ), + ] = None + session_id: Annotated[ + str | None, Field(description='Session to transition. Used by force_session_status.') + ] = None + product_id: Annotated[ + str | None, + Field( + description='Product to seed or prepare for deterministic compact lifecycle testing. Used by seed_product, seed_pricing_option, compact_product_lifecycle_probe prepare, and compact_direct_buy_lifecycle_probe prepare.' + ), + ] = None + proposal_id: Annotated[ + str | None, + Field( + description='Committed proposal whose hold the compact_product_lifecycle_probe expires.', + min_length=1, + ), + ] = None + pricing_option_id: Annotated[ + str | None, + Field( + description='Pricing option to seed, scoped to a product. Used by seed_pricing_option.' + ), + ] = None + plan_id: Annotated[str | None, Field(description='Plan to seed. Used by seed_plan.')] = None + rights_id: Annotated[ + str | None, Field(description='Rights grant to seed. Used by seed_rights_grant.') + ] = None + fixture: Annotated[ + dict[str, Any] | None, + Field( + description='Arbitrary fixture payload carried by seed_* scenarios. Shape matches the domain object the seed scenario creates (account, product, creative, plan, media buy, pricing option, rights grant). Seller MAY reject malformed fixtures with INVALID_PARAMS. Kept permissive so storyboard authors can declare the minimum shape each test needs without the spec locking down every field.' + ), + ] = None + operation: Annotated[ + Operation | None, + Field( + description="Scenario-specific probe operation. catalog_item_availability_probe uses seed_inaccessible_item, query_eligibility, advance_time, and recreate_catalog. compact_product_lifecycle_probe uses prepare to make one seeded product's compact proposal, acceptance, operational-control, and MediaBuy readback path deterministic and expire_proposal to advance strictly beyond a committed proposal's stored expires_at and process the hold lapse. compact_direct_buy_lifecycle_probe uses prepare to make one seeded product's list, direct-purchase, operational-control, and readback path deterministic. reporting_core_lifecycle_probe uses prepare, advance_time, publish_zero_row, publish_nonempty, restate_snapshot, restate_after_received, omit_obligation, advance_past_status_deadline, and advance_past_escalation to exercise obligation availability, health deadlines, explicit reporting, provisional restatement, stale-received grace, buyer-side missing-obligation detection, counted consumer-status silence, and consumer-mismatch escalation. advance_past_status_deadline moves the clock strictly past expected_at plus the advertised automated_recovery_window_seconds without recording any consumer status, so obligation_counts.consumer_status_pending is gradable; advance_past_escalation moves it strictly past the open CONSUMER_STATUS_MISMATCH issue opened_at plus the advertised consumer_mismatch_escalation_seconds and returns both that boundary and any still-future stale_received_grace_deadline, so the escalation precedence rule is gradable. restate_snapshot publishes a new snapshot revision that immediately supersedes the current snapshot for the same logical slice and is invalid when the current revision is official. restate_after_received does the same restatement but only against the revision named in received_reporting_revision_id, so the stale-received grace projection can be graded live; it returns the grace deadline and advance_to positions the virtual clock inside or past it. Reliable Reporting tier probes use prepare plus publish_official_adjustment, probe_scheduler_dst, suppress_readiness, advance_within_retention, revoke_access, or publish_adjustment to seed deterministic Core-integrity, Managed Delivery, and Reconciled Billing lifecycle evidence." + ), + ] = None + target_health: Annotated[ + TargetHealth | None, + Field( + description='Clock-derived health boundary selected by reporting_core_lifecycle_probe advance_time. The controller chooses the corresponding deterministic target time; callers do not supply timestamps.' + ), + ] = None + received_reporting_revision_id: Annotated[ + str | None, + Field( + description="Revision the caller currently reports as received, required by reporting_core_lifecycle_probe restate_after_received. The seller MUST reject the operation unless this names the caller's current required revision for the fixture obligation, and a seller advertising consumer_status_task MUST additionally require a current received consumer status from this caller naming it. This binds the restatement to a real prior read instead of restating into a vacuum.", + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] = None + advance_to: Annotated[ + AdvanceTo | None, + Field( + description='Where reporting_core_lifecycle_probe restate_after_received leaves the virtual clock relative to the returned stale_received_grace_deadline. within_grace grades the delayed projection; past_grace grades the escalation to action_required. Repeating the operation is convergent: it reuses the committed restatement and only moves the clock.' + ), + ] = AdvanceTo.within_grace + catalog_id: Annotated[ + str | None, + Field( + description='Catalog operated on by catalog_item_availability_probe.', + max_length=255, + min_length=1, + ), + ] = None + catalog_generation: Annotated[ + str | None, + Field( + description='Catalog incarnation operated on by catalog_item_availability_probe.', + max_length=255, + min_length=1, + ), + ] = None + item_id: Annotated[ + str | None, + Field( + description='Catalog item operated on by catalog_item_availability_probe.', + max_length=255, + min_length=1, + ), + ] = None + target_time: Annotated[ + AwareDatetime | None, + Field( + description='Deterministic sandbox clock target used by catalog_item_availability_probe advance_time.' + ), + ] = None + status: Annotated[ + str | None, + Field( + description="Target status for the resource. Type depends on scenario: creative-status for force_creative_status, account-status for force_account_status, media-buy-status for force_media_buy_status. For force_session_status, must be 'complete' or 'terminated'." + ), + ] = None + rejection_reason: Annotated[ + str | None, + Field( + description='Reason for rejection. Used by force_creative_status and force_media_buy_status when status = rejected.' + ), + ] = None + purge_kind: Annotated[ + PurgeKind | None, + Field( + description='Purge mode for force_creative_purge. soft retains a list_creatives tombstone; hard removes the creative entirely.' + ), + ] = None + reason_code: Annotated[ + creative_event_reason_code.CreativeEventReasonCode | None, + Field( + description='Creative lifecycle reason code for force_creative_status and force_creative_purge.' + ), + ] = None + reason_detail: Annotated[ + str | None, + Field( + description='Human-readable detail for force_creative_status and force_creative_purge.' + ), + ] = None + termination_reason: Annotated[ + str | None, + Field( + description='Reason for termination (e.g., session_timeout, host_terminated, policy_violation). Used by force_session_status when status = terminated.' + ), + ] = None + impressions: Annotated[ + SchemaInt | None, + Field(description='Impressions to simulate. Used by simulate_delivery.', ge=0), + ] = None + clicks: Annotated[ + SchemaInt | None, Field(description='Clicks to simulate. Used by simulate_delivery.', ge=0) + ] = None + plays: Annotated[ + SchemaInt | None, + Field( + description='Raw DOOH or broadcast plays to add to delivery. Used by simulate_delivery for single-package buys; sellers MUST surface the cumulative value at totals.plays and by_package[0].plays in the next get_media_buy_delivery response. Multi-package simulations require a future package-scoped form and MUST reject this media-buy-scoped field.', + ge=0, + ), + ] = None + dooh_metrics: Annotated[ + delivery_metrics.DoohMetrics | None, + Field( + description='DOOH delivery detail to inject. Used by simulate_delivery for single-package buys; the latest injected block replaces the previous simulated DOOH detail and MUST surface at totals.dooh_metrics and by_package[0].dooh_metrics. Multi-package simulations require a future package-scoped form and MUST reject this media-buy-scoped field.' + ), + ] = None + conversions: Annotated[ + SchemaInt | None, + Field(description='Conversions to simulate. Used by simulate_delivery.', ge=0), + ] = None + delivery_date: Annotated[ + date | None, + Field( + description="Calendar date, in the product's reporting_capabilities.timezone, attributable to this simulated delivery batch. Used by simulate_delivery to seed deterministic date-range tests. When get_media_buy_delivery supplies start_date or end_date, dated batches are included when delivery_date is greater than or equal to start_date and strictly less than end_date. Omit to preserve cumulative, unfiltered simulation behavior." + ), + ] = None + conversion_value: Annotated[ + StrictFloat | None, + Field( + description='Total attributed conversion value to simulate in the delivery reporting currency. Used by simulate_delivery.', + ge=0.0, + ), + ] = None + commissionable_value: Annotated[ + StrictFloat | None, + Field( + description='Settled attributed value eligible for revenue-share commission to simulate in the delivery reporting currency. Used by simulate_delivery.', + ge=0.0, + ), + ] = None + reported_spend: Annotated[ + ReportedSpend | None, + Field( + description='Spend as reported in delivery data. Does not affect budget. Used by simulate_delivery.' + ), + ] = None + reach: Annotated[ + StrictFloat | None, + Field( + description="Unique reach count to inject into the simulated delivery row. Used by simulate_delivery. The unit of measurement matches the reach_unit declared on the media buy's optimization goal. When supplied, sellers MUST surface this value at `totals.reach` in the next get_media_buy_delivery response.", + ge=0.0, + ), + ] = None + frequency: Annotated[ + StrictFloat | None, + Field( + description='Average frequency per reach unit to inject. Used by simulate_delivery. When supplied, sellers MUST surface this value at `totals.frequency`, including frequency-only delivery. The measurement window for this frequency value is declared in `reach_window` when also present.', + ge=0.0, + ), + ] = None + reach_unit: Annotated[ + reach_unit_1.ReachUnit | None, + Field( + description='Unit for an injected reach or frequency value. Used by simulate_delivery. When supplied with reach or frequency, sellers MUST surface it on the corresponding delivery row.' + ), + ] = None + reach_window: Annotated[ + ReachWindow | None, + Field( + description='Measurement window semantics to attach to the simulated reach and frequency values. Used by simulate_delivery. When present, sellers MUST populate `reach_window` on the delivery row so buyers can determine the window semantics (cumulative vs. period vs. rolling). Omitting this param produces a row with no `reach_window` — valid per schema but sum-unsafe. Mirrors the `reach_window` shape in delivery-metrics.json.' + ), + ] = None + viewability: Annotated[ + delivery_metrics.Viewability | None, + Field( + description='Viewability metrics to inject into simulated delivery. Uses the canonical delivery viewability shape, including viewed-seconds distributions. This media-buy-scoped form is valid only for a single-package buy, where the reference seller surfaces the same values at package and media-buy totals grain.' + ), + ] = None + vendor_metric_values: Annotated[ + list[vendor_metric_value.VendorMetricValue] | None, + Field( + description="Vendor-defined metric values to inject into the next delivery report. Used by simulate_delivery. The reference seller reconciles these rows against each package's seller-stamped committed_metrics contract." + ), + ] = None + vendor_metric_values_by_package: Annotated[ + dict[str, list[vendor_metric_value.VendorMetricValue]] | None, + Field( + description='Package-scoped vendor-defined values to inject, keyed by package_id. Used by simulate_delivery for multi-package buys. Sellers MUST use this form whenever more than one package could carry the same vendor/metric_id key; the legacy media-buy-scoped vendor_metric_values form is unambiguous only for a single-package buy.' + ), + ] = None + not_yet_measurable_vendor_metrics: Annotated[ + list[NotYetMeasurableVendorMetric] | None, + Field( + description='Committed vendor metrics that are not yet measurable for the simulated measurement window. Used by simulate_delivery to prove that a future metric is omitted from missing_metrics until its measurement window matures.' + ), + ] = None + not_yet_measurable_vendor_metrics_by_package: Annotated[ + dict[str, list[NotYetMeasurableVendorMetricsByPackageItem]] | None, + Field( + description="Package-scoped committed vendor metrics that are not yet measurable for the simulated window, keyed by package_id. Used by simulate_delivery for multi-package buys so a deferral on one package never suppresses another package's overdue gap." + ), + ] = None + spend_percentage: Annotated[ + StrictFloat | None, + Field( + description='Spend to this percentage of budget (0–100). Used by simulate_budget_spend.', + ge=0.0, + le=100.0, + ), + ] = None + arm: Annotated[ + Arm | None, + Field( + description="Response arm for the next forced operation call. Used by force_create_media_buy_arm, force_get_products_arm, force_get_signals_arm, and force_get_creative_features_arm. 'submitted' is supported for all four operations; get_creative_features also supports 'completed' with a deterministic result; create_media_buy also supports 'input-required'; get_products also supports 'rejected'. Async completion after a submitted task is covered by force_task_completion; 'working' is an out-of-band progress signal, not an initial response arm." + ), + ] = None + task_id: Annotated[ + str | None, + Field( + description="Deterministic task handle the seller MUST emit verbatim on the next forced operation response when arm is 'submitted'. The seller MUST accept this exact value on subsequent tasks/get or get_task_status calls within the same authenticated sandbox account + principal pair and MUST return REFERENCE_NOT_FOUND for the same task_id under any other account or principal. Sandbox task_ids are caller-opaque strings - the seller's production task-id format rules do not apply.", + max_length=128, + ), + ] = None + evaluation_id: Annotated[ + str | None, + Field( + description='Deterministic provider evaluation identity the seller MUST emit on the next forced get_creative_features submitted acknowledgement and preserve on its terminal completion result.', + max_length=255, + min_length=1, + ), + ] = None + message: Annotated[ + str | None, + Field( + description='Optional human-readable explanation surfaced on the next forced operation response. Used by force_create_media_buy_arm, force_get_products_arm, force_get_signals_arm, and force_get_creative_features_arm for the submitted arm. Plain text only.', + max_length=2000, + ), + ] = None + reason: Annotated[ + str | None, + Field( + description="Deterministic buyer-facing reason the seller MUST emit on the next get_products response when force_get_products_arm uses arm 'rejected'. Plain text only; MUST NOT expose confidential internal rules, inventory identifiers, credentials, or stack traces.", + max_length=2000, + min_length=1, + ), + ] = None + suggestions: Annotated[ + list[Suggestion] | None, + Field( + description="Optional deterministic alternatives the seller MUST emit on the next get_products rejection. Used only by force_get_products_arm with arm 'rejected'.", + max_length=20, + min_length=1, + ), + ] = None + format_id: Annotated[ + str | None, + Field( + deprecated=True, + description='**DEPRECATED 3.x compatibility fixture.** Legacy named-format ID to seed for seed_creative_format scenarios. The seller MUST expose it through the deprecated list_creative_formats projection for the duration of the compliance session. Canonical 3.2 scenarios seed creative.supported_formats capabilities instead.', + ), + ] = None + vendor: Annotated[ + brand_ref.BrandReference | None, + Field( + description="Measurement vendor whose catalog is being seeded. Used by seed_measurement_catalog. The seller MUST treat `metrics[]` as this vendor's `get_adcp_capabilities.measurement.metrics[]` snapshot for the duration of the compliance session." + ), + ] = None + metrics: Annotated[ + list[Metric] | None, + Field( + description='Measurement catalog entries to seed for the vendor. Used by seed_measurement_catalog. Shape mirrors `get_adcp_capabilities.measurement.metrics[]`; each metric_id is unique within the seeded vendor catalog.', + min_length=1, + ), + ] = None + tool: Annotated[ + str | None, + Field( + description="Tool name whose upstream dependency to affect. Used by force_upstream_unavailable. The seller marks the named upstream as unreachable for subsequent calls to this tool within the compliance session. Sellers MAY accept a wildcard ('*') to affect all tools." + ), + ] = None + upstream_name: Annotated[ + str | None, + Field( + description="Human-readable identifier for the upstream dependency to force unavailable (e.g., 'inventory-service', 'creative-agent'). Used by force_upstream_unavailable. When omitted, the seller marks its default upstream for the specified tool as unavailable. Sellers MUST include the same name in STALE_RESPONSE error.details.upstream.name on the affected response." + ), + ] = None + cache_age_seconds: Annotated[ + SchemaInt | None, + Field( + description='Deterministic age of the stale cache entry used by force_upstream_unavailable. When supplied, the seller MUST emit this exact value in the matching STALE_RESPONSE error.details.cache_age_seconds on the next affected response. This lets the conformance runner bind the advisory code and its required detail fields to the same errors[] entry without assuming array order.', + ge=0, + ), + ] = None + result: Annotated[ + task_completion_data.ComplianceTaskCompletionData | None, + Field( + description="Deterministic result payload. force_get_creative_features_arm uses it when arm is completed and returns it on the next matching operation call. force_task_completion records it against an existing task and supports the bounded get_products, get_signals, create_media_buy, and get_creative_features completion union; polling and production SDKs resolve all task results through the originating task's manifest response mapping instead of embedding a global result union. The seller MUST preserve caller-supplied fields. Sellers MUST emit INVALID_PARAMS when the payload does not match the required response branch and MAY reject payloads exceeding 256 KB." + ), + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.compliance.comply_test_controller_request import ( - Account, - AdvanceTo, - Arm, - ComplyTestControllerRequest, - Kind, - Metric, - NotYetMeasurableVendorMetric, - NotYetMeasurableVendorMetricsByPackageItem, - Operation, - Params, - PurgeKind, - ReachWindow, - ReportedSpend, - Suggestion, - TargetHealth, -) - -# Explicit exports -__all__ = [ - "Account", - "AdvanceTo", - "Arm", - "ComplyTestControllerRequest", - "Kind", - "Metric", - "NotYetMeasurableVendorMetric", - "NotYetMeasurableVendorMetricsByPackageItem", - "Operation", - "Params", - "PurgeKind", - "ReachWindow", - "ReportedSpend", - "Suggestion", - "TargetHealth", -] +class ComplyTestControllerRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + scenario: Annotated[ + str, + Field( + description="Test scenario to execute. 'list_scenarios' discovers supported scenarios. 'force_*' and 'simulate_*' trigger state transitions. 'reporting_core_lifecycle_probe' installs a caller/account-scoped Core fixture whose first elapsed obligation is visible before any report, advances its virtual clock into delayed or action_required, and can publish deterministic zero-row or non-empty revisions, restate a provisional revision, restate one the caller already received, or cross the consumer-status deadline and consumer-mismatch escalation boundaries, without waiting for wall-clock boundaries. Other scenarios provide deterministic sandbox probes for their documented lifecycle checks. Runners and sellers MUST accept unknown scenario strings - new scenarios may be added in additive releases." + ), + ] + params: Annotated[ + Params | None, + Field( + description='Scenario-specific parameters. Required for all scenarios except list_scenarios.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + account: Annotated[ + Account | None, + Field( + description="Sandbox account assertion. The runner MUST set sandbox: true on every comply_test_controller request. The seller MUST refuse the request (returning a structured error) if the targeted account is not a sandbox account in the seller's persisted records. This field is a caller-side declaration of intent — it does not grant sandbox status; sellers verify against their own account state. The (Sandbox) verification tier is defined by this gate: real production endpoints accept sandbox-flagged traffic and process it without real-world side effects, no separate test-mode endpoint required. See spec issue #3755 and the (Sandbox) framing in #4379." + ), + ] = None diff --git a/src/adcp/types/domains/compliance/comply_test_controller_response.py b/src/adcp/types/domains/compliance/comply_test_controller_response.py index cb40f7270..b4a45b863 100644 --- a/src/adcp/types/domains/compliance/comply_test_controller_response.py +++ b/src/adcp/types/domains/compliance/comply_test_controller_response.py @@ -1,60 +1,471 @@ -"""Types declared by the AdCP ``compliance/comply_test_controller_response`` schema. +# generated by datamodel-codegen: +# filename: compliance/comply_test_controller_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.compliance.comply_test_controller_response import +from adcp.types.base import AdcpResponse, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.response_dispatch import ResponseArmDispatchMixin -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any, Literal + +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictBool + +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from ..creative import audit_observation + + +class ComplyResponseArm(StrEnum): + submitted = 'submitted' + completed = 'completed' + input_required = 'input-required' + rejected = 'rejected' + + +class Suggestion(ScalarStr): + __slots__ = () + _constraints = {'max_length': 1000, 'min_length': 1} + + +class Forced(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + arm: Annotated[ + ComplyResponseArm, Field(description='ComplyResponseArm the seller will emit on the next forced operation response.') + ] + task_id: Annotated[ + str | None, + Field( + description="Echo of the registered task_id. Present only when arm is 'submitted' (the arm that emits a task envelope).", + max_length=128, + ), + ] = None + evaluation_id: Annotated[ + str | None, + Field( + description='Echo of the provider evaluation identity registered by force_get_creative_features_arm.', + max_length=255, + min_length=1, + ), + ] = None + reason: Annotated[ + str | None, + Field( + description="Echo of the deterministic buyer-facing rejection reason. Required when arm is 'rejected'.", + max_length=2000, + min_length=1, + ), + ] = None + suggestions: Annotated[ + list[Suggestion] | None, + Field( + description='Echo of the optional deterministic alternatives registered for a rejected get_products response.', + max_length=20, + min_length=1, + ), + ] = None + + +class Method(StrEnum): + GET = 'GET' + POST = 'POST' + PUT = 'PUT' + PATCH = 'PATCH' + DELETE = 'DELETE' + HEAD = 'HEAD' + OPTIONS = 'OPTIONS' + + +class AttestationMode(StrEnum): + raw = 'raw' + digest = 'digest' + + +class Purpose(StrEnum): + platform_primary = 'platform_primary' + measurement = 'measurement' + attribution = 'attribution' + creative_serving = 'creative_serving' + identity = 'identity' + other = 'other' + + +class IdentifierMatchProof(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + identifier_value_sha256: Annotated[ + str, + Field( + description='Echo of one digest from `params.identifier_value_digests` so the runner can pair this proof with the identifier it queried.', + pattern='^[a-f0-9]{64}$', + ), + ] + found: Annotated[ + StrictBool, + Field( + description='True if any string token in the recorded payload hashes to the queried digest. False otherwise.' + ), + ] + + +class RecordedCalls1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + method: Annotated[Method, Field(description='HTTP method of the outbound call.')] + endpoint: Annotated[ + str, + Field( + description="Composed ` ` string used for `endpoint_pattern` matching, e.g. 'POST https://api.tiktok.com/v2/audience/upload'. Convenience field — the runner can also reconstruct from `method` + URL components." + ), + ] + url: Annotated[ + AnyUrl, + Field( + description='Full URL of the outbound call (scheme + host + path + query). Treated as untrusted agent-controlled input by report renderers — see runner-output-contract.yaml > security.rendered_output_fencing.' + ), + ] + host: Annotated[ + str | None, + Field( + description='Host portion of the URL, useful for grouping calls by upstream platform.' + ), + ] = None + path: Annotated[ + str | None, Field(description='Path portion of the URL (without query string).') + ] = None + content_type: Annotated[ + str, + Field( + description="Media type of the outbound request body, mirroring the agent's outbound `Content-Type` header (e.g., `application/json`, `application/x-www-form-urlencoded`, `multipart/form-data`, `text/plain`). In raw mode this describes the returned `payload`; in digest mode it describes the body the digest was computed over. Storyboard `payload_must_contain` JSONPath-lite assertions are valid only when content_type is `application/json` or has a `+json` suffix AND attestation_mode is `raw` — digest mode and non-JSON content types grade `payload_must_contain` as not_applicable. Required so the runner can choose the right matcher deterministically." + ), + ] + attestation_mode: Annotated[ + Literal['raw'], + Field( + description="Per-call attestation mode echoing the request's `params.attestation_mode`. Required on every recorded_call so the `oneOf` discriminator always has an explicit value to dispatch on — no implicit defaults inside oneOf branches. Adopters MAY unilaterally downgrade a `raw` request to `digest` for a specific call when their policy requires it (e.g., the call carried regulated PII the adopter can't return raw). The runner reads this field to know which assertions to apply." + ), + ] = 'raw' + purpose: Annotated[ + Purpose | None, + Field( + description="Optional adopter-supplied semantic tag for the call's role. Values: `platform_primary` for the primary upstream platform the adapter is integrating with (e.g., a TikTok audience-upload call from a sales-social adapter); `measurement` for ancillary calls to measurement vendors (DV, IAS, Nielsen, MOAT); `attribution` for server-side conversion APIs (TTD Trans-API, Meta CAPI, AppsFlyer/Branch postbacks) that flow alongside primary platform calls in a buy-step; `creative_serving` for ad-server / CDN / tag-build calls (GAM tag generation, VAST/CDN fetches, creative trafficking); `identity` for ID-graph / hashing-service calls (LiveRamp, ID5, UID2); `other` for everything else (config fetches, internal telemetry, consent signal exchange). Lets storyboards scope `upstream_traffic` assertions via `purpose_filter` so a buyer-agent adapter that legitimately calls measurement vendors during a single buy step doesn't muddy the platform-primary assertion. Calls without a `purpose` field are treated as `purpose: other` for `purpose_filter` matching — adopters who haven't classified are matched only by storyboards filtering on `other` (or by storyboards with no `purpose_filter`). Self-reported, not adversarially trustworthy — same trust model as the rest of recorded_calls; misclassification by a façade is bounded by the runner's reporting of unclassified-call counts in `actual` when filters match zero." + ), + ] = None + payload: Annotated[ + Any, + Field( + description="Request body the agent sent. Required when `attestation_mode` is `raw` (or omitted); MUST be absent when `attestation_mode` is `digest`. Object when content_type is JSON-shaped and the controller decoded it; string otherwise. The `x-adcp-open-payload: true` annotation applies to the decoded JSON/object arm of this mixed field; non-JSON string payloads remain scalar values governed by the surrounding schema. Adopters MUST apply the recursive secret-key redaction described in this branch's top-level description before emission — secrets at any depth (Authorization values inlined into JSON bodies, embedded JWTs, presigned-URL tokens, OAuth refresh tokens) MUST be replaced with the literal string `[redacted]`. Storyboards that assert `payload_must_contain` or `identifier_paths` are matching against THIS field — secrets MUST be redacted but storyboard-supplied identifiers (hashed PII, request correlation values) MUST NOT be redacted. Adopters SHOULD cap individual payload size at 64 KiB; payloads exceeding that SHOULD be truncated with a trailing `[…truncated]` marker — large payloads bloat compliance reports and the LLM-rendered context windows that consume them.", + max_length=65536, + ), + ] + payload_digest_sha256: Annotated[ + str | None, + Field( + description='SHA-256 digest of the canonicalized outbound request body, lowercase hex (64 chars). Required when `attestation_mode` is `digest`; MUST be absent when `attestation_mode` is `raw`. Canonicalization order is normative: (1) controllers MUST apply the recursive secret-key redaction (same pattern as the raw-mode payload field) BEFORE computing the digest; (2) for `application/json` and `*+json` content types, controllers MUST then serialize the redacted body to RFC 8785 (JCS) canonical form — sorted keys, no extraneous whitespace — and digest the resulting bytes. Storyboard-supplied identifiers (hashed PII, request correlation values) MUST NOT be redacted before digest computation; they are the load-bearing match target for `identifier_match_proofs` and digesting them away makes echo verification impossible. Both `payload_digest_sha256` AND `identifier_match_proofs` MUST be computed against the same post-redaction canonical bytes — diverging the two surfaces breaks coherence between digest replay and identifier echo. JCS edge cases: when a digest-mode `query_upstream_traffic` response cannot be produced because the parsed JSON-like value tree contains a non-finite numeric value (`NaN`, `+Infinity`, or `-Infinity`), controllers MUST NOT coerce that value to `null`, a string, or any other placeholder for digest computation; they MUST return the typed ControllerError code `JCS_NON_FINITE_NUMBER`. Runners MUST grade the affected upstream_traffic digest validation `not_applicable` because RFC 8785 forbids non-finite numbers. Payloads carrying numeric identifiers MUST serialize them as JSON strings before digest computation (adtech bid payloads regularly carry IDs outside ±2^53 where I-JSON / RFC 7493 number round-tripping diverges across implementations). For non-JSON content types the digest is computed over the post-redaction raw body bytes.', + pattern='^[a-f0-9]{64}$', + ), + ] = None + payload_length: Annotated[ + SchemaInt, + Field( + description='Byte length of the post-redaction body bytes represented by this recorded_call. Required in both `raw` and `digest` modes — symmetric across modes so runners can detect adopter-side truncation regardless of attestation choice. In `raw` mode this MUST equal the UTF-8 byte length of the emitted `payload` value after the same recursive secret-key redaction the controller applied before returning it. In `digest` mode this MUST equal the exact number of bytes fed into SHA-256 for `payload_digest_sha256`: RFC 8785 (JCS) canonical bytes for JSON-shaped content after redaction, or the post-redaction raw body bytes for non-JSON content. Digest-mode `payload_length` is therefore NOT the original outbound body length before JSON parsing, redaction, or canonicalization. Mismatch between observed payload length and reported `payload_length` is a controller-side bug worth surfacing in the report.', + ge=0, + ), + ] + identifier_match_proofs: Annotated[ + list[IdentifierMatchProof] | None, + Field( + description='Per-identifier echo proofs for digest-mode calls. Required when `attestation_mode` is `digest` AND the request supplied `params.identifier_value_digests`; MUST be absent or empty otherwise. Each entry corresponds to one digest from the request. Capped at 64 to match the request-side `params.identifier_value_digests` cap. Lets storyboards verify `identifier_paths` echo in digest mode without ever transmitting plaintext identifiers to the controller. SHA-256 is a privacy mechanism here, not a trust mechanism — controllers self-report `found` and a determined façade can return any boolean; consumers MUST NOT treat digest-mode passing as cryptographically more trustworthy than raw mode. Tokenization is normative: for `application/json` and `*+json` content types, controllers MUST scan exactly the JSON string-typed leaf values of the post-redaction canonicalized body — no substring matching, no word splitting, no case folding, no Unicode normalization. A token matches when its SHA-256 hash equals one of the requested digests byte-for-byte. For non-JSON content types (form-urlencoded, multipart, plain text), `identifier_match_proofs` MUST be empty and runner-side `identifier_paths` assertions targeting those calls grade `not_applicable` — token boundaries are not portably defined across non-JSON shapes.', + max_length=64, + ), + ] = None + timestamp: Annotated[ + AwareDatetime, + Field( + description="ISO 8601 timestamp the adopter recorded the outbound call. MUST reflect the adopter's wall clock at the moment the outbound request was sent (not log-flush time), and MUST be monotonically non-decreasing across recorded_calls of a single response. Used by runners to scope assertions to a specific storyboard step's window — see runner-output-contract.yaml > validation_result for the timestamp boundary semantics." + ), + ] + status_code: Annotated[ + SchemaInt | None, + Field( + description='HTTP status code returned by the upstream. Optional — adopters MAY omit when the call was instrumented before the response arrived.', + ge=100, + le=599, + ), + ] = None + + +class RecordedCalls2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + method: Annotated[Method, Field(description='HTTP method of the outbound call.')] + endpoint: Annotated[ + str, + Field( + description="Composed ` ` string used for `endpoint_pattern` matching, e.g. 'POST https://api.tiktok.com/v2/audience/upload'. Convenience field — the runner can also reconstruct from `method` + URL components." + ), + ] + url: Annotated[ + AnyUrl, + Field( + description='Full URL of the outbound call (scheme + host + path + query). Treated as untrusted agent-controlled input by report renderers — see runner-output-contract.yaml > security.rendered_output_fencing.' + ), + ] + host: Annotated[ + str | None, + Field( + description='Host portion of the URL, useful for grouping calls by upstream platform.' + ), + ] = None + path: Annotated[ + str | None, Field(description='Path portion of the URL (without query string).') + ] = None + content_type: Annotated[ + str, + Field( + description="Media type of the outbound request body, mirroring the agent's outbound `Content-Type` header (e.g., `application/json`, `application/x-www-form-urlencoded`, `multipart/form-data`, `text/plain`). In raw mode this describes the returned `payload`; in digest mode it describes the body the digest was computed over. Storyboard `payload_must_contain` JSONPath-lite assertions are valid only when content_type is `application/json` or has a `+json` suffix AND attestation_mode is `raw` — digest mode and non-JSON content types grade `payload_must_contain` as not_applicable. Required so the runner can choose the right matcher deterministically." + ), + ] + attestation_mode: Annotated[ + Literal['digest'], + Field( + description="Per-call attestation mode echoing the request's `params.attestation_mode`. Required on every recorded_call so the `oneOf` discriminator always has an explicit value to dispatch on — no implicit defaults inside oneOf branches. Adopters MAY unilaterally downgrade a `raw` request to `digest` for a specific call when their policy requires it (e.g., the call carried regulated PII the adopter can't return raw). The runner reads this field to know which assertions to apply." + ), + ] = 'digest' + purpose: Annotated[ + Purpose | None, + Field( + description="Optional adopter-supplied semantic tag for the call's role. Values: `platform_primary` for the primary upstream platform the adapter is integrating with (e.g., a TikTok audience-upload call from a sales-social adapter); `measurement` for ancillary calls to measurement vendors (DV, IAS, Nielsen, MOAT); `attribution` for server-side conversion APIs (TTD Trans-API, Meta CAPI, AppsFlyer/Branch postbacks) that flow alongside primary platform calls in a buy-step; `creative_serving` for ad-server / CDN / tag-build calls (GAM tag generation, VAST/CDN fetches, creative trafficking); `identity` for ID-graph / hashing-service calls (LiveRamp, ID5, UID2); `other` for everything else (config fetches, internal telemetry, consent signal exchange). Lets storyboards scope `upstream_traffic` assertions via `purpose_filter` so a buyer-agent adapter that legitimately calls measurement vendors during a single buy step doesn't muddy the platform-primary assertion. Calls without a `purpose` field are treated as `purpose: other` for `purpose_filter` matching — adopters who haven't classified are matched only by storyboards filtering on `other` (or by storyboards with no `purpose_filter`). Self-reported, not adversarially trustworthy — same trust model as the rest of recorded_calls; misclassification by a façade is bounded by the runner's reporting of unclassified-call counts in `actual` when filters match zero." + ), + ] = None + payload: Annotated[ + Any | None, + Field( + description="Request body the agent sent. Required when `attestation_mode` is `raw` (or omitted); MUST be absent when `attestation_mode` is `digest`. Object when content_type is JSON-shaped and the controller decoded it; string otherwise. The `x-adcp-open-payload: true` annotation applies to the decoded JSON/object arm of this mixed field; non-JSON string payloads remain scalar values governed by the surrounding schema. Adopters MUST apply the recursive secret-key redaction described in this branch's top-level description before emission — secrets at any depth (Authorization values inlined into JSON bodies, embedded JWTs, presigned-URL tokens, OAuth refresh tokens) MUST be replaced with the literal string `[redacted]`. Storyboards that assert `payload_must_contain` or `identifier_paths` are matching against THIS field — secrets MUST be redacted but storyboard-supplied identifiers (hashed PII, request correlation values) MUST NOT be redacted. Adopters SHOULD cap individual payload size at 64 KiB; payloads exceeding that SHOULD be truncated with a trailing `[…truncated]` marker — large payloads bloat compliance reports and the LLM-rendered context windows that consume them.", + max_length=65536, + ), + ] = None + payload_digest_sha256: Annotated[ + str, + Field( + description='SHA-256 digest of the canonicalized outbound request body, lowercase hex (64 chars). Required when `attestation_mode` is `digest`; MUST be absent when `attestation_mode` is `raw`. Canonicalization order is normative: (1) controllers MUST apply the recursive secret-key redaction (same pattern as the raw-mode payload field) BEFORE computing the digest; (2) for `application/json` and `*+json` content types, controllers MUST then serialize the redacted body to RFC 8785 (JCS) canonical form — sorted keys, no extraneous whitespace — and digest the resulting bytes. Storyboard-supplied identifiers (hashed PII, request correlation values) MUST NOT be redacted before digest computation; they are the load-bearing match target for `identifier_match_proofs` and digesting them away makes echo verification impossible. Both `payload_digest_sha256` AND `identifier_match_proofs` MUST be computed against the same post-redaction canonical bytes — diverging the two surfaces breaks coherence between digest replay and identifier echo. JCS edge cases: when a digest-mode `query_upstream_traffic` response cannot be produced because the parsed JSON-like value tree contains a non-finite numeric value (`NaN`, `+Infinity`, or `-Infinity`), controllers MUST NOT coerce that value to `null`, a string, or any other placeholder for digest computation; they MUST return the typed ControllerError code `JCS_NON_FINITE_NUMBER`. Runners MUST grade the affected upstream_traffic digest validation `not_applicable` because RFC 8785 forbids non-finite numbers. Payloads carrying numeric identifiers MUST serialize them as JSON strings before digest computation (adtech bid payloads regularly carry IDs outside ±2^53 where I-JSON / RFC 7493 number round-tripping diverges across implementations). For non-JSON content types the digest is computed over the post-redaction raw body bytes.', + pattern='^[a-f0-9]{64}$', + ), + ] + payload_length: Annotated[ + SchemaInt, + Field( + description='Byte length of the post-redaction body bytes represented by this recorded_call. Required in both `raw` and `digest` modes — symmetric across modes so runners can detect adopter-side truncation regardless of attestation choice. In `raw` mode this MUST equal the UTF-8 byte length of the emitted `payload` value after the same recursive secret-key redaction the controller applied before returning it. In `digest` mode this MUST equal the exact number of bytes fed into SHA-256 for `payload_digest_sha256`: RFC 8785 (JCS) canonical bytes for JSON-shaped content after redaction, or the post-redaction raw body bytes for non-JSON content. Digest-mode `payload_length` is therefore NOT the original outbound body length before JSON parsing, redaction, or canonicalization. Mismatch between observed payload length and reported `payload_length` is a controller-side bug worth surfacing in the report.', + ge=0, + ), + ] + identifier_match_proofs: Annotated[ + list[IdentifierMatchProof] | None, + Field( + description='Per-identifier echo proofs for digest-mode calls. Required when `attestation_mode` is `digest` AND the request supplied `params.identifier_value_digests`; MUST be absent or empty otherwise. Each entry corresponds to one digest from the request. Capped at 64 to match the request-side `params.identifier_value_digests` cap. Lets storyboards verify `identifier_paths` echo in digest mode without ever transmitting plaintext identifiers to the controller. SHA-256 is a privacy mechanism here, not a trust mechanism — controllers self-report `found` and a determined façade can return any boolean; consumers MUST NOT treat digest-mode passing as cryptographically more trustworthy than raw mode. Tokenization is normative: for `application/json` and `*+json` content types, controllers MUST scan exactly the JSON string-typed leaf values of the post-redaction canonicalized body — no substring matching, no word splitting, no case folding, no Unicode normalization. A token matches when its SHA-256 hash equals one of the requested digests byte-for-byte. For non-JSON content types (form-urlencoded, multipart, plain text), `identifier_match_proofs` MUST be empty and runner-side `identifier_paths` assertions targeting those calls grade `not_applicable` — token boundaries are not portably defined across non-JSON shapes.', + max_length=64, + ), + ] = None + timestamp: Annotated[ + AwareDatetime, + Field( + description="ISO 8601 timestamp the adopter recorded the outbound call. MUST reflect the adopter's wall clock at the moment the outbound request was sent (not log-flush time), and MUST be monotonically non-decreasing across recorded_calls of a single response. Used by runners to scope assertions to a specific storyboard step's window — see runner-output-contract.yaml > validation_result for the timestamp boundary semantics." + ), + ] + status_code: Annotated[ + SchemaInt | None, + Field( + description='HTTP status code returned by the upstream. Optional — adopters MAY omit when the call was instrumented before the response arrived.', + ge=100, + le=599, + ), + ] = None -from adcp.types.generated_poc.compliance.comply_test_controller_response import ( - AttestationMode, - ComplyResponseArm, - ComplyTestControllerResponse, - ComplyTestControllerResponse1, - ComplyTestControllerResponse2, - ComplyTestControllerResponse3, - ComplyTestControllerResponse4, - ComplyTestControllerResponse5, - ComplyTestControllerResponse6, - ComplyTestControllerResponse7, - ComplyTestControllerResponse8, - Error, - Forced, - IdentifierMatchProof, - Method, - Purpose, - RecordedCalls, - RecordedCalls1, - RecordedCalls2, - Suggestion, -) - -# Explicit exports -__all__ = [ - "AttestationMode", - "ComplyResponseArm", - "ComplyTestControllerResponse", - "ComplyTestControllerResponse1", - "ComplyTestControllerResponse2", - "ComplyTestControllerResponse3", - "ComplyTestControllerResponse4", - "ComplyTestControllerResponse5", - "ComplyTestControllerResponse6", - "ComplyTestControllerResponse7", - "ComplyTestControllerResponse8", - "Error", - "Forced", - "IdentifierMatchProof", - "Method", - "Purpose", - "RecordedCalls", - "RecordedCalls1", - "RecordedCalls2", - "Suggestion", + +RecordedCalls = Annotated[ + RecordedCalls1 | RecordedCalls2, + Field(discriminator='attestation_mode'), ] + + +class Error(StrEnum): + INVALID_TRANSITION = 'INVALID_TRANSITION' + INVALID_STATE = 'INVALID_STATE' + NOT_FOUND = 'NOT_FOUND' + UNKNOWN_SCENARIO = 'UNKNOWN_SCENARIO' + INVALID_PARAMS = 'INVALID_PARAMS' + FORBIDDEN = 'FORBIDDEN' + JCS_NON_FINITE_NUMBER = 'JCS_NON_FINITE_NUMBER' + INTERNAL_ERROR = 'INTERNAL_ERROR' + + +class ComplyTestControllerResponse(AdcpResponse, ResponseArmDispatchMixin, AdcpVersionEnvelope, ProtocolEnvelope): + """Constructible compatibility base for generated response arms.""" + + @classmethod + def _response_arm_models(cls) -> tuple[type[ComplyTestControllerResponse], ...]: + return ( + ComplyTestControllerResponse1, + ComplyTestControllerResponse2, + ComplyTestControllerResponse3, + ComplyTestControllerResponse4, + ComplyTestControllerResponse5, + ComplyTestControllerResponse6, + ComplyTestControllerResponse7, + ComplyTestControllerResponse8, + ) + + +class ComplyTestControllerResponse1(ComplyTestControllerResponse): + model_config = ConfigDict( + extra='allow', + ) + success: Literal[True] + scenarios: Annotated[ + list[str], + Field( + description='Scenarios this seller has implemented. Runners and sellers MUST accept unknown scenario strings (open-for-extension) — new scenarios may be added in additive releases. Adopters who advertise `catalog_item_availability_probe` support deterministic cross-principal reference, eligibility-gate, expiry-clock, and catalog-generation tests for the catalog availability storyboard. Adopters who advertise `compact_product_lifecycle_probe` support deterministic synchronous list/request/finalize/decline/accept/control/readback behavior for a prepared product and strict post-deadline expiry of a committed proposal. Adopters who advertise `compact_direct_buy_lifecycle_probe` support deterministic synchronous list/buy/control/readback behavior for a prepared product. Adopters who advertise `reporting_core_lifecycle_probe` support deterministic obligation-before-report, clock-health, zero-row reporting, provisional-restatement, and post-received restatement-grace tests without wall-clock waits. `reliable_reporting_core_integrity_probe`, `reliable_reporting_managed_delivery_probe`, and `reliable_reporting_reconciled_billing_probe` seed the source-calendar/checkpoint, managed-resource, and receipt/adjustment workflows used by the Reliable Reporting tier storyboards. Adopters who advertise `force_creative_purge` opt in to deterministic creative purge coverage for account-level lifecycle webhooks. Adopters who advertise `force_media_buy_purge` opt in to deterministic deletion-independent idempotency replay coverage. Adopters who advertise `seed_measurement_catalog` opt in to deterministic measurement-catalog fixtures used by vendor_metric precondition storyboards. Adopters who advertise `query_upstream_traffic` opt in to the upstream-traffic conformance contract; storyboards that declare `check: upstream_traffic` grade not_applicable against adopters who do not advertise it. Adopters who advertise `query_provenance_audit_observations` opt in to sandbox-only audit-observation assertions for accepted creatives. Adopters who advertise `force_upstream_unavailable` opt in to stale-cache conformance testing via the `stale_response_advisory` storyboard.' + ), + ] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class ComplyTestControllerResponse2(ComplyTestControllerResponse): + model_config = ConfigDict( + extra='allow', + ) + success: Literal[True] + previous_state: Annotated[str, Field(description='State before this transition')] + current_state: Annotated[str, Field(description='State after this transition')] + message: Annotated[ + str | None, Field(description='Human-readable description of the transition') + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class ComplyTestControllerResponse3(ComplyTestControllerResponse): + model_config = ConfigDict( + extra='allow', + ) + success: Literal[True] + simulated: Annotated[ + dict[str, Any], + Field(description='Values injected or applied by this call. Shape depends on scenario.'), + ] + cumulative: Annotated[ + dict[str, Any] | None, + Field(description='Running totals across all simulation calls (simulate_delivery only)'), + ] = None + message: str | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class ComplyTestControllerResponse4(ComplyTestControllerResponse): + model_config = ConfigDict( + extra='allow', + ) + success: Literal[True] + forced: Annotated[ + Forced, + Field( + description='Echo of the registered directive. The next matching operation call from this sandbox account will return the named arm.' + ), + ] + message: Annotated[str | None, Field(description='Human-readable acknowledgement.')] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class ComplyTestControllerResponse5(ComplyTestControllerResponse): + model_config = ConfigDict( + extra='allow', + ) + success: Literal[True] + message: Annotated[str | None, Field(description='Human-readable acknowledgement.')] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class ComplyTestControllerResponse6(ComplyTestControllerResponse): + model_config = ConfigDict( + extra='allow', + ) + success: Literal[True] + creative_id: Annotated[ + str, Field(description='Creative ID whose audit observations were queried.') + ] + audit_observations: Annotated[ + list[audit_observation.CreativeAuditObservation], + Field(description='Audit observations recorded for the creative in the sandbox session.'), + ] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class ComplyTestControllerResponse7(ComplyTestControllerResponse): + model_config = ConfigDict( + extra='allow', + ) + success: Literal[True] + recorded_calls: Annotated[ + list[RecordedCalls], + Field( + description="Outbound HTTP calls caused by the requesting principal in the requested window, ordered by `timestamp` ascending. Cross-caller calls MUST NOT appear here. Each item declares its `attestation_mode`: `raw` items carry the full `payload`; `digest` items carry `payload_digest_sha256` + `payload_length` + optional `identifier_match_proofs[]` instead, for adopters who can't return raw payloads under their privacy/data-residency policy." + ), + ] + total_count: Annotated[ + SchemaInt, + Field( + description='Total calls in the requested window before any pagination — `recorded_calls.length` may be smaller when `params.limit` truncated the response.', + ge=0, + ), + ] + truncated: Annotated[ + StrictBool | None, + Field( + description='True when `total_count > recorded_calls.length`. Runners MAY raise the `limit` and re-query, but storyboards SHOULD declare assertions that fit within the default 100-call window — a truncated response is a signal that the storyboard step is causing more upstream activity than expected.' + ), + ] = None + since_timestamp: Annotated[ + AwareDatetime, + Field( + description="Echo of the `since_timestamp` the runner requested (or the session-start timestamp the adopter substituted when the runner omitted it). Informational — the runner SHOULD use its own clock-bracket of when it issued the AdCP step request, not this echo, when attributing recorded_calls to a specific step. The controller is part of the adopter's claimed conformance; the echo is not adversarially trustworthy." + ), + ] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class ComplyTestControllerResponse8(ComplyTestControllerResponse): + model_config = ConfigDict( + extra='allow', + ) + success: Literal[False] + error: Annotated[ + Error, + Field( + description='Structured error code. `JCS_NON_FINITE_NUMBER` is reserved for digest-mode upstream_traffic responses that cannot be RFC 8785/JCS-canonicalized because the parsed JSON-like value tree contains a non-finite numeric value (`NaN`, `+Infinity`, or `-Infinity`); controllers MUST NOT coerce those values during digest computation, and runners grade that validation `not_applicable`, not failed.' + ), + ] + error_detail: Annotated[ + str | None, Field(description='Human-readable explanation of the failure') + ] = None + current_state: Annotated[ + str | None, Field(description='Current state of the entity, or null if not found') + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/compliance/get_creative_features_completion.py b/src/adcp/types/domains/compliance/get_creative_features_completion.py index 172400f30..a7fd506c2 100644 --- a/src/adcp/types/domains/compliance/get_creative_features_completion.py +++ b/src/adcp/types/domains/compliance/get_creative_features_completion.py @@ -1,20 +1,26 @@ -"""Types declared by the AdCP ``compliance/get_creative_features_completion`` schema. +# generated by datamodel-codegen: +# filename: compliance/get_creative_features_completion.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.compliance.get_creative_features_completion import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import creative_consumption +from ..creative import creative_feature_result -from adcp.types.generated_poc.compliance.get_creative_features_completion import ( - GetCreativeFeaturesComplianceCompletion, -) -# Explicit exports -__all__ = ["GetCreativeFeaturesComplianceCompletion"] +class GetCreativeFeaturesComplianceCompletion(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + status: Literal['completed'] = 'completed' + evaluation_id: Annotated[str, Field(min_length=1)] + results: list[creative_feature_result.CreativeFeatureResult] + pricing_option_id: str | None = None + vendor_cost: Annotated[StrictFloat | None, Field(ge=0.0)] = None + currency: Annotated[str | None, Field(pattern='^[A-Z]{3}$')] = None + consumption: creative_consumption.CreativeConsumption | None = None diff --git a/src/adcp/types/domains/compliance/task_completion_data.py b/src/adcp/types/domains/compliance/task_completion_data.py index d6e56dfb3..5690b1089 100644 --- a/src/adcp/types/domains/compliance/task_completion_data.py +++ b/src/adcp/types/domains/compliance/task_completion_data.py @@ -1,18 +1,25 @@ -"""Types declared by the AdCP ``compliance/task_completion_data`` schema. +# generated by datamodel-codegen: +# filename: compliance/task_completion_data.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.compliance.task_completion_data import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..media_buy import create_media_buy_response, get_products_response +from ..signals import get_signals_response +from . import get_creative_features_completion -from adcp.types.generated_poc.compliance.task_completion_data import ComplianceTaskCompletionData -# Explicit exports -__all__ = ["ComplianceTaskCompletionData"] +ComplianceTaskCompletionData = Annotated[ + get_products_response.GetProductsResponse + | get_signals_response.GetSignalsResponse + | create_media_buy_response.CreateMediaBuyResponse1 + | get_creative_features_completion.GetCreativeFeaturesComplianceCompletion, + Field( + description="Bounded completion union for the async tasks supported by force_task_completion. Polling and production task-result resolution use the originating task's manifest response schema rather than embedding the global async union.", + title='Compliance Task Completion Data', + ), +] diff --git a/src/adcp/types/domains/content_standards/__init__.py b/src/adcp/types/domains/content_standards/__init__.py index 6040930aa..aaf3561de 100644 --- a/src/adcp/types/domains/content_standards/__init__.py +++ b/src/adcp/types/domains/content_standards/__init__.py @@ -9,14 +9,14 @@ it from its own schema's module, ``adcp.types.domains.content_standards.``. Nothing here is renamed. -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:33:59 UTC +Auto-generated from the generated domain tree. DO NOT EDIT MANUALLY. +Generation date: 2026-10-04 18:45:11 UTC """ # ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.content_standards.artifact import ( +from adcp.types.domains.content_standards.artifact import ( AssetAccess, AssetAccess1, AssetAccess2, @@ -35,65 +35,61 @@ TranscriptSource, TranscriptSource1, ) -from adcp.types.generated_poc.content_standards.artifact_webhook_payload import ( - ArtifactWebhookPayload, -) -from adcp.types.generated_poc.content_standards.calibrate_content_request import ( - CalibrateContentRequest, -) -from adcp.types.generated_poc.content_standards.calibrate_content_response import ( +from adcp.types.domains.content_standards.artifact_webhook_payload import ArtifactWebhookPayload +from adcp.types.domains.content_standards.calibrate_content_request import CalibrateContentRequest +from adcp.types.domains.content_standards.calibrate_content_response import ( CalibrateContentResponse, CalibrateContentResponse1, CalibrateContentResponse2, ) -from adcp.types.generated_poc.content_standards.content_standards import ContentStandards -from adcp.types.generated_poc.content_standards.create_content_standards_request import ( +from adcp.types.domains.content_standards.content_standards import ContentStandards +from adcp.types.domains.content_standards.create_content_standards_request import ( CreateContentStandardsRequest, ) -from adcp.types.generated_poc.content_standards.create_content_standards_response import ( +from adcp.types.domains.content_standards.create_content_standards_response import ( CreateContentStandardsResponse, CreateContentStandardsResponse1, CreateContentStandardsResponse2, ) -from adcp.types.generated_poc.content_standards.get_content_standards_request import ( +from adcp.types.domains.content_standards.get_content_standards_request import ( GetContentStandardsRequest, ) -from adcp.types.generated_poc.content_standards.get_content_standards_response import ( +from adcp.types.domains.content_standards.get_content_standards_response import ( GetContentStandardsResponse, GetContentStandardsResponse1, GetContentStandardsResponse2, ) -from adcp.types.generated_poc.content_standards.get_media_buy_artifacts_request import ( +from adcp.types.domains.content_standards.get_media_buy_artifacts_request import ( GetMediaBuyArtifactsRequest, TimeRange, ) -from adcp.types.generated_poc.content_standards.get_media_buy_artifacts_response import ( +from adcp.types.domains.content_standards.get_media_buy_artifacts_response import ( CollectionInfo, GetMediaBuyArtifactsResponse, GetMediaBuyArtifactsResponse1, GetMediaBuyArtifactsResponse2, ) -from adcp.types.generated_poc.content_standards.list_content_standards_request import ( +from adcp.types.domains.content_standards.list_content_standards_request import ( ListContentStandardsRequest, ) -from adcp.types.generated_poc.content_standards.list_content_standards_response import ( +from adcp.types.domains.content_standards.list_content_standards_response import ( ListContentStandardsResponse, ListContentStandardsResponse1, ListContentStandardsResponse2, ) -from adcp.types.generated_poc.content_standards.update_content_standards_request import ( +from adcp.types.domains.content_standards.update_content_standards_request import ( UpdateContentStandardsRequest, ) -from adcp.types.generated_poc.content_standards.update_content_standards_response import ( +from adcp.types.domains.content_standards.update_content_standards_response import ( UpdateContentStandardsResponse, UpdateContentStandardsResponse1, UpdateContentStandardsResponse2, ) -from adcp.types.generated_poc.content_standards.validate_content_delivery_request import ( +from adcp.types.domains.content_standards.validate_content_delivery_request import ( Record, ValidateContentDeliveryRequest, ) -from adcp.types.generated_poc.content_standards.validate_content_delivery_response import ( +from adcp.types.domains.content_standards.validate_content_delivery_response import ( Result, Summary, ValidateContentDeliveryResponse, diff --git a/src/adcp/types/domains/content_standards/artifact.py b/src/adcp/types/domains/content_standards/artifact.py index d7eeb63ba..82e314ab3 100644 --- a/src/adcp/types/domains/content_standards/artifact.py +++ b/src/adcp/types/domains/content_standards/artifact.py @@ -1,56 +1,316 @@ -"""Types declared by the AdCP ``content_standards/artifact`` schema. +# generated by datamodel-codegen: +# filename: content_standards/artifact.json +# timestamp: 2026-10-05T13:49:50+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.content_standards.artifact import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, RootModel + +from ..core import format_id as format_id_1 +from ..core import provenance as provenance_1 + + +class Role(StrEnum): + title = 'title' # type: ignore[assignment] + paragraph = 'paragraph' + heading = 'heading' + caption = 'caption' + quote = 'quote' + list_item = 'list_item' + description = 'description' + + +class ContentFormat(StrEnum): + text_plain = 'text/plain' + text_markdown = 'text/markdown' + text_html = 'text/html' + application_json = 'application/json' + + +class TranscriptFormat(StrEnum): + text_plain = 'text/plain' + text_markdown = 'text/markdown' + application_json = 'application/json' + + +class TranscriptSource(StrEnum): + original_script = 'original_script' + subtitles = 'subtitles' + closed_captions = 'closed_captions' + dub = 'dub' + generated = 'generated' + + +class TranscriptSource1(StrEnum): + original_script = 'original_script' + closed_captions = 'closed_captions' + generated = 'generated' + + +class Metadata(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + canonical: Annotated[AnyUrl | None, Field(description='Canonical URL')] = None + author: Annotated[str | None, Field(description='Artifact author name')] = None + keywords: Annotated[str | None, Field(description='Artifact keywords')] = None + open_graph: Annotated[ + dict[str, Any] | None, Field(description='Open Graph protocol metadata') + ] = None + twitter_card: Annotated[dict[str, Any] | None, Field(description='Twitter Card metadata')] = ( + None + ) + json_ld: Annotated[ + list[dict[str, Any]] | None, Field(description='JSON-LD structured data (schema.org)') + ] = None + + +class Identifiers(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + apple_podcast_id: Annotated[str | None, Field(description='Apple Podcasts ID')] = None + spotify_collection_id: Annotated[str | None, Field(description='Spotify collection ID')] = None + podcast_guid: Annotated[str | None, Field(description='Podcast GUID (from RSS feed)')] = None + youtube_video_id: Annotated[str | None, Field(description='YouTube video ID')] = None + rss_url: Annotated[AnyUrl | None, Field(description='RSS feed URL')] = None + + +class AssetAccess1(AdCPBaseModel): + method: Literal['bearer_token'] = 'bearer_token' + token: Annotated[ + str, + Field( + description="Short-lived, asset-scoped bearer token for the Authorization header. Producers MUST NOT issue a standing or broadly scoped credential here. Consumers MUST redact the token from logs, traces, errors, metrics, analytics, model context, and durable storage. Any temporary encrypted cache MUST be bounded by the credential expiry and the consumer's configured maximum lifetime." + ), + ] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.content_standards.artifact import ( - Artifact, - AssetAccess, - AssetAccess1, - AssetAccess2, - AssetAccess3, - Assets, - Assets1, - Assets2, - Assets3, - Assets4, - ContentFormat, - Identifiers, - Metadata, - Provider, - Role, - TranscriptFormat, - TranscriptSource, - TranscriptSource1, -) - -# Explicit exports -__all__ = [ - "Artifact", - "AssetAccess", - "AssetAccess1", - "AssetAccess2", - "AssetAccess3", - "Assets", - "Assets1", - "Assets2", - "Assets3", - "Assets4", - "ContentFormat", - "Identifiers", - "Metadata", - "Provider", - "Role", - "TranscriptFormat", - "TranscriptSource", - "TranscriptSource1", +class Provider(StrEnum): + gcp = 'gcp' + aws = 'aws' + + +class AssetAccess2(AdCPBaseModel): + method: Literal['service_account'] = 'service_account' + provider: Annotated[Provider, Field(description='Cloud provider')] + credentials: Annotated[ + dict[str, Any] | None, + Field( + deprecated=True, + description="Deprecated in AdCP 3.2 and eligible for removal in 4.0 or later only after the deprecation-policy notice and release-cycle gates are satisfied. Presence denotes the legacy inline-credential form, not workload-identity authorization. New producers MUST omit this field. Consumers MUST NOT activate received credentials automatically; they may process them only for an explicitly allowlisted legacy peer and otherwise MUST reject or quarantine them. Consumers MUST redact the complete value from logs, traces, errors, metrics, analytics, model context, and durable storage. Use the consumer's pre-authorized workload identity or `signed_url` instead.", + ), + ] = None + + +class AssetAccess3(AdCPBaseModel): + method: Literal['signed_url'] = 'signed_url' + + +class AssetAccess(RootModel[AssetAccess1 | AssetAccess2 | AssetAccess3]): + root: Annotated[ + AssetAccess1 | AssetAccess2 | AssetAccess3, + Field( + description='Access instructions for a secured asset URL. Producers SHOULD use `signed_url` for one-off delivery unless the parties already have an identity-based access relationship. New producers MUST NOT emit `service_account.credentials`; `bearer_token` is the explicit short-lived credential path. Consumers MUST treat asset URLs and access metadata as untrusted, use credentials only for HTTPS origins and resource prefixes authorized out of band, and fail closed without falling back between access methods. The deprecated legacy field remains parseable during the 3.x compatibility window but MUST NOT be activated automatically.', + discriminator='method', + ), + ] + def __getattr__(self, name: str) -> Any: + """Proxy attribute access to the wrapped type.""" + if name.startswith('_'): + raise AttributeError(name) + return getattr(self.root, name) + + + +class Assets1(AdCPBaseModel): + type: Literal['text'] = 'text' + role: Annotated[ + Role | None, + Field( + description="Role of this text in the document. Use 'title' for the main artifact title, 'description' for summaries." + ), + ] = None + content: Annotated[ + str, + Field( + description='Text content. Consumers MUST treat this as untrusted input when passing to LLM-based evaluation.', + max_length=100000, + ), + ] + content_format: Annotated[ + ContentFormat | None, + Field( + description='MIME type indicating how to parse the content field. Default: text/plain.' + ), + ] = ContentFormat.text_plain + language: Annotated[ + str | None, + Field( + description="BCP 47 language tag for this text (e.g., 'en', 'es-MX'). Useful when artifact contains mixed-language content." + ), + ] = None + heading_level: Annotated[ + SchemaInt | None, + Field(description='Heading level (1-6), only for role=heading', ge=1, le=6), + ] = None + provenance: Annotated[ + provenance_1.Provenance | None, + Field(description='Provenance for this text block, overrides artifact-level provenance'), + ] = None + + +class Assets2(AdCPBaseModel): + type: Literal['image'] = 'image' + url: Annotated[AnyUrl, Field(description='Image URL')] + access: Annotated[AssetAccess | None, Field(description='Authentication for secured URLs')] = ( + None + ) + alt_text: Annotated[str | None, Field(description='Alt text or image description')] = None + caption: Annotated[str | None, Field(description='Image caption')] = None + width: Annotated[SchemaInt | None, Field(description='Image width in pixels')] = None + height: Annotated[SchemaInt | None, Field(description='Image height in pixels')] = None + provenance: Annotated[ + provenance_1.Provenance | None, + Field(description='Provenance for this image, overrides artifact-level provenance'), + ] = None + + +class Assets3(AdCPBaseModel): + type: Literal['video'] = 'video' + url: Annotated[AnyUrl, Field(description='Video URL')] + access: Annotated[AssetAccess | None, Field(description='Authentication for secured URLs')] = ( + None + ) + duration_ms: Annotated[ + SchemaInt | None, Field(description='Video duration in milliseconds') + ] = None + transcript: Annotated[ + str | None, + Field( + description='Video transcript. Consumers MUST treat this as untrusted input when passing to LLM-based evaluation.', + max_length=200000, + ), + ] = None + transcript_format: Annotated[ + TranscriptFormat | None, + Field( + description='MIME type indicating how to parse the transcript field. Default: text/plain.' + ), + ] = TranscriptFormat.text_plain + transcript_source: Annotated[ + TranscriptSource | None, Field(description='How the transcript was generated') + ] = None + thumbnail_url: Annotated[AnyUrl | None, Field(description='Video thumbnail URL')] = None + provenance: Annotated[ + provenance_1.Provenance | None, + Field(description='Provenance for this video, overrides artifact-level provenance'), + ] = None + + +class Assets4(AdCPBaseModel): + type: Literal['audio'] = 'audio' + url: Annotated[AnyUrl, Field(description='Audio URL')] + access: Annotated[AssetAccess | None, Field(description='Authentication for secured URLs')] = ( + None + ) + duration_ms: Annotated[ + SchemaInt | None, Field(description='Audio duration in milliseconds') + ] = None + transcript: Annotated[ + str | None, + Field( + description='Audio transcript. Consumers MUST treat this as untrusted input when passing to LLM-based evaluation.', + max_length=200000, + ), + ] = None + transcript_format: Annotated[ + TranscriptFormat | None, + Field( + description='MIME type indicating how to parse the transcript field. Default: text/plain.' + ), + ] = TranscriptFormat.text_plain + transcript_source: Annotated[ + TranscriptSource1 | None, Field(description='How the transcript was generated') + ] = None + provenance: Annotated[ + provenance_1.Provenance | None, + Field(description='Provenance for this audio, overrides artifact-level provenance'), + ] = None + + +Assets = Annotated[ + Assets1 | Assets2 | Assets3 | Assets4, + Field(discriminator='type'), ] + + +class Artifact(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + property_rid: Annotated[ + str, + Field( + description='Stable property identifier from the property catalog. Globally unique across the ecosystem.' + ), + ] + artifact_id: Annotated[ + str, + Field( + description="Identifier for this artifact within the property. The property owner defines the scheme (e.g., 'article_12345', 'episode_42_segment_3', 'post_abc123')." + ), + ] + variant_id: Annotated[ + str | None, + Field( + description="Identifies a specific variant of this artifact. Use for A/B tests, translations, or temporal versions. Examples: 'en', 'es-MX', 'v2', 'headline_test_b'. The combination of artifact_id + variant_id must be unique." + ), + ] = None + format_id: Annotated[ + format_id_1.FormatReferenceStructuredObject | None, + Field( + deprecated=True, + description='**DEPRECATED in 3.2.** Legacy named-format reference. Use `format_kind` for canonical artifact media classification.', + ), + ] = None + format_kind: Annotated[ + str | None, + Field(description='Optional canonical media/creative kind associated with this artifact.'), + ] = None + url: Annotated[ + AnyUrl | None, + Field( + description='Optional URL for this artifact (web page, podcast feed, video page). Not all artifacts have URLs (e.g., Instagram content, podcast segments, TV scenes).' + ), + ] = None + published_time: Annotated[ + AwareDatetime | None, Field(description='When the artifact was published (ISO 8601 format)') + ] = None + last_update_time: Annotated[ + AwareDatetime | None, + Field(description='When the artifact was last modified (ISO 8601 format)'), + ] = None + assets: Annotated[ + list[Assets], + Field( + description='Artifact assets in document flow order - text blocks, images, video, audio', + max_length=200, + ), + ] + metadata: Annotated[ + Metadata | None, Field(description='Rich metadata extracted from the artifact') + ] = None + provenance: Annotated[ + provenance_1.Provenance | None, + Field( + description='Provenance metadata for this artifact. Serves as the default provenance for all assets within this artifact — individual assets can override with their own provenance.' + ), + ] = None + identifiers: Annotated[ + Identifiers | None, Field(description='Platform-specific identifiers for this artifact') + ] = None diff --git a/src/adcp/types/domains/content_standards/artifact_webhook_payload.py b/src/adcp/types/domains/content_standards/artifact_webhook_payload.py index 3c1403b5e..39c89399d 100644 --- a/src/adcp/types/domains/content_standards/artifact_webhook_payload.py +++ b/src/adcp/types/domains/content_standards/artifact_webhook_payload.py @@ -1,22 +1,73 @@ -"""Types declared by the AdCP ``content_standards/artifact_webhook_payload`` schema. +# generated by datamodel-codegen: +# filename: content_standards/artifact_webhook_payload.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.content_standards.artifact_webhook_payload import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import ext as ext_1 +from . import artifact as artifact_1 + + +class Pagination(AdCPBaseModel): + total_artifacts: Annotated[ + SchemaInt | None, Field(description='Total artifacts in the delivery period') + ] = None + batch_number: Annotated[ + SchemaInt | None, Field(description='Current batch number (1-indexed)') + ] = None + total_batches: Annotated[ + SchemaInt | None, Field(description='Total batches for this delivery period') + ] = None + + +class Artifact(AdCPBaseModel): + artifact: Annotated[artifact_1.Artifact, Field(description='The content artifact')] + delivered_at: Annotated[ + AwareDatetime, Field(description='When the impression was delivered (ISO 8601)') + ] + impression_id: Annotated[ + str | None, + Field(description='Optional impression identifier for correlation with delivery reports'), + ] = None + package_id: Annotated[ + str | None, Field(description='Package within the media buy this artifact relates to') + ] = None -from adcp.types.generated_poc.content_standards.artifact_webhook_payload import ( - Artifact, - ArtifactWebhookPayload, - Pagination, -) -# Explicit exports -__all__ = ["Artifact", "ArtifactWebhookPayload", "Pagination"] +class ArtifactWebhookPayload(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + idempotency_key: Annotated[ + str, + Field( + description='Sender-generated key stable across retries of the same webhook event. Sales agents MUST generate a cryptographically random value (UUID v4 recommended) per distinct emission of a batch and reuse the same key on every retry. Recipients MUST dedupe by this key, scoped to the authenticated sender identity (HMAC secret or Bearer credential) — keys from different sales agents are independent. Distinct from `batch_id`, which identifies the logical batch: `idempotency_key` identifies this specific emission event, so a re-emission of the same `batch_id` (e.g., after a correction) is a different event and MUST carry a fresh `idempotency_key`.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + media_buy_id: Annotated[ + str, Field(description='Media buy identifier these artifacts belong to') + ] + batch_id: Annotated[ + str, + Field( + description='Unique identifier for this batch of artifacts. Use for deduplication and acknowledgment.' + ), + ] + timestamp: Annotated[ + AwareDatetime, Field(description='When this batch was generated (ISO 8601)') + ] + artifacts: Annotated[ + list[Artifact], Field(description='Content artifacts from delivered impressions') + ] + pagination: Annotated[ + Pagination | None, Field(description='Pagination info when batching large artifact sets') + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/content_standards/calibrate_content_request.py b/src/adcp/types/domains/content_standards/calibrate_content_request.py index ee26918cf..f22e59cc6 100644 --- a/src/adcp/types/domains/content_standards/calibrate_content_request.py +++ b/src/adcp/types/domains/content_standards/calibrate_content_request.py @@ -1,20 +1,32 @@ -"""Types declared by the AdCP ``content_standards/calibrate_content_request`` schema. +# generated by datamodel-codegen: +# filename: content_standards/calibrate_content_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.content_standards.calibrate_content_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import Field + +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope +from . import artifact as artifact_1 -from adcp.types.generated_poc.content_standards.calibrate_content_request import ( - CalibrateContentRequest, -) -# Explicit exports -__all__ = ["CalibrateContentRequest"] +class CalibrateContentRequest(AdcpRequest, AdcpVersionEnvelope): + standards_id: Annotated[str, Field(description='Standards configuration to calibrate against')] + artifact: Annotated[artifact_1.Artifact, Field(description='Artifact to evaluate')] + idempotency_key: Annotated[ + str, + Field( + description='Client-generated unique key for at-most-once execution. If a request with the same key has already been processed, the server returns the original response without re-processing. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/content_standards/calibrate_content_response.py b/src/adcp/types/domains/content_standards/calibrate_content_response.py index c0e89b564..46897bfb1 100644 --- a/src/adcp/types/domains/content_standards/calibrate_content_response.py +++ b/src/adcp/types/domains/content_standards/calibrate_content_response.py @@ -1,28 +1,56 @@ -"""Types declared by the AdCP ``content_standards/calibrate_content_response`` schema. +# generated by datamodel-codegen: +# filename: content_standards/calibrate_content_response.json +# timestamp: preserved-by-post-generate-fixes -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.content_standards.calibrate_content_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, TypeAlias -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field + +from ..core.version_envelope import AdcpVersionEnvelope +from ..core import context as context_1 +from ..core import error as error_1 +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..enums import binary_verdict as binary_verdict_1 +from ..enums import feature_check_status as feature_check_status_1 + + +class Feature(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + feature_id: str + status: feature_check_status_1.FeatureCheckStatus + policy_id: str | None = None + explanation: str | None = None + confidence: Annotated[float, Field(ge=0, le=1)] | None = None + + +class CalibrateContentResponse1(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + verdict: binary_verdict_1.BinaryVerdict + confidence: Annotated[float, Field(ge=0, le=1)] | None = None + explanation: str | None = None + features: list[Feature] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class CalibrateContentResponse2(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + errors: list[error_1.Error] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +CalibrateContentResponse: TypeAlias = CalibrateContentResponse1 | CalibrateContentResponse2 -from adcp.types.generated_poc.content_standards.calibrate_content_response import ( - CalibrateContentResponse, - CalibrateContentResponse1, - CalibrateContentResponse2, - Feature, -) -# Explicit exports __all__ = [ - "CalibrateContentResponse", - "CalibrateContentResponse1", - "CalibrateContentResponse2", - "Feature", + 'CalibrateContentResponse', + 'CalibrateContentResponse1', + 'CalibrateContentResponse2', + 'Feature', ] diff --git a/src/adcp/types/domains/content_standards/content_standards.py b/src/adcp/types/domains/content_standards/content_standards.py index b574beb8f..18137b446 100644 --- a/src/adcp/types/domains/content_standards/content_standards.py +++ b/src/adcp/types/domains/content_standards/content_standards.py @@ -1,21 +1,78 @@ -"""Types declared by the AdCP ``content_standards/content_standards`` schema. +# generated by datamodel-codegen: +# filename: content_standards/content_standards.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.content_standards.content_standards import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import ext as ext_1 +from ..core import vendor_pricing_option +from ..enums import channels +from ..governance import policy_entry +from . import artifact + + +class CalibrationExemplars(AdCPBaseModel): + pass_: Annotated[ + list[artifact.Artifact] | None, + Field(alias='pass', description='Artifacts that pass the content standards'), + ] = None + fail: Annotated[ + list[artifact.Artifact] | None, + Field(description='Artifacts that fail the content standards'), + ] = None -from adcp.types.generated_poc.content_standards.content_standards import ( - CalibrationExemplars, - ContentStandards, -) -# Explicit exports -__all__ = ["CalibrationExemplars", "ContentStandards"] +class ContentStandards(AdCPBaseModel): + standards_id: Annotated[ + str, Field(description='Unique identifier for this standards configuration') + ] + name: Annotated[ + str | None, Field(description='Human-readable name for this standards configuration') + ] = None + countries_all: Annotated[ + list[str] | None, + Field( + description='ISO 3166-1 alpha-2 country codes. Standards apply in ALL listed countries (AND logic).', + min_length=1, + ), + ] = None + channels_any: Annotated[ + list[channels.MediaChannel] | None, + Field( + description='Advertising channels. Standards apply to ANY of the listed channels (OR logic).', + min_length=1, + ), + ] = None + languages_any: Annotated[ + list[str] | None, + Field( + description="BCP 47 language tags (e.g., 'en', 'de', 'fr'). Standards apply to content in ANY of these languages (OR logic). Content in unlisted languages is not covered by these standards.", + min_length=1, + ), + ] = None + policies: Annotated[ + list[policy_entry.PolicyEntry] | None, + Field( + description='Bespoke policies for this content-standards configuration, using the same shape as registry entries. Each policy is addressable by policy_id; governance findings reference the policy_id that triggered them.', + min_length=1, + ), + ] = None + calibration_exemplars: Annotated[ + CalibrationExemplars | None, + Field( + description='Training/test set to calibrate policy interpretation. Provides concrete examples of pass/fail decisions.' + ), + ] = None + pricing_options: Annotated[ + list[vendor_pricing_option.VendorPricingOption] | None, + Field( + description='Pricing options for this content standards service. The buyer passes the selected pricing_option_id in report_usage for billing verification.', + min_length=1, + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/content_standards/create_content_standards_request.py b/src/adcp/types/domains/content_standards/create_content_standards_request.py index bb2c447ed..b6ce2abcd 100644 --- a/src/adcp/types/domains/content_standards/create_content_standards_request.py +++ b/src/adcp/types/domains/content_standards/create_content_standards_request.py @@ -1,24 +1,132 @@ -"""Types declared by the AdCP ``content_standards/create_content_standards_request`` schema. +# generated by datamodel-codegen: +# filename: content_standards/create_content_standards_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.content_standards.create_content_standards_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Literal + +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field, model_validator + +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import channels +from ..governance import policy_entry +from . import artifact + + +class Scope(AdCPBaseModel): + countries_all: Annotated[ + list[str] | None, + Field( + description='ISO 3166-1 alpha-2 country codes. Standards apply in ALL listed countries (AND logic).', + min_length=1, + ), + ] = None + channels_any: Annotated[ + list[channels.MediaChannel] | None, + Field( + description='Advertising channels. Standards apply to ANY of the listed channels (OR logic).', + min_length=1, + ), + ] = None + languages_any: Annotated[ + list[str], + Field( + description="BCP 47 language tags (e.g., 'en', 'de', 'fr'). Standards apply to content in ANY of these languages (OR logic). Content in unlisted languages is not covered by these standards.", + min_length=1, + ), + ] + description: Annotated[ + str | None, Field(description='Human-readable description of this scope') + ] = None + + +class Pass(AdCPBaseModel): + type: Annotated[Literal['url'], Field(description='Indicates this is a URL reference')] = 'url' + value: Annotated[ + AnyUrl, + Field( + description="Full URL to a specific page (e.g., 'https://espn.com/nba/story/_/id/12345/lakers-win')" + ), + ] + language: Annotated[ + str | None, Field(description='BCP 47 language tag for content at this URL') + ] = None + + +class Fail(AdCPBaseModel): + type: Annotated[Literal['url'], Field(description='Indicates this is a URL reference')] = 'url' + value: Annotated[ + AnyUrl, + Field( + description="Full URL to a specific page (e.g., 'https://news.example.com/controversial-article')" + ), + ] + language: Annotated[ + str | None, Field(description='BCP 47 language tag for content at this URL') + ] = None + + +class CalibrationExemplars(AdCPBaseModel): + pass_: Annotated[ + list[Pass | artifact.Artifact] | None, + Field(alias='pass', description='Content that passes the standards'), + ] = None + fail: Annotated[ + list[Fail | artifact.Artifact] | None, Field(description='Content that fails the standards') + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.content_standards.create_content_standards_request import ( - CalibrationExemplars, - CreateContentStandardsRequest, - Fail, - Pass, - Scope, -) +class CreateContentStandardsRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + scope: Annotated[Scope, Field(description='Where this standards configuration applies')] + registry_policy_ids: Annotated[ + list[str] | None, + Field( + description="Registry policy IDs to use as the evaluation basis for this content standard. When provided, the agent resolves policies from the registry and uses their policy text and exemplars as the evaluation criteria. The 'policy' field becomes optional when registry_policy_ids is provided." + ), + ] = None + policies: Annotated[ + list[policy_entry.PolicyEntry] | None, + Field( + description='Bespoke policies for this content-standards configuration, using the same shape as registry entries. Each policy is addressable by policy_id and carries its own enforcement (must|should); governance findings reference the policy_id that triggered them. Inline bespoke policies can omit version/name/category (defaulted by the server). Combines with registry_policy_ids — registry policies and bespoke policies are both evaluated. Bespoke policy_ids MUST be flat (no colons/slashes) to avoid collision with namespaced registry ids.', + min_length=1, + ), + ] = None + calibration_exemplars: Annotated[ + CalibrationExemplars | None, + Field( + description='Training/test set to calibrate policy interpretation. Use URL references for pages to be fetched and analyzed, or full artifacts for pre-extracted content.' + ), + ] = None + idempotency_key: Annotated[ + str, + Field( + description='Client-generated unique key for this request. Prevents duplicate content standards creation on retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None -# Explicit exports -__all__ = ["CalibrationExemplars", "CreateContentStandardsRequest", "Fail", "Pass", "Scope"] + @model_validator(mode='after') + def _require_schema_required_group(self) -> CreateContentStandardsRequest: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('policies',), ('registry_policy_ids',),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'CreateContentStandardsRequest requires at least one of these field groups: policies | registry_policy_ids' + ) diff --git a/src/adcp/types/domains/content_standards/create_content_standards_response.py b/src/adcp/types/domains/content_standards/create_content_standards_response.py index 4e26c9f9c..bab6dbf3c 100644 --- a/src/adcp/types/domains/content_standards/create_content_standards_response.py +++ b/src/adcp/types/domains/content_standards/create_content_standards_response.py @@ -1,26 +1,50 @@ -"""Types declared by the AdCP ``content_standards/create_content_standards_response`` schema. +# generated by datamodel-codegen: +# filename: content_standards/create_content_standards_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.content_standards.create_content_standards_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.response_dispatch import ResponseArmDispatchMixin + +from typing import Annotated + +from pydantic import Field + +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope + + +class CreateContentStandardsResponse(AdcpResponse, ResponseArmDispatchMixin, AdcpVersionEnvelope, ProtocolEnvelope): + """Constructible compatibility base for generated response arms.""" + + @classmethod + def _response_arm_models(cls) -> tuple[type[CreateContentStandardsResponse], ...]: + return ( + CreateContentStandardsResponse1, + CreateContentStandardsResponse2, + ) + + +class CreateContentStandardsResponse1(CreateContentStandardsResponse): + standards_id: Annotated[ + str, Field(description='Unique identifier for the created standards configuration') + ] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.content_standards.create_content_standards_response import ( - CreateContentStandardsResponse, - CreateContentStandardsResponse1, - CreateContentStandardsResponse2, -) - -# Explicit exports -__all__ = [ - "CreateContentStandardsResponse", - "CreateContentStandardsResponse1", - "CreateContentStandardsResponse2", -] +class CreateContentStandardsResponse2(CreateContentStandardsResponse): + errors: list[error.Error] + conflicting_standards_id: Annotated[ + str | None, + Field( + description='If the error is a scope conflict, the ID of the existing standards that conflict' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/content_standards/get_content_standards_request.py b/src/adcp/types/domains/content_standards/get_content_standards_request.py index 866cc33d6..f9a21cf14 100644 --- a/src/adcp/types/domains/content_standards/get_content_standards_request.py +++ b/src/adcp/types/domains/content_standards/get_content_standards_request.py @@ -1,20 +1,23 @@ -"""Types declared by the AdCP ``content_standards/get_content_standards_request`` schema. +# generated by datamodel-codegen: +# filename: content_standards/get_content_standards_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.content_standards.get_content_standards_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import Field + +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope -from adcp.types.generated_poc.content_standards.get_content_standards_request import ( - GetContentStandardsRequest, -) -# Explicit exports -__all__ = ["GetContentStandardsRequest"] +class GetContentStandardsRequest(AdcpRequest, AdcpVersionEnvelope): + standards_id: Annotated[ + str, Field(description='Identifier for the standards configuration to retrieve') + ] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/content_standards/get_content_standards_response.py b/src/adcp/types/domains/content_standards/get_content_standards_response.py index f8d4cd50c..a4210844e 100644 --- a/src/adcp/types/domains/content_standards/get_content_standards_response.py +++ b/src/adcp/types/domains/content_standards/get_content_standards_response.py @@ -1,26 +1,40 @@ -"""Types declared by the AdCP ``content_standards/get_content_standards_response`` schema. +# generated by datamodel-codegen: +# filename: content_standards/get_content_standards_response.json +# timestamp: preserved-by-post-generate-fixes -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.content_standards.get_content_standards_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import TypeAlias -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict + +from ..core.version_envelope import AdcpVersionEnvelope +from ..core import context as context_1 +from ..core import error as error_1 +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope + + +class GetContentStandardsResponse1(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class GetContentStandardsResponse2(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + errors: list[error_1.Error] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +GetContentStandardsResponse: TypeAlias = GetContentStandardsResponse1 | GetContentStandardsResponse2 -from adcp.types.generated_poc.content_standards.get_content_standards_response import ( - GetContentStandardsResponse, - GetContentStandardsResponse1, - GetContentStandardsResponse2, -) -# Explicit exports __all__ = [ - "GetContentStandardsResponse", - "GetContentStandardsResponse1", - "GetContentStandardsResponse2", + 'GetContentStandardsResponse', + 'GetContentStandardsResponse1', + 'GetContentStandardsResponse2', ] diff --git a/src/adcp/types/domains/content_standards/get_media_buy_artifacts_request.py b/src/adcp/types/domains/content_standards/get_media_buy_artifacts_request.py index 2783259d4..e2128bcea 100644 --- a/src/adcp/types/domains/content_standards/get_media_buy_artifacts_request.py +++ b/src/adcp/types/domains/content_standards/get_media_buy_artifacts_request.py @@ -1,22 +1,69 @@ -"""Types declared by the AdCP ``content_standards/get_media_buy_artifacts_request`` schema. +# generated by datamodel-codegen: +# filename: content_standards/get_media_buy_artifacts_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.content_standards.get_media_buy_artifacts_request import +from adcp.types.base import AdcpRequest, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field, StrictBool + +from ..core import account_ref +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope + + +class TimeRange(AdCPBaseModel): + start: Annotated[AwareDatetime | None, Field(description='Start of time range (inclusive)')] = ( + None + ) + end: Annotated[AwareDatetime | None, Field(description='End of time range (exclusive)')] = None + + +class Pagination(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + max_results: Annotated[ + SchemaInt | None, + Field(description='Maximum number of artifacts to return per page', ge=1, le=10000), + ] = 1000 + cursor: Annotated[ + str | None, + Field(description='Opaque cursor from a previous response to fetch the next page'), + ] = None -from adcp.types.generated_poc.content_standards.get_media_buy_artifacts_request import ( - GetMediaBuyArtifactsRequest, - Pagination, - TimeRange, -) -# Explicit exports -__all__ = ["GetMediaBuyArtifactsRequest", "Pagination", "TimeRange"] +class GetMediaBuyArtifactsRequest(AdcpRequest, AdcpVersionEnvelope): + account: Annotated[ + account_ref.AccountReference | None, + Field( + description='Filter artifacts to a specific account. When omitted, returns artifacts across all accessible accounts.' + ), + ] = None + media_buy_id: Annotated[str, Field(description='Media buy to get artifacts from')] + package_ids: Annotated[ + list[str] | None, + Field(description='Filter to specific packages within the media buy', min_length=1), + ] = None + failures_only: Annotated[ + StrictBool | None, + Field( + description="When true, only return artifacts where the seller's local model returned local_verdict: 'fail'. Useful for auditing false positives. Not useful when the seller does not run a local evaluation model (all verdicts are 'unevaluated')." + ), + ] = False + time_range: Annotated[TimeRange | None, Field(description='Filter to specific time period')] = ( + None + ) + pagination: Annotated[ + Pagination | None, + Field( + description='Pagination parameters. Uses higher limits than standard pagination because artifact result sets can be very large.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/content_standards/get_media_buy_artifacts_response.py b/src/adcp/types/domains/content_standards/get_media_buy_artifacts_response.py index afac1b6dc..598849802 100644 --- a/src/adcp/types/domains/content_standards/get_media_buy_artifacts_response.py +++ b/src/adcp/types/domains/content_standards/get_media_buy_artifacts_response.py @@ -1,32 +1,75 @@ -"""Types declared by the AdCP ``content_standards/get_media_buy_artifacts_response`` schema. +# generated by datamodel-codegen: +# filename: content_standards/get_media_buy_artifacts_response.json +# timestamp: preserved-by-post-generate-fixes -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.content_standards.get_media_buy_artifacts_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Literal, TypeAlias -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import AwareDatetime, ConfigDict + +from ..core.version_envelope import AdcpVersionEnvelope +from . import artifact as artifact_1 +from ..core import context as context_1 +from ..core import error as error_1 +from ..core import ext as ext_1 +from ..core import pagination_response as pagination_response_1 +from ..core.protocol_envelope import ProtocolEnvelope + + +class BrandContext(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + brand_id: str | None = None + sku_id: str | None = None + + +class Artifact(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + record_id: str + timestamp: AwareDatetime | None = None + package_id: str | None = None + artifact: artifact_1.Artifact + country: str | None = None + channel: str | None = None + brand_context: BrandContext | None = None + local_verdict: Literal['pass', 'fail', 'unevaluated'] | None = None + + +class CollectionInfo(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + total_deliveries: int | None = None + total_collected: int | None = None + returned_count: int | None = None + effective_rate: float | None = None + + +class GetMediaBuyArtifactsResponse1(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + media_buy_id: str + artifacts: list[Artifact] + collection_info: CollectionInfo | None = None + pagination: pagination_response_1.PaginationResponse | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class GetMediaBuyArtifactsResponse2(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + errors: list[error_1.Error] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +GetMediaBuyArtifactsResponse: TypeAlias = GetMediaBuyArtifactsResponse1 | GetMediaBuyArtifactsResponse2 -from adcp.types.generated_poc.content_standards.get_media_buy_artifacts_response import ( - Artifact, - BrandContext, - CollectionInfo, - GetMediaBuyArtifactsResponse, - GetMediaBuyArtifactsResponse1, - GetMediaBuyArtifactsResponse2, -) -# Explicit exports __all__ = [ - "Artifact", - "BrandContext", - "CollectionInfo", - "GetMediaBuyArtifactsResponse", - "GetMediaBuyArtifactsResponse1", - "GetMediaBuyArtifactsResponse2", + 'GetMediaBuyArtifactsResponse', + 'GetMediaBuyArtifactsResponse1', + 'GetMediaBuyArtifactsResponse2', + 'Artifact', + 'BrandContext', + 'CollectionInfo', ] diff --git a/src/adcp/types/domains/content_standards/list_content_standards_request.py b/src/adcp/types/domains/content_standards/list_content_standards_request.py index 5ae76c89f..0669ef86a 100644 --- a/src/adcp/types/domains/content_standards/list_content_standards_request.py +++ b/src/adcp/types/domains/content_standards/list_content_standards_request.py @@ -1,20 +1,36 @@ -"""Types declared by the AdCP ``content_standards/list_content_standards_request`` schema. +# generated by datamodel-codegen: +# filename: content_standards/list_content_standards_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.content_standards.list_content_standards_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field + +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core import pagination_request +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import channels as channels_1 -from adcp.types.generated_poc.content_standards.list_content_standards_request import ( - ListContentStandardsRequest, -) -# Explicit exports -__all__ = ["ListContentStandardsRequest"] +class ListContentStandardsRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + channels: Annotated[ + list[channels_1.MediaChannel] | None, Field(description='Filter by channel', min_length=1) + ] = None + languages: Annotated[ + list[str] | None, Field(description='Filter by BCP 47 language tags', min_length=1) + ] = None + countries: Annotated[ + list[str] | None, + Field(description='Filter by ISO 3166-1 alpha-2 country codes', min_length=1), + ] = None + pagination: pagination_request.PaginationRequest | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/content_standards/list_content_standards_response.py b/src/adcp/types/domains/content_standards/list_content_standards_response.py index 11e91711f..15d935636 100644 --- a/src/adcp/types/domains/content_standards/list_content_standards_response.py +++ b/src/adcp/types/domains/content_standards/list_content_standards_response.py @@ -1,26 +1,48 @@ -"""Types declared by the AdCP ``content_standards/list_content_standards_response`` schema. +# generated by datamodel-codegen: +# filename: content_standards/list_content_standards_response.json +# timestamp: 2026-10-04T02:05:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.content_standards.list_content_standards_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.response_dispatch import ResponseArmDispatchMixin + +from typing import Annotated + +from pydantic import Field + +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 +from ..core import pagination_response +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from . import content_standards + + +class ListContentStandardsResponse(AdcpResponse, ResponseArmDispatchMixin, AdcpVersionEnvelope, ProtocolEnvelope): + """Constructible compatibility base for generated response arms.""" + + @classmethod + def _response_arm_models(cls) -> tuple[type[ListContentStandardsResponse], ...]: + return ( + ListContentStandardsResponse1, + ListContentStandardsResponse2, + ) + + +class ListContentStandardsResponse2(ListContentStandardsResponse): + errors: list[error.Error] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.content_standards.list_content_standards_response import ( - ListContentStandardsResponse, - ListContentStandardsResponse1, - ListContentStandardsResponse2, -) - -# Explicit exports -__all__ = [ - "ListContentStandardsResponse", - "ListContentStandardsResponse1", - "ListContentStandardsResponse2", -] +class ListContentStandardsResponse1(ListContentStandardsResponse): + standards: Annotated[ + list[content_standards.ContentStandards], + Field(description='Array of content standards configurations matching the filter criteria'), + ] + pagination: pagination_response.PaginationResponse | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/content_standards/update_content_standards_request.py b/src/adcp/types/domains/content_standards/update_content_standards_request.py index 5e6ddbfea..9c142430f 100644 --- a/src/adcp/types/domains/content_standards/update_content_standards_request.py +++ b/src/adcp/types/domains/content_standards/update_content_standards_request.py @@ -1,24 +1,123 @@ -"""Types declared by the AdCP ``content_standards/update_content_standards_request`` schema. +# generated by datamodel-codegen: +# filename: content_standards/update_content_standards_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.content_standards.update_content_standards_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field + +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import channels +from ..governance import policy_entry +from . import artifact + + +class Scope(AdCPBaseModel): + countries_all: Annotated[ + list[str] | None, + Field( + description='ISO 3166-1 alpha-2 country codes. Standards apply in ALL listed countries (AND logic).', + min_length=1, + ), + ] = None + channels_any: Annotated[ + list[channels.MediaChannel] | None, + Field( + description='Advertising channels. Standards apply to ANY of the listed channels (OR logic).', + min_length=1, + ), + ] = None + languages_any: Annotated[ + list[str] | None, + Field( + description="BCP 47 language tags (e.g., 'en', 'de', 'fr'). Standards apply to content in ANY of these languages (OR logic). Content in unlisted languages is not covered by these standards.", + min_length=1, + ), + ] = None + description: Annotated[ + str | None, Field(description='Human-readable description of this scope') + ] = None + + +class Pass(AdCPBaseModel): + type: Annotated[Literal['url'], Field(description='Indicates this is a URL reference')] = 'url' + value: Annotated[ + AnyUrl, + Field( + description="Full URL to a specific page (e.g., 'https://espn.com/nba/story/_/id/12345/lakers-win')" + ), + ] + language: Annotated[ + str | None, Field(description='BCP 47 language tag for content at this URL') + ] = None + + +class Fail(AdCPBaseModel): + type: Annotated[Literal['url'], Field(description='Indicates this is a URL reference')] = 'url' + value: Annotated[ + AnyUrl, + Field( + description="Full URL to a specific page (e.g., 'https://news.example.com/controversial-article')" + ), + ] + language: Annotated[ + str | None, Field(description='BCP 47 language tag for content at this URL') + ] = None + + +class CalibrationExemplars(AdCPBaseModel): + pass_: Annotated[ + list[Pass | artifact.Artifact] | None, + Field(alias='pass', description='Content that passes the standards'), + ] = None + fail: Annotated[ + list[Fail | artifact.Artifact] | None, Field(description='Content that fails the standards') + ] = None -from adcp.types.generated_poc.content_standards.update_content_standards_request import ( - CalibrationExemplars, - Fail, - Pass, - Scope, - UpdateContentStandardsRequest, -) -# Explicit exports -__all__ = ["CalibrationExemplars", "Fail", "Pass", "Scope", "UpdateContentStandardsRequest"] +class UpdateContentStandardsRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + standards_id: Annotated[str, Field(description='ID of the standards configuration to update')] + scope: Annotated[ + Scope | None, + Field(description='Updated scope for where this standards configuration applies'), + ] = None + registry_policy_ids: Annotated[ + list[str] | None, + Field( + description='Registry policy IDs to use as the evaluation basis. When provided, the agent resolves policies from the registry and uses their policy text and exemplars as the evaluation criteria.' + ), + ] = None + policies: Annotated[ + list[policy_entry.PolicyEntry] | None, + Field( + description='Updated bespoke policies for this content-standards configuration, using the same shape as registry entries. Replaces the existing policies array; use stable policy_ids to track policies across versions. Combines with registry_policy_ids. Bespoke policy_ids MUST be flat (no colons/slashes).', + min_length=1, + ), + ] = None + calibration_exemplars: Annotated[ + CalibrationExemplars | None, + Field( + description='Updated training/test set to calibrate policy interpretation. Use URL references for pages to be fetched and analyzed, or full artifacts for pre-extracted content.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + idempotency_key: Annotated[ + str, + Field( + description='Client-generated unique key for at-most-once execution. If a request with the same key has already been processed, the server returns the original response without re-processing. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] diff --git a/src/adcp/types/domains/content_standards/update_content_standards_response.py b/src/adcp/types/domains/content_standards/update_content_standards_response.py index cff418948..1b68f3c58 100644 --- a/src/adcp/types/domains/content_standards/update_content_standards_response.py +++ b/src/adcp/types/domains/content_standards/update_content_standards_response.py @@ -1,26 +1,60 @@ -"""Types declared by the AdCP ``content_standards/update_content_standards_response`` schema. +# generated by datamodel-codegen: +# filename: content_standards/update_content_standards_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.content_standards.update_content_standards_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.response_dispatch import ResponseArmDispatchMixin + +from typing import Annotated, Literal + +from pydantic import ConfigDict, Field + +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope + + +class UpdateContentStandardsResponse(AdcpResponse, ResponseArmDispatchMixin, AdcpVersionEnvelope, ProtocolEnvelope): + """Constructible compatibility base for generated response arms.""" + + @classmethod + def _response_arm_models(cls) -> tuple[type[UpdateContentStandardsResponse], ...]: + return ( + UpdateContentStandardsResponse1, + UpdateContentStandardsResponse2, + ) + + +class UpdateContentStandardsResponse1(UpdateContentStandardsResponse): + model_config = ConfigDict( + extra='allow', + ) + success: Annotated[ + Literal[True], Field(description='Indicates the update was applied successfully') + ] + standards_id: Annotated[str, Field(description='ID of the updated standards configuration')] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.content_standards.update_content_standards_response import ( - UpdateContentStandardsResponse, - UpdateContentStandardsResponse1, - UpdateContentStandardsResponse2, -) - -# Explicit exports -__all__ = [ - "UpdateContentStandardsResponse", - "UpdateContentStandardsResponse1", - "UpdateContentStandardsResponse2", -] +class UpdateContentStandardsResponse2(UpdateContentStandardsResponse): + model_config = ConfigDict( + extra='allow', + ) + success: Annotated[Literal[False], Field(description='Indicates the update failed')] + errors: Annotated[ + list[error.Error], Field(description='Errors that occurred during the update', min_length=1) + ] + conflicting_standards_id: Annotated[ + str | None, + Field( + description='If scope change conflicts with another configuration, the ID of the conflicting standards' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/content_standards/validate_content_delivery_request.py b/src/adcp/types/domains/content_standards/validate_content_delivery_request.py index 6e1fd4f92..0c1019bdb 100644 --- a/src/adcp/types/domains/content_standards/validate_content_delivery_request.py +++ b/src/adcp/types/domains/content_standards/validate_content_delivery_request.py @@ -1,22 +1,65 @@ -"""Types declared by the AdCP ``content_standards/validate_content_delivery_request`` schema. +# generated by datamodel-codegen: +# filename: content_standards/validate_content_delivery_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.content_standards.validate_content_delivery_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, Field, StrictBool + +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope +from . import artifact as artifact_1 + + +class BrandContext(AdCPBaseModel): + brand_id: Annotated[str | None, Field(description='Brand identifier')] = None + sku_id: Annotated[str | None, Field(description='Product/SKU identifier if applicable')] = None + + +class Record(AdCPBaseModel): + record_id: Annotated[str, Field(description='Unique identifier for this delivery record')] + media_buy_id: Annotated[ + str | None, + Field(description='Media buy this record belongs to (when batching across multiple buys)'), + ] = None + timestamp: Annotated[AwareDatetime | None, Field(description='When the delivery occurred')] = ( + None + ) + artifact: Annotated[artifact_1.Artifact, Field(description='Artifact where ad was delivered')] + country: Annotated[ + str | None, Field(description='ISO 3166-1 alpha-2 country code where delivery occurred') + ] = None + channel: Annotated[ + str | None, Field(description='Channel type (e.g., display, video, audio, social)') + ] = None + brand_context: Annotated[ + BrandContext | None, + Field( + description='Brand information for policy evaluation. Schema TBD - placeholder for brand identifiers.' + ), + ] = None -from adcp.types.generated_poc.content_standards.validate_content_delivery_request import ( - BrandContext, - Record, - ValidateContentDeliveryRequest, -) -# Explicit exports -__all__ = ["BrandContext", "Record", "ValidateContentDeliveryRequest"] +class ValidateContentDeliveryRequest(AdcpRequest, AdcpVersionEnvelope): + standards_id: Annotated[str, Field(description='Standards configuration to validate against')] + records: Annotated[ + list[Record], + Field( + description='Delivery records to validate (max 10,000)', max_length=10000, min_length=1 + ), + ] + feature_ids: Annotated[ + list[str] | None, + Field(description='Specific features to evaluate (defaults to all)', min_length=1), + ] = None + include_passed: Annotated[ + StrictBool | None, Field(description='Include passed records in results') + ] = True + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/content_standards/validate_content_delivery_response.py b/src/adcp/types/domains/content_standards/validate_content_delivery_response.py index a86b269d9..746470bb2 100644 --- a/src/adcp/types/domains/content_standards/validate_content_delivery_response.py +++ b/src/adcp/types/domains/content_standards/validate_content_delivery_response.py @@ -1,32 +1,70 @@ -"""Types declared by the AdCP ``content_standards/validate_content_delivery_response`` schema. +# generated by datamodel-codegen: +# filename: content_standards/validate_content_delivery_response.json +# timestamp: preserved-by-post-generate-fixes -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.content_standards.validate_content_delivery_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, TypeAlias -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field + +from ..core.version_envelope import AdcpVersionEnvelope +from ..core import context as context_1 +from ..core import error as error_1 +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..enums import binary_verdict as binary_verdict_1 +from ..enums import feature_check_status as feature_check_status_1 + + +class Summary(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + total_records: int + passed_records: int + failed_records: int + + +class Feature(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + feature_id: str + status: feature_check_status_1.FeatureCheckStatus + policy_id: str | None = None + explanation: str | None = None + confidence: Annotated[float, Field(ge=0, le=1)] | None = None + + +class Result(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + record_id: str + verdict: binary_verdict_1.BinaryVerdict + features: list[Feature] | None = None + + +class ValidateContentDeliveryResponse1(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + summary: Summary + results: list[Result] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class ValidateContentDeliveryResponse2(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + errors: list[error_1.Error] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +ValidateContentDeliveryResponse: TypeAlias = ValidateContentDeliveryResponse1 | ValidateContentDeliveryResponse2 -from adcp.types.generated_poc.content_standards.validate_content_delivery_response import ( - Feature, - Result, - Summary, - ValidateContentDeliveryResponse, - ValidateContentDeliveryResponse1, - ValidateContentDeliveryResponse2, -) -# Explicit exports __all__ = [ - "Feature", - "Result", - "Summary", - "ValidateContentDeliveryResponse", - "ValidateContentDeliveryResponse1", - "ValidateContentDeliveryResponse2", + 'ValidateContentDeliveryResponse', + 'ValidateContentDeliveryResponse1', + 'ValidateContentDeliveryResponse2', + 'Feature', + 'Result', + 'Summary', ] diff --git a/src/adcp/types/domains/core/__init__.py b/src/adcp/types/domains/core/__init__.py index 51730058b..0680e38de 100644 --- a/src/adcp/types/domains/core/__init__.py +++ b/src/adcp/types/domains/core/__init__.py @@ -9,44 +9,37 @@ it from its own schema's module, ``adcp.types.domains.core.``. Nothing here is renamed. -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-04 01:19:01 UTC +Auto-generated from the generated domain tree. DO NOT EDIT MANUALLY. +Generation date: 2026-10-04 18:45:11 UTC """ # ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.acceptance_policy_profile_ids import ( +from adcp.types.domains.core.acceptance_policy_profile_ids import ( AcceptancePolicyProfileId, AcceptancePolicyProfileIds, ) -from adcp.types.generated_poc.core.account import ( +from adcp.types.domains.core.account import ( Account, Compression, CreditLimit, GovernanceAgent, ReportingBucket, ) -from adcp.types.generated_poc.core.account_authorization import ( +from adcp.types.domains.core.account_authorization import ( AccountAuthorization, AllowedTask, ScopeName, ) -from adcp.types.generated_poc.core.account_change import ( - AccountChange, - Actor, - Repair, - UnavailableReason, -) -from adcp.types.generated_poc.core.account_change_recorded_webhook import ( - AccountChangeRecordedWebhook, -) -from adcp.types.generated_poc.core.account_identity_change import ( +from adcp.types.domains.core.account_change import AccountChange, Actor, Repair, UnavailableReason +from adcp.types.domains.core.account_change_recorded_webhook import AccountChangeRecordedWebhook +from adcp.types.domains.core.account_identity_change import ( AccountIdentityChange, AccountIdentityChange1, AccountIdentityChange2, ) -from adcp.types.generated_poc.core.account_identity_change_preview import ( +from adcp.types.domains.core.account_identity_change_preview import ( AccountIdentityChangePreview, AccountIdentityChangePreview1, AccountIdentityChangePreview2, @@ -58,46 +51,40 @@ NonblockingImpact, NonblockingImpacts, ) -from adcp.types.generated_poc.core.account_ref import ( +from adcp.types.domains.core.account_ref import ( AccountReference, AccountReference1, AccountReference2, ) -from adcp.types.generated_poc.core.account_status_changed_webhook import AccountStatusChangedWebhook -from adcp.types.generated_poc.core.account_timezone_capability import ( +from adcp.types.domains.core.account_status_changed_webhook import AccountStatusChangedWebhook +from adcp.types.domains.core.account_timezone_capability import ( AccountSelection, AccountTimezoneCapability, SupportedTimezone, ) -from adcp.types.generated_poc.core.account_with_authorization import AccountWithAuthorization -from adcp.types.generated_poc.core.activation_key import ( - ActivationKey, - ActivationKey1, - ActivationKey2, -) -from adcp.types.generated_poc.core.ad_inventory_config import AdInventoryConfiguration -from adcp.types.generated_poc.core.agent_encryption_key import AgentEncryptionKey -from adcp.types.generated_poc.core.agent_notification_config import AgentNotificationConfig -from adcp.types.generated_poc.core.agent_notification_config_state import ( - AgentNotificationConfigState, -) -from adcp.types.generated_poc.core.agent_reporting_destination import ( +from adcp.types.domains.core.account_with_authorization import AccountWithAuthorization +from adcp.types.domains.core.activation_key import ActivationKey, ActivationKey1, ActivationKey2 +from adcp.types.domains.core.ad_inventory_config import AdInventoryConfiguration +from adcp.types.domains.core.agent_encryption_key import AgentEncryptionKey +from adcp.types.domains.core.agent_notification_config import AgentNotificationConfig +from adcp.types.domains.core.agent_notification_config_state import AgentNotificationConfigState +from adcp.types.domains.core.agent_reporting_destination import ( AcceptedFormat, AgentReportingDestination, AgentReportingDestination1, AgentReportingDestination2, AgentReportingDestination3, ) -from adcp.types.generated_poc.core.agent_reporting_destination_state import ( +from adcp.types.domains.core.agent_reporting_destination_state import ( AgentReportingDestinationState, PriorDestinationRef, ) -from adcp.types.generated_poc.core.agent_signing_key import AgentSigningKey -from adcp.types.generated_poc.core.agent_webhook_challenge import AgentWebhookChallenge -from adcp.types.generated_poc.core.app_item import AppItem, Platform -from adcp.types.generated_poc.core.applicable_package_id import ApplicablePackageId -from adcp.types.generated_poc.core.asset_group_vocabulary import AdcpAssetGroupVocabularyRegistry -from adcp.types.generated_poc.core.assets.asset_union import ( +from adcp.types.domains.core.agent_signing_key import AgentSigningKey +from adcp.types.domains.core.agent_webhook_challenge import AgentWebhookChallenge +from adcp.types.domains.core.app_item import AppItem, Platform +from adcp.types.domains.core.applicable_package_id import ApplicablePackageId +from adcp.types.domains.core.asset_group_vocabulary import AdcpAssetGroupVocabularyRegistry +from adcp.types.domains.core.assets.asset_union import ( AssetVariant, AudioChannelLayout, C2paWatermarkAction, @@ -169,14 +156,14 @@ WebhookResponseType, WebhookSecurityMethod, ) -from adcp.types.generated_poc.core.assets.audio_asset import BitDepth -from adcp.types.generated_poc.core.assets.daast_asset import ( +from adcp.types.domains.core.assets.audio_asset import BitDepth +from adcp.types.domains.core.assets.daast_asset import ( DaastAsset3, DaastAsset4, Location13, MacroDeclaration12, ) -from adcp.types.generated_poc.core.assets.display_tag_asset import ( +from adcp.types.domains.core.assets.display_tag_asset import ( DisplayTagAsset4, DisplayTagAsset5, DisplayTagAsset6, @@ -185,159 +172,159 @@ MacroDeclaration15, MacroDeclaration16, ) -from adcp.types.generated_poc.core.assets.vast_asset import ( +from adcp.types.domains.core.assets.vast_asset import ( Location26, MacroDeclaration20, VastAsset3, VastAsset4, ) -from adcp.types.generated_poc.core.async_response_data import AdcpAsyncResponseData -from adcp.types.generated_poc.core.async_response_refs.creative.get_creative_features_async_response_submitted import ( +from adcp.types.domains.core.async_response_data import AdcpAsyncResponseData +from adcp.types.domains.core.async_response_refs.creative.get_creative_features_async_response_submitted import ( GetCreativeFeaturesSubmitted, ) -from adcp.types.generated_poc.core.async_response_refs.creative.sync_creatives_async_response_input_required import ( +from adcp.types.domains.core.async_response_refs.creative.sync_creatives_async_response_input_required import ( SyncCreativesInputRequired, ) -from adcp.types.generated_poc.core.async_response_refs.creative.sync_creatives_async_response_submitted import ( +from adcp.types.domains.core.async_response_refs.creative.sync_creatives_async_response_submitted import ( SyncCreativesSubmitted, ) -from adcp.types.generated_poc.core.async_response_refs.creative.sync_creatives_async_response_working import ( +from adcp.types.domains.core.async_response_refs.creative.sync_creatives_async_response_working import ( SyncCreativesWorking, ) -from adcp.types.generated_poc.core.async_response_refs.media_buy.accept_proposal_async_response_input_required import ( +from adcp.types.domains.core.async_response_refs.media_buy.accept_proposal_async_response_input_required import ( AcceptProposalInputRequired, ) -from adcp.types.generated_poc.core.async_response_refs.media_buy.accept_proposal_async_response_submitted import ( +from adcp.types.domains.core.async_response_refs.media_buy.accept_proposal_async_response_submitted import ( AcceptProposalSubmitted, ) -from adcp.types.generated_poc.core.async_response_refs.media_buy.accept_proposal_async_response_working import ( +from adcp.types.domains.core.async_response_refs.media_buy.accept_proposal_async_response_working import ( AcceptProposalWorking, ) -from adcp.types.generated_poc.core.async_response_refs.media_buy.build_creative_async_response_input_required import ( +from adcp.types.domains.core.async_response_refs.media_buy.build_creative_async_response_input_required import ( BuildCreativeInputRequired, ) -from adcp.types.generated_poc.core.async_response_refs.media_buy.build_creative_async_response_submitted import ( +from adcp.types.domains.core.async_response_refs.media_buy.build_creative_async_response_submitted import ( BuildCreativeSubmitted, ) -from adcp.types.generated_poc.core.async_response_refs.media_buy.build_creative_async_response_working import ( +from adcp.types.domains.core.async_response_refs.media_buy.build_creative_async_response_working import ( BuildCreativeWorking, ) -from adcp.types.generated_poc.core.async_response_refs.media_buy.buy_products_async_response_input_required import ( +from adcp.types.domains.core.async_response_refs.media_buy.buy_products_async_response_input_required import ( BuyProductsInputRequired, ) -from adcp.types.generated_poc.core.async_response_refs.media_buy.buy_products_async_response_submitted import ( +from adcp.types.domains.core.async_response_refs.media_buy.buy_products_async_response_submitted import ( BuyProductsSubmitted, ) -from adcp.types.generated_poc.core.async_response_refs.media_buy.buy_products_async_response_working import ( +from adcp.types.domains.core.async_response_refs.media_buy.buy_products_async_response_working import ( BuyProductsWorking, ) -from adcp.types.generated_poc.core.async_response_refs.media_buy.control_media_buy_async_response_input_required import ( +from adcp.types.domains.core.async_response_refs.media_buy.control_media_buy_async_response_input_required import ( ControlMediaBuyInputRequired, ) -from adcp.types.generated_poc.core.async_response_refs.media_buy.control_media_buy_async_response_submitted import ( +from adcp.types.domains.core.async_response_refs.media_buy.control_media_buy_async_response_submitted import ( ControlMediaBuySubmitted, ) -from adcp.types.generated_poc.core.async_response_refs.media_buy.control_media_buy_async_response_working import ( +from adcp.types.domains.core.async_response_refs.media_buy.control_media_buy_async_response_working import ( ControlMediaBuyWorking, ) -from adcp.types.generated_poc.core.async_response_refs.media_buy.create_media_buy_async_response_input_required import ( +from adcp.types.domains.core.async_response_refs.media_buy.create_media_buy_async_response_input_required import ( CreateMediaBuyInputRequired, ) -from adcp.types.generated_poc.core.async_response_refs.media_buy.create_media_buy_async_response_submitted import ( +from adcp.types.domains.core.async_response_refs.media_buy.create_media_buy_async_response_submitted import ( CreateMediaBuySubmitted, ) -from adcp.types.generated_poc.core.async_response_refs.media_buy.create_media_buy_async_response_working import ( +from adcp.types.domains.core.async_response_refs.media_buy.create_media_buy_async_response_working import ( CreateMediaBuyWorking, ) -from adcp.types.generated_poc.core.async_response_refs.media_buy.decline_proposals_async_response_input_required import ( +from adcp.types.domains.core.async_response_refs.media_buy.decline_proposals_async_response_input_required import ( DeclineProposalsInputRequired, ) -from adcp.types.generated_poc.core.async_response_refs.media_buy.decline_proposals_async_response_submitted import ( +from adcp.types.domains.core.async_response_refs.media_buy.decline_proposals_async_response_submitted import ( DeclineProposalsSubmitted, ) -from adcp.types.generated_poc.core.async_response_refs.media_buy.decline_proposals_async_response_working import ( +from adcp.types.domains.core.async_response_refs.media_buy.decline_proposals_async_response_working import ( DeclineProposalsWorking, ) -from adcp.types.generated_poc.core.async_response_refs.media_buy.get_products_async_response_input_required import ( +from adcp.types.domains.core.async_response_refs.media_buy.get_products_async_response_input_required import ( GetProductsInputRequired, ) -from adcp.types.generated_poc.core.async_response_refs.media_buy.get_products_async_response_submitted import ( +from adcp.types.domains.core.async_response_refs.media_buy.get_products_async_response_submitted import ( GetProductsSubmitted, ) -from adcp.types.generated_poc.core.async_response_refs.media_buy.get_products_async_response_working import ( +from adcp.types.domains.core.async_response_refs.media_buy.get_products_async_response_working import ( GetProductsWorking, ) -from adcp.types.generated_poc.core.async_response_refs.media_buy.refine_proposals_async_response_input_required import ( +from adcp.types.domains.core.async_response_refs.media_buy.refine_proposals_async_response_input_required import ( RefineProposalsInputRequired, ) -from adcp.types.generated_poc.core.async_response_refs.media_buy.refine_proposals_async_response_submitted import ( +from adcp.types.domains.core.async_response_refs.media_buy.refine_proposals_async_response_submitted import ( RefineProposalsSubmitted, ) -from adcp.types.generated_poc.core.async_response_refs.media_buy.refine_proposals_async_response_working import ( +from adcp.types.domains.core.async_response_refs.media_buy.refine_proposals_async_response_working import ( RefineProposalsWorking, ) -from adcp.types.generated_poc.core.async_response_refs.media_buy.request_proposals_async_response_input_required import ( +from adcp.types.domains.core.async_response_refs.media_buy.request_proposals_async_response_input_required import ( RequestProposalsInputRequired, ) -from adcp.types.generated_poc.core.async_response_refs.media_buy.request_proposals_async_response_submitted import ( +from adcp.types.domains.core.async_response_refs.media_buy.request_proposals_async_response_submitted import ( RequestProposalsSubmitted, ) -from adcp.types.generated_poc.core.async_response_refs.media_buy.request_proposals_async_response_working import ( +from adcp.types.domains.core.async_response_refs.media_buy.request_proposals_async_response_working import ( RequestProposalsWorking, ) -from adcp.types.generated_poc.core.async_response_refs.media_buy.sync_catalogs_async_response_input_required import ( +from adcp.types.domains.core.async_response_refs.media_buy.sync_catalogs_async_response_input_required import ( SyncCatalogsInputRequired, ) -from adcp.types.generated_poc.core.async_response_refs.media_buy.sync_catalogs_async_response_submitted import ( +from adcp.types.domains.core.async_response_refs.media_buy.sync_catalogs_async_response_submitted import ( SyncCatalogsSubmitted, ) -from adcp.types.generated_poc.core.async_response_refs.media_buy.sync_catalogs_async_response_working import ( +from adcp.types.domains.core.async_response_refs.media_buy.sync_catalogs_async_response_working import ( SyncCatalogsWorking, ) -from adcp.types.generated_poc.core.async_response_refs.media_buy.update_media_buy_async_response_input_required import ( +from adcp.types.domains.core.async_response_refs.media_buy.update_media_buy_async_response_input_required import ( UpdateMediaBuyInputRequired, ) -from adcp.types.generated_poc.core.async_response_refs.media_buy.update_media_buy_async_response_submitted import ( +from adcp.types.domains.core.async_response_refs.media_buy.update_media_buy_async_response_submitted import ( UpdateMediaBuySubmitted, ) -from adcp.types.generated_poc.core.async_response_refs.media_buy.update_media_buy_async_response_working import ( +from adcp.types.domains.core.async_response_refs.media_buy.update_media_buy_async_response_working import ( UpdateMediaBuyWorking, ) -from adcp.types.generated_poc.core.async_response_refs.signals.get_signals_async_response_submitted import ( +from adcp.types.domains.core.async_response_refs.signals.get_signals_async_response_submitted import ( GetSignalsSubmitted, ) -from adcp.types.generated_poc.core.async_response_refs.signals.get_signals_async_response_working import ( +from adcp.types.domains.core.async_response_refs.signals.get_signals_async_response_working import ( GetSignalsWorking, ) -from adcp.types.generated_poc.core.attestation_capabilities import ( +from adcp.types.domains.core.attestation_capabilities import ( AcceptedIssuer, AttestationCapabilities, CredentialOrigin, Resolver, SupportedDeliveryMethod, ) -from adcp.types.generated_poc.core.attestation_evaluation import Outcome -from adcp.types.generated_poc.core.attestation_issuer import ( +from adcp.types.domains.core.attestation_evaluation import Outcome +from adcp.types.domains.core.attestation_issuer import ( AttestationIssuer, AttestationIssuer1, AttestationIssuer2, AttestationIssuer3, ) -from adcp.types.generated_poc.core.attestation_reference import ( +from adcp.types.domains.core.attestation_reference import ( AttestationReference, EmbeddedCredential, Locator, Locator1, ValidityHint, ) -from adcp.types.generated_poc.core.attestation_subject import ( +from adcp.types.domains.core.attestation_subject import ( AttestationSubject, AttestationSubject1, AttestationSubject2, AttestationSubject3, ) -from adcp.types.generated_poc.core.attribution_window import AttributionWindow -from adcp.types.generated_poc.core.audience_activation_method import ( +from adcp.types.domains.core.attribution_window import AttributionWindow +from adcp.types.domains.core.audience_activation_method import ( AudienceActivationMethod, AudienceActivationMethod1, AudienceActivationMethod2, @@ -348,8 +335,8 @@ Cloud, ConsumerIdentity, ) -from adcp.types.generated_poc.core.audience_characteristic import AudienceCharacteristic -from adcp.types.generated_poc.core.audience_evidence import ( +from adcp.types.domains.core.audience_characteristic import AudienceCharacteristic +from adcp.types.domains.core.audience_evidence import ( AudienceEvidence, Subject11, Subject12, @@ -375,33 +362,21 @@ Subject36, Subject37, ) -from adcp.types.generated_poc.core.audience_evidence_pin import AudienceEvidencePin -from adcp.types.generated_poc.core.audience_evidence_requirements import ( - AudienceEvidenceRequirements, -) -from adcp.types.generated_poc.core.audience_evidence_selection import AudienceEvidenceSelection -from adcp.types.generated_poc.core.audience_member import AudienceMember -from adcp.types.generated_poc.core.audience_selector import ( +from adcp.types.domains.core.audience_evidence_pin import AudienceEvidencePin +from adcp.types.domains.core.audience_evidence_requirements import AudienceEvidenceRequirements +from adcp.types.domains.core.audience_evidence_selection import AudienceEvidenceSelection +from adcp.types.domains.core.audience_member import AudienceMember +from adcp.types.domains.core.audience_selector import ( AudienceSelector, AudienceSelector1, AudienceSelector2, AudienceSelector3, AudienceSelector4, ) -from adcp.types.generated_poc.core.audience_source import ( - AudienceSource, - AudienceSource1, - AudienceSource2, -) -from adcp.types.generated_poc.core.authorized_agent_base import AuthorizedAgentBaseFields -from adcp.types.generated_poc.core.bidding_policy import ( - BiddingPolicy, - CostPer, - Roas, - Strength, - Strength1, -) -from adcp.types.generated_poc.core.bidding_policy_capability import ( +from adcp.types.domains.core.audience_source import AudienceSource, AudienceSource1, AudienceSource2 +from adcp.types.domains.core.authorized_agent_base import AuthorizedAgentBaseFields +from adcp.types.domains.core.bidding_policy import BiddingPolicy, CostPer, Roas, Strength, Strength1 +from adcp.types.domains.core.bidding_policy_capability import ( BiddingPolicyCapability, CostPerStrength, MaxBidWithCostPer, @@ -410,21 +385,21 @@ RoasStrength, ScopeCapability, ) -from adcp.types.generated_poc.core.brand_id import BrandId -from adcp.types.generated_poc.core.brand_key import BrandKey -from adcp.types.generated_poc.core.brand_ref import ( +from adcp.types.domains.core.brand_id import BrandId +from adcp.types.domains.core.brand_key import BrandKey +from adcp.types.domains.core.brand_ref import ( BrandKitOverride, BrandReference, Colors, DataSubjectContestation, ) -from adcp.types.generated_poc.core.brand_response_authorization_result import ( +from adcp.types.domains.core.brand_response_authorization_result import ( BrandResponseAuthorizationResult, BrandResponseAuthorizationResult1, BrandResponseAuthorizationResult2, Trust, ) -from adcp.types.generated_poc.core.budget_allocation import ( +from adcp.types.domains.core.budget_allocation import ( BudgetAllocation, BudgetAllocation1, BudgetAllocation2, @@ -442,35 +417,35 @@ Target7, Target8, ) -from adcp.types.generated_poc.core.business_entity import Bank, BusinessEntity, Contact -from adcp.types.generated_poc.core.cancellation_policy import CancellationFee, CancellationPolicy -from adcp.types.generated_poc.core.canonical_account_ref import ( +from adcp.types.domains.core.business_entity import Bank, BusinessEntity, Contact +from adcp.types.domains.core.cancellation_policy import CancellationFee, CancellationPolicy +from adcp.types.domains.core.canonical_account_ref import ( CanonicalAccountReference, CanonicalAccountReference1, CanonicalAccountReference2, ) -from adcp.types.generated_poc.core.canonical_audience_evidence import ( +from adcp.types.domains.core.canonical_audience_evidence import ( AttestationDigest, CanonicalAudienceEvidence, ) -from adcp.types.generated_poc.core.canonical_audience_evidence_selection import ( +from adcp.types.domains.core.canonical_audience_evidence_selection import ( CanonicalAudienceEvidenceSelection, VerifiedAttestationDigest, ) -from adcp.types.generated_poc.core.canonical_budget_allocation import ( +from adcp.types.domains.core.canonical_budget_allocation import ( CanonicalBudgetAllocation, CanonicalBudgetAllocation1, CanonicalBudgetAllocation2, ) -from adcp.types.generated_poc.core.canonical_delivery_forecast import CanonicalDeliveryForecast -from adcp.types.generated_poc.core.canonical_forecast_point import CanonicalForecastPoint -from adcp.types.generated_poc.core.canonical_forecast_vendor_metric_value import ( +from adcp.types.domains.core.canonical_delivery_forecast import CanonicalDeliveryForecast +from adcp.types.domains.core.canonical_forecast_point import CanonicalForecastPoint +from adcp.types.domains.core.canonical_forecast_vendor_metric_value import ( CanonicalForecastVendorMetricValue, ) -from adcp.types.generated_poc.core.canonical_format_kind import CanonicalFormatKind -from adcp.types.generated_poc.core.canonical_format_option import CanonicalFormatOption, FormatKind -from adcp.types.generated_poc.core.canonical_measurement_terms import CanonicalMeasurementTerms -from adcp.types.generated_poc.core.canonical_media_buy_action import ( +from adcp.types.domains.core.canonical_format_kind import CanonicalFormatKind +from adcp.types.domains.core.canonical_format_option import CanonicalFormatOption, FormatKind +from adcp.types.domains.core.canonical_measurement_terms import CanonicalMeasurementTerms +from adcp.types.domains.core.canonical_media_buy_action import ( Action3, Action4, CanonicalMediaBuyAction, @@ -478,12 +453,10 @@ CanonicalMediaBuyAction2, CanonicalMediaBuyAction3, ) -from adcp.types.generated_poc.core.canonical_media_buy_action_fields import ( - CanonicalMediaBuyActionFields, -) -from adcp.types.generated_poc.core.canonical_media_buy_features import CanonicalMediaBuyFeatures -from adcp.types.generated_poc.core.canonical_metric_qualifier import CanonicalMetricQualifier -from adcp.types.generated_poc.core.canonical_optimization_goal import ( +from adcp.types.domains.core.canonical_media_buy_action_fields import CanonicalMediaBuyActionFields +from adcp.types.domains.core.canonical_media_buy_features import CanonicalMediaBuyFeatures +from adcp.types.domains.core.canonical_metric_qualifier import CanonicalMetricQualifier +from adcp.types.domains.core.canonical_optimization_goal import ( CanonicalOptimizationGoal, CanonicalOptimizationGoal1, CanonicalOptimizationGoal2, @@ -491,21 +464,16 @@ Target10, Target11, ) -from adcp.types.generated_poc.core.canonical_performance_standard import ( - CanonicalPerformanceStandard, -) -from adcp.types.generated_poc.core.canonical_placement import ( +from adcp.types.domains.core.canonical_performance_standard import CanonicalPerformanceStandard +from adcp.types.domains.core.canonical_placement import ( CanonicalDoohPlacementAttributes, CanonicalDoohScreenResolution, CanonicalProductPlacement, CanonicalProductPlacement1, CanonicalProductPlacement2, ) -from adcp.types.generated_poc.core.canonical_pricing_option import ( - CanonicalPricingOption, - PricingModel, -) -from adcp.types.generated_poc.core.canonical_product import ( +from adcp.types.domains.core.canonical_pricing_option import CanonicalPricingOption, PricingModel +from adcp.types.domains.core.canonical_product import ( CanonicalProduct, PublisherProperty1, PublisherProperty2, @@ -515,83 +483,73 @@ PublisherProperty6, PublisherProperty7, ) -from adcp.types.generated_poc.core.canonical_product_action import CanonicalProductAction -from adcp.types.generated_poc.core.canonical_projection_ref import ( +from adcp.types.domains.core.canonical_product_action import CanonicalProductAction +from adcp.types.domains.core.canonical_projection_ref import ( AssetSource, CanonicalProjectionReference, ) -from adcp.types.generated_poc.core.canonical_projection_slot_override import ( +from adcp.types.domains.core.canonical_projection_slot_override import ( CanonicalProjectionSlotOverride, ) -from adcp.types.generated_poc.core.canonical_proposal import CanonicalProposal, ProposalKind -from adcp.types.generated_poc.core.canonical_reporting_capabilities import ( - CanonicalReportingCapabilities, -) -from adcp.types.generated_poc.core.canonical_reporting_commitment import ( +from adcp.types.domains.core.canonical_proposal import CanonicalProposal, ProposalKind +from adcp.types.domains.core.canonical_reporting_capabilities import CanonicalReportingCapabilities +from adcp.types.domains.core.canonical_reporting_commitment import ( CanonicalReportingCommitment, CanonicalReportingCommitment1, CanonicalReportingCommitment2, ) -from adcp.types.generated_poc.core.canvas_constraint import CanvasConstraint, Constraint -from adcp.types.generated_poc.core.capabilities_changed_webhook import CapabilitiesChangedWebhook -from adcp.types.generated_poc.core.catalog_item_availability_error import ( - CatalogItemAvailabilityError, -) -from adcp.types.generated_poc.core.catalog_item_availability_ref import ( - CatalogItemAvailabilityReference, -) -from adcp.types.generated_poc.core.catalog_item_availability_state import ( - CatalogItemAvailabilityState, -) -from adcp.types.generated_poc.core.catalog_item_availability_update import ( - CatalogItemAvailabilityUpdate, -) -from adcp.types.generated_poc.core.catalog_item_availability_update_result import ( +from adcp.types.domains.core.canvas_constraint import CanvasConstraint, Constraint +from adcp.types.domains.core.capabilities_changed_webhook import CapabilitiesChangedWebhook +from adcp.types.domains.core.catalog_item_availability_error import CatalogItemAvailabilityError +from adcp.types.domains.core.catalog_item_availability_ref import CatalogItemAvailabilityReference +from adcp.types.domains.core.catalog_item_availability_state import CatalogItemAvailabilityState +from adcp.types.domains.core.catalog_item_availability_update import CatalogItemAvailabilityUpdate +from adcp.types.domains.core.catalog_item_availability_update_result import ( CatalogItemAvailabilityUpdateResult, ) -from adcp.types.generated_poc.core.catalog_item_delivery_metrics import CatalogItemDeliveryMetrics -from adcp.types.generated_poc.core.catalog_item_reference_not_found_error import ( +from adcp.types.domains.core.catalog_item_delivery_metrics import CatalogItemDeliveryMetrics +from adcp.types.domains.core.catalog_item_reference_not_found_error import ( CatalogItemReferenceNotFoundError, ) -from adcp.types.generated_poc.core.catalog_selection import CatalogSelection -from adcp.types.generated_poc.core.catchment import Catchment -from adcp.types.generated_poc.core.collection import Collection, RelatedCollection -from adcp.types.generated_poc.core.collection_delivery_metrics import CollectionDeliveryMetrics -from adcp.types.generated_poc.core.collection_distribution import CollectionDistribution -from adcp.types.generated_poc.core.collection_list_ref import CollectionListReference -from adcp.types.generated_poc.core.collection_property_delivery_metrics import ( +from adcp.types.domains.core.catalog_selection import CatalogSelection +from adcp.types.domains.core.catchment import Catchment +from adcp.types.domains.core.collection import Collection, RelatedCollection +from adcp.types.domains.core.collection_delivery_metrics import CollectionDeliveryMetrics +from adcp.types.domains.core.collection_distribution import CollectionDistribution +from adcp.types.domains.core.collection_list_ref import CollectionListReference +from adcp.types.domains.core.collection_property_delivery_metrics import ( CollectionPropertyDeliveryMetrics, ) -from adcp.types.generated_poc.core.collection_ref import CollectionReference -from adcp.types.generated_poc.core.collection_selection import ( +from adcp.types.domains.core.collection_ref import CollectionReference +from adcp.types.domains.core.collection_selection import ( CollectionSelection, CollectionSelection1, CollectionSelection2, ) -from adcp.types.generated_poc.core.collection_selector import CollectionSelector -from adcp.types.generated_poc.core.committed_metric import ( +from adcp.types.domains.core.collection_selector import CollectionSelector +from adcp.types.domains.core.committed_metric import ( CommittedMetric, CommittedMetric1, CommittedMetric2, Qualifier1, QualifierModel, ) -from adcp.types.generated_poc.core.compact_task_input_required import CompactTaskInputRequired -from adcp.types.generated_poc.core.compact_task_submitted import CompactTaskSubmitted -from adcp.types.generated_poc.core.compact_task_working import CompactTaskWorking -from adcp.types.generated_poc.core.content_rating import ContentRating -from adcp.types.generated_poc.core.context import ContextObject -from adcp.types.generated_poc.core.creative_approval_scope import ScopedCreativeApproval -from adcp.types.generated_poc.core.creative_asset import CreativeAsset, Input -from adcp.types.generated_poc.core.creative_assets import CreativeAssets, CreativeAssets1 -from adcp.types.generated_poc.core.creative_assignment import CreativeAssignment, RotationMode -from adcp.types.generated_poc.core.creative_consumption import CreativeConsumption -from adcp.types.generated_poc.core.creative_delivery_metrics import CreativeDeliveryMetrics -from adcp.types.generated_poc.core.creative_filters import CreativeFilters -from adcp.types.generated_poc.core.creative_item import CreativeItem, CreativeItem1, CreativeItem2 -from adcp.types.generated_poc.core.creative_locale_policy import CreativeLocalePolicy -from adcp.types.generated_poc.core.creative_localization import CreativeLocalization, TargetVariant -from adcp.types.generated_poc.core.creative_localization_readback import ( +from adcp.types.domains.core.compact_task_input_required import CompactTaskInputRequired +from adcp.types.domains.core.compact_task_submitted import CompactTaskSubmitted +from adcp.types.domains.core.compact_task_working import CompactTaskWorking +from adcp.types.domains.core.content_rating import ContentRating +from adcp.types.domains.core.context import ContextObject +from adcp.types.domains.core.creative_approval_scope import ScopedCreativeApproval +from adcp.types.domains.core.creative_asset import CreativeAsset, Input +from adcp.types.domains.core.creative_assets import CreativeAssets, CreativeAssets1 +from adcp.types.domains.core.creative_assignment import CreativeAssignment, RotationMode +from adcp.types.domains.core.creative_consumption import CreativeConsumption +from adcp.types.domains.core.creative_delivery_metrics import CreativeDeliveryMetrics +from adcp.types.domains.core.creative_filters import CreativeFilters +from adcp.types.domains.core.creative_item import CreativeItem, CreativeItem1, CreativeItem2 +from adcp.types.domains.core.creative_locale_policy import CreativeLocalePolicy +from adcp.types.domains.core.creative_localization import CreativeLocalization, TargetVariant +from adcp.types.domains.core.creative_localization_readback import ( CreativeLocalizationReadback, ResolvedAssets, ResolvedAssets1, @@ -599,48 +557,44 @@ Variants1, Variants2, ) -from adcp.types.generated_poc.core.creative_manifest import CreativeManifest -from adcp.types.generated_poc.core.creative_operation_format_declaration import ( +from adcp.types.domains.core.creative_manifest import CreativeManifest +from adcp.types.domains.core.creative_operation_format_declaration import ( CreativeOperationFormatDeclaration, ) -from adcp.types.generated_poc.core.creative_policy import CreativePolicy, ProvenanceRequirements -from adcp.types.generated_poc.core.creative_representation import CreativeRepresentation -from adcp.types.generated_poc.core.creative_representation_set import CreativeRepresentationSet -from adcp.types.generated_poc.core.creative_revision_id import CreativeRevisionId -from adcp.types.generated_poc.core.creative_variable import CreativeVariable, VariableType -from adcp.types.generated_poc.core.creative_variant import ( - Artifact, - CreativeVariant, - GenerationContext, -) -from adcp.types.generated_poc.core.daast_tracker_constraints import ( +from adcp.types.domains.core.creative_policy import CreativePolicy, ProvenanceRequirements +from adcp.types.domains.core.creative_representation import CreativeRepresentation +from adcp.types.domains.core.creative_representation_set import CreativeRepresentationSet +from adcp.types.domains.core.creative_revision_id import CreativeRevisionId +from adcp.types.domains.core.creative_variable import CreativeVariable, VariableType +from adcp.types.domains.core.creative_variant import Artifact, CreativeVariant, GenerationContext +from adcp.types.domains.core.daast_tracker_constraints import ( DaastEvent, DaastOffset, DaastTarget, DaastTrackerConstraints, DaastVersions, ) -from adcp.types.generated_poc.core.data_provider_signal_selector import ( +from adcp.types.domains.core.data_provider_signal_selector import ( DataProviderSignalSelector, DataProviderSignalSelector1, DataProviderSignalSelector2, DataProviderSignalSelector3, SignalTag, ) -from adcp.types.generated_poc.core.date_range import DateRange -from adcp.types.generated_poc.core.datetime_range import DatetimeRange -from adcp.types.generated_poc.core.daypart_target import DaypartTarget -from adcp.types.generated_poc.core.deadline_policy import DeadlinePolicy, MaterialStage -from adcp.types.generated_poc.core.delivery_breakdown_controls import DeliveryBreakdownControls -from adcp.types.generated_poc.core.delivery_forecast import DeliveryForecast -from adcp.types.generated_poc.core.delivery_metric_aggregate import ( +from adcp.types.domains.core.date_range import DateRange +from adcp.types.domains.core.datetime_range import DatetimeRange +from adcp.types.domains.core.daypart_target import DaypartTarget +from adcp.types.domains.core.deadline_policy import DeadlinePolicy, MaterialStage +from adcp.types.domains.core.delivery_breakdown_controls import DeliveryBreakdownControls +from adcp.types.domains.core.delivery_forecast import DeliveryForecast +from adcp.types.domains.core.delivery_metric_aggregate import ( DeliveryMetricAggregate, DeliveryMetricAggregate1, DeliveryMetricAggregate2, Field0, Qualifier3, ) -from adcp.types.generated_poc.core.delivery_metrics import ( +from adcp.types.domains.core.delivery_metrics import ( ByActionSourceItem, ByEventTypeItem, DeliveryMetrics, @@ -658,40 +612,40 @@ ViewedSecondsHistogramItem, ViewedSecondsPercentiles, ) -from adcp.types.generated_poc.core.delivery_provider import DeliveryProvider -from adcp.types.generated_poc.core.delivery_recipient import DeliveryRecipient -from adcp.types.generated_poc.core.demographic_age_range import DemographicAgeRange -from adcp.types.generated_poc.core.demographic_predicate import DemographicPredicate -from adcp.types.generated_poc.core.demographic_reporting_capability import ( +from adcp.types.domains.core.delivery_provider import DeliveryProvider +from adcp.types.domains.core.delivery_recipient import DeliveryRecipient +from adcp.types.domains.core.demographic_age_range import DemographicAgeRange +from adcp.types.domains.core.demographic_predicate import DemographicPredicate +from adcp.types.domains.core.demographic_reporting_capability import ( DemographicReportingCapability, ReportingMode, ) -from adcp.types.generated_poc.core.demographic_targeting_capability import ( +from adcp.types.domains.core.demographic_targeting_capability import ( DemographicTargetingCapability, ExecutionMode, UnknownHandling, ) -from adcp.types.generated_poc.core.demographic_targeting_intent import DemographicTargetingIntent -from adcp.types.generated_poc.core.demographic_targeting_resolution import ( +from adcp.types.domains.core.demographic_targeting_intent import DemographicTargetingIntent +from adcp.types.domains.core.demographic_targeting_resolution import ( DemographicTargetingResolution, Execution, Execution1, Execution2, IntervalId, ) -from adcp.types.generated_poc.core.deployment import Deployment, Deployment1, Deployment2 -from adcp.types.generated_poc.core.destination import Destination1, Destination2 -from adcp.types.generated_poc.core.destination_item import DestinationItem, DestinationType -from adcp.types.generated_poc.core.diagnostic_issue import DiagnosticIssue, Severity -from adcp.types.generated_poc.core.downstream_connection_requirement import ( +from adcp.types.domains.core.deployment import Deployment, Deployment1, Deployment2 +from adcp.types.domains.core.destination import Destination1, Destination2 +from adcp.types.domains.core.destination_item import DestinationItem, DestinationType +from adcp.types.domains.core.diagnostic_issue import DiagnosticIssue, Severity +from adcp.types.domains.core.downstream_connection_requirement import ( ConnectionType, DownstreamConnectionRequirement, ResourceRef, ) -from adcp.types.generated_poc.core.duration import Duration -from adcp.types.generated_poc.core.education_item import DegreeType, EducationItem, Level, Modality -from adcp.types.generated_poc.core.error import BuyerReason, DiscriminatorItem, Issue, Recovery -from adcp.types.generated_poc.core.evaluator_spec import ( +from adcp.types.domains.core.duration import Duration +from adcp.types.domains.core.education_item import DegreeType, EducationItem, Level, Modality +from adcp.types.domains.core.error import BuyerReason, DiscriminatorItem, Issue, Recovery +from adcp.types.domains.core.evaluator_spec import ( EvalBudget, EvaluatorSpec, EvaluatorSpec1, @@ -703,32 +657,29 @@ RankByItem1, RankByItem2, ) -from adcp.types.generated_poc.core.event import Event -from adcp.types.generated_poc.core.event_custom_data import Content, EventCustomData -from adcp.types.generated_poc.core.event_source_health import Detail, EventSourceHealth -from adcp.types.generated_poc.core.event_surface import Category, EventSurface -from adcp.types.generated_poc.core.experimental_feature_id import ExperimentalFeatureId -from adcp.types.generated_poc.core.ext import ExtensionObject -from adcp.types.generated_poc.core.feature_requirement import FeatureRequirement, IfNotCovered -from adcp.types.generated_poc.core.flight_item import FlightItem -from adcp.types.generated_poc.core.forecast_dimension_audience import AudienceForecastDimension -from adcp.types.generated_poc.core.forecast_dimension_device_platform import ( +from adcp.types.domains.core.event import Event +from adcp.types.domains.core.event_custom_data import Content, EventCustomData +from adcp.types.domains.core.event_source_health import Detail, EventSourceHealth +from adcp.types.domains.core.event_surface import Category, EventSurface +from adcp.types.domains.core.experimental_feature_id import ExperimentalFeatureId +from adcp.types.domains.core.ext import ExtensionObject +from adcp.types.domains.core.feature_requirement import FeatureRequirement, IfNotCovered +from adcp.types.domains.core.flight_item import FlightItem +from adcp.types.domains.core.forecast_dimension_audience import AudienceForecastDimension +from adcp.types.domains.core.forecast_dimension_device_platform import ( DevicePlatformForecastDimension, ) -from adcp.types.generated_poc.core.forecast_dimension_device_type import DeviceTypeForecastDimension -from adcp.types.generated_poc.core.forecast_dimension_geo import GeoForecastDimension -from adcp.types.generated_poc.core.forecast_dimension_placement import PlacementForecastDimension -from adcp.types.generated_poc.core.forecast_dimension_signal import ( - Presence, - SignalForecastDimension, -) -from adcp.types.generated_poc.core.forecast_dimension_time import TimeForecastDimension -from adcp.types.generated_poc.core.forecast_point import ForecastPoint, ViewableRate -from adcp.types.generated_poc.core.forecast_point_dimensions import ForecastPointDimensions -from adcp.types.generated_poc.core.forecast_range import ForecastRange -from adcp.types.generated_poc.core.forecast_rate_range import ForecastRateRange -from adcp.types.generated_poc.core.forecast_vendor_metric_value import ForecastVendorMetricValue -from adcp.types.generated_poc.core.format import ( +from adcp.types.domains.core.forecast_dimension_device_type import DeviceTypeForecastDimension +from adcp.types.domains.core.forecast_dimension_geo import GeoForecastDimension +from adcp.types.domains.core.forecast_dimension_placement import PlacementForecastDimension +from adcp.types.domains.core.forecast_dimension_signal import Presence, SignalForecastDimension +from adcp.types.domains.core.forecast_dimension_time import TimeForecastDimension +from adcp.types.domains.core.forecast_point import ForecastPoint, ViewableRate +from adcp.types.domains.core.forecast_point_dimensions import ForecastPointDimensions +from adcp.types.domains.core.forecast_range import ForecastRange +from adcp.types.domains.core.forecast_rate_range import ForecastRateRange +from adcp.types.domains.core.forecast_vendor_metric_value import ForecastVendorMetricValue +from adcp.types.domains.core.format import ( Assets10, Assets11, Assets12, @@ -818,102 +769,94 @@ Renders1, Responsive, ) -from adcp.types.generated_poc.core.format_id import FormatReferenceStructuredObject -from adcp.types.generated_poc.core.format_option_ref import ( +from adcp.types.domains.core.format_id import FormatReferenceStructuredObject +from adcp.types.domains.core.format_option_ref import ( FormatOptionReference, FormatOptionReference1, FormatOptionReference2, ) -from adcp.types.generated_poc.core.format_shape_vocabulary import AdcpFormatShapeVocabularyRegistry -from adcp.types.generated_poc.core.frequency_cap import FrequencyCap -from adcp.types.generated_poc.core.frequency_cap_constraints import FrequencyCapConstraints -from adcp.types.generated_poc.core.frequency_cap_duration_unit import FrequencyCapDurationUnit -from adcp.types.generated_poc.core.frequency_cap_impression_constraints import ( +from adcp.types.domains.core.format_shape_vocabulary import AdcpFormatShapeVocabularyRegistry +from adcp.types.domains.core.frequency_cap import FrequencyCap +from adcp.types.domains.core.frequency_cap_constraints import FrequencyCapConstraints +from adcp.types.domains.core.frequency_cap_duration_unit import FrequencyCapDurationUnit +from adcp.types.domains.core.frequency_cap_impression_constraints import ( AllowedValue, FrequencyCapImpressionConstraints, ) -from adcp.types.generated_poc.core.frequency_cap_interval_constraints import ( +from adcp.types.domains.core.frequency_cap_interval_constraints import ( AllowedInterval, FrequencyCapIntervalConstraints, ) -from adcp.types.generated_poc.core.frequency_cap_requirements import FrequencyCapRequirements -from adcp.types.generated_poc.core.generation_credential import GenerationCredential -from adcp.types.generated_poc.core.geo_breakdown_support import GeographicBreakdownSupport -from adcp.types.generated_poc.core.geo_delivery_metrics import GeoDeliveryMetrics -from adcp.types.generated_poc.core.geo_metro import GeoMetro -from adcp.types.generated_poc.core.geo_place_area import GeographicPlaceArea -from adcp.types.generated_poc.core.geo_place_catalog_capability import ( +from adcp.types.domains.core.frequency_cap_requirements import FrequencyCapRequirements +from adcp.types.domains.core.generation_credential import GenerationCredential +from adcp.types.domains.core.geo_breakdown_support import GeographicBreakdownSupport +from adcp.types.domains.core.geo_delivery_metrics import GeoDeliveryMetrics +from adcp.types.domains.core.geo_metro import GeoMetro +from adcp.types.domains.core.geo_place_area import GeographicPlaceArea +from adcp.types.domains.core.geo_place_catalog_capability import ( GeographicPlaceCatalogCapability, SupportedVersion, ) -from adcp.types.generated_poc.core.geo_place_catalog_entry import ( +from adcp.types.domains.core.geo_place_catalog_entry import ( GeographicPlaceCatalogEntry, ParentLabel, ReplacedByValue, ) -from adcp.types.generated_poc.core.geo_place_requirement import ( +from adcp.types.domains.core.geo_place_requirement import ( CatalogRequirement, GeographicPlaceRequirement, ) -from adcp.types.generated_poc.core.geo_place_resolver import Auth, GeographicPlaceResolver -from adcp.types.generated_poc.core.geo_place_support import GeographicPlaceSystemSupport -from adcp.types.generated_poc.core.geo_place_system import ( +from adcp.types.domains.core.geo_place_resolver import Auth, GeographicPlaceResolver +from adcp.types.domains.core.geo_place_support import GeographicPlaceSystemSupport +from adcp.types.domains.core.geo_place_system import ( GeographicPlaceIdentifierSystem, GeographicPlaceIdentifierSystem1, GeographicPlaceIdentifierSystem2, ) -from adcp.types.generated_poc.core.geo_place_type import ( +from adcp.types.domains.core.geo_place_type import ( GeographicPlaceType, GeographicPlaceType1, GeographicPlaceType2, ) -from adcp.types.generated_poc.core.geo_region_requirement import ( - Countries1, - GeographicRegionRequirement, -) -from adcp.types.generated_poc.core.geo_region_support import Countries3, GeographicRegionSupport -from adcp.types.generated_poc.core.get_geo_place_resolution_request import ( +from adcp.types.domains.core.geo_region_requirement import Countries1, GeographicRegionRequirement +from adcp.types.domains.core.geo_region_support import Countries3, GeographicRegionSupport +from adcp.types.domains.core.get_geo_place_resolution_request import ( GetGeographicPlaceResolutionRequest, ) -from adcp.types.generated_poc.core.get_geo_place_resolution_response import ( +from adcp.types.domains.core.get_geo_place_resolution_response import ( GetGeographicPlaceResolutionResponse, ) -from adcp.types.generated_poc.core.hotel_item import HotelItem -from adcp.types.generated_poc.core.iana_timezone import IanaTimezoneIdentifier -from adcp.types.generated_poc.core.impairment import Impairment, Transition -from adcp.types.generated_poc.core.indicator import Indicator -from adcp.types.generated_poc.core.indicator_bearing import IndicatorBearingResourceState -from adcp.types.generated_poc.core.indicator_scope import IndicatorScope -from adcp.types.generated_poc.core.indicators_changed_webhook import ( +from adcp.types.domains.core.hotel_item import HotelItem +from adcp.types.domains.core.iana_timezone import IanaTimezoneIdentifier +from adcp.types.domains.core.impairment import Impairment, Transition +from adcp.types.domains.core.indicator import Indicator +from adcp.types.domains.core.indicator_bearing import IndicatorBearingResourceState +from adcp.types.domains.core.indicator_scope import IndicatorScope +from adcp.types.domains.core.indicators_changed_webhook import ( IndicatorsChangedWebhook, RelationshipKind, ) -from adcp.types.generated_poc.core.industry_identifier import IndustryIdentifier -from adcp.types.generated_poc.core.insertion_order import ( - InsertionOrder, - PaymentTerms, - Terms, - TotalBudget, -) -from adcp.types.generated_poc.core.installment import DerivativeOf, Installment -from adcp.types.generated_poc.core.installment_deadlines import InstallmentDeadlines -from adcp.types.generated_poc.core.installment_delivery_metrics import InstallmentDeliveryMetrics -from adcp.types.generated_poc.core.installment_property_delivery_metrics import ( +from adcp.types.domains.core.industry_identifier import IndustryIdentifier +from adcp.types.domains.core.insertion_order import InsertionOrder, PaymentTerms, Terms, TotalBudget +from adcp.types.domains.core.installment import DerivativeOf, Installment +from adcp.types.domains.core.installment_deadlines import InstallmentDeadlines +from adcp.types.domains.core.installment_delivery_metrics import InstallmentDeliveryMetrics +from adcp.types.domains.core.installment_property_delivery_metrics import ( InstallmentPropertyDeliveryMetrics, ) -from adcp.types.generated_poc.core.installment_ref import InstallmentReference -from adcp.types.generated_poc.core.inventory_list_application import ( +from adcp.types.domains.core.installment_ref import InstallmentReference +from adcp.types.domains.core.inventory_list_application import ( InventoryListApplication, InventoryListApplication1, InventoryListApplication2, Summary, Summary2, ) -from adcp.types.generated_poc.core.job_item import EmploymentType, ExperienceLevel, JobItem, Salary -from adcp.types.generated_poc.core.keyword_delivery_metrics import KeywordDeliveryMetrics -from adcp.types.generated_poc.core.limited_series import LimitedSeries -from adcp.types.generated_poc.core.locale_tag import LanguageTag -from adcp.types.generated_poc.core.localized_creative_asset import ( +from adcp.types.domains.core.job_item import EmploymentType, ExperienceLevel, JobItem, Salary +from adcp.types.domains.core.keyword_delivery_metrics import KeywordDeliveryMetrics +from adcp.types.domains.core.limited_series import LimitedSeries +from adcp.types.domains.core.locale_tag import LanguageTag +from adcp.types.domains.core.localized_creative_asset import ( LocalizedCreativeAsset, LocalizedCreativeAsset1, LocalizedCreativeAsset10, @@ -937,56 +880,50 @@ LocalizedCreativeAsset8, LocalizedCreativeAsset9, ) -from adcp.types.generated_poc.core.macro_bearing_url import MacroBearingUrl3, MacroBearingUrl4 -from adcp.types.generated_poc.core.macro_resolution_capability import ( +from adcp.types.domains.core.macro_bearing_url import MacroBearingUrl3, MacroBearingUrl4 +from adcp.types.domains.core.macro_resolution_capability import ( MacroProcessingCapability, MappingStatus, Operation, ) -from adcp.types.generated_poc.core.macro_resolution_result import MacroResolutionResult -from adcp.types.generated_poc.core.material_deadline import MaterialDeadline -from adcp.types.generated_poc.core.mcp_webhook_payload import McpWebhookPayload -from adcp.types.generated_poc.core.measurement_readiness import MeasurementReadiness -from adcp.types.generated_poc.core.measurement_terms import MeasurementTerms -from adcp.types.generated_poc.core.measurement_window import MeasurementWindow -from adcp.types.generated_poc.core.media_buy import MediaBuy -from adcp.types.generated_poc.core.media_buy_available_action import MediaBuyAvailableAction -from adcp.types.generated_poc.core.media_buy_available_action_id import MediaBuyAvailableActionId -from adcp.types.generated_poc.core.media_buy_change_term_id import MediaBuyChangeTermId -from adcp.types.generated_poc.core.media_buy_features import MediaBuyFeatures -from adcp.types.generated_poc.core.media_buy_frequency_cap import MediaBuyFrequencyCap -from adcp.types.generated_poc.core.media_buy_frequency_cap_capability import ( +from adcp.types.domains.core.macro_resolution_result import MacroResolutionResult +from adcp.types.domains.core.material_deadline import MaterialDeadline +from adcp.types.domains.core.mcp_webhook_payload import McpWebhookPayload +from adcp.types.domains.core.measurement_readiness import MeasurementReadiness +from adcp.types.domains.core.measurement_terms import MeasurementTerms +from adcp.types.domains.core.measurement_window import MeasurementWindow +from adcp.types.domains.core.media_buy import MediaBuy +from adcp.types.domains.core.media_buy_available_action import MediaBuyAvailableAction +from adcp.types.domains.core.media_buy_available_action_id import MediaBuyAvailableActionId +from adcp.types.domains.core.media_buy_change_term_id import MediaBuyChangeTermId +from adcp.types.domains.core.media_buy_features import MediaBuyFeatures +from adcp.types.domains.core.media_buy_frequency_cap import MediaBuyFrequencyCap +from adcp.types.domains.core.media_buy_frequency_cap_capability import ( MediaBuyFrequencyCapCapability, ) -from adcp.types.generated_poc.core.media_buy_frequency_cap_requirement import ( +from adcp.types.domains.core.media_buy_frequency_cap_requirement import ( MediaBuyFrequencyCapRequirement, ) -from adcp.types.generated_poc.core.media_buy_frequency_cap_support import ( - MediaBuyFrequencyCapSupport, -) -from adcp.types.generated_poc.core.media_buy_legacy_terms_ref import MediaBuyTermsReference -from adcp.types.generated_poc.core.media_buy_support import ProductMediaBuySupport -from adcp.types.generated_poc.core.media_buy_support_requirements import ( +from adcp.types.domains.core.media_buy_frequency_cap_support import MediaBuyFrequencyCapSupport +from adcp.types.domains.core.media_buy_legacy_terms_ref import MediaBuyTermsReference +from adcp.types.domains.core.media_buy_support import ProductMediaBuySupport +from adcp.types.domains.core.media_buy_support_requirements import ( ProductMediaBuySupportRequirements, ) -from adcp.types.generated_poc.core.missing_metric import ( - MissingMetric, - MissingMetric1, - MissingMetric2, -) -from adcp.types.generated_poc.core.negative_keyword import NegativeKeyword -from adcp.types.generated_poc.core.notification_config import NotificationConfig -from adcp.types.generated_poc.core.offering import GeoTargets, Offering -from adcp.types.generated_poc.core.offering_asset_group import Items, OfferingAssetGroup -from adcp.types.generated_poc.core.operator_identity import OperatorIdentity -from adcp.types.generated_poc.core.operator_unit import OperatorUnit -from adcp.types.generated_poc.core.opportunity_context import ( +from adcp.types.domains.core.missing_metric import MissingMetric, MissingMetric1, MissingMetric2 +from adcp.types.domains.core.negative_keyword import NegativeKeyword +from adcp.types.domains.core.notification_config import NotificationConfig +from adcp.types.domains.core.offering import GeoTargets, Offering +from adcp.types.domains.core.offering_asset_group import Items, OfferingAssetGroup +from adcp.types.domains.core.operator_identity import OperatorIdentity +from adcp.types.domains.core.operator_unit import OperatorUnit +from adcp.types.domains.core.opportunity_context import ( CloseReason, Intent, OpportunityContext, Phase, ) -from adcp.types.generated_poc.core.optimization_goal import ( +from adcp.types.domains.core.optimization_goal import ( OptimizationGoal10, OptimizationGoal8, OptimizationGoal9, @@ -997,12 +934,12 @@ Target18, Target19, ) -from adcp.types.generated_poc.core.outcome_measurement import OutcomeMeasurement -from adcp.types.generated_poc.core.outcome_target_cost_per import OutcomeTargetCostPer -from adcp.types.generated_poc.core.overlay import Bounds, Overlay, Visual -from adcp.types.generated_poc.core.package import Package -from adcp.types.generated_poc.core.package_delivery_metric_value import PackageDeliveryMetricValue -from adcp.types.generated_poc.core.package_format_snapshot import ( +from adcp.types.domains.core.outcome_measurement import OutcomeMeasurement +from adcp.types.domains.core.outcome_target_cost_per import OutcomeTargetCostPer +from adcp.types.domains.core.overlay import Bounds, Overlay, Visual +from adcp.types.domains.core.package import Package +from adcp.types.domains.core.package_delivery_metric_value import PackageDeliveryMetricValue +from adcp.types.domains.core.package_format_snapshot import ( PackageFormatSnapshot, PackageFormatSnapshot1, PackageFormatSnapshot10, @@ -1038,7 +975,7 @@ PackageFormatSnapshot8, PackageFormatSnapshot9, ) -from adcp.types.generated_poc.core.package_signal_targeting import ( +from adcp.types.domains.core.package_signal_targeting import ( PackageSignalTargeting, PackageSignalTargeting1, PackageSignalTargeting2, @@ -1048,50 +985,48 @@ PackageSignalTargeting6, PackageSignalTargeting7, ) -from adcp.types.generated_poc.core.package_signal_targeting_group import ( +from adcp.types.domains.core.package_signal_targeting_group import ( Operator, PackageSignalTargetingGroup, ) -from adcp.types.generated_poc.core.package_signal_targeting_groups import ( - PackageSignalTargetingGroups, -) -from adcp.types.generated_poc.core.package_targeting_resolution import PackageTargetingResolution -from adcp.types.generated_poc.core.pagination_request import PaginationRequest -from adcp.types.generated_poc.core.pagination_response import PaginationResponse -from adcp.types.generated_poc.core.performance_feedback import ( +from adcp.types.domains.core.package_signal_targeting_groups import PackageSignalTargetingGroups +from adcp.types.domains.core.package_targeting_resolution import PackageTargetingResolution +from adcp.types.domains.core.pagination_request import PaginationRequest +from adcp.types.domains.core.pagination_response import PaginationResponse +from adcp.types.domains.core.performance_feedback import ( MeasurementPeriod, Metric7, PerformanceFeedback, ) -from adcp.types.generated_poc.core.performance_feedback_assertion import ( +from adcp.types.domains.core.performance_feedback_assertion import ( ConfidenceInterval, PerformanceFeedbackAssertion, ) -from adcp.types.generated_poc.core.performance_feedback_metric import ( +from adcp.types.domains.core.performance_feedback_metric import ( PerformanceFeedbackMetric, PerformanceFeedbackMetric1, PerformanceFeedbackMetric2, ) -from adcp.types.generated_poc.core.performance_standard import PerformanceStandard -from adcp.types.generated_poc.core.placement import ( +from adcp.types.domains.core.performance_standard import PerformanceStandard +from adcp.types.domains.core.placement import ( Placement, ProductDoohPlacementAttributes, ProductDoohScreenResolution, ) -from adcp.types.generated_poc.core.placement_definition import ( +from adcp.types.domains.core.placement_definition import ( FormatOptions, PlacementDefinition, PublisherDoohPlacementAttributes, PublisherDoohScreenResolution, ) -from adcp.types.generated_poc.core.placement_delivery_metrics import PlacementDeliveryMetrics -from adcp.types.generated_poc.core.placement_evidence import PlacementEvidence -from adcp.types.generated_poc.core.placement_identity import ( +from adcp.types.domains.core.placement_delivery_metrics import PlacementDeliveryMetrics +from adcp.types.domains.core.placement_evidence import PlacementEvidence +from adcp.types.domains.core.placement_identity import ( PlacementIdentity, PlacementIdentity1, PlacementIdentity2, ) -from adcp.types.generated_poc.core.placement_presentation import ( +from adcp.types.domains.core.placement_presentation import ( BoxDecoration, Canvas, Color, @@ -1104,18 +1039,15 @@ Rectangle, TextDecoration, ) -from adcp.types.generated_poc.core.placement_property_delivery_metrics import ( +from adcp.types.domains.core.placement_property_delivery_metrics import ( PlacementPropertyDeliveryMetrics, ) -from adcp.types.generated_poc.core.placement_ref import PlacementReference -from adcp.types.generated_poc.core.placement_selection import ( - PlacementSelection1, - PlacementSelection2, -) -from adcp.types.generated_poc.core.planned_delivery import Geo, PlannedDelivery -from adcp.types.generated_poc.core.platform_extension_ref import PlatformExtensionReference -from adcp.types.generated_poc.core.positive_postal_area_support import PositivePostalAreaSupport -from adcp.types.generated_poc.core.postal_area import ( +from adcp.types.domains.core.placement_ref import PlacementReference +from adcp.types.domains.core.placement_selection import PlacementSelection1, PlacementSelection2 +from adcp.types.domains.core.planned_delivery import Geo, PlannedDelivery +from adcp.types.domains.core.platform_extension_ref import PlatformExtensionReference +from adcp.types.domains.core.positive_postal_area_support import PositivePostalAreaSupport +from adcp.types.domains.core.postal_area import ( PostalArea, PostalArea1, PostalArea11, @@ -1145,14 +1077,14 @@ System3, System9, ) -from adcp.types.generated_poc.core.postal_area_support import ( +from adcp.types.domains.core.postal_area_support import ( CAEnum, GBEnum, ME, PostalAreaSupport, PostalAreaSupportAdditionalPropertyEnum, ) -from adcp.types.generated_poc.core.postal_country_system import ( +from adcp.types.domains.core.postal_country_system import ( PostalCountrySystem, PostalCountrySystem1, PostalCountrySystem10, @@ -1169,31 +1101,31 @@ System13, System19, ) -from adcp.types.generated_poc.core.presentation_ref import PlacementPresentationReference -from adcp.types.generated_poc.core.preview_provider import PublisherDesignatedPreviewProvider, Route -from adcp.types.generated_poc.core.preview_renderer_metadata import ( +from adcp.types.domains.core.presentation_ref import PlacementPresentationReference +from adcp.types.domains.core.preview_provider import PublisherDesignatedPreviewProvider, Route +from adcp.types.domains.core.preview_renderer_metadata import ( PreviewRendererMetadata, RenderingOrigin, ) -from adcp.types.generated_poc.core.price import Price -from adcp.types.generated_poc.core.pricing_option import PricingOption -from adcp.types.generated_poc.core.principal_changed_webhook import PrincipalChangedWebhook -from adcp.types.generated_poc.core.principal_declarations import ( +from adcp.types.domains.core.price import Price +from adcp.types.domains.core.pricing_option import PricingOption +from adcp.types.domains.core.principal_changed_webhook import PrincipalChangedWebhook +from adcp.types.domains.core.principal_declarations import ( AgentDeclarations, AsyncAdcpVersion, WebhookSigningAlgorithm, ) -from adcp.types.generated_poc.core.principal_declarations_state import ( +from adcp.types.domains.core.principal_declarations_state import ( Axis, Exclusion, PrincipalDeclarationsState, ) -from adcp.types.generated_poc.core.principal_state import ( +from adcp.types.domains.core.principal_state import ( DestinationRef, PrincipalState, RetiredDestination, ) -from adcp.types.generated_poc.core.product import ( +from adcp.types.domains.core.product import ( AudienceActivation, ConversionTracking, DeliveryMeasurement, @@ -1214,25 +1146,25 @@ SupportedTarget5, SupportedViewDuration, ) -from adcp.types.generated_poc.core.product_allocation import ProductAllocation -from adcp.types.generated_poc.core.product_allowed_action import ProductAllowedAction -from adcp.types.generated_poc.core.product_audience_evidence_requirements import ( +from adcp.types.domains.core.product_allocation import ProductAllocation +from adcp.types.domains.core.product_allowed_action import ProductAllowedAction +from adcp.types.domains.core.product_audience_evidence_requirements import ( AcceptedAttestationIssuers, AcceptedAttestationIssuers1, AcceptedAttestationIssuers2, AcceptedAttestationIssuers3, ProductAudienceEvidenceRequirements, ) -from adcp.types.generated_poc.core.product_card_reference_asset import ProductCardReferenceAsset -from adcp.types.generated_poc.core.product_change_map import ProductChangeMap, ProductChangeMap1 -from adcp.types.generated_poc.core.product_execution_requirement import ( +from adcp.types.domains.core.product_card_reference_asset import ProductCardReferenceAsset +from adcp.types.domains.core.product_change_map import ProductChangeMap, ProductChangeMap1 +from adcp.types.domains.core.product_execution_requirement import ( Connection, ProductExecutionRequirement, ProductExecutionRequirement1, ProductExecutionRequirement2, ProductExecutionRequirement3, ) -from adcp.types.generated_poc.core.product_filters import ( +from adcp.types.domains.core.product_filters import ( AudienceActivationMethods, AudienceActivationMethods1, AudienceActivationMethods2, @@ -1251,7 +1183,7 @@ SignalTargetingItem6, SignalTargetingItem7, ) -from adcp.types.generated_poc.core.product_format_declaration import ( +from adcp.types.domains.core.product_format_declaration import ( ProductFormatDeclaration, ProductFormatDeclaration1, ProductFormatDeclaration10, @@ -1270,37 +1202,37 @@ ProductFormatDeclaration8, ProductFormatDeclaration9, ) -from adcp.types.generated_poc.core.product_identity import ProductIdentity -from adcp.types.generated_poc.core.product_offer_filters import ( +from adcp.types.domains.core.product_identity import ProductIdentity +from adcp.types.domains.core.product_offer_filters import ( AvailabilityHorizon, ProductOfferFilters, RequiredPerformanceStandard, ) -from adcp.types.generated_poc.core.product_signal_targeting_option import ( +from adcp.types.domains.core.product_signal_targeting_option import ( ActivationStatus, AllowedTargetingMode, ProductSignalTargetingOption, ) -from adcp.types.generated_poc.core.product_targeting_resolution import ProductTargetingResolution -from adcp.types.generated_poc.core.property import Property -from adcp.types.generated_poc.core.property_delivery_metrics import PropertyDeliveryMetrics -from adcp.types.generated_poc.core.property_id import PropertyId -from adcp.types.generated_poc.core.property_list_ref import PropertyListReference -from adcp.types.generated_poc.core.property_ref import PropertyReference -from adcp.types.generated_poc.core.property_tag import PropertyTag -from adcp.types.generated_poc.core.proposal import Proposal -from adcp.types.generated_poc.core.protocol_envelope import ProtocolEnvelope -from adcp.types.generated_poc.core.provenance import VerifyAgent18 -from adcp.types.generated_poc.core.publisher_property_selector import ( +from adcp.types.domains.core.product_targeting_resolution import ProductTargetingResolution +from adcp.types.domains.core.property import Property +from adcp.types.domains.core.property_delivery_metrics import PropertyDeliveryMetrics +from adcp.types.domains.core.property_id import PropertyId +from adcp.types.domains.core.property_list_ref import PropertyListReference +from adcp.types.domains.core.property_ref import PropertyReference +from adcp.types.domains.core.property_tag import PropertyTag +from adcp.types.domains.core.proposal import Proposal +from adcp.types.domains.core.protocol_envelope import ProtocolEnvelope +from adcp.types.domains.core.provenance import VerifyAgent18 +from adcp.types.domains.core.publisher_property_selector import ( PublisherPropertySelector, PublisherPropertySelector1, PublisherPropertySelector2, PublisherPropertySelector3, ) -from adcp.types.generated_poc.core.push_notification_config import PushNotificationConfig -from adcp.types.generated_poc.core.real_estate_item import ListingType, PropertyType, RealEstateItem -from adcp.types.generated_poc.core.reference_renderer import ReferenceRenderer -from adcp.types.generated_poc.core.registry_event import ( +from adcp.types.domains.core.push_notification_config import PushNotificationConfig +from adcp.types.domains.core.real_estate_item import ListingType, PropertyType, RealEstateItem +from adcp.types.domains.core.reference_renderer import ReferenceRenderer +from adcp.types.domains.core.registry_event import ( AgentProfilePayload, AuthorizationPayload, AuthorizationType, @@ -1357,16 +1289,16 @@ StringArray, Tracks, ) -from adcp.types.generated_poc.core.registry_feed_response import Freshness, RegistryFeedResponse -from adcp.types.generated_poc.core.reporting_adjustment import AccountingPeriod, ReportingAdjustment -from adcp.types.generated_poc.core.reporting_adjustment_receipt import ( +from adcp.types.domains.core.registry_feed_response import Freshness, RegistryFeedResponse +from adcp.types.domains.core.reporting_adjustment import AccountingPeriod, ReportingAdjustment +from adcp.types.domains.core.reporting_adjustment_receipt import ( ReportingAdjustmentReceipt, ReportingAdjustmentRejectionCode, ) -from adcp.types.generated_poc.core.reporting_canonical_content_digest import ( +from adcp.types.domains.core.reporting_canonical_content_digest import ( ReportingCanonicalContentDigest, ) -from adcp.types.generated_poc.core.reporting_canonicalization_contract import ( +from adcp.types.domains.core.reporting_canonicalization_contract import ( AdditionalItem, EmptyReport, GoldenVectors, @@ -1374,52 +1306,52 @@ ReportingCanonicalizationContract, ReportingPrimaryKey, ) -from adcp.types.generated_poc.core.reporting_capabilities import ReportingCapabilities -from adcp.types.generated_poc.core.reporting_consumer_status import ( +from adcp.types.domains.core.reporting_capabilities import ReportingCapabilities +from adcp.types.domains.core.reporting_consumer_status import ( ConsumerStatus, FailureCode, MismatchCode, ReportingConsumerStatus, ) -from adcp.types.generated_poc.core.reporting_control_total import ( +from adcp.types.domains.core.reporting_control_total import ( ReportingControlTotal, ReportingControlTotal1, ReportingControlTotal2, ) -from adcp.types.generated_poc.core.reporting_coverage import ( +from adcp.types.domains.core.reporting_coverage import ( Limitation, ReportingCoverage, ReportingMediaBuyId, ReportingPackageId, ) -from adcp.types.generated_poc.core.reporting_dataset_share_destination import ( +from adcp.types.domains.core.reporting_dataset_share_destination import ( Recipient, ReportingCloud, ReportingDatasetShareDestination, ReportingDatasetShareDestination1, ReportingDatasetShareDestination2, ) -from adcp.types.generated_poc.core.reporting_delivery_capabilities import ( +from adcp.types.domains.core.reporting_delivery_capabilities import ( OperationsContact, ReportingDeliveryCapabilities, ) -from adcp.types.generated_poc.core.reporting_delivery_config import ( +from adcp.types.domains.core.reporting_delivery_config import ( AuthoritativeParty, CoverageRequirement, ReportingDeliveryConfiguration, ) -from adcp.types.generated_poc.core.reporting_delivery_config_state import ( +from adcp.types.domains.core.reporting_delivery_config_state import ( ReportingDeliveryConfigLifecycleState, ReportingDeliveryConfigurationState, ) -from adcp.types.generated_poc.core.reporting_delivery_method import ( +from adcp.types.domains.core.reporting_delivery_method import ( ReportingDeliveryMethod, ReportingDeliveryMethod1, ReportingDeliveryMethod2, ReportingDeliveryMethod3, ReportingOrchestration, ) -from adcp.types.generated_poc.core.reporting_delivery_offering import ( +from adcp.types.domains.core.reporting_delivery_offering import ( DestinationMode, ProducerIdentity, ReportingDeliveryOffering, @@ -1427,37 +1359,33 @@ ReportingFeedPurpose, ReportingProfile, ) -from adcp.types.generated_poc.core.reporting_delivery_offering_id import ReportingDeliveryOfferingId -from adcp.types.generated_poc.core.reporting_delivery_ready_webhook import ( +from adcp.types.domains.core.reporting_delivery_offering_id import ReportingDeliveryOfferingId +from adcp.types.domains.core.reporting_delivery_ready_webhook import ( Readiness, ReportingDeliveryReadyWebhook, ) -from adcp.types.generated_poc.core.reporting_file_compression import ReportingFileCompression -from adcp.types.generated_poc.core.reporting_file_entry import ReportingFileEntry -from adcp.types.generated_poc.core.reporting_file_manifest import ReportingFileManifest -from adcp.types.generated_poc.core.reporting_file_object_ref import ReportingFileObjectReference -from adcp.types.generated_poc.core.reporting_ledger_changed_webhook import ( - ReportingLedgerChangedWebhook, -) -from adcp.types.generated_poc.core.reporting_materialization import ReportingMaterialization -from adcp.types.generated_poc.core.reporting_native_version_ref import ( - ReportingNativeVersionReference, -) -from adcp.types.generated_poc.core.reporting_obligation import ( +from adcp.types.domains.core.reporting_file_compression import ReportingFileCompression +from adcp.types.domains.core.reporting_file_entry import ReportingFileEntry +from adcp.types.domains.core.reporting_file_manifest import ReportingFileManifest +from adcp.types.domains.core.reporting_file_object_ref import ReportingFileObjectReference +from adcp.types.domains.core.reporting_ledger_changed_webhook import ReportingLedgerChangedWebhook +from adcp.types.domains.core.reporting_materialization import ReportingMaterialization +from adcp.types.domains.core.reporting_native_version_ref import ReportingNativeVersionReference +from adcp.types.domains.core.reporting_obligation import ( ProductionStatus, ReconciliationStatus, ReportingObligation, ) -from adcp.types.generated_poc.core.reporting_receipt import RejectionCode, ReportingReceipt -from adcp.types.generated_poc.core.reporting_reconciliation_mode import ReportingReconciliationMode -from adcp.types.generated_poc.core.reporting_reliability_statistics import ( +from adcp.types.domains.core.reporting_receipt import RejectionCode, ReportingReceipt +from adcp.types.domains.core.reporting_reconciliation_mode import ReportingReconciliationMode +from adcp.types.domains.core.reporting_reliability_statistics import ( AdjustmentMagnitudeItem, Basis, LatencyPercentiles, ReportingReliabilityMeasurementPeriod, ReportingReliabilityStatistics, ) -from adcp.types.generated_poc.core.reporting_report_definition import ( +from adcp.types.domains.core.reporting_report_definition import ( Aggregation, Calendar, ContractVersion, @@ -1468,37 +1396,34 @@ ReportingReportDefinition, RestatementPolicy, ) -from adcp.types.generated_poc.core.reporting_resource import ( +from adcp.types.domains.core.reporting_resource import ( Immutability, ReportingReaderCompatibilityItem, ReportingResource, ) -from adcp.types.generated_poc.core.reporting_revision import ( +from adcp.types.domains.core.reporting_revision import ( DataThroughPrecision, FinalityBasis, ReportingRevision, ) -from adcp.types.generated_poc.core.reporting_schedule import ( - ReportingSchedule, - ReportingScheduleAlignment, -) -from adcp.types.generated_poc.core.reporting_schedule_offering import ( +from adcp.types.domains.core.reporting_schedule import ReportingSchedule, ReportingScheduleAlignment +from adcp.types.domains.core.reporting_schedule_offering import ( PeriodAnchorPolicy, PeriodTimezonePolicy, ReportingScheduleOffering, ) -from adcp.types.generated_poc.core.reporting_status_changed_webhook import ( +from adcp.types.domains.core.reporting_status_changed_webhook import ( IssueId, ReportingStatusChangedWebhook, ) -from adcp.types.generated_poc.core.reporting_status_issue import ( +from adcp.types.domains.core.reporting_status_issue import ( IssueState, RecommendedAction, ReportingStatusIssue, ReportingStatusSeverity, ResponsibleParty, ) -from adcp.types.generated_poc.core.reporting_verification import ( +from adcp.types.domains.core.reporting_verification import ( NativeCommitEvidence, ObservedThrough, PhysicalChecksums, @@ -1506,83 +1431,72 @@ ReportingVerification, VerificationPath, ) -from adcp.types.generated_poc.core.reporting_verification_profile import ( - ReportingVerificationProfile, -) -from adcp.types.generated_poc.core.reporting_verification_profile_set import ( +from adcp.types.domains.core.reporting_verification_profile import ReportingVerificationProfile +from adcp.types.domains.core.reporting_verification_profile_set import ( ReportingVerificationProfileSet, ReportingVerificationProfileSetEnum, ) -from adcp.types.generated_poc.core.reporting_webhook import ReportingFrequency, ReportingWebhook -from adcp.types.generated_poc.core.reporting_write_destination import ( +from adcp.types.domains.core.reporting_webhook import ReportingFrequency, ReportingWebhook +from adcp.types.domains.core.reporting_write_destination import ( ReportingWriteDestination, ReportingWriteDestination1, ReportingWriteDestination2, ) -from adcp.types.generated_poc.core.representation_destination import RepresentationDestination -from adcp.types.generated_poc.core.representation_rejection import RepresentationRejection -from adcp.types.generated_poc.core.representation_selection import ( - RepresentationSelection, - ResolvedBy, -) -from adcp.types.generated_poc.core.requirements.asset_requirements import AssetRequirements -from adcp.types.generated_poc.core.requirements.audio_asset_requirements import ( +from adcp.types.domains.core.representation_destination import RepresentationDestination +from adcp.types.domains.core.representation_rejection import RepresentationRejection +from adcp.types.domains.core.representation_selection import RepresentationSelection, ResolvedBy +from adcp.types.domains.core.requirements.asset_requirements import AssetRequirements +from adcp.types.domains.core.requirements.audio_asset_requirements import ( AudioAssetRequirements, SampleRate, ) -from adcp.types.generated_poc.core.requirements.catalog_field_binding import ( +from adcp.types.domains.core.requirements.catalog_field_binding import ( AssetPoolBinding, CatalogFieldBinding, CatalogFieldBinding1, PerItemBindings, ScalarBinding, ) -from adcp.types.generated_poc.core.requirements.catalog_requirements import CatalogRequirements -from adcp.types.generated_poc.core.requirements.css_asset_requirements import CssAssetRequirements -from adcp.types.generated_poc.core.requirements.daast_asset_requirements import ( - DaastAssetRequirements, -) -from adcp.types.generated_poc.core.requirements.html_asset_requirements import ( +from adcp.types.domains.core.requirements.catalog_requirements import CatalogRequirements +from adcp.types.domains.core.requirements.css_asset_requirements import CssAssetRequirements +from adcp.types.domains.core.requirements.daast_asset_requirements import DaastAssetRequirements +from adcp.types.domains.core.requirements.html_asset_requirements import ( HtmlAssetRequirements, Sandbox, ) -from adcp.types.generated_poc.core.requirements.image_asset_requirements import ( +from adcp.types.domains.core.requirements.image_asset_requirements import ( Bleed, Bleed1, ImageAssetRequirements, PixelRatio, ) -from adcp.types.generated_poc.core.requirements.javascript_asset_requirements import ( +from adcp.types.domains.core.requirements.javascript_asset_requirements import ( JavascriptAssetRequirements, ModuleType, ) -from adcp.types.generated_poc.core.requirements.markdown_asset_requirements import ( +from adcp.types.domains.core.requirements.markdown_asset_requirements import ( MarkdownAssetRequirements, ) -from adcp.types.generated_poc.core.requirements.offering_asset_constraint import ( - OfferingAssetConstraint, -) -from adcp.types.generated_poc.core.requirements.text_asset_requirements import TextAssetRequirements -from adcp.types.generated_poc.core.requirements.url_asset_requirements import ( +from adcp.types.domains.core.requirements.offering_asset_constraint import OfferingAssetConstraint +from adcp.types.domains.core.requirements.text_asset_requirements import TextAssetRequirements +from adcp.types.domains.core.requirements.url_asset_requirements import ( Protocol, UrlAssetRequirements, ) -from adcp.types.generated_poc.core.requirements.vast_asset_requirements import VastAssetRequirements -from adcp.types.generated_poc.core.requirements.video_asset_requirements import ( +from adcp.types.domains.core.requirements.vast_asset_requirements import VastAssetRequirements +from adcp.types.domains.core.requirements.video_asset_requirements import ( AudioCodec, AudioSampleRate, FrameRate, VideoAssetRequirements, ) -from adcp.types.generated_poc.core.requirements.webhook_asset_requirements import ( - WebhookAssetRequirements, -) -from adcp.types.generated_poc.core.response import ProtocolResponse -from adcp.types.generated_poc.core.response_payload_jws_envelope import ( +from adcp.types.domains.core.requirements.webhook_asset_requirements import WebhookAssetRequirements +from adcp.types.domains.core.response import ProtocolResponse +from adcp.types.domains.core.response_payload_jws_envelope import ( ResponsePayload, ResponsePayloadJwsEnvelope, ) -from adcp.types.generated_poc.core.rights_attestation_evaluation import ( +from adcp.types.domains.core.rights_attestation_evaluation import ( Issuer5, Issuer6, Reference, @@ -1590,7 +1504,7 @@ Subject10, Subject19, ) -from adcp.types.generated_poc.core.rights_constraint import ( +from adcp.types.domains.core.rights_constraint import ( ApprovalStatus, ExcludedCountry, GrantStatus, @@ -1602,23 +1516,20 @@ Subject20, Subject29, ) -from adcp.types.generated_poc.core.seller_agent_ref import SellerAgentReference -from adcp.types.generated_poc.core.signal_coverage_forecast import ( +from adcp.types.domains.core.seller_agent_ref import SellerAgentReference +from adcp.types.domains.core.signal_coverage_forecast import ( BucketCompleteness, BucketSemantics, Point, SignalCoverageForecast, ) -from adcp.types.generated_poc.core.signal_definition import AudienceScope, SignalDefinition, Tag -from adcp.types.generated_poc.core.signal_definition_enrichment import SignalDefinitionEnrichment -from adcp.types.generated_poc.core.signal_filters import SignalFilters -from adcp.types.generated_poc.core.signal_id import SignalId8, SignalId9 -from adcp.types.generated_poc.core.signal_listing import SignalListing -from adcp.types.generated_poc.core.signal_modeling_disclosure import ( - Audience, - SignalModelingDisclosure, -) -from adcp.types.generated_poc.core.signal_pricing import ( +from adcp.types.domains.core.signal_definition import AudienceScope, SignalDefinition, Tag +from adcp.types.domains.core.signal_definition_enrichment import SignalDefinitionEnrichment +from adcp.types.domains.core.signal_filters import SignalFilters +from adcp.types.domains.core.signal_id import SignalId8, SignalId9 +from adcp.types.domains.core.signal_listing import SignalListing +from adcp.types.domains.core.signal_modeling_disclosure import Audience, SignalModelingDisclosure +from adcp.types.domains.core.signal_pricing import ( VendorPricing, VendorPricing1, VendorPricing2, @@ -1626,41 +1537,38 @@ VendorPricing4, VendorPricing5, ) -from adcp.types.generated_poc.core.signal_pricing_option import SignalPricingOption -from adcp.types.generated_poc.core.signal_ref import SignalRef, SignalRef1, SignalRef2, SignalRef3 -from adcp.types.generated_poc.core.signal_selection_group_rule import SignalSelectionGroupRule -from adcp.types.generated_poc.core.signal_targeting import ( +from adcp.types.domains.core.signal_pricing_option import SignalPricingOption +from adcp.types.domains.core.signal_ref import SignalRef, SignalRef1, SignalRef2, SignalRef3 +from adcp.types.domains.core.signal_selection_group_rule import SignalSelectionGroupRule +from adcp.types.domains.core.signal_targeting import ( SignalTargeting, SignalTargeting1, SignalTargeting2, SignalTargeting3, ) -from adcp.types.generated_poc.core.signal_targeting_expression import ( +from adcp.types.domains.core.signal_targeting_expression import ( SignalTargetingExpression, SignalTargetingExpression1, SignalTargetingExpression2, SignalTargetingExpression3, ) -from adcp.types.generated_poc.core.signal_targeting_rules import ( - ResolutionModel, - SignalTargetingRules, -) -from adcp.types.generated_poc.core.sla_window import SlaWindow -from adcp.types.generated_poc.core.special import Special -from adcp.types.generated_poc.core.spot_reporting_capability import SpotReportingCapability -from adcp.types.generated_poc.core.start_timing import StartTiming -from adcp.types.generated_poc.core.store_item import StoreItem -from adcp.types.generated_poc.core.talent import Talent -from adcp.types.generated_poc.core.targeting import AgeRestriction, TargetingOverlay -from adcp.types.generated_poc.core.targeting_input import TargetingOverlayInput -from adcp.types.generated_poc.core.targeting_modification import ( +from adcp.types.domains.core.signal_targeting_rules import ResolutionModel, SignalTargetingRules +from adcp.types.domains.core.sla_window import SlaWindow +from adcp.types.domains.core.special import Special +from adcp.types.domains.core.spot_reporting_capability import SpotReportingCapability +from adcp.types.domains.core.start_timing import StartTiming +from adcp.types.domains.core.store_item import StoreItem +from adcp.types.domains.core.talent import Talent +from adcp.types.domains.core.targeting import AgeRestriction, TargetingOverlay +from adcp.types.domains.core.targeting_input import TargetingOverlayInput +from adcp.types.domains.core.targeting_modification import ( Path, Selector, TargetingModification, TargetingModification1, TargetingModification2, ) -from adcp.types.generated_poc.core.targeting_overlay_requirements import ( +from adcp.types.domains.core.targeting_overlay_requirements import ( BrowserRequirement, BrowserRequirement1, DaypartRequirement, @@ -1672,7 +1580,7 @@ Required, TargetingOverlayRequirements, ) -from adcp.types.generated_poc.core.targeting_overlay_support import ( +from adcp.types.domains.core.targeting_overlay_support import ( BrowserSupport, BrowserSupport1, CountrySupport, @@ -1689,34 +1597,34 @@ Supported, TargetingOverlaySupport, ) -from adcp.types.generated_poc.core.targeting_unknown_age_eligibility_constraint import ( +from adcp.types.domains.core.targeting_unknown_age_eligibility_constraint import ( TargetingUnknownAgeEligibilityConstraint, ) -from adcp.types.generated_poc.core.targeting_verified_age_basis_constraint import ( +from adcp.types.domains.core.targeting_verified_age_basis_constraint import ( TargetingVerifiedAgeBasisConstraint, ) -from adcp.types.generated_poc.core.tasks_get_request import TasksGetRequest -from adcp.types.generated_poc.core.tasks_get_response import ( +from adcp.types.domains.core.tasks_get_request import TasksGetRequest +from adcp.types.domains.core.tasks_get_response import ( Details, HistoryItem, Progress, TasksGetResponse, ) -from adcp.types.generated_poc.core.tasks_list_request import Filters, Sort, TasksListRequest -from adcp.types.generated_poc.core.tasks_list_response import ( +from adcp.types.domains.core.tasks_list_request import Filters, Sort, TasksListRequest +from adcp.types.domains.core.tasks_list_response import ( DomainBreakdown, QuerySummary, SortApplied, TasksListResponse, ) -from adcp.types.generated_poc.core.tracker_execution_contract import TrackerExecutionContract -from adcp.types.generated_poc.core.tracker_execution_selector import ( +from adcp.types.domains.core.tracker_execution_contract import TrackerExecutionContract +from adcp.types.domains.core.tracker_execution_selector import ( TrackerExecutionSelector, TrackerExecutionSelector1, TrackerExecutionSelector2, TrackerExecutionSelector3, ) -from adcp.types.generated_poc.core.transformer import ( +from adcp.types.domains.core.transformer import ( BrandAgent, InputFormat, InputFormat1, @@ -1758,21 +1666,18 @@ VariantDimension, VoiceSynthesisRefItem, ) -from adcp.types.generated_poc.core.transformer_param import Option, TransformerParam, ValueSource -from adcp.types.generated_poc.core.truncation_sentinel import FieldTruncation, TruncationSentinel -from adcp.types.generated_poc.core.user_match import UserMatch -from adcp.types.generated_poc.core.vast_media_file_requirements import ( - MimeType, - VastMediafileRequirements, -) -from adcp.types.generated_poc.core.vast_tracker_constraints import ( +from adcp.types.domains.core.transformer_param import Option, TransformerParam, ValueSource +from adcp.types.domains.core.truncation_sentinel import FieldTruncation, TruncationSentinel +from adcp.types.domains.core.user_match import UserMatch +from adcp.types.domains.core.vast_media_file_requirements import MimeType, VastMediafileRequirements +from adcp.types.domains.core.vast_tracker_constraints import ( VastEvent, VastOffset, VastTarget, VastTrackerConstraints, VastVersions, ) -from adcp.types.generated_poc.core.vehicle_item import ( +from adcp.types.domains.core.vehicle_item import ( BodyStyle, Condition, FuelType, @@ -1780,13 +1685,13 @@ Transmission, VehicleItem, ) -from adcp.types.generated_poc.core.vendor_metric_id import VendorMetricId -from adcp.types.generated_poc.core.vendor_metric_optimization import VendorMetricOptimization -from adcp.types.generated_poc.core.vendor_metric_optimization_supported_metric import ( +from adcp.types.domains.core.vendor_metric_id import VendorMetricId +from adcp.types.domains.core.vendor_metric_optimization import VendorMetricOptimization +from adcp.types.domains.core.vendor_metric_optimization_supported_metric import ( VendorMetricOptimizationSupportedMetric, ) -from adcp.types.generated_poc.core.vendor_metric_value import VendorMetricValue -from adcp.types.generated_poc.core.vendor_pricing_option import ( +from adcp.types.domains.core.vendor_metric_value import VendorMetricValue +from adcp.types.domains.core.vendor_pricing_option import ( AppliesToOutputCapabilityId, VendorPricingOption, VendorPricingOption1, @@ -1801,18 +1706,18 @@ VendorPricingOption8, VendorPricingOption9, ) -from adcp.types.generated_poc.core.verification_token_claims import ( +from adcp.types.domains.core.verification_token_claims import ( AgenticadvertisingOrgVerificationTokenClaims, VerificationTokenGradingProfile, VerificationTokenMode, ) -from adcp.types.generated_poc.core.version_envelope import AdcpVersionEnvelope -from adcp.types.generated_poc.core.warning import Warning -from adcp.types.generated_poc.core.warning_resource import WarningAffectedResource -from adcp.types.generated_poc.core.webhook_activity_record import WebhookActivityRecord -from adcp.types.generated_poc.core.webhook_challenge import WebhookChallenge -from adcp.types.generated_poc.core.webhook_challenge_response import WebhookChallengeResponse -from adcp.types.generated_poc.core.wholesale_feed_event import ( +from adcp.types.domains.core.version_envelope import AdcpVersionEnvelope +from adcp.types.domains.core.warning import Warning +from adcp.types.domains.core.warning_resource import WarningAffectedResource +from adcp.types.domains.core.webhook_activity_record import WebhookActivityRecord +from adcp.types.domains.core.webhook_challenge import WebhookChallenge +from adcp.types.domains.core.webhook_challenge_response import WebhookChallengeResponse +from adcp.types.domains.core.wholesale_feed_event import ( AffectedEntityType, AppliesTo, AppliesTo1, @@ -1838,12 +1743,12 @@ WholesaleFeedEvent8, WholesaleFeedEvent9, ) -from adcp.types.generated_poc.core.wholesale_feed_webhook import ( +from adcp.types.domains.core.wholesale_feed_webhook import ( CacheScope, NotificationType, WholesaleFeedWebhook, ) -from adcp.types.generated_poc.core.x_entity_types import XEntityTypes +from adcp.types.domains.core.x_entity_types import XEntityTypes # Explicit exports __all__ = [ diff --git a/src/adcp/types/domains/core/acceptance_policy_profile_ids.py b/src/adcp/types/domains/core/acceptance_policy_profile_ids.py index 92b889e98..dfaf4589f 100644 --- a/src/adcp/types/domains/core/acceptance_policy_profile_ids.py +++ b/src/adcp/types/domains/core/acceptance_policy_profile_ids.py @@ -1,21 +1,27 @@ -"""Types declared by the AdCP ``core/acceptance_policy_profile_ids`` schema. +# generated by datamodel-codegen: +# filename: core/acceptance_policy_profile_ids.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.acceptance_policy_profile_ids import +from adcp.types._scalar import ScalarStr -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import Field, RootModel + + +class AcceptancePolicyProfileId(ScalarStr): + __slots__ = () + _constraints = {'min_length': 1} -from adcp.types.generated_poc.core.acceptance_policy_profile_ids import ( - AcceptancePolicyProfileId, - AcceptancePolicyProfileIds, -) -# Explicit exports -__all__ = ["AcceptancePolicyProfileId", "AcceptancePolicyProfileIds"] +class AcceptancePolicyProfileIds(RootModel[list[AcceptancePolicyProfileId]]): + root: Annotated[ + list[AcceptancePolicyProfileId], + Field( + description='Acceptance-policy profiles from the seller catalog that apply to this product in addition to seller defaults. Profiles compose restrictively; the most restrictive matching disposition wins.', + min_length=1, + title='Acceptance Policy Profile IDs', + ), + ] diff --git a/src/adcp/types/domains/core/account.py b/src/adcp/types/domains/core/account.py index 5ce435fd6..0142c4c4c 100644 --- a/src/adcp/types/domains/core/account.py +++ b/src/adcp/types/domains/core/account.py @@ -1,34 +1,266 @@ -"""Types declared by the AdCP ``core/account`` schema. +# generated by datamodel-codegen: +# filename: core/account.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.account import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat + +from ..enums import account_scope as account_scope_1 +from ..enums import account_status, billing_party, cloud_storage_protocol +from ..enums import payment_terms as payment_terms_1 +from . import account_identity_change, brand_ref, business_entity +from . import ext as ext_1 +from . import notification_config +from . import operator_unit as operator_unit_1 +from . import reporting_delivery_config_state, webhook_activity_record + + +class CreditLimit(AdCPBaseModel): + amount: Annotated[StrictFloat, Field(ge=0.0)] + currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] + + +class Setup(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + url: Annotated[ + AnyUrl | None, + Field( + description='URL where the human can complete the required action (credit application, legal agreement, add funds).' + ), + ] = None + message: Annotated[str, Field(description="Human-readable description of what's needed.")] + expires_at: Annotated[ + AwareDatetime | None, Field(description='When this setup link expires.') + ] = None + + +class GovernanceAgent(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + url: Annotated[AnyUrl, Field(description='Governance agent endpoint URL. Must use HTTPS.')] + + +class Format(StrEnum): + jsonl = 'jsonl' + csv = 'csv' + parquet = 'parquet' + avro = 'avro' + orc = 'orc' + + +class Compression(StrEnum): + gzip = 'gzip' + none = 'none' + + +class ReportingBucket(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + protocol: Annotated[ + cloud_storage_protocol.CloudStorageProtocol, Field(description='Cloud storage protocol') + ] + bucket: Annotated[ + str, + Field( + description='Bucket or container name', + max_length=63, + min_length=3, + pattern='^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$', + ), + ] + prefix: Annotated[ + str | None, + Field( + description='Path prefix within the bucket. Seller appends date-based partitioning beneath this prefix.', + examples=['accounts/pinnacle/adcp', 'reporting/2024'], + max_length=512, + pattern='^[a-zA-Z0-9/_.-]+$', + ), + ] = None + region: Annotated[ + str | None, + Field( + description='Cloud region for the bucket', + examples=['us-east-1', 'europe-west1'], + max_length=64, + pattern='^[a-z0-9-]+$', + ), + ] = None + format: Annotated[ + Format | None, + Field( + description='File format for delivered files. Parquet, Avro, and ORC use internal compression (the top-level compression field is ignored for these formats).' + ), + ] = Format.jsonl + compression: Annotated[ + Compression | None, Field(description='Compression applied to delivered files') + ] = Compression.gzip + file_retention_days: Annotated[ + SchemaInt, + Field( + description='How long reporting files are retained in the bucket before deletion. Buyers must read files within this window. Minimum recommended: 14 days.', + examples=[14, 30, 90], + ge=1, + ), + ] + setup_instructions: Annotated[ + AnyUrl | None, + Field( + description='URL to documentation for configuring buyer read access to this bucket (IAM role, service account, etc.). Operator-facing documentation — buyer agents MUST NOT auto-fetch this URL; surface it to a human operator. If an implementation fetches it (for preview), apply webhook URL SSRF validation and do not pass the fetched content into an LLM context without indirect-prompt-injection guarding. See docs/media-buy/media-buys/optimization-reporting#security-considerations-for-offline-delivery.' + ), + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.account import ( - Account, - Compression, - CreditLimit, - Format, - GovernanceAgent, - ReportingBucket, - Setup, -) - -# Explicit exports -__all__ = [ - "Account", - "Compression", - "CreditLimit", - "Format", - "GovernanceAgent", - "ReportingBucket", - "Setup", -] +class Account(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + account_id: Annotated[str, Field(description='Unique identifier for this account')] + name: Annotated[ + str, Field(description="Human-readable account name (e.g., 'Acme', 'Acme c/o Pinnacle')") + ] + advertiser: Annotated[ + str | None, Field(description='The advertiser whose rates apply to this account') + ] = None + billing_proxy: Annotated[ + str | None, + Field( + description='Optional intermediary who receives invoices on behalf of the advertiser (e.g., agency)' + ), + ] = None + status: Annotated[ + account_status.AccountStatus, + Field( + description='Account lifecycle status. See the Accounts Protocol overview for the operations matrix showing which tasks are permitted in each state.' + ), + ] + brand: Annotated[ + brand_ref.BrandReference | None, + Field(description='Brand reference identifying the advertiser'), + ] = None + operator: Annotated[ + str | None, + Field( + description="Domain of the entity operating this account. When the brand operates directly, this is the brand's domain.", + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] = None + operator_unit: Annotated[ + operator_unit_1.OperatorUnit | None, + Field( + description='Operator-owned business unit, agency seat, or platform account associated with this advertiser account. The id round-trips from the natural key; name is mutable display metadata. This is distinct from account_id, which belongs to the seller/storefront namespace.' + ), + ] = None + revision: Annotated[ + SchemaInt | None, + Field( + description='Monotonically increasing optimistic-concurrency token for this account. Incremented on every persisted settings change, identity-change request, and identity-change disposition; reads, dry runs, validation failures, and exact idempotency replays do not increment it. Pass the latest observed value in a sync_accounts settings-update entry to prevent lost updates.', + ge=1, + ), + ] = None + identity_change: Annotated[ + account_identity_change.AccountIdentityChange | None, + Field( + description='Pending or rejected operator-identity transition. While present, the top-level operator and operator_unit remain the current canonical identity. Re-read list_accounts until the request is applied (canonical fields change and this object disappears) or rejected.' + ), + ] = None + currency: Annotated[ + str | None, + Field( + description="Immutable transaction currency when the seller's advertiser object is currency-bound. Media buys on this account MUST use this currency. Omit when the account selects currency independently per media buy.", + pattern='^[A-Z]{3}$', + ), + ] = None + timezone: Annotated[ + str | None, + Field( + description='Immutable operational timezone for this account, expressed as UTC or an IANA timezone identifier. AdCP 3.2 sellers return it on every account. It is the default calendar-day boundary for account-scoped behavior unless a feature explicitly declares another timezone basis. For buyer-selected account_fixed provisioning it participates in the natural account key.', + min_length=1, + ), + ] = None + billing: Annotated[ + billing_party.BillingParty | None, + Field( + description="Who is invoiced on this account. See billing_entity for the invoiced party's business details." + ), + ] = None + billing_entity: Annotated[ + business_entity.BusinessEntity | None, + Field( + description='Current canonical business entity for the party responsible for payment. Contains the legal name, tax IDs, and address needed for formal B2B invoicing. Corresponds to whoever billing points to (operator, agent, or advertiser). When this account appears in a response, bank details MUST be omitted and the request-only destination_billing_entity MUST NOT be exposed.' + ), + ] = None + destination_billing_entity: Annotated[ + Any | None, + Field(description='Request-only staging field. It MUST NOT appear in account read models.'), + ] = None + rate_card: Annotated[ + str | None, Field(description='Identifier for the rate card applied to this account') + ] = None + payment_terms: Annotated[ + payment_terms_1.PaymentTerms | None, + Field( + description='Payment terms agreed for this account. Binding for all invoices when the account is active.' + ), + ] = None + credit_limit: Annotated[ + CreditLimit | None, Field(description='Maximum outstanding balance allowed') + ] = None + setup: Annotated[ + Setup | None, + Field( + description="Present when status is 'pending_approval'. Contains next steps for completing account activation." + ), + ] = None + account_scope: account_scope_1.AccountScope | None = None + governance_agents: Annotated[ + list[GovernanceAgent] | None, + Field( + description="Governance agent endpoint registered on this account. Exactly one entry per sync_governance's one-agent-per-account invariant. The array shape is preserved for wire compatibility with 3.0; `maxItems: 1` is load-bearing and mirrors the singular `governance_context` on the protocol envelope. Authentication credentials are write-only and not included in responses — use sync_governance to set or update credentials.", + max_length=1, + min_length=1, + ), + ] = None + reporting_bucket: Annotated[ + ReportingBucket | None, + Field( + description="Cloud storage bucket where the seller delivers offline reporting files for this account. Seller provisions a dedicated bucket or a per-account prefix within a shared bucket, and grants the buyer read access out-of-band. Access MUST be scoped at the IAM layer so each account can only read its own prefix — bucket-wide grants are non-compliant even with per-account prefixes. Seller MUST revoke access when the account's status transitions to inactive, suspended, or closed. See security considerations for offline delivery in docs/media-buy/media-buys/optimization-reporting. Only present when the seller supports offline delivery (reporting_delivery_methods includes 'offline' in capabilities)." + ), + ] = None + sandbox: Annotated[ + StrictBool | None, + Field( + description='When true, this is a sandbox account — no real platform calls, no real spend. For account-id namespaces, sandbox accounts are pre-existing test accounts on the platform discovered via list_accounts or supplied out-of-band. For buyer-declared accounts, sandbox is part of the natural key: the same brand/operator pair can have both a production and sandbox account.' + ), + ] = None + notification_configs: Annotated[ + list[notification_config.NotificationConfig] | None, + Field( + description='Account-level webhook subscriptions for creative lifecycle/assignment changes, indicators.changed, account status, durable account-change wake-ups, wholesale feed changes, and reporting.delivery_ready. Buyers manage entries via sync_accounts and verify persisted state on list_accounts. account.change_recorded wakes receivers to drain list_account_changes; reporting.delivery_ready is repaired through get_reporting_status; indicator and assignment payloads are invalidations repaired completely through get_media_buys; list_creatives may provide a bounded reverse projection. Distinct from per-resource push_notification_config. Entries are keyed by account-scoped subscriber_id; credentials are write-only.', + max_length=16, + ), + ] = None + reporting_delivery_configs: Annotated[ + list[reporting_delivery_config_state.ReportingDeliveryConfigurationState] | None, + Field( + description="Resolved durable reporting delivery configurations owned by the authenticated caller for this account. list_accounts MUST expose only the calling principal's set. State and seller-issued destination_ref are returned; credentials and bearer profiles MUST NOT appear. Any setup URL is a secret-free authenticated entry point, not a bearer credential.", + max_length=16, + ), + ] = None + webhook_activity: Annotated[ + list[webhook_activity_record.WebhookActivityRecord] | None, + Field( + description='Recent webhook delivery attempts scoped to this account when the caller requested webhook activity on list_accounts and the seller surfaces the log. Includes account-anchored notifications such as account.status_changed and MAY include other account-level fires relevant to this account. Three-state presence follows the shared webhook_activity[] contract: omitted means unsupported or not requested, [] means supported but no retained fires, non-empty lists recent attempts most-recent-first.', + max_length=200, + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/account_authorization.py b/src/adcp/types/domains/core/account_authorization.py index f5b90b160..f4acc12c1 100644 --- a/src/adcp/types/domains/core/account_authorization.py +++ b/src/adcp/types/domains/core/account_authorization.py @@ -1,22 +1,55 @@ -"""Types declared by the AdCP ``core/account_authorization`` schema. +# generated by datamodel-codegen: +# filename: core/account_authorization.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.account_authorization import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.account_authorization import ( - AccountAuthorization, - AllowedTask, - ScopeName, -) +class AllowedTask(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[a-z][a-z0-9_]*$'} + + +class ScopeName(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^custom:[a-z][a-z0-9_]*$'} + _json_schema_extra = { + 'description': 'Agent-defined scope name, prefixed with `custom:`. Any vendor agent (media-buy seller, signals agent, governance agent, creative agent, brand agent) MAY define custom scopes. Callers MUST NOT assume any semantics from a custom-prefixed scope name.', + 'title': 'CustomScope', + } + -# Explicit exports -__all__ = ["AccountAuthorization", "AllowedTask", "ScopeName"] +class AccountAuthorization(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + allowed_tasks: Annotated[ + list[AllowedTask], + Field( + description='Canonical snake_case task names the caller may invoke against this account (for example get_media_buys, buy_products, accept_proposal, control_media_buy, or sync_creatives). Absence of a task from this list means not permitted and returns SCOPE_INSUFFICIENT. Compact 3.2 tools are authorized by their own names; grants for deprecated get_products/create_media_buy/update_media_buy do not silently transfer across aliases.' + ), + ] + field_scopes: Annotated[ + dict[str, list[str]] | None, + Field( + description='Optional per-task allowlist of request fields the caller may set. Keys are task names (which MUST also appear in allowed_tasks). Values are arrays of top-level request-field paths permitted for that task. When a task appears in field_scopes, requests to that task with any field outside the allowlist MUST be rejected with FIELD_NOT_PERMITTED. Compact product tools use their own task names and top-level fields such as criteria and refinements. Implicit framing fields are always permitted and do NOT need to appear in the allowlist — they identify the resource or shape the call rather than mutating business state. Tasks absent from field_scopes have no field-level restriction beyond what the task schema already enforces.' + ), + ] = None + scope_name: Annotated[ + Literal['attestation_verifier'] | ScopeName | None, + Field( + description='Optional named scope identifier. When present, callers and the vendor agent can reason about the grant by name rather than by enumerating allowed_tasks and field_scopes. Modeled as a discriminated union so code generators produce a literal type for the standard scope(s) and a distinct type for agent-defined values — this prevents a typo of `attestation_verifier` from being silently accepted as a custom scope. Agent-defined scope names MUST use a `custom:` prefix to avoid collision with future standard scopes. The prefix is protocol-neutral: a signals agent, a governance agent, or a creative agent defines custom scopes the same way a media-buy sales agent does.' + ), + ] = None + read_only: Annotated[ + StrictBool | None, + Field( + description='Convenience flag. When true, the caller is permitted only non-mutating tasks. Sellers MUST reject any mutation from a read-only caller with READ_ONLY_SCOPE. For the AdCP 3.2 product split, read-only permits list_products but rejects request_proposals, refine_proposals, and decline_proposals; a legacy get_products call is permitted only when the seller can guarantee the selected arm is a synchronous side-effect-free read. Sellers MAY omit this field; omission is equivalent to `false`. Callers MUST NOT infer read-only from `allowed_tasks` alone — the seller MUST set this explicitly when it applies.' + ), + ] = False diff --git a/src/adcp/types/domains/core/account_change.py b/src/adcp/types/domains/core/account_change.py index 196a25076..2150fedd6 100644 --- a/src/adcp/types/domains/core/account_change.py +++ b/src/adcp/types/domains/core/account_change.py @@ -1,40 +1,240 @@ -"""Types declared by the AdCP ``core/account_change`` schema. +# generated by datamodel-codegen: +# filename: core/account_change.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.account_change import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from adcp.types._scalar import ScalarStr +from pydantic import AwareDatetime, ConfigDict, Field, StrictBool + +from . import ext as ext_1 + + +class Resource(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + type: Annotated[ + str, + Field( + description='Open resource-type name such as account, media_buy, package, creative, creative_assignment, delivery_report, audience, event_source, catalog, or account_financials.', + max_length=100, + min_length=1, + pattern='^[a-z][a-z0-9_.-]{0,99}$', + ), + ] + account_id: Annotated[ + str, + Field( + description='Seller-assigned account containing the resource.', + max_length=255, + min_length=1, + ), + ] + resource_id: Annotated[ + str, + Field( + description='Canonical identifier for the resource within its type. For an account change this equals account_id.', + max_length=255, + min_length=1, + ), + ] + parent_ids: Annotated[ + dict[str, str] | None, + Field( + description='Additional canonical parent identifiers needed to repair a nested resource, for example media_buy_id and package_id for a creative assignment. Keys and values MUST NOT contain sensitive payload data.', + max_length=12, + ), + ] = None + + +class Kind(StrEnum): + adcp = 'adcp' + seller_operator = 'seller_operator' + seller_system = 'seller_system' + connected_platform = 'connected_platform' + unknown = 'unknown' + + +class Origin(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + kind: Kind + connection_id: Annotated[ + str | None, + Field( + description='Opaque, non-secret reference to the connected source when safe for this caller.', + max_length=255, + ), + ] = None + + +class ChangedPath(ScalarStr): + __slots__ = () + _constraints = {'max_length': 500, 'pattern': '^(|/.*)$'} + + +class Task(StrEnum): + list_accounts = 'list_accounts' + get_media_buys = 'get_media_buys' + get_media_buy_delivery = 'get_media_buy_delivery' + list_creatives = 'list_creatives' + sync_audiences = 'sync_audiences' + sync_event_sources = 'sync_event_sources' + sync_catalogs = 'sync_catalogs' + get_account_financials = 'get_account_financials' + list_products = 'list_products' + get_signals = 'get_signals' + + +class UnavailableReason(StrEnum): + deleted = 'deleted' + purged = 'purged' + legal_erasure = 'legal_erasure' + access_revoked = 'access_revoked' + other = 'other' + + +class Repair(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + task: Annotated[ + Task, + Field( + description='Allowlisted authoritative read task. This is a repair hint, never an instruction to dispatch dynamically. The buyer constructs and validates the request locally from the authenticated feed account and resource identity.' + ), + ] + available: Annotated[ + StrictBool | None, + Field( + description='False when deletion or compelled erasure makes the resource unavailable on the repair read.' + ), + ] = True + unavailable_reason: UnavailableReason | None = None + + +class Type(StrEnum): + principal = 'principal' + operator = 'operator' + system = 'system' + platform = 'platform' + unknown = 'unknown' + + +class Actor(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + type: Type + id: Annotated[ + str | None, Field(description='Opaque, redaction-safe actor reference.', max_length=255) + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.account_change import ( - AccountChange, - Actor, - ChangedPath, - Kind, - Origin, - Repair, - Resource, - Task, - Type, - UnavailableReason, -) - -# Explicit exports -__all__ = [ - "AccountChange", - "Actor", - "ChangedPath", - "Kind", - "Origin", - "Repair", - "Resource", - "Task", - "Type", - "UnavailableReason", -] +class AccountChange(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + change_id: Annotated[ + str, + Field( + description='Stable seller-generated identifier for this logical change. Retries and notification re-emissions reuse this identifier.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] + recorded_at: Annotated[ + AwareDatetime, + Field( + description='Time the seller committed or durably observed the change. Feed order is defined by the cursor, not by this timestamp.' + ), + ] + occurred_at: Annotated[ + AwareDatetime | None, + Field( + description='Upstream business time when the change occurred, only when the seller can establish it reliably.' + ), + ] = None + batch_id: Annotated[ + str | None, + Field( + description='Optional stable identifier grouping records produced by one committed operation or one external-source ingestion batch. Each independently repairable authoritative identity still receives its own record; batch_id does not change cursor ordering or notification identity.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] = None + resource: Annotated[ + Resource, + Field( + description='Stable identity of the changed resource. Resource types are open for forward compatibility. account_id is always present; resource_id identifies the changed entity and parent_ids supplies any IDs needed to disambiguate nested resources.' + ), + ] + action: Annotated[ + str, + Field( + description='Material change action. Standard values are created, discovered, updated, status_changed, linked, unlinked, deleted, and purged. Future standard or vendor-namespaced values are allowed; receivers MUST treat unknown values as generic invalidations.', + max_length=100, + min_length=1, + pattern='^[a-z][a-z0-9_.-]{0,99}$', + ), + ] + origin: Annotated[ + Origin, + Field( + description='Server-derived origin classification. The seller MUST NOT trust caller-supplied origin or actor claims.' + ), + ] + resource_revision: Annotated[ + SchemaInt | str | None, + Field( + description='Post-change revision exposed by the repair read, when that resource family defines one.' + ), + ] = None + changed_paths: Annotated[ + list[ChangedPath] | None, + Field( + description='Bounded set of RFC 6901 JSON Pointers naming material fields that changed. Values are intentionally omitted.', + max_length=64, + ), + ] = None + repair: Annotated[ + Repair, + Field( + description='Authoritative AdCP read the receiver uses to reconcile current state. The buyer constructs safe request arguments from the structured resource identity. A deleted or legally purged resource may instead declare unavailable with a categorical reason.' + ), + ] + actor: Annotated[ + Actor | None, + Field( + description='Optional privacy-safe actor classification. Sellers MUST omit direct personal identifiers unless the authenticated caller is authorized for them.' + ), + ] = None + reason: Annotated[ + str | None, + Field( + description='Short machine-readable reason code, when available.', + max_length=100, + pattern='^[a-z][a-z0-9_.-]{0,99}$', + ), + ] = None + summary: Annotated[ + str | None, + Field( + description='Optional brief, untrusted human-readable summary. MUST NOT contain secrets or sensitive payload data.', + max_length=500, + ), + ] = None + ext: Annotated[ + ext_1.ExtensionObject | None, + Field( + description='Bounded vendor extensions. The entire encoded change record, including extensions, MUST NOT exceed 64 KiB and remains subject to the same secret/PII prohibitions.' + ), + ] = None diff --git a/src/adcp/types/domains/core/account_change_recorded_webhook.py b/src/adcp/types/domains/core/account_change_recorded_webhook.py index 22f5e4537..8cea2f4b8 100644 --- a/src/adcp/types/domains/core/account_change_recorded_webhook.py +++ b/src/adcp/types/domains/core/account_change_recorded_webhook.py @@ -1,21 +1,74 @@ -"""Types declared by the AdCP ``core/account_change_recorded_webhook`` schema. +# generated by datamodel-codegen: +# filename: core/account_change_recorded_webhook.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.account_change_recorded_webhook import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import ext as ext_1 + + +class Resource(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + type: Annotated[str, Field(max_length=100, min_length=1, pattern='^[a-z][a-z0-9_.-]{0,99}$')] + resource_id: Annotated[str, Field(max_length=255, min_length=1)] + parent_ids: Annotated[dict[str, str] | None, Field(max_length=12)] = None -from adcp.types.generated_poc.core.account_change_recorded_webhook import ( - AccountChangeRecordedWebhook, - Resource, -) -# Explicit exports -__all__ = ["AccountChangeRecordedWebhook", "Resource"] +class AccountChangeRecordedWebhook(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + idempotency_key: Annotated[ + str, + Field( + description='Sender-generated delivery key stable across retries of one fire. Deliberate re-emission uses a new key.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + notification_id: Annotated[ + str, + Field( + description='Logical notification identifier. Always equals change_id; retries and deliberate re-emissions of the same change retain it.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] + notification_type: Literal['account.change_recorded'] = 'account.change_recorded' + fired_at: AwareDatetime + subscriber_id: Annotated[ + str, Field(max_length=64, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,64}$') + ] + account_id: Annotated[str, Field(max_length=255, min_length=1)] + change_id: Annotated[ + str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') + ] + recorded_at: AwareDatetime + resource: Annotated[ + Resource, + Field(description='Resource identity copied from the corresponding account change record.'), + ] + action: Annotated[str, Field(max_length=100, min_length=1)] + through_cursor: Annotated[ + str | None, + Field( + description='Optional advisory checkpoint at or after this change. It is a drain target, not a cursor the receiver may install without reading every intervening page.', + max_length=4096, + min_length=1, + ), + ] = None + ext: Annotated[ + ext_1.ExtensionObject | None, + Field( + description='Bounded vendor extensions subject to the same secret/PII prohibitions as the base payload.' + ), + ] = None diff --git a/src/adcp/types/domains/core/account_identity_change.py b/src/adcp/types/domains/core/account_identity_change.py index e6705ea64..dea0f8d79 100644 --- a/src/adcp/types/domains/core/account_identity_change.py +++ b/src/adcp/types/domains/core/account_identity_change.py @@ -1,22 +1,60 @@ -"""Types declared by the AdCP ``core/account_identity_change`` schema. +# generated by datamodel-codegen: +# filename: core/account_identity_change.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.account_identity_change import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import operator_identity + + +class AccountIdentityChange1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + status: Annotated[ + Literal['pending_approval'], + Field(description='The seller is reviewing the requested transition.'), + ] = 'pending_approval' + requested_operator_identity: Annotated[ + operator_identity.OperatorIdentity, + Field(description='Complete desired operator identity submitted by the buyer.'), + ] + requested_at: Annotated[ + AwareDatetime | None, Field(description='When the seller recorded the request.') + ] = None + + +class AccountIdentityChange2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + status: Annotated[ + Literal['rejected'], + Field( + description='The seller rejected the transition and retained the current canonical identity.' + ), + ] = 'rejected' + requested_operator_identity: Annotated[ + operator_identity.OperatorIdentity, + Field(description='Complete desired operator identity submitted by the buyer.'), + ] + requested_at: Annotated[ + AwareDatetime | None, Field(description='When the seller recorded the request.') + ] = None + reason: Annotated[ + str, Field(description='Human-readable rejection reason.', max_length=1000, min_length=1) + ] -from adcp.types.generated_poc.core.account_identity_change import ( - AccountIdentityChange, - AccountIdentityChange1, - AccountIdentityChange2, -) -# Explicit exports -__all__ = ["AccountIdentityChange", "AccountIdentityChange1", "AccountIdentityChange2"] +AccountIdentityChange = Annotated[ + AccountIdentityChange1 | AccountIdentityChange2, + Field( + description="A requested operator-identity change that has not been applied. The account's top-level operator and operator_unit remain canonical until this object disappears after approval and the canonical fields advance. Rejected changes remain visible until replaced by a later request so buyers polling list_accounts can observe the outcome.", + title='Account Identity Change', + ), +] diff --git a/src/adcp/types/domains/core/account_identity_change_preview.py b/src/adcp/types/domains/core/account_identity_change_preview.py index 700c1d25c..e614355c3 100644 --- a/src/adcp/types/domains/core/account_identity_change_preview.py +++ b/src/adcp/types/domains/core/account_identity_change_preview.py @@ -1,44 +1,116 @@ -"""Types declared by the AdCP ``core/account_identity_change_preview`` schema. +# generated by datamodel-codegen: +# filename: core/account_identity_change_preview.json +# timestamp: 2026-10-04T00:53:27+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.account_identity_change_preview import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field, RootModel + +from . import operator_identity + + +class Blocker(ScalarStr): + __slots__ = () + _constraints = {'max_length': 1000, 'min_length': 1} + + +class Area(StrEnum): + account_id = 'account_id' + media_buys = 'media_buys' + reporting = 'reporting' + approval = 'approval' + billing = 'billing' + grants = 'grants' + other = 'other' + + +class Effect(StrEnum): + preserved = 'preserved' + revalidation_required = 'revalidation_required' + revoke_and_regrant = 'revoke_and_regrant' + blocked = 'blocked' + + +class Impact(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + area: Area + effect: Effect + reason: Annotated[str | None, Field(max_length=1000, min_length=1)] = None + + +class Effect1(StrEnum): + preserved = 'preserved' + revalidation_required = 'revalidation_required' + revoke_and_regrant = 'revoke_and_regrant' + + +class NonblockingImpact(Impact): + effect: Effect1 | None = None + + +class NonblockingImpacts(RootModel[list[NonblockingImpact]]): + root: Annotated[ + list[NonblockingImpact], + Field( + description='Account areas the seller evaluated for continuity or reauthorization. Non-blocked outcomes cannot carry a blocked effect.', + max_length=16, + min_length=1, + ), + ] + + +class BlockedImpacts(RootModel[list[Impact]]): + root: Annotated[ + list[Impact], + Field( + description='Account areas evaluated for a blocked change. At least one impact identifies the blocking area.', + max_length=16, + min_length=1, + ), + ] + + +class AccountIdentityChangePreview1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + outcome: Literal['would_apply'] = 'would_apply' + requested_operator_identity: operator_identity.OperatorIdentity + impacts: NonblockingImpacts + + +class AccountIdentityChangePreview2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + outcome: Literal['would_require_approval'] = 'would_require_approval' + requested_operator_identity: operator_identity.OperatorIdentity + impacts: NonblockingImpacts + + +class AccountIdentityChangePreview3(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + outcome: Literal['blocked'] = 'blocked' + requested_operator_identity: operator_identity.OperatorIdentity + impacts: BlockedImpacts + blockers: Annotated[list[Blocker], Field(min_length=1)] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.account_identity_change_preview import ( - AccountIdentityChangePreview, - AccountIdentityChangePreview1, - AccountIdentityChangePreview2, - AccountIdentityChangePreview3, - Area, - BlockedImpacts, - Blocker, - Effect, - Effect1, - Impact, - NonblockingImpact, - NonblockingImpacts, -) - -# Explicit exports -__all__ = [ - "AccountIdentityChangePreview", - "AccountIdentityChangePreview1", - "AccountIdentityChangePreview2", - "AccountIdentityChangePreview3", - "Area", - "BlockedImpacts", - "Blocker", - "Effect", - "Effect1", - "Impact", - "NonblockingImpact", - "NonblockingImpacts", +AccountIdentityChangePreview = Annotated[ + AccountIdentityChangePreview1 + | AccountIdentityChangePreview2 + | AccountIdentityChangePreview3, + Field( + description='Non-persisted dry-run result for a requested operator-identity change. Canonical account fields and revision remain unchanged.', + title='Account Identity Change Preview', + ), ] diff --git a/src/adcp/types/domains/core/account_ref.py b/src/adcp/types/domains/core/account_ref.py index f37710441..abb742bcc 100644 --- a/src/adcp/types/domains/core/account_ref.py +++ b/src/adcp/types/domains/core/account_ref.py @@ -1,22 +1,96 @@ -"""Types declared by the AdCP ``core/account_ref`` schema. +# generated by datamodel-codegen: +# filename: core/account_ref.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.account_ref import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import brand_ref +from . import operator_unit as operator_unit_1 + + +class AccountReference1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + account_id: Annotated[ + str, + Field( + description="Seller-assigned account identifier. For upstream-managed account namespaces, this value comes from list_accounts; for seller-defined namespaces without a list_accounts surface, it is supplied out-of-band. Buyer-declared account sellers MAY echo account_id from sync_accounts as an internal handle, but they MUST continue accepting the account's current natural-key AccountRef on subsequent calls. A former key tombstoned by identity reconciliation returns ACCOUNT_MOVED to authorized callers." + ), + ] + + +class AccountReference2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + brand: Annotated[ + brand_ref.BrandReference, Field(description='Brand reference identifying the advertiser') + ] + operator: Annotated[ + str, + Field( + description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] + operator_unit: Annotated[ + operator_unit_1.OperatorUnit | None, + Field( + description='Optional operator-owned business unit, agency seat, or platform account. Only id participates in the natural account key; name is mutable display metadata.' + ), + ] = None + currency: Annotated[ + str | None, + Field( + description="Immutable ISO 4217 transaction currency when the seller's advertiser object is currency-bound. When present, this is part of the natural account key and media buys on the account MUST use it. Omit when currency is selected independently per media buy.", + pattern='^[A-Z]{3}$', + ), + ] = None + timezone: Annotated[ + str | None, + Field( + description='Immutable account timezone. Include it in the natural key when get_adcp_capabilities.account.timezone declares account_fixed with buyer_selected; omit it for seller_fixed or seller_assigned accounts.', + min_length=1, + ), + ] = None + sandbox: Annotated[ + StrictBool | None, + Field( + description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' + ), + ] = False -from adcp.types.generated_poc.core.account_ref import ( - AccountReference, - AccountReference1, - AccountReference2, -) -# Explicit exports -__all__ = ["AccountReference", "AccountReference1", "AccountReference2"] +AccountReference = Annotated[ + AccountReference1 | AccountReference2, + Field( + description="Reference to an advertiser account by seller-assigned ID or natural key. account_id is the seller/storefront handle returned by list_accounts. The natural key is brand + operator + optional operator_unit.id + optional fixed currency + optional buyer-selected account timezone + sandbox; it describes the advertiser object the seller resolves or provisions. After a capability-gated identity rekey, the former natural key is tombstoned and returns ACCOUNT_MOVED to authorized callers rather than resolving or provisioning another account. brand.countries qualifies the advertiser's geographic identity. operator_unit identifies the operator-owned buying seat or business unit and is distinct from the seller account. operator_unit.name is display metadata and does not participate in identity. Use account_id when the seller or upstream platform owns the canonical account namespace; use the natural key for buyer-declared accounts (require_operator_auth: false).", + examples=[ + {'account_id': 'acc_acme_001'}, + {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, + { + 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, + 'operator': 'pinnacle-media.com', + }, + { + 'brand': {'domain': 'nova-athletics.example', 'countries': ['NL']}, + 'operator': 'nova-athletics.example', + 'operator_unit': {'id': '234284238', 'name': 'Nova EMEA'}, + 'currency': 'EUR', + 'timezone': 'Europe/Amsterdam', + }, + { + 'brand': {'domain': 'acme-corp.com'}, + 'operator': 'acme-corp.com', + 'sandbox': True, + }, + ], + title='Account Reference', + ), +] diff --git a/src/adcp/types/domains/core/account_status_changed_webhook.py b/src/adcp/types/domains/core/account_status_changed_webhook.py index d639d3add..4e3fbf2e9 100644 --- a/src/adcp/types/domains/core/account_status_changed_webhook.py +++ b/src/adcp/types/domains/core/account_status_changed_webhook.py @@ -1,22 +1,132 @@ -"""Types declared by the AdCP ``core/account_status_changed_webhook`` schema. +# generated by datamodel-codegen: +# filename: core/account_status_changed_webhook.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.account_status_changed_webhook import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import account_status +from . import ext as ext_1 + + +class ReasonCode(StrEnum): + seller_approved = 'seller_approved' + seller_rejected = 'seller_rejected' + payment_required = 'payment_required' + credit_limit_reached = 'credit_limit_reached' + funds_depleted = 'funds_depleted' + setup_required = 'setup_required' + policy_review = 'policy_review' + policy_violation = 'policy_violation' + compliance_hold = 'compliance_hold' + buyer_requested = 'buyer_requested' + seller_closed = 'seller_closed' + manual_update = 'manual_update' + other = 'other' + + +class Setup(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + message: Annotated[ + str, + Field( + description='Human-readable setup message safe to fan out to every active account notification subscriber.' + ), + ] + expires_at: Annotated[ + AwareDatetime | None, Field(description='When the setup action or hint expires, if known.') + ] = None -from adcp.types.generated_poc.core.account_status_changed_webhook import ( - AccountStatusChangedWebhook, - ReasonCode, - Setup, -) -# Explicit exports -__all__ = ["AccountStatusChangedWebhook", "ReasonCode", "Setup"] +class AccountStatusChangedWebhook(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + idempotency_key: Annotated[ + str, + Field( + description='Sender-generated key stable across retries of the same fire. Sellers MUST generate a cryptographically random value (UUID v4 recommended) per distinct fire and reuse it on every retry of the same fire. Receivers MUST dedupe by this key, scoped to the authenticated sender identity.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + notification_id: Annotated[ + str, + Field( + description='Stable identifier for this logical account status transition. Stability key is (account_id, previous_status, status, observed_at): retries and re-emissions of the same transition reuse the id under a new idempotency_key, while a later transition cycle receives a new id.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] + notification_type: Annotated[ + Literal['account.status_changed'], + Field( + description="Fixed notification type discriminator. Matches the value registered on the subscriber's event_types." + ), + ] = 'account.status_changed' + fired_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the seller initiated this fire. Distinct from observed_at, which is when the seller recorded the account transition.' + ), + ] + subscriber_id: Annotated[ + str, + Field( + description="Identifies which sync_accounts.accounts[].notification_configs[] entry is receiving this fire. Echoed verbatim from the entry's subscriber_id.", + max_length=64, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,64}$', + ), + ] + account_id: Annotated[ + str, + Field( + description='Seller-assigned account identifier whose status changed. Sellers MUST assign account_id before activating an account.status_changed subscriber, including accounts still pending external approval, so later pending_approval -> rejected or pending_approval -> active transitions can be delivered and repaired through list_accounts.' + ), + ] + previous_status: Annotated[ + account_status.AccountStatus, + Field(description='Account status immediately before this transition.'), + ] + status: Annotated[ + account_status.AccountStatus, + Field( + description='Account status after this transition. Receivers SHOULD treat this as advisory and re-read list_accounts for the authoritative account snapshot.' + ), + ] + observed_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the seller recorded the account status transition. Used in the notification_id stability key; this is seller wall time for the transition, not webhook fire time.' + ), + ] + reason_code: Annotated[ + ReasonCode, + Field( + description='Machine-readable reason for the transition. This is advisory routing/debug metadata; receivers MUST re-read list_accounts rather than relying on the reason code as the source of truth.' + ), + ] + reason_detail: Annotated[ + str | None, + Field( + description='Optional short human-readable detail. Treat as untrusted text. Sellers MUST NOT include secrets, setup tokens, internal stack traces, or regulated financial details.', + max_length=500, + ), + ] = None + setup: Annotated[ + Setup | None, + Field( + description='Reduced setup hint when the new status requires human action. This block intentionally omits setup.url; receivers fetch the current setup URL from list_accounts if the caller is authorized to see it.' + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/account_timezone_capability.py b/src/adcp/types/domains/core/account_timezone_capability.py index cd3a7ba50..bf7b14481 100644 --- a/src/adcp/types/domains/core/account_timezone_capability.py +++ b/src/adcp/types/domains/core/account_timezone_capability.py @@ -1,23 +1,59 @@ -"""Types declared by the AdCP ``core/account_timezone_capability`` schema. +# generated by datamodel-codegen: +# filename: core/account_timezone_capability.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.account_timezone_capability import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.account_timezone_capability import ( - AccountSelection, - AccountTimezoneCapability, - Mode, - SupportedTimezone, -) +class Mode(StrEnum): + seller_fixed = 'seller_fixed' + account_fixed = 'account_fixed' + + +class AccountSelection(StrEnum): + seller_assigned = 'seller_assigned' + buyer_selected = 'buyer_selected' + + +class SupportedTimezone(ScalarStr): + __slots__ = () + _constraints = {'min_length': 1} + -# Explicit exports -__all__ = ["AccountSelection", "AccountTimezoneCapability", "Mode", "SupportedTimezone"] +class AccountTimezoneCapability(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + mode: Annotated[ + Mode, + Field( + description='seller_fixed means every account uses fixed_timezone. account_fixed means each account has an immutable timezone returned on Account and selected or assigned during account establishment.' + ), + ] + fixed_timezone: Annotated[ + str | None, + Field( + description='Seller-wide timezone used by every account. Required only for seller_fixed. Use UTC or an IANA timezone identifier.', + min_length=1, + ), + ] = None + account_selection: Annotated[ + AccountSelection | None, + Field( + description='How an account_fixed timezone is established. seller_assigned covers an existing upstream account or seller onboarding choice; buyer_selected requires timezone in buyer-declared sync_accounts provisioning.' + ), + ] = None + supported_timezones: Annotated[ + list[SupportedTimezone] | None, + Field( + description='Exact timezone values accepted during buyer-selected account provisioning. Required when account_selection is buyer_selected so buyers can validate the choice before sync_accounts.', + min_length=1, + ), + ] = None diff --git a/src/adcp/types/domains/core/account_with_authorization.py b/src/adcp/types/domains/core/account_with_authorization.py index 26b7b016a..7a56c8547 100644 --- a/src/adcp/types/domains/core/account_with_authorization.py +++ b/src/adcp/types/domains/core/account_with_authorization.py @@ -1,18 +1,21 @@ -"""Types declared by the AdCP ``core/account_with_authorization`` schema. +# generated by datamodel-codegen: +# filename: core/account_with_authorization.json +# timestamp: 2026-06-05T16:54:33+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.account_with_authorization import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import account_authorization +from .account import Account -from adcp.types.generated_poc.core.account_with_authorization import AccountWithAuthorization -# Explicit exports -__all__ = ["AccountWithAuthorization"] +class AccountWithAuthorization(Account): + authorization: Annotated[ + account_authorization.AccountAuthorization | None, + Field( + description="Optional. The caller's scope grant against this account. Vendor agents of any type (media-buy, signals, governance, creative, brand) that support scope introspection SHOULD populate this so callers can preempt SCOPE_INSUFFICIENT / FIELD_NOT_PERMITTED errors rather than discovering scope by trial and error. Media-buy sales agents claiming the `attestation_verifier` standard scope MUST populate it. Absence means the vendor agent does not advertise introspectable scope for this account — callers MUST NOT infer access from absence, and fall back to error-driven discovery via the RBAC error codes." + ), + ] = None diff --git a/src/adcp/types/domains/core/activation_key.py b/src/adcp/types/domains/core/activation_key.py index 40d2c7596..f1f666888 100644 --- a/src/adcp/types/domains/core/activation_key.py +++ b/src/adcp/types/domains/core/activation_key.py @@ -1,22 +1,40 @@ -"""Types declared by the AdCP ``core/activation_key`` schema. +# generated by datamodel-codegen: +# filename: core/activation_key.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.activation_key import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.activation_key import ( - ActivationKey, - ActivationKey1, - ActivationKey2, -) +class ActivationKey1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + type: Annotated[Literal['segment_id'], Field(description='Segment ID based targeting')] = 'segment_id' + segment_id: Annotated[ + str, + Field(description='The platform-specific segment identifier to use in campaign targeting'), + ] + + +class ActivationKey2(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + type: Annotated[Literal['key_value'], Field(description='Key-value pair based targeting')] = 'key_value' + key: Annotated[str, Field(description='The targeting parameter key')] + value: Annotated[str, Field(description='The targeting parameter value')] + -# Explicit exports -__all__ = ["ActivationKey", "ActivationKey1", "ActivationKey2"] +ActivationKey = Annotated[ + ActivationKey1 | ActivationKey2, + Field( + description="Universal identifier for using a signal on a destination platform. Can be either a segment ID or a key-value pair depending on the platform's targeting mechanism.", + discriminator='type', + title='Activation Key', + ), +] diff --git a/src/adcp/types/domains/core/ad_inventory_config.py b/src/adcp/types/domains/core/ad_inventory_config.py index e4ffefa19..40c8df5df 100644 --- a/src/adcp/types/domains/core/ad_inventory_config.py +++ b/src/adcp/types/domains/core/ad_inventory_config.py @@ -1,18 +1,41 @@ -"""Types declared by the AdCP ``core/ad_inventory_config`` schema. +# generated by datamodel-codegen: +# filename: core/ad_inventory_config.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.ad_inventory_config import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictBool -from adcp.types.generated_poc.core.ad_inventory_config import AdInventoryConfiguration -# Explicit exports -__all__ = ["AdInventoryConfiguration"] +class AdInventoryConfiguration(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + expected_breaks: Annotated[ + SchemaInt, Field(description='Number of planned ad breaks in the installment', ge=0) + ] + total_ad_seconds: Annotated[ + SchemaInt | None, Field(description='Total seconds of ad time across all breaks', ge=0) + ] = None + max_ad_duration_seconds: Annotated[ + SchemaInt | None, + Field( + description='Maximum duration in seconds for a single ad within a break. Buyers need this to know whether their creative fits.', + ge=1, + ), + ] = None + unplanned_breaks: Annotated[ + StrictBool | None, + Field( + description='Whether ad breaks are dynamic and driven by live conditions (sports timeouts, election coverage). When false, all breaks are pre-defined.' + ), + ] = None + supported_formats: Annotated[ + list[str] | None, + Field( + description="Ad format types supported in breaks (e.g., 'video', 'audio', 'display')" + ), + ] = None diff --git a/src/adcp/types/domains/core/agent_encryption_key.py b/src/adcp/types/domains/core/agent_encryption_key.py index 801fe6600..f9dbda1a1 100644 --- a/src/adcp/types/domains/core/agent_encryption_key.py +++ b/src/adcp/types/domains/core/agent_encryption_key.py @@ -1,18 +1,31 @@ -"""Types declared by the AdCP ``core/agent_encryption_key`` schema. +# generated by datamodel-codegen: +# filename: core/agent_encryption_key.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.agent_encryption_key import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -from adcp.types.generated_poc.core.agent_encryption_key import AgentEncryptionKey -# Explicit exports -__all__ = ["AgentEncryptionKey"] +class AgentEncryptionKey(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kid: Annotated[ + str, + Field( + description='Key identifier. Opaque — MUST NOT encode geographic or deployment information.', + max_length=8, + ), + ] + kty: Annotated[Literal['OKP'], Field(description='JWK key type. Must be OKP for X25519.')] = 'OKP' + crv: Annotated[ + Literal['X25519'], Field(description='Curve name. Must be X25519 for TMPX encryption.') + ] = 'X25519' + use: Annotated[ + Literal['enc'], Field(description='JWK use value. Must be enc for encryption keys.') + ] = 'enc' + x: Annotated[str, Field(description='Base64url-encoded X25519 public key (32 bytes).')] diff --git a/src/adcp/types/domains/core/agent_notification_config.py b/src/adcp/types/domains/core/agent_notification_config.py index d95956840..af2f567ad 100644 --- a/src/adcp/types/domains/core/agent_notification_config.py +++ b/src/adcp/types/domains/core/agent_notification_config.py @@ -1,21 +1,81 @@ -"""Types declared by the AdCP ``core/agent_notification_config`` schema. +# generated by datamodel-codegen: +# filename: core/agent_notification_config.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.agent_notification_config import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import auth_scheme, notification_type +from . import ext as ext_1 + + +class Authentication(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + schemes: Annotated[list[auth_scheme.AuthenticationScheme], Field(max_length=1, min_length=1)] + credentials: Annotated[ + str | None, + Field( + description='Credentials for the legacy scheme. Bearer: token. HMAC-SHA256: shared secret. Minimum 32 characters. Exchanged out-of-band during onboarding. Write-only.', + min_length=32, + ), + ] = None -from adcp.types.generated_poc.core.agent_notification_config import ( - AgentNotificationConfig, - Authentication, -) -# Explicit exports -__all__ = ["AgentNotificationConfig", "Authentication"] +class AgentNotificationConfig(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + subscriber_id: Annotated[ + str, + Field( + description="Buyer- or registry-supplied identifier for this agent-level subscription endpoint. This is the stable logical key within the authenticated caller's agent-level notification config set: re-sending the same subscriber_id replaces that caller's subscriber URL, event_types, authentication selector, and active flag rather than creating a duplicate. Echoed on every webhook payload so multi-subscriber consumers can route by endpoint. MUST be unique within the submitted `notification_configs[]` array.", + max_length=64, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,64}$', + ), + ] + url: Annotated[ + AnyUrl, + Field( + description='Webhook endpoint URL. Same wire contract as `push-notification-config.url` and account-level `notification-config.url`: `format: "uri"`, no destination-port allowlist enforced by the protocol, SSRF protection via the IP-range check defined in docs/building/by-layer/L1/security.mdx#webhook-url-validation-ssrf. Sellers MUST validate URL syntax, HTTPS usage, hostname normalization, and reserved-range rejection when writing any config, including `active: false` configs. Sellers MUST complete an activation challenge or equivalent proof-of-control before treating a new or changed active subscriber as active.' + ), + ] + event_types: Annotated[ + list[notification_type.NotificationType], + Field( + description="Notification types this subscriber wishes to receive on the registered `url`. Caller-anchored types (`capabilities.changed`, `principal.changed`) always fire principal-wide. Account-anchored types additionally require the explicit all_authorized_accounts acknowledgment. Account-anchored types (such as `creative.status_changed` or `account.change_recorded`) are also accepted here: each fire then covers only accounts the authenticated caller is authorized for at each delivery attempt — the subscription is standing, authorization is evaluated per attempt including retries, and losing account authority both stops new fires and suppresses queued retries carrying that account's data. Media-buy-anchored types are rejected on this surface; their per-buy cadence configuration stays on `push_notification_config`. Caller-level and account-level subscriptions to the same event are independent — both fire, and receivers dedupe by the event's logical `notification_id`.", + min_length=1, + ), + ] + all_authorized_accounts: Annotated[ + StrictBool | None, + Field( + description="Explicit scope acknowledgment required whenever event_types includes any account-anchored type: true states that this subscriber intentionally receives those events for every account the principal is authorized for at each delivery attempt. Subscribing an endpoint to all accounts is never implicit. Authorization is evaluated per delivery attempt, including retries: losing authority for an account suppresses queued retries carrying that account's data." + ), + ] = None + include_future_event_types: Annotated[ + StrictBool | None, + Field( + description='When true, the seller also fires caller-eligible notification types added to the enum by later AdCP versions — but only types classified invalidation-only, whose payloads carry identifiers and a repair pointer rather than domain data. Payload-bearing types always require explicit enumeration in event_types; this flag never silently opts a caller into more-sensitive payloads. There is deliberately no wildcard event type. Receivers setting this MUST tolerate unknown notification_type values.' + ), + ] = False + authentication: Annotated[ + Authentication | None, + Field( + deprecated=True, + description="Legacy authentication selector. Same precedence and semantics as `push-notification-config.authentication` and account-level `notification-config.authentication`: presence opts the seller into Bearer or HMAC-SHA256 signing; absence selects the default RFC 9421 webhook profile keyed off the seller's brand.json `agents[]` JWKS. Deprecated; removed in AdCP 4.0. Credentials are write-only and MUST NOT be echoed on reads.", + ), + ] = None + active: Annotated[ + StrictBool | None, + Field( + description='When false, the seller persists the configuration but suppresses fires. Use to pause a subscriber without losing the registration. Paused configs may skip only the outbound proof challenge while inactive; sellers MUST still enforce URL parsing, HTTPS, hostname normalization, and reserved-range rejection at write time. Reactivation requires full SSRF validation with connect pinning plus proof-of-control for any tuple without current valid proof.' + ), + ] = True + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/agent_notification_config_state.py b/src/adcp/types/domains/core/agent_notification_config_state.py index c359ed4f9..68353b331 100644 --- a/src/adcp/types/domains/core/agent_notification_config_state.py +++ b/src/adcp/types/domains/core/agent_notification_config_state.py @@ -1,21 +1,41 @@ -"""Types declared by the AdCP ``core/agent_notification_config_state`` schema. +# generated by datamodel-codegen: +# filename: core/agent_notification_config_state.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.agent_notification_config_state import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import auth_scheme, notification_type +from . import ext as ext_1 + + +class Authentication(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + schemes: Annotated[list[auth_scheme.AuthenticationScheme], Field(max_length=1, min_length=1)] -from adcp.types.generated_poc.core.agent_notification_config_state import ( - AgentNotificationConfigState, - Authentication, -) -# Explicit exports -__all__ = ["AgentNotificationConfigState", "Authentication"] +class AgentNotificationConfigState(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + subscriber_id: Annotated[ + str, Field(max_length=64, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,64}$') + ] + url: AnyUrl + event_types: Annotated[list[notification_type.NotificationType], Field(min_length=1)] + all_authorized_accounts: Annotated[ + StrictBool | None, + Field(description='Echoed scope acknowledgment for account-anchored event types.'), + ] = None + include_future_event_types: Annotated[ + StrictBool | None, Field(description='Echoed from the desired configuration when set.') + ] = None + authentication: Annotated[Authentication | None, Field(deprecated=True)] = None + active: StrictBool | None = True + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/agent_reporting_destination.py b/src/adcp/types/domains/core/agent_reporting_destination.py index 2d28549d2..d5e09f31d 100644 --- a/src/adcp/types/domains/core/agent_reporting_destination.py +++ b/src/adcp/types/domains/core/agent_reporting_destination.py @@ -1,30 +1,211 @@ -"""Types declared by the AdCP ``core/agent_reporting_destination`` schema. +# generated by datamodel-codegen: +# filename: core/agent_reporting_destination.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.agent_reporting_destination import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictBool + +from . import delivery_provider, delivery_recipient, reporting_verification_profile_set + + +class AcceptedFormat(StrEnum): + jsonl = 'jsonl' + csv = 'csv' + parquet = 'parquet' + avro = 'avro' + orc = 'orc' + + +class AgentReportingDestination1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + pattern: Annotated[ + Literal['file_transfer'], + Field( + description='Discriminator. Producer publishes immutable files plus a manifest to caller-controlled object storage.' + ), + ] = 'file_transfer' + destination_id: Annotated[ + str, + Field( + description='Caller-selected stable logical key, unique within this seller relationship. Reusing it replaces desired configuration; when proof-bound coordinates or the accepted delivery contract change, the seller issues a new immutable destination_ref generation while retaining the old reference for existing account bindings and reporting history.', + max_length=64, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,64}$', + ), + ] + operator_id: Annotated[ + str | None, + Field( + description="Optional agent-scoped label naming the operator this destination serves, for per-operator isolation, audit, and offboarding under an agent principal. It is the principal's own bookkeeping and confers no identity or authority: sellers MUST NOT link, dedupe, or authorize across principals based on matching operator labels or domains.", + max_length=64, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,64}$', + ), + ] = None + active: Annotated[ + StrictBool, + Field( + description='True permits use. False suspends the destination: the seller MUST stop initiating new deliveries to every generation of this destination_id within the advertised suspension interval, and new account-level delivery configurations MUST NOT select it. Suspension does not delete caller-owned data already delivered.' + ), + ] + provider: delivery_provider.DeliveryProvider + transport: Annotated[ + str, + Field( + description='Open provider transport name, such as s3, gcs, or azure_blob.', + max_length=64, + min_length=1, + pattern='^[a-z][a-z0-9_.-]*$', + ), + ] + location: Annotated[ + str, + Field( + description='Provider-native bucket/prefix locator, such as s3://bucket/prefix/ or abfss://container@account.dfs.core.windows.net/path. Printable ASCII without whitespace, \'?\', \'#\', \'%\', \'"\', \'<\', or \'>\'. Never a credential or signed URL; sellers additionally screen and normalize per the secret_rejection and normalization rules.', + max_length=2048, + min_length=1, + pattern='^[!$&-;=@-~]+$', + ), + ] + accepted_formats: Annotated[ + list[AcceptedFormat], + Field( + description='Physical formats accepted by this file-transfer destination.', min_length=1 + ), + ] + accepted_verification_profiles: ( + reporting_verification_profile_set.ReportingVerificationProfileSet + ) + + +class AgentReportingDestination2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + pattern: Annotated[ + Literal['warehouse_materialization'], + Field( + description='Discriminator. Producer or its connector commits reporting revisions into a caller-owned warehouse relation.' + ), + ] = 'warehouse_materialization' + destination_id: Annotated[ + str, + Field( + description='Caller-selected stable logical key, unique within this seller relationship. Reusing it replaces desired configuration; when proof-bound coordinates or the accepted delivery contract change, the seller issues a new immutable destination_ref generation while retaining the old reference for existing account bindings and reporting history.', + max_length=64, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,64}$', + ), + ] + operator_id: Annotated[ + str | None, + Field( + description="Optional agent-scoped label naming the operator this destination serves, for per-operator isolation, audit, and offboarding under an agent principal. It is the principal's own bookkeeping and confers no identity or authority: sellers MUST NOT link, dedupe, or authorize across principals based on matching operator labels or domains.", + max_length=64, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,64}$', + ), + ] = None + active: Annotated[ + StrictBool, + Field( + description='True permits use. False suspends the destination: the seller MUST stop initiating new deliveries to every generation of this destination_id within the advertised suspension interval, and new account-level delivery configurations MUST NOT select it. Suspension does not delete caller-owned data already delivered.' + ), + ] + provider: delivery_provider.DeliveryProvider + transport: Annotated[ + str, + Field( + description='Open provider transport name, such as bigquery, snowflake, or databricks_sql.', + max_length=64, + min_length=1, + pattern='^[a-z][a-z0-9_.-]*$', + ), + ] + location: Annotated[ + str, + Field( + description='Provider-native project/dataset, database/schema, or catalog/schema locator. Printable ASCII without whitespace, \'?\', \'#\', \'%\', \'"\', \'<\', or \'>\'. Never a credential or signed URL; sellers additionally screen and normalize per the secret_rejection and normalization rules.', + max_length=2048, + min_length=1, + pattern='^[!$&-;=@-~]+$', + ), + ] + accepted_verification_profiles: ( + reporting_verification_profile_set.ReportingVerificationProfileSet + ) + + +class AgentReportingDestination3(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + pattern: Annotated[ + Literal['dataset_share'], + Field( + description='Discriminator. Producer retains and versions the dataset; the named recipient reads provider-hosted share objects.' + ), + ] = 'dataset_share' + destination_id: Annotated[ + str, + Field( + description='Caller-selected stable logical key, unique within this seller relationship. Reusing it replaces desired configuration; when proof-bound coordinates or the accepted delivery contract change, the seller issues a new immutable destination_ref generation while retaining the old reference for existing account bindings and reporting history.', + max_length=64, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,64}$', + ), + ] + operator_id: Annotated[ + str | None, + Field( + description="Optional agent-scoped label naming the operator this destination serves, for per-operator isolation, audit, and offboarding under an agent principal. It is the principal's own bookkeeping and confers no identity or authority: sellers MUST NOT link, dedupe, or authorize across principals based on matching operator labels or domains.", + max_length=64, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,64}$', + ), + ] = None + active: Annotated[ + StrictBool, + Field( + description='True permits use. False suspends the destination: the seller MUST stop publishing new revisions to every generation of this destination_id within the advertised suspension interval, and new account-level delivery configurations MUST NOT select it. Suspension does not delete caller-owned data already delivered or revoke provider-side grants by itself.' + ), + ] + provider: delivery_provider.DeliveryProvider + transport: Annotated[ + str, + Field( + description='Open provider transport name, such as delta_sharing or snowflake_secure_sharing.', + max_length=64, + min_length=1, + pattern='^[a-z][a-z0-9_.-]*$', + ), + ] + access_mode: Annotated[ + str, + Field( + description='Dataset-share access family, such as databricks_to_databricks, open_sharing, or secure_data_sharing.', + max_length=64, + min_length=1, + pattern='^[a-z][a-z0-9_.-]*$', + ), + ] + recipient: delivery_recipient.DeliveryRecipient + accepted_verification_profiles: ( + reporting_verification_profile_set.ReportingVerificationProfileSet + ) -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.agent_reporting_destination import ( - AcceptedFormat, - AgentReportingDestination, - AgentReportingDestination1, - AgentReportingDestination2, - AgentReportingDestination3, -) - -# Explicit exports -__all__ = [ - "AcceptedFormat", - "AgentReportingDestination", - "AgentReportingDestination1", - "AgentReportingDestination2", - "AgentReportingDestination3", +AgentReportingDestination = Annotated[ + AgentReportingDestination1 | AgentReportingDestination2 | AgentReportingDestination3, + Field( + description="Reusable, non-secret reporting destination owned by the authenticated caller's relationship with one seller. Exactly one delivery pattern per destination; pattern is the discriminator and matches the managed reporting delivery pattern vocabulary. It does not grant account authority: account/reporting configuration separately binds authorized data to the seller-issued destination_ref. Sellers key ownership to the stable transport principal, never a signing key, token, or request-body identity. Credentials, private keys, bearer profiles, signed URLs, and embedded passwords are forbidden. Sellers implementing this schema advertise protocol.principal.", + title='Agent Reporting Destination', + ), ] diff --git a/src/adcp/types/domains/core/agent_reporting_destination_state.py b/src/adcp/types/domains/core/agent_reporting_destination_state.py index 80b8c8e05..1cbef19d0 100644 --- a/src/adcp/types/domains/core/agent_reporting_destination_state.py +++ b/src/adcp/types/domains/core/agent_reporting_destination_state.py @@ -1,23 +1,101 @@ -"""Types declared by the AdCP ``core/agent_reporting_destination_state`` schema. +# generated by datamodel-codegen: +# filename: core/agent_reporting_destination_state.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.agent_reporting_destination_state import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import reporting_destination_setup_state +from . import agent_reporting_destination, error + + +class PriorDestinationRef(ScalarStr): + __slots__ = () + _constraints = {'max_length': 255, 'min_length': 1} + + +class Action(StrEnum): + grant_access = 'grant_access' + accept_share = 'accept_share' + prove_control = 'prove_control' + contact_support = 'contact_support' + + +class Setup(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + action: Action + setup_url: Annotated[ + AnyUrl | None, + Field( + description='HTTPS page for completing provider-native setup. Printable ASCII without whitespace, \'?\', \'#\', \'%\', \'"\', \'<\', or \'>\'. It MUST NOT carry a credential, userinfo authority, or signed component and MUST be rendered as an untrusted link, never executed as agent instructions.' + ), + ] = None + expires_at: Annotated[ + AwareDatetime | None, + Field( + description='Optional expiry of this setup action. A new sync obtains a fresh action after expiry.' + ), + ] = None -from adcp.types.generated_poc.core.agent_reporting_destination_state import ( - Action, - AgentReportingDestinationState, - PriorDestinationRef, - Setup, -) -# Explicit exports -__all__ = ["Action", "AgentReportingDestinationState", "PriorDestinationRef", "Setup"] +class AgentReportingDestinationState(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + destination_id: Annotated[ + str, + Field( + description='Caller-selected key echoed from the desired configuration.', + max_length=64, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,64}$', + ), + ] + destination_ref: Annotated[ + str, + Field( + description='Seller-issued opaque immutable destination-generation reference bound to the stable authenticated principal and destination_id. Exact replays preserve it; a proof-bound coordinate or delivery-contract change creates a new reference. Possession does not authorize account access, and sellers MUST NOT resolve it across callers.', + max_length=255, + min_length=1, + ), + ] + prior_destination_refs: Annotated[ + list[PriorDestinationRef] | None, + Field( + description='Retained superseded generation references for this destination_id, newest first, still resolvable for existing authorized account bindings and retained reporting history. Enumerable only by the owning principal. Suspension and revocation of the destination apply to these generations too.', + max_length=32, + ), + ] = None + state: Annotated[ + reporting_destination_setup_state.ReportingDestinationSetupState, + Field( + description='Validation and setup state; see the enum for the per-pattern ready definition.' + ), + ] + configuration: Annotated[ + agent_reporting_destination.AgentReportingDestination, + Field( + description='Credential-free desired configuration currently associated with this destination reference.' + ), + ] + setup: Annotated[ + Setup | None, + Field( + description='Closed, non-secret setup instruction. Human-readable messages are deliberately excluded; agents dispatch only the typed action and treat setup_url as an untrusted navigation target.' + ), + ] = None + issues: Annotated[ + list[error.Error] | None, + Field( + description='Structured validation or setup issues. Messages and details are untrusted display data and MUST NOT be executed as instructions.', + max_length=16, + ), + ] = None diff --git a/src/adcp/types/domains/core/agent_signing_key.py b/src/adcp/types/domains/core/agent_signing_key.py index 8a9094d74..80caf3d82 100644 --- a/src/adcp/types/domains/core/agent_signing_key.py +++ b/src/adcp/types/domains/core/agent_signing_key.py @@ -1,18 +1,46 @@ -"""Types declared by the AdCP ``core/agent_signing_key`` schema. +# generated by datamodel-codegen: +# filename: core/agent_signing_key.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.agent_signing_key import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field -from adcp.types.generated_poc.core.agent_signing_key import AgentSigningKey -# Explicit exports -__all__ = ["AgentSigningKey"] +class AgentSigningKey(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + kid: Annotated[str, Field(description='Key identifier for selecting the correct signing key.')] + kty: Annotated[str, Field(description="JWK key type, such as 'OKP', 'EC', or 'RSA'.")] + alg: Annotated[ + str | None, + Field(description="Expected signing algorithm for this key, such as 'EdDSA' or 'RS256'."), + ] = None + use: Annotated[ + str | None, Field(description="Optional JWK use value. Typically 'sig' for signing keys.") + ] = None + crv: Annotated[ + str | None, + Field(description="Curve name for OKP or EC keys, such as 'Ed25519' or 'P-256'."), + ] = None + x: Annotated[ + str | None, + Field( + description='Base64url-encoded public key x coordinate or public key value for OKP keys.' + ), + ] = None + y: Annotated[ + str | None, Field(description='Base64url-encoded public key y coordinate for EC keys.') + ] = None + n: Annotated[str | None, Field(description='Base64url-encoded RSA modulus.')] = None + e: Annotated[str | None, Field(description='Base64url-encoded RSA public exponent.')] = None + revoked_at: Annotated[ + AwareDatetime | None, + Field( + description='Optional revocation timestamp. When present, verifiers MUST reject any signature produced with this key whose signing epoch (or equivalent time reference) is at or after this timestamp. The key may continue to appear in the trust anchor during a grace period so caches that have not yet refreshed still find the key and can evaluate the revocation marker. Keys past their revocation can be removed once the cache TTL (recommended: 5 minutes) has elapsed across all verifiers.' + ), + ] = None diff --git a/src/adcp/types/domains/core/agent_webhook_challenge.py b/src/adcp/types/domains/core/agent_webhook_challenge.py index a5c232f98..00e893df2 100644 --- a/src/adcp/types/domains/core/agent_webhook_challenge.py +++ b/src/adcp/types/domains/core/agent_webhook_challenge.py @@ -1,22 +1,87 @@ -"""Types declared by the AdCP ``core/agent_webhook_challenge`` schema. +# generated by datamodel-codegen: +# filename: core/agent_webhook_challenge.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.agent_webhook_challenge import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.agent_webhook_challenge import ( - AgentWebhookChallenge, - DeliveryAuth, - Mode, -) +class Mode(StrEnum): + rfc9421 = 'rfc9421' + Bearer = 'Bearer' + HMAC_SHA256 = 'HMAC-SHA256' + + +class DeliveryAuth(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + mode: Annotated[ + Mode, + Field( + description='Future webhook delivery authentication mode. `rfc9421` is used when notification_configs[].authentication is absent; Bearer and HMAC-SHA256 are the deprecated legacy modes selected by notification_configs[].authentication.' + ), + ] + credential_fingerprint: Annotated[ + str | None, + Field( + description='SHA-256 hex fingerprint of the exact legacy credential string supplied in notification_configs[].authentication.credentials. Required when mode is Bearer or HMAC-SHA256; absent for rfc9421.', + pattern='^[a-f0-9]{64}$', + ), + ] = None + -# Explicit exports -__all__ = ["AgentWebhookChallenge", "DeliveryAuth", "Mode"] +class AgentWebhookChallenge(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Annotated[ + Literal['webhook.challenge'], + Field(description='Discriminator for endpoint proof-of-control challenges.'), + ] = 'webhook.challenge' + scope: Annotated[ + Literal['agent'], + Field(description='Discriminator for agent-level endpoint proof challenges.'), + ] = 'agent' + challenge: Annotated[ + str, + Field( + description='Opaque, cryptographically random value that the receiver must echo in the response body. Recommended encoding: base64url without padding.', + max_length=255, + min_length=32, + pattern='^[A-Za-z0-9_.:-]{32,255}$', + ), + ] + subscriber_id: Annotated[ + str, + Field( + description='Buyer-supplied subscriber identifier from the caller-scoped notification_configs[] entry being challenged.', + max_length=64, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,64}$', + ), + ] + seller_agent_url: Annotated[ + AnyUrl, + Field( + description='Exact seller agent URL whose RFC 9421 webhook profile key signs this challenge and that will send subsequent webhooks.' + ), + ] + delivery_auth: Annotated[ + DeliveryAuth, + Field( + description='Authentication/signing mode the seller will use for subsequent webhooks delivered to this notification config.' + ), + ] + event_types: Annotated[ + list[Literal['capabilities.changed']], + Field( + description='Normalized agent-level notification types requested by the subscriber at the time of the challenge. Part of the endpoint proof scope; changing event_types[] requires a fresh challenge before the new set can become active. Currently only `capabilities.changed` is valid.', + min_length=1, + ), + ] diff --git a/src/adcp/types/domains/core/app_item.py b/src/adcp/types/domains/core/app_item.py index b08a4b998..ae754a723 100644 --- a/src/adcp/types/domains/core/app_item.py +++ b/src/adcp/types/domains/core/app_item.py @@ -1,18 +1,137 @@ -"""Types declared by the AdCP ``core/app_item`` schema. +# generated by datamodel-codegen: +# filename: core/app_item.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.app_item import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AnyUrl, ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import ext as ext_1 +from . import offering_asset_group +from . import price as price_1 + + +class Platform(StrEnum): + ios = 'ios' + android = 'android' -from adcp.types.generated_poc.core.app_item import AppItem, Platform -# Explicit exports -__all__ = ["AppItem", "Platform"] +class AppItem(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + app_id: Annotated[ + str, + Field( + description='Buyer-assigned unique identifier for this app item. Used for catalog deduplication and content_ids matching on install and launch events.' + ), + ] + name: Annotated[ + str, + Field( + description="App display name as shown in the store (e.g., 'Puzzle Quest: Match 3', 'Acme Banking')." + ), + ] + platform: Annotated[ + Platform, + Field( + description='Target platform. iOS and Android are separate items because they have distinct store identifiers and attribution mechanisms.' + ), + ] + bundle_id: Annotated[ + str | None, + Field( + description="Reverse-domain bundle identifier (e.g., 'com.acmegames.puzzlequest'). The universal store identifier: required for Android (Google Play), also used for iOS MMP attribution, SKAN matching, and app-ads.txt verification. Distinct from app_id, which is a buyer-assigned catalog key." + ), + ] = None + apple_id: Annotated[ + str | None, + Field( + description="Numeric Apple App Store ID (e.g., '389801252'). Required for Apple Search Ads and iOS platforms that use the numeric ID rather than bundle_id.", + pattern='^[0-9]+$', + ), + ] = None + description: Annotated[ + str | None, + Field( + description='App description. Platforms typically pull this from the store listing automatically; supply here to override or for platforms that require it in the request.' + ), + ] = None + category: Annotated[ + str | None, + Field( + description="Primary store category (e.g., 'games', 'productivity', 'finance', 'health_fitness', 'social_networking')." + ), + ] = None + genre: Annotated[ + str | None, + Field( + description="Sub-genre within the category. Particularly relevant for games (e.g., 'puzzle', 'strategy', 'rpg', 'casual', 'simulation', 'action')." + ), + ] = None + icon_url: Annotated[ + AnyUrl | None, Field(description='App icon image URL. Typically 1024×1024 px.') + ] = None + screenshots: Annotated[ + list[AnyUrl] | None, + Field( + description='App store screenshot URLs. Used by platforms for creative generation when native store assets are not available.', + min_length=1, + ), + ] = None + preview_video_url: Annotated[ + AnyUrl | None, + Field(description='App preview or gameplay video URL for use in video ad creatives.'), + ] = None + store_url: Annotated[ + AnyUrl | None, + Field( + description="Direct link to the app's store listing (Apple App Store or Google Play)." + ), + ] = None + deep_link_url: Annotated[ + AnyUrl | None, + Field( + description="Deep link URI for re-engagement campaigns targeting existing users. Use Universal Links (iOS) or App Links (Android) where available (e.g., 'https://acmegames.com/app/level/5'). Falls back to URI scheme (e.g., 'acmegames://level/5') when universal links are not configured." + ), + ] = None + price: Annotated[ + price_1.Price | None, + Field(description='App download price. Set amount to 0 for free apps.'), + ] = None + rating: Annotated[ + StrictFloat | None, + Field( + description='Average store rating (0–5). Use 0 to indicate no ratings yet.', + ge=0.0, + le=5.0, + ), + ] = None + rating_count: Annotated[ + SchemaInt | None, Field(description='Total number of store ratings.', ge=0) + ] = None + content_rating: Annotated[ + str | None, + Field( + description="Age or content rating (e.g., '4+', '12+', 'Everyone', 'Teen', 'PEGI 12'). Format depends on store and region." + ), + ] = None + tags: Annotated[ + list[str] | None, + Field( + description="Tags for filtering and targeting (e.g., 'multiplayer', 'offline', 'no-ads', 'subscription').", + min_length=1, + ), + ] = None + assets: Annotated[ + list[offering_asset_group.OfferingAssetGroup] | None, + Field( + description="Typed creative asset pools for this app. Uses the same OfferingAssetGroup structure as offering-type catalogs. Standard group IDs: 'images_landscape' (promotional hero), 'images_vertical' (9:16 for Snap, Stories), 'images_square' (1:1 for display), 'video' (gameplay or demo video). Supplements icon_url and screenshots for platform-specific format requirements.", + min_length=1, + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/applicable_package_id.py b/src/adcp/types/domains/core/applicable_package_id.py index 3e766551f..777cc31f7 100644 --- a/src/adcp/types/domains/core/applicable_package_id.py +++ b/src/adcp/types/domains/core/applicable_package_id.py @@ -1,18 +1,16 @@ -"""Types declared by the AdCP ``core/applicable_package_id`` schema. +# generated by datamodel-codegen: +# filename: core/applicable_package_id.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.applicable_package_id import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.applicable_package_id import ApplicablePackageId +from adcp.types._scalar import ScalarStr + -# Explicit exports -__all__ = ["ApplicablePackageId"] +class ApplicablePackageId(ScalarStr): + __slots__ = () + _constraints = {'min_length': 1} + _json_schema_extra = { + 'description': 'A package identifier to which a package-scoped media-buy action currently applies.', + 'title': 'Applicable Package ID', + } diff --git a/src/adcp/types/domains/core/asset_group_vocabulary.py b/src/adcp/types/domains/core/asset_group_vocabulary.py index 0f3c55328..782e1078e 100644 --- a/src/adcp/types/domains/core/asset_group_vocabulary.py +++ b/src/adcp/types/domains/core/asset_group_vocabulary.py @@ -1,18 +1,19 @@ -"""Types declared by the AdCP ``core/asset_group_vocabulary`` schema. +# generated by datamodel-codegen: +# filename: core/asset_group_vocabulary.json +# timestamp: 2026-06-01T00:32:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.asset_group_vocabulary import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Any -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import Field, RootModel -from adcp.types.generated_poc.core.asset_group_vocabulary import AdcpAssetGroupVocabularyRegistry -# Explicit exports -__all__ = ["AdcpAssetGroupVocabularyRegistry"] +class AdcpAssetGroupVocabularyRegistry(RootModel[Any]): + root: Annotated[ + Any, + Field( + description='Canonical registry of asset_group_id values used in offering asset groups (OfferingAssetGroup) and in v2 product format declarations. Non-canonical IDs remain valid for platform-specific extensions; this registry codifies the recommended canonical set so that buyers and sellers share a vocabulary for the most common slot roles. Validators may emit soft warnings on non-canonical IDs to encourage convergence.\n\nThe registry covers everything the buyer ships in the manifest\'s `assets` map — both directly-rendered creative content (image, video, audio) AND content the seller consumes for production (script, creative_brief, video_brief). The seller dispatches per the format\'s slot declaration. There is no separate "inputs" map on the manifest; everything is an asset.\n\n**Two-tier boundary (normative).** This registry is the *canonical, portable* tier — IAB-aligned, platform-agnostic slot roles every adopter shares (`headline`, `body_text`, `main_image`, `cta`, `landing_page_url`, etc.). Platform-specific asset identifiers (e.g., YouTube video IDs, Pinterest pin IDs, TikTok video IDs, Snap attachment IDs, Meta Advantage+ creative IDs) MUST NOT be added here — they live on the canonical\'s `platform_extensions[]` (URI+digest reference to the platform\'s extension schema) so the canonical registry stays portable. Earlier drafts carried `youtube_video_id` and `pin_id` here; they were dropped in 3.1 GA precisely because adding them set precedent for every platform\'s identifier vocabulary to leak into the canonical tier, defeating the registry\'s portability purpose. Adopters needing to reference an existing platform-hosted asset attach a `platform_extensions[]` entry on the format declaration whose extension schema defines the platform-specific ID slot.', + title='AdCP Asset Group Vocabulary Registry', + ), + ] diff --git a/src/adcp/types/domains/core/assets/__init__.py b/src/adcp/types/domains/core/assets/__init__.py index 409902835..a01e99719 100644 --- a/src/adcp/types/domains/core/assets/__init__.py +++ b/src/adcp/types/domains/core/assets/__init__.py @@ -1,7 +1,3 @@ -"""Public mirror of the AdCP ``core/assets`` schema directory. - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:34:03 UTC -""" - -__all__: list[str] = [] +# generated by datamodel-codegen: +# filename: .schema_temp +# timestamp: 2026-06-01T00:32:59+00:00 diff --git a/src/adcp/types/domains/core/assets/asset_union.py b/src/adcp/types/domains/core/assets/asset_union.py index 4906aa15a..7c03eab18 100644 --- a/src/adcp/types/domains/core/assets/asset_union.py +++ b/src/adcp/types/domains/core/assets/asset_union.py @@ -1,294 +1,3274 @@ -"""Types declared by the AdCP ``core/assets/asset_union`` schema. +# generated by datamodel-codegen: +# filename: core/assets/asset_union.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.assets.asset_union import +from enum import IntEnum +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from adcp.types._scalar import ScalarFloat, ScalarStr +from pydantic import ( + AnyUrl, + AwareDatetime, + ConfigDict, + Field, + RootModel, + StrictBool, + StrictFloat, +) -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.assets.asset_union import ( - Accessibility, - AiTool, - AssetVariant, - AudioAsset, - AudioBitDepth, - AudioChannelLayout, - BriefAsset, - C2pa, - C2paWatermarkAction, - CardAsset, - Catalog, - CatalogAsset, - CatalogFieldMapping, - CatalogType, - ChromaSubsampling, - ColorSpace, - Compliance, - ContentIdType, - CreativeBrief, - CssAsset, - DaastAsset, - DaastAsset1, - DaastAsset2, - DaastTrackerAsset, - DaastTrackingEvent, - DaastVersion, - DeclaredBy, - DigitalSourceType, - Disclosure, - DisclosurePersistence, - DisclosurePosition, - DisplayTagAsset, - DisplayTagAsset1, - DisplayTagAsset2, - DisplayTagAsset3, - EmbeddedProvenanceItem, - EmbeddedProvenanceMethod, - EventType, - Ext, - FeedFormat, - FieldModel, - FocalPointItem, - FrameRateType, - GopType, - Gtin, - HdrFormat, - HtmlAsset, - HttpMethod, - HumanOversight, - IdentityRef, - ImageAsset, - JavascriptAsset, - JavascriptModuleType, - Jurisdiction, - Jurisdiction2, - Kind, - Location, - Location1, - Location2, - Location4, - Location5, - Location6, - Location8, - Location9, - MacroBearingUrl, - MacroBearingUrl1, - MacroBearingUrl2, - MacroDeclaration, - MacroDeclaration1, - MacroDeclaration10, - MacroDeclaration2, - MacroDeclaration3, - MacroDeclaration4, - MacroDeclaration5, - MacroDeclaration6, - MacroDeclaration7, - MacroDeclaration8, - MacroDeclaration9, - MacroDeclarationModel, - MacroDialect, - MacroEncoding, - MacroMappingStatus, - MacroProcessingOperation, - MacroResolver, - MacroTranslationTarget, - MacroValueContext, - MarkdownAsset, - MarkdownFlavor, - MarkupType, - Messaging, - Method, - MoovAtomPosition, - Objective, - PixelTrackerAsset, - PixelTrackingEvent, - PlatformExtensionRef, - Provenance, - PublishedPostAsset, - PublishedPostAsset1, - PublishedPostAsset2, - ReferenceAsset, - ReferenceAuthorization, - ReferenceAuthorization1, - RenderGuidance, - RequiredDisclosure, - Result, - Role, - Role2, - ScanType, - Security, - Status, - Target, - Target1, - TextAsset, - Transform, - UnavailableBehavior, - UniversalMacro, - UpdateFrequency, - UrlAsset, - UrlAssetType, - VastAsset, - VastAsset1, - VastAsset2, - VastTrackerAsset, - VastTrackingEvent, - VastVersion, - VerificationItem, - VerifyAgent, - VerifyAgent1, - VideoAsset, - VideoBitDepth, - Watermark, - WatermarkMediaType, - WebhookAsset, - WebhookResponseType, - WebhookSecurityMethod, - ZipAsset, -) +class FocalPointItem(ScalarFloat): + __slots__ = () + _constraints = {'ge': 0.0, 'le': 1.0} + + +class AiTool(AdCPBaseModel): + name: Annotated[ + str, + Field( + description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" + ), + ] + version: Annotated[ + str | None, + Field( + description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." + ), + ] = None + provider: Annotated[ + str | None, + Field( + description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" + ), + ] = None + + +class HumanOversight(StrEnum): + none = 'none' + prompt_only = 'prompt_only' + selected = 'selected' + edited = 'edited' + directed = 'directed' + + +class Role(StrEnum): + creator = 'creator' + advertiser = 'advertiser' + agency = 'agency' + platform = 'platform' + tool = 'tool' + + +class DeclaredBy(AdCPBaseModel): + agent_url: Annotated[ + AnyUrl | None, + Field(description='URL of the agent or service that declared this provenance'), + ] = None + role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] + + +class C2pa(AdCPBaseModel): + manifest_url: Annotated[ + AnyUrl, Field(description='URL to the C2PA manifest store for this content') + ] + + +class VerifyAgent(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + agent_url: Annotated[ + AnyUrl, + Field( + description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." + ), + ] + feature_id: Annotated[ + str | None, + Field( + description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." + ), + ] = None + + +class VerifyAgent1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + agent_url: Annotated[ + AnyUrl, + Field( + description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." + ), + ] + feature_id: Annotated[ + str | None, + Field( + description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." + ), + ] = None + + +class Result(StrEnum): + authentic = 'authentic' + ai_generated = 'ai_generated' + ai_modified = 'ai_modified' + inconclusive = 'inconclusive' + + +class VerificationItem(AdCPBaseModel): + verified_by: Annotated[ + str, + Field( + description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" + ), + ] + verified_time: Annotated[ + AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') + ] = None + result: Annotated[Result, Field(description='Verification outcome')] + confidence: Annotated[ + StrictFloat | None, + Field( + description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 + ), + ] = None + details_url: Annotated[ + AnyUrl | None, Field(description='URL to the full verification report') + ] = None + + +class DigitalSourceType(StrEnum): + digital_capture = 'digital_capture' + digital_creation = 'digital_creation' + trained_algorithmic_media = 'trained_algorithmic_media' + composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' + algorithmic_media = 'algorithmic_media' + composite_capture = 'composite_capture' + composite_synthetic = 'composite_synthetic' + human_edits = 'human_edits' + data_driven_media = 'data_driven_media' + + +class EmbeddedProvenanceMethod(StrEnum): + manifest_wrapper = 'manifest_wrapper' + provenance_markers = 'provenance_markers' + + +class WatermarkMediaType(StrEnum): + audio = 'audio' + image = 'image' + video = 'video' + text = 'text' + + +class C2paWatermarkAction(StrEnum): + c2pa_watermarked_bound = 'c2pa.watermarked.bound' + c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' + + +class DisclosurePersistence(StrEnum): + continuous = 'continuous' + initial = 'initial' + flexible = 'flexible' + + +class DisclosurePosition(StrEnum): + prominent = 'prominent' + footer = 'footer' + audio = 'audio' + subtitle = 'subtitle' + overlay = 'overlay' + end_card = 'end_card' + pre_roll = 'pre_roll' + companion = 'companion' + + +class Ext(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + + +class ColorSpace(StrEnum): + rec709 = 'rec709' + rec2020 = 'rec2020' + rec2100 = 'rec2100' + srgb = 'srgb' + dci_p3 = 'dci_p3' + + +class HdrFormat(StrEnum): + sdr = 'sdr' + hdr10 = 'hdr10' + hdr10_plus = 'hdr10_plus' + hlg = 'hlg' + dolby_vision = 'dolby_vision' + + +class ChromaSubsampling(StrEnum): + field_4_2_0 = '4:2:0' + field_4_2_2 = '4:2:2' + field_4_4_4 = '4:4:4' + + +class VideoBitDepth(IntEnum): + integer_8 = 8 + integer_10 = 10 + integer_12 = 12 + + +class AudioBitDepth(IntEnum): + integer_16 = 16 + integer_24 = 24 + integer_32 = 32 + + +class FrameRateType(StrEnum): + constant = 'constant' + variable = 'variable' + + +class ScanType(StrEnum): + progressive = 'progressive' + interlaced = 'interlaced' + + +class GopType(StrEnum): + closed = 'closed' + open = 'open' + + +class MoovAtomPosition(StrEnum): + start = 'start' + end = 'end' + + +class AudioChannelLayout(StrEnum): + mono = 'mono' + stereo = 'stereo' + field_5_1 = '5.1' + field_7_1 = '7.1' + + +class FieldModel(StrEnum): + url = 'url' + markup = 'markup' + content = 'content' + ad_request_url = 'ad_request_url' + clickthrough_url = 'clickthrough_url' + + +class UnavailableBehavior(StrEnum): + preserve = 'preserve' + omit_parameter = 'omit_parameter' + dialect_sentinel = 'dialect_sentinel' + reject = 'reject' + + +class MacroBearingUrl1(RootModel[AnyUrl]): + root: Annotated[ + AnyUrl, + Field( + description='Backward-compatible URL value that preserves the existing `uri-template` branch (including deep links, protocol-relative references, and RFC 6570 host variables) while additionally accepting absolute HTTP(S) URLs with byte-preserved macro delimiters not legal in a strict URI, including `%%...%%`, `[...]`, and `${...}`. Macro-bearing HTTP(S) URLs require a valid lexical authority and valid percent triplets; an AdCP verifier replaces each recognized token with an unreserved sentinel and validates the resulting absolute URI. Token semantics are established only by `macro_declarations`.', + title='Macro-bearing URL', + ), + ] + + +class MacroBearingUrl2(ScalarStr): + __slots__ = () + _constraints = { + 'pattern': '^https?://(?:[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?|\\[[0-9A-Fa-f:.]+\\])(?::[0-9]{1,5})?(?:[/?#](?:[^%\\s\\u0000-\\u001F\\u007F"<>`\\\\]|%[0-9A-Fa-f]{2}|%%[A-Za-z0-9_.:-]+%%)*)?$', + } + _json_schema_extra = { + 'description': 'Backward-compatible URL value that preserves the existing `uri-template` branch (including deep links, protocol-relative references, and RFC 6570 host variables) while additionally accepting absolute HTTP(S) URLs with byte-preserved macro delimiters not legal in a strict URI, including `%%...%%`, `[...]`, and `${...}`. Macro-bearing HTTP(S) URLs require a valid lexical authority and valid percent triplets; an AdCP verifier replaces each recognized token with an unreserved sentinel and validates the resulting absolute URI. Token semantics are established only by `macro_declarations`.', + 'title': 'Macro-bearing URL', + } + + +MacroBearingUrl = Annotated[ + str | MacroBearingUrl1 | MacroBearingUrl2, + Field( + description='Backward-compatible URL value that preserves the existing `uri-template` branch (including deep links, protocol-relative references, and RFC 6570 host variables) while additionally accepting absolute HTTP(S) URLs with byte-preserved macro delimiters not legal in a strict URI, including `%%...%%`, `[...]`, and `${...}`. Macro-bearing HTTP(S) URLs require a valid lexical authority and valid percent triplets; an AdCP verifier replaces each recognized token with an unreserved sentinel and validates the resulting absolute URI. Token semantics are established only by `macro_declarations`.', + title='Macro-bearing URL', + ), +] + + +class VastVersion(StrEnum): + field_2_0 = '2.0' + field_3_0 = '3.0' + field_4_0 = '4.0' + field_4_1 = '4.1' + field_4_2 = '4.2' + field_4_3 = '4.3' + + +class MacroDialect(StrEnum): + adcp = 'adcp' + iab_vast = 'iab_vast' + iab_daast = 'iab_daast' + vendor = 'vendor' + unknown = 'unknown' + + +class MacroMappingStatus(StrEnum): + verified_universal = 'verified_universal' + dialect_defined = 'dialect_defined' + unresolved = 'unresolved' + + +class UniversalMacro(StrEnum): + MEDIA_BUY_ID = 'MEDIA_BUY_ID' + PACKAGE_ID = 'PACKAGE_ID' + CREATIVE_ID = 'CREATIVE_ID' + CACHEBUSTER = 'CACHEBUSTER' + TIMESTAMP = 'TIMESTAMP' + CLICK_URL = 'CLICK_URL' + GDPR = 'GDPR' + GDPR_CONSENT = 'GDPR_CONSENT' + US_PRIVACY = 'US_PRIVACY' + GPP_STRING = 'GPP_STRING' + GPP_SID = 'GPP_SID' + IP_ADDRESS = 'IP_ADDRESS' + LIMIT_AD_TRACKING = 'LIMIT_AD_TRACKING' + DEVICE_TYPE = 'DEVICE_TYPE' + OS = 'OS' + OS_VERSION = 'OS_VERSION' + DEVICE_MAKE = 'DEVICE_MAKE' + DEVICE_MODEL = 'DEVICE_MODEL' + USER_AGENT = 'USER_AGENT' + APP_BUNDLE = 'APP_BUNDLE' + APP_NAME = 'APP_NAME' + COUNTRY = 'COUNTRY' + REGION = 'REGION' + CITY = 'CITY' + ZIP = 'ZIP' + DMA = 'DMA' + LAT = 'LAT' + LONG = 'LONG' + DEVICE_ID = 'DEVICE_ID' + DEVICE_ID_TYPE = 'DEVICE_ID_TYPE' + DOMAIN = 'DOMAIN' + PAGE_URL = 'PAGE_URL' + REFERRER = 'REFERRER' + KEYWORDS = 'KEYWORDS' + PLACEMENT_ID = 'PLACEMENT_ID' + FOLD_POSITION = 'FOLD_POSITION' + AD_WIDTH = 'AD_WIDTH' + AD_HEIGHT = 'AD_HEIGHT' + VIDEO_ID = 'VIDEO_ID' + VIDEO_TITLE = 'VIDEO_TITLE' + VIDEO_DURATION = 'VIDEO_DURATION' + VIDEO_CATEGORY = 'VIDEO_CATEGORY' + CONTENT_GENRE = 'CONTENT_GENRE' + CONTENT_RATING = 'CONTENT_RATING' + PLAYER_WIDTH = 'PLAYER_WIDTH' + PLAYER_HEIGHT = 'PLAYER_HEIGHT' + POD_POSITION = 'POD_POSITION' + POD_SIZE = 'POD_SIZE' + AD_BREAK_ID = 'AD_BREAK_ID' + STATION_ID = 'STATION_ID' + COLLECTION_NAME = 'COLLECTION_NAME' + INSTALLMENT_ID = 'INSTALLMENT_ID' + AUDIO_DURATION = 'AUDIO_DURATION' + TMPX = 'TMPX' + IMPRESSION_ID = 'IMPRESSION_ID' + AXEM = 'AXEM' + CATALOG_ID = 'CATALOG_ID' + SKU = 'SKU' + GTIN = 'GTIN' + OFFERING_ID = 'OFFERING_ID' + JOB_ID = 'JOB_ID' + HOTEL_ID = 'HOTEL_ID' + FLIGHT_ID = 'FLIGHT_ID' + VEHICLE_ID = 'VEHICLE_ID' + LISTING_ID = 'LISTING_ID' + STORE_ID = 'STORE_ID' + PROGRAM_ID = 'PROGRAM_ID' + DESTINATION_ID = 'DESTINATION_ID' + CREATIVE_VARIANT_ID = 'CREATIVE_VARIANT_ID' + APP_ITEM_ID = 'APP_ITEM_ID' + ITEM_NAME = 'ITEM_NAME' + ITEM_DESCRIPTION = 'ITEM_DESCRIPTION' + ITEM_TAGLINE = 'ITEM_TAGLINE' + ITEM_PRICE = 'ITEM_PRICE' + ITEM_PRICE_CURRENCY = 'ITEM_PRICE_CURRENCY' + + +class MacroProcessingOperation(StrEnum): + translate_to_native = 'translate_to_native' + resolve_value = 'resolve_value' + preserve = 'preserve' + + +class MacroResolver(StrEnum): + buyer = 'buyer' + creative_agent = 'creative_agent' + seller = 'seller' + request_executor = 'request_executor' + source_ad_server = 'source_ad_server' + + +class Kind(StrEnum): + none = 'none' + rfc3986 = 'rfc3986' + iab_vast_uri = 'iab_vast_uri' + + +class MacroEncoding(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Kind + depth: Annotated[SchemaInt, Field(ge=0)] + + +class MacroValueContext(StrEnum): + url_query_value = 'url_query_value' + url_path_segment = 'url_path_segment' + opaque = 'opaque' + + +class VastTrackingEvent(StrEnum): + impression = 'impression' + creativeView = 'creativeView' + loaded = 'loaded' + start = 'start' + firstQuartile = 'firstQuartile' + midpoint = 'midpoint' + thirdQuartile = 'thirdQuartile' + complete = 'complete' + mute = 'mute' + unmute = 'unmute' + pause = 'pause' + resume = 'resume' + rewind = 'rewind' + skip = 'skip' + playerExpand = 'playerExpand' + playerCollapse = 'playerCollapse' + fullscreen = 'fullscreen' + exitFullscreen = 'exitFullscreen' + progress = 'progress' + acceptInvitation = 'acceptInvitation' + adExpand = 'adExpand' + adCollapse = 'adCollapse' + minimize = 'minimize' + overlayViewDuration = 'overlayViewDuration' + otherAdInteraction = 'otherAdInteraction' + interactiveStart = 'interactiveStart' + clickTracking = 'clickTracking' + customClick = 'customClick' + close = 'close' + closeLinear = 'closeLinear' + error = 'error' + viewable = 'viewable' + notViewable = 'notViewable' + viewUndetermined = 'viewUndetermined' + measurableImpression = 'measurableImpression' + viewableImpression = 'viewableImpression' + + +class Location(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + field: Annotated[FieldModel, Field(description='Asset field containing this occurrence.')] + occurrence: Annotated[ + SchemaInt, + Field( + description='Zero-based occurrence of this exact token within the named field.', ge=0 + ), + ] + context: MacroValueContext + + +class Location4(Location): + pass + + +class MarkupType(StrEnum): + iframe_javascript = 'iframe_javascript' + javascript = 'javascript' + standard = 'standard' + + +class Location5(Location): + pass + + +class Location6(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + field: Annotated[Literal['url'], Field(description='Asset field containing this occurrence.')] = 'url' + occurrence: Annotated[ + SchemaInt, + Field( + description='Zero-based occurrence of this exact token within the named field.', ge=0 + ), + ] + context: MacroValueContext + + +class UrlAssetType(StrEnum): + clickthrough = 'clickthrough' + ad_request = 'ad_request' + tracker_pixel = 'tracker_pixel' + tracker_script = 'tracker_script' + + +class Accessibility(AdCPBaseModel): + alt_text: Annotated[ + str | None, Field(description='Text alternative describing the creative content') + ] = None + keyboard_navigable: Annotated[ + StrictBool | None, + Field(description='Whether the creative can be fully operated via keyboard'), + ] = None + motion_control: Annotated[ + StrictBool | None, + Field( + description='Whether the creative respects prefers-reduced-motion or provides pause/stop controls' + ), + ] = None + screen_reader_tested: Annotated[ + StrictBool | None, + Field(description='Whether the creative has been tested with screen readers'), + ] = None + + +class JavascriptModuleType(StrEnum): + esm = 'esm' + commonjs = 'commonjs' + script = 'script' + + +class HttpMethod(StrEnum): + GET = 'GET' + POST = 'POST' + + +class WebhookResponseType(StrEnum): + html = 'html' + json = 'json' + xml = 'xml' + javascript = 'javascript' + + +class WebhookSecurityMethod(StrEnum): + hmac_sha256 = 'hmac_sha256' + api_key = 'api_key' + none = 'none' + + +class Location8(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + field: Annotated[ + Literal['content'], Field(description='Asset field containing this occurrence.') + ] = 'content' + occurrence: Annotated[ + SchemaInt, + Field( + description='Zero-based occurrence of this exact token within the named field.', ge=0 + ), + ] + context: MacroValueContext + + +class DaastVersion(StrEnum): + field_1_0 = '1.0' + field_1_1 = '1.1' + + +class DaastTrackingEvent(StrEnum): + impression = 'impression' + creativeView = 'creativeView' + start = 'start' + firstQuartile = 'firstQuartile' + midpoint = 'midpoint' + thirdQuartile = 'thirdQuartile' + complete = 'complete' + mute = 'mute' + unmute = 'unmute' + pause = 'pause' + resume = 'resume' + rewind = 'rewind' + skip = 'skip' + progress = 'progress' + clickTracking = 'clickTracking' + customClick = 'customClick' + close = 'close' + error = 'error' + viewable = 'viewable' + notViewable = 'notViewable' + viewUndetermined = 'viewUndetermined' + measurableImpression = 'measurableImpression' + viewableImpression = 'viewableImpression' + + +class MarkdownFlavor(StrEnum): + commonmark = 'commonmark' + gfm = 'gfm' + + +class Objective(StrEnum): + awareness = 'awareness' + consideration = 'consideration' + conversion = 'conversion' + retention = 'retention' + engagement = 'engagement' + + +class Messaging(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + headline: Annotated[str | None, Field(description='Primary headline')] = None + tagline: Annotated[str | None, Field(description='Supporting tagline or sub-headline')] = None + cta: Annotated[str | None, Field(description='Call-to-action text')] = None + key_messages: Annotated[ + list[str] | None, Field(description='Key messages to communicate in priority order') + ] = None + + +class Jurisdiction(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{2}(-[A-Z0-9]{1,3})?$'} + + +class RequiredDisclosure(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + text: Annotated[str, Field(description='The disclosure text that must appear in the creative')] + position: Annotated[ + DisclosurePosition | None, + Field( + description='Where the disclosure should appear within the creative. prominent: clearly visible in the main creative area; footer: at the bottom of visual creatives; audio: spoken in audio/video creatives; subtitle: displayed in the subtitle or closed-caption track; overlay: superimposed on video content; end_card: displayed on video end card; pre_roll: spoken or displayed before main content; companion: in companion ad unit alongside primary creative' + ), + ] = None + jurisdictions: Annotated[ + list[Jurisdiction] | None, + Field( + description="Jurisdictions where this disclosure is required. ISO 3166-1 alpha-2 country codes or ISO 3166-2 subdivision codes (e.g., 'US', 'GB', 'US-NJ', 'CA-QC'). If omitted, the disclosure applies to all jurisdictions in the campaign.", + min_length=1, + ), + ] = None + regulation: Annotated[ + str | None, + Field( + description="The regulation or legal authority requiring this disclosure (e.g., 'SEC Rule 156', 'FCA COBS 4.5', 'FDA 21 CFR 202')" + ), + ] = None + min_duration_ms: Annotated[ + SchemaInt | None, + Field( + description='Minimum display duration in milliseconds. For video/audio disclosures, how long the disclosure must be visible or audible. For static formats, how long the disclosure must remain on screen before any auto-advance.', + ge=1, + ), + ] = None + language: Annotated[ + str | None, + Field( + description="Language of the disclosure text as a BCP 47 language tag (e.g., 'en', 'fr-CA', 'es'). When omitted, the disclosure is assumed to match the creative's language." + ), + ] = None + persistence: Annotated[ + DisclosurePersistence | None, + Field( + description='How long the disclosure must persist during content playback or display. When omitted, the creative agent should use the most restrictive persistence mode supported by the target format.' + ), + ] = None + + +class Compliance(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + required_disclosures: Annotated[ + list[RequiredDisclosure] | None, + Field( + description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', + min_length=1, + ), + ] = None + prohibited_claims: Annotated[ + list[str] | None, + Field( + description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', + min_length=1, + ), + ] = None + + +class Role2(StrEnum): + style_reference = 'style_reference' + product_shot = 'product_shot' + mood_board = 'mood_board' + example_creative = 'example_creative' + logo = 'logo' + strategy_doc = 'strategy_doc' + storyboard = 'storyboard' + + +class ReferenceAsset(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + url: Annotated[ + AnyUrl, Field(description='URL to the reference asset (image, video, or document)') + ] + role: Annotated[ + Role2, + Field( + description='How the creative agent should use this asset. style_reference: match the visual style; product_shot: include this product; mood_board: overall look and feel; example_creative: example of a similar execution; logo: logo to use; strategy_doc: strategy or planning document for context; storyboard: sequential visual direction for video or multi-scene creative' + ), + ] + description: Annotated[ + str | None, + Field( + description='Human-readable description of the asset and how it should inform creative generation' + ), + ] = None + + +class Gtin(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[0-9]{8,14}$'} + + +class CatalogType(StrEnum): + offering = 'offering' + product = 'product' + inventory = 'inventory' + store = 'store' + promotion = 'promotion' + hotel = 'hotel' + flight = 'flight' + job = 'job' + vehicle = 'vehicle' + real_estate = 'real_estate' + education = 'education' + destination = 'destination' + app = 'app' + + +class FeedFormat(StrEnum): + google_merchant_center = 'google_merchant_center' + facebook_catalog = 'facebook_catalog' + shopify = 'shopify' + linkedin_jobs = 'linkedin_jobs' + tiktok_shop = 'tiktok_shop' + pinterest_catalog = 'pinterest_catalog' + openai_product_feed = 'openai_product_feed' + custom = 'custom' + + +class UpdateFrequency(StrEnum): + realtime = 'realtime' + hourly = 'hourly' + daily = 'daily' + weekly = 'weekly' + + +class EventType(StrEnum): + page_view = 'page_view' + view_content = 'view_content' + select_content = 'select_content' + select_item = 'select_item' + search = 'search' + share = 'share' + add_to_cart = 'add_to_cart' + remove_from_cart = 'remove_from_cart' + viewed_cart = 'viewed_cart' + add_to_wishlist = 'add_to_wishlist' + initiate_checkout = 'initiate_checkout' + add_payment_info = 'add_payment_info' + purchase = 'purchase' + refund = 'refund' + lead = 'lead' + qualify_lead = 'qualify_lead' + close_convert_lead = 'close_convert_lead' + disqualify_lead = 'disqualify_lead' + complete_registration = 'complete_registration' + subscribe = 'subscribe' + follow = 'follow' + content_view = 'content_view' + watch_milestone = 'watch_milestone' + start_trial = 'start_trial' + app_install = 'app_install' + app_launch = 'app_launch' + contact = 'contact' + schedule = 'schedule' + donate = 'donate' + submit_application = 'submit_application' + custom = 'custom' + + +class ContentIdType(StrEnum): + sku = 'sku' + gtin = 'gtin' + offering_id = 'offering_id' + job_id = 'job_id' + hotel_id = 'hotel_id' + flight_id = 'flight_id' + vehicle_id = 'vehicle_id' + listing_id = 'listing_id' + store_id = 'store_id' + program_id = 'program_id' + destination_id = 'destination_id' + app_id = 'app_id' + + +class Transform(StrEnum): + date = 'date' + divide = 'divide' + boolean = 'boolean' + split = 'split' # type: ignore[assignment] + + +class CatalogFieldMapping(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + feed_field: Annotated[ + str | None, + Field( + description='Field name in the external feed record. Omit when injecting a static literal value (use the value property instead).' + ), + ] = None + catalog_field: Annotated[ + str | None, + Field( + description="Target field on the catalog item schema, using dot notation for nested fields (e.g., 'name', 'price.amount', 'location.city'). Mutually exclusive with asset_group_id." + ), + ] = None + asset_group_id: Annotated[ + str | None, + Field( + description="Places the feed field value (a URL) into a typed asset pool on the catalog item's assets array. The value is wrapped as an image or video asset in a group with this ID. Use standard group IDs: 'images_landscape', 'images_vertical', 'images_square', 'logo', 'video'. Mutually exclusive with catalog_field." + ), + ] = None + value: Annotated[ + Any | None, + Field( + description='Static literal value to inject into catalog_field for every item, regardless of what the feed contains. Mutually exclusive with feed_field. Useful for fields the feed omits (e.g., currency when price is always USD, or a constant category value).' + ), + ] = None + transform: Annotated[ + Transform | None, + Field( + description='Named transform to apply to the feed field value before writing to the catalog schema. See transform-specific parameters (format, timezone, by, separator).' + ), + ] = None + format: Annotated[ + str | None, + Field( + description="For transform 'date': the input date format string (e.g., 'YYYYMMDD', 'MM/DD/YYYY', 'DD-MM-YYYY'). Output is always ISO 8601 (e.g., '2025-03-01'). Uses Unicode date pattern tokens." + ), + ] = None + timezone: Annotated[ + str | None, + Field( + description="For transform 'date': the timezone of the input value. IANA timezone identifier (e.g., 'UTC', 'America/New_York', 'Europe/Amsterdam'). Defaults to UTC when omitted." + ), + ] = None + by: Annotated[ + StrictFloat | None, + Field( + description="For transform 'divide': the divisor to apply (e.g., 100 to convert integer cents to decimal dollars).", + gt=0.0, + ), + ] = None + separator: Annotated[ + str | None, + Field( + description="For transform 'split': the separator character or string to split on. Defaults to ','." + ), + ] = ',' + default: Annotated[ + Any | None, + Field( + description='Fallback value to use when feed_field is absent, null, or empty. Applied after any transform would have been applied. Allows optional feed fields to have a guaranteed baseline value.' + ), + ] = None + ext: Ext | None = None + + +class IdentityRef(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + handle: Annotated[ + str | None, Field(description='Human-readable handle or username when available.') + ] = None + profile_url: Annotated[ + AnyUrl | None, Field(description='URL for the authoring profile, page, or channel.') + ] = None + platform_identity_id: Annotated[ + str | None, Field(description='Opaque platform-native identity identifier.') + ] = None + + +class Status(StrEnum): + authorized = 'authorized' + pending = 'pending' + expired = 'expired' + revoked = 'revoked' + not_required = 'not_required' + unknown = 'unknown' + + +class ReferenceAuthorization(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + status: Annotated[ + Status, + Field( + description='Current seller-observed authorization state. `authorized`: seller can serve the referenced post. `pending`: additional creator/account action is required. `expired` or `revoked`: authorization previously existed but no longer permits serving. `not_required`: the seller can legally and technically serve without a separate identity authorization. `unknown`: seller has not checked or cannot disclose the state.' + ), + ] + checked_at: Annotated[ + AwareDatetime | None, + Field(description='When the seller last checked the authorization state.'), + ] = None + expires_at: Annotated[ + AwareDatetime | None, + Field(description='When the authorization expires, if the platform exposes an expiry.'), + ] = None + authorization_url: Annotated[ + AnyUrl | None, + Field(description='Optional URL the buyer or creator can use to complete authorization.'), + ] = None + authorization_instructions: Annotated[ + str | None, + Field(description='Human-readable instructions for completing or restoring authorization.'), + ] = None + + +class ReferenceAuthorization1(ReferenceAuthorization): + pass + + +class PlatformExtensionRef(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + uri: Annotated[ + AnyUrl, + Field( + description="HTTPS URL identifying the extension. `https://` is mandatory — `http://`, `file://`, `data:`, and other schemes are rejected at the schema layer (defense-in-depth on top of the fetch-contract normative rules). The URI base is the owning agent's URL; the path identifies the extension within that agent. Example: 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel'. The full fetch contract — SSRF allowlist, response-size cap, $ref sandbox, schema-compile bounds — is documented on `product-format-declaration.json#format_schema` and applies to ALL fetches of this reference shape regardless of whether the field is named `format_schema` (load-bearing for validation) or `platform_extensions` (informational); the *transport* rules are identical, only the *consumption* semantics differ." + ), + ] + digest: Annotated[ + str, + Field( + description='SHA-256 content digest of the extension definition (sha256:). Used to detect drift — if the agent revises the extension, the digest changes and cached definitions become invalid.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] + + +class Method(StrEnum): + img = 'img' + js = 'js' + + +class Location9(Location6): + pass + + +class PixelTrackingEvent(StrEnum): + impression = 'impression' + viewable_mrc_50 = 'viewable_mrc_50' + viewable_mrc_100 = 'viewable_mrc_100' + viewable_video_50 = 'viewable_video_50' + audible_video_complete = 'audible_video_complete' + click = 'click' + custom = 'custom' + + +class Target(StrEnum): + linear = 'linear' + non_linear = 'non_linear' + companion = 'companion' + + +class Target1(StrEnum): + linear = 'linear' + companion = 'companion' + + +class EmbeddedProvenanceItem(AdCPBaseModel): + method: Annotated[ + EmbeddedProvenanceMethod, + Field(description='How provenance data is carried within the content'), + ] + standard: Annotated[ + str | None, + Field( + description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" + ), + ] = None + provider: Annotated[ + str, + Field( + description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." + ), + ] + verify_agent: Annotated[ + VerifyAgent | None, + Field( + description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." + ), + ] = None + embedded_at: Annotated[ + AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') + ] = None + + +class Watermark(AdCPBaseModel): + media_type: Annotated[ + WatermarkMediaType, Field(description='Media category of the watermarked content') + ] + provider: Annotated[ + str, + Field( + description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." + ), + ] + verify_agent: Annotated[ + VerifyAgent1 | None, + Field( + description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." + ), + ] = None + c2pa_action: Annotated[ + C2paWatermarkAction | None, + Field(description='C2PA action classification for this watermark'), + ] = None + embedded_at: Annotated[ + AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') + ] = None + + +class RenderGuidance(AdCPBaseModel): + persistence: Annotated[ + DisclosurePersistence | None, + Field( + description='How long the disclosure must persist during content playback or display' + ), + ] = None + min_duration_ms: Annotated[ + SchemaInt | None, + Field( + description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", + ge=1, + ), + ] = None + positions: Annotated[ + list[DisclosurePosition] | None, + Field( + description='Preferred disclosure positions in priority order. The first position a format supports should be used.', + min_length=1, + ), + ] = None + ext: Ext | None = None + + +class Jurisdiction2(AdCPBaseModel): + country: Annotated[ + str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") + ] + region: Annotated[ + str | None, + Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), + ] = None + regulation: Annotated[ + str, + Field( + description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" + ), + ] + label_text: Annotated[ + str | None, + Field( + description='Required disclosure label text for this jurisdiction, in the local language' + ), + ] = None + render_guidance: Annotated[ + RenderGuidance | None, + Field( + description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." + ), + ] = None + + +class Disclosure(AdCPBaseModel): + required: Annotated[ + StrictBool, + Field( + description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." + ), + ] + jurisdictions: Annotated[ + list[Jurisdiction2] | None, + Field(description='Jurisdictions where disclosure obligations apply', min_length=1), + ] = None + + +class Provenance(AdCPBaseModel): + digital_source_type: Annotated[ + DigitalSourceType | None, + Field( + description='IPTC-aligned classification of AI involvement in producing this content' + ), + ] = None + synthetic_depiction: Annotated[ + StrictBool | None, + Field( + description='Assessed declaration of whether the content synthetically depicts a real or fictional person performing or appearing in a way that was generated or materially manipulated rather than captured as depicted. `true` covers both a fully synthetic performer and material manipulation of a real performer; `false` is an assessed declaration that the content does not contain such a depiction. Absence means the content has not been assessed for synthetic depiction. This field does not claim consent, legality, or independent verification, and receivers MUST NOT derive it solely from `digital_source_type`.' + ), + ] = None + ai_tool: Annotated[ + AiTool | None, + Field( + description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' + ), + ] = None + human_oversight: Annotated[ + HumanOversight | None, + Field( + description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' + ), + ] = None + declared_by: Annotated[ + DeclaredBy | None, + Field( + description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' + ), + ] = None + declared_at: Annotated[ + AwareDatetime | None, + Field( + description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' + ), + ] = None + created_time: Annotated[ + AwareDatetime | None, + Field(description='When this content was created or generated (ISO 8601)'), + ] = None + c2pa: Annotated[ + C2pa | None, + Field( + description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' + ), + ] = None + embedded_provenance: Annotated[ + list[EmbeddedProvenanceItem] | None, + Field( + description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', + min_length=1, + ), + ] = None + watermarks: Annotated[ + list[Watermark] | None, + Field( + description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', + min_length=1, + ), + ] = None + disclosure: Annotated[ + Disclosure | None, + Field( + description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' + ), + ] = None + verification: Annotated[ + list[VerificationItem] | None, + Field( + description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', + min_length=1, + ), + ] = None + ext: Ext | None = None + + +class VideoAsset(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['video'], + Field( + description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'video' + url: Annotated[AnyUrl, Field(description='URL to the video asset')] + width: Annotated[ + SchemaInt, + Field( + description="Width in pixels — the video file's intrinsic native width. Required: a hosted file always has concrete dimensions. (Tag-delivered video carries no width; see the `vast` asset.)", + ge=1, + ), + ] + height: Annotated[ + SchemaInt, + Field( + description="Height in pixels — the video file's intrinsic native height. Required: a hosted file always has concrete dimensions. (Tag-delivered video carries no height; see the `vast` asset.)", + ge=1, + ), + ] + duration_ms: Annotated[ + SchemaInt | None, Field(description='Video duration in milliseconds', ge=1) + ] = None + file_size_bytes: Annotated[SchemaInt | None, Field(description='File size in bytes', ge=1)] = ( + None + ) + container_format: Annotated[ + str | None, Field(description='Video container format (mp4, webm, mov, etc.)') + ] = None + video_codec: Annotated[ + str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') + ] = None + video_bitrate_kbps: Annotated[ + SchemaInt | None, Field(description='Video stream bitrate in kilobits per second', ge=1) + ] = None + frame_rate: Annotated[ + str | None, + Field( + description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" + ), + ] = None + frame_rate_type: FrameRateType | None = None + scan_type: ScanType | None = None + color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None + hdr_format: Annotated[ + HdrFormat | None, + Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), + ] = None + chroma_subsampling: Annotated[ + ChromaSubsampling | None, Field(description='Chroma subsampling format') + ] = None + video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None + gop_interval_seconds: Annotated[ + StrictFloat | None, Field(description='GOP/keyframe interval in seconds') + ] = None + gop_type: GopType | None = None + moov_atom_position: MoovAtomPosition | None = None + has_audio: Annotated[ + StrictBool | None, Field(description='Whether the video contains an audio track') + ] = None + audio_codec: Annotated[ + str | None, + Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), + ] = None + audio_sampling_rate_hz: Annotated[ + SchemaInt | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') + ] = None + audio_channels: Annotated[ + AudioChannelLayout | None, Field(description='Audio channel configuration') + ] = None + audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None + audio_bitrate_kbps: Annotated[ + SchemaInt | None, Field(description='Audio bitrate in kilobits per second', ge=1) + ] = None + audio_loudness_lufs: Annotated[ + StrictFloat | None, Field(description='Integrated loudness in LUFS') + ] = None + audio_true_peak_dbfs: Annotated[ + StrictFloat | None, Field(description='True peak level in dBFS') + ] = None + captions_url: Annotated[ + AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') + ] = None + transcript_url: Annotated[ + AnyUrl | None, Field(description='URL to text transcript of the video content') + ] = None + audio_description_url: Annotated[ + AnyUrl | None, + Field(description='URL to audio description track for visually impaired users'), + ] = None + provenance: Annotated[ + Provenance | None, + Field( + description='Provenance metadata for this asset, overrides manifest-level provenance' + ), + ] = None + + +class AudioAsset(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['audio'], + Field( + description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'audio' + url: Annotated[AnyUrl, Field(description='URL to the audio asset')] + duration_ms: Annotated[ + SchemaInt | None, Field(description='Audio duration in milliseconds', ge=0) + ] = None + file_size_bytes: Annotated[SchemaInt | None, Field(description='File size in bytes', ge=1)] = ( + None + ) + container_format: Annotated[ + str | None, + Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), + ] = None + codec: Annotated[ + str | None, + Field( + description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' + ), + ] = None + sampling_rate_hz: Annotated[ + SchemaInt | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') + ] = None + channels: Annotated[AudioChannelLayout | None, Field(description='Channel configuration')] = ( + None + ) + bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None + bitrate_kbps: Annotated[ + SchemaInt | None, Field(description='Bitrate in kilobits per second', ge=1) + ] = None + loudness_lufs: Annotated[ + StrictFloat | None, Field(description='Integrated loudness in LUFS') + ] = None + true_peak_dbfs: Annotated[StrictFloat | None, Field(description='True peak level in dBFS')] = ( + None + ) + transcript_url: Annotated[ + AnyUrl | None, Field(description='URL to text transcript of the audio content') + ] = None + provenance: Annotated[ + Provenance | None, + Field( + description='Provenance metadata for this asset, overrides manifest-level provenance' + ), + ] = None + + +class Location1(Location8): + pass + + +class Location2(Location): + pass + + +class MacroTranslationTarget(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + token: Annotated[str, Field(min_length=1)] + dialect: MacroDialect + dialect_namespace: AnyUrl | None = None + dialect_revision: Annotated[str | None, Field(min_length=1)] = None + dialect_semantic: Annotated[str, Field(min_length=1)] + mapping_status: MacroMappingStatus + universal_semantic: UniversalMacro | None = None + next_operation: Literal['resolve_value'] = 'resolve_value' + performed_by: MacroResolver + encoding: MacroEncoding + + +class TextAsset(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['text'], + Field( + description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'text' + content: Annotated[str, Field(description='Text content')] + language: Annotated[ + str | None, + Field( + description='Optional language claim for this text. In a materialized creative localization variant, localized-creative-asset.json requires this value to use /schemas/core/locale-tag.json and conformance requires exact equality with the enclosing variant locale. General non-localized assets retain the legacy unconstrained string for compatibility.' + ), + ] = None + provenance: Annotated[ + Provenance | None, + Field( + description='Provenance metadata for this asset, overrides manifest-level provenance' + ), + ] = None + + +class HtmlAsset(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['html'], + Field( + description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'html' + content: Annotated[str, Field(description='HTML content')] + version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None + accessibility: Annotated[ + Accessibility | None, + Field(description='Self-declared accessibility properties for this opaque creative'), + ] = None + provenance: Annotated[ + Provenance | None, + Field( + description='Provenance metadata for this asset, overrides manifest-level provenance' + ), + ] = None + + +class JavascriptAsset(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['javascript'], + Field( + description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'javascript' + content: Annotated[str, Field(description='JavaScript content')] + module_type: Annotated[ + JavascriptModuleType | None, Field(description='JavaScript module type') + ] = None + accessibility: Annotated[ + Accessibility | None, + Field(description='Self-declared accessibility properties for this opaque creative'), + ] = None + provenance: Annotated[ + Provenance | None, + Field( + description='Provenance metadata for this asset, overrides manifest-level provenance' + ), + ] = None + + +class ZipAsset(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['zip'], + Field( + description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'zip' + url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] + max_file_size_kb: Annotated[ + SchemaInt | None, + Field( + description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', + ge=0, + ), + ] = None + entry_point: Annotated[ + str | None, + Field( + description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." + ), + ] = None + allowed_inner_extensions: Annotated[ + list[str] | None, + Field( + description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." + ), + ] = None + backup_image_url: Annotated[ + AnyUrl | None, + Field( + description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' + ), + ] = None + digest: Annotated[ + str | None, + Field( + description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + accessibility: Annotated[ + Accessibility | None, + Field(description='Self-declared accessibility properties for this opaque creative'), + ] = None + provenance: Annotated[ + Provenance | None, + Field( + description='Provenance metadata for this asset, overrides manifest-level provenance' + ), + ] = None + + +class Security(AdCPBaseModel): + method: Annotated[WebhookSecurityMethod, Field(description='Authentication method')] + hmac_header: Annotated[ + str | None, Field(description="Header name for HMAC signature (e.g., 'X-Signature')") + ] = None + api_key_header: Annotated[ + str | None, Field(description="Header name for API key (e.g., 'X-API-Key')") + ] = None + + +class WebhookAsset(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['webhook'], + Field( + description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'webhook' + url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] + method: Annotated[HttpMethod | None, Field(description='HTTP method')] = HttpMethod.POST + timeout_ms: Annotated[ + SchemaInt | None, + Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), + ] = 500 + supported_macros: Annotated[ + list[UniversalMacro | str] | None, + Field( + description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' + ), + ] = None + required_macros: Annotated[ + list[UniversalMacro | str] | None, + Field(description='Universal macros that must be provided for webhook to function'), + ] = None + response_type: Annotated[ + WebhookResponseType, Field(description='Expected content type of webhook response') + ] + security: Annotated[Security, Field(description='Security configuration for webhook calls')] + provenance: Annotated[ + Provenance | None, + Field( + description='Provenance metadata for this asset, overrides manifest-level provenance' + ), + ] = None + + +class CssAsset(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['css'], + Field( + description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'css' + content: Annotated[str, Field(description='CSS content')] + media: Annotated[ + str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") + ] = None + provenance: Annotated[ + Provenance | None, + Field( + description='Provenance metadata for this asset, overrides manifest-level provenance' + ), + ] = None + + +class MacroDeclaration6(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + declaration_id: Annotated[ + str, + Field( + description='Identifier unique within the enclosing asset, used to correlate validation results.', + min_length=1, + pattern='^[A-Za-z0-9_-]+$', + ), + ] + token: Annotated[ + str, + Field( + description='Exact byte-preserved token expression at this occurrence, including delimiters.', + min_length=1, + ), + ] + dialect: MacroDialect + dialect_namespace: Annotated[ + AnyUrl | None, + Field( + description='Authority-controlled registry or mapping URI. Required for IAB VAST, IAB DAAST, and vendor dialects.' + ), + ] = None + dialect_revision: Annotated[ + str | None, + Field( + description='Immutable registry/mapping revision, release, commit, or digest. Required for IAB VAST, IAB DAAST, and vendor dialects.', + min_length=1, + ), + ] = None + dialect_semantic: Annotated[ + str, + Field( + description='Exact semantic identifier in the cited dialect, such as `CACHEBUSTING`, `PLAYERSTATE`, or a documented vendor name. This is not inferred from token spelling.', + min_length=1, + ), + ] + mapping_status: MacroMappingStatus + universal_semantic: Annotated[ + UniversalMacro | None, + Field( + description='Verified AdCP universal meaning. Present only when mapping_status is `verified_universal`.' + ), + ] = None + operation: MacroProcessingOperation + performed_by: Annotated[ + MacroResolver | None, + Field( + description='Actor authorized to perform the declared translation or value resolution. Omitted for preservation.' + ), + ] = None + translation_target: Annotated[ + MacroTranslationTarget | None, + Field( + description='Required only for `translate_to_native`. The output token contract that replaces this declaration after translation.' + ), + ] = None + location: Location6 + encoding: Annotated[ + MacroEncoding, + Field( + description='Encoding for the concrete value. Translation/preservation uses `none` at depth zero.' + ), + ] + required: Annotated[ + StrictBool, + Field( + description='Whether terminal delivery must fail if the declared operation cannot complete.' + ), + ] + unavailable_behavior: Annotated[ + UnavailableBehavior, + Field( + description='Action when the responsible actor has no value. Omission is legal only for a complete URL query-value occurrence; dialect sentinels require a cited dialect rule.' + ), + ] + + +class DaastAsset1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['daast'], + Field( + description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'daast' + daast_version: Annotated[ + DaastVersion | None, Field(description='DAAST specification version') + ] = None + duration_ms: Annotated[ + SchemaInt | None, + Field(description='Expected audio duration in milliseconds (if known)', ge=0), + ] = None + tracking_events: Annotated[ + list[DaastTrackingEvent] | None, + Field(description='Tracking events supported by this DAAST tag'), + ] = None + companion_ads: Annotated[ + StrictBool | None, Field(description='Whether companion display ads are included') + ] = None + transcript_url: Annotated[ + AnyUrl | None, Field(description='URL to text transcript of the audio content') + ] = None + provenance: Annotated[ + Provenance | None, + Field( + description='Provenance metadata for this asset, overrides manifest-level provenance' + ), + ] = None + macro_declarations: Annotated[ + list[MacroDeclaration6] | None, + Field( + description='One declaration per occurrence in a field carried by this asset. URL-delivered assets declare only locator-URL occurrences; receivers do not infer declarations for tokens discovered later in a fetched document.', + min_length=1, + ), + ] = None + delivery_type: Annotated[ + Literal['url'], + Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), + ] = 'url' + url: Annotated[ + MacroBearingUrl, + Field( + description='URL endpoint returning DAAST XML. Macro delimiters remain byte-preserved and are processed only under attached occurrence declarations.' + ), + ] + + +class MacroDeclaration7(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + declaration_id: Annotated[ + str, + Field( + description='Identifier unique within the enclosing asset, used to correlate validation results.', + min_length=1, + pattern='^[A-Za-z0-9_-]+$', + ), + ] + token: Annotated[ + str, + Field( + description='Exact byte-preserved token expression at this occurrence, including delimiters.', + min_length=1, + ), + ] + dialect: MacroDialect + dialect_namespace: Annotated[ + AnyUrl | None, + Field( + description='Authority-controlled registry or mapping URI. Required for IAB VAST, IAB DAAST, and vendor dialects.' + ), + ] = None + dialect_revision: Annotated[ + str | None, + Field( + description='Immutable registry/mapping revision, release, commit, or digest. Required for IAB VAST, IAB DAAST, and vendor dialects.', + min_length=1, + ), + ] = None + dialect_semantic: Annotated[ + str, + Field( + description='Exact semantic identifier in the cited dialect, such as `CACHEBUSTING`, `PLAYERSTATE`, or a documented vendor name. This is not inferred from token spelling.', + min_length=1, + ), + ] + mapping_status: MacroMappingStatus + universal_semantic: Annotated[ + UniversalMacro | None, + Field( + description='Verified AdCP universal meaning. Present only when mapping_status is `verified_universal`.' + ), + ] = None + operation: MacroProcessingOperation + performed_by: Annotated[ + MacroResolver | None, + Field( + description='Actor authorized to perform the declared translation or value resolution. Omitted for preservation.' + ), + ] = None + translation_target: Annotated[ + MacroTranslationTarget | None, + Field( + description='Required only for `translate_to_native`. The output token contract that replaces this declaration after translation.' + ), + ] = None + location: Location8 + encoding: Annotated[ + MacroEncoding, + Field( + description='Encoding for the concrete value. Translation/preservation uses `none` at depth zero.' + ), + ] + required: Annotated[ + StrictBool, + Field( + description='Whether terminal delivery must fail if the declared operation cannot complete.' + ), + ] + unavailable_behavior: Annotated[ + UnavailableBehavior, + Field( + description='Action when the responsible actor has no value. Omission is legal only for a complete URL query-value occurrence; dialect sentinels require a cited dialect rule.' + ), + ] + + +class DaastAsset2(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['daast'], + Field( + description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'daast' + daast_version: Annotated[ + DaastVersion | None, Field(description='DAAST specification version') + ] = None + duration_ms: Annotated[ + SchemaInt | None, + Field(description='Expected audio duration in milliseconds (if known)', ge=0), + ] = None + tracking_events: Annotated[ + list[DaastTrackingEvent] | None, + Field(description='Tracking events supported by this DAAST tag'), + ] = None + companion_ads: Annotated[ + StrictBool | None, Field(description='Whether companion display ads are included') + ] = None + transcript_url: Annotated[ + AnyUrl | None, Field(description='URL to text transcript of the audio content') + ] = None + provenance: Annotated[ + Provenance | None, + Field( + description='Provenance metadata for this asset, overrides manifest-level provenance' + ), + ] = None + macro_declarations: Annotated[ + list[MacroDeclaration7] | None, + Field( + description='One declaration per occurrence in a field carried by this asset. URL-delivered assets declare only locator-URL occurrences; receivers do not infer declarations for tokens discovered later in a fetched document.', + min_length=1, + ), + ] = None + delivery_type: Annotated[ + Literal['inline'], + Field(description='Discriminator indicating DAAST is delivered as inline XML content'), + ] = 'inline' + content: Annotated[str, Field(description='Inline DAAST XML content')] + + +class DaastAsset(RootModel[DaastAsset1 | DaastAsset2]): + root: Annotated[ + DaastAsset1 | DaastAsset2, + Field( + description='DAAST (Digital Audio Ad Serving Template) tag for third-party audio ad serving', + discriminator='delivery_type', + title='DAAST Asset', + ), + ] + def __getattr__(self, name: str) -> Any: + """Proxy attribute access to the wrapped type.""" + if name.startswith('_'): + raise AttributeError(name) + return getattr(self.root, name) + + + +class MarkdownAsset(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['markdown'], + Field( + description='Discriminator identifying this as a markdown asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'markdown' + content: Annotated[ + str, + Field( + description='Markdown content following CommonMark spec with optional GitHub Flavored Markdown extensions' + ), + ] + language: Annotated[ + str | None, + Field( + description='Optional language claim for this markdown. In a materialized creative localization variant, localized-creative-asset.json requires this value to use /schemas/core/locale-tag.json and conformance requires exact equality with the enclosing variant locale. General non-localized assets retain the legacy unconstrained string for compatibility.' + ), + ] = None + markdown_flavor: Annotated[ + MarkdownFlavor | None, + Field( + description='Markdown flavor used. CommonMark for strict compatibility, GFM for tables/task lists/strikethrough.' + ), + ] = MarkdownFlavor.commonmark + allow_raw_html: Annotated[ + StrictBool | None, + Field( + description='Whether raw HTML blocks are allowed in the markdown. False recommended for security.' + ), + ] = False + + +class CreativeBrief(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + name: Annotated[str, Field(description='Campaign or flight name for identification')] + objective: Annotated[ + Objective | None, + Field( + description='Campaign objective that guides creative tone and call-to-action strategy' + ), + ] = None + tone: Annotated[ + str | None, + Field( + description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" + ), + ] = None + audience: Annotated[ + str | None, Field(description='Target audience description for this campaign') + ] = None + territory: Annotated[ + str | None, + Field(description='Creative territory or positioning the campaign should occupy'), + ] = None + messaging: Annotated[ + Messaging | None, Field(description='Messaging framework for the campaign') + ] = None + reference_assets: Annotated[ + list[ReferenceAsset] | None, + Field( + description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' + ), + ] = None + compliance: Annotated[ + Compliance | None, + Field( + description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' + ), + ] = None + + +class Catalog(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + catalog_id: Annotated[ + str | None, + Field( + description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." + ), + ] = None + name: Annotated[ + str | None, + Field( + description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." + ), + ] = None + type: Annotated[ + CatalogType, + Field( + description="Catalog type. Structural types: 'offering' (AdCP Offering objects), 'product' (ecommerce entries), 'inventory' (stock per location), 'store' (physical locations), 'promotion' (deals and pricing). Vertical types: 'hotel', 'flight', 'job', 'vehicle', 'real_estate', 'education', 'destination', 'app' — each with an industry-specific item schema." + ), + ] + url: Annotated[ + AnyUrl | None, + Field( + description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." + ), + ] = None + feed_format: Annotated[ + FeedFormat | None, + Field( + description='Format of the external feed at url. Required when url points to a non-AdCP feed (e.g., Google Merchant Center XML, Meta Product Catalog). Omit for offering-type catalogs where the feed is native AdCP JSON.' + ), + ] = None + update_frequency: Annotated[ + UpdateFrequency | None, + Field( + description='How often the platform should re-fetch the feed from url. Only applicable when url is provided. Platforms may use this as a hint for polling schedules.' + ), + ] = None + items: Annotated[ + list[dict[str, Any]] | None, + Field( + description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", + min_length=1, + ), + ] = None + ids: Annotated[ + list[str] | None, + Field( + description='Filter catalog to exact canonical item keys. The key field is offering_id for offering, store_id for store, hotel_id for hotel, flight_id for flight, job_id for job, vehicle_id for vehicle, listing_id for real_estate, program_id for education, destination_id for destination, and app_id for app. Product, inventory, and promotion catalogs use the stable normalized source identifier retained during ingestion (for example a retailer SKU). The same canonical key is used by catalog availability item_id.', + min_length=1, + ), + ] = None + gtins: Annotated[ + list[Gtin] | None, + Field( + description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", + min_length=1, + ), + ] = None + tags: Annotated[ + list[str] | None, + Field( + description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', + min_length=1, + ), + ] = None + category: Annotated[ + str | None, + Field( + description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." + ), + ] = None + query: Annotated[ + str | None, + Field( + description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." + ), + ] = None + conversion_events: Annotated[ + list[EventType] | None, + Field( + description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", + min_length=1, + ), + ] = None + content_id_type: Annotated[ + ContentIdType | None, + Field( + description="Identifier type that the event's content_ids field should be matched against for items in this catalog. For example, 'gtin' means content_ids values are Global Trade Item Numbers, 'sku' means retailer SKUs. Omit when using a custom identifier scheme not listed in the enum." + ), + ] = None + feed_field_mappings: Annotated[ + list[CatalogFieldMapping] | None, + Field( + description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', + min_length=1, + ), + ] = None + + +class PublishedPostAsset1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['published_post'], + Field( + description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'published_post' + post_url: Annotated[ + AnyUrl, + Field( + description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' + ), + ] + platform: Annotated[ + str | None, + Field( + description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." + ), + ] = None + platform_post_id: Annotated[ + str | None, + Field( + description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' + ), + ] = None + identity_ref: Annotated[ + IdentityRef | None, + Field( + description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' + ), + ] = None + published_at: Annotated[ + AwareDatetime | None, + Field(description='When the referenced post was originally published, if known.'), + ] = None + reference_authorization: Annotated[ + ReferenceAuthorization | None, + Field( + description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' + ), + ] = None + provenance: Annotated[ + Provenance | None, + Field( + description='Provenance metadata for this reference asset, overrides manifest-level provenance.' + ), + ] = None + + +class PublishedPostAsset2(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['published_post'], + Field( + description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'published_post' + post_url: Annotated[ + AnyUrl | None, + Field( + description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' + ), + ] = None + platform: Annotated[ + str | None, + Field( + description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." + ), + ] = None + platform_post_id: Annotated[ + str, + Field( + description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' + ), + ] + identity_ref: Annotated[ + IdentityRef | None, + Field( + description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' + ), + ] = None + published_at: Annotated[ + AwareDatetime | None, + Field(description='When the referenced post was originally published, if known.'), + ] = None + reference_authorization: Annotated[ + ReferenceAuthorization1 | None, + Field( + description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' + ), + ] = None + provenance: Annotated[ + Provenance | None, + Field( + description='Provenance metadata for this reference asset, overrides manifest-level provenance.' + ), + ] = None + + +class PublishedPostAsset(RootModel[PublishedPostAsset1 | PublishedPostAsset2]): + root: Annotated[ + PublishedPostAsset1 | PublishedPostAsset2, + Field( + description="Reference to an already-published post on a publisher, creator, or social platform. The buyer supplies a reference to the existing post; the seller resolves, authorizes, reviews, and serves the referenced post according to the target product's format declaration. This asset is for post references, not uploaded bytes and not catalog rows.", + title='Published Post Asset', + ), + ] + def __getattr__(self, name: str) -> Any: + """Proxy attribute access to the wrapped type.""" + if name.startswith('_'): + raise AttributeError(name) + return getattr(self.root, name) + + + +class ImageAsset(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['image'], + Field( + description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'image' + url: Annotated[AnyUrl, Field(description='URL to the image asset')] + width: Annotated[SchemaInt, Field(description='Width in pixels', ge=1)] + height: Annotated[SchemaInt, Field(description='Height in pixels', ge=1)] + file_size_bytes: Annotated[ + SchemaInt | None, + Field( + description='Image file size in bytes. Required by agents that advertise a max_file_size_kb constraint.', + ge=1, + ), + ] = None + pixel_ratio: Annotated[ + StrictFloat | None, + Field( + description='Intrinsic pixels per logical render pixel (for example `2` for a 600×500 image intended to render at 300×250). Optional because a validator can infer the ratio when the target format declares logical dimensions. When supplied, it MUST agree with both `width / logical_width` and `height / logical_height`; it is never a substitute for the intrinsic `width` and `height` fields.', + gt=0.0, + ), + ] = None + state_id: Annotated[ + str | None, + Field( + description='Binding used only when this image populates a `seller_rendered_stateful_display` `state_canvases` slot. It MUST match one declared `states[].state_id` (semantic validators resolve it). Omit for ordinary image slots.' + ), + ] = None + breakpoint_id: Annotated[ + str | None, + Field( + description='Binding used only when this image populates a `seller_rendered_stateful_display` `state_canvases` slot. It MUST match one breakpoint declared on the selected state (semantic validators resolve it). Omit for ordinary image slots.' + ), + ] = None + focal_point: Annotated[ + list[FocalPointItem] | None, + Field( + description="Normalized `[x, y]` coordinates (0–1 from top-left) of the image's visual anchor. Seller-side renderers crop toward the focal point when deriving renditions across breakpoints and aspect ratios; absent, cropping falls back to center-weighted defaults.", + max_length=2, + min_length=2, + ), + ] = None + format: Annotated[ + str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') + ] = None + alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None + provenance: Annotated[ + Provenance | None, + Field( + description='Provenance metadata for this asset, overrides manifest-level provenance' + ), + ] = None + + +class MacroDeclaration(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + declaration_id: Annotated[ + str, + Field( + description='Identifier unique within the enclosing asset, used to correlate validation results.', + min_length=1, + pattern='^[A-Za-z0-9_-]+$', + ), + ] + token: Annotated[ + str, + Field( + description='Exact byte-preserved token expression at this occurrence, including delimiters.', + min_length=1, + ), + ] + dialect: MacroDialect + dialect_namespace: Annotated[ + AnyUrl | None, + Field( + description='Authority-controlled registry or mapping URI. Required for IAB VAST, IAB DAAST, and vendor dialects.' + ), + ] = None + dialect_revision: Annotated[ + str | None, + Field( + description='Immutable registry/mapping revision, release, commit, or digest. Required for IAB VAST, IAB DAAST, and vendor dialects.', + min_length=1, + ), + ] = None + dialect_semantic: Annotated[ + str, + Field( + description='Exact semantic identifier in the cited dialect, such as `CACHEBUSTING`, `PLAYERSTATE`, or a documented vendor name. This is not inferred from token spelling.', + min_length=1, + ), + ] + mapping_status: MacroMappingStatus + universal_semantic: Annotated[ + UniversalMacro | None, + Field( + description='Verified AdCP universal meaning. Present only when mapping_status is `verified_universal`.' + ), + ] = None + operation: MacroProcessingOperation + performed_by: Annotated[ + MacroResolver | None, + Field( + description='Actor authorized to perform the declared translation or value resolution. Omitted for preservation.' + ), + ] = None + translation_target: Annotated[ + MacroTranslationTarget | None, + Field( + description='Required only for `translate_to_native`. The output token contract that replaces this declaration after translation.' + ), + ] = None + location: Location9 + encoding: Annotated[ + MacroEncoding, + Field( + description='Encoding for the concrete value. Translation/preservation uses `none` at depth zero.' + ), + ] + required: Annotated[ + StrictBool, + Field( + description='Whether terminal delivery must fail if the declared operation cannot complete.' + ), + ] + unavailable_behavior: Annotated[ + UnavailableBehavior, + Field( + description='Action when the responsible actor has no value. Omission is legal only for a complete URL query-value occurrence; dialect sentinels require a cited dialect rule.' + ), + ] + + +class VastAsset1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['vast'], + Field( + description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'vast' + vast_version: Annotated[ + VastVersion | None, + Field( + description='Exact VAST version declared by the supplied URL response or inline document. Required by the 3.2 canonical `video_vast` and `audio_vast` manifest paths; optional only on the deprecated named-format compatibility path. Receivers MUST NOT relabel or synthesize a newer version merely because the destination accepts it.' + ), + ] = None + macro_declarations: Annotated[ + list[MacroDeclaration] | None, + Field( + description='One declaration per exact occurrence in a field carried by this asset. A URL-delivered asset can declare only occurrences in its locator `url`; tokens discovered later in a fetched VAST response require document validation evidence or an inline/snapshotted asset and MUST NOT be guessed from the locator. IAB tokens cite a registry namespace and revision rather than copying the live registry into AdCP.', + min_length=1, + ), + ] = None + vpaid_enabled: Annotated[ + StrictBool | None, + Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), + ] = None + duration_ms: Annotated[ + SchemaInt | None, + Field(description='Expected media duration in milliseconds (if known)', ge=0), + ] = None + tracking_events: Annotated[ + list[VastTrackingEvent] | None, + Field(description='Tracking events supported by this VAST tag'), + ] = None + captions_url: Annotated[ + AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') + ] = None + audio_description_url: Annotated[ + AnyUrl | None, + Field(description='URL to audio description track for visually impaired users'), + ] = None + provenance: Annotated[ + Provenance | None, + Field( + description='Provenance metadata for this asset, overrides manifest-level provenance' + ), + ] = None + delivery_type: Annotated[ + Literal['url'], + Field(description='Discriminator indicating VAST is delivered via URL endpoint'), + ] = 'url' + url: Annotated[ + MacroBearingUrl, + Field( + description='URL endpoint returning VAST XML. Macro delimiters remain byte-preserved; declarations distinguish occurrences in this locator URL from occurrences in inline or fetched VAST content.' + ), + ] + + +class MacroDeclaration1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + declaration_id: Annotated[ + str, + Field( + description='Identifier unique within the enclosing asset, used to correlate validation results.', + min_length=1, + pattern='^[A-Za-z0-9_-]+$', + ), + ] + token: Annotated[ + str, + Field( + description='Exact byte-preserved token expression at this occurrence, including delimiters.', + min_length=1, + ), + ] + dialect: MacroDialect + dialect_namespace: Annotated[ + AnyUrl | None, + Field( + description='Authority-controlled registry or mapping URI. Required for IAB VAST, IAB DAAST, and vendor dialects.' + ), + ] = None + dialect_revision: Annotated[ + str | None, + Field( + description='Immutable registry/mapping revision, release, commit, or digest. Required for IAB VAST, IAB DAAST, and vendor dialects.', + min_length=1, + ), + ] = None + dialect_semantic: Annotated[ + str, + Field( + description='Exact semantic identifier in the cited dialect, such as `CACHEBUSTING`, `PLAYERSTATE`, or a documented vendor name. This is not inferred from token spelling.', + min_length=1, + ), + ] + mapping_status: MacroMappingStatus + universal_semantic: Annotated[ + UniversalMacro | None, + Field( + description='Verified AdCP universal meaning. Present only when mapping_status is `verified_universal`.' + ), + ] = None + operation: MacroProcessingOperation + performed_by: Annotated[ + MacroResolver | None, + Field( + description='Actor authorized to perform the declared translation or value resolution. Omitted for preservation.' + ), + ] = None + translation_target: Annotated[ + MacroTranslationTarget | None, + Field( + description='Required only for `translate_to_native`. The output token contract that replaces this declaration after translation.' + ), + ] = None + location: Location1 + encoding: Annotated[ + MacroEncoding, + Field( + description='Encoding for the concrete value. Translation/preservation uses `none` at depth zero.' + ), + ] + required: Annotated[ + StrictBool, + Field( + description='Whether terminal delivery must fail if the declared operation cannot complete.' + ), + ] + unavailable_behavior: Annotated[ + UnavailableBehavior, + Field( + description='Action when the responsible actor has no value. Omission is legal only for a complete URL query-value occurrence; dialect sentinels require a cited dialect rule.' + ), + ] + + +class VastAsset2(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['vast'], + Field( + description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'vast' + vast_version: Annotated[ + VastVersion | None, + Field( + description='Exact VAST version declared by the supplied URL response or inline document. Required by the 3.2 canonical `video_vast` and `audio_vast` manifest paths; optional only on the deprecated named-format compatibility path. Receivers MUST NOT relabel or synthesize a newer version merely because the destination accepts it.' + ), + ] = None + macro_declarations: Annotated[ + list[MacroDeclaration1] | None, + Field( + description='One declaration per exact occurrence in a field carried by this asset. A URL-delivered asset can declare only occurrences in its locator `url`; tokens discovered later in a fetched VAST response require document validation evidence or an inline/snapshotted asset and MUST NOT be guessed from the locator. IAB tokens cite a registry namespace and revision rather than copying the live registry into AdCP.', + min_length=1, + ), + ] = None + vpaid_enabled: Annotated[ + StrictBool | None, + Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), + ] = None + duration_ms: Annotated[ + SchemaInt | None, + Field(description='Expected media duration in milliseconds (if known)', ge=0), + ] = None + tracking_events: Annotated[ + list[VastTrackingEvent] | None, + Field(description='Tracking events supported by this VAST tag'), + ] = None + captions_url: Annotated[ + AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') + ] = None + audio_description_url: Annotated[ + AnyUrl | None, + Field(description='URL to audio description track for visually impaired users'), + ] = None + provenance: Annotated[ + Provenance | None, + Field( + description='Provenance metadata for this asset, overrides manifest-level provenance' + ), + ] = None + delivery_type: Annotated[ + Literal['inline'], + Field(description='Discriminator indicating VAST is delivered as inline XML content'), + ] = 'inline' + content: Annotated[str, Field(description='Inline VAST XML content')] + + +class VastAsset(RootModel[VastAsset1 | VastAsset2]): + root: Annotated[ + VastAsset1 | VastAsset2, + Field( + description='VAST (Video Ad Serving Template) tag for third-party video or audio ad serving. Unlike a hosted media asset, a VAST tag carries no single `width`/`height`: a response can return multiple renditions and the player selects one at serve time. Standardized VAST audio support begins at 4.1 and uses MediaFile width and height values of 0; older audio-in-VAST versions are seller-declared legacy interoperability. Dimensional, duration, MIME-type, and codec constraints live on the format/requirements layer, not on this asset.', + discriminator='delivery_type', + title='VAST Asset', + ), + ] + def __getattr__(self, name: str) -> Any: + """Proxy attribute access to the wrapped type.""" + if name.startswith('_'): + raise AttributeError(name) + return getattr(self.root, name) + + + +class MacroDeclarationModel(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + declaration_id: Annotated[ + str, + Field( + description='Identifier unique within the enclosing asset, used to correlate validation results.', + min_length=1, + pattern='^[A-Za-z0-9_-]+$', + ), + ] + token: Annotated[ + str, + Field( + description='Exact byte-preserved token expression at this occurrence, including delimiters.', + min_length=1, + ), + ] + dialect: MacroDialect + dialect_namespace: Annotated[ + AnyUrl | None, + Field( + description='Authority-controlled registry or mapping URI. Required for IAB VAST, IAB DAAST, and vendor dialects.' + ), + ] = None + dialect_revision: Annotated[ + str | None, + Field( + description='Immutable registry/mapping revision, release, commit, or digest. Required for IAB VAST, IAB DAAST, and vendor dialects.', + min_length=1, + ), + ] = None + dialect_semantic: Annotated[ + str, + Field( + description='Exact semantic identifier in the cited dialect, such as `CACHEBUSTING`, `PLAYERSTATE`, or a documented vendor name. This is not inferred from token spelling.', + min_length=1, + ), + ] + mapping_status: MacroMappingStatus + universal_semantic: Annotated[ + UniversalMacro | None, + Field( + description='Verified AdCP universal meaning. Present only when mapping_status is `verified_universal`.' + ), + ] = None + operation: MacroProcessingOperation + performed_by: Annotated[ + MacroResolver | None, + Field( + description='Actor authorized to perform the declared translation or value resolution. Omitted for preservation.' + ), + ] = None + translation_target: Annotated[ + MacroTranslationTarget | None, + Field( + description='Required only for `translate_to_native`. The output token contract that replaces this declaration after translation.' + ), + ] = None + location: Location2 + encoding: Annotated[ + MacroEncoding, + Field( + description='Encoding for the concrete value. Translation/preservation uses `none` at depth zero.' + ), + ] + required: Annotated[ + StrictBool, + Field( + description='Whether terminal delivery must fail if the declared operation cannot complete.' + ), + ] + unavailable_behavior: Annotated[ + UnavailableBehavior, + Field( + description='Action when the responsible actor has no value. Omission is legal only for a complete URL query-value occurrence; dialect sentinels require a cited dialect rule.' + ), + ] + + +class MacroDeclaration2(MacroDeclarationModel): + model_config = ConfigDict( + extra='forbid', + ) + location: Location | None = None + declaration_id: Annotated[ + str, + Field( + description='Identifier unique within the enclosing asset, used to correlate validation results.', + min_length=1, + pattern='^[A-Za-z0-9_-]+$', + ), + ] + token: Annotated[ + str, + Field( + description='Exact byte-preserved token expression at this occurrence, including delimiters.', + min_length=1, + ), + ] + dialect: MacroDialect + dialect_namespace: Annotated[ + AnyUrl | None, + Field( + description='Authority-controlled registry or mapping URI. Required for IAB VAST, IAB DAAST, and vendor dialects.' + ), + ] = None + dialect_revision: Annotated[ + str | None, + Field( + description='Immutable registry/mapping revision, release, commit, or digest. Required for IAB VAST, IAB DAAST, and vendor dialects.', + min_length=1, + ), + ] = None + dialect_semantic: Annotated[ + str, + Field( + description='Exact semantic identifier in the cited dialect, such as `CACHEBUSTING`, `PLAYERSTATE`, or a documented vendor name. This is not inferred from token spelling.', + min_length=1, + ), + ] + mapping_status: MacroMappingStatus + universal_semantic: Annotated[ + UniversalMacro | None, + Field( + description='Verified AdCP universal meaning. Present only when mapping_status is `verified_universal`.' + ), + ] = None + operation: MacroProcessingOperation + performed_by: Annotated[ + MacroResolver | None, + Field( + description='Actor authorized to perform the declared translation or value resolution. Omitted for preservation.' + ), + ] = None + translation_target: Annotated[ + MacroTranslationTarget | None, + Field( + description='Required only for `translate_to_native`. The output token contract that replaces this declaration after translation.' + ), + ] = None + encoding: Annotated[ + MacroEncoding, + Field( + description='Encoding for the concrete value. Translation/preservation uses `none` at depth zero.' + ), + ] + required: Annotated[ + StrictBool, + Field( + description='Whether terminal delivery must fail if the declared operation cannot complete.' + ), + ] + unavailable_behavior: Annotated[ + UnavailableBehavior, + Field( + description='Action when the responsible actor has no value. Omission is legal only for a complete URL query-value occurrence; dialect sentinels require a cited dialect rule.' + ), + ] + + +class DisplayTagAsset1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['display_tag'], + Field( + description='Discriminator identifying an atomic third-party display-tag representation.' + ), + ] = 'display_tag' + macro_declarations: Annotated[ + list[MacroDeclaration2] | None, + Field( + description='Exact macro tokens present anywhere in this representation. Tokens remain byte-preserved until the resolver named by each declaration substitutes them.', + min_length=1, + ), + ] = None + provenance: Annotated[ + Provenance | None, + Field( + description='Provenance metadata for this asset, overriding manifest-level provenance.' + ), + ] = None + delivery_type: Literal['tag_url'] = 'tag_url' + url: Annotated[ + MacroBearingUrl, + Field( + description='Ad-request URL invoked at impression time. It is equivalent to the backward-compatible `url` asset with `url_type: "ad_request"`.' + ), + ] + + +class MacroDeclaration3(MacroDeclarationModel): + model_config = ConfigDict( + extra='forbid', + ) + location: Location4 | None = None + declaration_id: Annotated[ + str, + Field( + description='Identifier unique within the enclosing asset, used to correlate validation results.', + min_length=1, + pattern='^[A-Za-z0-9_-]+$', + ), + ] + token: Annotated[ + str, + Field( + description='Exact byte-preserved token expression at this occurrence, including delimiters.', + min_length=1, + ), + ] + dialect: MacroDialect + dialect_namespace: Annotated[ + AnyUrl | None, + Field( + description='Authority-controlled registry or mapping URI. Required for IAB VAST, IAB DAAST, and vendor dialects.' + ), + ] = None + dialect_revision: Annotated[ + str | None, + Field( + description='Immutable registry/mapping revision, release, commit, or digest. Required for IAB VAST, IAB DAAST, and vendor dialects.', + min_length=1, + ), + ] = None + dialect_semantic: Annotated[ + str, + Field( + description='Exact semantic identifier in the cited dialect, such as `CACHEBUSTING`, `PLAYERSTATE`, or a documented vendor name. This is not inferred from token spelling.', + min_length=1, + ), + ] + mapping_status: MacroMappingStatus + universal_semantic: Annotated[ + UniversalMacro | None, + Field( + description='Verified AdCP universal meaning. Present only when mapping_status is `verified_universal`.' + ), + ] = None + operation: MacroProcessingOperation + performed_by: Annotated[ + MacroResolver | None, + Field( + description='Actor authorized to perform the declared translation or value resolution. Omitted for preservation.' + ), + ] = None + translation_target: Annotated[ + MacroTranslationTarget | None, + Field( + description='Required only for `translate_to_native`. The output token contract that replaces this declaration after translation.' + ), + ] = None + encoding: Annotated[ + MacroEncoding, + Field( + description='Encoding for the concrete value. Translation/preservation uses `none` at depth zero.' + ), + ] + required: Annotated[ + StrictBool, + Field( + description='Whether terminal delivery must fail if the declared operation cannot complete.' + ), + ] + unavailable_behavior: Annotated[ + UnavailableBehavior, + Field( + description='Action when the responsible actor has no value. Omission is legal only for a complete URL query-value occurrence; dialect sentinels require a cited dialect rule.' + ), + ] + + +class DisplayTagAsset2(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['display_tag'], + Field( + description='Discriminator identifying an atomic third-party display-tag representation.' + ), + ] = 'display_tag' + macro_declarations: Annotated[ + list[MacroDeclaration3] | None, + Field( + description='Exact macro tokens present anywhere in this representation. Tokens remain byte-preserved until the resolver named by each declaration substitutes them.', + min_length=1, + ), + ] = None + provenance: Annotated[ + Provenance | None, + Field( + description='Provenance metadata for this asset, overriding manifest-level provenance.' + ), + ] = None + delivery_type: Literal['inline_markup'] = 'inline_markup' + markup_type: Annotated[ + MarkupType, Field(description='How the destination traffics the byte-preserved markup.') + ] + markup: Annotated[ + str, + Field( + description='Exact third-party tag markup. Receivers MUST preserve its bytes and MUST NOT reinterpret it as a seller-hosted HTML5 bundle.', + max_length=1048576, + min_length=1, + ), + ] + + +class MacroDeclaration4(MacroDeclarationModel): + model_config = ConfigDict( + extra='forbid', + ) + location: Location5 | None = None + declaration_id: Annotated[ + str, + Field( + description='Identifier unique within the enclosing asset, used to correlate validation results.', + min_length=1, + pattern='^[A-Za-z0-9_-]+$', + ), + ] + token: Annotated[ + str, + Field( + description='Exact byte-preserved token expression at this occurrence, including delimiters.', + min_length=1, + ), + ] + dialect: MacroDialect + dialect_namespace: Annotated[ + AnyUrl | None, + Field( + description='Authority-controlled registry or mapping URI. Required for IAB VAST, IAB DAAST, and vendor dialects.' + ), + ] = None + dialect_revision: Annotated[ + str | None, + Field( + description='Immutable registry/mapping revision, release, commit, or digest. Required for IAB VAST, IAB DAAST, and vendor dialects.', + min_length=1, + ), + ] = None + dialect_semantic: Annotated[ + str, + Field( + description='Exact semantic identifier in the cited dialect, such as `CACHEBUSTING`, `PLAYERSTATE`, or a documented vendor name. This is not inferred from token spelling.', + min_length=1, + ), + ] + mapping_status: MacroMappingStatus + universal_semantic: Annotated[ + UniversalMacro | None, + Field( + description='Verified AdCP universal meaning. Present only when mapping_status is `verified_universal`.' + ), + ] = None + operation: MacroProcessingOperation + performed_by: Annotated[ + MacroResolver | None, + Field( + description='Actor authorized to perform the declared translation or value resolution. Omitted for preservation.' + ), + ] = None + translation_target: Annotated[ + MacroTranslationTarget | None, + Field( + description='Required only for `translate_to_native`. The output token contract that replaces this declaration after translation.' + ), + ] = None + encoding: Annotated[ + MacroEncoding, + Field( + description='Encoding for the concrete value. Translation/preservation uses `none` at depth zero.' + ), + ] + required: Annotated[ + StrictBool, + Field( + description='Whether terminal delivery must fail if the declared operation cannot complete.' + ), + ] + unavailable_behavior: Annotated[ + UnavailableBehavior, + Field( + description='Action when the responsible actor has no value. Omission is legal only for a complete URL query-value occurrence; dialect sentinels require a cited dialect rule.' + ), + ] + + +class DisplayTagAsset3(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['display_tag'], + Field( + description='Discriminator identifying an atomic third-party display-tag representation.' + ), + ] = 'display_tag' + macro_declarations: Annotated[ + list[MacroDeclaration4] | None, + Field( + description='Exact macro tokens present anywhere in this representation. Tokens remain byte-preserved until the resolver named by each declaration substitutes them.', + min_length=1, + ), + ] = None + provenance: Annotated[ + Provenance | None, + Field( + description='Provenance metadata for this asset, overriding manifest-level provenance.' + ), + ] = None + delivery_type: Literal['paired_redirect'] = 'paired_redirect' + ad_request_url: Annotated[ + MacroBearingUrl, + Field( + description='Image or ad-request URL entered into the destination ad server. This field deliberately accepts byte-preserved vendor tokens that are not RFC 6570 URI templates.' + ), + ] + clickthrough_url: Annotated[ + MacroBearingUrl, + Field( + description='Click-through URL paired with `ad_request_url`. This field deliberately accepts byte-preserved vendor tokens that are not RFC 6570 URI templates. The pair MUST NOT be split, mixed, or revised independently.' + ), + ] + + +class DisplayTagAsset(RootModel[DisplayTagAsset1 | DisplayTagAsset2 | DisplayTagAsset3]): + root: Annotated[ + DisplayTagAsset1 | DisplayTagAsset2 | DisplayTagAsset3, + Field( + description='One atomic third-party display delivery representation. `delivery_type` distinguishes a single ad-request URL, byte-preserved inline markup, and a paired redirect whose ad-request and click-through URLs MUST travel together. This is not an HTML5 bundle: seller-hosted HTML5 zip files use `asset_type: "zip"` under `format_kind: "html5"`.', + discriminator='delivery_type', + title='Display Tag Asset', + ), + ] + def __getattr__(self, name: str) -> Any: + """Proxy attribute access to the wrapped type.""" + if name.startswith('_'): + raise AttributeError(name) + return getattr(self.root, name) + + + +class MacroDeclaration5(MacroDeclarationModel): + location: Location6 | None = None + + +class UrlAsset(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['url'], + Field( + description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'url' + url: Annotated[ + MacroBearingUrl, + Field( + description='URL reference carrying plain, AdCP, IAB, or declared vendor macro syntax. Buyers preserve token delimiters; the authoritative declaration determines the processing operation and encoding.' + ), + ] + url_type: Annotated[ + UrlAssetType | None, + Field( + description='Mechanism a receiver uses to invoke this URL: `clickthrough` for a user destination, `ad_request` for a third-party display creative request, `tracker_pixel` for an event HTTP request, or `tracker_script` for a script include. SHOULD be present on every URL asset.' + ), + ] = None + macro_declarations: Annotated[ + list[MacroDeclaration5] | None, + Field( + description='One declaration per token occurrence in `url`; declaration_id values MUST be unique and every location MUST identify an existing occurrence. Absence retains legacy opaque transport behavior.', + min_length=1, + ), + ] = None + description: Annotated[ + str | None, Field(description='Description of what this URL points to') + ] = None + state_id: Annotated[ + str | None, + Field( + description='Binding used only when this URL populates a `seller_rendered_stateful_display` `state_click_urls` slot. It MUST match one declared `states[].state_id` (semantic validators resolve it); at most one entry per state. Omit for ordinary URL slots.' + ), + ] = None + provenance: Annotated[ + Provenance | None, + Field( + description='Provenance metadata for this asset, overrides manifest-level provenance' + ), + ] = None + + +class BriefAsset(CreativeBrief): + asset_type: Annotated[ + Literal['brief'], + Field( + description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'brief' + + +class CatalogAsset(Catalog): + asset_type: Annotated[ + Literal['catalog'], + Field( + description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'catalog' + + +class CardAsset(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['card'], + Field( + description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'card' + media: Annotated[ + ImageAsset | VideoAsset, + Field( + description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", + discriminator='asset_type', + ), + ] + headline: Annotated[ + str | None, + Field( + description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' + ), + ] = None + description: Annotated[ + str | None, + Field( + description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' + ), + ] = None + cta: Annotated[ + str | None, + Field( + description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." + ), + ] = None + landing_page_url: Annotated[ + UrlAsset | None, + Field( + description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.' + ), + ] = None + platform_extensions: Annotated[ + list[PlatformExtensionRef] | None, + Field( + description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' + ), + ] = None + provenance: Annotated[ + Provenance | None, + Field( + description='Provenance metadata for this card, overrides manifest-level provenance.' + ), + ] = None + + +class MacroDeclaration8(MacroDeclarationModel): + location: Location9 | None = None + + +class PixelTrackerAsset(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['pixel_tracker'], + Field( + description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'pixel_tracker' + event: Annotated[ + PixelTrackingEvent, + Field( + description="Which event this tracker fires on. The event enum maps its first four measurement values to IAB OpenRTB Native 1.2 event types 1-4 and adds explicit AdCP events for renderer behavior that Native does not assign a standard event type:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds. Native type 4 does not require audio. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (AdCP-defined) — video reached 100% completion with audio on. Native reserves event types 500+ for exchange-specific use and does not assign this event a standard numeric type. Meaningful on non-VAST video formats where audible-complete is measured but VAST `` is not the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`; it can represent Native's exchange-specific 500+ range or another qualified vendor event without assigning an IAB numeric identity." + ), + ] + method: Annotated[ + Method | None, + Field( + description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." + ), + ] = Method.img + url: Annotated[ + MacroBearingUrl, + Field( + description='Tracker URL fired when `event` occurs. Macro processing and encoding follow an attached occurrence declaration; absent declarations retain the legacy universal-macro path.' + ), + ] + macro_declarations: Annotated[ + list[MacroDeclaration8] | None, + Field( + description='One declaration per token occurrence in `url`; declaration_id values MUST be unique and locations MUST resolve. When omitted, legacy behavior applies.', + min_length=1, + ), + ] = None + custom_event_name: Annotated[ + str | None, + Field( + description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. When tracker execution is undeclared, an unknown custom event is a forward-compatible probe and the seller silently no-ops instead of rejecting the creative. An effective tracker_execution_contract overrides that legacy fallback: with complete:true an unlisted custom selector is unsupported and rejects compatibility with tracker_contract_mismatch; a listed custom selector is an affirmative accept-and-initiate commitment.' + ), + ] = None + provenance: Annotated[ + Provenance | None, + Field( + description='Provenance metadata for this asset, overrides manifest-level provenance.' + ), + ] = None + + +class MacroDeclaration9(MacroDeclaration8): + pass + + +class VastTrackerAsset(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['vast_tracker'], + Field( + description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'vast_tracker' + vast_event: Annotated[ + VastTrackingEvent, + Field( + description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' + ), + ] + url: Annotated[ + MacroBearingUrl, + Field( + description='Tracker URL fired for the VAST event. Attached declarations identify each macro occurrence, registry revision, processing actor, and exact encoding profile.' + ), + ] + macro_declarations: Annotated[ + list[MacroDeclaration9] | None, + Field( + description='Exact tokens in `url` and their resolver/encoding contracts.', min_length=1 + ), + ] = None + offset: Annotated[ + str | None, + Field( + description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise for compatibility with existing 3.x manifests. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', + pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', + ), + ] = None + target: Annotated[ + Target | None, + Field( + description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. Defaults to `linear`. Existing 3.x assets remain structurally permissive; a tracker execution contract applies the standards-valid event/target matrix when matching a creative to a product.' + ), + ] = Target.linear + provenance: Annotated[ + Provenance | None, + Field( + description='Provenance metadata for this asset, overrides manifest-level provenance.' + ), + ] = None + + +class MacroDeclaration10(MacroDeclaration8): + pass + + +class DaastTrackerAsset(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['daast_tracker'], + Field( + description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'daast_tracker' + daast_event: Annotated[ + DaastTrackingEvent, + Field( + description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' + ), + ] + url: Annotated[ + MacroBearingUrl, + Field( + description='Tracker URL fired for the DAAST event. Attached declarations identify each macro occurrence, processing actor, and exact encoding profile.' + ), + ] + macro_declarations: Annotated[ + list[MacroDeclaration10] | None, + Field( + description='Exact tokens in `url` and their resolver/encoding contracts.', min_length=1 + ), + ] = None + offset: Annotated[ + str | None, + Field( + description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise for compatibility with existing 3.x manifests. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', + pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', + ), + ] = None + target: Annotated[ + Target1 | None, + Field( + description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7). DAAST has no `` element. Defaults to `linear`. Existing 3.x assets remain structurally permissive; a tracker execution contract applies the standards-valid event/target matrix when matching a creative to a product.' + ), + ] = Target1.linear + provenance: Annotated[ + Provenance | None, + Field( + description='Provenance metadata for this asset, overrides manifest-level provenance.' + ), + ] = None + -# Explicit exports -__all__ = [ - "Accessibility", - "AiTool", - "AssetVariant", - "AudioAsset", - "AudioBitDepth", - "AudioChannelLayout", - "BriefAsset", - "C2pa", - "C2paWatermarkAction", - "CardAsset", - "Catalog", - "CatalogAsset", - "CatalogFieldMapping", - "CatalogType", - "ChromaSubsampling", - "ColorSpace", - "Compliance", - "ContentIdType", - "CreativeBrief", - "CssAsset", - "DaastAsset", - "DaastAsset1", - "DaastAsset2", - "DaastTrackerAsset", - "DaastTrackingEvent", - "DaastVersion", - "DeclaredBy", - "DigitalSourceType", - "Disclosure", - "DisclosurePersistence", - "DisclosurePosition", - "DisplayTagAsset", - "DisplayTagAsset1", - "DisplayTagAsset2", - "DisplayTagAsset3", - "EmbeddedProvenanceItem", - "EmbeddedProvenanceMethod", - "EventType", - "Ext", - "FeedFormat", - "FieldModel", - "FocalPointItem", - "FrameRateType", - "GopType", - "Gtin", - "HdrFormat", - "HtmlAsset", - "HttpMethod", - "HumanOversight", - "IdentityRef", - "ImageAsset", - "JavascriptAsset", - "JavascriptModuleType", - "Jurisdiction", - "Jurisdiction2", - "Kind", - "Location", - "Location1", - "Location2", - "Location4", - "Location5", - "Location6", - "Location8", - "Location9", - "MacroBearingUrl", - "MacroBearingUrl1", - "MacroBearingUrl2", - "MacroDeclaration", - "MacroDeclaration1", - "MacroDeclaration10", - "MacroDeclaration2", - "MacroDeclaration3", - "MacroDeclaration4", - "MacroDeclaration5", - "MacroDeclaration6", - "MacroDeclaration7", - "MacroDeclaration8", - "MacroDeclaration9", - "MacroDeclarationModel", - "MacroDialect", - "MacroEncoding", - "MacroMappingStatus", - "MacroProcessingOperation", - "MacroResolver", - "MacroTranslationTarget", - "MacroValueContext", - "MarkdownAsset", - "MarkdownFlavor", - "MarkupType", - "Messaging", - "Method", - "MoovAtomPosition", - "Objective", - "PixelTrackerAsset", - "PixelTrackingEvent", - "PlatformExtensionRef", - "Provenance", - "PublishedPostAsset", - "PublishedPostAsset1", - "PublishedPostAsset2", - "ReferenceAsset", - "ReferenceAuthorization", - "ReferenceAuthorization1", - "RenderGuidance", - "RequiredDisclosure", - "Result", - "Role", - "Role2", - "ScanType", - "Security", - "Status", - "Target", - "Target1", - "TextAsset", - "Transform", - "UnavailableBehavior", - "UniversalMacro", - "UpdateFrequency", - "UrlAsset", - "UrlAssetType", - "VastAsset", - "VastAsset1", - "VastAsset2", - "VastTrackerAsset", - "VastTrackingEvent", - "VastVersion", - "VerificationItem", - "VerifyAgent", - "VerifyAgent1", - "VideoAsset", - "VideoBitDepth", - "Watermark", - "WatermarkMediaType", - "WebhookAsset", - "WebhookResponseType", - "WebhookSecurityMethod", - "ZipAsset", +AssetVariant = Annotated[ + ImageAsset + | VideoAsset + | AudioAsset + | VastAsset + | DisplayTagAsset + | TextAsset + | UrlAsset + | HtmlAsset + | JavascriptAsset + | ZipAsset + | WebhookAsset + | CssAsset + | DaastAsset + | MarkdownAsset + | BriefAsset + | CatalogAsset + | PublishedPostAsset + | CardAsset + | PixelTrackerAsset + | VastTrackerAsset + | DaastTrackerAsset, + Field( + description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', + discriminator='asset_type', + title='AssetVariant', + ), ] diff --git a/src/adcp/types/domains/core/assets/audio_asset.py b/src/adcp/types/domains/core/assets/audio_asset.py index 13796bc2f..8cb913633 100644 --- a/src/adcp/types/domains/core/assets/audio_asset.py +++ b/src/adcp/types/domains/core/assets/audio_asset.py @@ -1,18 +1,74 @@ -"""Types declared by the AdCP ``core/assets/audio_asset`` schema. +# generated by datamodel-codegen: +# filename: core/assets/audio_asset.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.assets.audio_asset import +from enum import IntEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AnyUrl, ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ...enums import audio_channel_layout +from .. import provenance as provenance_1 + + +class BitDepth(IntEnum): + integer_16 = 16 + integer_24 = 24 + integer_32 = 32 -from adcp.types.generated_poc.core.assets.audio_asset import AudioAsset, BitDepth -# Explicit exports -__all__ = ["AudioAsset", "BitDepth"] +class AudioAsset(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['audio'], + Field( + description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'audio' + url: Annotated[AnyUrl, Field(description='URL to the audio asset')] + duration_ms: Annotated[ + SchemaInt | None, Field(description='Audio duration in milliseconds', ge=0) + ] = None + file_size_bytes: Annotated[SchemaInt | None, Field(description='File size in bytes', ge=1)] = ( + None + ) + container_format: Annotated[ + str | None, + Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), + ] = None + codec: Annotated[ + str | None, + Field( + description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' + ), + ] = None + sampling_rate_hz: Annotated[ + SchemaInt | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') + ] = None + channels: Annotated[ + audio_channel_layout.AudioChannelLayout | None, Field(description='Channel configuration') + ] = None + bit_depth: Annotated[BitDepth | None, Field(description='Bit depth')] = None + bitrate_kbps: Annotated[ + SchemaInt | None, Field(description='Bitrate in kilobits per second', ge=1) + ] = None + loudness_lufs: Annotated[ + StrictFloat | None, Field(description='Integrated loudness in LUFS') + ] = None + true_peak_dbfs: Annotated[StrictFloat | None, Field(description='True peak level in dBFS')] = ( + None + ) + transcript_url: Annotated[ + AnyUrl | None, Field(description='URL to text transcript of the audio content') + ] = None + provenance: Annotated[ + provenance_1.Provenance | None, + Field( + description='Provenance metadata for this asset, overrides manifest-level provenance' + ), + ] = None diff --git a/src/adcp/types/domains/core/assets/brief_asset.py b/src/adcp/types/domains/core/assets/brief_asset.py index fd0bb3ebb..cb6f0f962 100644 --- a/src/adcp/types/domains/core/assets/brief_asset.py +++ b/src/adcp/types/domains/core/assets/brief_asset.py @@ -1,18 +1,20 @@ -"""Types declared by the AdCP ``core/assets/brief_asset`` schema. +# generated by datamodel-codegen: +# filename: core/assets/brief_asset.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.assets.brief_asset import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..creative_brief import CreativeBrief -from adcp.types.generated_poc.core.assets.brief_asset import BriefAsset -# Explicit exports -__all__ = ["BriefAsset"] +class BriefAsset(CreativeBrief): + asset_type: Annotated[ + Literal['brief'], + Field( + description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'brief' diff --git a/src/adcp/types/domains/core/assets/card_asset.py b/src/adcp/types/domains/core/assets/card_asset.py index 7359c3d9f..5d202c99b 100644 --- a/src/adcp/types/domains/core/assets/card_asset.py +++ b/src/adcp/types/domains/core/assets/card_asset.py @@ -1,18 +1,67 @@ -"""Types declared by the AdCP ``core/assets/card_asset`` schema. +# generated by datamodel-codegen: +# filename: core/assets/card_asset.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.assets.card_asset import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import asset_union -from adcp.types.generated_poc.core.assets.card_asset import CardAsset -# Explicit exports -__all__ = ["CardAsset"] +class CardAsset(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['card'], + Field( + description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'card' + media: Annotated[ + asset_union.ImageAsset | asset_union.VideoAsset, + Field( + description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", + discriminator='asset_type', + ), + ] + headline: Annotated[ + str | None, + Field( + description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' + ), + ] = None + description: Annotated[ + str | None, + Field( + description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' + ), + ] = None + cta: Annotated[ + str | None, + Field( + description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." + ), + ] = None + landing_page_url: Annotated[ + asset_union.UrlAsset | None, + Field( + description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.' + ), + ] = None + platform_extensions: Annotated[ + list[asset_union.PlatformExtensionRef] | None, + Field( + description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' + ), + ] = None + provenance: Annotated[ + asset_union.Provenance | None, + Field( + description='Provenance metadata for this card, overrides manifest-level provenance.' + ), + ] = None diff --git a/src/adcp/types/domains/core/assets/catalog_asset.py b/src/adcp/types/domains/core/assets/catalog_asset.py index cae62030c..939cdcad0 100644 --- a/src/adcp/types/domains/core/assets/catalog_asset.py +++ b/src/adcp/types/domains/core/assets/catalog_asset.py @@ -1,18 +1,20 @@ -"""Types declared by the AdCP ``core/assets/catalog_asset`` schema. +# generated by datamodel-codegen: +# filename: core/assets/catalog_asset.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.assets.catalog_asset import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..catalog import Catalog -from adcp.types.generated_poc.core.assets.catalog_asset import CatalogAsset -# Explicit exports -__all__ = ["CatalogAsset"] +class CatalogAsset(Catalog): + asset_type: Annotated[ + Literal['catalog'], + Field( + description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'catalog' diff --git a/src/adcp/types/domains/core/assets/css_asset.py b/src/adcp/types/domains/core/assets/css_asset.py index a3d65d712..41e41b5ad 100644 --- a/src/adcp/types/domains/core/assets/css_asset.py +++ b/src/adcp/types/domains/core/assets/css_asset.py @@ -1,18 +1,34 @@ -"""Types declared by the AdCP ``core/assets/css_asset`` schema. +# generated by datamodel-codegen: +# filename: core/assets/css_asset.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.assets.css_asset import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from .. import provenance as provenance_1 -from adcp.types.generated_poc.core.assets.css_asset import CssAsset -# Explicit exports -__all__ = ["CssAsset"] +class CssAsset(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['css'], + Field( + description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'css' + content: Annotated[str, Field(description='CSS content')] + media: Annotated[ + str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") + ] = None + provenance: Annotated[ + provenance_1.Provenance | None, + Field( + description='Provenance metadata for this asset, overrides manifest-level provenance' + ), + ] = None diff --git a/src/adcp/types/domains/core/assets/daast_asset.py b/src/adcp/types/domains/core/assets/daast_asset.py index 1722d3681..cf4a9c9b9 100644 --- a/src/adcp/types/domains/core/assets/daast_asset.py +++ b/src/adcp/types/domains/core/assets/daast_asset.py @@ -1,38 +1,339 @@ -"""Types declared by the AdCP ``core/assets/daast_asset`` schema. +# generated by datamodel-codegen: +# filename: core/assets/daast_asset.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.assets.daast_asset import +from adcp.types._str_enum import StrEnum +from typing import Any, Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AnyUrl, ConfigDict, Field, RootModel, StrictBool + +from ...enums import daast_tracking_event +from ...enums import daast_version as daast_version_1 +from .. import macro_bearing_url +from .. import provenance as provenance_1 +from . import asset_union + + +class Field1(StrEnum): + url = 'url' + markup = 'markup' + content = 'content' + ad_request_url = 'ad_request_url' + clickthrough_url = 'clickthrough_url' + + +class Location(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + field: Annotated[Literal['url'], Field(description='Asset field containing this occurrence.')] = 'url' + occurrence: Annotated[ + SchemaInt, + Field( + description='Zero-based occurrence of this exact token within the named field.', ge=0 + ), + ] + context: asset_union.MacroValueContext + + +class UnavailableBehavior(StrEnum): + preserve = 'preserve' + omit_parameter = 'omit_parameter' + dialect_sentinel = 'dialect_sentinel' + reject = 'reject' + + +class Location13(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + field: Annotated[ + Literal['content'], Field(description='Asset field containing this occurrence.') + ] = 'content' + occurrence: Annotated[ + SchemaInt, + Field( + description='Zero-based occurrence of this exact token within the named field.', ge=0 + ), + ] + context: asset_union.MacroValueContext + + +class MacroDeclaration(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + declaration_id: Annotated[ + str, + Field( + description='Identifier unique within the enclosing asset, used to correlate validation results.', + min_length=1, + pattern='^[A-Za-z0-9_-]+$', + ), + ] + token: Annotated[ + str, + Field( + description='Exact byte-preserved token expression at this occurrence, including delimiters.', + min_length=1, + ), + ] + dialect: asset_union.MacroDialect + dialect_namespace: Annotated[ + AnyUrl | None, + Field( + description='Authority-controlled registry or mapping URI. Required for IAB VAST, IAB DAAST, and vendor dialects.' + ), + ] = None + dialect_revision: Annotated[ + str | None, + Field( + description='Immutable registry/mapping revision, release, commit, or digest. Required for IAB VAST, IAB DAAST, and vendor dialects.', + min_length=1, + ), + ] = None + dialect_semantic: Annotated[ + str, + Field( + description='Exact semantic identifier in the cited dialect, such as `CACHEBUSTING`, `PLAYERSTATE`, or a documented vendor name. This is not inferred from token spelling.', + min_length=1, + ), + ] + mapping_status: asset_union.MacroMappingStatus + universal_semantic: Annotated[ + asset_union.UniversalMacro | None, + Field( + description='Verified AdCP universal meaning. Present only when mapping_status is `verified_universal`.' + ), + ] = None + operation: asset_union.MacroProcessingOperation + performed_by: Annotated[ + asset_union.MacroResolver | None, + Field( + description='Actor authorized to perform the declared translation or value resolution. Omitted for preservation.' + ), + ] = None + translation_target: Annotated[ + asset_union.MacroTranslationTarget | None, + Field( + description='Required only for `translate_to_native`. The output token contract that replaces this declaration after translation.' + ), + ] = None + location: Location + encoding: Annotated[ + asset_union.MacroEncoding, + Field( + description='Encoding for the concrete value. Translation/preservation uses `none` at depth zero.' + ), + ] + required: Annotated[ + StrictBool, + Field( + description='Whether terminal delivery must fail if the declared operation cannot complete.' + ), + ] + unavailable_behavior: Annotated[ + UnavailableBehavior, + Field( + description='Action when the responsible actor has no value. Omission is legal only for a complete URL query-value occurrence; dialect sentinels require a cited dialect rule.' + ), + ] + + +class MacroDeclaration12(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + declaration_id: Annotated[ + str, + Field( + description='Identifier unique within the enclosing asset, used to correlate validation results.', + min_length=1, + pattern='^[A-Za-z0-9_-]+$', + ), + ] + token: Annotated[ + str, + Field( + description='Exact byte-preserved token expression at this occurrence, including delimiters.', + min_length=1, + ), + ] + dialect: asset_union.MacroDialect + dialect_namespace: Annotated[ + AnyUrl | None, + Field( + description='Authority-controlled registry or mapping URI. Required for IAB VAST, IAB DAAST, and vendor dialects.' + ), + ] = None + dialect_revision: Annotated[ + str | None, + Field( + description='Immutable registry/mapping revision, release, commit, or digest. Required for IAB VAST, IAB DAAST, and vendor dialects.', + min_length=1, + ), + ] = None + dialect_semantic: Annotated[ + str, + Field( + description='Exact semantic identifier in the cited dialect, such as `CACHEBUSTING`, `PLAYERSTATE`, or a documented vendor name. This is not inferred from token spelling.', + min_length=1, + ), + ] + mapping_status: asset_union.MacroMappingStatus + universal_semantic: Annotated[ + asset_union.UniversalMacro | None, + Field( + description='Verified AdCP universal meaning. Present only when mapping_status is `verified_universal`.' + ), + ] = None + operation: asset_union.MacroProcessingOperation + performed_by: Annotated[ + asset_union.MacroResolver | None, + Field( + description='Actor authorized to perform the declared translation or value resolution. Omitted for preservation.' + ), + ] = None + translation_target: Annotated[ + asset_union.MacroTranslationTarget | None, + Field( + description='Required only for `translate_to_native`. The output token contract that replaces this declaration after translation.' + ), + ] = None + location: Location13 + encoding: Annotated[ + asset_union.MacroEncoding, + Field( + description='Encoding for the concrete value. Translation/preservation uses `none` at depth zero.' + ), + ] + required: Annotated[ + StrictBool, + Field( + description='Whether terminal delivery must fail if the declared operation cannot complete.' + ), + ] + unavailable_behavior: Annotated[ + UnavailableBehavior, + Field( + description='Action when the responsible actor has no value. Omission is legal only for a complete URL query-value occurrence; dialect sentinels require a cited dialect rule.' + ), + ] + + +class DaastAsset3(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['daast'], + Field( + description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'daast' + daast_version: Annotated[ + daast_version_1.DaastVersion | None, Field(description='DAAST specification version') + ] = None + duration_ms: Annotated[ + SchemaInt | None, + Field(description='Expected audio duration in milliseconds (if known)', ge=0), + ] = None + tracking_events: Annotated[ + list[daast_tracking_event.DaastTrackingEvent] | None, + Field(description='Tracking events supported by this DAAST tag'), + ] = None + companion_ads: Annotated[ + StrictBool | None, Field(description='Whether companion display ads are included') + ] = None + transcript_url: Annotated[ + AnyUrl | None, Field(description='URL to text transcript of the audio content') + ] = None + provenance: Annotated[ + provenance_1.Provenance | None, + Field( + description='Provenance metadata for this asset, overrides manifest-level provenance' + ), + ] = None + macro_declarations: Annotated[ + list[MacroDeclaration] | None, + Field( + description='One declaration per occurrence in a field carried by this asset. URL-delivered assets declare only locator-URL occurrences; receivers do not infer declarations for tokens discovered later in a fetched document.', + min_length=1, + ), + ] = None + delivery_type: Annotated[ + Literal['url'], + Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), + ] = 'url' + url: Annotated[ + macro_bearing_url.MacroBearingUrl, + Field( + description='URL endpoint returning DAAST XML. Macro delimiters remain byte-preserved and are processed only under attached occurrence declarations.' + ), + ] + + +class DaastAsset4(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['daast'], + Field( + description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'daast' + daast_version: Annotated[ + daast_version_1.DaastVersion | None, Field(description='DAAST specification version') + ] = None + duration_ms: Annotated[ + SchemaInt | None, + Field(description='Expected audio duration in milliseconds (if known)', ge=0), + ] = None + tracking_events: Annotated[ + list[daast_tracking_event.DaastTrackingEvent] | None, + Field(description='Tracking events supported by this DAAST tag'), + ] = None + companion_ads: Annotated[ + StrictBool | None, Field(description='Whether companion display ads are included') + ] = None + transcript_url: Annotated[ + AnyUrl | None, Field(description='URL to text transcript of the audio content') + ] = None + provenance: Annotated[ + provenance_1.Provenance | None, + Field( + description='Provenance metadata for this asset, overrides manifest-level provenance' + ), + ] = None + macro_declarations: Annotated[ + list[MacroDeclaration12] | None, + Field( + description='One declaration per occurrence in a field carried by this asset. URL-delivered assets declare only locator-URL occurrences; receivers do not infer declarations for tokens discovered later in a fetched document.', + min_length=1, + ), + ] = None + delivery_type: Annotated[ + Literal['inline'], + Field(description='Discriminator indicating DAAST is delivered as inline XML content'), + ] = 'inline' + content: Annotated[str, Field(description='Inline DAAST XML content')] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.assets.daast_asset import ( - DaastAsset, - DaastAsset3, - DaastAsset4, - Field1, - Location, - Location13, - MacroDeclaration, - MacroDeclaration12, - UnavailableBehavior, -) - -# Explicit exports -__all__ = [ - "DaastAsset", - "DaastAsset3", - "DaastAsset4", - "Field1", - "Location", - "Location13", - "MacroDeclaration", - "MacroDeclaration12", - "UnavailableBehavior", -] +class DaastAsset(RootModel[DaastAsset3 | DaastAsset4]): + root: Annotated[ + DaastAsset3 | DaastAsset4, + Field( + description='DAAST (Digital Audio Ad Serving Template) tag for third-party audio ad serving', + discriminator='delivery_type', + title='DAAST Asset', + ), + ] + def __getattr__(self, name: str) -> Any: + """Proxy attribute access to the wrapped type.""" + if name.startswith('_'): + raise AttributeError(name) + return getattr(self.root, name) diff --git a/src/adcp/types/domains/core/assets/daast_tracker_asset.py b/src/adcp/types/domains/core/assets/daast_tracker_asset.py index a0e82c787..065ec6771 100644 --- a/src/adcp/types/domains/core/assets/daast_tracker_asset.py +++ b/src/adcp/types/domains/core/assets/daast_tracker_asset.py @@ -1,24 +1,97 @@ -"""Types declared by the AdCP ``core/assets/daast_tracker_asset`` schema. +# generated by datamodel-codegen: +# filename: core/assets/daast_tracker_asset.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.assets.daast_tracker_asset import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ...enums import daast_tracking_event +from .. import macro_bearing_url +from .. import provenance as provenance_1 +from ..macro_declaration import MacroDeclaration as MacroDeclaration_1 +from . import asset_union + + +class Field1(StrEnum): + url = 'url' + markup = 'markup' + content = 'content' + ad_request_url = 'ad_request_url' + clickthrough_url = 'clickthrough_url' + + +class Location(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + field: Annotated[Literal['url'], Field(description='Asset field containing this occurrence.')] = 'url' + occurrence: Annotated[ + SchemaInt, + Field( + description='Zero-based occurrence of this exact token within the named field.', ge=0 + ), + ] + context: asset_union.MacroValueContext + + +class Target(StrEnum): + linear = 'linear' + companion = 'companion' + + +class MacroDeclaration(MacroDeclaration_1): + location: Location | None = None -from adcp.types.generated_poc.core.assets.daast_tracker_asset import ( - DaastTrackerAsset, - Field1, - Location, - MacroDeclaration, - Target, -) -# Explicit exports -__all__ = ["DaastTrackerAsset", "Field1", "Location", "MacroDeclaration", "Target"] +class DaastTrackerAsset(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['daast_tracker'], + Field( + description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'daast_tracker' + daast_event: Annotated[ + daast_tracking_event.DaastTrackingEvent, + Field( + description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' + ), + ] + url: Annotated[ + macro_bearing_url.MacroBearingUrl, + Field( + description='Tracker URL fired for the DAAST event. Attached declarations identify each macro occurrence, processing actor, and exact encoding profile.' + ), + ] + macro_declarations: Annotated[ + list[MacroDeclaration] | None, + Field( + description='Exact tokens in `url` and their resolver/encoding contracts.', min_length=1 + ), + ] = None + offset: Annotated[ + str | None, + Field( + description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise for compatibility with existing 3.x manifests. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', + pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', + ), + ] = None + target: Annotated[ + Target | None, + Field( + description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7). DAAST has no `` element. Defaults to `linear`. Existing 3.x assets remain structurally permissive; a tracker execution contract applies the standards-valid event/target matrix when matching a creative to a product.' + ), + ] = Target.linear + provenance: Annotated[ + provenance_1.Provenance | None, + Field( + description='Provenance metadata for this asset, overrides manifest-level provenance.' + ), + ] = None diff --git a/src/adcp/types/domains/core/assets/display_tag_asset.py b/src/adcp/types/domains/core/assets/display_tag_asset.py index e768cbb8f..d5ceb0365 100644 --- a/src/adcp/types/domains/core/assets/display_tag_asset.py +++ b/src/adcp/types/domains/core/assets/display_tag_asset.py @@ -1,46 +1,425 @@ -"""Types declared by the AdCP ``core/assets/display_tag_asset`` schema. +# generated by datamodel-codegen: +# filename: core/assets/display_tag_asset.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.assets.display_tag_asset import +from adcp.types._str_enum import StrEnum +from typing import Any, Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AnyUrl, ConfigDict, Field, RootModel, StrictBool + +from .. import macro_bearing_url +from .. import provenance as provenance_1 +from ..macro_declaration import MacroDeclaration as MacroDeclaration_1 +from . import asset_union + + +class Field1(StrEnum): + url = 'url' + markup = 'markup' + content = 'content' + ad_request_url = 'ad_request_url' + clickthrough_url = 'clickthrough_url' + + +class Location(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + field: Annotated[Field1, Field(description='Asset field containing this occurrence.')] + occurrence: Annotated[ + SchemaInt, + Field( + description='Zero-based occurrence of this exact token within the named field.', ge=0 + ), + ] + context: asset_union.MacroValueContext + + +class UnavailableBehavior(StrEnum): + preserve = 'preserve' + omit_parameter = 'omit_parameter' + dialect_sentinel = 'dialect_sentinel' + reject = 'reject' + + +class Location17(Location): + pass + + +class MarkupType(StrEnum): + iframe_javascript = 'iframe_javascript' + javascript = 'javascript' + standard = 'standard' + + +class Location18(Location): + pass + + +class MacroDeclaration(MacroDeclaration_1): + model_config = ConfigDict( + extra='forbid', + ) + location: Location | None = None + declaration_id: Annotated[ + str, + Field( + description='Identifier unique within the enclosing asset, used to correlate validation results.', + min_length=1, + pattern='^[A-Za-z0-9_-]+$', + ), + ] + token: Annotated[ + str, + Field( + description='Exact byte-preserved token expression at this occurrence, including delimiters.', + min_length=1, + ), + ] + dialect: asset_union.MacroDialect + dialect_namespace: Annotated[ + AnyUrl | None, + Field( + description='Authority-controlled registry or mapping URI. Required for IAB VAST, IAB DAAST, and vendor dialects.' + ), + ] = None + dialect_revision: Annotated[ + str | None, + Field( + description='Immutable registry/mapping revision, release, commit, or digest. Required for IAB VAST, IAB DAAST, and vendor dialects.', + min_length=1, + ), + ] = None + dialect_semantic: Annotated[ + str, + Field( + description='Exact semantic identifier in the cited dialect, such as `CACHEBUSTING`, `PLAYERSTATE`, or a documented vendor name. This is not inferred from token spelling.', + min_length=1, + ), + ] + mapping_status: asset_union.MacroMappingStatus + universal_semantic: Annotated[ + asset_union.UniversalMacro | None, + Field( + description='Verified AdCP universal meaning. Present only when mapping_status is `verified_universal`.' + ), + ] = None + operation: asset_union.MacroProcessingOperation + performed_by: Annotated[ + asset_union.MacroResolver | None, + Field( + description='Actor authorized to perform the declared translation or value resolution. Omitted for preservation.' + ), + ] = None + translation_target: Annotated[ + asset_union.MacroTranslationTarget | None, + Field( + description='Required only for `translate_to_native`. The output token contract that replaces this declaration after translation.' + ), + ] = None + encoding: Annotated[ + asset_union.MacroEncoding, + Field( + description='Encoding for the concrete value. Translation/preservation uses `none` at depth zero.' + ), + ] + required: Annotated[ + StrictBool, + Field( + description='Whether terminal delivery must fail if the declared operation cannot complete.' + ), + ] + unavailable_behavior: Annotated[ + UnavailableBehavior, + Field( + description='Action when the responsible actor has no value. Omission is legal only for a complete URL query-value occurrence; dialect sentinels require a cited dialect rule.' + ), + ] + + +class DisplayTagAsset4(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['display_tag'], + Field( + description='Discriminator identifying an atomic third-party display-tag representation.' + ), + ] = 'display_tag' + macro_declarations: Annotated[ + list[MacroDeclaration] | None, + Field( + description='Exact macro tokens present anywhere in this representation. Tokens remain byte-preserved until the resolver named by each declaration substitutes them.', + min_length=1, + ), + ] = None + provenance: Annotated[ + provenance_1.Provenance | None, + Field( + description='Provenance metadata for this asset, overriding manifest-level provenance.' + ), + ] = None + delivery_type: Literal['tag_url'] = 'tag_url' + url: Annotated[ + macro_bearing_url.MacroBearingUrl, + Field( + description='Ad-request URL invoked at impression time. It is equivalent to the backward-compatible `url` asset with `url_type: "ad_request"`.' + ), + ] + + +class MacroDeclaration15(MacroDeclaration_1): + model_config = ConfigDict( + extra='forbid', + ) + location: Location17 | None = None + declaration_id: Annotated[ + str, + Field( + description='Identifier unique within the enclosing asset, used to correlate validation results.', + min_length=1, + pattern='^[A-Za-z0-9_-]+$', + ), + ] + token: Annotated[ + str, + Field( + description='Exact byte-preserved token expression at this occurrence, including delimiters.', + min_length=1, + ), + ] + dialect: asset_union.MacroDialect + dialect_namespace: Annotated[ + AnyUrl | None, + Field( + description='Authority-controlled registry or mapping URI. Required for IAB VAST, IAB DAAST, and vendor dialects.' + ), + ] = None + dialect_revision: Annotated[ + str | None, + Field( + description='Immutable registry/mapping revision, release, commit, or digest. Required for IAB VAST, IAB DAAST, and vendor dialects.', + min_length=1, + ), + ] = None + dialect_semantic: Annotated[ + str, + Field( + description='Exact semantic identifier in the cited dialect, such as `CACHEBUSTING`, `PLAYERSTATE`, or a documented vendor name. This is not inferred from token spelling.', + min_length=1, + ), + ] + mapping_status: asset_union.MacroMappingStatus + universal_semantic: Annotated[ + asset_union.UniversalMacro | None, + Field( + description='Verified AdCP universal meaning. Present only when mapping_status is `verified_universal`.' + ), + ] = None + operation: asset_union.MacroProcessingOperation + performed_by: Annotated[ + asset_union.MacroResolver | None, + Field( + description='Actor authorized to perform the declared translation or value resolution. Omitted for preservation.' + ), + ] = None + translation_target: Annotated[ + asset_union.MacroTranslationTarget | None, + Field( + description='Required only for `translate_to_native`. The output token contract that replaces this declaration after translation.' + ), + ] = None + encoding: Annotated[ + asset_union.MacroEncoding, + Field( + description='Encoding for the concrete value. Translation/preservation uses `none` at depth zero.' + ), + ] + required: Annotated[ + StrictBool, + Field( + description='Whether terminal delivery must fail if the declared operation cannot complete.' + ), + ] + unavailable_behavior: Annotated[ + UnavailableBehavior, + Field( + description='Action when the responsible actor has no value. Omission is legal only for a complete URL query-value occurrence; dialect sentinels require a cited dialect rule.' + ), + ] + + +class DisplayTagAsset5(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['display_tag'], + Field( + description='Discriminator identifying an atomic third-party display-tag representation.' + ), + ] = 'display_tag' + macro_declarations: Annotated[ + list[MacroDeclaration15] | None, + Field( + description='Exact macro tokens present anywhere in this representation. Tokens remain byte-preserved until the resolver named by each declaration substitutes them.', + min_length=1, + ), + ] = None + provenance: Annotated[ + provenance_1.Provenance | None, + Field( + description='Provenance metadata for this asset, overriding manifest-level provenance.' + ), + ] = None + delivery_type: Literal['inline_markup'] = 'inline_markup' + markup_type: Annotated[ + MarkupType, Field(description='How the destination traffics the byte-preserved markup.') + ] + markup: Annotated[ + str, + Field( + description='Exact third-party tag markup. Receivers MUST preserve its bytes and MUST NOT reinterpret it as a seller-hosted HTML5 bundle.', + max_length=1048576, + min_length=1, + ), + ] + + +class MacroDeclaration16(MacroDeclaration_1): + model_config = ConfigDict( + extra='forbid', + ) + location: Location18 | None = None + declaration_id: Annotated[ + str, + Field( + description='Identifier unique within the enclosing asset, used to correlate validation results.', + min_length=1, + pattern='^[A-Za-z0-9_-]+$', + ), + ] + token: Annotated[ + str, + Field( + description='Exact byte-preserved token expression at this occurrence, including delimiters.', + min_length=1, + ), + ] + dialect: asset_union.MacroDialect + dialect_namespace: Annotated[ + AnyUrl | None, + Field( + description='Authority-controlled registry or mapping URI. Required for IAB VAST, IAB DAAST, and vendor dialects.' + ), + ] = None + dialect_revision: Annotated[ + str | None, + Field( + description='Immutable registry/mapping revision, release, commit, or digest. Required for IAB VAST, IAB DAAST, and vendor dialects.', + min_length=1, + ), + ] = None + dialect_semantic: Annotated[ + str, + Field( + description='Exact semantic identifier in the cited dialect, such as `CACHEBUSTING`, `PLAYERSTATE`, or a documented vendor name. This is not inferred from token spelling.', + min_length=1, + ), + ] + mapping_status: asset_union.MacroMappingStatus + universal_semantic: Annotated[ + asset_union.UniversalMacro | None, + Field( + description='Verified AdCP universal meaning. Present only when mapping_status is `verified_universal`.' + ), + ] = None + operation: asset_union.MacroProcessingOperation + performed_by: Annotated[ + asset_union.MacroResolver | None, + Field( + description='Actor authorized to perform the declared translation or value resolution. Omitted for preservation.' + ), + ] = None + translation_target: Annotated[ + asset_union.MacroTranslationTarget | None, + Field( + description='Required only for `translate_to_native`. The output token contract that replaces this declaration after translation.' + ), + ] = None + encoding: Annotated[ + asset_union.MacroEncoding, + Field( + description='Encoding for the concrete value. Translation/preservation uses `none` at depth zero.' + ), + ] + required: Annotated[ + StrictBool, + Field( + description='Whether terminal delivery must fail if the declared operation cannot complete.' + ), + ] + unavailable_behavior: Annotated[ + UnavailableBehavior, + Field( + description='Action when the responsible actor has no value. Omission is legal only for a complete URL query-value occurrence; dialect sentinels require a cited dialect rule.' + ), + ] + + +class DisplayTagAsset6(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['display_tag'], + Field( + description='Discriminator identifying an atomic third-party display-tag representation.' + ), + ] = 'display_tag' + macro_declarations: Annotated[ + list[MacroDeclaration16] | None, + Field( + description='Exact macro tokens present anywhere in this representation. Tokens remain byte-preserved until the resolver named by each declaration substitutes them.', + min_length=1, + ), + ] = None + provenance: Annotated[ + provenance_1.Provenance | None, + Field( + description='Provenance metadata for this asset, overriding manifest-level provenance.' + ), + ] = None + delivery_type: Literal['paired_redirect'] = 'paired_redirect' + ad_request_url: Annotated[ + macro_bearing_url.MacroBearingUrl, + Field( + description='Image or ad-request URL entered into the destination ad server. This field deliberately accepts byte-preserved vendor tokens that are not RFC 6570 URI templates.' + ), + ] + clickthrough_url: Annotated[ + macro_bearing_url.MacroBearingUrl, + Field( + description='Click-through URL paired with `ad_request_url`. This field deliberately accepts byte-preserved vendor tokens that are not RFC 6570 URI templates. The pair MUST NOT be split, mixed, or revised independently.' + ), + ] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.assets.display_tag_asset import ( - DisplayTagAsset, - DisplayTagAsset4, - DisplayTagAsset5, - DisplayTagAsset6, - Field1, - Location, - Location17, - Location18, - MacroDeclaration, - MacroDeclaration15, - MacroDeclaration16, - MarkupType, - UnavailableBehavior, -) - -# Explicit exports -__all__ = [ - "DisplayTagAsset", - "DisplayTagAsset4", - "DisplayTagAsset5", - "DisplayTagAsset6", - "Field1", - "Location", - "Location17", - "Location18", - "MacroDeclaration", - "MacroDeclaration15", - "MacroDeclaration16", - "MarkupType", - "UnavailableBehavior", -] +class DisplayTagAsset(RootModel[DisplayTagAsset4 | DisplayTagAsset5 | DisplayTagAsset6]): + root: Annotated[ + DisplayTagAsset4 | DisplayTagAsset5 | DisplayTagAsset6, + Field( + description='One atomic third-party display delivery representation. `delivery_type` distinguishes a single ad-request URL, byte-preserved inline markup, and a paired redirect whose ad-request and click-through URLs MUST travel together. This is not an HTML5 bundle: seller-hosted HTML5 zip files use `asset_type: "zip"` under `format_kind: "html5"`.', + discriminator='delivery_type', + title='Display Tag Asset', + ), + ] + def __getattr__(self, name: str) -> Any: + """Proxy attribute access to the wrapped type.""" + if name.startswith('_'): + raise AttributeError(name) + return getattr(self.root, name) diff --git a/src/adcp/types/domains/core/assets/html_asset.py b/src/adcp/types/domains/core/assets/html_asset.py index 2a4740185..9ad378fc1 100644 --- a/src/adcp/types/domains/core/assets/html_asset.py +++ b/src/adcp/types/domains/core/assets/html_asset.py @@ -1,18 +1,56 @@ -"""Types declared by the AdCP ``core/assets/html_asset`` schema. +# generated by datamodel-codegen: +# filename: core/assets/html_asset.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.assets.html_asset import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations +from .. import provenance as provenance_1 + + +class Accessibility(AdCPBaseModel): + alt_text: Annotated[ + str | None, Field(description='Text alternative describing the creative content') + ] = None + keyboard_navigable: Annotated[ + StrictBool | None, + Field(description='Whether the creative can be fully operated via keyboard'), + ] = None + motion_control: Annotated[ + StrictBool | None, + Field( + description='Whether the creative respects prefers-reduced-motion or provides pause/stop controls' + ), + ] = None + screen_reader_tested: Annotated[ + StrictBool | None, + Field(description='Whether the creative has been tested with screen readers'), + ] = None -from adcp.types.generated_poc.core.assets.html_asset import Accessibility, HtmlAsset -# Explicit exports -__all__ = ["Accessibility", "HtmlAsset"] +class HtmlAsset(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['html'], + Field( + description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'html' + content: Annotated[str, Field(description='HTML content')] + version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None + accessibility: Annotated[ + Accessibility | None, + Field(description='Self-declared accessibility properties for this opaque creative'), + ] = None + provenance: Annotated[ + provenance_1.Provenance | None, + Field( + description='Provenance metadata for this asset, overrides manifest-level provenance' + ), + ] = None diff --git a/src/adcp/types/domains/core/assets/image_asset.py b/src/adcp/types/domains/core/assets/image_asset.py index 9ae1065dd..9a5de6b11 100644 --- a/src/adcp/types/domains/core/assets/image_asset.py +++ b/src/adcp/types/domains/core/assets/image_asset.py @@ -1,18 +1,77 @@ -"""Types declared by the AdCP ``core/assets/image_asset`` schema. +# generated by datamodel-codegen: +# filename: core/assets/image_asset.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.assets.image_asset import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from adcp.types._scalar import ScalarFloat +from pydantic import AnyUrl, ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from .. import provenance as provenance_1 + + +class FocalPointItem(ScalarFloat): + __slots__ = () + _constraints = {'ge': 0.0, 'le': 1.0} -from adcp.types.generated_poc.core.assets.image_asset import FocalPointItem, ImageAsset -# Explicit exports -__all__ = ["FocalPointItem", "ImageAsset"] +class ImageAsset(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['image'], + Field( + description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'image' + url: Annotated[AnyUrl, Field(description='URL to the image asset')] + width: Annotated[SchemaInt, Field(description='Width in pixels', ge=1)] + height: Annotated[SchemaInt, Field(description='Height in pixels', ge=1)] + file_size_bytes: Annotated[ + SchemaInt | None, + Field( + description='Image file size in bytes. Required by agents that advertise a max_file_size_kb constraint.', + ge=1, + ), + ] = None + pixel_ratio: Annotated[ + StrictFloat | None, + Field( + description='Intrinsic pixels per logical render pixel (for example `2` for a 600×500 image intended to render at 300×250). Optional because a validator can infer the ratio when the target format declares logical dimensions. When supplied, it MUST agree with both `width / logical_width` and `height / logical_height`; it is never a substitute for the intrinsic `width` and `height` fields.', + gt=0.0, + ), + ] = None + state_id: Annotated[ + str | None, + Field( + description='Binding used only when this image populates a `seller_rendered_stateful_display` `state_canvases` slot. It MUST match one declared `states[].state_id` (semantic validators resolve it). Omit for ordinary image slots.' + ), + ] = None + breakpoint_id: Annotated[ + str | None, + Field( + description='Binding used only when this image populates a `seller_rendered_stateful_display` `state_canvases` slot. It MUST match one breakpoint declared on the selected state (semantic validators resolve it). Omit for ordinary image slots.' + ), + ] = None + focal_point: Annotated[ + list[FocalPointItem] | None, + Field( + description="Normalized `[x, y]` coordinates (0–1 from top-left) of the image's visual anchor. Seller-side renderers crop toward the focal point when deriving renditions across breakpoints and aspect ratios; absent, cropping falls back to center-weighted defaults.", + max_length=2, + min_length=2, + ), + ] = None + format: Annotated[ + str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') + ] = None + alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None + provenance: Annotated[ + provenance_1.Provenance | None, + Field( + description='Provenance metadata for this asset, overrides manifest-level provenance' + ), + ] = None diff --git a/src/adcp/types/domains/core/assets/javascript_asset.py b/src/adcp/types/domains/core/assets/javascript_asset.py index ad84239df..81b2d19fa 100644 --- a/src/adcp/types/domains/core/assets/javascript_asset.py +++ b/src/adcp/types/domains/core/assets/javascript_asset.py @@ -1,18 +1,60 @@ -"""Types declared by the AdCP ``core/assets/javascript_asset`` schema. +# generated by datamodel-codegen: +# filename: core/assets/javascript_asset.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.assets.javascript_asset import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations +from ...enums import javascript_module_type +from .. import provenance as provenance_1 + + +class Accessibility(AdCPBaseModel): + alt_text: Annotated[ + str | None, Field(description='Text alternative describing the creative content') + ] = None + keyboard_navigable: Annotated[ + StrictBool | None, + Field(description='Whether the creative can be fully operated via keyboard'), + ] = None + motion_control: Annotated[ + StrictBool | None, + Field( + description='Whether the creative respects prefers-reduced-motion or provides pause/stop controls' + ), + ] = None + screen_reader_tested: Annotated[ + StrictBool | None, + Field(description='Whether the creative has been tested with screen readers'), + ] = None -from adcp.types.generated_poc.core.assets.javascript_asset import Accessibility, JavascriptAsset -# Explicit exports -__all__ = ["Accessibility", "JavascriptAsset"] +class JavascriptAsset(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['javascript'], + Field( + description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'javascript' + content: Annotated[str, Field(description='JavaScript content')] + module_type: Annotated[ + javascript_module_type.JavascriptModuleType | None, + Field(description='JavaScript module type'), + ] = None + accessibility: Annotated[ + Accessibility | None, + Field(description='Self-declared accessibility properties for this opaque creative'), + ] = None + provenance: Annotated[ + provenance_1.Provenance | None, + Field( + description='Provenance metadata for this asset, overrides manifest-level provenance' + ), + ] = None diff --git a/src/adcp/types/domains/core/assets/markdown_asset.py b/src/adcp/types/domains/core/assets/markdown_asset.py index 84b610071..af288a283 100644 --- a/src/adcp/types/domains/core/assets/markdown_asset.py +++ b/src/adcp/types/domains/core/assets/markdown_asset.py @@ -1,18 +1,48 @@ -"""Types declared by the AdCP ``core/assets/markdown_asset`` schema. +# generated by datamodel-codegen: +# filename: core/assets/markdown_asset.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.assets.markdown_asset import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations +from ...enums import markdown_flavor as markdown_flavor_1 -from adcp.types.generated_poc.core.assets.markdown_asset import MarkdownAsset -# Explicit exports -__all__ = ["MarkdownAsset"] +class MarkdownAsset(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['markdown'], + Field( + description='Discriminator identifying this as a markdown asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'markdown' + content: Annotated[ + str, + Field( + description='Markdown content following CommonMark spec with optional GitHub Flavored Markdown extensions' + ), + ] + language: Annotated[ + str | None, + Field( + description='Optional language claim for this markdown. In a materialized creative localization variant, localized-creative-asset.json requires this value to use /schemas/core/locale-tag.json and conformance requires exact equality with the enclosing variant locale. General non-localized assets retain the legacy unconstrained string for compatibility.' + ), + ] = None + markdown_flavor: Annotated[ + markdown_flavor_1.MarkdownFlavor | None, + Field( + description='Markdown flavor used. CommonMark for strict compatibility, GFM for tables/task lists/strikethrough.' + ), + ] = markdown_flavor_1.MarkdownFlavor.commonmark + allow_raw_html: Annotated[ + StrictBool | None, + Field( + description='Whether raw HTML blocks are allowed in the markdown. False recommended for security.' + ), + ] = False diff --git a/src/adcp/types/domains/core/assets/pixel_tracker_asset.py b/src/adcp/types/domains/core/assets/pixel_tracker_asset.py index 8a7e8986f..e7c9ff657 100644 --- a/src/adcp/types/domains/core/assets/pixel_tracker_asset.py +++ b/src/adcp/types/domains/core/assets/pixel_tracker_asset.py @@ -1,24 +1,97 @@ -"""Types declared by the AdCP ``core/assets/pixel_tracker_asset`` schema. +# generated by datamodel-codegen: +# filename: core/assets/pixel_tracker_asset.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.assets.pixel_tracker_asset import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ...enums import pixel_tracking_event +from .. import macro_bearing_url +from .. import provenance as provenance_1 +from ..macro_declaration import MacroDeclaration as MacroDeclaration_1 +from . import asset_union + + +class Method(StrEnum): + img = 'img' + js = 'js' + + +class Field1(StrEnum): + url = 'url' + markup = 'markup' + content = 'content' + ad_request_url = 'ad_request_url' + clickthrough_url = 'clickthrough_url' + + +class Location(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + field: Annotated[Literal['url'], Field(description='Asset field containing this occurrence.')] = 'url' + occurrence: Annotated[ + SchemaInt, + Field( + description='Zero-based occurrence of this exact token within the named field.', ge=0 + ), + ] + context: asset_union.MacroValueContext + + +class MacroDeclaration(MacroDeclaration_1): + location: Location | None = None -from adcp.types.generated_poc.core.assets.pixel_tracker_asset import ( - Field1, - Location, - MacroDeclaration, - Method, - PixelTrackerAsset, -) -# Explicit exports -__all__ = ["Field1", "Location", "MacroDeclaration", "Method", "PixelTrackerAsset"] +class PixelTrackerAsset(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['pixel_tracker'], + Field( + description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'pixel_tracker' + event: Annotated[ + pixel_tracking_event.PixelTrackingEvent, + Field( + description="Which event this tracker fires on. The event enum maps its first four measurement values to IAB OpenRTB Native 1.2 event types 1-4 and adds explicit AdCP events for renderer behavior that Native does not assign a standard event type:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds. Native type 4 does not require audio. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (AdCP-defined) — video reached 100% completion with audio on. Native reserves event types 500+ for exchange-specific use and does not assign this event a standard numeric type. Meaningful on non-VAST video formats where audible-complete is measured but VAST `` is not the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`; it can represent Native's exchange-specific 500+ range or another qualified vendor event without assigning an IAB numeric identity." + ), + ] + method: Annotated[ + Method | None, + Field( + description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." + ), + ] = Method.img + url: Annotated[ + macro_bearing_url.MacroBearingUrl, + Field( + description='Tracker URL fired when `event` occurs. Macro processing and encoding follow an attached occurrence declaration; absent declarations retain the legacy universal-macro path.' + ), + ] + macro_declarations: Annotated[ + list[MacroDeclaration] | None, + Field( + description='One declaration per token occurrence in `url`; declaration_id values MUST be unique and locations MUST resolve. When omitted, legacy behavior applies.', + min_length=1, + ), + ] = None + custom_event_name: Annotated[ + str | None, + Field( + description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. When tracker execution is undeclared, an unknown custom event is a forward-compatible probe and the seller silently no-ops instead of rejecting the creative. An effective tracker_execution_contract overrides that legacy fallback: with complete:true an unlisted custom selector is unsupported and rejects compatibility with tracker_contract_mismatch; a listed custom selector is an affirmative accept-and-initiate commitment.' + ), + ] = None + provenance: Annotated[ + provenance_1.Provenance | None, + Field( + description='Provenance metadata for this asset, overrides manifest-level provenance.' + ), + ] = None diff --git a/src/adcp/types/domains/core/assets/published_post_asset.py b/src/adcp/types/domains/core/assets/published_post_asset.py index 48c2c4927..204165e61 100644 --- a/src/adcp/types/domains/core/assets/published_post_asset.py +++ b/src/adcp/types/domains/core/assets/published_post_asset.py @@ -1,23 +1,130 @@ -"""Types declared by the AdCP ``core/assets/published_post_asset`` schema. +# generated by datamodel-codegen: +# filename: core/assets/published_post_asset.json +# timestamp: 2026-10-04T01:14:11+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.assets.published_post_asset import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, model_validator + +from .. import provenance as provenance_1 + + +class IdentityRef(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + handle: Annotated[ + str | None, Field(description='Human-readable handle or username when available.') + ] = None + profile_url: Annotated[ + AnyUrl | None, Field(description='URL for the authoring profile, page, or channel.') + ] = None + platform_identity_id: Annotated[ + str | None, Field(description='Opaque platform-native identity identifier.') + ] = None + + +class Status(StrEnum): + authorized = 'authorized' + pending = 'pending' + expired = 'expired' + revoked = 'revoked' + not_required = 'not_required' + unknown = 'unknown' + + +class ReferenceAuthorization(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + status: Annotated[ + Status, + Field( + description='Current seller-observed authorization state. `authorized`: seller can serve the referenced post. `pending`: additional creator/account action is required. `expired` or `revoked`: authorization previously existed but no longer permits serving. `not_required`: the seller can legally and technically serve without a separate identity authorization. `unknown`: seller has not checked or cannot disclose the state.' + ), + ] + checked_at: Annotated[ + AwareDatetime | None, + Field(description='When the seller last checked the authorization state.'), + ] = None + expires_at: Annotated[ + AwareDatetime | None, + Field(description='When the authorization expires, if the platform exposes an expiry.'), + ] = None + authorization_url: Annotated[ + AnyUrl | None, + Field(description='Optional URL the buyer or creator can use to complete authorization.'), + ] = None + authorization_instructions: Annotated[ + str | None, + Field(description='Human-readable instructions for completing or restoring authorization.'), + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.assets.published_post_asset import ( - IdentityRef, - PublishedPostAsset, - ReferenceAuthorization, - Status, -) +class PublishedPostAsset(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['published_post'], + Field( + description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'published_post' + post_url: Annotated[ + AnyUrl | None, + Field( + description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' + ), + ] = None + platform: Annotated[ + str | None, + Field( + description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." + ), + ] = None + platform_post_id: Annotated[ + str | None, + Field( + description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' + ), + ] = None + identity_ref: Annotated[ + IdentityRef | None, + Field( + description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' + ), + ] = None + published_at: Annotated[ + AwareDatetime | None, + Field(description='When the referenced post was originally published, if known.'), + ] = None + reference_authorization: Annotated[ + ReferenceAuthorization | None, + Field( + description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' + ), + ] = None + provenance: Annotated[ + provenance_1.Provenance | None, + Field( + description='Provenance metadata for this reference asset, overrides manifest-level provenance.' + ), + ] = None -# Explicit exports -__all__ = ["IdentityRef", "PublishedPostAsset", "ReferenceAuthorization", "Status"] + @model_validator(mode='after') + def _require_schema_required_group(self) -> PublishedPostAsset: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('post_url',), ('platform_post_id',),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'PublishedPostAsset requires at least one of these field groups: post_url | platform_post_id' + ) diff --git a/src/adcp/types/domains/core/assets/text_asset.py b/src/adcp/types/domains/core/assets/text_asset.py index e270f860b..11f1ea791 100644 --- a/src/adcp/types/domains/core/assets/text_asset.py +++ b/src/adcp/types/domains/core/assets/text_asset.py @@ -1,18 +1,37 @@ -"""Types declared by the AdCP ``core/assets/text_asset`` schema. +# generated by datamodel-codegen: +# filename: core/assets/text_asset.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.assets.text_asset import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from .. import provenance as provenance_1 -from adcp.types.generated_poc.core.assets.text_asset import TextAsset -# Explicit exports -__all__ = ["TextAsset"] +class TextAsset(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['text'], + Field( + description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'text' + content: Annotated[str, Field(description='Text content')] + language: Annotated[ + str | None, + Field( + description='Optional language claim for this text. In a materialized creative localization variant, localized-creative-asset.json requires this value to use /schemas/core/locale-tag.json and conformance requires exact equality with the enclosing variant locale. General non-localized assets retain the legacy unconstrained string for compatibility.' + ), + ] = None + provenance: Annotated[ + provenance_1.Provenance | None, + Field( + description='Provenance metadata for this asset, overrides manifest-level provenance' + ), + ] = None diff --git a/src/adcp/types/domains/core/assets/url_asset.py b/src/adcp/types/domains/core/assets/url_asset.py index e6ae5f484..0e312fafa 100644 --- a/src/adcp/types/domains/core/assets/url_asset.py +++ b/src/adcp/types/domains/core/assets/url_asset.py @@ -1,23 +1,89 @@ -"""Types declared by the AdCP ``core/assets/url_asset`` schema. +# generated by datamodel-codegen: +# filename: core/assets/url_asset.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.assets.url_asset import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ...enums import url_asset_type +from .. import macro_bearing_url +from .. import provenance as provenance_1 +from ..macro_declaration import MacroDeclaration as MacroDeclaration_1 +from . import asset_union + + +class Field1(StrEnum): + url = 'url' + markup = 'markup' + content = 'content' + ad_request_url = 'ad_request_url' + clickthrough_url = 'clickthrough_url' + + +class Location(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + field: Annotated[Literal['url'], Field(description='Asset field containing this occurrence.')] = 'url' + occurrence: Annotated[ + SchemaInt, + Field( + description='Zero-based occurrence of this exact token within the named field.', ge=0 + ), + ] + context: asset_union.MacroValueContext + + +class MacroDeclaration(MacroDeclaration_1): + location: Location | None = None -from adcp.types.generated_poc.core.assets.url_asset import ( - Field1, - Location, - MacroDeclaration, - UrlAsset, -) -# Explicit exports -__all__ = ["Field1", "Location", "MacroDeclaration", "UrlAsset"] +class UrlAsset(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['url'], + Field( + description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'url' + url: Annotated[ + macro_bearing_url.MacroBearingUrl, + Field( + description='URL reference carrying plain, AdCP, IAB, or declared vendor macro syntax. Buyers preserve token delimiters; the authoritative declaration determines the processing operation and encoding.' + ), + ] + url_type: Annotated[ + url_asset_type.UrlAssetType | None, + Field( + description='Mechanism a receiver uses to invoke this URL: `clickthrough` for a user destination, `ad_request` for a third-party display creative request, `tracker_pixel` for an event HTTP request, or `tracker_script` for a script include. SHOULD be present on every URL asset.' + ), + ] = None + macro_declarations: Annotated[ + list[MacroDeclaration] | None, + Field( + description='One declaration per token occurrence in `url`; declaration_id values MUST be unique and every location MUST identify an existing occurrence. Absence retains legacy opaque transport behavior.', + min_length=1, + ), + ] = None + description: Annotated[ + str | None, Field(description='Description of what this URL points to') + ] = None + state_id: Annotated[ + str | None, + Field( + description='Binding used only when this URL populates a `seller_rendered_stateful_display` `state_click_urls` slot. It MUST match one declared `states[].state_id` (semantic validators resolve it); at most one entry per state. Omit for ordinary URL slots.' + ), + ] = None + provenance: Annotated[ + provenance_1.Provenance | None, + Field( + description='Provenance metadata for this asset, overrides manifest-level provenance' + ), + ] = None diff --git a/src/adcp/types/domains/core/assets/vast_asset.py b/src/adcp/types/domains/core/assets/vast_asset.py index f265bee87..4f9ea6726 100644 --- a/src/adcp/types/domains/core/assets/vast_asset.py +++ b/src/adcp/types/domains/core/assets/vast_asset.py @@ -1,38 +1,355 @@ -"""Types declared by the AdCP ``core/assets/vast_asset`` schema. +# generated by datamodel-codegen: +# filename: core/assets/vast_asset.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.assets.vast_asset import +from adcp.types._str_enum import StrEnum +from typing import Any, Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AnyUrl, ConfigDict, Field, RootModel, StrictBool + +from ...enums import vast_tracking_event +from ...enums import vast_version as vast_version_1 +from .. import macro_bearing_url +from .. import provenance as provenance_1 +from . import asset_union + + +class Field1(StrEnum): + url = 'url' + markup = 'markup' + content = 'content' + ad_request_url = 'ad_request_url' + clickthrough_url = 'clickthrough_url' + + +class Location(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + field: Annotated[Literal['url'], Field(description='Asset field containing this occurrence.')] = 'url' + occurrence: Annotated[ + SchemaInt, + Field( + description='Zero-based occurrence of this exact token within the named field.', ge=0 + ), + ] + context: asset_union.MacroValueContext + + +class UnavailableBehavior(StrEnum): + preserve = 'preserve' + omit_parameter = 'omit_parameter' + dialect_sentinel = 'dialect_sentinel' + reject = 'reject' + + +class Location26(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + field: Annotated[ + Literal['content'], Field(description='Asset field containing this occurrence.') + ] = 'content' + occurrence: Annotated[ + SchemaInt, + Field( + description='Zero-based occurrence of this exact token within the named field.', ge=0 + ), + ] + context: asset_union.MacroValueContext + + +class MacroDeclaration(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + declaration_id: Annotated[ + str, + Field( + description='Identifier unique within the enclosing asset, used to correlate validation results.', + min_length=1, + pattern='^[A-Za-z0-9_-]+$', + ), + ] + token: Annotated[ + str, + Field( + description='Exact byte-preserved token expression at this occurrence, including delimiters.', + min_length=1, + ), + ] + dialect: asset_union.MacroDialect + dialect_namespace: Annotated[ + AnyUrl | None, + Field( + description='Authority-controlled registry or mapping URI. Required for IAB VAST, IAB DAAST, and vendor dialects.' + ), + ] = None + dialect_revision: Annotated[ + str | None, + Field( + description='Immutable registry/mapping revision, release, commit, or digest. Required for IAB VAST, IAB DAAST, and vendor dialects.', + min_length=1, + ), + ] = None + dialect_semantic: Annotated[ + str, + Field( + description='Exact semantic identifier in the cited dialect, such as `CACHEBUSTING`, `PLAYERSTATE`, or a documented vendor name. This is not inferred from token spelling.', + min_length=1, + ), + ] + mapping_status: asset_union.MacroMappingStatus + universal_semantic: Annotated[ + asset_union.UniversalMacro | None, + Field( + description='Verified AdCP universal meaning. Present only when mapping_status is `verified_universal`.' + ), + ] = None + operation: asset_union.MacroProcessingOperation + performed_by: Annotated[ + asset_union.MacroResolver | None, + Field( + description='Actor authorized to perform the declared translation or value resolution. Omitted for preservation.' + ), + ] = None + translation_target: Annotated[ + asset_union.MacroTranslationTarget | None, + Field( + description='Required only for `translate_to_native`. The output token contract that replaces this declaration after translation.' + ), + ] = None + location: Location + encoding: Annotated[ + asset_union.MacroEncoding, + Field( + description='Encoding for the concrete value. Translation/preservation uses `none` at depth zero.' + ), + ] + required: Annotated[ + StrictBool, + Field( + description='Whether terminal delivery must fail if the declared operation cannot complete.' + ), + ] + unavailable_behavior: Annotated[ + UnavailableBehavior, + Field( + description='Action when the responsible actor has no value. Omission is legal only for a complete URL query-value occurrence; dialect sentinels require a cited dialect rule.' + ), + ] + + +class VastAsset3(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['vast'], + Field( + description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'vast' + vast_version: Annotated[ + vast_version_1.VastVersion | None, + Field( + description='Exact VAST version declared by the supplied URL response or inline document. Required by the 3.2 canonical `video_vast` and `audio_vast` manifest paths; optional only on the deprecated named-format compatibility path. Receivers MUST NOT relabel or synthesize a newer version merely because the destination accepts it.' + ), + ] = None + macro_declarations: Annotated[ + list[MacroDeclaration] | None, + Field( + description='One declaration per exact occurrence in a field carried by this asset. A URL-delivered asset can declare only occurrences in its locator `url`; tokens discovered later in a fetched VAST response require document validation evidence or an inline/snapshotted asset and MUST NOT be guessed from the locator. IAB tokens cite a registry namespace and revision rather than copying the live registry into AdCP.', + min_length=1, + ), + ] = None + vpaid_enabled: Annotated[ + StrictBool | None, + Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), + ] = None + duration_ms: Annotated[ + SchemaInt | None, + Field(description='Expected media duration in milliseconds (if known)', ge=0), + ] = None + tracking_events: Annotated[ + list[vast_tracking_event.VastTrackingEvent] | None, + Field(description='Tracking events supported by this VAST tag'), + ] = None + captions_url: Annotated[ + AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') + ] = None + audio_description_url: Annotated[ + AnyUrl | None, + Field(description='URL to audio description track for visually impaired users'), + ] = None + provenance: Annotated[ + provenance_1.Provenance | None, + Field( + description='Provenance metadata for this asset, overrides manifest-level provenance' + ), + ] = None + delivery_type: Annotated[ + Literal['url'], + Field(description='Discriminator indicating VAST is delivered via URL endpoint'), + ] = 'url' + url: Annotated[ + macro_bearing_url.MacroBearingUrl, + Field( + description='URL endpoint returning VAST XML. Macro delimiters remain byte-preserved; declarations distinguish occurrences in this locator URL from occurrences in inline or fetched VAST content.' + ), + ] + + +class MacroDeclaration20(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + declaration_id: Annotated[ + str, + Field( + description='Identifier unique within the enclosing asset, used to correlate validation results.', + min_length=1, + pattern='^[A-Za-z0-9_-]+$', + ), + ] + token: Annotated[ + str, + Field( + description='Exact byte-preserved token expression at this occurrence, including delimiters.', + min_length=1, + ), + ] + dialect: asset_union.MacroDialect + dialect_namespace: Annotated[ + AnyUrl | None, + Field( + description='Authority-controlled registry or mapping URI. Required for IAB VAST, IAB DAAST, and vendor dialects.' + ), + ] = None + dialect_revision: Annotated[ + str | None, + Field( + description='Immutable registry/mapping revision, release, commit, or digest. Required for IAB VAST, IAB DAAST, and vendor dialects.', + min_length=1, + ), + ] = None + dialect_semantic: Annotated[ + str, + Field( + description='Exact semantic identifier in the cited dialect, such as `CACHEBUSTING`, `PLAYERSTATE`, or a documented vendor name. This is not inferred from token spelling.', + min_length=1, + ), + ] + mapping_status: asset_union.MacroMappingStatus + universal_semantic: Annotated[ + asset_union.UniversalMacro | None, + Field( + description='Verified AdCP universal meaning. Present only when mapping_status is `verified_universal`.' + ), + ] = None + operation: asset_union.MacroProcessingOperation + performed_by: Annotated[ + asset_union.MacroResolver | None, + Field( + description='Actor authorized to perform the declared translation or value resolution. Omitted for preservation.' + ), + ] = None + translation_target: Annotated[ + asset_union.MacroTranslationTarget | None, + Field( + description='Required only for `translate_to_native`. The output token contract that replaces this declaration after translation.' + ), + ] = None + location: Location26 + encoding: Annotated[ + asset_union.MacroEncoding, + Field( + description='Encoding for the concrete value. Translation/preservation uses `none` at depth zero.' + ), + ] + required: Annotated[ + StrictBool, + Field( + description='Whether terminal delivery must fail if the declared operation cannot complete.' + ), + ] + unavailable_behavior: Annotated[ + UnavailableBehavior, + Field( + description='Action when the responsible actor has no value. Omission is legal only for a complete URL query-value occurrence; dialect sentinels require a cited dialect rule.' + ), + ] + + +class VastAsset4(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['vast'], + Field( + description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'vast' + vast_version: Annotated[ + vast_version_1.VastVersion | None, + Field( + description='Exact VAST version declared by the supplied URL response or inline document. Required by the 3.2 canonical `video_vast` and `audio_vast` manifest paths; optional only on the deprecated named-format compatibility path. Receivers MUST NOT relabel or synthesize a newer version merely because the destination accepts it.' + ), + ] = None + macro_declarations: Annotated[ + list[MacroDeclaration20] | None, + Field( + description='One declaration per exact occurrence in a field carried by this asset. A URL-delivered asset can declare only occurrences in its locator `url`; tokens discovered later in a fetched VAST response require document validation evidence or an inline/snapshotted asset and MUST NOT be guessed from the locator. IAB tokens cite a registry namespace and revision rather than copying the live registry into AdCP.', + min_length=1, + ), + ] = None + vpaid_enabled: Annotated[ + StrictBool | None, + Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), + ] = None + duration_ms: Annotated[ + SchemaInt | None, + Field(description='Expected media duration in milliseconds (if known)', ge=0), + ] = None + tracking_events: Annotated[ + list[vast_tracking_event.VastTrackingEvent] | None, + Field(description='Tracking events supported by this VAST tag'), + ] = None + captions_url: Annotated[ + AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') + ] = None + audio_description_url: Annotated[ + AnyUrl | None, + Field(description='URL to audio description track for visually impaired users'), + ] = None + provenance: Annotated[ + provenance_1.Provenance | None, + Field( + description='Provenance metadata for this asset, overrides manifest-level provenance' + ), + ] = None + delivery_type: Annotated[ + Literal['inline'], + Field(description='Discriminator indicating VAST is delivered as inline XML content'), + ] = 'inline' + content: Annotated[str, Field(description='Inline VAST XML content')] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.assets.vast_asset import ( - Field1, - Location, - Location26, - MacroDeclaration, - MacroDeclaration20, - UnavailableBehavior, - VastAsset, - VastAsset3, - VastAsset4, -) - -# Explicit exports -__all__ = [ - "Field1", - "Location", - "Location26", - "MacroDeclaration", - "MacroDeclaration20", - "UnavailableBehavior", - "VastAsset", - "VastAsset3", - "VastAsset4", -] +class VastAsset(RootModel[VastAsset3 | VastAsset4]): + root: Annotated[ + VastAsset3 | VastAsset4, + Field( + description='VAST (Video Ad Serving Template) tag for third-party video or audio ad serving. Unlike a hosted media asset, a VAST tag carries no single `width`/`height`: a response can return multiple renditions and the player selects one at serve time. Standardized VAST audio support begins at 4.1 and uses MediaFile width and height values of 0; older audio-in-VAST versions are seller-declared legacy interoperability. Dimensional, duration, MIME-type, and codec constraints live on the format/requirements layer, not on this asset.', + discriminator='delivery_type', + title='VAST Asset', + ), + ] + def __getattr__(self, name: str) -> Any: + """Proxy attribute access to the wrapped type.""" + if name.startswith('_'): + raise AttributeError(name) + return getattr(self.root, name) diff --git a/src/adcp/types/domains/core/assets/vast_tracker_asset.py b/src/adcp/types/domains/core/assets/vast_tracker_asset.py index 4b01b6c65..d6395ac3a 100644 --- a/src/adcp/types/domains/core/assets/vast_tracker_asset.py +++ b/src/adcp/types/domains/core/assets/vast_tracker_asset.py @@ -1,24 +1,98 @@ -"""Types declared by the AdCP ``core/assets/vast_tracker_asset`` schema. +# generated by datamodel-codegen: +# filename: core/assets/vast_tracker_asset.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.assets.vast_tracker_asset import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ...enums import vast_tracking_event +from .. import macro_bearing_url +from .. import provenance as provenance_1 +from ..macro_declaration import MacroDeclaration as MacroDeclaration_1 +from . import asset_union + + +class Field1(StrEnum): + url = 'url' + markup = 'markup' + content = 'content' + ad_request_url = 'ad_request_url' + clickthrough_url = 'clickthrough_url' + + +class Location(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + field: Annotated[Literal['url'], Field(description='Asset field containing this occurrence.')] = 'url' + occurrence: Annotated[ + SchemaInt, + Field( + description='Zero-based occurrence of this exact token within the named field.', ge=0 + ), + ] + context: asset_union.MacroValueContext + + +class Target(StrEnum): + linear = 'linear' + non_linear = 'non_linear' + companion = 'companion' + + +class MacroDeclaration(MacroDeclaration_1): + location: Location | None = None -from adcp.types.generated_poc.core.assets.vast_tracker_asset import ( - Field1, - Location, - MacroDeclaration, - Target, - VastTrackerAsset, -) -# Explicit exports -__all__ = ["Field1", "Location", "MacroDeclaration", "Target", "VastTrackerAsset"] +class VastTrackerAsset(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['vast_tracker'], + Field( + description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'vast_tracker' + vast_event: Annotated[ + vast_tracking_event.VastTrackingEvent, + Field( + description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' + ), + ] + url: Annotated[ + macro_bearing_url.MacroBearingUrl, + Field( + description='Tracker URL fired for the VAST event. Attached declarations identify each macro occurrence, registry revision, processing actor, and exact encoding profile.' + ), + ] + macro_declarations: Annotated[ + list[MacroDeclaration] | None, + Field( + description='Exact tokens in `url` and their resolver/encoding contracts.', min_length=1 + ), + ] = None + offset: Annotated[ + str | None, + Field( + description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise for compatibility with existing 3.x manifests. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', + pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', + ), + ] = None + target: Annotated[ + Target | None, + Field( + description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. Defaults to `linear`. Existing 3.x assets remain structurally permissive; a tracker execution contract applies the standards-valid event/target matrix when matching a creative to a product.' + ), + ] = Target.linear + provenance: Annotated[ + provenance_1.Provenance | None, + Field( + description='Provenance metadata for this asset, overrides manifest-level provenance.' + ), + ] = None diff --git a/src/adcp/types/domains/core/assets/video_asset.py b/src/adcp/types/domains/core/assets/video_asset.py index 543f5a105..0612ddb2d 100644 --- a/src/adcp/types/domains/core/assets/video_asset.py +++ b/src/adcp/types/domains/core/assets/video_asset.py @@ -1,32 +1,157 @@ -"""Types declared by the AdCP ``core/assets/video_asset`` schema. +# generated by datamodel-codegen: +# filename: core/assets/video_asset.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.assets.video_asset import +from enum import IntEnum +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AnyUrl, ConfigDict, Field, StrictBool, StrictFloat + +from ...enums import audio_channel_layout +from ...enums import frame_rate_type as frame_rate_type_1 +from ...enums import gop_type as gop_type_1 +from ...enums import moov_atom_position as moov_atom_position_1 +from ...enums import scan_type as scan_type_1 +from .. import provenance as provenance_1 + + +class ColorSpace(StrEnum): + rec709 = 'rec709' + rec2020 = 'rec2020' + rec2100 = 'rec2100' + srgb = 'srgb' + dci_p3 = 'dci_p3' + + +class HdrFormat(StrEnum): + sdr = 'sdr' + hdr10 = 'hdr10' + hdr10_plus = 'hdr10_plus' + hlg = 'hlg' + dolby_vision = 'dolby_vision' + + +class ChromaSubsampling(StrEnum): + field_4_2_0 = '4:2:0' + field_4_2_2 = '4:2:2' + field_4_4_4 = '4:4:4' + + +class VideoBitDepth(IntEnum): + integer_8 = 8 + integer_10 = 10 + integer_12 = 12 + + +class AudioBitDepth(IntEnum): + integer_16 = 16 + integer_24 = 24 + integer_32 = 32 -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.assets.video_asset import ( - AudioBitDepth, - ChromaSubsampling, - ColorSpace, - HdrFormat, - VideoAsset, - VideoBitDepth, -) - -# Explicit exports -__all__ = [ - "AudioBitDepth", - "ChromaSubsampling", - "ColorSpace", - "HdrFormat", - "VideoAsset", - "VideoBitDepth", -] +class VideoAsset(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['video'], + Field( + description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'video' + url: Annotated[AnyUrl, Field(description='URL to the video asset')] + width: Annotated[ + SchemaInt, + Field( + description="Width in pixels — the video file's intrinsic native width. Required: a hosted file always has concrete dimensions. (Tag-delivered video carries no width; see the `vast` asset.)", + ge=1, + ), + ] + height: Annotated[ + SchemaInt, + Field( + description="Height in pixels — the video file's intrinsic native height. Required: a hosted file always has concrete dimensions. (Tag-delivered video carries no height; see the `vast` asset.)", + ge=1, + ), + ] + duration_ms: Annotated[ + SchemaInt | None, Field(description='Video duration in milliseconds', ge=1) + ] = None + file_size_bytes: Annotated[SchemaInt | None, Field(description='File size in bytes', ge=1)] = ( + None + ) + container_format: Annotated[ + str | None, Field(description='Video container format (mp4, webm, mov, etc.)') + ] = None + video_codec: Annotated[ + str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') + ] = None + video_bitrate_kbps: Annotated[ + SchemaInt | None, Field(description='Video stream bitrate in kilobits per second', ge=1) + ] = None + frame_rate: Annotated[ + str | None, + Field( + description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" + ), + ] = None + frame_rate_type: frame_rate_type_1.FrameRateType | None = None + scan_type: scan_type_1.ScanType | None = None + color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None + hdr_format: Annotated[ + HdrFormat | None, + Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), + ] = None + chroma_subsampling: Annotated[ + ChromaSubsampling | None, Field(description='Chroma subsampling format') + ] = None + video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None + gop_interval_seconds: Annotated[ + StrictFloat | None, Field(description='GOP/keyframe interval in seconds') + ] = None + gop_type: gop_type_1.GopType | None = None + moov_atom_position: moov_atom_position_1.MoovAtomPosition | None = None + has_audio: Annotated[ + StrictBool | None, Field(description='Whether the video contains an audio track') + ] = None + audio_codec: Annotated[ + str | None, + Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), + ] = None + audio_sampling_rate_hz: Annotated[ + SchemaInt | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') + ] = None + audio_channels: Annotated[ + audio_channel_layout.AudioChannelLayout | None, + Field(description='Audio channel configuration'), + ] = None + audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None + audio_bitrate_kbps: Annotated[ + SchemaInt | None, Field(description='Audio bitrate in kilobits per second', ge=1) + ] = None + audio_loudness_lufs: Annotated[ + StrictFloat | None, Field(description='Integrated loudness in LUFS') + ] = None + audio_true_peak_dbfs: Annotated[ + StrictFloat | None, Field(description='True peak level in dBFS') + ] = None + captions_url: Annotated[ + AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') + ] = None + transcript_url: Annotated[ + AnyUrl | None, Field(description='URL to text transcript of the video content') + ] = None + audio_description_url: Annotated[ + AnyUrl | None, + Field(description='URL to audio description track for visually impaired users'), + ] = None + provenance: Annotated[ + provenance_1.Provenance | None, + Field( + description='Provenance metadata for this asset, overrides manifest-level provenance' + ), + ] = None diff --git a/src/adcp/types/domains/core/assets/webhook_asset.py b/src/adcp/types/domains/core/assets/webhook_asset.py index 41f60cdd3..a1dc08d0c 100644 --- a/src/adcp/types/domains/core/assets/webhook_asset.py +++ b/src/adcp/types/domains/core/assets/webhook_asset.py @@ -1,18 +1,66 @@ -"""Types declared by the AdCP ``core/assets/webhook_asset`` schema. +# generated by datamodel-codegen: +# filename: core/assets/webhook_asset.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.assets.webhook_asset import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AnyUrl, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ...enums import http_method, universal_macro, webhook_response_type, webhook_security_method +from .. import provenance as provenance_1 + + +class Security(AdCPBaseModel): + method: Annotated[ + webhook_security_method.WebhookSecurityMethod, Field(description='Authentication method') + ] + hmac_header: Annotated[ + str | None, Field(description="Header name for HMAC signature (e.g., 'X-Signature')") + ] = None + api_key_header: Annotated[ + str | None, Field(description="Header name for API key (e.g., 'X-API-Key')") + ] = None -from adcp.types.generated_poc.core.assets.webhook_asset import Security, WebhookAsset -# Explicit exports -__all__ = ["Security", "WebhookAsset"] +class WebhookAsset(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['webhook'], + Field( + description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'webhook' + url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] + method: Annotated[http_method.HttpMethod | None, Field(description='HTTP method')] = ( + http_method.HttpMethod.POST + ) + timeout_ms: Annotated[ + SchemaInt | None, + Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), + ] = 500 + supported_macros: Annotated[ + list[universal_macro.UniversalMacro | str] | None, + Field( + description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' + ), + ] = None + required_macros: Annotated[ + list[universal_macro.UniversalMacro | str] | None, + Field(description='Universal macros that must be provided for webhook to function'), + ] = None + response_type: Annotated[ + webhook_response_type.WebhookResponseType, + Field(description='Expected content type of webhook response'), + ] + security: Annotated[Security, Field(description='Security configuration for webhook calls')] + provenance: Annotated[ + provenance_1.Provenance | None, + Field( + description='Provenance metadata for this asset, overrides manifest-level provenance' + ), + ] = None diff --git a/src/adcp/types/domains/core/assets/zip_asset.py b/src/adcp/types/domains/core/assets/zip_asset.py index ed4a95ec6..c6f97d094 100644 --- a/src/adcp/types/domains/core/assets/zip_asset.py +++ b/src/adcp/types/domains/core/assets/zip_asset.py @@ -1,18 +1,87 @@ -"""Types declared by the AdCP ``core/assets/zip_asset`` schema. +# generated by datamodel-codegen: +# filename: core/assets/zip_asset.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.assets.zip_asset import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AnyUrl, ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations +from .. import provenance as provenance_1 + + +class Accessibility(AdCPBaseModel): + alt_text: Annotated[ + str | None, Field(description='Text alternative describing the creative content') + ] = None + keyboard_navigable: Annotated[ + StrictBool | None, + Field(description='Whether the creative can be fully operated via keyboard'), + ] = None + motion_control: Annotated[ + StrictBool | None, + Field( + description='Whether the creative respects prefers-reduced-motion or provides pause/stop controls' + ), + ] = None + screen_reader_tested: Annotated[ + StrictBool | None, + Field(description='Whether the creative has been tested with screen readers'), + ] = None -from adcp.types.generated_poc.core.assets.zip_asset import Accessibility, ZipAsset -# Explicit exports -__all__ = ["Accessibility", "ZipAsset"] +class ZipAsset(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_type: Annotated[ + Literal['zip'], + Field( + description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' + ), + ] = 'zip' + url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] + max_file_size_kb: Annotated[ + SchemaInt | None, + Field( + description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', + ge=0, + ), + ] = None + entry_point: Annotated[ + str | None, + Field( + description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." + ), + ] = None + allowed_inner_extensions: Annotated[ + list[str] | None, + Field( + description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." + ), + ] = None + backup_image_url: Annotated[ + AnyUrl | None, + Field( + description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' + ), + ] = None + digest: Annotated[ + str | None, + Field( + description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + accessibility: Annotated[ + Accessibility | None, + Field(description='Self-declared accessibility properties for this opaque creative'), + ] = None + provenance: Annotated[ + provenance_1.Provenance | None, + Field( + description='Provenance metadata for this asset, overrides manifest-level provenance' + ), + ] = None diff --git a/src/adcp/types/domains/core/async_response_data.py b/src/adcp/types/domains/core/async_response_data.py index f8a516787..e76ca6936 100644 --- a/src/adcp/types/domains/core/async_response_data.py +++ b/src/adcp/types/domains/core/async_response_data.py @@ -1,18 +1,106 @@ -"""Types declared by the AdCP ``core/async_response_data`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_data.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.async_response_data import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..creative import ( + get_creative_features_async_response_submitted, + get_creative_features_response, + preview_creative_response, + sync_creatives_async_response_input_required, + sync_creatives_async_response_submitted, + sync_creatives_async_response_working, + sync_creatives_response, +) +from ..media_buy import ( + build_creative_async_response_input_required, + build_creative_async_response_submitted, + build_creative_async_response_working, + build_creative_response, + control_media_buy_response, + create_media_buy_async_response_input_required, + create_media_buy_async_response_submitted, + create_media_buy_async_response_working, + create_media_buy_response, + decline_proposals_response, + get_products_async_response_input_required, + get_products_async_response_submitted, + get_products_async_response_working, + get_products_rejected, + get_products_response, + media_buy_commitment_response, + media_buy_delivery_webhook_result, + refine_proposals_async_response_submitted, + refine_proposals_response, + request_proposals_async_response_submitted, + request_proposals_response, + sync_catalogs_async_response_input_required, + sync_catalogs_async_response_submitted, + sync_catalogs_async_response_working, + sync_catalogs_response, + update_media_buy_async_response_input_required, + update_media_buy_async_response_submitted, + update_media_buy_async_response_working, + update_media_buy_response, +) +from ..signals import ( + get_signals_async_response_submitted, + get_signals_async_response_working, + get_signals_response, +) +from . import compact_task_input_required, compact_task_submitted, compact_task_working -from adcp.types.generated_poc.core.async_response_data import AdcpAsyncResponseData -# Explicit exports -__all__ = ["AdcpAsyncResponseData"] +AdcpAsyncResponseData = Annotated[ + get_products_response.GetProductsResponse + | get_products_rejected.GetProductsRejected + | get_products_async_response_working.GetProductsWorking + | get_products_async_response_input_required.GetProductsInputRequired + | get_products_async_response_submitted.GetProductsSubmitted + | request_proposals_response.RequestProposalsResponse + | request_proposals_async_response_submitted.RequestProposalsSubmitted + | refine_proposals_response.RefineProposalsResponse + | refine_proposals_async_response_submitted.RefineProposalsSubmitted + | decline_proposals_response.DeclineProposalsResponse + | media_buy_commitment_response.MediaBuyCommitmentResponse + | control_media_buy_response.ControlMediaBuyResponse + | compact_task_submitted.CompactTaskSubmitted + | compact_task_working.CompactTaskWorking + | compact_task_input_required.CompactTaskInputRequired + | get_signals_response.GetSignalsResponse + | get_signals_async_response_working.GetSignalsWorking + | get_signals_async_response_submitted.GetSignalsSubmitted + | create_media_buy_response.CreateMediaBuyResponse + | create_media_buy_async_response_working.CreateMediaBuyWorking + | create_media_buy_async_response_input_required.CreateMediaBuyInputRequired + | create_media_buy_async_response_submitted.CreateMediaBuySubmitted + | update_media_buy_response.UpdateMediaBuyResponse + | update_media_buy_async_response_working.UpdateMediaBuyWorking + | update_media_buy_async_response_input_required.UpdateMediaBuyInputRequired + | update_media_buy_async_response_submitted.UpdateMediaBuySubmitted + | media_buy_delivery_webhook_result.MediaBuyDeliveryWebhookResult + | build_creative_response.BuildCreativeResponse + | preview_creative_response.PreviewCreativeResponse + | build_creative_async_response_working.BuildCreativeWorking + | build_creative_async_response_input_required.BuildCreativeInputRequired + | build_creative_async_response_submitted.BuildCreativeSubmitted + | get_creative_features_response.GetCreativeFeaturesResponse + | get_creative_features_async_response_submitted.GetCreativeFeaturesSubmitted + | sync_creatives_response.SyncCreativesResponse + | sync_creatives_async_response_working.SyncCreativesWorking + | sync_creatives_async_response_input_required.SyncCreativesInputRequired + | sync_creatives_async_response_submitted.SyncCreativesSubmitted + | sync_catalogs_response.SyncCatalogsResponse + | sync_catalogs_async_response_working.SyncCatalogsWorking + | sync_catalogs_async_response_input_required.SyncCatalogsInputRequired + | sync_catalogs_async_response_submitted.SyncCatalogsSubmitted, + Field( + description="Validation union of supported async webhook payloads. For completed/failed statuses, use the main task response schema. For rejected get_products outcomes and working/input-required/submitted statuses, use the status-specific schemas. Because this shared webhook union is not tagged with the originating task type, callers MUST also validate a terminal result against that task or event's specific schema. Polling responses use a generic result selected through manifest.task_result_resolution instead of embedding this union.", + title='AdCP Async Response Data', + ), +] diff --git a/src/adcp/types/domains/core/async_response_refs/__init__.py b/src/adcp/types/domains/core/async_response_refs/__init__.py index 37382438a..4a4523033 100644 --- a/src/adcp/types/domains/core/async_response_refs/__init__.py +++ b/src/adcp/types/domains/core/async_response_refs/__init__.py @@ -1,7 +1,3 @@ -"""Public mirror of the AdCP ``core/async_response_refs`` schema directory. - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:34:03 UTC -""" - -__all__: list[str] = [] +# generated by datamodel-codegen: +# filename: .schema_temp +# timestamp: 2026-05-29T23:56:01+00:00 diff --git a/src/adcp/types/domains/core/async_response_refs/creative/__init__.py b/src/adcp/types/domains/core/async_response_refs/creative/__init__.py index 01e9ef446..4a4523033 100644 --- a/src/adcp/types/domains/core/async_response_refs/creative/__init__.py +++ b/src/adcp/types/domains/core/async_response_refs/creative/__init__.py @@ -1,7 +1,3 @@ -"""Public mirror of the AdCP ``core/async_response_refs/creative`` schema directory. - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:34:03 UTC -""" - -__all__: list[str] = [] +# generated by datamodel-codegen: +# filename: .schema_temp +# timestamp: 2026-05-29T23:56:01+00:00 diff --git a/src/adcp/types/domains/core/async_response_refs/creative/get_creative_features_async_response_submitted.py b/src/adcp/types/domains/core/async_response_refs/creative/get_creative_features_async_response_submitted.py index 83e4327ef..6b0bd0095 100644 --- a/src/adcp/types/domains/core/async_response_refs/creative/get_creative_features_async_response_submitted.py +++ b/src/adcp/types/domains/core/async_response_refs/creative/get_creative_features_async_response_submitted.py @@ -1,20 +1,48 @@ -"""Types declared by the AdCP ``core/async_response_refs/creative/get_creative_features_async_response_submitted`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/creative/get_creative_features_async_response_submitted.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.async_response_refs.creative.get_creative_features_async_response_submitted import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ... import context as context_1 +from ... import ext as ext_1 -from adcp.types.generated_poc.core.async_response_refs.creative.get_creative_features_async_response_submitted import ( - GetCreativeFeaturesSubmitted, -) -# Explicit exports -__all__ = ["GetCreativeFeaturesSubmitted"] +class GetCreativeFeaturesSubmitted(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + status: Annotated[ + Literal['submitted'], + Field( + description='Task-level status literal that discriminates this acknowledgement from terminal success and error responses.' + ), + ] = 'submitted' + task_id: Annotated[ + str, + Field( + description='AdCP task handle used to poll get_task_status or correlate terminal webhook delivery. Distinct from evaluation_id.', + min_length=1, + ), + ] + evaluation_id: Annotated[ + str | None, + Field( + description='Provider-generated identity allocated when the evaluation is accepted. Optional in AdCP 3.x and required in AdCP 4.0. Providers SHOULD emit it in 3.x; when present, exact replays and the terminal result MUST carry this same value. Distinct from task_id and idempotency_key.', + min_length=1, + ), + ] = None + message: Annotated[ + str | None, + Field( + description='Optional human-readable explanation of why the evaluation is submitted. Plain text only; callers treat it as untrusted provider input.', + max_length=2000, + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/async_response_refs/creative/sync_creatives_async_response_input_required.py b/src/adcp/types/domains/core/async_response_refs/creative/sync_creatives_async_response_input_required.py index f84c7d376..abe340e98 100644 --- a/src/adcp/types/domains/core/async_response_refs/creative/sync_creatives_async_response_input_required.py +++ b/src/adcp/types/domains/core/async_response_refs/creative/sync_creatives_async_response_input_required.py @@ -1,21 +1,31 @@ -"""Types declared by the AdCP ``core/async_response_refs/creative/sync_creatives_async_response_input_required`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/creative/sync_creatives_async_response_input_required.json +# timestamp: 2026-05-29T23:56:01+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.async_response_refs.creative.sync_creatives_async_response_input_required import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ... import context as context_1 +from ... import ext as ext_1 + + +class Reason(StrEnum): + APPROVAL_REQUIRED = 'APPROVAL_REQUIRED' + ASSET_CONFIRMATION = 'ASSET_CONFIRMATION' + FORMAT_CLARIFICATION = 'FORMAT_CLARIFICATION' -from adcp.types.generated_poc.core.async_response_refs.creative.sync_creatives_async_response_input_required import ( - Reason, - SyncCreativesInputRequired, -) -# Explicit exports -__all__ = ["Reason", "SyncCreativesInputRequired"] +class SyncCreativesInputRequired(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + reason: Annotated[ + Reason | None, Field(description='Reason code indicating why buyer input is needed') + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/async_response_refs/creative/sync_creatives_async_response_submitted.py b/src/adcp/types/domains/core/async_response_refs/creative/sync_creatives_async_response_submitted.py index 74e444d6b..9c87f5d91 100644 --- a/src/adcp/types/domains/core/async_response_refs/creative/sync_creatives_async_response_submitted.py +++ b/src/adcp/types/domains/core/async_response_refs/creative/sync_creatives_async_response_submitted.py @@ -1,20 +1,47 @@ -"""Types declared by the AdCP ``core/async_response_refs/creative/sync_creatives_async_response_submitted`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/creative/sync_creatives_async_response_submitted.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.async_response_refs.creative.sync_creatives_async_response_submitted import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ... import context as context_1 +from ... import error +from ... import ext as ext_1 -from adcp.types.generated_poc.core.async_response_refs.creative.sync_creatives_async_response_submitted import ( - SyncCreativesSubmitted, -) -# Explicit exports -__all__ = ["SyncCreativesSubmitted"] +class SyncCreativesSubmitted(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + status: Annotated[ + Literal['submitted'], + Field( + description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, whose creatives array carries per-item approval state via CreativeStatus. See task-status.json for the full task-status enum.' + ), + ] = 'submitted' + task_id: Annotated[ + str, + Field( + description='Task handle the buyer uses with get_task_status (or the legacy AdCP tasks/get alias), and that the seller references on push-notification callbacks. The creatives array is issued on the completion artifact, not here. This AdCP application-layer handle remains the snake_case task_id in every transport payload and is distinct from any transport-native A2A Task id.' + ), + ] + message: Annotated[ + str | None, + Field( + description="Optional human-readable explanation of why the task is submitted — e.g., 'Batch ingestion queued; typical turnaround 15-30 minutes.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", + max_length=2000, + ), + ] = None + errors: Annotated[ + list[error.Error] | None, + Field( + description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/async_response_refs/creative/sync_creatives_async_response_working.py b/src/adcp/types/domains/core/async_response_refs/creative/sync_creatives_async_response_working.py index 42cfc3e9c..e306bbcdf 100644 --- a/src/adcp/types/domains/core/async_response_refs/creative/sync_creatives_async_response_working.py +++ b/src/adcp/types/domains/core/async_response_refs/creative/sync_creatives_async_response_working.py @@ -1,20 +1,37 @@ -"""Types declared by the AdCP ``core/async_response_refs/creative/sync_creatives_async_response_working`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/creative/sync_creatives_async_response_working.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.async_response_refs.creative.sync_creatives_async_response_working import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ... import context as context_1 +from ... import ext as ext_1 -from adcp.types.generated_poc.core.async_response_refs.creative.sync_creatives_async_response_working import ( - SyncCreativesWorking, -) -# Explicit exports -__all__ = ["SyncCreativesWorking"] +class SyncCreativesWorking(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + percentage: Annotated[ + StrictFloat | None, Field(description='Completion percentage (0-100)', ge=0.0, le=100.0) + ] = None + current_step: Annotated[ + str | None, Field(description='Current step or phase of the operation') + ] = None + total_steps: Annotated[ + SchemaInt | None, Field(description='Total number of steps in the operation', ge=1) + ] = None + step_number: Annotated[SchemaInt | None, Field(description='Current step number', ge=1)] = None + creatives_processed: Annotated[ + SchemaInt | None, Field(description='Number of creatives processed so far', ge=0) + ] = None + creatives_total: Annotated[ + SchemaInt | None, Field(description='Total number of creatives to process', ge=0) + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/async_response_refs/media_buy/__init__.py b/src/adcp/types/domains/core/async_response_refs/media_buy/__init__.py index eb9a05387..4a4523033 100644 --- a/src/adcp/types/domains/core/async_response_refs/media_buy/__init__.py +++ b/src/adcp/types/domains/core/async_response_refs/media_buy/__init__.py @@ -1,7 +1,3 @@ -"""Public mirror of the AdCP ``core/async_response_refs/media_buy`` schema directory. - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:34:03 UTC -""" - -__all__: list[str] = [] +# generated by datamodel-codegen: +# filename: .schema_temp +# timestamp: 2026-05-29T23:56:01+00:00 diff --git a/src/adcp/types/domains/core/async_response_refs/media_buy/accept_proposal_async_response_input_required.py b/src/adcp/types/domains/core/async_response_refs/media_buy/accept_proposal_async_response_input_required.py index ecc2328b6..a06ebe861 100644 --- a/src/adcp/types/domains/core/async_response_refs/media_buy/accept_proposal_async_response_input_required.py +++ b/src/adcp/types/domains/core/async_response_refs/media_buy/accept_proposal_async_response_input_required.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``core/async_response_refs/media_buy/accept_proposal_async_response_input_required`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/media_buy/accept_proposal_async_response_input_required.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.async_response_refs.media_buy.accept_proposal_async_response_input_required import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.async_response_refs.media_buy.accept_proposal_async_response_input_required import ( - AcceptProposalInputRequired, -) +from ...compact_task_input_required import CompactTaskInputRequired + -# Explicit exports -__all__ = ["AcceptProposalInputRequired"] +class AcceptProposalInputRequired(CompactTaskInputRequired): + pass diff --git a/src/adcp/types/domains/core/async_response_refs/media_buy/accept_proposal_async_response_submitted.py b/src/adcp/types/domains/core/async_response_refs/media_buy/accept_proposal_async_response_submitted.py index a6bb9a238..6d66fd5c6 100644 --- a/src/adcp/types/domains/core/async_response_refs/media_buy/accept_proposal_async_response_submitted.py +++ b/src/adcp/types/domains/core/async_response_refs/media_buy/accept_proposal_async_response_submitted.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``core/async_response_refs/media_buy/accept_proposal_async_response_submitted`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/media_buy/accept_proposal_async_response_submitted.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.async_response_refs.media_buy.accept_proposal_async_response_submitted import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.async_response_refs.media_buy.accept_proposal_async_response_submitted import ( - AcceptProposalSubmitted, -) +from ...compact_task_submitted import CompactTaskSubmitted + -# Explicit exports -__all__ = ["AcceptProposalSubmitted"] +class AcceptProposalSubmitted(CompactTaskSubmitted): + pass diff --git a/src/adcp/types/domains/core/async_response_refs/media_buy/accept_proposal_async_response_working.py b/src/adcp/types/domains/core/async_response_refs/media_buy/accept_proposal_async_response_working.py index 32b186ee0..5aa6c23f4 100644 --- a/src/adcp/types/domains/core/async_response_refs/media_buy/accept_proposal_async_response_working.py +++ b/src/adcp/types/domains/core/async_response_refs/media_buy/accept_proposal_async_response_working.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``core/async_response_refs/media_buy/accept_proposal_async_response_working`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/media_buy/accept_proposal_async_response_working.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.async_response_refs.media_buy.accept_proposal_async_response_working import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.async_response_refs.media_buy.accept_proposal_async_response_working import ( - AcceptProposalWorking, -) +from ...compact_task_working import CompactTaskWorking + -# Explicit exports -__all__ = ["AcceptProposalWorking"] +class AcceptProposalWorking(CompactTaskWorking): + pass diff --git a/src/adcp/types/domains/core/async_response_refs/media_buy/build_creative_async_response_input_required.py b/src/adcp/types/domains/core/async_response_refs/media_buy/build_creative_async_response_input_required.py index 43af36539..a322fbc3e 100644 --- a/src/adcp/types/domains/core/async_response_refs/media_buy/build_creative_async_response_input_required.py +++ b/src/adcp/types/domains/core/async_response_refs/media_buy/build_creative_async_response_input_required.py @@ -1,21 +1,38 @@ -"""Types declared by the AdCP ``core/async_response_refs/media_buy/build_creative_async_response_input_required`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/media_buy/build_creative_async_response_input_required.json +# timestamp: 2026-05-29T23:56:01+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.async_response_refs.media_buy.build_creative_async_response_input_required import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ... import context as context_1 +from ... import error +from ... import ext as ext_1 + + +class Reason(StrEnum): + APPROVAL_REQUIRED = 'APPROVAL_REQUIRED' + CREATIVE_DIRECTION_NEEDED = 'CREATIVE_DIRECTION_NEEDED' + ASSET_SELECTION_NEEDED = 'ASSET_SELECTION_NEEDED' -from adcp.types.generated_poc.core.async_response_refs.media_buy.build_creative_async_response_input_required import ( - BuildCreativeInputRequired, - Reason, -) -# Explicit exports -__all__ = ["BuildCreativeInputRequired", "Reason"] +class BuildCreativeInputRequired(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + reason: Annotated[ + Reason | None, Field(description='Reason code indicating why input is needed') + ] = None + errors: Annotated[ + list[error.Error] | None, + Field( + description='Optional validation errors or warnings explaining why input is required.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/async_response_refs/media_buy/build_creative_async_response_submitted.py b/src/adcp/types/domains/core/async_response_refs/media_buy/build_creative_async_response_submitted.py index b714d7cd6..2fb45a85a 100644 --- a/src/adcp/types/domains/core/async_response_refs/media_buy/build_creative_async_response_submitted.py +++ b/src/adcp/types/domains/core/async_response_refs/media_buy/build_creative_async_response_submitted.py @@ -1,20 +1,47 @@ -"""Types declared by the AdCP ``core/async_response_refs/media_buy/build_creative_async_response_submitted`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/media_buy/build_creative_async_response_submitted.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.async_response_refs.media_buy.build_creative_async_response_submitted import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ... import context as context_1 +from ... import error +from ... import ext as ext_1 -from adcp.types.generated_poc.core.async_response_refs.media_buy.build_creative_async_response_submitted import ( - BuildCreativeSubmitted, -) -# Explicit exports -__all__ = ["BuildCreativeSubmitted"] +class BuildCreativeSubmitted(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + status: Annotated[ + Literal['submitted'], + Field( + description='Task-level status literal. Discriminates this async envelope from the synchronous success shapes, whose creative_manifest or creative_manifests are issued in-line. See task-status.json for the full task-status enum.' + ), + ] = 'submitted' + task_id: Annotated[ + str, + Field( + description='Task handle the buyer uses with get_task_status (or the legacy AdCP tasks/get alias), and that the seller references on push-notification callbacks. This AdCP application-layer handle remains the snake_case task_id in every transport payload and is distinct from any transport-native A2A Task id.' + ), + ] + message: Annotated[ + str | None, + Field( + description="Optional human-readable explanation of why the task is submitted — e.g., 'Generative build queued; typical turnaround 3–5 minutes.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", + max_length=2000, + ), + ] = None + errors: Annotated[ + list[error.Error] | None, + Field( + description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/async_response_refs/media_buy/build_creative_async_response_working.py b/src/adcp/types/domains/core/async_response_refs/media_buy/build_creative_async_response_working.py index ebeadf435..653e18b6b 100644 --- a/src/adcp/types/domains/core/async_response_refs/media_buy/build_creative_async_response_working.py +++ b/src/adcp/types/domains/core/async_response_refs/media_buy/build_creative_async_response_working.py @@ -1,20 +1,34 @@ -"""Types declared by the AdCP ``core/async_response_refs/media_buy/build_creative_async_response_working`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/media_buy/build_creative_async_response_working.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.async_response_refs.media_buy.build_creative_async_response_working import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ... import context as context_1 +from ... import ext as ext_1 -from adcp.types.generated_poc.core.async_response_refs.media_buy.build_creative_async_response_working import ( - BuildCreativeWorking, -) -# Explicit exports -__all__ = ["BuildCreativeWorking"] +class BuildCreativeWorking(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + percentage: Annotated[ + StrictFloat | None, Field(description='Completion percentage (0-100)', ge=0.0, le=100.0) + ] = None + current_step: Annotated[ + str | None, + Field( + description="Current step or phase of the operation (e.g., 'generating_assets', 'resolving_macros', 'rendering_preview')" + ), + ] = None + total_steps: Annotated[ + SchemaInt | None, Field(description='Total number of steps in the operation', ge=1) + ] = None + step_number: Annotated[SchemaInt | None, Field(description='Current step number', ge=1)] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/async_response_refs/media_buy/buy_products_async_response_input_required.py b/src/adcp/types/domains/core/async_response_refs/media_buy/buy_products_async_response_input_required.py index 8dae0db91..29d5786e3 100644 --- a/src/adcp/types/domains/core/async_response_refs/media_buy/buy_products_async_response_input_required.py +++ b/src/adcp/types/domains/core/async_response_refs/media_buy/buy_products_async_response_input_required.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``core/async_response_refs/media_buy/buy_products_async_response_input_required`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/media_buy/buy_products_async_response_input_required.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.async_response_refs.media_buy.buy_products_async_response_input_required import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.async_response_refs.media_buy.buy_products_async_response_input_required import ( - BuyProductsInputRequired, -) +from ...compact_task_input_required import CompactTaskInputRequired + -# Explicit exports -__all__ = ["BuyProductsInputRequired"] +class BuyProductsInputRequired(CompactTaskInputRequired): + pass diff --git a/src/adcp/types/domains/core/async_response_refs/media_buy/buy_products_async_response_submitted.py b/src/adcp/types/domains/core/async_response_refs/media_buy/buy_products_async_response_submitted.py index 2f81268cf..440962ec4 100644 --- a/src/adcp/types/domains/core/async_response_refs/media_buy/buy_products_async_response_submitted.py +++ b/src/adcp/types/domains/core/async_response_refs/media_buy/buy_products_async_response_submitted.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``core/async_response_refs/media_buy/buy_products_async_response_submitted`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/media_buy/buy_products_async_response_submitted.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.async_response_refs.media_buy.buy_products_async_response_submitted import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.async_response_refs.media_buy.buy_products_async_response_submitted import ( - BuyProductsSubmitted, -) +from ...compact_task_submitted import CompactTaskSubmitted + -# Explicit exports -__all__ = ["BuyProductsSubmitted"] +class BuyProductsSubmitted(CompactTaskSubmitted): + pass diff --git a/src/adcp/types/domains/core/async_response_refs/media_buy/buy_products_async_response_working.py b/src/adcp/types/domains/core/async_response_refs/media_buy/buy_products_async_response_working.py index 37e5ff6ac..3828a18e8 100644 --- a/src/adcp/types/domains/core/async_response_refs/media_buy/buy_products_async_response_working.py +++ b/src/adcp/types/domains/core/async_response_refs/media_buy/buy_products_async_response_working.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``core/async_response_refs/media_buy/buy_products_async_response_working`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/media_buy/buy_products_async_response_working.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.async_response_refs.media_buy.buy_products_async_response_working import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.async_response_refs.media_buy.buy_products_async_response_working import ( - BuyProductsWorking, -) +from ...compact_task_working import CompactTaskWorking + -# Explicit exports -__all__ = ["BuyProductsWorking"] +class BuyProductsWorking(CompactTaskWorking): + pass diff --git a/src/adcp/types/domains/core/async_response_refs/media_buy/control_media_buy_async_response_input_required.py b/src/adcp/types/domains/core/async_response_refs/media_buy/control_media_buy_async_response_input_required.py index d74e1c7a5..b02ecc6d6 100644 --- a/src/adcp/types/domains/core/async_response_refs/media_buy/control_media_buy_async_response_input_required.py +++ b/src/adcp/types/domains/core/async_response_refs/media_buy/control_media_buy_async_response_input_required.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``core/async_response_refs/media_buy/control_media_buy_async_response_input_required`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/media_buy/control_media_buy_async_response_input_required.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.async_response_refs.media_buy.control_media_buy_async_response_input_required import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.async_response_refs.media_buy.control_media_buy_async_response_input_required import ( - ControlMediaBuyInputRequired, -) +from ...compact_task_input_required import CompactTaskInputRequired + -# Explicit exports -__all__ = ["ControlMediaBuyInputRequired"] +class ControlMediaBuyInputRequired(CompactTaskInputRequired): + pass diff --git a/src/adcp/types/domains/core/async_response_refs/media_buy/control_media_buy_async_response_submitted.py b/src/adcp/types/domains/core/async_response_refs/media_buy/control_media_buy_async_response_submitted.py index 137400e50..70f02ccc6 100644 --- a/src/adcp/types/domains/core/async_response_refs/media_buy/control_media_buy_async_response_submitted.py +++ b/src/adcp/types/domains/core/async_response_refs/media_buy/control_media_buy_async_response_submitted.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``core/async_response_refs/media_buy/control_media_buy_async_response_submitted`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/media_buy/control_media_buy_async_response_submitted.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.async_response_refs.media_buy.control_media_buy_async_response_submitted import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.async_response_refs.media_buy.control_media_buy_async_response_submitted import ( - ControlMediaBuySubmitted, -) +from ...compact_task_submitted import CompactTaskSubmitted + -# Explicit exports -__all__ = ["ControlMediaBuySubmitted"] +class ControlMediaBuySubmitted(CompactTaskSubmitted): + pass diff --git a/src/adcp/types/domains/core/async_response_refs/media_buy/control_media_buy_async_response_working.py b/src/adcp/types/domains/core/async_response_refs/media_buy/control_media_buy_async_response_working.py index 2773a103d..11a44fec5 100644 --- a/src/adcp/types/domains/core/async_response_refs/media_buy/control_media_buy_async_response_working.py +++ b/src/adcp/types/domains/core/async_response_refs/media_buy/control_media_buy_async_response_working.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``core/async_response_refs/media_buy/control_media_buy_async_response_working`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/media_buy/control_media_buy_async_response_working.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.async_response_refs.media_buy.control_media_buy_async_response_working import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.async_response_refs.media_buy.control_media_buy_async_response_working import ( - ControlMediaBuyWorking, -) +from ...compact_task_working import CompactTaskWorking + -# Explicit exports -__all__ = ["ControlMediaBuyWorking"] +class ControlMediaBuyWorking(CompactTaskWorking): + pass diff --git a/src/adcp/types/domains/core/async_response_refs/media_buy/create_media_buy_async_response_input_required.py b/src/adcp/types/domains/core/async_response_refs/media_buy/create_media_buy_async_response_input_required.py index 78e00b4ca..bb7da427a 100644 --- a/src/adcp/types/domains/core/async_response_refs/media_buy/create_media_buy_async_response_input_required.py +++ b/src/adcp/types/domains/core/async_response_refs/media_buy/create_media_buy_async_response_input_required.py @@ -1,21 +1,37 @@ -"""Types declared by the AdCP ``core/async_response_refs/media_buy/create_media_buy_async_response_input_required`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/media_buy/create_media_buy_async_response_input_required.json +# timestamp: 2026-05-29T23:56:01+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.async_response_refs.media_buy.create_media_buy_async_response_input_required import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ... import context as context_1 +from ... import error +from ... import ext as ext_1 + + +class Reason(StrEnum): + APPROVAL_REQUIRED = 'APPROVAL_REQUIRED' + BUDGET_EXCEEDS_LIMIT = 'BUDGET_EXCEEDS_LIMIT' -from adcp.types.generated_poc.core.async_response_refs.media_buy.create_media_buy_async_response_input_required import ( - CreateMediaBuyInputRequired, - Reason, -) -# Explicit exports -__all__ = ["CreateMediaBuyInputRequired", "Reason"] +class CreateMediaBuyInputRequired(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + reason: Annotated[ + Reason | None, Field(description='Reason code indicating why input is needed') + ] = None + errors: Annotated[ + list[error.Error] | None, + Field( + description='Optional validation errors or warnings for debugging purposes. Helps explain why input is required.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/async_response_refs/media_buy/create_media_buy_async_response_submitted.py b/src/adcp/types/domains/core/async_response_refs/media_buy/create_media_buy_async_response_submitted.py index d19ee9da9..19fa10e0a 100644 --- a/src/adcp/types/domains/core/async_response_refs/media_buy/create_media_buy_async_response_submitted.py +++ b/src/adcp/types/domains/core/async_response_refs/media_buy/create_media_buy_async_response_submitted.py @@ -1,20 +1,47 @@ -"""Types declared by the AdCP ``core/async_response_refs/media_buy/create_media_buy_async_response_submitted`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/media_buy/create_media_buy_async_response_submitted.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.async_response_refs.media_buy.create_media_buy_async_response_submitted import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ... import context as context_1 +from ... import error +from ... import ext as ext_1 -from adcp.types.generated_poc.core.async_response_refs.media_buy.create_media_buy_async_response_submitted import ( - CreateMediaBuySubmitted, -) -# Explicit exports -__all__ = ["CreateMediaBuySubmitted"] +class CreateMediaBuySubmitted(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + status: Annotated[ + Literal['submitted'], + Field( + description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, which carries media-buy lifecycle state in `media_buy_status`. See task-status.json for the full task-status enum.' + ), + ] = 'submitted' + task_id: Annotated[ + str, + Field( + description='Task handle the buyer uses with get_task_status (or the legacy AdCP tasks/get alias), and that the seller references on push-notification callbacks. The media_buy_id is issued on the completion artifact, not here. This AdCP application-layer handle remains the snake_case task_id in every transport payload and is distinct from any transport-native A2A Task id.' + ), + ] + message: Annotated[ + str | None, + Field( + description="Optional human-readable explanation of why the task is submitted — e.g., 'Awaiting IO signature from sales team; typical turnaround 2–4 hours.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", + max_length=2000, + ), + ] = None + errors: Annotated[ + list[error.Error] | None, + Field( + description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/async_response_refs/media_buy/create_media_buy_async_response_working.py b/src/adcp/types/domains/core/async_response_refs/media_buy/create_media_buy_async_response_working.py index 399c890ae..b09e8be39 100644 --- a/src/adcp/types/domains/core/async_response_refs/media_buy/create_media_buy_async_response_working.py +++ b/src/adcp/types/domains/core/async_response_refs/media_buy/create_media_buy_async_response_working.py @@ -1,20 +1,31 @@ -"""Types declared by the AdCP ``core/async_response_refs/media_buy/create_media_buy_async_response_working`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/media_buy/create_media_buy_async_response_working.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.async_response_refs.media_buy.create_media_buy_async_response_working import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ... import context as context_1 +from ... import ext as ext_1 -from adcp.types.generated_poc.core.async_response_refs.media_buy.create_media_buy_async_response_working import ( - CreateMediaBuyWorking, -) -# Explicit exports -__all__ = ["CreateMediaBuyWorking"] +class CreateMediaBuyWorking(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + percentage: Annotated[ + StrictFloat | None, Field(description='Completion percentage (0-100)', ge=0.0, le=100.0) + ] = None + current_step: Annotated[ + str | None, Field(description='Current step or phase of the operation') + ] = None + total_steps: Annotated[ + SchemaInt | None, Field(description='Total number of steps in the operation', ge=1) + ] = None + step_number: Annotated[SchemaInt | None, Field(description='Current step number', ge=1)] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/async_response_refs/media_buy/decline_proposals_async_response_input_required.py b/src/adcp/types/domains/core/async_response_refs/media_buy/decline_proposals_async_response_input_required.py index 4de23310d..3452c3a47 100644 --- a/src/adcp/types/domains/core/async_response_refs/media_buy/decline_proposals_async_response_input_required.py +++ b/src/adcp/types/domains/core/async_response_refs/media_buy/decline_proposals_async_response_input_required.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``core/async_response_refs/media_buy/decline_proposals_async_response_input_required`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/media_buy/decline_proposals_async_response_input_required.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.async_response_refs.media_buy.decline_proposals_async_response_input_required import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.async_response_refs.media_buy.decline_proposals_async_response_input_required import ( - DeclineProposalsInputRequired, -) +from ...compact_task_input_required import CompactTaskInputRequired + -# Explicit exports -__all__ = ["DeclineProposalsInputRequired"] +class DeclineProposalsInputRequired(CompactTaskInputRequired): + pass diff --git a/src/adcp/types/domains/core/async_response_refs/media_buy/decline_proposals_async_response_submitted.py b/src/adcp/types/domains/core/async_response_refs/media_buy/decline_proposals_async_response_submitted.py index 01ffc906e..28872c305 100644 --- a/src/adcp/types/domains/core/async_response_refs/media_buy/decline_proposals_async_response_submitted.py +++ b/src/adcp/types/domains/core/async_response_refs/media_buy/decline_proposals_async_response_submitted.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``core/async_response_refs/media_buy/decline_proposals_async_response_submitted`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/media_buy/decline_proposals_async_response_submitted.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.async_response_refs.media_buy.decline_proposals_async_response_submitted import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.async_response_refs.media_buy.decline_proposals_async_response_submitted import ( - DeclineProposalsSubmitted, -) +from ...compact_task_submitted import CompactTaskSubmitted + -# Explicit exports -__all__ = ["DeclineProposalsSubmitted"] +class DeclineProposalsSubmitted(CompactTaskSubmitted): + pass diff --git a/src/adcp/types/domains/core/async_response_refs/media_buy/decline_proposals_async_response_working.py b/src/adcp/types/domains/core/async_response_refs/media_buy/decline_proposals_async_response_working.py index 0b71972a6..0e902ab30 100644 --- a/src/adcp/types/domains/core/async_response_refs/media_buy/decline_proposals_async_response_working.py +++ b/src/adcp/types/domains/core/async_response_refs/media_buy/decline_proposals_async_response_working.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``core/async_response_refs/media_buy/decline_proposals_async_response_working`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/media_buy/decline_proposals_async_response_working.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.async_response_refs.media_buy.decline_proposals_async_response_working import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.async_response_refs.media_buy.decline_proposals_async_response_working import ( - DeclineProposalsWorking, -) +from ...compact_task_working import CompactTaskWorking + -# Explicit exports -__all__ = ["DeclineProposalsWorking"] +class DeclineProposalsWorking(CompactTaskWorking): + pass diff --git a/src/adcp/types/domains/core/async_response_refs/media_buy/get_products_async_response_input_required.py b/src/adcp/types/domains/core/async_response_refs/media_buy/get_products_async_response_input_required.py index 02ebb1382..33e346407 100644 --- a/src/adcp/types/domains/core/async_response_refs/media_buy/get_products_async_response_input_required.py +++ b/src/adcp/types/domains/core/async_response_refs/media_buy/get_products_async_response_input_required.py @@ -1,21 +1,38 @@ -"""Types declared by the AdCP ``core/async_response_refs/media_buy/get_products_async_response_input_required`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/media_buy/get_products_async_response_input_required.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.async_response_refs.media_buy.get_products_async_response_input_required import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ... import context as context_1 +from ... import ext as ext_1 +from ... import product + + +class Reason(StrEnum): + CLARIFICATION_NEEDED = 'CLARIFICATION_NEEDED' + BUDGET_REQUIRED = 'BUDGET_REQUIRED' -from adcp.types.generated_poc.core.async_response_refs.media_buy.get_products_async_response_input_required import ( - GetProductsInputRequired, - Reason, -) -# Explicit exports -__all__ = ["GetProductsInputRequired", "Reason"] +class GetProductsInputRequired(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + reason: Annotated[ + Reason | None, Field(description='Reason code indicating why input is needed') + ] = None + partial_results: Annotated[ + list[product.Product] | None, + Field(description='Partial product results that may help inform the clarification'), + ] = None + suggestions: Annotated[ + list[str] | None, Field(description='Suggested values or options for the required input') + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/async_response_refs/media_buy/get_products_async_response_submitted.py b/src/adcp/types/domains/core/async_response_refs/media_buy/get_products_async_response_submitted.py index 71f4f5442..5e4be1c86 100644 --- a/src/adcp/types/domains/core/async_response_refs/media_buy/get_products_async_response_submitted.py +++ b/src/adcp/types/domains/core/async_response_refs/media_buy/get_products_async_response_submitted.py @@ -1,20 +1,50 @@ -"""Types declared by the AdCP ``core/async_response_refs/media_buy/get_products_async_response_submitted`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/media_buy/get_products_async_response_submitted.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.async_response_refs.media_buy.get_products_async_response_submitted import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ... import context as context_1 +from ... import error +from ... import ext as ext_1 -from adcp.types.generated_poc.core.async_response_refs.media_buy.get_products_async_response_submitted import ( - GetProductsSubmitted, -) -# Explicit exports -__all__ = ["GetProductsSubmitted"] +class GetProductsSubmitted(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + status: Annotated[ + Literal['submitted'], + Field( + description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, whose products array is issued in-line. See task-status.json for the full task-status enum.' + ), + ] = 'submitted' + task_id: Annotated[ + str, + Field( + description='Task handle the buyer uses with get_task_status (or the legacy AdCP tasks/get alias), and that the seller references on push-notification callbacks. The products array is issued on the completion artifact, not here. This AdCP application-layer handle remains the snake_case task_id in every transport payload and is distinct from any transport-native A2A Task id.' + ), + ] + message: Annotated[ + str | None, + Field( + description="Optional human-readable explanation of why the task is submitted — e.g., 'Custom curation queued; typical turnaround 10–30 minutes.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", + max_length=2000, + ), + ] = None + estimated_completion: Annotated[ + AwareDatetime | None, Field(description='Estimated completion time for the search') + ] = None + errors: Annotated[ + list[error.Error] | None, + Field( + description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/async_response_refs/media_buy/get_products_async_response_working.py b/src/adcp/types/domains/core/async_response_refs/media_buy/get_products_async_response_working.py index 88c4ac4e6..49a7e2e4c 100644 --- a/src/adcp/types/domains/core/async_response_refs/media_buy/get_products_async_response_working.py +++ b/src/adcp/types/domains/core/async_response_refs/media_buy/get_products_async_response_working.py @@ -1,20 +1,37 @@ -"""Types declared by the AdCP ``core/async_response_refs/media_buy/get_products_async_response_working`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/media_buy/get_products_async_response_working.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.async_response_refs.media_buy.get_products_async_response_working import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ... import context as context_1 +from ... import ext as ext_1 -from adcp.types.generated_poc.core.async_response_refs.media_buy.get_products_async_response_working import ( - GetProductsWorking, -) -# Explicit exports -__all__ = ["GetProductsWorking"] +class GetProductsWorking(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + percentage: Annotated[ + StrictFloat | None, + Field(description='Progress percentage of the search operation', ge=0.0, le=100.0), + ] = None + current_step: Annotated[ + str | None, + Field( + description="Current step in the search process (e.g., 'searching_inventory', 'validating_availability')" + ), + ] = None + total_steps: Annotated[ + SchemaInt | None, Field(description='Total number of steps in the search process') + ] = None + step_number: Annotated[ + SchemaInt | None, Field(description='Current step number (1-indexed)') + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/async_response_refs/media_buy/refine_proposals_async_response_input_required.py b/src/adcp/types/domains/core/async_response_refs/media_buy/refine_proposals_async_response_input_required.py index ccfd1ccd0..8fc8c2c45 100644 --- a/src/adcp/types/domains/core/async_response_refs/media_buy/refine_proposals_async_response_input_required.py +++ b/src/adcp/types/domains/core/async_response_refs/media_buy/refine_proposals_async_response_input_required.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``core/async_response_refs/media_buy/refine_proposals_async_response_input_required`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/media_buy/refine_proposals_async_response_input_required.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.async_response_refs.media_buy.refine_proposals_async_response_input_required import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.async_response_refs.media_buy.refine_proposals_async_response_input_required import ( - RefineProposalsInputRequired, -) +from ...compact_task_input_required import CompactTaskInputRequired + -# Explicit exports -__all__ = ["RefineProposalsInputRequired"] +class RefineProposalsInputRequired(CompactTaskInputRequired): + pass diff --git a/src/adcp/types/domains/core/async_response_refs/media_buy/refine_proposals_async_response_submitted.py b/src/adcp/types/domains/core/async_response_refs/media_buy/refine_proposals_async_response_submitted.py index fba55dab1..86358f468 100644 --- a/src/adcp/types/domains/core/async_response_refs/media_buy/refine_proposals_async_response_submitted.py +++ b/src/adcp/types/domains/core/async_response_refs/media_buy/refine_proposals_async_response_submitted.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``core/async_response_refs/media_buy/refine_proposals_async_response_submitted`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/media_buy/refine_proposals_async_response_submitted.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.async_response_refs.media_buy.refine_proposals_async_response_submitted import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.async_response_refs.media_buy.refine_proposals_async_response_submitted import ( - RefineProposalsSubmitted, -) +from ...compact_task_submitted import CompactTaskSubmitted + -# Explicit exports -__all__ = ["RefineProposalsSubmitted"] +class RefineProposalsSubmitted(CompactTaskSubmitted): + pass diff --git a/src/adcp/types/domains/core/async_response_refs/media_buy/refine_proposals_async_response_working.py b/src/adcp/types/domains/core/async_response_refs/media_buy/refine_proposals_async_response_working.py index c61855eaf..7efc83e9f 100644 --- a/src/adcp/types/domains/core/async_response_refs/media_buy/refine_proposals_async_response_working.py +++ b/src/adcp/types/domains/core/async_response_refs/media_buy/refine_proposals_async_response_working.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``core/async_response_refs/media_buy/refine_proposals_async_response_working`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/media_buy/refine_proposals_async_response_working.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.async_response_refs.media_buy.refine_proposals_async_response_working import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.async_response_refs.media_buy.refine_proposals_async_response_working import ( - RefineProposalsWorking, -) +from ...compact_task_working import CompactTaskWorking + -# Explicit exports -__all__ = ["RefineProposalsWorking"] +class RefineProposalsWorking(CompactTaskWorking): + pass diff --git a/src/adcp/types/domains/core/async_response_refs/media_buy/request_proposals_async_response_input_required.py b/src/adcp/types/domains/core/async_response_refs/media_buy/request_proposals_async_response_input_required.py index c99608b87..b3c37a0d6 100644 --- a/src/adcp/types/domains/core/async_response_refs/media_buy/request_proposals_async_response_input_required.py +++ b/src/adcp/types/domains/core/async_response_refs/media_buy/request_proposals_async_response_input_required.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``core/async_response_refs/media_buy/request_proposals_async_response_input_required`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/media_buy/request_proposals_async_response_input_required.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.async_response_refs.media_buy.request_proposals_async_response_input_required import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.async_response_refs.media_buy.request_proposals_async_response_input_required import ( - RequestProposalsInputRequired, -) +from ...compact_task_input_required import CompactTaskInputRequired + -# Explicit exports -__all__ = ["RequestProposalsInputRequired"] +class RequestProposalsInputRequired(CompactTaskInputRequired): + pass diff --git a/src/adcp/types/domains/core/async_response_refs/media_buy/request_proposals_async_response_submitted.py b/src/adcp/types/domains/core/async_response_refs/media_buy/request_proposals_async_response_submitted.py index eb95d1d98..f2f7b507b 100644 --- a/src/adcp/types/domains/core/async_response_refs/media_buy/request_proposals_async_response_submitted.py +++ b/src/adcp/types/domains/core/async_response_refs/media_buy/request_proposals_async_response_submitted.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``core/async_response_refs/media_buy/request_proposals_async_response_submitted`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/media_buy/request_proposals_async_response_submitted.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.async_response_refs.media_buy.request_proposals_async_response_submitted import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.async_response_refs.media_buy.request_proposals_async_response_submitted import ( - RequestProposalsSubmitted, -) +from ...compact_task_submitted import CompactTaskSubmitted + -# Explicit exports -__all__ = ["RequestProposalsSubmitted"] +class RequestProposalsSubmitted(CompactTaskSubmitted): + pass diff --git a/src/adcp/types/domains/core/async_response_refs/media_buy/request_proposals_async_response_working.py b/src/adcp/types/domains/core/async_response_refs/media_buy/request_proposals_async_response_working.py index f5992a748..3b9f603c0 100644 --- a/src/adcp/types/domains/core/async_response_refs/media_buy/request_proposals_async_response_working.py +++ b/src/adcp/types/domains/core/async_response_refs/media_buy/request_proposals_async_response_working.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``core/async_response_refs/media_buy/request_proposals_async_response_working`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/media_buy/request_proposals_async_response_working.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.async_response_refs.media_buy.request_proposals_async_response_working import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.async_response_refs.media_buy.request_proposals_async_response_working import ( - RequestProposalsWorking, -) +from ...compact_task_working import CompactTaskWorking + -# Explicit exports -__all__ = ["RequestProposalsWorking"] +class RequestProposalsWorking(CompactTaskWorking): + pass diff --git a/src/adcp/types/domains/core/async_response_refs/media_buy/sync_catalogs_async_response_input_required.py b/src/adcp/types/domains/core/async_response_refs/media_buy/sync_catalogs_async_response_input_required.py index 41fcef6e3..332b83101 100644 --- a/src/adcp/types/domains/core/async_response_refs/media_buy/sync_catalogs_async_response_input_required.py +++ b/src/adcp/types/domains/core/async_response_refs/media_buy/sync_catalogs_async_response_input_required.py @@ -1,21 +1,35 @@ -"""Types declared by the AdCP ``core/async_response_refs/media_buy/sync_catalogs_async_response_input_required`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/media_buy/sync_catalogs_async_response_input_required.json +# timestamp: 2026-05-29T23:56:01+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.async_response_refs.media_buy.sync_catalogs_async_response_input_required import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ... import context as context_1 +from ... import ext as ext_1 + + +class Reason(StrEnum): + APPROVAL_REQUIRED = 'APPROVAL_REQUIRED' + FEED_VALIDATION = 'FEED_VALIDATION' + ITEM_REVIEW = 'ITEM_REVIEW' + FEED_ACCESS = 'FEED_ACCESS' -from adcp.types.generated_poc.core.async_response_refs.media_buy.sync_catalogs_async_response_input_required import ( - Reason, - SyncCatalogsInputRequired, -) -# Explicit exports -__all__ = ["Reason", "SyncCatalogsInputRequired"] +class SyncCatalogsInputRequired(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + reason: Annotated[ + Reason | None, + Field( + description='Reason code indicating why buyer input is needed. APPROVAL_REQUIRED: platform requires explicit approval before activating the catalog. FEED_VALIDATION: feed URL returned unexpected format or schema errors. ITEM_REVIEW: platform flagged items for manual review. FEED_ACCESS: platform cannot access the feed URL (authentication, CORS, etc.).' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/async_response_refs/media_buy/sync_catalogs_async_response_submitted.py b/src/adcp/types/domains/core/async_response_refs/media_buy/sync_catalogs_async_response_submitted.py index 650cf14d5..e50407f29 100644 --- a/src/adcp/types/domains/core/async_response_refs/media_buy/sync_catalogs_async_response_submitted.py +++ b/src/adcp/types/domains/core/async_response_refs/media_buy/sync_catalogs_async_response_submitted.py @@ -1,20 +1,47 @@ -"""Types declared by the AdCP ``core/async_response_refs/media_buy/sync_catalogs_async_response_submitted`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/media_buy/sync_catalogs_async_response_submitted.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.async_response_refs.media_buy.sync_catalogs_async_response_submitted import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ... import context as context_1 +from ... import error +from ... import ext as ext_1 -from adcp.types.generated_poc.core.async_response_refs.media_buy.sync_catalogs_async_response_submitted import ( - SyncCatalogsSubmitted, -) -# Explicit exports -__all__ = ["SyncCatalogsSubmitted"] +class SyncCatalogsSubmitted(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + status: Annotated[ + Literal['submitted'], + Field( + description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, whose catalogs array is issued in-line. See task-status.json for the full task-status enum.' + ), + ] = 'submitted' + task_id: Annotated[ + str, + Field( + description='Task handle the buyer uses with get_task_status (or the legacy AdCP tasks/get alias), and that the seller references on push-notification callbacks. This AdCP application-layer handle remains the snake_case task_id in every transport payload and is distinct from any transport-native A2A Task id.' + ), + ] + message: Annotated[ + str | None, + Field( + description="Optional human-readable explanation of why the task is submitted — e.g., 'Catalog ingestion queued; typical turnaround 5–15 minutes.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", + max_length=2000, + ), + ] = None + errors: Annotated[ + list[error.Error] | None, + Field( + description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/async_response_refs/media_buy/sync_catalogs_async_response_working.py b/src/adcp/types/domains/core/async_response_refs/media_buy/sync_catalogs_async_response_working.py index 6af0447f3..ee29491da 100644 --- a/src/adcp/types/domains/core/async_response_refs/media_buy/sync_catalogs_async_response_working.py +++ b/src/adcp/types/domains/core/async_response_refs/media_buy/sync_catalogs_async_response_working.py @@ -1,20 +1,48 @@ -"""Types declared by the AdCP ``core/async_response_refs/media_buy/sync_catalogs_async_response_working`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/media_buy/sync_catalogs_async_response_working.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.async_response_refs.media_buy.sync_catalogs_async_response_working import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ... import context as context_1 +from ... import ext as ext_1 -from adcp.types.generated_poc.core.async_response_refs.media_buy.sync_catalogs_async_response_working import ( - SyncCatalogsWorking, -) -# Explicit exports -__all__ = ["SyncCatalogsWorking"] +class SyncCatalogsWorking(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + percentage: Annotated[ + StrictFloat | None, Field(description='Completion percentage (0-100)', ge=0.0, le=100.0) + ] = None + current_step: Annotated[ + str | None, + Field( + description="Current step or phase of the operation (e.g., 'Fetching product feed', 'Validating items', 'Platform review')" + ), + ] = None + total_steps: Annotated[ + SchemaInt | None, Field(description='Total number of steps in the operation', ge=1) + ] = None + step_number: Annotated[SchemaInt | None, Field(description='Current step number', ge=1)] = None + catalogs_processed: Annotated[ + SchemaInt | None, Field(description='Number of catalogs processed so far', ge=0) + ] = None + catalogs_total: Annotated[ + SchemaInt | None, Field(description='Total number of catalogs to process', ge=0) + ] = None + items_processed: Annotated[ + SchemaInt | None, + Field(description='Total number of catalog items processed across all catalogs', ge=0), + ] = None + items_total: Annotated[ + SchemaInt | None, + Field(description='Total number of catalog items to process across all catalogs', ge=0), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/async_response_refs/media_buy/update_media_buy_async_response_input_required.py b/src/adcp/types/domains/core/async_response_refs/media_buy/update_media_buy_async_response_input_required.py index 99637b2f8..570fd47d6 100644 --- a/src/adcp/types/domains/core/async_response_refs/media_buy/update_media_buy_async_response_input_required.py +++ b/src/adcp/types/domains/core/async_response_refs/media_buy/update_media_buy_async_response_input_required.py @@ -1,21 +1,30 @@ -"""Types declared by the AdCP ``core/async_response_refs/media_buy/update_media_buy_async_response_input_required`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/media_buy/update_media_buy_async_response_input_required.json +# timestamp: 2026-05-29T23:56:01+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.async_response_refs.media_buy.update_media_buy_async_response_input_required import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ... import context as context_1 +from ... import ext as ext_1 + + +class Reason(StrEnum): + APPROVAL_REQUIRED = 'APPROVAL_REQUIRED' + CHANGE_CONFIRMATION = 'CHANGE_CONFIRMATION' -from adcp.types.generated_poc.core.async_response_refs.media_buy.update_media_buy_async_response_input_required import ( - Reason, - UpdateMediaBuyInputRequired, -) -# Explicit exports -__all__ = ["Reason", "UpdateMediaBuyInputRequired"] +class UpdateMediaBuyInputRequired(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + reason: Annotated[ + Reason | None, Field(description='Reason code indicating why input is needed') + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/async_response_refs/media_buy/update_media_buy_async_response_submitted.py b/src/adcp/types/domains/core/async_response_refs/media_buy/update_media_buy_async_response_submitted.py index 26694a4ba..ef9261393 100644 --- a/src/adcp/types/domains/core/async_response_refs/media_buy/update_media_buy_async_response_submitted.py +++ b/src/adcp/types/domains/core/async_response_refs/media_buy/update_media_buy_async_response_submitted.py @@ -1,20 +1,47 @@ -"""Types declared by the AdCP ``core/async_response_refs/media_buy/update_media_buy_async_response_submitted`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/media_buy/update_media_buy_async_response_submitted.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.async_response_refs.media_buy.update_media_buy_async_response_submitted import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ... import context as context_1 +from ... import error +from ... import ext as ext_1 -from adcp.types.generated_poc.core.async_response_refs.media_buy.update_media_buy_async_response_submitted import ( - UpdateMediaBuySubmitted, -) -# Explicit exports -__all__ = ["UpdateMediaBuySubmitted"] +class UpdateMediaBuySubmitted(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + status: Annotated[ + Literal['submitted'], + Field( + description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, whose media_buy_id is issued in-line. See task-status.json for the full task-status enum.' + ), + ] = 'submitted' + task_id: Annotated[ + str, + Field( + description='Task handle the buyer uses with get_task_status (or the legacy AdCP tasks/get alias), and that the seller references on push-notification callbacks. This AdCP application-layer handle remains the snake_case task_id in every transport payload and is distinct from any transport-native A2A Task id.' + ), + ] + message: Annotated[ + str | None, + Field( + description="Optional human-readable explanation of why the task is submitted — e.g., 'Awaiting operator re-approval; typical turnaround 2–4 hours.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", + max_length=2000, + ), + ] = None + errors: Annotated[ + list[error.Error] | None, + Field( + description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/async_response_refs/media_buy/update_media_buy_async_response_working.py b/src/adcp/types/domains/core/async_response_refs/media_buy/update_media_buy_async_response_working.py index a17604b05..dbe6e7e2c 100644 --- a/src/adcp/types/domains/core/async_response_refs/media_buy/update_media_buy_async_response_working.py +++ b/src/adcp/types/domains/core/async_response_refs/media_buy/update_media_buy_async_response_working.py @@ -1,20 +1,31 @@ -"""Types declared by the AdCP ``core/async_response_refs/media_buy/update_media_buy_async_response_working`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/media_buy/update_media_buy_async_response_working.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.async_response_refs.media_buy.update_media_buy_async_response_working import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ... import context as context_1 +from ... import ext as ext_1 -from adcp.types.generated_poc.core.async_response_refs.media_buy.update_media_buy_async_response_working import ( - UpdateMediaBuyWorking, -) -# Explicit exports -__all__ = ["UpdateMediaBuyWorking"] +class UpdateMediaBuyWorking(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + percentage: Annotated[ + StrictFloat | None, Field(description='Completion percentage (0-100)', ge=0.0, le=100.0) + ] = None + current_step: Annotated[ + str | None, Field(description='Current step or phase of the operation') + ] = None + total_steps: Annotated[ + SchemaInt | None, Field(description='Total number of steps in the operation', ge=1) + ] = None + step_number: Annotated[SchemaInt | None, Field(description='Current step number', ge=1)] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/async_response_refs/signals/__init__.py b/src/adcp/types/domains/core/async_response_refs/signals/__init__.py index 9ea7258ae..0bf6028ff 100644 --- a/src/adcp/types/domains/core/async_response_refs/signals/__init__.py +++ b/src/adcp/types/domains/core/async_response_refs/signals/__init__.py @@ -1,7 +1,3 @@ -"""Public mirror of the AdCP ``core/async_response_refs/signals`` schema directory. - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:34:03 UTC -""" - -__all__: list[str] = [] +# generated by datamodel-codegen: +# filename: .schema_temp +# timestamp: 2026-06-04T19:44:00+00:00 diff --git a/src/adcp/types/domains/core/async_response_refs/signals/get_signals_async_response_submitted.py b/src/adcp/types/domains/core/async_response_refs/signals/get_signals_async_response_submitted.py index 1125579bc..35ec0661d 100644 --- a/src/adcp/types/domains/core/async_response_refs/signals/get_signals_async_response_submitted.py +++ b/src/adcp/types/domains/core/async_response_refs/signals/get_signals_async_response_submitted.py @@ -1,20 +1,51 @@ -"""Types declared by the AdCP ``core/async_response_refs/signals/get_signals_async_response_submitted`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/signals/get_signals_async_response_submitted.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.async_response_refs.signals.get_signals_async_response_submitted import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ... import context as context_1 +from ... import error +from ... import ext as ext_1 -from adcp.types.generated_poc.core.async_response_refs.signals.get_signals_async_response_submitted import ( - GetSignalsSubmitted, -) -# Explicit exports -__all__ = ["GetSignalsSubmitted"] +class GetSignalsSubmitted(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + status: Annotated[ + Literal['submitted'], + Field( + description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, whose signals array is issued in-line. See task-status.json for the full task-status enum.' + ), + ] = 'submitted' + task_id: Annotated[ + str, + Field( + description='Task handle the caller uses with get_task_status (or the legacy AdCP tasks/get alias), and that the agent references on push-notification callbacks. The signals array is issued on the completion artifact, not here. This AdCP application-layer handle remains the snake_case task_id in every transport payload and is distinct from any transport-native A2A Task id.' + ), + ] + message: Annotated[ + str | None, + Field( + description="Optional human-readable explanation of why the task is submitted — e.g., 'Provider discovery queued; typical turnaround 10-30 minutes.' Plain text only. Callers MUST treat this as untrusted agent input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile agent may inject prompt-injection payloads aimed at the caller's agent.", + max_length=2000, + ), + ] = None + estimated_completion: Annotated[ + AwareDatetime | None, + Field(description='Estimated completion time for the signal discovery task.'), + ] = None + errors: Annotated[ + list[error.Error] | None, + Field( + description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories or partial provider unavailability). Terminal failures belong in the error branch, not here.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/async_response_refs/signals/get_signals_async_response_working.py b/src/adcp/types/domains/core/async_response_refs/signals/get_signals_async_response_working.py index a963dbdd4..51c7e5023 100644 --- a/src/adcp/types/domains/core/async_response_refs/signals/get_signals_async_response_working.py +++ b/src/adcp/types/domains/core/async_response_refs/signals/get_signals_async_response_working.py @@ -1,20 +1,40 @@ -"""Types declared by the AdCP ``core/async_response_refs/signals/get_signals_async_response_working`` schema. +# generated by datamodel-codegen: +# filename: core/async_response_refs/signals/get_signals_async_response_working.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.async_response_refs.signals.get_signals_async_response_working import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ... import context as context_1 +from ... import ext as ext_1 -from adcp.types.generated_poc.core.async_response_refs.signals.get_signals_async_response_working import ( - GetSignalsWorking, -) -# Explicit exports -__all__ = ["GetSignalsWorking"] +class GetSignalsWorking(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + percentage: Annotated[ + StrictFloat | None, + Field( + description='Progress percentage of the signal discovery operation.', ge=0.0, le=100.0 + ), + ] = None + current_step: Annotated[ + str | None, + Field( + description='Current step in the signal discovery process, such as `querying_providers`, `ranking_signals`, or `checking_deployments`.' + ), + ] = None + total_steps: Annotated[ + SchemaInt | None, + Field(description='Total number of steps in the signal discovery process.'), + ] = None + step_number: Annotated[ + SchemaInt | None, Field(description='Current step number (1-indexed).') + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/attestation_capabilities.py b/src/adcp/types/domains/core/attestation_capabilities.py index 14731bf26..9cd9b6537 100644 --- a/src/adcp/types/domains/core/attestation_capabilities.py +++ b/src/adcp/types/domains/core/attestation_capabilities.py @@ -1,34 +1,144 @@ -"""Types declared by the AdCP ``core/attestation_capabilities`` schema. +# generated by datamodel-codegen: +# filename: core/attestation_capabilities.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.attestation_capabilities import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AnyUrl, ConfigDict, Field, RootModel + +from . import attestation_issuer +from . import ext as ext_1 + + +class SupportedDeliveryMethod(StrEnum): + credential_uri = 'credential_uri' + issuer_credential_id = 'issuer_credential_id' + embedded = 'embedded' + + +class CredentialOrigin(RootModel[AnyUrl]): + root: AnyUrl + + +class Authentication(StrEnum): + none = 'none' + evaluator_managed = 'evaluator_managed' + + +class Resolver(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + resolver_id: Annotated[str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9._:-]+$')] + url: Annotated[ + AnyUrl, + Field( + description='Evaluator-configured HTTPS resolver endpoint. Calls use POST with Content-Type application/json and a body containing only credential_id; query-string and path interpolation are forbidden. The evaluator still applies the attestation fetch contract before every call.' + ), + ] + authentication: Annotated[ + Authentication, + Field( + description='Whether the resolver is public or uses credentials managed outside AdCP task payloads. Presenter-supplied credentials are never accepted.' + ), + ] + + +class AcceptedVerifier(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + agent_url: AnyUrl + claim_types: Annotated[list[AnyUrl] | None, Field(min_length=1)] = None + proof_formats: Annotated[list[AnyUrl] | None, Field(min_length=1)] = None + ext: ext_1.ExtensionObject | None = None + + +class AcceptedIssuer(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + issuer: attestation_issuer.AttestationIssuer + claim_types: Annotated[ + list[AnyUrl] | None, + Field( + description='Optional subset of accepted_claim_types this issuer may assert. Omit to allow any globally accepted claim type for this issuer.', + min_length=1, + ), + ] = None + proof_formats: Annotated[ + list[AnyUrl] | None, + Field( + description='Optional subset of accepted_proof_formats allowed for this issuer. Omit to allow any globally accepted proof format for this issuer.', + min_length=1, + ), + ] = None + credential_origins: Annotated[ + list[CredentialOrigin] | None, + Field( + description='Canonical HTTPS origins from which credential_uri locators may be fetched for this issuer. Exact origin matching happens after URL canonicalization and before DNS resolution. Paths in the credential URI may vary; userinfo is forbidden.', + min_length=1, + ), + ] = None + resolvers: Annotated[ + list[Resolver] | None, + Field( + description='Evaluator-approved resolver endpoints for issuer_credential_id delivery. Presentations carry only resolver_id; they cannot replace url or authentication policy.', + min_length=1, + ), + ] = None + ext: ext_1.ExtensionObject | None = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.attestation_capabilities import ( - AcceptedIssuer, - AcceptedVerifier, - AttestationCapabilities, - Authentication, - CredentialOrigin, - Resolver, - SupportedDeliveryMethod, -) - -# Explicit exports -__all__ = [ - "AcceptedIssuer", - "AcceptedVerifier", - "AttestationCapabilities", - "Authentication", - "CredentialOrigin", - "Resolver", - "SupportedDeliveryMethod", -] +class AttestationCapabilities(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + accepted_claim_types: Annotated[ + list[AnyUrl], + Field( + description='Open claim identifiers the evaluator is prepared to evaluate. Each value is an absolute URI. Absence means the evaluator has not advertised portable-attestation support; an empty list is not permitted.', + min_length=1, + ), + ] + accepted_proof_formats: Annotated[ + list[AnyUrl], + Field( + description='Open credential/proof format identifiers the evaluator can verify. Values are absolute URIs rather than a protocol enum so issuers can adopt new formats without AdCP endorsement.', + min_length=1, + ), + ] + supported_delivery_methods: Annotated[ + list[SupportedDeliveryMethod], + Field( + description='Credential delivery paths this evaluator supports. credential_uri resolves an HTTPS credential URI from the presentation; issuer_credential_id combines issuer, credential_id, and an evaluator-published resolver_id; embedded accepts an inline credential.', + min_length=1, + ), + ] + accepted_issuers: Annotated[ + list[AcceptedIssuer], + Field( + description='Issuer allowlist and resolver policy. Matching is on the canonical AttestationIssuer identity. A presenter-supplied issuer or credential URI that does not match this policy is rejected without an outbound request.', + min_length=1, + ), + ] + accepted_verifiers: Annotated[ + list[AcceptedVerifier] | None, + Field( + description="Verifier agents the evaluator may call. A presenter's verify_agent nomination must match one of these canonicalized URLs, but the evaluator remains verifier-of-record and chooses whether to use the nominated agent, another accepted agent, or local verification.", + min_length=1, + ), + ] = None + max_embedded_credential_bytes: Annotated[ + SchemaInt | None, + Field( + description='Maximum UTF-8 byte size accepted for one embedded credential. Evaluators MUST enforce this limit before parsing the credential. The protocol ceiling is 1 MiB.', + ge=1024, + le=1048576, + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/attestation_evaluation.py b/src/adcp/types/domains/core/attestation_evaluation.py index 024a09e67..9a0536ab7 100644 --- a/src/adcp/types/domains/core/attestation_evaluation.py +++ b/src/adcp/types/domains/core/attestation_evaluation.py @@ -1,23 +1,140 @@ -"""Types declared by the AdCP ``core/attestation_evaluation`` schema. +# generated by datamodel-codegen: +# filename: core/attestation_evaluation.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.attestation_evaluation import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import ext as ext_1 + + +class Outcome(StrEnum): + verified = 'verified' + not_found = 'not_found' + expired = 'expired' + revoked = 'revoked' + invalid = 'invalid' + unsupported = 'unsupported' + unverifiable = 'unverifiable' + untrusted_issuer = 'untrusted_issuer' + untrusted_resolver = 'untrusted_resolver' + subject_mismatch = 'subject_mismatch' + digest_mismatch = 'digest_mismatch' + resolution_failed = 'resolution_failed' + + +class ReasonCode(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Za-z0-9._:-]+$'} + + +class ActionBinding(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + action_type: Annotated[ + AnyUrl, + Field( + description='Absolute URI naming the action vocabulary, such as an AdCP governance-check or audience-evidence snapshot type.' + ), + ] + action_id: Annotated[ + str | None, + Field( + description="Stable identifier of the consuming action within the domain consumer's namespace.", + max_length=1024, + min_length=1, + ), + ] = None + action_digest: Annotated[ + str | None, + Field( + description='SHA-256 digest of the domain-defined canonical action preimage.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None -from adcp.types.generated_poc.core.attestation_evaluation import ( - ActionBinding, - AttestationEvaluation, - Outcome, - ReasonCode, -) -# Explicit exports -__all__ = ["ActionBinding", "AttestationEvaluation", "Outcome", "ReasonCode"] +class AttestationEvaluation(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + reference_digest: Annotated[ + str, + Field( + description='SHA-256 of the RFC 8785 JSON Canonicalization Scheme encoding of the complete AttestationReference, including embedded_credential when present. This prevents a result for one presentation from being replayed for another.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] + credential_digest: Annotated[ + str | None, + Field( + description='SHA-256 of the exact resolved or embedded credential bytes according to proof_format. REQUIRED by the normative contract when outcome is verified, expired, revoked, invalid, subject_mismatch, or digest_mismatch; omitted when no credential bytes were obtained. For dual delivery, this is the resolved credential digest; reference_digest independently binds the embedded bytes.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + proof_format: Annotated[ + AnyUrl | None, + Field( + description='Absolute URI identifying the credential/proof format used to interpret and hash obtained credential bytes. REQUIRED whenever credential_digest is present.' + ), + ] = None + outcome: Annotated[ + Outcome, + Field( + description='Stable evaluation outcome. verified means all required issuer, signature, claim type, subject, consuming-action scope, validity, revocation, and digest checks passed under evaluator policy. subject_mismatch covers either a credential/presentation subject mismatch or a presentation/consuming-action subject mismatch. Other values are non-authoritative failures and MUST NOT be treated as verified.' + ), + ] + evaluated_at: AwareDatetime + evaluated_by: Annotated[ + AnyUrl, + Field( + description='Canonical agent URL of the evaluator-of-record that produced this result, even when it delegated proof checking to an accepted verifier.' + ), + ] + verifier_agent_url: Annotated[ + AnyUrl | None, + Field( + description='Accepted verifier agent actually used, when verification was delegated. Omit for local verification or when no verifier was called.' + ), + ] = None + reason_codes: Annotated[ + list[ReasonCode] | None, + Field( + description='Open, machine-readable reason identifiers supplied by the evaluator. Consumers MUST branch on outcome, not on free-form or evaluator-specific reason codes.', + min_length=1, + ), + ] = None + confidence: Annotated[ + StrictFloat | None, + Field( + description='Optional evaluator-produced confidence in the normalized outcome on the AdCP 0–1 scale. This is evaluation output, never copied from an authoritative presenter field. Omit when the proof or evaluator does not support a meaningful confidence score.', + ge=0.0, + le=1.0, + ), + ] = None + revocation_checked_at: Annotated[ + AwareDatetime | None, + Field( + description='When the evaluator last checked authoritative revocation state. Its freshness is governed by proof format and evaluator policy.' + ), + ] = None + valid_until: Annotated[ + AwareDatetime | None, + Field( + description='Upper bound on reuse of this evaluation, considering credential expiry, revocation-cache TTL, and evaluator policy. Consumers MUST re-evaluate after this time.' + ), + ] = None + action_binding: Annotated[ + ActionBinding | None, + Field( + description='Optional binding to the consuming action or readback. Domain consumers that rely on an evaluation MUST carry either an action id or an action digest so the result cannot be transplanted to an unrelated decision.' + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/attestation_issuer.py b/src/adcp/types/domains/core/attestation_issuer.py index ffcd1e81b..f1e8bd57b 100644 --- a/src/adcp/types/domains/core/attestation_issuer.py +++ b/src/adcp/types/domains/core/attestation_issuer.py @@ -1,23 +1,80 @@ -"""Types declared by the AdCP ``core/attestation_issuer`` schema. +# generated by datamodel-codegen: +# filename: core/attestation_issuer.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.attestation_issuer import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import brand_ref +from . import ext as ext_1 + + +class AttestationIssuer2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Annotated[ + Literal['agent'], + Field( + description='The issuer is an AdCP agent identified by its canonical HTTPS endpoint.' + ), + ] = 'agent' + agent_url: Annotated[ + AnyUrl, + Field( + description='Canonical HTTPS endpoint of the issuing agent. Evaluators compare it using AdCP URL canonicalization rules.' + ), + ] + ext: ext_1.ExtensionObject | None = None + + +class AttestationIssuer3(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Annotated[ + Literal['origin'], + Field( + description='The issuer is identified by a canonical HTTPS origin because no AdCP brand or agent identity applies.' + ), + ] = 'origin' + origin: Annotated[ + AnyUrl, + Field( + description='Canonical HTTPS origin with no path, query, fragment, or userinfo. This identifies the issuer; it does not authorize a fetch.' + ), + ] + ext: ext_1.ExtensionObject | None = None + + +class AttestationIssuer1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Annotated[ + Literal['brand'], Field(description='The issuer is identified by an AdCP BrandRef.') + ] = 'brand' + brand: brand_ref.BrandReference + ext: ext_1.ExtensionObject | None = None -from adcp.types.generated_poc.core.attestation_issuer import ( - AttestationIssuer, - AttestationIssuer1, - AttestationIssuer2, - AttestationIssuer3, -) -# Explicit exports -__all__ = ["AttestationIssuer", "AttestationIssuer1", "AttestationIssuer2", "AttestationIssuer3"] +AttestationIssuer = Annotated[ + AttestationIssuer1 | AttestationIssuer2 | AttestationIssuer3, + Field( + description='Canonical identity of the party that issued an attestation credential. The discriminator selects an existing AdCP identity when one exists and falls back to an HTTPS origin for other attestors. This identity is a claim carried by the presentation; evaluators MUST match it against their configured trust policy and verify it from the resolved or embedded credential before relying on it.', + discriminator='type', + examples=[ + { + 'type': 'brand', + 'brand': {'domain': 'nova-brands.example', 'brand_id': 'nova_motors'}, + }, + {'type': 'agent', 'agent_url': 'https://attestor.example/adcp'}, + {'type': 'origin', 'origin': 'https://credentials.example'}, + ], + title='Attestation Issuer', + ), +] diff --git a/src/adcp/types/domains/core/attestation_reference.py b/src/adcp/types/domains/core/attestation_reference.py index 7771f7363..e97dadf65 100644 --- a/src/adcp/types/domains/core/attestation_reference.py +++ b/src/adcp/types/domains/core/attestation_reference.py @@ -1,32 +1,155 @@ -"""Types declared by the AdCP ``core/attestation_reference`` schema. +# generated by datamodel-codegen: +# filename: core/attestation_reference.json +# timestamp: 2026-10-04T01:14:11+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.attestation_reference import +from typing import Annotated, Any, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, model_validator -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import attestation_issuer, attestation_subject +from . import ext as ext_1 + + +class ValidityHint(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + not_before: AwareDatetime | None = None + expires_at: AwareDatetime | None = None + + +class VerifyAgent(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + agent_url: AnyUrl + + +class Locator(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Literal['credential_uri'] = 'credential_uri' + credential_uri: Annotated[ + AnyUrl, + Field( + description='HTTPS URI of the credential. Before any fetch, evaluators MUST match its canonical origin to credential_origins on the accepted issuer capability and then apply the attestation fetch contract.' + ), + ] + ext: ext_1.ExtensionObject | None = None + + +class Locator1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Literal['issuer_credential_id'] = 'issuer_credential_id' + credential_id: Annotated[ + str, + Field( + description="Stable credential identifier in the issuer's namespace. It is meaningful only together with issuer and resolver_id.", + max_length=1024, + min_length=1, + ), + ] + resolver_id: Annotated[ + str, + Field( + description='Identifier of a resolver already published in the matched accepted_issuers[].resolvers[] capability. A presenter cannot supply or override its URL.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9._:-]+$', + ), + ] + ext: ext_1.ExtensionObject | None = None + + +class EmbeddedCredential(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + format: Annotated[ + AnyUrl, + Field( + description='Open, absolute URI identifying the credential/proof format. AdCP does not impose a universal issuer payload schema.' + ), + ] + value: Annotated[ + dict[str, Any] | str, + Field( + description='Credential encoded as a JSON object or a compact string, according to format.', + min_length=1, + ), + ] + ext: ext_1.ExtensionObject | None = None + + +class AttestationReference(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + issuer: attestation_issuer.AttestationIssuer + claim_type: Annotated[ + AnyUrl, + Field( + description='Open, absolute URI identifying the claim vocabulary. The URI is an identifier and need not be dereferenceable. AdCP does not maintain an enum of approved claims.' + ), + ] + subject: attestation_subject.AttestationSubject + locator: Annotated[ + Locator | Locator1 | None, + Field( + description='Stable locator for resolving the credential. Credential URLs are permitted only when their canonical origin is allowlisted for the matched issuer. Issuer-scoped IDs name a resolver_id already published by the evaluator; the presentation cannot introduce a resolver URL.', + discriminator='type', + ), + ] = None + embedded_credential: Annotated[ + EmbeddedCredential | None, + Field( + description='Optional inline credential for private, authenticated, or offline delivery. It may accompany locator or be the only delivery path. Its format must be supported by the evaluator, and the evaluator MUST verify the credential exactly as it would a resolved credential.' + ), + ] = None + content_digest: Annotated[ + str | None, + Field( + description='Optional SHA-256 digest pin for the exact credential bytes, formatted as sha256:. The credential format defines its canonical byte representation. A mismatch is invalid and MUST NOT fall back to the unpinned credential. REQUIRED when both locator and embedded_credential are present; both byte representations MUST match this digest.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + credential_version: Annotated[ + str | None, + Field( + description='Optional issuer-defined credential version hint. It is advisory; the resolved credential is authoritative.', + max_length=255, + min_length=1, + ), + ] = None + validity_hint: Annotated[ + ValidityHint | None, + Field( + description="Optional planning-time validity hint copied from issuer metadata. Evaluators MUST use the resolved or embedded credential's signed validity and revocation state as authoritative." + ), + ] = None + verify_agent: Annotated[ + VerifyAgent | None, + Field( + description='Optional presenter nomination of a verifier already published by the evaluator. This is a representation, not routing authority: agent_url MUST match an accepted_verifiers[] entry after canonicalization, and the evaluator may choose another accepted verifier or verify locally.' + ), + ] = None + ext: ext_1.ExtensionObject | None = None -from adcp.types.generated_poc.core.attestation_reference import ( - AttestationReference, - EmbeddedCredential, - Locator, - Locator1, - ValidityHint, - VerifyAgent, -) - -# Explicit exports -__all__ = [ - "AttestationReference", - "EmbeddedCredential", - "Locator", - "Locator1", - "ValidityHint", - "VerifyAgent", -] + @model_validator(mode='after') + def _require_schema_required_group(self) -> AttestationReference: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('locator',), ('embedded_credential',),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'AttestationReference requires at least one of these field groups: locator | embedded_credential' + ) diff --git a/src/adcp/types/domains/core/attestation_subject.py b/src/adcp/types/domains/core/attestation_subject.py index 0b45c2ec3..ff4620b51 100644 --- a/src/adcp/types/domains/core/attestation_subject.py +++ b/src/adcp/types/domains/core/attestation_subject.py @@ -1,28 +1,90 @@ -"""Types declared by the AdCP ``core/attestation_subject`` schema. +# generated by datamodel-codegen: +# filename: core/attestation_subject.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.attestation_subject import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field + +from . import brand_ref +from . import ext as ext_1 + + +class AttestationSubject2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Literal['agent'] = 'agent' + agent_url: Annotated[ + AnyUrl, Field(description='Canonical HTTPS endpoint of the agent the claim concerns.') + ] + ext: ext_1.ExtensionObject | None = None + + +class AttestationSubject3(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Literal['resource'] = 'resource' + resource_type: Annotated[ + AnyUrl, + Field( + description='Open, absolute URI naming the subject vocabulary, such as https://adcontextprotocol.org/claims/subjects/signal. AdCP does not maintain an exhaustive enum.' + ), + ] + namespace: Annotated[ + AnyUrl, + Field( + description='Absolute URI identifying the namespace in which id is unique. This may be an AdCP agent endpoint, a catalog origin, or a domain-specific namespace URI.' + ), + ] + id: Annotated[ + str, + Field( + description='Stable identifier for the subject within namespace. It MUST NOT be compared without resource_type and namespace.', + max_length=1024, + min_length=1, + ), + ] + content_digest: Annotated[ + str | None, + Field( + description='Optional SHA-256 pin for the exact content or immutable snapshot identified by this resource subject. This is part of the complete typed subject identity and is distinct from AttestationReference.content_digest, which pins credential bytes.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + ext: ext_1.ExtensionObject | None = None + + +class AttestationSubject1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Literal['brand'] = 'brand' + brand: brand_ref.BrandReference + ext: ext_1.ExtensionObject | None = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.attestation_subject import ( - AttestationSubject, - AttestationSubject1, - AttestationSubject2, - AttestationSubject3, -) - -# Explicit exports -__all__ = [ - "AttestationSubject", - "AttestationSubject1", - "AttestationSubject2", - "AttestationSubject3", +AttestationSubject = Annotated[ + AttestationSubject1 | AttestationSubject2 | AttestationSubject3, + Field( + description='Typed identity of the entity or object an attestation credential is about. Brand and agent subjects reuse canonical AdCP identities. Other resources use an open, URI-namespaced resource_type plus an identifier whose namespace is explicit. Evaluators MUST compare the resolved credential subject to this complete typed identity, not to id alone.', + discriminator='type', + examples=[ + { + 'type': 'brand', + 'brand': {'domain': 'nova-brands.example', 'brand_id': 'nova_motors'}, + }, + { + 'type': 'resource', + 'resource_type': 'https://adcontextprotocol.org/claims/subjects/signal', + 'namespace': 'https://signals.meridian.example/adcp', + 'id': 'signal_urban_commuters', + }, + ], + title='Attestation Subject', + ), ] diff --git a/src/adcp/types/domains/core/attribution_window.py b/src/adcp/types/domains/core/attribution_window.py index 775839ea9..a78e4ecbf 100644 --- a/src/adcp/types/domains/core/attribution_window.py +++ b/src/adcp/types/domains/core/attribution_window.py @@ -1,18 +1,37 @@ -"""Types declared by the AdCP ``core/attribution_window`` schema. +# generated by datamodel-codegen: +# filename: core/attribution_window.json +# timestamp: 2026-05-22T13:15:12+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.attribution_window import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import attribution_model +from . import duration -from adcp.types.generated_poc.core.attribution_window import AttributionWindow -# Explicit exports -__all__ = ["AttributionWindow"] +class AttributionWindow(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + post_click: Annotated[ + duration.Duration | None, + Field( + description='Post-click attribution window. Conversions occurring within this duration after a click are attributed to the ad.' + ), + ] = None + post_view: Annotated[ + duration.Duration | None, + Field( + description='Post-view attribution window. Conversions occurring within this duration after an ad impression (without click) are attributed to the ad.' + ), + ] = None + model: Annotated[ + attribution_model.AttributionModel | None, + Field( + description="Attribution model used to assign credit when multiple touchpoints exist. SHOULD be populated when committing to a specific model; when absent, the seller's default applies." + ), + ] = None diff --git a/src/adcp/types/domains/core/audience_activation_method.py b/src/adcp/types/domains/core/audience_activation_method.py index 4a12567e7..8b1aae52c 100644 --- a/src/adcp/types/domains/core/audience_activation_method.py +++ b/src/adcp/types/domains/core/audience_activation_method.py @@ -1,42 +1,197 @@ -"""Types declared by the AdCP ``core/audience_activation_method`` schema. +# generated by datamodel-codegen: +# filename: core/audience_activation_method.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.audience_activation_method import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AnyUrl, ConfigDict, Field + +from ..enums import cloud_storage_protocol +from . import brand_ref + + +class AudienceActivationMethod1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + pattern: Literal['sync_audiences'] = 'sync_audiences' + + +class BuyerAgent(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + agent_url: Annotated[ + AnyUrl, + Field( + description="Buyer agent's registered agent URL. Canonical identifier, mirroring trusted_match.providers[].agent_url." + ), + ] + + +class AudienceActivationMethod2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + pattern: Literal['tmp_identity_match'] = 'tmp_identity_match' + buyer_agent: BuyerAgent + + +class Direction(StrEnum): + buyer_to_seller = 'buyer_to_seller' + seller_to_buyer = 'seller_to_buyer' + + +class Cloud(StrEnum): + aws = 'aws' + azure = 'azure' + gcp = 'gcp' + + +class ConsumerIdentity(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + cloud: Annotated[ + Cloud | None, Field(description="Cloud family hosting this identity's account.") + ] = None + region: Annotated[ + str | None, + Field( + description="Vendor/cloud region identifier. Format follows the cloud family's region naming; not constrained by this schema." + ), + ] = None + identity: Annotated[ + str, + Field( + description="Principal to grant, interpreted relative to the enclosing entry's vendor.domain. Format is vendor-specific and opaque — Snowflake: orgname.accountname; Databricks: sharing recipient identifier; BigQuery: IAM principal.", + min_length=1, + ), + ] + + +class AudienceActivationMethod3(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + pattern: Literal['file_transfer'] = 'file_transfer' + transport: Annotated[ + cloud_storage_protocol.CloudStorageProtocol, + Field(description='Storage protocol for the exchange.'), + ] + directions: Annotated[ + list[Direction] | None, + Field( + description='Supported transfer directions. buyer_to_seller: buyer writes to a seller-hosted bucket. seller_to_buyer: seller reads from a buyer-hosted bucket. Absent means unspecified (resolve during account setup), not neither.', + min_length=1, + ), + ] = None + vendor: Annotated[ + brand_ref.BrandReference, + Field( + description="Platform providing the storage primitive (e.g., the cloud provider's domain)." + ), + ] + + +class AudienceActivationMethod4(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + pattern: Literal['dataset_query'] = 'dataset_query' + vendor: Annotated[ + brand_ref.BrandReference, + Field(description='Data-sharing platform the seller can consume from.'), + ] + consumer_identities: Annotated[ + list[ConsumerIdentity] | None, + Field( + description="Principals the buyer grants access to in the vendor's system. identity is always required. cloud and region are optional, paired deployment metadata: omit both for global principals (for example, an IAM principal or federated identity). Their operational meaning is vendor-specific — they may constrain direct-share reachability or select a fulfillment route, or may be routing and cost hints only. They are not compliance boundaries; data-transfer assessments key on the recipient entity's jurisdiction, not the grantee account's region. Optional: sellers MAY instead communicate identities during account setup.", + min_length=1, + ), + ] = None + + +class AudienceActivationMethod5(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + pattern: Literal['clean_room'] = 'clean_room' + vendor: Annotated[ + brand_ref.BrandReference, Field(description="Clean-room product's operating domain.") + ] + + +class AudienceActivationMethod6(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + pattern: Literal['platform_distribution'] = 'platform_distribution' + vendor: Annotated[ + brand_ref.BrandReference, Field(description="Distribution platform's operating domain.") + ] + destination_ref: Annotated[ + str | None, + Field( + description="Opaque, account-scoped destination or seat reference in the vendor's system, as the buyer needs it to initiate distribution. A seller-declared configuration reference, not a buyer-invoked key or secret. Sellers MUST NOT publish one global reference when the vendor configuration is buyer- or account-specific. Omit until account setup has established the destination.", + max_length=256, + min_length=1, + ), + ] = None + bind_expiry_days: Annotated[ + SchemaInt | None, + Field( + description="Window after which the seller MAY expire an unfulfilled platform_segment bind (per-audience action: failed on sync_audiences). Initial vendor distribution is days-scale; windows shorter than the vendor's documented distribution latency are non-conformant.", + ge=1, + ), + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.audience_activation_method import ( - AudienceActivationMethod, - AudienceActivationMethod1, - AudienceActivationMethod2, - AudienceActivationMethod3, - AudienceActivationMethod4, - AudienceActivationMethod5, - AudienceActivationMethod6, - BuyerAgent, - Cloud, - ConsumerIdentity, - Direction, -) - -# Explicit exports -__all__ = [ - "AudienceActivationMethod", - "AudienceActivationMethod1", - "AudienceActivationMethod2", - "AudienceActivationMethod3", - "AudienceActivationMethod4", - "AudienceActivationMethod5", - "AudienceActivationMethod6", - "BuyerAgent", - "Cloud", - "ConsumerIdentity", - "Direction", +AudienceActivationMethod = Annotated[ + AudienceActivationMethod1 + | AudienceActivationMethod2 + | AudienceActivationMethod3 + | AudienceActivationMethod4 + | AudienceActivationMethod5 + | AudienceActivationMethod6, + Field( + description='One way buyer audience data can reach a seller for targeting. The pattern field discriminates the entry; each pattern carries only its own fields. Two patterns are AdCP-canonical (sync_audiences, tmp_identity_match); the rest describe integration paths that move data outside the protocol while remaining discoverable through it. Vendor identity is a BrandRef domain so new platforms declare a domain rather than waiting on an enum change. Experimental (x-status: experimental): the audience-activation surface — this schema, product.audience_activation, audience_targeting.supported_activation_methods, and the audience_activation_methods product filter — is new and not yet field-tested across parties. Sellers that implement it MUST list media_buy.audience_activation in experimental_features. Per docs/reference/experimental-status, it MAY change between 3.x releases with notice.', + examples=[ + {'pattern': 'sync_audiences'}, + { + 'pattern': 'tmp_identity_match', + 'buyer_agent': {'agent_url': 'https://buyer.example-agent.com'}, + }, + { + 'pattern': 'file_transfer', + 'transport': 's3', + 'directions': ['buyer_to_seller'], + 'vendor': {'domain': 'object-store.example'}, + }, + { + 'pattern': 'dataset_query', + 'vendor': {'domain': 'data-cloud.example'}, + 'consumer_identities': [ + { + 'cloud': 'aws', + 'region': 'us-east-1', + 'identity': 'SELLERORG.ADCP_INGEST', + }, + {'identity': 'serviceAccount:adcp-ingest@publisher.example'}, + ], + }, + {'pattern': 'clean_room', 'vendor': {'domain': 'collaboration-room.example'}}, + { + 'pattern': 'platform_distribution', + 'vendor': {'domain': 'activation-hub.example'}, + 'destination_ref': 'dest_12345', + 'bind_expiry_days': 14, + }, + ], + title='Audience Activation Method', + ), ] diff --git a/src/adcp/types/domains/core/audience_characteristic.py b/src/adcp/types/domains/core/audience_characteristic.py index 0d64727d3..43bb30e53 100644 --- a/src/adcp/types/domains/core/audience_characteristic.py +++ b/src/adcp/types/domains/core/audience_characteristic.py @@ -1,24 +1,93 @@ -"""Types declared by the AdCP ``core/audience_characteristic`` schema. +# generated by datamodel-codegen: +# filename: core/audience_characteristic.json +# timestamp: 2026-10-04T01:14:11+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.audience_characteristic import +from typing import Any, Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field, RootModel, StrictBool, StrictFloat, model_validator + + +class Dimension(RootModel[AnyUrl]): + root: Annotated[ + AnyUrl, + Field( + description='Canonical dimension name or HTTPS URI. AdCP 3.2 defines `age`; other values remain provider- or taxonomy-scoped.' + ), + ] + + +class Value(RootModel[list[str | StrictFloat | StrictBool]]): + root: Annotated[ + list[str | StrictFloat | StrictBool], + Field(description='Scalar or set value for the dimension.', min_length=1), + ] + def __getattr__(self, name: str) -> Any: + """Proxy attribute access to the wrapped type.""" + if name.startswith('_'): + raise AttributeError(name) + return getattr(self.root, name) + + + +class Range(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + min: StrictFloat | None = None + max: StrictFloat | None = None + + +class Taxonomy(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + system: AnyUrl + version: Annotated[str | None, Field(min_length=1)] = None + value_id: Annotated[str | None, Field(min_length=1)] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.audience_characteristic import ( - AudienceCharacteristic, - Dimension, - Range, - Taxonomy, - Value, -) +class AudienceCharacteristic(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + dimension: Annotated[ + Literal['age'] | Dimension, + Field( + description='Canonical dimension name or HTTPS URI. AdCP 3.2 defines `age`; other values remain provider- or taxonomy-scoped.' + ), + ] + value: Annotated[ + str | StrictFloat | StrictBool | Value | None, + Field(description='Scalar or set value for the dimension.'), + ] = None + range: Annotated[ + Range | None, + Field( + description='Inclusive numeric interval. For dimension `age`, values are completed years and MUST be non-negative integers, and min MUST be less than or equal to max. JSON Schema enforces the age value and bound types; implementations enforce the relational min <= max constraint.' + ), + ] = None + taxonomy: Annotated[ + Taxonomy | None, + Field( + description='External system that defines the dimension or value. Taxonomy metadata is descriptive and does not create exact demographic targeting semantics.' + ), + ] = None + label: Annotated[ + str | None, Field(description='Human-readable display label; never the comparison key.') + ] = None -# Explicit exports -__all__ = ["AudienceCharacteristic", "Dimension", "Range", "Taxonomy", "Value"] + @model_validator(mode='after') + def _require_schema_required_group(self) -> AudienceCharacteristic: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('value',), ('range',),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'AudienceCharacteristic requires at least one of these field groups: value | range' + ) diff --git a/src/adcp/types/domains/core/audience_evidence.py b/src/adcp/types/domains/core/audience_evidence.py index 2433894f2..826e8a853 100644 --- a/src/adcp/types/domains/core/audience_evidence.py +++ b/src/adcp/types/domains/core/audience_evidence.py @@ -1,80 +1,476 @@ -"""Types declared by the AdCP ``core/audience_evidence`` schema. +# generated by datamodel-codegen: +# filename: core/audience_evidence.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.audience_evidence import +from adcp.types._str_enum import StrEnum +from typing import Any, Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, RootModel, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import audience_evidence_methodology, audience_resolution_method, audience_subject_type +from . import audience_characteristic, brand_ref, date_range +from . import ext as ext_1 +from .attestation_reference import AttestationReference + + +class Relationship(StrEnum): + composition = 'composition' + index = 'index' # type: ignore[assignment] + reach_estimate = 'reach_estimate' + + +class Unit(StrEnum): + fraction = 'fraction' + ratio = 'ratio' + count = 'count' # type: ignore[assignment] + + +class Baseline(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + system: AnyUrl + population_id: Annotated[str, Field(min_length=1)] + version: Annotated[str, Field(min_length=1)] + description: str | None = None + + +class EvidenceType(StrEnum): + measured = 'measured' + forecast = 'forecast' + seller_declared = 'seller_declared' + + +class Subject12(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Literal['agent'] = 'agent' + agent_url: Annotated[ + AnyUrl, Field(description='Canonical HTTPS endpoint of the agent the claim concerns.') + ] + ext: ext_1.ExtensionObject | None = None + + +class Subject13(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Literal['resource'] = 'resource' + resource_type: Annotated[ + AnyUrl, + Field( + description='Open, absolute URI naming the subject vocabulary, such as https://adcontextprotocol.org/claims/subjects/signal. AdCP does not maintain an exhaustive enum.' + ), + ] + namespace: Annotated[ + AnyUrl, + Field( + description='Absolute URI identifying the namespace in which id is unique. This may be an AdCP agent endpoint, a catalog origin, or a domain-specific namespace URI.' + ), + ] + id: Annotated[ + str, + Field( + description='Stable identifier for the subject within namespace. It MUST NOT be compared without resource_type and namespace.', + max_length=1024, + min_length=1, + ), + ] + content_digest: Annotated[ + str | None, + Field( + description='Optional SHA-256 pin for the exact content or immutable snapshot identified by this resource subject. This is part of the complete typed subject identity and is distinct from AttestationReference.content_digest, which pins credential bytes.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + ext: ext_1.ExtensionObject | None = None + + +class Subject22(Subject12): + pass + + +class Subject23(Subject13): + pass + + +class Subject24(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Literal['resource'] = 'resource' + resource_type: Literal['https://adcontextprotocol.org/claims/subjects/audience-evidence'] = 'https://adcontextprotocol.org/claims/subjects/audience-evidence' + content_digest: Annotated[str, Field(pattern='^sha256:[a-f0-9]{64}$')] + agent_url: Annotated[ + AnyUrl, Field(description='Canonical HTTPS endpoint of the agent the claim concerns.') + ] + ext: ext_1.ExtensionObject | None = None + + +class Subject26(Subject22): + model_config = ConfigDict( + extra='forbid', + ) + + +class Subject27(Subject24): + model_config = ConfigDict( + extra='forbid', + ) + + +class Subject32(Subject12): + pass + + +class Subject33(Subject13): + pass + + +class Subject34(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Literal['resource'] = 'resource' + resource_type: Literal['https://adcontextprotocol.org/claims/subjects/audience-evidence'] = 'https://adcontextprotocol.org/claims/subjects/audience-evidence' + content_digest: Annotated[str, Field(pattern='^sha256:[a-f0-9]{64}$')] + namespace: Annotated[ + AnyUrl, + Field( + description='Absolute URI identifying the namespace in which id is unique. This may be an AdCP agent endpoint, a catalog origin, or a domain-specific namespace URI.' + ), + ] + id: Annotated[ + str, + Field( + description='Stable identifier for the subject within namespace. It MUST NOT be compared without resource_type and namespace.', + max_length=1024, + min_length=1, + ), + ] + ext: ext_1.ExtensionObject | None = None + + +class Subject36(Subject32): + model_config = ConfigDict( + extra='forbid', + ) + resource_type: Annotated[ + AnyUrl, + Field( + description='Open, absolute URI naming the subject vocabulary, such as https://adcontextprotocol.org/claims/subjects/signal. AdCP does not maintain an exhaustive enum.' + ), + ] + namespace: Annotated[ + AnyUrl, + Field( + description='Absolute URI identifying the namespace in which id is unique. This may be an AdCP agent endpoint, a catalog origin, or a domain-specific namespace URI.' + ), + ] + id: Annotated[ + str, + Field( + description='Stable identifier for the subject within namespace. It MUST NOT be compared without resource_type and namespace.', + max_length=1024, + min_length=1, + ), + ] + content_digest: Annotated[ + str | None, + Field( + description='Optional SHA-256 pin for the exact content or immutable snapshot identified by this resource subject. This is part of the complete typed subject identity and is distinct from AttestationReference.content_digest, which pins credential bytes.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + + +class Subject37(Subject34): + model_config = ConfigDict( + extra='forbid', + ) + + +class Subject11(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Literal['brand'] = 'brand' + brand: brand_ref.BrandReference + ext: ext_1.ExtensionObject | None = None + + +class Subject14(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Literal['resource'] = 'resource' + resource_type: Literal['https://adcontextprotocol.org/claims/subjects/audience-evidence'] = 'https://adcontextprotocol.org/claims/subjects/audience-evidence' + content_digest: Annotated[str, Field(pattern='^sha256:[a-f0-9]{64}$')] + brand: brand_ref.BrandReference + ext: ext_1.ExtensionObject | None = None + + +class Subject15(Subject11): + model_config = ConfigDict( + extra='forbid', + ) + + +class Subject16(Subject12): + model_config = ConfigDict( + extra='forbid', + ) + brand: brand_ref.BrandReference + + +class Subject17(Subject14): + model_config = ConfigDict( + extra='forbid', + ) + + +class Subject(RootModel[Subject15 | Subject16 | Subject17]): + root: Annotated[ + Subject15 | Subject16 | Subject17, + Field( + description='Typed identity of the entity or object an attestation credential is about. Brand and agent subjects reuse canonical AdCP identities. Other resources use an open, URI-namespaced resource_type plus an identifier whose namespace is explicit. Evaluators MUST compare the resolved credential subject to this complete typed identity, not to id alone.', + discriminator='type', + examples=[ + { + 'type': 'brand', + 'brand': {'domain': 'nova-brands.example', 'brand_id': 'nova_motors'}, + }, + { + 'type': 'resource', + 'resource_type': 'https://adcontextprotocol.org/claims/subjects/signal', + 'namespace': 'https://signals.meridian.example/adcp', + 'id': 'signal_urban_commuters', + }, + ], + title='AttestationBrandSubject', + ), + ] + def __getattr__(self, name: str) -> Any: + """Proxy attribute access to the wrapped type.""" + if name.startswith('_'): + raise AttributeError(name) + return getattr(self.root, name) + + + +class Subject21(Subject11): + pass + + +class Subject25(Subject21): + model_config = ConfigDict( + extra='forbid', + ) + agent_url: Annotated[ + AnyUrl, Field(description='Canonical HTTPS endpoint of the agent the claim concerns.') + ] + + +class Subject2(RootModel[Subject25 | Subject26 | Subject27]): + root: Annotated[ + Subject25 | Subject26 | Subject27, + Field( + description='Typed identity of the entity or object an attestation credential is about. Brand and agent subjects reuse canonical AdCP identities. Other resources use an open, URI-namespaced resource_type plus an identifier whose namespace is explicit. Evaluators MUST compare the resolved credential subject to this complete typed identity, not to id alone.', + discriminator='type', + examples=[ + { + 'type': 'brand', + 'brand': {'domain': 'nova-brands.example', 'brand_id': 'nova_motors'}, + }, + { + 'type': 'resource', + 'resource_type': 'https://adcontextprotocol.org/claims/subjects/signal', + 'namespace': 'https://signals.meridian.example/adcp', + 'id': 'signal_urban_commuters', + }, + ], + title='AttestationAgentSubject', + ), + ] + def __getattr__(self, name: str) -> Any: + """Proxy attribute access to the wrapped type.""" + if name.startswith('_'): + raise AttributeError(name) + return getattr(self.root, name) + + + +class Subject31(Subject11): + pass + + +class Subject35(Subject31): + model_config = ConfigDict( + extra='forbid', + ) + resource_type: Annotated[ + AnyUrl, + Field( + description='Open, absolute URI naming the subject vocabulary, such as https://adcontextprotocol.org/claims/subjects/signal. AdCP does not maintain an exhaustive enum.' + ), + ] + namespace: Annotated[ + AnyUrl, + Field( + description='Absolute URI identifying the namespace in which id is unique. This may be an AdCP agent endpoint, a catalog origin, or a domain-specific namespace URI.' + ), + ] + id: Annotated[ + str, + Field( + description='Stable identifier for the subject within namespace. It MUST NOT be compared without resource_type and namespace.', + max_length=1024, + min_length=1, + ), + ] + content_digest: Annotated[ + str | None, + Field( + description='Optional SHA-256 pin for the exact content or immutable snapshot identified by this resource subject. This is part of the complete typed subject identity and is distinct from AttestationReference.content_digest, which pins credential bytes.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + + +class Subject3(RootModel[Subject35 | Subject36 | Subject37]): + root: Annotated[ + Subject35 | Subject36 | Subject37, + Field( + description='Typed identity of the entity or object an attestation credential is about. Brand and agent subjects reuse canonical AdCP identities. Other resources use an open, URI-namespaced resource_type plus an identifier whose namespace is explicit. Evaluators MUST compare the resolved credential subject to this complete typed identity, not to id alone.', + discriminator='type', + examples=[ + { + 'type': 'brand', + 'brand': {'domain': 'nova-brands.example', 'brand_id': 'nova_motors'}, + }, + { + 'type': 'resource', + 'resource_type': 'https://adcontextprotocol.org/claims/subjects/signal', + 'namespace': 'https://signals.meridian.example/adcp', + 'id': 'signal_urban_commuters', + }, + ], + title='AttestationResourceSubject', + ), + ] + def __getattr__(self, name: str) -> Any: + """Proxy attribute access to the wrapped type.""" + if name.startswith('_'): + raise AttributeError(name) + return getattr(self.root, name) + + + +class AttestationRef(AttestationReference): + subject: Subject34 -from adcp.types.generated_poc.core.audience_evidence import ( - AttestationRef, - AudienceEvidence, - Baseline, - EvidenceType, - Relationship, - Subject, - Subject11, - Subject12, - Subject13, - Subject14, - Subject15, - Subject16, - Subject17, - Subject2, - Subject21, - Subject22, - Subject23, - Subject24, - Subject25, - Subject26, - Subject27, - Subject3, - Subject31, - Subject32, - Subject33, - Subject34, - Subject35, - Subject36, - Subject37, - Unit, -) - -# Explicit exports -__all__ = [ - "AttestationRef", - "AudienceEvidence", - "Baseline", - "EvidenceType", - "Relationship", - "Subject", - "Subject11", - "Subject12", - "Subject13", - "Subject14", - "Subject15", - "Subject16", - "Subject17", - "Subject2", - "Subject21", - "Subject22", - "Subject23", - "Subject24", - "Subject25", - "Subject26", - "Subject27", - "Subject3", - "Subject31", - "Subject32", - "Subject33", - "Subject34", - "Subject35", - "Subject36", - "Subject37", - "Unit", -] +class AudienceEvidence(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + evidence_id: Annotated[ + str, + Field( + description='Stable provider-scoped identifier for the logical evidence series across versions.', + min_length=1, + ), + ] + snapshot_id: Annotated[ + str, + Field( + description='Seller-scoped immutable snapshot identifier. It MUST never be reused for different content.', + min_length=1, + ), + ] + version: Annotated[ + str, Field(description='Provider version of this evidence snapshot.', min_length=1) + ] + content_digest: Annotated[ + str, + Field( + description='SHA-256 of the RFC 8785 (JCS) canonical evidence core with content_digest and attestation_refs omitted. Attestation references are excluded so independently issued credentials can be attached without changing the immutable evidence snapshot and without creating a recursive digest. This digest is required even when snapshot_id is present so buyers can detect identifier reuse or catalog drift.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] + audience: audience_characteristic.AudienceCharacteristic + relationship: Annotated[ + Relationship, + Field( + description='What the estimate says: population share, ratio to a baseline, or estimated reachable count.' + ), + ] + value: Annotated[ + StrictFloat, + Field(description='Estimated relationship value, interpreted according to unit.', ge=0.0), + ] + unit: Annotated[ + Unit, + Field( + description='composition uses fraction in [0,1], index uses ratio, and reach_estimate uses count.' + ), + ] + baseline: Annotated[ + Baseline, + Field(description='Reference population against which the estimate was calculated.'), + ] + evidence_type: Annotated[ + EvidenceType, + Field( + description='Nature of the evidence. Independent verification is represented separately by attestation_refs and never inferred from this value.' + ), + ] + methodology: audience_evidence_methodology.AudienceEvidenceMethodology + subject_type: audience_subject_type.AudienceSubjectType + resolution_method: Annotated[ + audience_resolution_method.AudienceResolutionMethod | None, + Field( + description='Optional subject-resolution method used by the study or estimate. This describes population methodology, not a promise that the product can execute that resolution at serving time.' + ), + ] = None + provider: Annotated[ + brand_ref.BrandReference, Field(description='Brand responsible for producing the evidence.') + ] + measurement_window: date_range.DateRange + sample_size: Annotated[ + SchemaInt | None, + Field( + description='Number of observations or respondents underlying the estimate, when applicable.', + ge=1, + ), + ] = None + confidence: Annotated[ + StrictFloat | None, + Field( + description="Provider-defined confidence score in [0,1]. Consumers MUST compare scores only when the provider's methodology makes them comparable.", + ge=0.0, + le=1.0, + ), + ] = None + last_updated: Annotated[ + AwareDatetime, + Field(description='When the provider last produced or refreshed this immutable snapshot.'), + ] + methodology_url: Annotated[ + AnyUrl | None, + Field( + description='Documentation for interpreting the methodology. It is disclosure, not executable routing.' + ), + ] = None + attestation_refs: Annotated[ + list[AttestationRef] | None, + Field( + description="Optional portable attestations about this exact evidence snapshot. Each subject MUST be resource type https://adcontextprotocol.org/claims/subjects/audience-evidence, subject.id MUST equal snapshot_id, and subject.content_digest MUST equal this evidence object's content_digest. Buyer references do not override evaluator issuer or resolver policy.", + max_length=10, + min_length=1, + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/audience_evidence_pin.py b/src/adcp/types/domains/core/audience_evidence_pin.py index 4444a0951..cc35d766b 100644 --- a/src/adcp/types/domains/core/audience_evidence_pin.py +++ b/src/adcp/types/domains/core/audience_evidence_pin.py @@ -1,18 +1,23 @@ -"""Types declared by the AdCP ``core/audience_evidence_pin`` schema. +# generated by datamodel-codegen: +# filename: core/audience_evidence_pin.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.audience_evidence_pin import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import ext as ext_1 -from adcp.types.generated_poc.core.audience_evidence_pin import AudienceEvidencePin -# Explicit exports -__all__ = ["AudienceEvidencePin"] +class AudienceEvidencePin(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + evidence_id: Annotated[str, Field(min_length=1)] + snapshot_id: Annotated[str, Field(min_length=1)] + version: Annotated[str, Field(min_length=1)] + content_digest: Annotated[str, Field(pattern='^sha256:[a-f0-9]{64}$')] + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/audience_evidence_requirements.py b/src/adcp/types/domains/core/audience_evidence_requirements.py index 1d7a4bb0e..fb55a146c 100644 --- a/src/adcp/types/domains/core/audience_evidence_requirements.py +++ b/src/adcp/types/domains/core/audience_evidence_requirements.py @@ -1,32 +1,135 @@ -"""Types declared by the AdCP ``core/audience_evidence_requirements`` schema. +# generated by datamodel-codegen: +# filename: core/audience_evidence_requirements.json +# timestamp: 2026-10-03T15:09:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.audience_evidence_requirements import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field, StrictBool, StrictFloat + +from ..enums import audience_evidence_methodology, audience_resolution_method, audience_subject_type +from . import attestation_issuer, brand_ref +from . import ext as ext_1 +from .duration import Duration + + +class RequirementMode(StrEnum): + required = 'required' + preferred = 'preferred' + + +class EvidencePresence(StrEnum): + required = 'required' + when_available = 'when_available' + + +class AcceptedEvidenceType(StrEnum): + measured = 'measured' + forecast = 'forecast' + seller_declared = 'seller_declared' + + +class Unit(StrEnum): + seconds = 'seconds' + minutes = 'minutes' + hours = 'hours' + days = 'days' + + +class MaximumAge(Duration): + unit: Annotated[ + Unit | None, + Field( + description="Time unit. 'seconds' for sub-minute precision. 'campaign' spans the full campaign flight." + ), + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.audience_evidence_requirements import ( - AcceptedEvidenceType, - AudienceEvidenceRequirements, - EvidencePresence, - MaximumAge, - RequirementMode, - Unit, -) - -# Explicit exports -__all__ = [ - "AcceptedEvidenceType", - "AudienceEvidenceRequirements", - "EvidencePresence", - "MaximumAge", - "RequirementMode", - "Unit", -] +class AudienceEvidenceRequirements(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + requirement_mode: Annotated[ + RequirementMode, + Field( + description='required excludes products whose published evidence violates the constraints; preferred only affects ranking and explanation. When this policy affects inclusion or ranking, the seller MUST return the exact matching audience_evidence_selections even if get_products.fields omitted that field.' + ), + ] + evidence_presence: Annotated[ + EvidencePresence, + Field( + description='required applies the mode to evidence presence itself. when_available applies constraints only when a product publishes evidence: with requirement_mode required, a product with no evidence remains eligible but a product with evidence must have an admissible item; with preferred, presence and admissibility only affect ranking.' + ), + ] + accepted_methodologies: Annotated[ + list[audience_evidence_methodology.AudienceEvidenceMethodology] | None, + Field( + description='Allowed methodologies. excluded_methodologies always wins if a value appears in both lists.', + min_length=1, + ), + ] = None + excluded_methodologies: Annotated[ + list[audience_evidence_methodology.AudienceEvidenceMethodology] | None, + Field( + description='Disallowed methodologies. Exclusion wins over accepted_methodologies when the lists overlap.', + min_length=1, + ), + ] = None + accepted_evidence_types: Annotated[list[AcceptedEvidenceType] | None, Field(min_length=1)] = ( + None + ) + accepted_providers: Annotated[ + list[brand_ref.BrandReference] | None, + Field( + description='Allowed evidence providers. excluded_providers always wins if the same canonical BrandRef appears in both lists.', + min_length=1, + ), + ] = None + excluded_providers: Annotated[ + list[brand_ref.BrandReference] | None, + Field( + description='Disallowed evidence providers. Exclusion wins over accepted_providers when the lists overlap.', + min_length=1, + ), + ] = None + accepted_subject_types: Annotated[ + list[audience_subject_type.AudienceSubjectType] | None, Field(min_length=1) + ] = None + accepted_resolution_methods: Annotated[ + list[audience_resolution_method.AudienceResolutionMethod] | None, Field(min_length=1) + ] = None + minimum_confidence: Annotated[StrictFloat | None, Field(ge=0.0, le=1.0)] = None + maximum_age: Annotated[ + MaximumAge | None, + Field( + description='Maximum age at evaluation time measured from last_updated. Campaign-relative duration is not valid for evidence age.' + ), + ] = None + methodology_documentation_required: Annotated[ + StrictBool | None, + Field(description='Whether an admissible item must carry methodology_url.'), + ] = False + independent_attestation_required: Annotated[ + StrictBool | None, + Field( + description="Whether an admissible item must have at least one exact reference/evaluation pair whose outcome is verified, whose reference is published in the evidence, and whose claim type and issuer match the buyer's accepted lists as well as the seller's issuer and resolver policy. Setting true requires accepted_attestation_issuers." + ), + ] = False + accepted_attestation_issuers: Annotated[ + list[attestation_issuer.AttestationIssuer] | None, + Field( + description="Buyer allowlist for independent attestation issuers. An exact reference/evaluation pair satisfies the requirement only when reference.issuer matches an entry here. This list narrows and never broadens the seller's accepted issuer policy.", + min_length=1, + ), + ] = None + accepted_attestation_claim_types: Annotated[ + list[AnyUrl] | None, + Field( + description='Optional acceptable claim types when attestation is used. Buyer-supplied claim types further constrain but never broaden seller policy.', + min_length=1, + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/audience_evidence_selection.py b/src/adcp/types/domains/core/audience_evidence_selection.py index 78dfb9d79..06ba87388 100644 --- a/src/adcp/types/domains/core/audience_evidence_selection.py +++ b/src/adcp/types/domains/core/audience_evidence_selection.py @@ -1,30 +1,96 @@ -"""Types declared by the AdCP ``core/audience_evidence_selection`` schema. +# generated by datamodel-codegen: +# filename: core/audience_evidence_selection.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.audience_evidence_selection import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field + +from . import attestation_reference, audience_evidence +from . import ext as ext_1 +from .attestation_evaluation import AttestationEvaluation as AttestationEvaluation_1 + + +class DecisionUse(StrEnum): + recommendation = 'recommendation' + eligibility = 'eligibility' + package_construction = 'package_construction' + + +class ActionBinding(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + action_type: Annotated[ + Literal['https://adcontextprotocol.org/actions/audience-evidence-evaluation'], + Field( + description='Absolute URI naming the action vocabulary, such as an AdCP governance-check or audience-evidence snapshot type.' + ), + ] = 'https://adcontextprotocol.org/actions/audience-evidence-evaluation' + action_id: Annotated[ + str, + Field( + description="Stable identifier of the consuming action within the domain consumer's namespace.", + max_length=1024, + min_length=1, + ), + ] + action_digest: Annotated[ + str, + Field( + description='SHA-256 digest of the domain-defined canonical action preimage.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] + + +class Evaluation(AttestationEvaluation_1): + action_binding: Annotated[ + ActionBinding, + Field( + description='Optional binding to the consuming action or readback. Domain consumers that rely on an evaluation MUST carry either an action id or an action digest so the result cannot be transplanted to an unrelated decision.' + ), + ] + + +class AttestationEvaluation(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + reference: attestation_reference.AttestationReference + evaluation: Evaluation -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.audience_evidence_selection import ( - ActionBinding, - AttestationEvaluation, - AudienceEvidenceSelection, - DecisionUse, - Evaluation, -) - -# Explicit exports -__all__ = [ - "ActionBinding", - "AttestationEvaluation", - "AudienceEvidenceSelection", - "DecisionUse", - "Evaluation", -] +class AudienceEvidenceSelection(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + evidence_id: Annotated[str, Field(min_length=1)] + snapshot_id: Annotated[str, Field(min_length=1)] + version: Annotated[str, Field(min_length=1)] + content_digest: Annotated[str, Field(pattern='^sha256:[a-f0-9]{64}$')] + decision_use: Annotated[ + DecisionUse, + Field( + description="How this evidence affected the seller's decision. This is not targeting execution." + ), + ] + evidence: Annotated[ + audience_evidence.AudienceEvidence | None, + Field( + description='Optional inline immutable snapshot. Its identity, version, and digest MUST equal the surrounding fields.' + ), + ] = None + attestation_evaluations: Annotated[ + list[AttestationEvaluation] | None, + Field( + description="Exact reference/evaluation pairs used in the decision. The reference MUST be one of the selected evidence snapshot's attestation_refs, evaluation.reference_digest MUST equal the digest of that same reference, reference.subject.content_digest and evaluation.action_binding.action_digest MUST equal this selection's content_digest, evaluation.action_binding.action_id MUST equal snapshot_id, and action_type MUST be https://adcontextprotocol.org/actions/audience-evidence-evaluation.", + max_length=10, + min_length=1, + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/audience_member.py b/src/adcp/types/domains/core/audience_member.py index 06938a4ca..9265d1a96 100644 --- a/src/adcp/types/domains/core/audience_member.py +++ b/src/adcp/types/domains/core/audience_member.py @@ -1,18 +1,68 @@ -"""Types declared by the AdCP ``core/audience_member`` schema. +# generated by datamodel-codegen: +# filename: core/audience_member.json +# timestamp: 2026-10-04T01:14:11+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.audience_member import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, model_validator + +from ..enums import uid_type +from . import ext as ext_1 + + +class Uid(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + type: Annotated[uid_type.UidType, Field(description='Universal ID type')] + value: Annotated[str, Field(description='Universal ID value')] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.audience_member import AudienceMember, Uid +class AudienceMember(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + external_id: Annotated[ + str, + Field( + description="Buyer-assigned stable identifier for this audience member (e.g. CRM record ID, loyalty ID). Used for deduplication, removal, and cross-referencing with buyer systems. Adapters for CDPs that don't natively assign IDs can derive one (e.g. hash of the member's identifiers)." + ), + ] + hashed_email: Annotated[ + str | None, + Field( + description='SHA-256 hash of lowercase, trimmed email address. Pseudonymous PII, not anonymous — the email namespace is small enough that an unsalted SHA-256 is recoverable via precomputed dictionaries. Treat as PII for retention, consent, and access-control purposes. See docs/reference/privacy-considerations#unsalted-hashed-identifiers-are-pseudonymous-not-anonymous.', + pattern='^[a-f0-9]{64}$', + ), + ] = None + hashed_phone: Annotated[ + str | None, + Field( + description='SHA-256 hash of E.164-formatted phone number (e.g. +12065551234). Pseudonymous PII, not anonymous — the E.164 namespace is small enough that an unsalted SHA-256 is recoverable via precomputed dictionaries. Treat as PII for retention, consent, and access-control purposes. See docs/reference/privacy-considerations#unsalted-hashed-identifiers-are-pseudonymous-not-anonymous.', + pattern='^[a-f0-9]{64}$', + ), + ] = None + uids: Annotated[ + list[Uid] | None, + Field( + description='Universal ID values (MAIDs, RampID, UID2, etc.) for user matching.', + min_length=1, + ), + ] = None + ext: ext_1.ExtensionObject | None = None -# Explicit exports -__all__ = ["AudienceMember", "Uid"] + @model_validator(mode='after') + def _require_schema_required_group(self) -> AudienceMember: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('hashed_email',), ('hashed_phone',), ('uids',),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'AudienceMember requires at least one of these field groups: hashed_email | hashed_phone | uids' + ) diff --git a/src/adcp/types/domains/core/audience_selector.py b/src/adcp/types/domains/core/audience_selector.py index 7fae9936d..0fb308a27 100644 --- a/src/adcp/types/domains/core/audience_selector.py +++ b/src/adcp/types/domains/core/audience_selector.py @@ -1,30 +1,137 @@ -"""Types declared by the AdCP ``core/audience_selector`` schema. +# generated by datamodel-codegen: +# filename: core/audience_selector.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.audience_selector import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictBool, StrictFloat + +from . import signal_id as signal_id_1 +from . import signal_ref as signal_ref_1 + + +class AudienceSelector4(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + type: Annotated[ + Literal['description'], Field(description='Discriminator for description-based selectors') + ] = 'description' + description: Annotated[ + str, + Field( + description="Natural language description of the audience (e.g., 'likely EV buyers', 'high net worth individuals', 'vulnerable communities')", + max_length=2000, + min_length=1, + ), + ] + category: Annotated[ + str | None, + Field( + description="Optional grouping hint for the governance agent (e.g., 'demographic', 'behavioral', 'contextual', 'financial')" + ), + ] = None + + +class AudienceSelector1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + type: Annotated[ + Literal['signal'], Field(description='Discriminator for signal-based selectors') + ] = 'signal' + signal_ref: Annotated[ + signal_ref_1.SignalRef | None, + Field(description='The signal to target. New selectors SHOULD use signal_ref.'), + ] = None + signal_id: Annotated[ + signal_id_1.SignalId | None, + Field( + deprecated=True, + description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', + ), + ] = None + value_type: Annotated[Literal['binary'], Field(description='Discriminator for binary signals')] = 'binary' + value: Annotated[ + StrictBool, + Field( + description='Whether to include (true) or exclude (false) users matching this signal' + ), + ] + + +class AudienceSelector2(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + type: Annotated[ + Literal['signal'], Field(description='Discriminator for signal-based selectors') + ] = 'signal' + signal_ref: Annotated[ + signal_ref_1.SignalRef | None, + Field(description='The signal to target. New selectors SHOULD use signal_ref.'), + ] = None + signal_id: Annotated[ + signal_id_1.SignalId | None, + Field( + deprecated=True, + description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', + ), + ] = None + value_type: Annotated[ + Literal['categorical'], Field(description='Discriminator for categorical signals') + ] = 'categorical' + values: Annotated[ + list[str], + Field( + description='Values to target. Users with any of these values will be included.', + min_length=1, + ), + ] + + +class AudienceSelector3(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + type: Annotated[ + Literal['signal'], Field(description='Discriminator for signal-based selectors') + ] = 'signal' + signal_ref: Annotated[ + signal_ref_1.SignalRef | None, + Field(description='The signal to target. New selectors SHOULD use signal_ref.'), + ] = None + signal_id: Annotated[ + signal_id_1.SignalId | None, + Field( + deprecated=True, + description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', + ), + ] = None + value_type: Annotated[ + Literal['numeric'], Field(description='Discriminator for numeric signals') + ] = 'numeric' + min_value: Annotated[ + StrictFloat | None, + Field( + description='Minimum value (inclusive). Omit for no minimum. Must be <= max_value when both are provided.' + ), + ] = None + max_value: Annotated[ + StrictFloat | None, + Field( + description='Maximum value (inclusive). Omit for no maximum. Must be >= min_value when both are provided.' + ), + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.audience_selector import ( - AudienceSelector, - AudienceSelector1, - AudienceSelector2, - AudienceSelector3, - AudienceSelector4, -) - -# Explicit exports -__all__ = [ - "AudienceSelector", - "AudienceSelector1", - "AudienceSelector2", - "AudienceSelector3", - "AudienceSelector4", +AudienceSelector = Annotated[ + AudienceSelector1 | AudienceSelector2 | AudienceSelector3 | AudienceSelector4, + Field( + description="Selects an audience by signal reference or natural language description. Uses 'type' as the primary discriminator (signal vs description). Signal selectors additionally use 'value_type' to determine the targeting expression format (matching signal-targeting.json variants).", + title='Audience Selector', + ), ] diff --git a/src/adcp/types/domains/core/audience_source.py b/src/adcp/types/domains/core/audience_source.py index e98bd8e3f..d4ac99053 100644 --- a/src/adcp/types/domains/core/audience_source.py +++ b/src/adcp/types/domains/core/audience_source.py @@ -1,22 +1,77 @@ -"""Types declared by the AdCP ``core/audience_source`` schema. +# generated by datamodel-codegen: +# filename: core/audience_source.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.audience_source import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import brand_ref + + +class AudienceSource1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['dataset'] = 'dataset' + vendor: Annotated[ + brand_ref.BrandReference, + Field(description='Data-sharing platform hosting the shared object.'), + ] + locator: Annotated[ + str, + Field( + description="Vendor-native reference to the shared object (share/database/table path). Opaque to AdCP; meaningful to the vendor. Never a credential. For the Databricks path, the locator convention is share:////.; this identifies what to read and is distinct from the seller's recipient identity in consumer_identities[].", + max_length=512, + min_length=1, + ), + ] + access_expires_at: Annotated[ + AwareDatetime | None, + Field( + description='ISO 8601 time after which the buyer will revoke the grant. Declarative — tells the seller when the pipe closes so re-read behavior is predictable. Expiry bounds the access window, not retention: revocation does not claw back matched membership, and retention remains governed by the buyer-seller data processing agreement.' + ), + ] = None + + +class AudienceSource2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['platform_segment'] = 'platform_segment' + vendor: Annotated[ + brand_ref.BrandReference, Field(description='Distribution platform delivering the segment.') + ] + segment_ref: Annotated[ + str, + Field( + description="The vendor's segment identifier as issued to the buyer (the ID observable in the vendor's console/API). The seller owns the mapping to whatever identifier its ingest minted — it configured the destination and is the only party that can see both sides.", + max_length=256, + min_length=1, + ), + ] -from adcp.types.generated_poc.core.audience_source import ( - AudienceSource, - AudienceSource1, - AudienceSource2, -) -# Explicit exports -__all__ = ["AudienceSource", "AudienceSource1", "AudienceSource2"] +AudienceSource = Annotated[ + AudienceSource1 | AudienceSource2, + Field( + description="External source reference for an audience: a pointer to buyer data that the seller ingests over a declared activation rail, instead of inline member deltas. The data never transits AdCP — the protocol carries the reference; ingestion happens on the vendor's rails. kind discriminates the entry. A buyer MUST only send a source kind whose corresponding activation pattern (dataset_query for dataset, platform_distribution for platform_segment) the seller has declared via audience_activation; sellers reject undeclared kinds with UNSUPPORTED_FEATURE. References are never credentials: grant-based paths are in-protocol only when the vendor flow is grantee-identified (the grant names a principal; no secret is conveyed), and requests carrying credential material are rejected with CREDENTIAL_IN_ARGS. Experimental (x-status: experimental): part of the media_buy.audience_activation surface — sellers implementing external sources MUST list media_buy.audience_activation in experimental_features. Per docs/reference/experimental-status, this surface MAY change between 3.x releases with notice.", + examples=[ + { + 'kind': 'dataset', + 'vendor': {'domain': 'data-cloud.example'}, + 'locator': 'PINNACLE.AUDIENCES.HIGH_VALUE_V3', + 'access_expires_at': '2026-12-01T00:00:00Z', + }, + { + 'kind': 'platform_segment', + 'vendor': {'domain': 'activation-hub.example'}, + 'segment_ref': 'seg_88213', + }, + ], + title='Audience Source', + ), +] diff --git a/src/adcp/types/domains/core/authorized_agent_base.py b/src/adcp/types/domains/core/authorized_agent_base.py index 26b52eb9c..98159143c 100644 --- a/src/adcp/types/domains/core/authorized_agent_base.py +++ b/src/adcp/types/domains/core/authorized_agent_base.py @@ -1,18 +1,49 @@ -"""Types declared by the AdCP ``core/authorized_agent_base`` schema. +# generated by datamodel-codegen: +# filename: core/authorized_agent_base.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.authorized_agent_base import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, AwareDatetime, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import agent_encryption_key, agent_signing_key -from adcp.types.generated_poc.core.authorized_agent_base import AuthorizedAgentBaseFields -# Explicit exports -__all__ = ["AuthorizedAgentBaseFields"] +class AuthorizedAgentBaseFields(AdCPBaseModel): + url: Annotated[ + AnyUrl, + Field( + description="The authorized agent's API endpoint URL. Callers comparing this URL against a registry (sales-agent list, signal-provider registry, TMP provider lookup, etc.) MUST canonicalize both sides per the AdCP URL canonicalization rules, not byte-equality — two URLs that differ only in case, default port, or percent-encoding of unreserved characters are the same agent. See docs/reference/url-canonicalization." + ), + ] + authorized_for: Annotated[ + str, + Field( + description='Human-readable description of what this agent is authorized to do — what it sells (for sales/property agents) or what data it provides (for signal agents). The variant the entry uses (`property_ids`, `signal_tags`, etc.) tells consumers which inflection applies; this field carries the operator-supplied label.', + max_length=500, + min_length=1, + ), + ] + signing_keys: Annotated[ + list[agent_signing_key.AgentSigningKey] | None, + Field( + description='Optional publisher-attested public signing keys for this agent. Use these as the trust anchor for verifying signed agent responses instead of relying on key discovery from the agent domain alone.', + min_length=1, + ), + ] = None + encryption_keys: Annotated[ + list[agent_encryption_key.AgentEncryptionKey] | None, + Field( + description='X25519 public keys for TMPX exposure token encryption. Each key identifies a cluster master that can decrypt TMPX tokens. Used with HPKE mode_base — read replicas encrypt with this public key, only the master can decrypt.', + min_length=1, + ), + ] = None + last_updated: Annotated[ + AwareDatetime | None, + Field( + description='Optional ISO 8601 timestamp indicating when this `authorized_agents[]` entry last changed. Independent of the file-level `last_updated`. Lets validators perform a partial walk by skipping entries whose `last_updated` is older than their indexed value. Advisory — consumers MAY ignore and re-index the full file.' + ), + ] = None diff --git a/src/adcp/types/domains/core/bidding_policy.py b/src/adcp/types/domains/core/bidding_policy.py index c56e2c43c..d6d448201 100644 --- a/src/adcp/types/domains/core/bidding_policy.py +++ b/src/adcp/types/domains/core/bidding_policy.py @@ -1,24 +1,97 @@ -"""Types declared by the AdCP ``core/bidding_policy`` schema. +# generated by datamodel-codegen: +# filename: core/bidding_policy.json +# timestamp: 2026-10-03T15:09:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.bidding_policy import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.bidding_policy import ( - BiddingPolicy, - CostPer, - Roas, - Strength, - Strength1, -) +class Strength(StrEnum): + cap = 'cap' + target = 'target' + + +class CostPer(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + amount: Annotated[ + StrictFloat, + Field( + description='Average cost amount per scope-bound primary-goal result, denominated in the media-buy currency.', + gt=0.0, + ), + ] + strength: Annotated[ + Strength, + Field( + description='`cap` optimizes for an average at or below the amount and accepts underdelivery when necessary; `target` optimizes around the amount while balancing volume and spend. Neither is a per-result or per-auction guarantee.' + ), + ] + + +class Strength1(StrEnum): + floor = 'floor' + target = 'target' + + +class Roas(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + value: Annotated[ + StrictFloat, + Field( + description='Return per unit of ad spend; 4 means 4 units of value per 1 unit spent.', + gt=0.0, + ), + ] + strength: Annotated[ + Strength1, + Field( + description='`floor` prefers underdelivery to knowingly optimizing below the requested return; `target` optimizes around the requested return. Neither guarantees realized return.' + ), + ] + -# Explicit exports -__all__ = ["BiddingPolicy", "CostPer", "Roas", "Strength", "Strength1"] +class BiddingPolicy(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + automatic: Annotated[ + Literal[True] | None, + Field( + description='Explicitly use seller/provider automatic bidding at this authored scope. At package scope this is a complete override of a media-buy policy, not inheritance. It MUST be the only field in the block and MUST be preserved on readback.' + ), + ] = None + bid_amount: Annotated[ + StrictFloat | None, + Field( + description="Manual auction bid denominated in the media-buy currency and expressed per the selected pricing option's auction unit. For example, a CPM option interprets the amount per thousand impressions. This is the amount submitted to the auction, not a promise that the clearing price equals it. Requires an auction-priced pricing option whose currency equals the media-buy currency.", + gt=0.0, + ), + ] = None + max_bid: Annotated[ + StrictFloat | None, + Field( + description="Hard per-auction ceiling denominated in the media-buy currency and expressed per the selected pricing option's auction unit. This is the only canonical hard auction ceiling and MUST NOT be translated into an average outcome-cost control. Requires an auction-priced pricing option whose currency equals the media-buy currency. May stand alone or supplement cost_per/roas only when the relevant scope capability advertises that combination.", + gt=0.0, + ), + ] = None + cost_per: Annotated[ + CostPer | None, + Field( + description="Average cost control per result of the scope-bound primary optimization goal. At seller-optimized media-buy scope it binds to budget_allocation.optimization_goals; at package scope it binds to that package's optimization_goals; at fixed media-buy scope it binds independently to each inheriting package and is valid only when their primary-goal result units are compatible. Metric goals are compatible only when metric and every result-defining qualifier match; vendor_metric goals only when vendor and metric_id match; event goals only when the event_type/custom_event_name set and resolved attribution_window match. Primary is the earliest array entry among goals tied for the lowest explicit numeric priority; unprioritized goals follow explicitly prioritized goals; when all priorities are absent, the first entry is primary." + ), + ] = None + roas: Annotated[ + Roas | None, + Field( + description='Dimensionless return-on-ad-spend control bound to the same scope-specific primary goal rules as cost_per. The bound goal must be value-bearing; a fixed media-buy default requires a value-bearing primary goal on every inheriting package. Every referenced value-bearing event source MUST declare value_currencies containing the media-buy currency. The seller validates this at buy creation; each buy consumes only exact-currency records, while other declared currencies remain available to other buys. Sellers MUST NOT perform currency conversion.' + ), + ] = None diff --git a/src/adcp/types/domains/core/bidding_policy_capability.py b/src/adcp/types/domains/core/bidding_policy_capability.py index de90dbcd4..02de476bf 100644 --- a/src/adcp/types/domains/core/bidding_policy_capability.py +++ b/src/adcp/types/domains/core/bidding_policy_capability.py @@ -1,36 +1,117 @@ -"""Types declared by the AdCP ``core/bidding_policy_capability`` schema. +# generated by datamodel-codegen: +# filename: core/bidding_policy_capability.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.bidding_policy_capability import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field + + +class Mode(StrEnum): + automatic = 'automatic' + bid_amount = 'bid_amount' + max_bid = 'max_bid' + cost_per = 'cost_per' + roas = 'roas' + + +class CostPerStrength(StrEnum): + cap = 'cap' + target = 'target' + + +class RoasStrength(StrEnum): + floor = 'floor' + target = 'target' + + +class MaxBidWithCostPer(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['max_bid_with_cost_per'] = 'max_bid_with_cost_per' + cost_per_strengths: Annotated[ + list[CostPerStrength], + Field(description='cost_per strengths supported when combined with max_bid.', min_length=1), + ] + + +class MaxBidWithRoas(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['max_bid_with_roas'] = 'max_bid_with_roas' + roas_strengths: Annotated[ + list[RoasStrength], + Field(description='roas strengths supported when combined with max_bid.', min_length=1), + ] + + +class PolicyProfile(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + modes: Annotated[ + list[Mode] | None, + Field( + description='Standalone policy modes accepted in this exact scope and allocation context. Combination-only components belong only in supported_combinations and need not appear here. `automatic` means the seller accepts and preserves an explicit `{automatic:true}` authored block; omission only invokes inheritance/default behavior.', + min_length=1, + ), + ] = None + cost_per_strengths: Annotated[ + list[CostPerStrength] | None, + Field( + description='Supported standalone cost_per strengths in this profile. Required exactly when modes includes cost_per.', + min_length=1, + ), + ] = None + roas_strengths: Annotated[ + list[RoasStrength] | None, + Field( + description='Supported standalone roas strengths in this profile. Required exactly when modes includes roas.', + min_length=1, + ), + ] = None + supported_combinations: Annotated[ + list[MaxBidWithCostPer | MaxBidWithRoas] | None, + Field( + description='Multi-field policies supported in this exact scope and allocation context. Each entry independently qualifies the strengths supported in that combination; standalone strength support does not imply combination support. Absence means no multi-field combination is claimed.', + min_length=1, + ), + ] = None + + +class ScopeCapability(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + fixed: Annotated[ + PolicyProfile | None, + Field(description='Policies supported when budget allocation is fixed or omitted.'), + ] = None + seller_optimized: Annotated[ + PolicyProfile | None, + Field(description='Policies supported when budget_allocation.mode is seller_optimized.'), + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.bidding_policy_capability import ( - BiddingPolicyCapability, - CostPerStrength, - MaxBidWithCostPer, - MaxBidWithRoas, - Mode, - PolicyProfile, - RoasStrength, - ScopeCapability, -) - -# Explicit exports -__all__ = [ - "BiddingPolicyCapability", - "CostPerStrength", - "MaxBidWithCostPer", - "MaxBidWithRoas", - "Mode", - "PolicyProfile", - "RoasStrength", - "ScopeCapability", -] +class BiddingPolicyCapability(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + media_buy: Annotated[ + ScopeCapability | None, + Field( + description='Policies accepted at media-buy scope and inherited by packages that omit a package override.' + ), + ] = None + package: Annotated[ + ScopeCapability | None, + Field( + description='Policies accepted as package-authored policies, including explicit automatic overrides.' + ), + ] = None diff --git a/src/adcp/types/domains/core/brand_id.py b/src/adcp/types/domains/core/brand_id.py index d0c90dc8c..40c782997 100644 --- a/src/adcp/types/domains/core/brand_id.py +++ b/src/adcp/types/domains/core/brand_id.py @@ -1,18 +1,17 @@ -"""Types declared by the AdCP ``core/brand_id`` schema. +# generated by datamodel-codegen: +# filename: core/brand_id.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.brand_id import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.brand_id import BrandId +from adcp.types._scalar import ScalarStr + -# Explicit exports -__all__ = ["BrandId"] +class BrandId(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[a-z0-9_]+$'} + _json_schema_extra = { + 'description': 'Identifier for a brand within a house portfolio. Must be lowercase alphanumeric with underscores only. The house chooses this identifier.', + 'examples': ['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], + 'title': 'Brand ID', + } diff --git a/src/adcp/types/domains/core/brand_key.py b/src/adcp/types/domains/core/brand_key.py index 8ec6444c4..57dfbd434 100644 --- a/src/adcp/types/domains/core/brand_key.py +++ b/src/adcp/types/domains/core/brand_key.py @@ -1,18 +1,44 @@ -"""Types declared by the AdCP ``core/brand_key`` schema. +# generated by datamodel-codegen: +# filename: core/brand_key.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.brand_key import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import brand_id as brand_id_1 + + +class Country(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{2}$'} -from adcp.types.generated_poc.core.brand_key import BrandKey, Country -# Explicit exports -__all__ = ["BrandKey", "Country"] +class BrandKey(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + domain: Annotated[ + str, + Field( + description='Domain that hosts /.well-known/brand.json or is registered for the brand.', + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] + brand_id: Annotated[ + brand_id_1.BrandId | None, + Field( + description='Brand within a house-of-brands manifest. Omit for a single-brand domain.' + ), + ] = None + countries: Annotated[ + list[Country] | None, + Field( + description='Canonical set of ISO 3166-1 alpha-2 countries for this advertiser identity. Omit when the identity is global or the house does not split the brand geographically. Array order is not meaningful; producers MUST sort codes lexicographically before computing keys or signatures. This qualifies account and proposal identity and is not delivery targeting.', + min_length=1, + ), + ] = None diff --git a/src/adcp/types/domains/core/brand_ref.py b/src/adcp/types/domains/core/brand_ref.py index 983e81b3c..9ce889552 100644 --- a/src/adcp/types/domains/core/brand_ref.py +++ b/src/adcp/types/domains/core/brand_ref.py @@ -1,24 +1,98 @@ -"""Types declared by the AdCP ``core/brand_ref`` schema. +# generated by datamodel-codegen: +# filename: core/brand_ref.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.brand_ref import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import AnyUrl, ConfigDict, EmailStr, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import brand_id as brand_id_1 +from .assets import image_asset + + +class Country(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{2}$'} + + +class DataSubjectContestation(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + url: AnyUrl | None = None + email: EmailStr | None = None + languages: list[str] | None = None + + +class Colors(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None + secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None + accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None + + +class BrandKitOverride(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + logo: Annotated[image_asset.ImageAsset | None, Field(description='Override logo asset.')] = None + colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( + None + ) + voice: Annotated[ + str | None, + Field( + description='Override brand-voice description for surface-composed text/audio output.' + ), + ] = None + tagline: Annotated[str | None, Field(description='Override tagline.')] = None -from adcp.types.generated_poc.core.brand_ref import ( - BrandKitOverride, - BrandReference, - Colors, - Country, - DataSubjectContestation, -) -# Explicit exports -__all__ = ["BrandKitOverride", "BrandReference", "Colors", "Country", "DataSubjectContestation"] +class BrandReference(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + domain: Annotated[ + str, + Field( + description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] + brand_id: Annotated[ + brand_id_1.BrandId | None, + Field( + description='Brand identifier within the house portfolio. Optional for single-brand domains.' + ), + ] = None + countries: Annotated[ + list[Country] | None, + Field( + description='Canonical set of ISO 3166-1 alpha-2 countries for this advertiser identity. Omit for a global/default identity. Array order is not meaningful; producers MUST sort codes lexicographically before computing keys or signatures. This qualifies account identity and is not delivery targeting.', + min_length=1, + ), + ] = None + industries: Annotated[ + list[str] | None, + Field( + description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." + ), + ] = None + data_subject_contestation: Annotated[ + DataSubjectContestation | None, + Field( + description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." + ), + ] = None + brand_kit_override: Annotated[ + BrandKitOverride | None, + Field( + description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." + ), + ] = None diff --git a/src/adcp/types/domains/core/brand_response_authorization_result.py b/src/adcp/types/domains/core/brand_response_authorization_result.py index 8df188d52..33a5de9f6 100644 --- a/src/adcp/types/domains/core/brand_response_authorization_result.py +++ b/src/adcp/types/domains/core/brand_response_authorization_result.py @@ -1,30 +1,80 @@ -"""Types declared by the AdCP ``core/brand_response_authorization_result`` schema. +# generated by datamodel-codegen: +# filename: core/brand_response_authorization_result.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.brand_response_authorization_result import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field + + +class Trust(StrEnum): + trusted = 'trusted' + untrusted = 'untrusted' + + +class Reason(StrEnum): + brand_json_unavailable = 'brand_json_unavailable' + agent_not_authorized = 'agent_not_authorized' + agent_authorization_ambiguous = 'agent_authorization_ambiguous' + jwks_unavailable = 'jwks_unavailable' + kid_not_authorized = 'kid_not_authorized' + kid_authorization_ambiguous = 'kid_authorization_ambiguous' + key_material_mismatch = 'key_material_mismatch' + key_purpose_invalid = 'key_purpose_invalid' + + +class BrandResponseAuthorizationResult1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + trust: Annotated[ + Literal['trusted'], + Field( + description='Whether the signing key was bound to the asserted brand_domain through an authorized brand-agent entry. An untrusted result MUST NOT extend or revoke relationship trust on its own.' + ), + ] = 'trusted' + reason: Annotated[ + Reason | None, Field(description='Machine-readable reason for an untrusted result.') + ] = None + kid: Annotated[str, Field(description='JWS kid evaluated by the cross-check.', min_length=1)] + jwks_uri: Annotated[ + AnyUrl, + Field( + description='JWKS URI selected only from the matched brand.json agent entry, or its same-origin default.' + ), + ] + + +class BrandResponseAuthorizationResult2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + trust: Annotated[ + Literal['untrusted'], + Field( + description='Whether the signing key was bound to the asserted brand_domain through an authorized brand-agent entry. An untrusted result MUST NOT extend or revoke relationship trust on its own.' + ), + ] = 'untrusted' + reason: Annotated[Reason, Field(description='Machine-readable reason for an untrusted result.')] + kid: Annotated[ + str | None, Field(description='JWS kid evaluated by the cross-check.', min_length=1) + ] = None + jwks_uri: Annotated[ + AnyUrl | None, + Field( + description='JWKS URI selected only from the matched brand.json agent entry, or its same-origin default.' + ), + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.brand_response_authorization_result import ( - BrandResponseAuthorizationResult, - BrandResponseAuthorizationResult1, - BrandResponseAuthorizationResult2, - Reason, - Trust, -) - -# Explicit exports -__all__ = [ - "BrandResponseAuthorizationResult", - "BrandResponseAuthorizationResult1", - "BrandResponseAuthorizationResult2", - "Reason", - "Trust", +BrandResponseAuthorizationResult = Annotated[ + BrandResponseAuthorizationResult1 | BrandResponseAuthorizationResult2, + Field( + description='Verifier-local result of the additive 3.2 brand.json authorization cross-check for a cryptographically valid verify_brand_claim or verify_brand_claims signed response. This object is produced by the verifier and is not a field asserted by the responding brand agent.', + title='Brand Response Authorization Result', + ), ] diff --git a/src/adcp/types/domains/core/budget_allocation.py b/src/adcp/types/domains/core/budget_allocation.py index 19d5485ec..1a7055a44 100644 --- a/src/adcp/types/domains/core/budget_allocation.py +++ b/src/adcp/types/domains/core/budget_allocation.py @@ -1,62 +1,360 @@ -"""Types declared by the AdCP ``core/budget_allocation`` schema. +# generated by datamodel-codegen: +# filename: core/budget_allocation.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.budget_allocation import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictFloat + +from ..enums import event_type as event_type_1 +from ..enums import reach_unit as reach_unit_1 +from ..enums import viewability_standard +from . import attribution_window as attribution_window_1 +from . import brand_ref, duration, vendor_metric_id + + +class BudgetAllocation1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + mode: Annotated[ + Literal['fixed'], + Field( + description='Packages have independent budgets. The seller does not automatically move budget between packages.' + ), + ] = 'fixed' + + +class Metric(StrEnum): + clicks = 'clicks' + views = 'views' + completed_views = 'completed_views' + viewed_seconds = 'viewed_seconds' + viewable_rate = 'viewable_rate' + attention_seconds = 'attention_seconds' + attention_score = 'attention_score' + engagements = 'engagements' + follows = 'follows' + saves = 'saves' + profile_visits = 'profile_visits' + reach = 'reach' + + +class Target(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + kind: Literal['cost_per'] = 'cost_per' + value: Annotated[ + StrictFloat, Field(description='Target cost per metric unit in the buy currency', gt=0.0) + ] + + +class Target3(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + kind: Literal['threshold_rate'] = 'threshold_rate' + value: Annotated[ + StrictFloat, + Field( + description='Minimum per-impression value. Units depend on the metric: proportion (clicks, views, completed_views, viewable_rate), seconds (viewed_seconds, attention_seconds), or score (attention_score).', + gt=0.0, + ), + ] + + +class Target4(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + kind: Literal['cost_per'] = 'cost_per' + value: Annotated[ + StrictFloat, Field(description='Target cost per event in the buy currency', gt=0.0) + ] + + +class Target5(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + kind: Literal['per_ad_spend'] = 'per_ad_spend' + value: Annotated[ + StrictFloat, + Field(description='Target return ratio (e.g., 4.0 means $4 of value per $1 spent)', gt=0.0), + ] + + +class Target6(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + kind: Literal['maximize_value'] = 'maximize_value' + + +class Target7(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + kind: Literal['cost_per'] = 'cost_per' + value: Annotated[ + StrictFloat, + Field( + description='Target cost per metric unit in the buy currency. Units of the metric are vendor-defined.', + gt=0.0, + ), + ] + + +class Target8(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + kind: Literal['threshold_rate'] = 'threshold_rate' + value: Annotated[ + StrictFloat, + Field( + description='Minimum per-impression value. Units of the metric are vendor-defined.', + gt=0.0, + ), + ] + + +class OptimizationGoal4(AdCPBaseModel): + pass + + +class TargetFrequency(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + min: Annotated[ + SchemaInt | None, + Field( + description='Minimum frequency for an entity to be considered meaningfully reached within the window. Impressions that would bring an entity below this threshold are treated as high-value (growing reach). When omitted, the seller uses their platform default (typically 1).', + ge=1, + ), + ] = None + max: Annotated[ + SchemaInt | None, + Field( + description='Frequency at which an entity is considered saturated within the window. Impressions toward entities at or above this threshold are treated as lower-value. When both min and max are present, max must be greater than or equal to min. When omitted, the seller determines the saturation point.', + ge=1, + ), + ] = None + window: Annotated[ + duration.Duration, + Field( + description='Time window over which frequency is measured (e.g. {"interval": 7, "unit": "days"} or {"interval": 1, "unit": "campaign"} for the full flight). Weekly windows are typical for brand campaigns; daily windows suit high-cadence direct response.' + ), + ] + + +class EventSource(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + event_source_id: Annotated[ + str, + Field( + description='Event source to include (must be configured on this account via sync_event_sources)', + min_length=1, + ), + ] + event_type: Annotated[ + event_type_1.EventType, + Field( + description='Event type to include from this source (e.g., purchase, lead, app_install, refund)' + ), + ] + custom_event_name: Annotated[ + str | None, + Field( + description="Required when event_type is 'custom'. Platform-specific name for the custom event." + ), + ] = None + value_field: Annotated[ + str | None, + Field( + description="Which field in the event's custom_data carries the monetary value. The seller must use this field for value extraction and aggregation when computing ROAS and conversion value metrics. Required on at least one entry when target.kind is 'per_ad_spend' or 'maximize_value' — sellers must reject these target kinds when no event source entry includes value_field. When canonical bidding.roas binds to this goal, every referenced value-bearing source MUST declare value_currencies containing the media-buy currency; the seller validates this at buy creation. Once that source contract is declared, log_event rejects monetary records whose currency is missing or absent from the list. For each buy, only records exactly matching its media-buy currency contribute to ROAS; records in other declared currencies remain available to matching buys, and sellers MUST NOT convert between them. When present without a value-oriented target, the seller may use it for delivery reporting but must not change the objective. Common values: 'value', 'order_total', 'profit_margin'. This is not passed as a parameter to underlying platform APIs — the seller maps it to their platform's value ingestion mechanism." + ), + ] = None + value_factor: Annotated[ + StrictFloat | None, + Field( + description="Unit-scaling multiplier the seller must apply to value_field before aggregation. Use -1 for refund events (negate the value), 0.01 for values in cents, -0.01 for refunds in cents. It MUST NOT be used for currency conversion. A value of 0 zeroes out this source's value contribution (the source still counts for event dedup). Defaults to 1. This is not passed as a parameter to underlying platform APIs — the seller applies it when computing aggregated value metrics." + ), + ] = 1 + + +class OptimizationGoal2(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + kind: Literal['event'] = 'event' + event_sources: Annotated[ + list[EventSource], + Field( + description='Event source and type pairs that feed this goal. Each entry identifies a source and event type to include. When the seller supports multi_source_event_dedup (declared in get_adcp_capabilities), they deduplicate by event_id across all entries — the same business event from multiple sources counts once, using value_field and value_factor from the first matching entry. When multi_source_event_dedup is false or absent, buyers should use a single entry per goal; the seller will use only the first entry. All event sources must be configured via sync_event_sources.', + min_length=1, + ), + ] + target: Annotated[ + Target4 | Target5 | Target6 | None, + Field( + description='Target cost or return for this event goal. When omitted, the seller optimizes for maximum conversion count within budget — regardless of whether value_field is present on event sources. The presence of value_field alone does not change the optimization objective; it only makes value available for reporting. An explicit target of maximize_value or per_ad_spend is required to steer toward value.', + discriminator='kind', + ), + ] = None + attribution_window: Annotated[ + attribution_window_1.AttributionWindow | None, + Field( + description="Attribution window for this optimization goal — references the canonical `attribution-window` shape (post_click, post_view, model). Values must match an option declared in the seller's `conversion_tracking.attribution_windows` capability. Sellers MUST reject windows not in their declared capabilities. When the entire field is omitted, the seller uses their default window." + ), + ] = None + priority: Annotated[ + SchemaInt | None, + Field( + description='Relative priority among sibling goals. Lower numbers rank first. Goals without priority follow explicitly prioritized goals. Ties use array order, so the earliest goal at the lowest explicit priority is primary; when all priorities are omitted, the first goal is primary.', + ge=1, + ), + ] = None + + +class OptimizationGoal6(OptimizationGoal2, OptimizationGoal4): + pass + + +class OptimizationGoal1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + kind: Literal['metric'] = 'metric' + metric: Annotated[ + Metric, + Field( + description="Seller-native metric to optimize for. Delivery metrics: clicks (link clicks, swipe-throughs, CTA taps that navigate away), views (content views at the billable view threshold, as defined by delivery-metrics `views`; for viewability use viewable_rate), completed_views (video/audio completions — see view_duration_seconds), reach (unique audience reach — see reach_unit and target_frequency). Duration/score metrics: viewed_seconds (time in view per impression — reported back via `delivery-metrics.viewability.viewed_seconds`, governed by the viewability `standard`). Quality-rate metrics: viewable_rate (viewable / measurable impressions under the goal's `standard`; requires `standard`). Audience action metrics: engagements (any direct interaction with the ad unit beyond viewing — social reactions/comments/shares, story/unit opens, interactive overlay taps, companion banner interactions on audio and CTV), follows (new followers, page likes, artist/podcast/channel follows, or free channel/feed subscribes; paid subscriptions use event_type: subscribe), saves (saves, bookmarks, playlist adds, pins — signals of intent to return), profile_visits (visits to the brand's in-platform page — profile, artist page, channel, or storefront. Does not include external website clicks, which are covered by 'clicks'). **DEPRECATED values** (slated for removal at next major): `attention_seconds` and `attention_score` — these have no industry-graduated definition (DoubleVerify, IAS, Adelaide, TVision, Lumen each define them differently) and cannot be meaningfully optimized for without a vendor binding. Use `kind: 'vendor_metric'` with an explicit `vendor` and `metric_id` instead — that path binds the goal to a specific measurement vendor and reconciles to the same `(vendor, metric_id)` key in delivery's `vendor_metric_values[]`. Sellers MAY reject the deprecated values with `TERMS_REJECTED` and a suggestion to use the `vendor_metric` kind." + ), + ] + standard: Annotated[ + viewability_standard.ViewabilityStandard | None, + Field( + description="Viewability standard the goal is judged against. Required when metric is 'viewable_rate'; optional for 'viewed_seconds' (seller default standard when omitted); not allowed for other metrics. Must be in metric_optimization.supported_viewability_standards when declared. A goal below a same-standard viewability performance_standard never relaxes that standard." + ), + ] = None + vendor: Annotated[ + brand_ref.BrandReference | None, + Field( + description="Measurement vendor judging a viewable_rate or viewed_seconds goal; not allowed for other metrics. When omitted, the seller's default viewability measurement applies. Sellers MUST reject a vendor they cannot optimize against rather than substitute another." + ), + ] = None + reach_unit: Annotated[ + reach_unit_1.ReachUnit | None, + Field( + description="Unit for reach measurement. Required when metric is 'reach'. Must be a value declared in the product's metric_optimization.supported_reach_units." + ), + ] = None + target_frequency: Annotated[ + TargetFrequency | None, + Field( + description="Target frequency band for reach optimization. Only applicable when metric is 'reach'. Frames frequency as an optimization signal: the seller should treat impressions toward entities already within the [min, max] band as lower-value, and impressions toward unreached entities as higher-value. This shifts budget toward fresh reach rather than re-reaching known users. When omitted, the seller maximizes unique reach without a frequency constraint. A hard cap can still be layered via targeting_overlay.frequency_cap if a ceiling is needed." + ), + ] = None + view_duration_seconds: Annotated[ + StrictFloat | None, + Field( + description="Minimum video view duration in seconds that qualifies as a completed_view for this goal. Only applicable when metric is 'completed_views'. When omitted, the seller uses their platform default (typically 2–15 seconds). Common values: 2 (Snap/LinkedIn default), 6 (TikTok), 15 (Snap 15-second views, Meta ThruPlay). Sellers declare which durations they support in metric_optimization.supported_view_durations. Sellers must reject goals with unsupported values — silent rounding would create measurement discrepancies.", + gt=0.0, + ), + ] = None + target: Annotated[ + Target | Target3 | None, + Field( + description='Target for this metric. When omitted, the seller optimizes for maximum metric volume within budget.', + discriminator='kind', + ), + ] = None + priority: Annotated[ + SchemaInt | None, + Field( + description='Relative priority among sibling goals. Lower numbers rank first. Goals without priority follow explicitly prioritized goals. Ties use array order, so the earliest goal at the lowest explicit priority is primary; when all priorities are omitted, the first goal is primary.', + ge=1, + ), + ] = None + + +class OptimizationGoal3(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + kind: Literal['vendor_metric'] = 'vendor_metric' + vendor: Annotated[ + brand_ref.BrandReference, + Field( + description='Vendor that defines and computes this metric. Same shape as `vendor_metric_values.vendor`, `reporting_capabilities.vendor_metrics[].vendor`, and `vendor_metric_optimization.supported_metrics[].vendor` — symmetric across discovery, capability, commitment, optimization, and reporting surfaces.' + ), + ] + metric_id: Annotated[ + vendor_metric_id.VendorMetricId, + Field( + description="Identifier for the metric within the vendor's vocabulary (e.g., `attention_score`, `attention_seconds`, `gco2e_per_impression`, `awareness_lift`). MUST be present in the vendor's published `measurement.metrics[]` catalog and in the product's `vendor_metric_optimization.supported_metrics[]`." + ), + ] + target: Annotated[ + Target7 | Target8 | None, + Field( + description="Target for this vendor metric. When omitted, the seller optimizes for maximum metric volume / score within budget. `cost_per` and `threshold_rate` semantics mirror the same target kinds on the `metric` kind — units are vendor-defined and depend on the vendor's `measurement.metrics[]` declaration for this `metric_id`.", + discriminator='kind', + ), + ] = None + priority: Annotated[ + SchemaInt | None, + Field( + description='Relative priority among sibling goals. Lower numbers rank first. Goals without priority follow explicitly prioritized goals. Ties use array order, so the earliest goal at the lowest explicit priority is primary; when all priorities are omitted, the first goal is primary.', + ge=1, + ), + ] = None + + +class OptimizationGoal5(OptimizationGoal1, OptimizationGoal4): + pass + + +class OptimizationGoal7(OptimizationGoal3, OptimizationGoal4): + pass + + +OptimizationGoal = OptimizationGoal5 | OptimizationGoal6 | OptimizationGoal7 + + +class BudgetAllocation2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + mode: Annotated[ + Literal['seller_optimized'], + Field( + description='Packages draw from a shared media-buy budget. The seller continuously allocates spend across packages to optimize the declared goals.' + ), + ] = 'seller_optimized' + optimization_goals: Annotated[ + list[OptimizationGoal], + Field( + description='Goals the seller uses to allocate the shared budget across packages. These are distinct from packages[].optimization_goals, which optimize delivery within an individual package. The primary goal is the earliest array entry among goals with the lowest explicit numeric priority; goals without priority follow all explicitly prioritized goals; when all priorities are omitted, the first entry is primary. It supplies the result unit/value source for media-buy bidding.cost_per or bidding.roas. Legacy monetary target kinds are prohibited at allocation scope because canonical controls belong in media-buy bidding. Every participating product MUST support the primary cross-package goal; otherwise the seller MUST reject the request with TERMS_REJECTED or UNSUPPORTED_FEATURE and identify the incompatible package.', + min_length=1, + ), + ] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.budget_allocation import ( - BudgetAllocation, - BudgetAllocation1, - BudgetAllocation2, - EventSource, - Metric, - OptimizationGoal, - OptimizationGoal1, - OptimizationGoal2, - OptimizationGoal3, - OptimizationGoal4, - OptimizationGoal5, - OptimizationGoal6, - OptimizationGoal7, - Target, - Target3, - Target4, - Target5, - Target6, - Target7, - Target8, - TargetFrequency, -) - -# Explicit exports -__all__ = [ - "BudgetAllocation", - "BudgetAllocation1", - "BudgetAllocation2", - "EventSource", - "Metric", - "OptimizationGoal", - "OptimizationGoal1", - "OptimizationGoal2", - "OptimizationGoal3", - "OptimizationGoal4", - "OptimizationGoal5", - "OptimizationGoal6", - "OptimizationGoal7", - "Target", - "Target3", - "Target4", - "Target5", - "Target6", - "Target7", - "Target8", - "TargetFrequency", +BudgetAllocation = Annotated[ + BudgetAllocation1 | BudgetAllocation2, + Field( + description='How a media buy\'s total budget is allocated across its packages. Omission on a request means fixed allocation (legacy-compatible); buyer agents SHOULD send this block explicitly ({mode: "fixed"} or seller_optimized) rather than rely on omission, and SHOULD ask the principal rather than guess when the instruction does not determine whether the budget is one shared pool or split per package. Fixed allocation preserves independent package budgets. Seller-optimized allocation delegates continuous cross-package allocation to the seller within the media-buy total, package caps, minimum-spend targets, flight windows, and pacing controls. The core seller-optimized contract is gated by media_buy.features.seller_optimized_budget; package caps, minimum-spend targets, and package pacing are separately gated by seller_optimized_package_budgets, seller_optimized_min_spend_targets, and seller_optimized_package_pacing; an undeclared package control is rejected with UNSUPPORTED_FEATURE before any over-subscription validation. Core seller-optimized pacing guarantees only omitted or `even` media-buy pacing; sellers MAY reject `asap` or `front_loaded` with UNSUPPORTED_FEATURE and MUST NOT coerce them to `even`. When a seller-optimized media-buy BiddingPolicy carries cost_per or roas, that control binds to this allocation block\'s primary optimization goal rather than independently to each package goal.', + discriminator='mode', + title='Budget Allocation', + ), ] diff --git a/src/adcp/types/domains/core/budget_range.py b/src/adcp/types/domains/core/budget_range.py index 73acda095..986c3896a 100644 --- a/src/adcp/types/domains/core/budget_range.py +++ b/src/adcp/types/domains/core/budget_range.py @@ -1,18 +1,32 @@ -"""Types declared by the AdCP ``core/budget_range`` schema. +# generated by datamodel-codegen: +# filename: core/budget_range.json +# timestamp: 2026-10-04T01:14:11+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.budget_range import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictFloat, model_validator -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.budget_range import BudgetRange +class BudgetRange(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + min: Annotated[StrictFloat | None, Field(ge=0.0)] = None + max: Annotated[StrictFloat | None, Field(ge=0.0)] = None + currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] -# Explicit exports -__all__ = ["BudgetRange"] + @model_validator(mode='after') + def _require_schema_required_group(self) -> BudgetRange: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('min',), ('max',),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'BudgetRange requires at least one of these field groups: min | max' + ) diff --git a/src/adcp/types/domains/core/business_entity.py b/src/adcp/types/domains/core/business_entity.py index 977bc94af..8d5f43ecb 100644 --- a/src/adcp/types/domains/core/business_entity.py +++ b/src/adcp/types/domains/core/business_entity.py @@ -1,24 +1,129 @@ -"""Types declared by the AdCP ``core/business_entity`` schema. +# generated by datamodel-codegen: +# filename: core/business_entity.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.business_entity import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, EmailStr, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import ext as ext_1 + + +class Address(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + street: Annotated[ + str, Field(description='Street address including building number', max_length=200) + ] + city: Annotated[str, Field(max_length=100)] + postal_code: Annotated[str, Field(max_length=20)] + region: Annotated[ + str | None, Field(description='State, province, or region', max_length=100) + ] = None + country: Annotated[ + str, Field(description='ISO 3166-1 alpha-2 country code', pattern='^[A-Z]{2}$') + ] + + +class Role(StrEnum): + billing = 'billing' + legal = 'legal' + creative = 'creative' + general = 'general' + + +class Contact(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + role: Annotated[ + Role, Field(description="Contact's functional role in the business relationship") + ] + name: Annotated[str | None, Field(description='Full name of the contact', max_length=200)] = ( + None + ) + email: Annotated[EmailStr | None, Field(max_length=254)] = None + phone: Annotated[str | None, Field(max_length=30)] = None + + +class Bank(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + account_holder: Annotated[str, Field(description='Name on the bank account', max_length=200)] + iban: Annotated[ + str | None, + Field( + description='International Bank Account Number (SEPA markets)', + pattern='^[A-Z]{2}[0-9]{2}[A-Z0-9]{4,30}$', + ), + ] = None + bic: Annotated[ + str | None, + Field( + description='Bank Identifier Code / SWIFT code (SEPA markets)', + pattern='^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$', + ), + ] = None + routing_number: Annotated[ + str | None, + Field( + description='Bank routing number for non-SEPA markets (e.g., US ABA routing number, Canadian transit/institution number)', + max_length=30, + ), + ] = None + account_number: Annotated[ + str | None, Field(description='Bank account number for non-SEPA markets', max_length=30) + ] = None -from adcp.types.generated_poc.core.business_entity import ( - Address, - Bank, - BusinessEntity, - Contact, - Role, -) -# Explicit exports -__all__ = ["Address", "Bank", "BusinessEntity", "Contact", "Role"] +class BusinessEntity(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + legal_name: Annotated[ + str, Field(description='Registered legal name of the business entity', max_length=200) + ] + vat_id: Annotated[ + str | None, + Field( + description='VAT identification number (e.g., DE123456789 for Germany, FR12345678901 for France). Required for B2B invoicing in the EU. Must be normalized: no spaces, dots, or dashes.', + pattern='^[A-Z]{2}[A-Z0-9]{2,13}$', + ), + ] = None + tax_id: Annotated[ + str | None, + Field( + description='Tax identification number for jurisdictions that do not use VAT (e.g., US EIN)', + max_length=30, + ), + ] = None + registration_number: Annotated[ + str | None, + Field( + description='Company registration number (e.g., HRB 12345 for German Handelsregister)', + max_length=50, + ), + ] = None + address: Annotated[ + Address | None, Field(description='Postal address for invoicing and legal correspondence') + ] = None + contacts: Annotated[ + list[Contact] | None, + Field( + description='Contacts for billing, legal, and operational matters. Contains personal data subject to GDPR and equivalent regulations. Implementations MUST use this data only for invoicing and account management.', + max_length=10, + ), + ] = None + bank: Annotated[ + Bank | None, + Field( + description='Bank account details for payment processing. Write-only: included in requests to provide payment coordinates, but MUST NOT be echoed in responses. Sellers store these details and confirm receipt without returning them.' + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/cancellation_policy.py b/src/adcp/types/domains/core/cancellation_policy.py index f5304ed25..a053919fe 100644 --- a/src/adcp/types/domains/core/cancellation_policy.py +++ b/src/adcp/types/domains/core/cancellation_policy.py @@ -1,22 +1,62 @@ -"""Types declared by the AdCP ``core/cancellation_policy`` schema. +# generated by datamodel-codegen: +# filename: core/cancellation_policy.json +# timestamp: 2026-10-03T15:09:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.cancellation_policy import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import duration + + +class Type(StrEnum): + percent_remaining = 'percent_remaining' + full_commitment = 'full_commitment' + fixed_fee = 'fixed_fee' + none = 'none' + + +class CancellationFee(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + type: Annotated[ + Type, + Field( + description="Fee calculation method. 'percent_remaining': percentage of remaining uncommitted spend. 'full_commitment': buyer owes the full committed budget regardless of delivery. 'fixed_fee': flat monetary amount. 'none': no financial fee (cancellation with notice is free)." + ), + ] + rate: Annotated[ + StrictFloat | None, + Field( + description="Fee rate as a decimal proportion of remaining committed spend. Required when type is 'percent_remaining' (e.g., 0.5 means 50% of remaining spend).", + ge=0.0, + le=1.0, + ), + ] = None + amount: Annotated[ + StrictFloat | None, + Field( + description="Fixed fee amount in the buy's currency. Required when type is 'fixed_fee'.", + ge=0.0, + ), + ] = None -from adcp.types.generated_poc.core.cancellation_policy import ( - CancellationFee, - CancellationPolicy, - Type, -) -# Explicit exports -__all__ = ["CancellationFee", "CancellationPolicy", "Type"] +class CancellationPolicy(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + notice_period: Annotated[ + duration.Duration, + Field( + description="Minimum notice period before cancellation takes effect (e.g., { interval: 30, unit: 'days' }). A guaranteed buy canceled without sufficient notice incurs the declared cancellation fee." + ), + ] + cancellation_fee: Annotated[ + CancellationFee, Field(description='Fee applied when the notice period is not met.') + ] diff --git a/src/adcp/types/domains/core/canonical_account_ref.py b/src/adcp/types/domains/core/canonical_account_ref.py index 971462c4c..e8972c2a6 100644 --- a/src/adcp/types/domains/core/canonical_account_ref.py +++ b/src/adcp/types/domains/core/canonical_account_ref.py @@ -1,22 +1,70 @@ -"""Types declared by the AdCP ``core/canonical_account_ref`` schema. +# generated by datamodel-codegen: +# filename: core/canonical_account_ref.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.canonical_account_ref import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import brand_key +from . import operator_unit as operator_unit_1 + + +class CanonicalAccountReference1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + account_id: Annotated[str, Field(min_length=1)] + + +class CanonicalAccountReference2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + brand: brand_key.BrandKey + operator: Annotated[ + str, Field(pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$') + ] + operator_unit: Annotated[ + operator_unit_1.OperatorUnit | None, + Field( + description='Optional operator-owned business unit, agency seat, or platform account. Only id participates in identity; name is mutable display metadata.' + ), + ] = None + currency: Annotated[ + str | None, + Field( + description='Immutable ISO 4217 transaction currency when the advertiser object is currency-bound. When present, this is part of the natural key and all media buys on the account use it. Omit for per-media-buy currency selection.', + pattern='^[A-Z]{3}$', + ), + ] = None + timezone: Annotated[ + str | None, + Field( + description='Immutable account timezone. Include it in the natural key only for buyer-selected account_fixed provisioning.', + min_length=1, + ), + ] = None + sandbox: StrictBool | None = False -from adcp.types.generated_poc.core.canonical_account_ref import ( - CanonicalAccountReference, - CanonicalAccountReference1, - CanonicalAccountReference2, -) -# Explicit exports -__all__ = ["CanonicalAccountReference", "CanonicalAccountReference1", "CanonicalAccountReference2"] +CanonicalAccountReference = Annotated[ + CanonicalAccountReference1 | CanonicalAccountReference2, + Field( + description='Compact advertiser-account identity for canonical 3.2 tools. Uses either the seller/storefront account_id returned by list_accounts or the buyer-declared natural key without BrandRef inline overrides. The natural key is brand + operator + optional operator_unit.id + optional fixed currency + optional buyer-selected account timezone + sandbox. brand.countries qualifies the commercial advertiser identity without targeting delivery; operator_unit identifies the operator-owned buying context. operator_unit.name is display metadata and is excluded from identity.', + examples=[ + {'account_id': 'acc_nova_nl'}, + { + 'brand': {'domain': 'nova-athletics.example', 'countries': ['NL']}, + 'operator': 'nova-athletics.example', + 'operator_unit': {'id': '234284238', 'name': 'Nova EMEA'}, + 'currency': 'EUR', + 'timezone': 'Europe/Amsterdam', + }, + ], + title='Canonical Account Reference', + ), +] diff --git a/src/adcp/types/domains/core/canonical_audience_evidence.py b/src/adcp/types/domains/core/canonical_audience_evidence.py index 477335e49..3b07cb61a 100644 --- a/src/adcp/types/domains/core/canonical_audience_evidence.py +++ b/src/adcp/types/domains/core/canonical_audience_evidence.py @@ -1,32 +1,82 @@ -"""Types declared by the AdCP ``core/canonical_audience_evidence`` schema. +# generated by datamodel-codegen: +# filename: core/canonical_audience_evidence.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.canonical_audience_evidence import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from adcp.types._scalar import ScalarStr +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictFloat + +from ..enums import audience_evidence_methodology, audience_resolution_method, audience_subject_type +from . import audience_characteristic, brand_key, date_range +from . import ext as ext_1 + + +class Relationship(StrEnum): + composition = 'composition' + index = 'index' # type: ignore[assignment] + reach_estimate = 'reach_estimate' + + +class Unit(StrEnum): + fraction = 'fraction' + ratio = 'ratio' + count = 'count' # type: ignore[assignment] + + +class Baseline(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + system: AnyUrl + population_id: Annotated[str, Field(min_length=1)] + version: Annotated[str, Field(min_length=1)] + description: str | None = None + + +class EvidenceType(StrEnum): + measured = 'measured' + forecast = 'forecast' + seller_declared = 'seller_declared' + + +class AttestationDigest(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^sha256:[a-f0-9]{64}$'} -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.canonical_audience_evidence import ( - AttestationDigest, - Baseline, - CanonicalAudienceEvidence, - EvidenceType, - Relationship, - Unit, -) - -# Explicit exports -__all__ = [ - "AttestationDigest", - "Baseline", - "CanonicalAudienceEvidence", - "EvidenceType", - "Relationship", - "Unit", -] +class CanonicalAudienceEvidence(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + evidence_id: Annotated[str, Field(min_length=1)] + snapshot_id: Annotated[str, Field(min_length=1)] + version: Annotated[str, Field(min_length=1)] + content_digest: Annotated[str, Field(pattern='^sha256:[a-f0-9]{64}$')] + audience: audience_characteristic.AudienceCharacteristic + relationship: Relationship + value: Annotated[StrictFloat, Field(ge=0.0)] + unit: Unit + baseline: Baseline + evidence_type: EvidenceType + methodology: audience_evidence_methodology.AudienceEvidenceMethodology + subject_type: audience_subject_type.AudienceSubjectType + resolution_method: audience_resolution_method.AudienceResolutionMethod | None = None + provider: brand_key.BrandKey + measurement_window: date_range.DateRange + sample_size: Annotated[SchemaInt | None, Field(ge=1)] = None + confidence: Annotated[StrictFloat | None, Field(ge=0.0, le=1.0)] = None + last_updated: AwareDatetime + methodology_url: AnyUrl | None = None + attestation_digests: Annotated[ + list[AttestationDigest] | None, + Field( + description='Portable-attestation reference digests available through the evidence provider; credential bodies are not inlined into product discovery.', + min_length=1, + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/canonical_audience_evidence_selection.py b/src/adcp/types/domains/core/canonical_audience_evidence_selection.py index 2207654a7..870e13462 100644 --- a/src/adcp/types/domains/core/canonical_audience_evidence_selection.py +++ b/src/adcp/types/domains/core/canonical_audience_evidence_selection.py @@ -1,22 +1,42 @@ -"""Types declared by the AdCP ``core/canonical_audience_evidence_selection`` schema. +# generated by datamodel-codegen: +# filename: core/canonical_audience_evidence_selection.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.canonical_audience_evidence_selection import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import canonical_audience_evidence +from . import ext as ext_1 + + +class DecisionUse(StrEnum): + recommendation = 'recommendation' + eligibility = 'eligibility' + package_construction = 'package_construction' + + +class VerifiedAttestationDigest(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^sha256:[a-f0-9]{64}$'} -from adcp.types.generated_poc.core.canonical_audience_evidence_selection import ( - CanonicalAudienceEvidenceSelection, - DecisionUse, - VerifiedAttestationDigest, -) -# Explicit exports -__all__ = ["CanonicalAudienceEvidenceSelection", "DecisionUse", "VerifiedAttestationDigest"] +class CanonicalAudienceEvidenceSelection(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + evidence_id: Annotated[str, Field(min_length=1)] + snapshot_id: Annotated[str, Field(min_length=1)] + version: Annotated[str, Field(min_length=1)] + content_digest: Annotated[str, Field(pattern='^sha256:[a-f0-9]{64}$')] + decision_use: DecisionUse + evidence: canonical_audience_evidence.CanonicalAudienceEvidence | None = None + verified_attestation_digests: Annotated[ + list[VerifiedAttestationDigest] | None, Field(min_length=1) + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/canonical_budget_allocation.py b/src/adcp/types/domains/core/canonical_budget_allocation.py index 7e23430cb..1cd3fc12f 100644 --- a/src/adcp/types/domains/core/canonical_budget_allocation.py +++ b/src/adcp/types/domains/core/canonical_budget_allocation.py @@ -1,22 +1,39 @@ -"""Types declared by the AdCP ``core/canonical_budget_allocation`` schema. +# generated by datamodel-codegen: +# filename: core/canonical_budget_allocation.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.canonical_budget_allocation import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import canonical_optimization_goal + + +class CanonicalBudgetAllocation1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + mode: Literal['fixed'] = 'fixed' + + +class CanonicalBudgetAllocation2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + mode: Literal['seller_optimized'] = 'seller_optimized' + optimization_goals: Annotated[ + list[canonical_optimization_goal.CanonicalOptimizationGoal], Field(min_length=1) + ] -from adcp.types.generated_poc.core.canonical_budget_allocation import ( - CanonicalBudgetAllocation, - CanonicalBudgetAllocation1, - CanonicalBudgetAllocation2, -) -# Explicit exports -__all__ = ["CanonicalBudgetAllocation", "CanonicalBudgetAllocation1", "CanonicalBudgetAllocation2"] +CanonicalBudgetAllocation = Annotated[ + CanonicalBudgetAllocation1 | CanonicalBudgetAllocation2, + Field( + description='Canonical fixed or seller-optimized allocation using only canonical optimization goals.', + discriminator='mode', + title='Canonical Budget Allocation', + ), +] diff --git a/src/adcp/types/domains/core/canonical_delivery_forecast.py b/src/adcp/types/domains/core/canonical_delivery_forecast.py index 9d4ea676e..fbf98ddee 100644 --- a/src/adcp/types/domains/core/canonical_delivery_forecast.py +++ b/src/adcp/types/domains/core/canonical_delivery_forecast.py @@ -1,18 +1,34 @@ -"""Types declared by the AdCP ``core/canonical_delivery_forecast`` schema. +# generated by datamodel-codegen: +# filename: core/canonical_delivery_forecast.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.canonical_delivery_forecast import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import demographic_system as demographic_system_1 +from ..enums import forecast_method +from ..enums import forecast_range_unit as forecast_range_unit_1 +from ..enums import reach_unit as reach_unit_1 +from . import canonical_forecast_point +from . import ext as ext_1 -from adcp.types.generated_poc.core.canonical_delivery_forecast import CanonicalDeliveryForecast -# Explicit exports -__all__ = ["CanonicalDeliveryForecast"] +class CanonicalDeliveryForecast(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + points: Annotated[list[canonical_forecast_point.CanonicalForecastPoint], Field(min_length=1)] + forecast_range_unit: forecast_range_unit_1.ForecastRangeUnit | None = None + method: forecast_method.ForecastMethod + currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] + demographic_system: demographic_system_1.DemographicSystem | None = None + demographic: str | None = None + measurement_source: Annotated[str | None, Field(max_length=64, pattern='^[a-z0-9_]+$')] = None + reach_unit: reach_unit_1.ReachUnit | None = None + generated_at: AwareDatetime | None = None + valid_until: AwareDatetime | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/canonical_forecast_point.py b/src/adcp/types/domains/core/canonical_forecast_point.py index 22963fcf9..2513975a5 100644 --- a/src/adcp/types/domains/core/canonical_forecast_point.py +++ b/src/adcp/types/domains/core/canonical_forecast_point.py @@ -1,22 +1,58 @@ -"""Types declared by the AdCP ``core/canonical_forecast_point`` schema. +# generated by datamodel-codegen: +# filename: core/canonical_forecast_point.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.canonical_forecast_point import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Dict -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictFloat -from adcp.types.generated_poc.core.canonical_forecast_point import ( - CanonicalForecastPoint, - Metrics, - Viewability, +from ..enums import availability_status as availability_status_1 +from ..enums import viewability_standard +from . import ( + brand_key, + canonical_forecast_vendor_metric_value, + forecast_point_dimensions, + forecast_range, + forecast_rate_range, ) -# Explicit exports -__all__ = ["CanonicalForecastPoint", "Metrics", "Viewability"] + +class Metrics(AdCPBaseModel): + __pydantic_extra__: Dict[str, forecast_range.ForecastRange] + model_config = ConfigDict( + extra='allow', + ) + coverage_rate: forecast_rate_range.ForecastRateRange | None = None + + + + +class Viewability(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + vendor: brand_key.BrandKey | None = None + measurable_impressions: forecast_range.ForecastRange | None = None + viewable_impressions: forecast_range.ForecastRange | None = None + viewable_rate: forecast_rate_range.ForecastRateRange | None = None + viewed_seconds: forecast_range.ForecastRange | None = None + standard: viewability_standard.ViewabilityStandard | None = None + + +class CanonicalForecastPoint(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + label: Annotated[str | None, Field(max_length=128)] = None + budget: Annotated[StrictFloat | None, Field(ge=0.0)] = None + product_id: str | None = None + dimensions: forecast_point_dimensions.ForecastPointDimensions | None = None + availability_status: availability_status_1.AvailabilityStatus | None = None + metrics: Metrics + viewability: Viewability | None = None + vendor_metric_values: ( + list[canonical_forecast_vendor_metric_value.CanonicalForecastVendorMetricValue] | None + ) = None diff --git a/src/adcp/types/domains/core/canonical_forecast_vendor_metric_value.py b/src/adcp/types/domains/core/canonical_forecast_vendor_metric_value.py index aa4089dea..0132ddf65 100644 --- a/src/adcp/types/domains/core/canonical_forecast_vendor_metric_value.py +++ b/src/adcp/types/domains/core/canonical_forecast_vendor_metric_value.py @@ -1,20 +1,24 @@ -"""Types declared by the AdCP ``core/canonical_forecast_vendor_metric_value`` schema. +# generated by datamodel-codegen: +# filename: core/canonical_forecast_vendor_metric_value.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.canonical_forecast_vendor_metric_value import +from typing import Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import brand_key, forecast_range, vendor_metric_id -from adcp.types.generated_poc.core.canonical_forecast_vendor_metric_value import ( - CanonicalForecastVendorMetricValue, -) -# Explicit exports -__all__ = ["CanonicalForecastVendorMetricValue"] +class CanonicalForecastVendorMetricValue(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + vendor: brand_key.BrandKey + metric_id: vendor_metric_id.VendorMetricId + value: forecast_range.ForecastRange + unit: str | None = None + measurable_impressions: forecast_range.ForecastRange | None = None + breakdown: dict[str, Any] | None = None diff --git a/src/adcp/types/domains/core/canonical_format_kind.py b/src/adcp/types/domains/core/canonical_format_kind.py index 2029fc958..e0039d139 100644 --- a/src/adcp/types/domains/core/canonical_format_kind.py +++ b/src/adcp/types/domains/core/canonical_format_kind.py @@ -1,18 +1,26 @@ -"""Types declared by the AdCP ``core/canonical_format_kind`` schema. +# generated by datamodel-codegen: +# filename: core/canonical_format_kind.json +# timestamp: 2026-09-01T19:37:00+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.canonical_format_kind import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.canonical_format_kind import CanonicalFormatKind +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["CanonicalFormatKind"] +class CanonicalFormatKind(StrEnum): + image = 'image' + html5 = 'html5' + display_tag = 'display_tag' + image_carousel = 'image_carousel' + video_hosted = 'video_hosted' + video_vast = 'video_vast' + audio_hosted = 'audio_hosted' + audio_vast = 'audio_vast' + audio_daast = 'audio_daast' + sponsored_placement = 'sponsored_placement' + native_in_feed = 'native_in_feed' + responsive_creative = 'responsive_creative' + agent_placement = 'agent_placement' + seller_rendered_stateful_display = 'seller_rendered_stateful_display' + coordinated_placements = 'coordinated_placements' + custom = 'custom' diff --git a/src/adcp/types/domains/core/canonical_format_option.py b/src/adcp/types/domains/core/canonical_format_option.py index f418989c7..85983d6f1 100644 --- a/src/adcp/types/domains/core/canonical_format_option.py +++ b/src/adcp/types/domains/core/canonical_format_option.py @@ -1,22 +1,79 @@ -"""Types declared by the AdCP ``core/canonical_format_option`` schema. +# generated by datamodel-codegen: +# filename: core/canonical_format_option.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.canonical_format_option import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import channels +from . import creative_locale_policy, macro_resolution_capability, platform_extension_ref +from . import tracker_execution_contract as tracker_execution_contract_1 + + +class SellerPreference(StrEnum): + preferred = 'preferred' + accepted = 'accepted' + discouraged = 'discouraged' + + +class FormatKind(StrEnum): + image = 'image' + html5 = 'html5' + display_tag = 'display_tag' + image_carousel = 'image_carousel' + video_hosted = 'video_hosted' + video_vast = 'video_vast' + audio_hosted = 'audio_hosted' + audio_vast = 'audio_vast' + audio_daast = 'audio_daast' + sponsored_placement = 'sponsored_placement' + native_in_feed = 'native_in_feed' + responsive_creative = 'responsive_creative' + agent_placement = 'agent_placement' + custom = 'custom' -from adcp.types.generated_poc.core.canonical_format_option import ( - CanonicalFormatOption, - FormatKind, - SellerPreference, -) -# Explicit exports -__all__ = ["CanonicalFormatOption", "FormatKind", "SellerPreference"] +class CanonicalFormatOption(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + format_option_id: Annotated[str | None, Field(min_length=1)] = None + publisher_domain: Annotated[ + str | None, + Field(pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$'), + ] = None + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored production commitment for first-class manifest tracker execution. Presence requires a stable format_option_id. Creative-agent projections MUST strip this authority-bearing field.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='When true, every technical creative-acceptance constraint for this declaration layer is expressed and omitted technical fields mean no constraint. When false or absent, omitted technical fields are undeclared and MUST NOT be inferred. The effective contract is complete only when every applicable declaration layer asserts true.' + ), + ] = None + display_name: Annotated[str | None, Field(min_length=1)] = None + sample_render_url: AnyUrl | None = None + applies_to_channels: Annotated[list[channels.MediaChannel] | None, Field(min_length=1)] = None + seller_preference: SellerPreference | None = None + locale_policy: creative_locale_policy.CreativeLocalePolicy | None = None + canonical_formats_only: StrictBool | None = False + experimental: StrictBool | None = False + format_kind: FormatKind + params: dict[str, Any] + format_shape: Annotated[str | None, Field(min_length=1)] = None + format_schema: platform_extension_ref.PlatformExtensionReference | None = None diff --git a/src/adcp/types/domains/core/canonical_measurement_terms.py b/src/adcp/types/domains/core/canonical_measurement_terms.py index d13cadae4..96c9c47b0 100644 --- a/src/adcp/types/domains/core/canonical_measurement_terms.py +++ b/src/adcp/types/domains/core/canonical_measurement_terms.py @@ -1,22 +1,38 @@ -"""Types declared by the AdCP ``core/canonical_measurement_terms`` schema. +# generated by datamodel-codegen: +# filename: core/canonical_measurement_terms.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.canonical_measurement_terms import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import makegood_remedy +from . import brand_key + + +class BillingMeasurement(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + vendor: brand_key.BrandKey + max_variance_percent: Annotated[StrictFloat | None, Field(ge=0.0, lt=100.0)] = None + measurement_window: Annotated[str | None, Field(min_length=1)] = None + finalization_deadline_hours: Annotated[SchemaInt | None, Field(ge=0)] = None + + +class MakegoodPolicy(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + available_remedies: Annotated[list[makegood_remedy.MakegoodRemedy], Field(min_length=1)] -from adcp.types.generated_poc.core.canonical_measurement_terms import ( - BillingMeasurement, - CanonicalMeasurementTerms, - MakegoodPolicy, -) -# Explicit exports -__all__ = ["BillingMeasurement", "CanonicalMeasurementTerms", "MakegoodPolicy"] +class CanonicalMeasurementTerms(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + billing_measurement: BillingMeasurement | None = None + makegood_policy: MakegoodPolicy | None = None diff --git a/src/adcp/types/domains/core/canonical_media_buy_action.py b/src/adcp/types/domains/core/canonical_media_buy_action.py index d04688e0d..75b00f1c0 100644 --- a/src/adcp/types/domains/core/canonical_media_buy_action.py +++ b/src/adcp/types/domains/core/canonical_media_buy_action.py @@ -1,34 +1,84 @@ -"""Types declared by the AdCP ``core/canonical_media_buy_action`` schema. +# generated by datamodel-codegen: +# filename: core/canonical_media_buy_action.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.canonical_media_buy_action import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import Field + +from .canonical_media_buy_action_fields import CanonicalMediaBuyActionFields + + +class Action(StrEnum): + pause = 'pause' + resume = 'resume' + cancel = 'cancel' + update_name = 'update_name' + increase_budget = 'increase_budget' + decrease_budget = 'decrease_budget' + reallocate_budget = 'reallocate_budget' + update_budget_allocation = 'update_budget_allocation' + update_targeting = 'update_targeting' + update_pacing = 'update_pacing' + update_bidding = 'update_bidding' + update_frequency_caps = 'update_frequency_caps' + update_media_buy_frequency_cap = 'update_media_buy_frequency_cap' + update_catalog_assignments = 'update_catalog_assignments' + update_keywords = 'update_keywords' + update_optimization_goals = 'update_optimization_goals' + update_impression_goal = 'update_impression_goal' + update_spend_target = 'update_spend_target' + update_reporting_webhook = 'update_reporting_webhook' + remove_packages = 'remove_packages' + + +class Action3(StrEnum): + cancel = 'cancel' + extend_flight = 'extend_flight' + shorten_flight = 'shorten_flight' + update_flight_dates = 'update_flight_dates' + increase_budget = 'increase_budget' + decrease_budget = 'decrease_budget' + reallocate_budget = 'reallocate_budget' + update_budget_allocation = 'update_budget_allocation' + update_targeting = 'update_targeting' + update_pacing = 'update_pacing' + update_bidding = 'update_bidding' + update_frequency_caps = 'update_frequency_caps' + update_media_buy_frequency_cap = 'update_media_buy_frequency_cap' + add_packages = 'add_packages' + remove_packages = 'remove_packages' + + +class Action4(StrEnum): + replace_creative = 'replace_creative' + update_creative_assignments = 'update_creative_assignments' + remove_creative = 'remove_creative' + + +class CanonicalMediaBuyAction1(CanonicalMediaBuyActionFields): + task: Literal['control_media_buy'] = 'control_media_buy' + action: Action + + +class CanonicalMediaBuyAction2(CanonicalMediaBuyActionFields): + task: Literal['refine_proposals'] = 'refine_proposals' + action: Action3 + + +class CanonicalMediaBuyAction3(CanonicalMediaBuyActionFields): + task: Literal['sync_creatives'] = 'sync_creatives' + action: Action4 | None = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.canonical_media_buy_action import ( - Action, - Action3, - Action4, - CanonicalMediaBuyAction, - CanonicalMediaBuyAction1, - CanonicalMediaBuyAction2, - CanonicalMediaBuyAction3, -) - -# Explicit exports -__all__ = [ - "Action", - "Action3", - "Action4", - "CanonicalMediaBuyAction", - "CanonicalMediaBuyAction1", - "CanonicalMediaBuyAction2", - "CanonicalMediaBuyAction3", +CanonicalMediaBuyAction = Annotated[ + CanonicalMediaBuyAction1 | CanonicalMediaBuyAction2 | CanonicalMediaBuyAction3, + Field( + description='A currently available MediaBuy action with the compact-lifecycle task an SDK calls to exercise it. Deprecated coarse action values are absent.', + discriminator='task', + title='Canonical Media Buy Action', + ), ] diff --git a/src/adcp/types/domains/core/canonical_media_buy_action_fields.py b/src/adcp/types/domains/core/canonical_media_buy_action_fields.py index 24f22b561..278979e8b 100644 --- a/src/adcp/types/domains/core/canonical_media_buy_action_fields.py +++ b/src/adcp/types/domains/core/canonical_media_buy_action_fields.py @@ -1,21 +1,44 @@ -"""Types declared by the AdCP ``core/canonical_media_buy_action_fields`` schema. +# generated by datamodel-codegen: +# filename: core/canonical_media_buy_action_fields.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.canonical_media_buy_action_fields import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -from adcp.types.generated_poc.core.canonical_media_buy_action_fields import ( - CanonicalMediaBuyActionFields, - Task, +from ..enums import canonical_media_buy_action_mode +from . import ( + applicable_package_id, + media_buy_change_term_id, + media_buy_legacy_terms_ref, + sla_window, ) -# Explicit exports -__all__ = ["CanonicalMediaBuyActionFields", "Task"] + +class Task(StrEnum): + control_media_buy = 'control_media_buy' + refine_proposals = 'refine_proposals' + sync_creatives = 'sync_creatives' + + +class CanonicalMediaBuyActionFields(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + task: Task + action: str + mode: canonical_media_buy_action_mode.CanonicalMediaBuyActionMode + sla: sla_window.SlaWindow | None = None + change_term_id: media_buy_change_term_id.MediaBuyChangeTermId | None = None + terms_ref: media_buy_legacy_terms_ref.MediaBuyTermsReference | None = None + applicable_package_ids: Annotated[ + list[applicable_package_id.ApplicablePackageId] | None, + Field( + description='Exact eligible packages for a package-scoped action; omission means all relevant packages.', + min_length=1, + ), + ] = None diff --git a/src/adcp/types/domains/core/canonical_media_buy_features.py b/src/adcp/types/domains/core/canonical_media_buy_features.py index 4f3158c8f..75711473a 100644 --- a/src/adcp/types/domains/core/canonical_media_buy_features.py +++ b/src/adcp/types/domains/core/canonical_media_buy_features.py @@ -1,18 +1,52 @@ -"""Types declared by the AdCP ``core/canonical_media_buy_features`` schema. +# generated by datamodel-codegen: +# filename: core/canonical_media_buy_features.json +# timestamp: 2026-10-03T15:09:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.canonical_media_buy_features import +from typing import Annotated, Dict -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import bidding_policy_capability -from adcp.types.generated_poc.core.canonical_media_buy_features import CanonicalMediaBuyFeatures -# Explicit exports -__all__ = ["CanonicalMediaBuyFeatures"] +class CanonicalMediaBuyFeatures(AdCPBaseModel): + __pydantic_extra__: Dict[str, StrictBool] + model_config = ConfigDict( + extra='allow', + ) + property_filtering: StrictBool | None = None + catalog_management: StrictBool | None = None + reporting_commitment_snapshots: Annotated[ + StrictBool | None, + Field( + description='Seller preserves the accepted per-purchase reporting contract and exposes it on proposal and MediaBuy readback.' + ), + ] = None + seller_optimized_budget: Annotated[ + StrictBool | None, + Field( + description='Core seller-optimized shared-budget contract: shared total_budget, seller allocation across packages, media-buy pacing, and allocation echo.' + ), + ] = None + seller_optimized_package_budgets: Annotated[ + StrictBool | None, + Field( + description='Package budget caps inside seller-optimized buys; implies seller_optimized_budget.' + ), + ] = None + seller_optimized_min_spend_targets: Annotated[ + StrictBool | None, + Field( + description='Package minimum-spend targets inside seller-optimized buys; implies seller_optimized_budget.' + ), + ] = None + seller_optimized_package_pacing: Annotated[ + StrictBool | None, + Field( + description='Package pacing inside seller-optimized buys; implies seller_optimized_budget.' + ), + ] = None + bidding_policy: bidding_policy_capability.BiddingPolicyCapability | None = None diff --git a/src/adcp/types/domains/core/canonical_metric_qualifier.py b/src/adcp/types/domains/core/canonical_metric_qualifier.py index 8b8258323..c58737f95 100644 --- a/src/adcp/types/domains/core/canonical_metric_qualifier.py +++ b/src/adcp/types/domains/core/canonical_metric_qualifier.py @@ -1,18 +1,25 @@ -"""Types declared by the AdCP ``core/canonical_metric_qualifier`` schema. +# generated by datamodel-codegen: +# filename: core/canonical_metric_qualifier.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.canonical_metric_qualifier import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import attribution_methodology as attribution_methodology_1 +from ..enums import completion_source as completion_source_1 +from ..enums import lift_dimension as lift_dimension_1 +from ..enums import viewability_standard as viewability_standard_1 +from . import duration -from adcp.types.generated_poc.core.canonical_metric_qualifier import CanonicalMetricQualifier -# Explicit exports -__all__ = ["CanonicalMetricQualifier"] +class CanonicalMetricQualifier(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + viewability_standard: viewability_standard_1.ViewabilityStandard | None = None + completion_source: completion_source_1.CompletionSource | None = None + attribution_methodology: attribution_methodology_1.AttributionMethodology | None = None + attribution_window: duration.Duration | None = None + lift_dimension: lift_dimension_1.LiftDimension | None = None diff --git a/src/adcp/types/domains/core/canonical_optimization_goal.py b/src/adcp/types/domains/core/canonical_optimization_goal.py index 5fba6c562..200de5597 100644 --- a/src/adcp/types/domains/core/canonical_optimization_goal.py +++ b/src/adcp/types/domains/core/canonical_optimization_goal.py @@ -1,40 +1,116 @@ -"""Types declared by the AdCP ``core/canonical_optimization_goal`` schema. +# generated by datamodel-codegen: +# filename: core/canonical_optimization_goal.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.canonical_optimization_goal import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictFloat + +from ..enums import event_type as event_type_1 +from ..enums import reach_unit as reach_unit_1 +from ..enums import viewability_standard +from . import attribution_window as attribution_window_1 +from . import brand_key, duration, vendor_metric_id + + +class Metric(StrEnum): + clicks = 'clicks' + views = 'views' + completed_views = 'completed_views' + viewed_seconds = 'viewed_seconds' + viewable_rate = 'viewable_rate' + engagements = 'engagements' + follows = 'follows' + saves = 'saves' + profile_visits = 'profile_visits' + reach = 'reach' + + +class Target(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['threshold_rate'] = 'threshold_rate' + value: Annotated[StrictFloat, Field(gt=0.0)] + + +class Target10(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['maximize_value'] = 'maximize_value' + + +class Target11(Target): + pass + + +class TargetFrequency(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + min: Annotated[SchemaInt | None, Field(ge=1)] = None + max: Annotated[SchemaInt | None, Field(ge=1)] = None + window: duration.Duration + + +class CanonicalOptimizationGoal1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['metric'] = 'metric' + metric: Metric + standard: viewability_standard.ViewabilityStandard | None = None + vendor: brand_key.BrandKey | None = None + reach_unit: reach_unit_1.ReachUnit | None = None + target_frequency: TargetFrequency | None = None + view_duration_seconds: Annotated[StrictFloat | None, Field(gt=0.0)] = None + target: Target | None = None + priority: Annotated[SchemaInt | None, Field(ge=1)] = None + + +class EventSource(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + event_source_id: Annotated[str, Field(min_length=1)] + event_type: event_type_1.EventType + custom_event_name: Annotated[str | None, Field(min_length=1)] = None + value_field: Annotated[str | None, Field(min_length=1)] = None + value_factor: StrictFloat | None = 1 + + +class CanonicalOptimizationGoal2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['event'] = 'event' + event_sources: Annotated[list[EventSource], Field(min_length=1)] + target: Target10 | None = None + attribution_window: attribution_window_1.AttributionWindow | None = None + priority: Annotated[SchemaInt | None, Field(ge=1)] = None + + +class CanonicalOptimizationGoal3(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['vendor_metric'] = 'vendor_metric' + vendor: brand_key.BrandKey + metric_id: vendor_metric_id.VendorMetricId + target: Target11 | None = None + priority: Annotated[SchemaInt | None, Field(ge=1)] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.canonical_optimization_goal import ( - CanonicalOptimizationGoal, - CanonicalOptimizationGoal1, - CanonicalOptimizationGoal2, - CanonicalOptimizationGoal3, - EventSource, - Metric, - Target, - Target10, - Target11, - TargetFrequency, -) - -# Explicit exports -__all__ = [ - "CanonicalOptimizationGoal", - "CanonicalOptimizationGoal1", - "CanonicalOptimizationGoal2", - "CanonicalOptimizationGoal3", - "EventSource", - "Metric", - "Target", - "Target10", - "Target11", - "TargetFrequency", +CanonicalOptimizationGoal = Annotated[ + CanonicalOptimizationGoal1 | CanonicalOptimizationGoal2 | CanonicalOptimizationGoal3, + Field( + description='Canonical 3.2 optimization objective. Monetary execution policy belongs in BiddingPolicy; legacy monetary targets and unbound attention metrics are excluded.', + discriminator='kind', + title='Canonical Optimization Goal', + ), ] diff --git a/src/adcp/types/domains/core/canonical_performance_standard.py b/src/adcp/types/domains/core/canonical_performance_standard.py index 0b2b063be..193960353 100644 --- a/src/adcp/types/domains/core/canonical_performance_standard.py +++ b/src/adcp/types/domains/core/canonical_performance_standard.py @@ -1,20 +1,23 @@ -"""Types declared by the AdCP ``core/canonical_performance_standard`` schema. +# generated by datamodel-codegen: +# filename: core/canonical_performance_standard.json +# timestamp: 2026-10-03T15:09:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.canonical_performance_standard import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import performance_standard_metric, viewability_standard +from . import brand_key -from adcp.types.generated_poc.core.canonical_performance_standard import ( - CanonicalPerformanceStandard, -) -# Explicit exports -__all__ = ["CanonicalPerformanceStandard"] +class CanonicalPerformanceStandard(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + metric: performance_standard_metric.PerformanceStandardMetric + threshold: Annotated[StrictFloat, Field(ge=0.0, le=1.0)] + standard: viewability_standard.ViewabilityStandard | None = None + vendor: brand_key.BrandKey diff --git a/src/adcp/types/domains/core/canonical_placement.py b/src/adcp/types/domains/core/canonical_placement.py index fe48214be..738eabc38 100644 --- a/src/adcp/types/domains/core/canonical_placement.py +++ b/src/adcp/types/domains/core/canonical_placement.py @@ -1,36 +1,179 @@ -"""Types declared by the AdCP ``core/canonical_placement`` schema. +# generated by datamodel-codegen: +# filename: core/canonical_placement.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.canonical_placement import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field -from adcp.types.generated_poc.core.canonical_placement import ( - CanonicalDoohPlacementAttributes, - CanonicalDoohScreenResolution, - CanonicalProductPlacement, - CanonicalProductPlacement1, - CanonicalProductPlacement2, - Identifier, - Kind, - Mode, +from ..enums import ( + audio_distribution_type, + dooh_motion_type, + identifier_types, + social_placement_surface, + sponsored_placement_type, + video_placement_type, ) +from . import canonical_format_option, seller_agent_ref + + +class Kind(StrEnum): + publisher_ref = 'publisher_ref' + seller_inline = 'seller_inline' + + +class Mode(StrEnum): + targetable = 'targetable' + included = 'included' + + +class CanonicalDoohScreenResolution(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + width: Annotated[SchemaInt, Field(ge=1)] + height: Annotated[SchemaInt, Field(ge=1)] + + +class Identifier(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + type: identifier_types.PropertyIdentifierTypes + value: str + + +class CanonicalDoohPlacementAttributes(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + slot_duration_seconds: Annotated[ + SchemaInt | None, + Field( + description='Scheduled duration of one ad slot in seconds; not the creative-duration contract.', + ge=1, + ), + ] = None + loop_duration_seconds: Annotated[ + SchemaInt | None, + Field( + description='Duration of the full ad loop rotation in seconds and the canonical source for loop duration.', + ge=1, + ), + ] = None + screen_resolution: CanonicalDoohScreenResolution | None = None + motion: Annotated[ + dooh_motion_type.DoohMotionType | None, + Field( + description='Physical motion capability of a visual DOOH screen, not an accepted-format declaration. Omit for audio-only placements.' + ), + ] = None + + +class CanonicalProductPlacement1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['publisher_ref'] = 'publisher_ref' + placement_id: Annotated[str, Field(min_length=1)] + publisher_domain: Annotated[ + str, + Field( + description='For publisher_ref, the adagents.json publisher namespace. For seller_inline, optional inventory-publisher attribution only.', + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] + seller_agent: Annotated[ + seller_agent_ref.SellerAgentReference | None, + Field( + description='Defining sales agent for seller_inline identity. Recommended for new declarations; optional only to preserve legacy product-context placements.' + ), + ] = None + name: Annotated[str | None, Field(min_length=1)] = None + description: str | None = None + mode: Mode + tags: list[str] | None = None + format_options: Annotated[ + list[canonical_format_option.CanonicalFormatOption] | None, Field(min_length=1) + ] = None + video_placement_types: Annotated[ + list[video_placement_type.VideoPlacementType] | None, Field(min_length=1) + ] = None + audio_distribution_types: Annotated[ + list[audio_distribution_type.AudioDistributionType] | None, Field(min_length=1) + ] = None + sponsored_placement_types: Annotated[ + list[sponsored_placement_type.SponsoredPlacementType] | None, Field(min_length=1) + ] = None + social_placement_surfaces: Annotated[ + list[social_placement_surface.SocialPlacementSurface] | None, Field(min_length=1) + ] = None + identifiers: Annotated[ + list[Identifier] | None, + Field( + description='Optional external inventory identifiers for this placement. Externally governed values should be authority-prefixed; seller-local values are scoped by the surrounding publisher namespace. For publisher_ref placements, the effective set is the union of publisher and product declarations, de-duplicated by exact (type, value).', + min_length=1, + ), + ] = None + dooh_placement_attributes: CanonicalDoohPlacementAttributes | None = None + + +class CanonicalProductPlacement2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['seller_inline'] = 'seller_inline' + placement_id: Annotated[str, Field(min_length=1)] + publisher_domain: Annotated[ + str | None, + Field( + description='For publisher_ref, the adagents.json publisher namespace. For seller_inline, optional inventory-publisher attribution only.', + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] = None + seller_agent: Annotated[ + seller_agent_ref.SellerAgentReference | None, + Field( + description='Defining sales agent for seller_inline identity. Recommended for new declarations; optional only to preserve legacy product-context placements.' + ), + ] = None + name: Annotated[str, Field(min_length=1)] + description: str | None = None + mode: Mode + tags: list[str] | None = None + format_options: Annotated[ + list[canonical_format_option.CanonicalFormatOption] | None, Field(min_length=1) + ] = None + video_placement_types: Annotated[ + list[video_placement_type.VideoPlacementType] | None, Field(min_length=1) + ] = None + audio_distribution_types: Annotated[ + list[audio_distribution_type.AudioDistributionType] | None, Field(min_length=1) + ] = None + sponsored_placement_types: Annotated[ + list[sponsored_placement_type.SponsoredPlacementType] | None, Field(min_length=1) + ] = None + social_placement_surfaces: Annotated[ + list[social_placement_surface.SocialPlacementSurface] | None, Field(min_length=1) + ] = None + identifiers: Annotated[ + list[Identifier] | None, + Field( + description='Optional external inventory identifiers for this placement. Externally governed values should be authority-prefixed; seller-local values are scoped by the surrounding publisher namespace. For publisher_ref placements, the effective set is the union of publisher and product declarations, de-duplicated by exact (type, value).', + min_length=1, + ), + ] = None + dooh_placement_attributes: CanonicalDoohPlacementAttributes | None = None + -# Explicit exports -__all__ = [ - "CanonicalDoohPlacementAttributes", - "CanonicalDoohScreenResolution", - "CanonicalProductPlacement", - "CanonicalProductPlacement1", - "CanonicalProductPlacement2", - "Identifier", - "Kind", - "Mode", +CanonicalProductPlacement = Annotated[ + CanonicalProductPlacement1 | CanonicalProductPlacement2, + Field( + description='Compact product placement with canonical format narrowing. Publisher references carry their publisher authority. New seller-inline placements SHOULD carry seller_agent as their defining authority; legacy seller-inline placements without it remain valid in product context.', + title='Canonical Product Placement', + ), ] diff --git a/src/adcp/types/domains/core/canonical_pricing_option.py b/src/adcp/types/domains/core/canonical_pricing_option.py index 2074dcce9..09fed7ff6 100644 --- a/src/adcp/types/domains/core/canonical_pricing_option.py +++ b/src/adcp/types/domains/core/canonical_pricing_option.py @@ -1,21 +1,50 @@ -"""Types declared by the AdCP ``core/canonical_pricing_option`` schema. +# generated by datamodel-codegen: +# filename: core/canonical_pricing_option.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.canonical_pricing_option import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import adjustment_kind +from ..enums import event_type as event_type_1 +from ..pricing_options import price_breakdown as price_breakdown_1 +from ..pricing_options import price_guidance as price_guidance_1 + + +class PricingModel(StrEnum): + cpm = 'cpm' + vcpm = 'vcpm' + cpc = 'cpc' + cpcv = 'cpcv' + cpv = 'cpv' + cpp = 'cpp' + cpa = 'cpa' + revenue_share = 'revenue_share' + flat_rate = 'flat_rate' + time = 'time' -from adcp.types.generated_poc.core.canonical_pricing_option import ( - CanonicalPricingOption, - PricingModel, -) -# Explicit exports -__all__ = ["CanonicalPricingOption", "PricingModel"] +class CanonicalPricingOption(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + pricing_option_id: Annotated[str, Field(min_length=1)] + pricing_model: PricingModel + currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] + fixed_price: Annotated[StrictFloat | None, Field(ge=0.0)] = None + floor_price: Annotated[StrictFloat | None, Field(ge=0.0)] = None + price_guidance: price_guidance_1.PriceGuidance | None = None + min_spend_per_package: Annotated[StrictFloat | None, Field(ge=0.0)] = None + price_breakdown: price_breakdown_1.PriceBreakdown | None = None + eligible_adjustments: list[adjustment_kind.PriceAdjustmentKind] | None = None + parameters: dict[str, Any] | None = None + event_type: event_type_1.EventType | None = None + custom_event_name: Annotated[str | None, Field(min_length=1)] = None + event_source_id: Annotated[str | None, Field(min_length=1)] = None + commission_rate: Annotated[StrictFloat | None, Field(gt=0.0, le=1.0)] = None + commission_basis_description: Annotated[str | None, Field(max_length=1000, min_length=1)] = None diff --git a/src/adcp/types/domains/core/canonical_product.py b/src/adcp/types/domains/core/canonical_product.py index ad214da16..68373692c 100644 --- a/src/adcp/types/domains/core/canonical_product.py +++ b/src/adcp/types/domains/core/canonical_product.py @@ -1,44 +1,290 @@ -"""Types declared by the AdCP ``core/canonical_product`` schema. +# generated by datamodel-codegen: +# filename: core/canonical_product.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.canonical_product import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel, SchemaInt +from adcp.types._scalar import ScalarStr +from pydantic import AwareDatetime, ConfigDict, Field, StrictBool -from adcp.types.generated_poc.core.canonical_product import ( - CanonicalProduct, - CatalogMatch, - MatchedGtin, - PublisherDomain, - PublisherProperty, - PublisherProperty1, - PublisherProperty2, - PublisherProperty3, - PublisherProperty4, - PublisherProperty5, - PublisherProperty6, - PublisherProperty7, +from ..enums import audio_distribution_type, catalog_type +from ..enums import channels as channels_1 +from ..enums import delivery_type as delivery_type_1 +from ..enums import exclusivity as exclusivity_1 +from ..enums import social_placement_surface, sponsored_placement_type, video_placement_type +from . import acceptance_policy_profile_ids as acceptance_policy_profile_ids_1 +from . import ( + canonical_audience_evidence, + canonical_audience_evidence_selection, + canonical_delivery_forecast, + canonical_format_option, + canonical_measurement_terms, + canonical_performance_standard, + canonical_placement, + canonical_pricing_option, + canonical_product_action, + canonical_reporting_capabilities, + collection_selector, + demographic_targeting_capability, +) +from . import ext as ext_1 +from . import inventory_list_application +from . import media_buy_support as media_buy_support_1 +from . import ( + product_execution_requirement, + product_identity, + product_targeting_resolution, + property_id, + property_tag, ) +from . import signal_targeting_rules as signal_targeting_rules_1 +from . import targeting_overlay_support + + +class PublisherDomain(ScalarStr): + __slots__ = () + _constraints = { + 'pattern': '^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + } + + +class PublisherProperty1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + publisher_domain: Annotated[ + str | None, + Field( + description="Domain where publisher's adagents.json is hosted (e.g., 'cnn.com'). XOR with `publisher_domains` — exactly one MUST be present on each `publisher_properties[]` entry; both-present and neither-present both fail validation.", + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] = None + publisher_domains: Annotated[ + list[PublisherDomain] | None, + Field( + description="Compact form for fanning the same selector across many publishers (e.g., a managed network listing every publisher it represents). Each entry is the domain where that publisher's adagents.json is hosted. Each listed domain MUST be canonicalized to lowercase (the `pattern` already rejects uppercase). Mutually exclusive with `publisher_domain`. Each listed domain counts as explicitly scoped for the `managerdomain` fallback safety rule.", + min_length=1, + ), + ] = None + selection_type: Annotated[ + Literal['all'], + Field( + description='Discriminator indicating all properties from each addressed publisher are included' + ), + ] = 'all' + + +class PublisherProperty4(AdCPBaseModel): + pass + + +class PublisherProperty5(PublisherProperty1, PublisherProperty4): + pass + + +class MatchedGtin(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[0-9]{8,14}$'} + + +class CatalogMatch(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + matched_gtins: list[MatchedGtin] | None = None + matched_ids: list[str] | None = None + matched_count: Annotated[SchemaInt | None, Field(ge=0)] = None + submitted_count: Annotated[SchemaInt, Field(ge=0)] + + +class PublisherProperty2(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + publisher_domain: Annotated[ + str, + Field( + description="Domain where publisher's adagents.json is hosted (e.g., 'cnn.com').", + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] + selection_type: Annotated[ + Literal['by_id'], + Field(description='Discriminator indicating selection by specific property IDs'), + ] = 'by_id' + property_ids: Annotated[ + list[property_id.PropertyId], + Field(description="Specific property IDs from the publisher's adagents.json", min_length=1), + ] + + +class PublisherProperty3(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + publisher_domain: Annotated[ + str | None, + Field( + description="Domain where publisher's adagents.json is hosted (e.g., 'cnn.com'). XOR with `publisher_domains` — exactly one MUST be present on each `publisher_properties[]` entry; both-present and neither-present both fail validation.", + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] = None + publisher_domains: Annotated[ + list[PublisherDomain] | None, + Field( + description="Compact form for fanning the same tag predicate across many publishers (canonical managed-network shape). Each entry is the domain where that publisher's adagents.json is hosted. Each listed domain MUST be canonicalized to lowercase (the `pattern` already rejects uppercase). Mutually exclusive with `publisher_domain`. Each listed domain counts as explicitly scoped for the `managerdomain` fallback safety rule.", + min_length=1, + ), + ] = None + selection_type: Annotated[ + Literal['by_tag'], Field(description='Discriminator indicating selection by property tags') + ] = 'by_tag' + property_tags: Annotated[ + list[property_tag.PropertyTag], + Field( + description="Property tags resolved against each addressed publisher's adagents.json, OR against the parent file's top-level `properties[]` when those properties carry a `publisher_domain` matching the selector. Selector covers all properties carrying any of these tags.", + min_length=1, + ), + ] + + +class PublisherProperty6(PublisherProperty2, PublisherProperty4): + pass + + +class PublisherProperty7(PublisherProperty3, PublisherProperty4): + pass + + +PublisherProperty = PublisherProperty5 | PublisherProperty6 | PublisherProperty7 + -# Explicit exports -__all__ = [ - "CanonicalProduct", - "CatalogMatch", - "MatchedGtin", - "PublisherDomain", - "PublisherProperty", - "PublisherProperty1", - "PublisherProperty2", - "PublisherProperty3", - "PublisherProperty4", - "PublisherProperty5", - "PublisherProperty6", - "PublisherProperty7", -] +class CanonicalProduct(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + product_id: Annotated[str, Field(min_length=1)] + name: Annotated[str, Field(min_length=1)] + description: str | None = None + publisher_properties: Annotated[list[PublisherProperty] | None, Field(min_length=1)] = None + channels: list[channels_1.MediaChannel] | None = None + video_placement_types: Annotated[ + list[video_placement_type.VideoPlacementType] | None, Field(min_length=1) + ] = None + audio_distribution_types: Annotated[ + list[audio_distribution_type.AudioDistributionType] | None, Field(min_length=1) + ] = None + sponsored_placement_types: Annotated[ + list[sponsored_placement_type.SponsoredPlacementType] | None, Field(min_length=1) + ] = None + social_placement_surfaces: Annotated[ + list[social_placement_surface.SocialPlacementSurface] | None, Field(min_length=1) + ] = None + format_options: Annotated[ + list[canonical_format_option.CanonicalFormatOption] | None, Field(min_length=1) + ] = None + placements: list[canonical_placement.CanonicalProductPlacement] | None = None + collections: Annotated[ + list[collection_selector.CollectionSelector] | None, + Field( + description='Collections available in this product, each referencing collections declared in an adagents.json by domain and explicit collection_ids. The domain-only bulk-grant selector form is for authorization scoping, not product composition. A selected-mode collection_selection names collections from this set.', + min_length=1, + ), + ] = None + collection_targeting_allowed: Annotated[ + StrictBool | None, + Field( + description="Whether buyers can select a subset of this product's collections through targeting_overlay.collection_list or targeting_overlay.collection_selection. When false, the product is a fixed bundle (a collection_selection that exactly restates the complete bundle remains an inherent match)." + ), + ] = False + delivery_type: delivery_type_1.DeliveryType | None = None + exclusivity: exclusivity_1.Exclusivity | None = None + pricing_options: Annotated[ + list[canonical_pricing_option.CanonicalPricingOption] | None, Field(min_length=1) + ] = None + forecast: canonical_delivery_forecast.CanonicalDeliveryForecast | None = None + reporting_capabilities: ( + canonical_reporting_capabilities.CanonicalReportingCapabilities | None + ) = None + measurement_terms: Annotated[ + canonical_measurement_terms.CanonicalMeasurementTerms | None, + Field( + description='Default billing measurement and makegood terms inherited by a direct purchase unless a negotiated proposal replaces them.' + ), + ] = None + performance_standards: Annotated[ + list[canonical_performance_standard.CanonicalPerformanceStandard] | None, + Field( + description='Default performance thresholds and measurement vendors inherited by a direct purchase.', + min_length=1, + ), + ] = None + catalog_types: Annotated[list[catalog_type.CatalogType] | None, Field(min_length=1)] = None + signal_targeting_allowed: StrictBool | None = None + signal_targeting_rules: signal_targeting_rules_1.SignalTargetingRules | None = None + demographic_targeting: ( + demographic_targeting_capability.DemographicTargetingCapability | None + ) = None + overlay_support: Annotated[ + targeting_overlay_support.TargetingOverlaySupport | None, + Field( + description='Binding product-scoped targeting dimensions the buyer may set independently on a package after discovery.' + ), + ] = None + media_buy_support: Annotated[ + media_buy_support_1.ProductMediaBuySupport | None, + Field(description='Binding product participation in shared MediaBuy-level controls.'), + ] = None + identity: Annotated[ + product_identity.ProductIdentity | None, + Field( + description='Experimental product-scoped identity and reach-measurement facts. See Product.identity for the cross-object delivery and frequency-cap rules.' + ), + ] = None + execution_requirements: Annotated[ + list[product_execution_requirement.ProductExecutionRequirement] | None, + Field( + description='Experimental account resources a package on this product needs before it can be created. See Product.execution_requirements for completeness, binding, and rejection rules.', + min_length=1, + ), + ] = None + audience_evidence: Annotated[ + list[canonical_audience_evidence.CanonicalAudienceEvidence] | None, Field(min_length=1) + ] = None + audience_evidence_selections: Annotated[ + list[canonical_audience_evidence_selection.CanonicalAudienceEvidenceSelection] | None, + Field( + description='Exact evidence snapshots that affected eligibility or ranking. Returned whenever evidence requirements affected the result, even if not requested explicitly.', + min_length=1, + ), + ] = None + max_optimization_goals: Annotated[SchemaInt | None, Field(ge=0)] = None + catalog_match: CatalogMatch | None = None + list_applications: Annotated[ + list[inventory_list_application.InventoryListApplication] | None, + Field( + description='Product-scoped receipts for every effective property- or collection-list targeting reference. Sellers MUST return one receipt per application regardless of response field projection; exclusion applications receive a receipt even when summary.matched is zero, while zero matches for any inclusion application make the product ineligible and it is not returned. Each receipt uses the same pre-list product inventory baseline; pricing and forecast reflect inventory remaining after all effective lists are composed.', + min_length=1, + ), + ] = None + brief_relevance: str | None = None + targeting_resolution: Annotated[ + product_targeting_resolution.ProductTargetingResolution | None, + Field( + description='Discovery-time targeting resolution bound to this configured product. modifications sparsely disclose product-specific differences from criteria.targeting_overlay; absence means exact acceptance of the structured overlay. Request-level brief interpretation is returned once on the response-root targeting_resolution. Selecting product_id accepts the disclosed modifications; pricing and forecast MUST reflect them. Requires expires_at.' + ), + ] = None + expires_at: Annotated[ + AwareDatetime | None, + Field( + description='Expiration of a request-specific configured offer. Canonical products have no is_custom flag; expires_at is what marks an offer as request-specific. After it, the buyer rediscovers.' + ), + ] = None + allowed_actions: list[canonical_product_action.CanonicalProductAction] | None = None + acceptance_policy_profile_ids: ( + acceptance_policy_profile_ids_1.AcceptancePolicyProfileIds | None + ) = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/canonical_product_action.py b/src/adcp/types/domains/core/canonical_product_action.py index 1308e7bdd..c15fdeb61 100644 --- a/src/adcp/types/domains/core/canonical_product_action.py +++ b/src/adcp/types/domains/core/canonical_product_action.py @@ -1,18 +1,40 @@ -"""Types declared by the AdCP ``core/canonical_product_action`` schema. +# generated by datamodel-codegen: +# filename: core/canonical_product_action.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.canonical_product_action import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import canonical_media_buy_action, canonical_media_buy_action_mode, media_buy_status +from ..media_buy import change_term_constraints +from . import sla_window -from adcp.types.generated_poc.core.canonical_product_action import CanonicalProductAction -# Explicit exports -__all__ = ["CanonicalProductAction"] +class CanonicalProductAction(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + action: canonical_media_buy_action.CanonicalMediaBuyActionName + modes: Annotated[ + list[canonical_media_buy_action_mode.CanonicalMediaBuyActionMode], Field(min_length=1) + ] + allowed_statuses: Annotated[ + list[media_buy_status.MediaBuyStatus] | None, Field(min_length=1) + ] = None + sla: sla_window.SlaWindow | None = None + constraints: Annotated[ + change_term_constraints.MediaBuyChangeTermConstraints | None, + Field( + description='Advisory machine-readable bounds for product selection; proposal change terms restate binding bounds.' + ), + ] = None + terms_ref: Annotated[ + str | None, + Field( + description='Optional advisory pointer to published commercial terms. It is not a proposal change-term identity.' + ), + ] = None diff --git a/src/adcp/types/domains/core/canonical_projection_ref.py b/src/adcp/types/domains/core/canonical_projection_ref.py index 4186925de..ba7572552 100644 --- a/src/adcp/types/domains/core/canonical_projection_ref.py +++ b/src/adcp/types/domains/core/canonical_projection_ref.py @@ -1,21 +1,47 @@ -"""Types declared by the AdCP ``core/canonical_projection_ref`` schema. +# generated by datamodel-codegen: +# filename: core/canonical_projection_ref.json +# timestamp: 2026-10-05T13:49:50+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.canonical_projection_ref import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import canonical_projection_slot_override + + +class AssetSource(StrEnum): + buyer_uploaded = 'buyer_uploaded' + publisher_host_recorded = 'publisher_host_recorded' + seller_pre_rendered_from_brief = 'seller_pre_rendered_from_brief' + seller_human_designed = 'seller_human_designed' + agent_synthesized = 'agent_synthesized' + publisher_owned_reference = 'publisher_owned_reference' -from adcp.types.generated_poc.core.canonical_projection_ref import ( - AssetSource, - CanonicalProjectionReference, -) -# Explicit exports -__all__ = ["AssetSource", "CanonicalProjectionReference"] +class CanonicalProjectionReference(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + kind: Annotated[ + str, + Field( + description='The v2 canonical-format-kind this v1 format projects to (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_vast`, `audio_daast`, `sponsored_placement`, `native_in_feed`, `responsive_creative`, `agent_placement`, `seller_rendered_stateful_display`, `coordinated_placements`, or `custom`).' + ), + ] + asset_source: Annotated[ + AssetSource | None, + Field( + description="Where the rendered asset bytes come from on the projected v2 declaration. Default (when omitted) is `buyer_uploaded` — the canonical's default. Set explicitly when the v1 named format doesn't follow that default. Required for generative entries (`agent_synthesized` or `seller_pre_rendered_from_brief`) because their asset shape doesn't carry image/video/audio bytes, and for published-post reference entries (`publisher_owned_reference`) because their asset shape carries a post reference rather than uploaded bytes. Projection without this hint produces a lossy v2 declaration that claims buyer-uploaded bytes." + ), + ] = None + slots_override: Annotated[ + list[canonical_projection_slot_override.CanonicalProjectionSlotOverride] | None, + Field( + description="When the v1 named format's slot shape differs from the canonical's default slots, this carries the override that the projected v2 declaration's `params.slots[]` should use. REPLACES (does not merge with) the canonical's default slots — projection-time semantics. The slot vocabulary follows `asset-group-vocabulary.json`. Asset IDs in the v1 format's `assets[*]` MUST resolve (directly or via the vocabulary's aliases) to the `asset_group_id` values declared here.", + min_length=1, + ), + ] = None diff --git a/src/adcp/types/domains/core/canonical_projection_slot_override.py b/src/adcp/types/domains/core/canonical_projection_slot_override.py index 576c57a02..25df5ee65 100644 --- a/src/adcp/types/domains/core/canonical_projection_slot_override.py +++ b/src/adcp/types/domains/core/canonical_projection_slot_override.py @@ -1,20 +1,41 @@ -"""Types declared by the AdCP ``core/canonical_projection_slot_override`` schema. +# generated by datamodel-codegen: +# filename: core/canonical_projection_slot_override.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.canonical_projection_slot_override import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictBool -from adcp.types.generated_poc.core.canonical_projection_slot_override import ( - CanonicalProjectionSlotOverride, -) -# Explicit exports -__all__ = ["CanonicalProjectionSlotOverride"] +class CanonicalProjectionSlotOverride(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_group_id: Annotated[ + str, + Field( + description='Asset group identifier from `asset-group-vocabulary.json` (e.g., `generation_prompt`, `creative_brief`, `image_main`, `video_main`).' + ), + ] + asset_type: Annotated[ + str, + Field( + description='Asset type — `image`, `video`, `audio`, `text`, `html`, `javascript`, `url`, `zip`, `brief`, `catalog`, `published_post`, or another canonical slot asset type.' + ), + ] + required: Annotated[ + StrictBool | None, + Field(description='Whether the slot is required in the projected declaration.'), + ] = False + max_chars: Annotated[ + SchemaInt | None, Field(description='Max character count for text slots.', ge=1) + ] = None + consumed_for_production: Annotated[ + StrictBool | None, + Field( + description="When false, slot is for moderation/review only and is NOT consumed by the seller's renderer (e.g., a brand-safety brief that informs review but doesn't appear in the rendered ad)." + ), + ] = True diff --git a/src/adcp/types/domains/core/canonical_proposal.py b/src/adcp/types/domains/core/canonical_proposal.py index 89e01c5de..8e085820c 100644 --- a/src/adcp/types/domains/core/canonical_proposal.py +++ b/src/adcp/types/domains/core/canonical_proposal.py @@ -1,22 +1,96 @@ -"""Types declared by the AdCP ``core/canonical_proposal`` schema. +# generated by datamodel-codegen: +# filename: core/canonical_proposal.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.canonical_proposal import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import proposal_status as proposal_status_1 +from ..media_buy import commercial_terms as commercial_terms_1 +from . import canonical_delivery_forecast +from . import insertion_order as insertion_order_1 + + +class ProposalKind(StrEnum): + new_media_buy = 'new_media_buy' + media_buy_update = 'media_buy_update' + media_buy_cancellation = 'media_buy_cancellation' + + +class TotalBudgetGuidance(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + min: Annotated[StrictFloat | None, Field(ge=0.0)] = None + recommended: Annotated[StrictFloat | None, Field(ge=0.0)] = None + max: Annotated[StrictFloat | None, Field(ge=0.0)] = None + currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] -from adcp.types.generated_poc.core.canonical_proposal import ( - CanonicalProposal, - ProposalKind, - TotalBudgetGuidance, -) -# Explicit exports -__all__ = ["CanonicalProposal", "ProposalKind", "TotalBudgetGuidance"] +class CanonicalProposal(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + proposal_id: Annotated[str, Field(max_length=255, min_length=1)] + proposal_kind: ProposalKind + parent_proposal_id: Annotated[ + str | None, + Field( + description="Immediate predecessor this snapshot was forked from. Every proposal produced by refine_proposals carries it, equal to the request's source proposal_id, so negotiation lineage is reconstructible from proposals alone.", + max_length=255, + min_length=1, + ), + ] = None + media_buy_id: Annotated[str | None, Field(min_length=1)] = None + opportunity_id: Annotated[ + str | None, + Field( + description='Buyer planning cycle associated with this proposal. Revisions inherit it; it does not participate in proposal identity.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] = None + base_media_buy_revision: Annotated[SchemaInt | None, Field(ge=1)] = None + proposal_status: Annotated[ + proposal_status_1.ProposalStatus, + Field( + description='draft is indicative and unreserved; committed has firm terms with inventory reserved until expires_at; accepted is the historical snapshot attached to a MediaBuy.' + ), + ] + accepted_at: AwareDatetime | None = None + expires_at: Annotated[ + AwareDatetime | None, + Field( + description='For a draft, the indicative-terms freshness deadline. For a committed proposal, the inventory-hold deadline.' + ), + ] = None + name: Annotated[str, Field(max_length=500, min_length=1)] + description: Annotated[str | None, Field(max_length=2000)] = None + brief_alignment: Annotated[str | None, Field(max_length=2000)] = None + commercial_terms: commercial_terms_1.CommercialTerms + terms_digest: Annotated[ + str, + Field( + description='Base64url SHA-256 digest of the RFC 8785 JCS serialization of commercial_terms, prefixed with sha256:.', + pattern='^sha256:[A-Za-z0-9_-]{43}$', + ), + ] + insertion_order: insertion_order_1.InsertionOrder | None = None + total_budget_guidance: Annotated[ + TotalBudgetGuidance | None, + Field( + description="Optional budget guidance for this proposal — the planning answer to criteria.outcome_target and to open-budget briefs. commercial_terms_1.total_budget remains the concrete figure the plan is priced at; this band expresses the seller's recommended range around it. When criteria.outcome_target carries cost_per, the cost answer is commercial_terms_1.bidding.cost_per and this band's currency equals cost_per.currency." + ), + ] = None + forecast: Annotated[ + canonical_delivery_forecast.CanonicalDeliveryForecast | None, + Field( + description="Aggregate forecasted delivery for the proposal. For outcome_target requests, points carry the goal's metric or event key in metrics; with cost_per, that is the goal volume planned under the commercial_terms_1.bidding policy, and currency equals cost_per.currency." + ), + ] = None diff --git a/src/adcp/types/domains/core/canonical_reporting_capabilities.py b/src/adcp/types/domains/core/canonical_reporting_capabilities.py index fb7698b7f..5130f4082 100644 --- a/src/adcp/types/domains/core/canonical_reporting_capabilities.py +++ b/src/adcp/types/domains/core/canonical_reporting_capabilities.py @@ -1,22 +1,83 @@ -"""Types declared by the AdCP ``core/canonical_reporting_capabilities`` schema. +# generated by datamodel-codegen: +# filename: core/canonical_reporting_capabilities.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.canonical_reporting_capabilities import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictBool -from adcp.types.generated_poc.core.canonical_reporting_capabilities import ( - CanonicalReportingCapabilities, - DateRangeSupport, - VendorMetric, +from ..enums import available_metric, reporting_frequency +from . import ( + brand_key, + demographic_reporting_capability, + geo_breakdown_support, + measurement_window, + reporting_delivery_offering_id, + spot_reporting_capability, + vendor_metric_id, ) -# Explicit exports -__all__ = ["CanonicalReportingCapabilities", "DateRangeSupport", "VendorMetric"] + +class DateRangeSupport(StrEnum): + date_range = 'date_range' + lifetime_only = 'lifetime_only' + + +class VendorMetric(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + vendor: brand_key.BrandKey + metric_id: vendor_metric_id.VendorMetricId + + +class CanonicalReportingCapabilities(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + available_reporting_frequencies: Annotated[ + list[reporting_frequency.ReportingFrequency], Field(min_length=1) + ] + expected_delay_minutes: Annotated[SchemaInt, Field(ge=0)] + timezone: Annotated[ + str, + Field( + description='Explicit reporting-period timezone for this product. It may equal Account.timezone or differ when the upstream platform reports on a separate boundary.' + ), + ] + supports_webhooks: StrictBool + reporting_delivery_offering_ids: Annotated[ + list[reporting_delivery_offering_id.ReportingDeliveryOfferingId] | None, + Field( + description='Product-scoped subset of get_adcp_capabilities.media_buy.reporting_delivery.offerings[].offering_id that packages using this product can satisfy. This binds seller-wide managed-delivery offerings to product/package eligibility. An empty array explicitly declares no managed offering; absence means product-level applicability is unknown and MUST NOT be inferred from the seller-wide list. Account, seat, credential, or provider constraints may narrow support further during sync_accounts validation.' + ), + ] = None + available_metrics: list[available_metric.AvailableMetric] + vendor_metrics: list[VendorMetric] | None = None + supports_creative_breakdown: StrictBool | None = None + supports_format_breakdown: StrictBool | None = None + supports_keyword_breakdown: StrictBool | None = None + supports_geo_breakdown: geo_breakdown_support.GeographicBreakdownSupport | None = None + supports_device_type_breakdown: StrictBool | None = None + supports_device_platform_breakdown: StrictBool | None = None + supports_audience_breakdown: StrictBool | None = None + supports_demographic_breakdown: ( + demographic_reporting_capability.DemographicReportingCapability | None + ) = None + supports_placement_breakdown: StrictBool | None = None + supports_property_breakdown: StrictBool | None = None + supports_collection_breakdown: StrictBool | None = None + supports_installment_breakdown: StrictBool | None = None + supports_collection_property_breakdown: StrictBool | None = None + supports_installment_property_breakdown: StrictBool | None = None + supports_placement_property_breakdown: StrictBool | None = None + supports_spot_breakdown: spot_reporting_capability.SpotReportingCapability | None = None + date_range_support: DateRangeSupport + windowed_pull_granularities: list[reporting_frequency.ReportingFrequency] | None = None + measurement_windows: Annotated[ + list[measurement_window.MeasurementWindow] | None, Field(min_length=1) + ] = None diff --git a/src/adcp/types/domains/core/canonical_reporting_commitment.py b/src/adcp/types/domains/core/canonical_reporting_commitment.py index deb90157c..e7d121d35 100644 --- a/src/adcp/types/domains/core/canonical_reporting_commitment.py +++ b/src/adcp/types/domains/core/canonical_reporting_commitment.py @@ -1,26 +1,44 @@ -"""Types declared by the AdCP ``core/canonical_reporting_commitment`` schema. +# generated by datamodel-codegen: +# filename: core/canonical_reporting_commitment.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.canonical_reporting_commitment import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field + +from ..enums import available_metric +from . import brand_key, canonical_metric_qualifier, vendor_metric_id + + +class CanonicalReportingCommitment1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + scope: Literal['standard'] = 'standard' + metric_id: available_metric.AvailableMetric + qualifier: canonical_metric_qualifier.CanonicalMetricQualifier | None = None + effective_at: AwareDatetime | None = None + + +class CanonicalReportingCommitment2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + scope: Literal['vendor'] = 'vendor' + vendor: brand_key.BrandKey + metric_id: vendor_metric_id.VendorMetricId + qualifier: canonical_metric_qualifier.CanonicalMetricQualifier | None = None + effective_at: AwareDatetime | None = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.canonical_reporting_commitment import ( - CanonicalReportingCommitment, - CanonicalReportingCommitment1, - CanonicalReportingCommitment2, -) - -# Explicit exports -__all__ = [ - "CanonicalReportingCommitment", - "CanonicalReportingCommitment1", - "CanonicalReportingCommitment2", +CanonicalReportingCommitment = Annotated[ + CanonicalReportingCommitment1 | CanonicalReportingCommitment2, + Field( + description='One metric the seller commits to report for a commercial product term. Vendor identity uses BrandKey so the compact lifecycle does not inline brand or provenance documents.', + discriminator='scope', + title='Canonical Reporting Commitment', + ), ] diff --git a/src/adcp/types/domains/core/canvas_constraint.py b/src/adcp/types/domains/core/canvas_constraint.py index 9cbbef8bb..27dd15603 100644 --- a/src/adcp/types/domains/core/canvas_constraint.py +++ b/src/adcp/types/domains/core/canvas_constraint.py @@ -1,23 +1,44 @@ -"""Types declared by the AdCP ``core/canvas_constraint`` schema. +# generated by datamodel-codegen: +# filename: core/canvas_constraint.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.canvas_constraint import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.canvas_constraint import ( - CanvasConstraint, - Constraint, - Region, - Unit, -) +class Constraint(StrEnum): + safe_area = 'safe_area' + reserved_region = 'reserved_region' + decoration_only_edge = 'decoration_only_edge' + no_text_or_logos = 'no_text_or_logos' + + +class Unit(StrEnum): + px = 'px' + percent = 'percent' + + +class Region(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + x: Annotated[StrictFloat, Field(ge=0.0)] + y: Annotated[StrictFloat, Field(ge=0.0)] + width: Annotated[StrictFloat, Field(gt=0.0)] + height: Annotated[StrictFloat, Field(gt=0.0)] + unit: Unit | None = Unit.px + -# Explicit exports -__all__ = ["CanvasConstraint", "Constraint", "Region", "Unit"] +class CanvasConstraint(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + constraint: Constraint + state_id: Annotated[str | None, Field(pattern='^[a-z][a-z0-9_]*$')] = None + breakpoint_id: Annotated[str | None, Field(pattern='^[a-z][a-z0-9_]*$')] = None + region: Region diff --git a/src/adcp/types/domains/core/capabilities_changed_webhook.py b/src/adcp/types/domains/core/capabilities_changed_webhook.py index 7b36a13bd..7bc784bb7 100644 --- a/src/adcp/types/domains/core/capabilities_changed_webhook.py +++ b/src/adcp/types/domains/core/capabilities_changed_webhook.py @@ -1,22 +1,108 @@ -"""Types declared by the AdCP ``core/capabilities_changed_webhook`` schema. +# generated by datamodel-codegen: +# filename: core/capabilities_changed_webhook.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.capabilities_changed_webhook import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import ext as ext_1 + + +class Reason(StrEnum): + configuration_changed = 'configuration_changed' + deployment_changed = 'deployment_changed' + capability_enabled = 'capability_enabled' + capability_disabled = 'capability_disabled' + protocol_versions_changed = 'protocol_versions_changed' + manual_refresh = 'manual_refresh' + other = 'other' + + +class ChangedPath(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^(/[^\\s]*)$'} -from adcp.types.generated_poc.core.capabilities_changed_webhook import ( - CapabilitiesChangedWebhook, - ChangedPath, - Reason, -) -# Explicit exports -__all__ = ["CapabilitiesChangedWebhook", "ChangedPath", "Reason"] +class CapabilitiesChangedWebhook(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + idempotency_key: Annotated[ + str, + Field( + description='Sender-generated key stable across retries of the same fire. Sellers MUST generate a cryptographically random value (UUID v4 recommended) per distinct fire and reuse it on every retry of the same fire. Receivers MUST dedupe by this key, scoped to the authenticated sender identity.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + notification_id: Annotated[ + str, + Field( + description='Stable identifier for this logical capability-change event. Re-emissions of the same logical change reuse this value under a new idempotency_key; a later material capability revision receives a new id.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] + notification_type: Annotated[ + Literal['capabilities.changed'], + Field( + description="Fixed notification type discriminator. Matches the value registered on the subscriber's `event_types`." + ), + ] = 'capabilities.changed' + fired_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the seller initiated this fire. Distinct from `changed_at`, which is when the seller recorded the material capability change.' + ), + ] + subscriber_id: Annotated[ + str, + Field( + description="Identifies which caller-scoped notification_configs[] entry is receiving this fire. Echoed verbatim from the entry's subscriber_id.", + max_length=64, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,64}$', + ), + ] + agent_url: Annotated[ + AnyUrl, + Field( + description='Canonical seller agent URL whose capabilities changed. Receivers that subscribe to multiple agents use this to select the cache entry to invalidate.' + ), + ] + changed_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the seller recorded the material capability change. SHOULD match or precede `adcp.capability_changes.last_modified` on the next `get_adcp_capabilities` response.' + ), + ] + reason: Annotated[ + Reason, + Field( + description='Coarse reason for the invalidation. This is advisory routing/debug metadata; receivers MUST re-read `get_adcp_capabilities` rather than relying on the reason to infer the new capability surface.' + ), + ] + capabilities_version: Annotated[ + str, + Field( + description='Required opaque revision token for the capability document after the change. MUST equal `adcp.capability_changes.capabilities_version` on the authoritative `get_adcp_capabilities` response available before this webhook is sent. Receivers MUST treat the token as opaque and compare it only for equality.', + max_length=255, + min_length=1, + ), + ] + changed_paths: Annotated[ + list[ChangedPath] | None, + Field( + description='Optional advisory JSON Pointer-style paths that identify the top-level or nested capability subtrees that changed (for example `/account/sandbox` or `/supported_protocols`). Receivers MAY use this for logging or selective downstream invalidation, but MUST still treat the full `get_adcp_capabilities` response as the authoritative replacement snapshot.', + min_length=1, + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/catalog.py b/src/adcp/types/domains/core/catalog.py index f694266b9..9cd49ead5 100644 --- a/src/adcp/types/domains/core/catalog.py +++ b/src/adcp/types/domains/core/catalog.py @@ -1,18 +1,125 @@ -"""Types declared by the AdCP ``core/catalog`` schema. +# generated by datamodel-codegen: +# filename: core/catalog.json +# timestamp: 2026-10-04T00:56:30+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.catalog import +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-04 01:19:07 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import AnyUrl, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import catalog_type +from ..enums import content_id_type as content_id_type_1 +from ..enums import event_type +from ..enums import feed_format as feed_format_1 +from ..enums import update_frequency as update_frequency_1 +from . import catalog_field_mapping + + +class Gtin(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[0-9]{8,14}$'} -from adcp.types.generated_poc.core.catalog import Catalog, Gtin -# Explicit exports -__all__ = ["Catalog", "Gtin"] +class Catalog(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + catalog_id: Annotated[ + str | None, + Field( + description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." + ), + ] = None + name: Annotated[ + str | None, + Field( + description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." + ), + ] = None + type: Annotated[ + catalog_type.CatalogType, + Field( + description="Catalog type. Structural types: 'offering' (AdCP Offering objects), 'product' (ecommerce entries), 'inventory' (stock per location), 'store' (physical locations), 'promotion' (deals and pricing). Vertical types: 'hotel', 'flight', 'job', 'vehicle', 'real_estate', 'education', 'destination', 'app' — each with an industry-specific item schema." + ), + ] + url: Annotated[ + AnyUrl | None, + Field( + description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." + ), + ] = None + feed_format: Annotated[ + feed_format_1.FeedFormat | None, + Field( + description='Format of the external feed at url. Required when url points to a non-AdCP feed (e.g., Google Merchant Center XML, Meta Product Catalog). Omit for offering-type catalogs where the feed is native AdCP JSON.' + ), + ] = None + update_frequency: Annotated[ + update_frequency_1.UpdateFrequency | None, + Field( + description='How often the platform should re-fetch the feed from url. Only applicable when url is provided. Platforms may use this as a hint for polling schedules.' + ), + ] = None + items: Annotated[ + list[dict[str, Any]] | None, + Field( + description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", + min_length=1, + ), + ] = None + ids: Annotated[ + list[str] | None, + Field( + description='Filter catalog to exact canonical item keys. The key field is offering_id for offering, store_id for store, hotel_id for hotel, flight_id for flight, job_id for job, vehicle_id for vehicle, listing_id for real_estate, program_id for education, destination_id for destination, and app_id for app. Product, inventory, and promotion catalogs use the stable normalized source identifier retained during ingestion (for example a retailer SKU). The same canonical key is used by catalog availability item_id.', + min_length=1, + ), + ] = None + gtins: Annotated[ + list[Gtin] | None, + Field( + description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", + min_length=1, + ), + ] = None + tags: Annotated[ + list[str] | None, + Field( + description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', + min_length=1, + ), + ] = None + category: Annotated[ + str | None, + Field( + description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." + ), + ] = None + query: Annotated[ + str | None, + Field( + description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." + ), + ] = None + conversion_events: Annotated[ + list[event_type.EventType] | None, + Field( + description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", + min_length=1, + ), + ] = None + content_id_type: Annotated[ + content_id_type_1.ContentIdType | None, + Field( + description="Identifier type that the event's content_ids field should be matched against for items in this catalog. For example, 'gtin' means content_ids values are Global Trade Item Numbers, 'sku' means retailer SKUs. Omit when using a custom identifier scheme not listed in the enum." + ), + ] = None + feed_field_mappings: Annotated[ + list[catalog_field_mapping.CatalogFieldMapping] | None, + Field( + description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', + min_length=1, + ), + ] = None diff --git a/src/adcp/types/domains/core/catalog_field_mapping.py b/src/adcp/types/domains/core/catalog_field_mapping.py index e6dd9b0a1..e02382404 100644 --- a/src/adcp/types/domains/core/catalog_field_mapping.py +++ b/src/adcp/types/domains/core/catalog_field_mapping.py @@ -1,18 +1,88 @@ -"""Types declared by the AdCP ``core/catalog_field_mapping`` schema. +# generated by datamodel-codegen: +# filename: core/catalog_field_mapping.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.catalog_field_mapping import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import ext as ext_1 + + +class Transform(StrEnum): + date = 'date' + divide = 'divide' + boolean = 'boolean' + split = 'split' # type: ignore[assignment] -from adcp.types.generated_poc.core.catalog_field_mapping import CatalogFieldMapping, Transform -# Explicit exports -__all__ = ["CatalogFieldMapping", "Transform"] +class CatalogFieldMapping(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + feed_field: Annotated[ + str | None, + Field( + description='Field name in the external feed record. Omit when injecting a static literal value (use the value property instead).' + ), + ] = None + catalog_field: Annotated[ + str | None, + Field( + description="Target field on the catalog item schema, using dot notation for nested fields (e.g., 'name', 'price.amount', 'location.city'). Mutually exclusive with asset_group_id." + ), + ] = None + asset_group_id: Annotated[ + str | None, + Field( + description="Places the feed field value (a URL) into a typed asset pool on the catalog item's assets array. The value is wrapped as an image or video asset in a group with this ID. Use standard group IDs: 'images_landscape', 'images_vertical', 'images_square', 'logo', 'video'. Mutually exclusive with catalog_field." + ), + ] = None + value: Annotated[ + Any | None, + Field( + description='Static literal value to inject into catalog_field for every item, regardless of what the feed contains. Mutually exclusive with feed_field. Useful for fields the feed omits (e.g., currency when price is always USD, or a constant category value).' + ), + ] = None + transform: Annotated[ + Transform | None, + Field( + description='Named transform to apply to the feed field value before writing to the catalog schema. See transform-specific parameters (format, timezone, by, separator).' + ), + ] = None + format: Annotated[ + str | None, + Field( + description="For transform 'date': the input date format string (e.g., 'YYYYMMDD', 'MM/DD/YYYY', 'DD-MM-YYYY'). Output is always ISO 8601 (e.g., '2025-03-01'). Uses Unicode date pattern tokens." + ), + ] = None + timezone: Annotated[ + str | None, + Field( + description="For transform 'date': the timezone of the input value. IANA timezone identifier (e.g., 'UTC', 'America/New_York', 'Europe/Amsterdam'). Defaults to UTC when omitted." + ), + ] = None + by: Annotated[ + StrictFloat | None, + Field( + description="For transform 'divide': the divisor to apply (e.g., 100 to convert integer cents to decimal dollars).", + gt=0.0, + ), + ] = None + separator: Annotated[ + str | None, + Field( + description="For transform 'split': the separator character or string to split on. Defaults to ','." + ), + ] = ',' + default: Annotated[ + Any | None, + Field( + description='Fallback value to use when feed_field is absent, null, or empty. Applied after any transform would have been applied. Allows optional feed fields to have a guaranteed baseline value.' + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/catalog_item_availability_error.py b/src/adcp/types/domains/core/catalog_item_availability_error.py index a147f6be5..44ad6db10 100644 --- a/src/adcp/types/domains/core/catalog_item_availability_error.py +++ b/src/adcp/types/domains/core/catalog_item_availability_error.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``core/catalog_item_availability_error`` schema. +# generated by datamodel-codegen: +# filename: core/catalog_item_availability_error.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.catalog_item_availability_error import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.catalog_item_availability_error import ( - CatalogItemAvailabilityError, -) +from .error import Error + -# Explicit exports -__all__ = ["CatalogItemAvailabilityError"] +class CatalogItemAvailabilityError(Error): + pass diff --git a/src/adcp/types/domains/core/catalog_item_availability_ref.py b/src/adcp/types/domains/core/catalog_item_availability_ref.py index 62e2b7ae3..1fd98ad77 100644 --- a/src/adcp/types/domains/core/catalog_item_availability_ref.py +++ b/src/adcp/types/domains/core/catalog_item_availability_ref.py @@ -1,20 +1,35 @@ -"""Types declared by the AdCP ``core/catalog_item_availability_ref`` schema. +# generated by datamodel-codegen: +# filename: core/catalog_item_availability_ref.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.catalog_item_availability_ref import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -from adcp.types.generated_poc.core.catalog_item_availability_ref import ( - CatalogItemAvailabilityReference, -) -# Explicit exports -__all__ = ["CatalogItemAvailabilityReference"] +class CatalogItemAvailabilityReference(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + catalog_id: Annotated[ + str, Field(description='Buyer-assigned catalog ID.', max_length=255, min_length=1) + ] + catalog_generation: Annotated[ + str, + Field( + description='Opaque seller-issued token for the referenced catalog incarnation. It remains stable across ordinary upserts and feed refreshes, changes when a deleted catalog_id is recreated, and MUST never be reused for that account and catalog_id.', + max_length=255, + min_length=1, + ), + ] + item_id: Annotated[ + str, + Field( + description='Exact canonical item key used by Catalog.ids. For typed catalogs this is the type-specific identifier field; for product, inventory, and promotion catalogs it is the stable normalized source identifier retained during ingestion.', + max_length=255, + min_length=1, + ), + ] diff --git a/src/adcp/types/domains/core/catalog_item_availability_state.py b/src/adcp/types/domains/core/catalog_item_availability_state.py index eabaa94b3..48a4ec100 100644 --- a/src/adcp/types/domains/core/catalog_item_availability_state.py +++ b/src/adcp/types/domains/core/catalog_item_availability_state.py @@ -1,22 +1,74 @@ -"""Types declared by the AdCP ``core/catalog_item_availability_state`` schema. +# generated by datamodel-codegen: +# filename: core/catalog_item_availability_state.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.catalog_item_availability_state import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import catalog_item_availability_error +from . import ext as ext_1 + + +class Status(StrEnum): + found = 'found' + failed = 'failed' + + +class Availability(StrEnum): + active = 'active' + suppressed = 'suppressed' -from adcp.types.generated_poc.core.catalog_item_availability_state import ( - Availability, - CatalogItemAvailabilityState, - Status, -) -# Explicit exports -__all__ = ["Availability", "CatalogItemAvailabilityState", "Status"] +class CatalogItemAvailabilityState(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + request_index: Annotated[ + SchemaInt, + Field( + description='Zero-based index of the corresponding item_availability_queries entry.', + ge=0, + ), + ] + catalog_id: Annotated[str, Field(max_length=255, min_length=1)] + catalog_generation: Annotated[str, Field(max_length=255, min_length=1)] + item_id: Annotated[str, Field(max_length=255, min_length=1)] + status: Annotated[ + Status, + Field( + description='found returns current state; failed means the reference could not be read.' + ), + ] + availability: Annotated[ + Availability | None, + Field( + description='Current buyer-authored overlay state. active does not imply seller approval or delivery eligibility.' + ), + ] = None + overlay_revision: Annotated[ + SchemaInt | None, + Field( + description='Current optimistic-concurrency token. Revision 0 is the initial active state. Every applied suppress, applied restore, and automatic expiry increments it exactly once; unchanged updates, reads, and idempotent replays do not increment it.', + ge=0, + ), + ] = None + expires_at: Annotated[ + AwareDatetime | None, + Field( + description='Current automatic expiry, present only while availability is suppressed with an expiry.' + ), + ] = None + updated_at: Annotated[ + AwareDatetime | None, + Field(description='Seller timestamp of the state represented by overlay_revision.'), + ] = None + errors: Annotated[ + list[catalog_item_availability_error.CatalogItemAvailabilityError] | None, + Field(min_length=1), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/catalog_item_availability_update.py b/src/adcp/types/domains/core/catalog_item_availability_update.py index b3f0b5dff..0e4d5164f 100644 --- a/src/adcp/types/domains/core/catalog_item_availability_update.py +++ b/src/adcp/types/domains/core/catalog_item_availability_update.py @@ -1,22 +1,95 @@ -"""Types declared by the AdCP ``core/catalog_item_availability_update`` schema. +# generated by datamodel-codegen: +# filename: core/catalog_item_availability_update.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.catalog_item_availability_update import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import ext as ext_1 + + +class Action(StrEnum): + suppress = 'suppress' + restore = 'restore' + + +class Reason(StrEnum): + out_of_stock = 'out_of_stock' + back_in_stock = 'back_in_stock' + content_unavailable = 'content_unavailable' + content_available = 'content_available' + promotion_start = 'promotion_start' + promotion_end = 'promotion_end' + time_window_started = 'time_window_started' + time_window_expired = 'time_window_expired' + buyer_request = 'buyer_request' + other = 'other' -from adcp.types.generated_poc.core.catalog_item_availability_update import ( - Action, - CatalogItemAvailabilityUpdate, - Reason, -) -# Explicit exports -__all__ = ["Action", "CatalogItemAvailabilityUpdate", "Reason"] +class CatalogItemAvailabilityUpdate(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + catalog_id: Annotated[ + str, + Field( + description='Buyer-assigned ID of the buyer-managed catalog containing the item.', + max_length=255, + min_length=1, + ), + ] + catalog_generation: Annotated[ + str, + Field( + description='Opaque seller-issued token for the catalog incarnation. Obtain it from sync_catalogs discovery or mutation results. A generation mismatch is treated as REFERENCE_NOT_FOUND, preventing a delayed update from affecting a deleted-and-recreated catalog.', + max_length=255, + min_length=1, + ), + ] + item_id: Annotated[ + str, + Field( + description='Exact canonical item key used by Catalog.ids. For typed catalogs this is the type-specific identifier field; for product, inventory, and promotion catalogs it is the stable normalized source identifier retained during ingestion.', + max_length=255, + min_length=1, + ), + ] + expected_overlay_revision: Annotated[ + SchemaInt, + Field( + description='Required optimistic-concurrency token obtained from item_availability_states. Revision 0 is the initial active state. The seller MUST compare this value atomically with the write and return CONFLICT without mutation when it differs from current state.', + ge=0, + ), + ] + action: Annotated[ + Action, + Field( + description='suppress makes the item and known derived creative variants ineligible immediately; restore removes the buyer-authored suppression but does not bypass seller controls.' + ), + ] + reason: Annotated[ + Reason, + Field( + description='Machine-readable reason for the transition. Use other only when no standard reason applies and explain the condition in reason_detail.' + ), + ] + reason_detail: Annotated[ + str | None, + Field( + description='Optional human-readable context. Required when reason is other. Plain text only; receivers MUST treat it as untrusted buyer input.', + max_length=1000, + min_length=1, + ), + ] = None + expires_at: Annotated[ + AwareDatetime | None, + Field( + description='Optional expiry for a suppress overlay. At this instant the seller automatically removes the buyer-authored suppression as if it received restore. Valid only with action suppress; omit for an indefinite suppression. Sellers MUST reject a timestamp that is not in the future when the request is processed. On a repeated suppress, this value is complete replacement state: a changed timestamp replaces the prior expiry, and omission clears a prior expiry to make suppression indefinite. Either change returns status applied, not unchanged.' + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/catalog_item_availability_update_result.py b/src/adcp/types/domains/core/catalog_item_availability_update_result.py index 70a7bf905..675646cfc 100644 --- a/src/adcp/types/domains/core/catalog_item_availability_update_result.py +++ b/src/adcp/types/domains/core/catalog_item_availability_update_result.py @@ -1,23 +1,85 @@ -"""Types declared by the AdCP ``core/catalog_item_availability_update_result`` schema. +# generated by datamodel-codegen: +# filename: core/catalog_item_availability_update_result.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.catalog_item_availability_update_result import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import catalog_item_availability_error +from . import ext as ext_1 + + +class Action(StrEnum): + suppress = 'suppress' + restore = 'restore' + + +class Status(StrEnum): + applied = 'applied' + unchanged = 'unchanged' + failed = 'failed' + + +class Availability(StrEnum): + active = 'active' + suppressed = 'suppressed' -from adcp.types.generated_poc.core.catalog_item_availability_update_result import ( - Action, - Availability, - CatalogItemAvailabilityUpdateResult, - Status, -) -# Explicit exports -__all__ = ["Action", "Availability", "CatalogItemAvailabilityUpdateResult", "Status"] +class CatalogItemAvailabilityUpdateResult(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + request_index: Annotated[ + SchemaInt, + Field( + description='Zero-based index of the corresponding item_availability_updates entry.', + ge=0, + ), + ] + catalog_id: Annotated[str, Field(description='Catalog ID from the request.')] + catalog_generation: Annotated[ + str, Field(description='Catalog generation from the request.', max_length=255, min_length=1) + ] + item_id: Annotated[str, Field(description='Item ID from the request.')] + action: Annotated[Action, Field(description='Action from the request.')] + status: Annotated[ + Status, + Field( + description='applied means the requested overlay transition completed, including replacement or removal of an existing expires_at; unchanged means the item was already in the requested buyer-availability state with the same expiry; failed means no transition was applied.' + ), + ] + availability: Annotated[ + Availability | None, + Field(description='Persisted buyer-authored state after an applied or unchanged result.'), + ] = None + overlay_revision: Annotated[ + SchemaInt | None, + Field( + description="Persisted state revision after an applied or unchanged result. Applied increments the request's expected_overlay_revision exactly once; unchanged preserves it.", + ge=0, + ), + ] = None + expires_at: Annotated[ + AwareDatetime | None, + Field( + description='Persisted expiry after this update, present only for a suppressed state with an expiry.' + ), + ] = None + applied_at: Annotated[ + AwareDatetime | None, + Field( + description='Seller timestamp when the buyer-availability state took effect. Required for applied. Optional for unchanged when the seller knows the timestamp of the already-persisted state.' + ), + ] = None + errors: Annotated[ + list[catalog_item_availability_error.CatalogItemAvailabilityError] | None, + Field( + description='Why this item update failed. Required when status is failed.', min_length=1 + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/catalog_item_delivery_metrics.py b/src/adcp/types/domains/core/catalog_item_delivery_metrics.py index 0a22b618c..d8ce20ece 100644 --- a/src/adcp/types/domains/core/catalog_item_delivery_metrics.py +++ b/src/adcp/types/domains/core/catalog_item_delivery_metrics.py @@ -1,18 +1,24 @@ -"""Types declared by the AdCP ``core/catalog_item_delivery_metrics`` schema. +# generated by datamodel-codegen: +# filename: core/catalog_item_delivery_metrics.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.catalog_item_delivery_metrics import +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import content_id_type as content_id_type_1 +from .delivery_metrics import DeliveryMetrics -from adcp.types.generated_poc.core.catalog_item_delivery_metrics import CatalogItemDeliveryMetrics -# Explicit exports -__all__ = ["CatalogItemDeliveryMetrics"] +class CatalogItemDeliveryMetrics(DeliveryMetrics): + content_id: Annotated[ + str, Field(description='Catalog item identifier (e.g., SKU, GTIN, job_id, offering_id)') + ] + content_id_type: Annotated[ + content_id_type_1.ContentIdType | None, + Field(description='Identifier type for this content_id'), + ] = None + impressions: Any + spend: Any diff --git a/src/adcp/types/domains/core/catalog_item_reference_not_found_error.py b/src/adcp/types/domains/core/catalog_item_reference_not_found_error.py index 7ddf0c4d8..0f54748ca 100644 --- a/src/adcp/types/domains/core/catalog_item_reference_not_found_error.py +++ b/src/adcp/types/domains/core/catalog_item_reference_not_found_error.py @@ -1,20 +1,19 @@ -"""Types declared by the AdCP ``core/catalog_item_reference_not_found_error`` schema. +# generated by datamodel-codegen: +# filename: core/catalog_item_reference_not_found_error.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.catalog_item_reference_not_found_error import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict -from adcp.types.generated_poc.core.catalog_item_reference_not_found_error import ( - CatalogItemReferenceNotFoundError, -) -# Explicit exports -__all__ = ["CatalogItemReferenceNotFoundError"] +class CatalogItemReferenceNotFoundError(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + code: Literal['REFERENCE_NOT_FOUND'] = 'REFERENCE_NOT_FOUND' + message: Literal['Catalog item not found'] = 'Catalog item not found' + recovery: Literal['correctable'] = 'correctable' diff --git a/src/adcp/types/domains/core/catalog_selection.py b/src/adcp/types/domains/core/catalog_selection.py index e047f0312..3db938d81 100644 --- a/src/adcp/types/domains/core/catalog_selection.py +++ b/src/adcp/types/domains/core/catalog_selection.py @@ -1,18 +1,70 @@ -"""Types declared by the AdCP ``core/catalog_selection`` schema. +# generated by datamodel-codegen: +# filename: core/catalog_selection.json +# timestamp: 2026-10-04T00:56:30+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.catalog_selection import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-04 01:19:07 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import catalog_type + + +class Gtin(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[0-9]{8,14}$'} -from adcp.types.generated_poc.core.catalog_selection import CatalogSelection, Gtin -# Explicit exports -__all__ = ["CatalogSelection", "Gtin"] +class CatalogSelection(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + catalog_id: Annotated[ + str, + Field( + description='Seller-known catalog identifier returned by sync_catalogs.', min_length=1 + ), + ] + type: Annotated[ + catalog_type.CatalogType | None, + Field( + description="Catalog type. Structural types: 'offering' (AdCP Offering objects), 'product' (ecommerce entries), 'inventory' (stock per location), 'store' (physical locations), 'promotion' (deals and pricing). Vertical types: 'hotel', 'flight', 'job', 'vehicle', 'real_estate', 'education', 'destination', 'app' — each with an industry-specific item schema." + ), + ] = None + ids: Annotated[ + list[str] | None, + Field( + description='Filter catalog to exact canonical item keys. The key field is offering_id for offering, store_id for store, hotel_id for hotel, flight_id for flight, job_id for job, vehicle_id for vehicle, listing_id for real_estate, program_id for education, destination_id for destination, and app_id for app. Product, inventory, and promotion catalogs use the stable normalized source identifier retained during ingestion (for example a retailer SKU). The same canonical key is used by catalog availability item_id.', + min_length=1, + ), + ] = None + gtins: Annotated[ + list[Gtin] | None, + Field( + description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", + min_length=1, + ), + ] = None + tags: Annotated[ + list[str] | None, + Field( + description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', + min_length=1, + ), + ] = None + category: Annotated[ + str | None, + Field( + description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." + ), + ] = None + query: Annotated[ + str | None, + Field( + description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies').", + min_length=1, + ), + ] = None diff --git a/src/adcp/types/domains/core/catchment.py b/src/adcp/types/domains/core/catchment.py index 6c7682531..d89d3aa3b 100644 --- a/src/adcp/types/domains/core/catchment.py +++ b/src/adcp/types/domains/core/catchment.py @@ -1,18 +1,106 @@ -"""Types declared by the AdCP ``core/catchment`` schema. +# generated by datamodel-codegen: +# filename: core/catchment.json +# timestamp: 2026-10-04T01:14:11+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.catchment import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictFloat, model_validator + +from ..enums import distance_unit +from ..enums import transport_mode as transport_mode_1 +from ..enums import travel_time_unit +from . import ext as ext_1 + + +class Type(StrEnum): + Polygon = 'Polygon' + MultiPolygon = 'MultiPolygon' + + +class Geometry(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Annotated[Type, Field(description='GeoJSON geometry type.')] + coordinates: Annotated[ + list[Any], + Field( + description='GeoJSON coordinates array. For Polygon: array of linear rings. For MultiPolygon: array of polygons.' + ), + ] + + +class TravelTime(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + value: Annotated[StrictFloat, Field(description='Travel time limit.', ge=1.0)] + unit: travel_time_unit.TravelTimeUnit + + +class Radius(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + value: Annotated[StrictFloat, Field(description='Radius distance.', gt=0.0)] + unit: Annotated[distance_unit.DistanceUnit, Field(description='Distance unit.')] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.catchment import Catchment, Geometry, Radius, TravelTime, Type +class Catchment(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + catchment_id: Annotated[ + str, + Field( + description="Identifier for this catchment, used to reference specific catchment areas in targeting (e.g., 'walk', 'drive', 'primary')." + ), + ] + label: Annotated[ + str | None, + Field( + description="Human-readable label for this catchment (e.g., '15-min drive', '1km walking radius')." + ), + ] = None + travel_time: Annotated[ + TravelTime | None, + Field( + description='Travel time limit for isochrone calculation. The platform resolves this to a geographic boundary based on actual transportation networks, accounting for road connectivity, transit schedules, and terrain.' + ), + ] = None + transport_mode: Annotated[ + transport_mode_1.TransportMode | None, + Field( + description='Transportation mode for isochrone calculation. Required when travel_time is provided.' + ), + ] = None + radius: Annotated[ + Radius | None, + Field( + description="Simple radius from the store location. The platform draws a circle of this distance around the store's coordinates." + ), + ] = None + geometry: Annotated[ + Geometry | None, + Field( + description='Pre-computed GeoJSON geometry defining the catchment boundary. Use this when the buyer has already calculated isochrones (via TravelTime, Mapbox, etc.) or has custom trade area boundaries. Supports Polygon and MultiPolygon types.' + ), + ] = None + ext: ext_1.ExtensionObject | None = None -# Explicit exports -__all__ = ["Catchment", "Geometry", "Radius", "TravelTime", "Type"] + @model_validator(mode='after') + def _require_schema_required_group(self) -> Catchment: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('travel_time', 'transport_mode'), ('radius',), ('geometry',),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'Catchment requires at least one of these field groups: travel_time+transport_mode | radius | geometry' + ) diff --git a/src/adcp/types/domains/core/collection.py b/src/adcp/types/domains/core/collection.py index c1d43665b..093ddae7a 100644 --- a/src/adcp/types/domains/core/collection.py +++ b/src/adcp/types/domains/core/collection.py @@ -1,18 +1,139 @@ -"""Types declared by the AdCP ``core/collection`` schema. +# generated by datamodel-codegen: +# filename: core/collection.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.collection import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import collection_cadence, collection_kind, collection_relationship, collection_status +from ..enums import production_quality as production_quality_1 +from . import collection_distribution +from . import content_rating as content_rating_1 +from . import deadline_policy as deadline_policy_1 +from . import ext as ext_1 +from . import limited_series as limited_series_1 +from . import special as special_1 +from . import talent as talent_1 + + +class RelatedCollection(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + collection_id: Annotated[ + str, + Field(description="The related collection's collection_id within this seller's response"), + ] + relationship: Annotated[ + collection_relationship.CollectionRelationship, + Field(description='How the collections are related'), + ] -from adcp.types.generated_poc.core.collection import Collection, RelatedCollection -# Explicit exports -__all__ = ["Collection", "RelatedCollection"] +class Collection(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + publisher_domain: Annotated[ + str | None, + Field( + description="Publisher namespace that owns this collection. Required for supply-path verification when the declaration is read from a cross-origin authoritative document; it must match the publisher whose origin delegated retrieval. A pointer alone cannot claim another publisher's collection. May be omitted for a publisher-origin document, where that origin supplies the namespace.", + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] = None + collection_id: Annotated[ + str, + Field( + description="Publisher-assigned identifier for this collection. Declared in the publisher's adagents.json collections array. Products reference collections via collection selectors with publisher_domain and collection_ids. Use distribution identifiers for cross-seller matching across publishers." + ), + ] + name: Annotated[str, Field(description='Human-readable collection name')] + kind: Annotated[ + collection_kind.CollectionKind | None, + Field( + description="What kind of content program this is. Helps agents interpret installments correctly. A channel collection represents the persistent programmed stream; its installments, when present, are scheduled airings or programming blocks. Defaults to 'series' when absent." + ), + ] = None + description: Annotated[str | None, Field(description='What the collection is about')] = None + genre: Annotated[ + list[str] | None, + Field( + description='Genre tags. When genre_taxonomy is present, values are taxonomy IDs (e.g., IAB Content Taxonomy 3.0 codes). Otherwise free-form.' + ), + ] = None + genre_taxonomy: Annotated[ + str | None, + Field( + description="Taxonomy system for genre values (e.g., 'iab_content_3.0'). When present, genre values should be valid taxonomy IDs. Recommended for machine-readable brand safety evaluation." + ), + ] = None + language: Annotated[ + str | None, Field(description="Primary language (BCP 47 tag, e.g., 'en', 'es-MX')") + ] = None + content_rating: Annotated[ + content_rating_1.ContentRating | None, + Field( + description='Baseline content rating for the collection. Individual installments may override this.' + ), + ] = None + cadence: Annotated[ + collection_cadence.CollectionCadence | None, + Field(description='How frequently the collection releases new installments'), + ] = None + season: Annotated[ + str | None, + Field( + description="Current or most recent season identifier (e.g., '3', '2026', 'spring_2026'). A lightweight label — not a full season object." + ), + ] = None + status: Annotated[ + collection_status.CollectionStatus | None, + Field(description='Lifecycle status of the collection'), + ] = None + production_quality: Annotated[ + production_quality_1.ProductionQuality | None, + Field( + description='Production quality tier. Seller-declared. Maps to OpenRTB content.prodq (professional=1, prosumer=2, ugc=3).' + ), + ] = None + talent: Annotated[ + list[talent_1.Talent] | None, + Field( + description='Hosts, recurring cast, creators associated with the collection. Each talent entry may include a brand_url linking to their brand.json identity.' + ), + ] = None + special: Annotated[ + special_1.Special | None, + Field( + description='When present, this collection is a special — content anchored to a real-world event or occasion. Individual installments may override with their own event context.' + ), + ] = None + limited_series: Annotated[ + limited_series_1.LimitedSeries | None, + Field( + description='When present, this collection is a limited series — a bounded run with a defined arc, installment count, and end date.' + ), + ] = None + distribution: Annotated[ + list[collection_distribution.CollectionDistribution] | None, + Field( + description="Where this collection is distributed. Each entry maps the collection to a publisher platform using host property IDs, identifiers, or both. For channel collections this is the carriage map. It is a publisher assertion for discovery, not sales authorization; buyers validate owner-sold carriage against the host publisher's collection-scoped authorized_agents declaration. Collections SHOULD include at least one platform-independent identifier (imdb_id, gracenote_id, eidr_id) when available." + ), + ] = None + deadline_policy: Annotated[ + deadline_policy_1.DeadlinePolicy | None, + Field( + description="Default deadline rules for installments of this collection. Agents compute absolute deadlines from each installment's scheduled_at and these lead times. Installments with explicit deadlines override this policy. Only meaningful when installments carry scheduled_at; a continuously programmed channel collection without enumerated installments has no deadlines to derive." + ), + ] = None + related_collections: Annotated[ + list[RelatedCollection] | None, + Field( + description="Relationships to other collections (spin-offs, companion collections, etc.). Each entry references another collection by collection_id within the same publisher's adagents.json." + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/collection_delivery_metrics.py b/src/adcp/types/domains/core/collection_delivery_metrics.py index f179c89b4..b18260eec 100644 --- a/src/adcp/types/domains/core/collection_delivery_metrics.py +++ b/src/adcp/types/domains/core/collection_delivery_metrics.py @@ -1,18 +1,24 @@ -"""Types declared by the AdCP ``core/collection_delivery_metrics`` schema. +# generated by datamodel-codegen: +# filename: core/collection_delivery_metrics.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.collection_delivery_metrics import +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import collection_ref as collection_ref_1 +from .delivery_metrics import DeliveryMetrics -from adcp.types.generated_poc.core.collection_delivery_metrics import CollectionDeliveryMetrics -# Explicit exports -__all__ = ["CollectionDeliveryMetrics"] +class CollectionDeliveryMetrics(DeliveryMetrics): + collection_ref: collection_ref_1.CollectionReference + collection_name: Annotated[ + str | None, + Field( + description='Current human-readable collection name. Convenience metadata only; collection_ref is stable identity.' + ), + ] = None + impressions: Any + spend: Any diff --git a/src/adcp/types/domains/core/collection_distribution.py b/src/adcp/types/domains/core/collection_distribution.py index dd298110f..8cb4f0c3b 100644 --- a/src/adcp/types/domains/core/collection_distribution.py +++ b/src/adcp/types/domains/core/collection_distribution.py @@ -1,18 +1,63 @@ -"""Types declared by the AdCP ``core/collection_distribution`` schema. +# generated by datamodel-codegen: +# filename: core/collection_distribution.json +# timestamp: 2026-10-04T01:14:11+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.collection_distribution import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, model_validator + +from ..enums import distribution_identifier_type +from . import property_id + + +class Identifier(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Annotated[ + distribution_identifier_type.DistributionIdentifierType, + Field(description='Type of distribution identifier'), + ] + value: Annotated[str, Field(description='The identifier value')] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.collection_distribution import CollectionDistribution, Identifier +class CollectionDistribution(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + publisher_domain: Annotated[ + str, + Field( + description="Domain of the publisher platform where the collection is distributed (e.g., 'youtube.com', 'spotify.com'). Property IDs and publisher-scoped identifiers in this object resolve in this publisher's namespace." + ), + ] + property_ids: Annotated[ + list[property_id.PropertyId] | None, + Field( + description="Publisher-scoped property IDs from publisher_domain's adagents.json that carry this collection. For owner-sold channel inventory, these identify the host apps without requiring the host to publish channel-owner placement definitions.", + min_length=1, + ), + ] = None + identifiers: Annotated[ + list[Identifier] | None, + Field( + description='Identifiers for the collection on this publisher, including publisher-scoped channel/EPG identifiers and platform-independent metadata identifiers', + min_length=1, + ), + ] = None -# Explicit exports -__all__ = ["CollectionDistribution", "Identifier"] + @model_validator(mode='after') + def _require_schema_required_group(self) -> CollectionDistribution: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('property_ids',), ('identifiers',),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'CollectionDistribution requires at least one of these field groups: property_ids | identifiers' + ) diff --git a/src/adcp/types/domains/core/collection_list_ref.py b/src/adcp/types/domains/core/collection_list_ref.py index 67d104065..622bb3130 100644 --- a/src/adcp/types/domains/core/collection_list_ref.py +++ b/src/adcp/types/domains/core/collection_list_ref.py @@ -1,18 +1,26 @@ -"""Types declared by the AdCP ``core/collection_list_ref`` schema. +# generated by datamodel-codegen: +# filename: core/collection_list_ref.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.collection_list_ref import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field -from adcp.types.generated_poc.core.collection_list_ref import CollectionListReference -# Explicit exports -__all__ = ["CollectionListReference"] +class CollectionListReference(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + agent_url: Annotated[AnyUrl, Field(description='URL of the agent managing the collection list')] + list_id: Annotated[ + str, Field(description='Identifier for the collection list within the agent', min_length=1) + ] + auth_token: Annotated[ + str | None, + Field( + description='JWT or other authorization token for accessing the list. Optional if the list is public or caller has implicit access.' + ), + ] = None diff --git a/src/adcp/types/domains/core/collection_property_delivery_metrics.py b/src/adcp/types/domains/core/collection_property_delivery_metrics.py index 7279bb153..ba0a0ff65 100644 --- a/src/adcp/types/domains/core/collection_property_delivery_metrics.py +++ b/src/adcp/types/domains/core/collection_property_delivery_metrics.py @@ -1,20 +1,45 @@ -"""Types declared by the AdCP ``core/collection_property_delivery_metrics`` schema. +# generated by datamodel-codegen: +# filename: core/collection_property_delivery_metrics.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.collection_property_delivery_metrics import +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import collection_ref as collection_ref_1 +from . import identifier as identifier_1 +from . import property_ref as property_ref_1 +from .delivery_metrics import DeliveryMetrics -from adcp.types.generated_poc.core.collection_property_delivery_metrics import ( - CollectionPropertyDeliveryMetrics, -) -# Explicit exports -__all__ = ["CollectionPropertyDeliveryMetrics"] +class CollectionPropertyDeliveryMetrics(DeliveryMetrics): + collection_ref: collection_ref_1.CollectionReference + collection_name: Annotated[ + str | None, + Field(description='Current human-readable collection name. Convenience metadata only.'), + ] = None + publisher_domain: Annotated[ + str, + Field( + description='Publisher or platform authority that namespaces the property identifier, including for an unregistered surface.', + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] + identifier: Annotated[ + identifier_1.Identifier, + Field(description='Operational identity of the property that delivered.'), + ] + property_ref: Annotated[ + property_ref_1.PropertyReference | None, + Field( + description="Canonical publisher-scoped catalog identity when available. Its publisher_domain MUST equal the row's publisher_domain." + ), + ] = None + property_name: Annotated[ + str | None, + Field(description='Current human-readable property name. Convenience metadata only.'), + ] = None + impressions: Any + spend: Any diff --git a/src/adcp/types/domains/core/collection_ref.py b/src/adcp/types/domains/core/collection_ref.py index 51f141d78..a842ae070 100644 --- a/src/adcp/types/domains/core/collection_ref.py +++ b/src/adcp/types/domains/core/collection_ref.py @@ -1,18 +1,30 @@ -"""Types declared by the AdCP ``core/collection_ref`` schema. +# generated by datamodel-codegen: +# filename: core/collection_ref.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.collection_ref import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -from adcp.types.generated_poc.core.collection_ref import CollectionReference -# Explicit exports -__all__ = ["CollectionReference"] +class CollectionReference(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + publisher_domain: Annotated[ + str, + Field( + description='Domain where the adagents.json declaring this collection is hosted.', + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] + collection_id: Annotated[ + str, + Field( + description="Collection ID from the publisher's adagents.json collection catalog.", + min_length=1, + ), + ] diff --git a/src/adcp/types/domains/core/collection_selection.py b/src/adcp/types/domains/core/collection_selection.py index 9fbc9f121..334f3ed50 100644 --- a/src/adcp/types/domains/core/collection_selection.py +++ b/src/adcp/types/domains/core/collection_selection.py @@ -1,22 +1,46 @@ -"""Types declared by the AdCP ``core/collection_selection`` schema. +# generated by datamodel-codegen: +# filename: core/collection_selection.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.collection_selection import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import collection_selector +from . import ext as ext_1 + + +class CollectionSelection1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + mode: Literal['selected'] = 'selected' + collections: Annotated[ + list[collection_selector.CollectionSelector], + Field( + description="Complete required collection set as domain-qualified selectors with explicit collection_ids; the domain-only bulk-grant form is authorization scoping, not selection. publisher_domain may be an external channel owner's domain.", + min_length=1, + ), + ] + ext: ext_1.ExtensionObject | None = None + + +class CollectionSelection2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + mode: Literal['default'] = 'default' + ext: ext_1.ExtensionObject | None = None -from adcp.types.generated_poc.core.collection_selection import ( - CollectionSelection, - CollectionSelection1, - CollectionSelection2, -) -# Explicit exports -__all__ = ["CollectionSelection", "CollectionSelection1", "CollectionSelection2"] +CollectionSelection = Annotated[ + CollectionSelection1 | CollectionSelection2, + Field( + description="Complete purchased collection set within a product. Mode selected names the complete required set (partial selection requires product collection_targeting_allowed: true; exactly restating the full bundle is an inherent match); mode default accepts the product's full bundle. Package readback MUST echo the committed selection here as concrete selectors, materializing any collection_list composition; a purchase made with mode default therefore reads back as mode selected naming the product's complete bundle. Rejection of a schema-valid selector that references an unknown collection_id or publisher_domain uses REFERENCE_NOT_FOUND per the error-code registry (INVALID_REQUEST applies only to schema-invalid shapes such as a selected-mode item omitting collection_ids). Sellers reject unknown, duplicate, or partially applicable selectors rather than silently changing the selection.", + discriminator='mode', + title='Collection Selection', + ), +] diff --git a/src/adcp/types/domains/core/collection_selector.py b/src/adcp/types/domains/core/collection_selector.py index 547979790..08e0e9415 100644 --- a/src/adcp/types/domains/core/collection_selector.py +++ b/src/adcp/types/domains/core/collection_selector.py @@ -1,18 +1,30 @@ -"""Types declared by the AdCP ``core/collection_selector`` schema. +# generated by datamodel-codegen: +# filename: core/collection_selector.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.collection_selector import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -from adcp.types.generated_poc.core.collection_selector import CollectionSelector -# Explicit exports -__all__ = ["CollectionSelector"] +class CollectionSelector(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + publisher_domain: Annotated[ + str, + Field( + description="Domain where the adagents.json declaring these collections is hosted (e.g., 'mrbeast.com'). The collections array in that file contains the authoritative collection definitions.", + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] + collection_ids: Annotated[ + list[str] | None, + Field( + description='Collection IDs from the adagents.json collections array. Each ID must match a collection_id declared in that file. Omit to reference all collections declared in that file.', + min_length=1, + ), + ] = None diff --git a/src/adcp/types/domains/core/committed_metric.py b/src/adcp/types/domains/core/committed_metric.py index 21d38ee7c..edb267c8c 100644 --- a/src/adcp/types/domains/core/committed_metric.py +++ b/src/adcp/types/domains/core/committed_metric.py @@ -1,32 +1,158 @@ -"""Types declared by the AdCP ``core/committed_metric`` schema. +# generated by datamodel-codegen: +# filename: core/committed_metric.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.committed_metric import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field + +from ..enums import attribution_methodology as attribution_methodology_1 +from ..enums import available_metric +from ..enums import completion_source as completion_source_1 +from ..enums import lift_dimension as lift_dimension_1 +from ..enums import viewability_standard as viewability_standard_1 +from . import brand_ref, duration, vendor_metric_id + + +class Qualifier(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + viewability_standard: Annotated[ + viewability_standard_1.ViewabilityStandard | None, + Field(description='Viewability standard for this vendor metric, as a closed enum.'), + ] = None + completion_source: Annotated[ + completion_source_1.CompletionSource | None, + Field(description='Completion attestation source for this vendor metric.'), + ] = None + attribution_methodology: Annotated[ + attribution_methodology_1.AttributionMethodology | None, + Field(description='Attribution methodology for this vendor outcome metric.'), + ] = None + attribution_window: Annotated[ + duration.Duration | None, + Field( + description='Attribution window for this vendor outcome metric, as a structured duration.' + ), + ] = None + lift_dimension: Annotated[ + lift_dimension_1.LiftDimension | None, + Field(description='Brand-lift dimension for this vendor metric.'), + ] = None + + +class QualifierModel(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + viewability_standard: Annotated[ + viewability_standard_1.ViewabilityStandard | None, + Field( + description='Viewability standard the seller commits to for this metric. MUST be set when `metric_id` ∈ {`viewable_impressions`, `viewable_rate`, `measurable_impressions`, `viewed_seconds`, `viewed_seconds_percentiles`, `viewed_seconds_histogram`} and the seller commits to a specific standard; absence means the contract leaves the standard unspecified and reconciliation falls back to whatever standard the delivery report carries on its `viewability.standard` field. A distribution whose standard remains unspecified MUST NOT be combined with any other row, including another row with an unspecified standard.' + ), + ] = None + completion_source: Annotated[ + completion_source_1.CompletionSource | None, + Field( + description="Source of `completion_rate` attestation. MUST be set when `metric_id` is `completion_rate` and the seller commits to a specific source — `seller_attested` when the player/ad server's own completion event is the contract, `vendor_attested` when a third-party measurement vendor (anchored on the matching `performance_standard.vendor` BrandRef) is the contract. Absence means the contract leaves the source unspecified and reconciliation falls back to whatever the delivery report happens to carry." + ), + ] = None + attribution_methodology: Annotated[ + attribution_methodology_1.AttributionMethodology | None, + Field( + description='How attribution between ad exposure and outcome events was computed. SHOULD be set when `metric_id` is an outcome metric (`conversions`, `conversion_value`, `roas`, `cost_per_acquisition`, `incremental_sales_lift`, `brand_lift`, `foot_traffic`, `conversion_lift`, `brand_search_lift`, `units_sold`, `new_to_brand_rate`, `new_to_brand_units`, `leads`) and the seller commits to a specific methodology. `deterministic_purchase` is the retail-media default; `modeled` covers MMM and clean-room outputs; `probabilistic` and `panel_based` cover their respective methodologies. Two outcome rows with the same `metric_id` and different `attribution_methodology` are not interchangeable and must not be summed.' + ), + ] = None + attribution_window: Annotated[ + duration.Duration | None, + Field( + description="Time window over which outcome attribution is computed. **Object-valued, not string** — MUST be a structured duration object like `{interval: 14, unit: 'days'}`, NEVER a shorthand string like `'14d'`. SHOULD be set when `metric_id` is an outcome metric and the seller commits to a specific window. Common windows: `{interval: 7, unit: 'days'}`, `{interval: 14, unit: 'days'}`, `{interval: 30, unit: 'days'}`, `{interval: 90, unit: 'days'}`. Two outcome rows with the same `metric_id` and `attribution_methodology` but different `attribution_window` represent the same metric measured over different periods — the join on `(metric_id, qualifier)` keeps them as separate rows so buyers don't accidentally aggregate across windows." + ), + ] = None + lift_dimension: Annotated[ + lift_dimension_1.LiftDimension | None, + Field( + description='Which dimension of `brand_lift` this row represents — awareness, consideration, favorability, purchase intent, or ad recall. MUST be set when `metric_id` is `brand_lift` and the seller commits to (or is reporting) a specific dimension. Brand-lift vendors (Kantar, Upwave, Cint, DoubleVerify) report each dimension separately with its own sample size and confidence interval; combining them into a single number is a category error. Two `brand_lift` rows under different `lift_dimension` are different surveyed outcomes and must not be summed.' + ), + ] = None + + +class CommittedMetric1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + scope: Annotated[ + Literal['standard'], + Field(description='Standard metric from the closed `available-metric.json` enum.'), + ] = 'standard' + metric_id: Annotated[ + available_metric.AvailableMetric, Field(description='Identifier for the standard metric.') + ] + qualifier: Annotated[ + QualifierModel | None, + Field( + description="Disambiguates metrics whose definition varies by qualifier. Today carries five keys — `viewability_standard` (MRC vs GroupM viewability), `completion_source` (seller- vs vendor-attested completion), `attribution_methodology` (how attribution was computed for outcome metrics), `attribution_window` (the time window over which outcomes were attributed), and `lift_dimension` (which dimension of brand_lift this row represents — awareness, consideration, etc.). Required when the underlying `metric_id` has multiple incompatible measurement paths AND the seller commits to a specific one. Symmetric on `missing_metrics`. Reserved for additive qualifiers in future minors — schema is closed (`additionalProperties: false`); new keys ship explicitly. **Heterogeneous value types**: qualifier values can be either string enums (`viewability_standard`, `completion_source`, `attribution_methodology`, `lift_dimension`) or structured objects (`attribution_window` is a duration `{interval, unit}`). Consumers MUST dispatch on key name to know value shape; structured-value qualifiers join on canonical (key-sorted) deep equality so `{interval: 14, unit: 'days'}` and `{unit: 'days', interval: 14}` resolve to the same partition. Rate-style metrics (`new_to_brand_rate`, `engagement_rate`, etc.) inherit the methodology of their numerator — when a rate carries `attribution_methodology` qualifier, it applies to the underlying conversions/events being rated." + ), + ] = None + committed_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when this metric became part of the contract. Day-1 commitments use `create_media_buy.confirmed_at`; mid-flight additions use the time the amendment was accepted.' + ), + ] + + +class Qualifier1(Qualifier): + pass + + +class CommittedMetric2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + scope: Annotated[ + Literal['vendor'], + Field(description='Vendor-defined metric, identified by the tuple `(vendor, metric_id)`.'), + ] = 'vendor' + vendor: Annotated[ + brand_ref.BrandReference, + Field( + description="Vendor that defines and computes this metric. The vendor's `brand.json` `agents[type='measurement']` is the canonical anchor." + ), + ] + metric_id: Annotated[ + vendor_metric_id.VendorMetricId, + Field(description="Identifier for the metric within the vendor's vocabulary."), + ] + methodology_version: Annotated[ + str | None, + Field( + description="Optional pin of the vendor's `get_adcp_capabilities.measurement.metrics[].methodology_version` that this commitment is contracted against. When present, the seller commits to reporting values computed under this methodology version; a vendor-side methodology change that alters the value definition is a contract change and SHOULD surface as a new appended entry with its own `committed_at`, never a silent substitution. Absence means the contract does not pin a version and buyers MUST treat methodology changes as untracked. Opaque string — compare for equality, do not parse." + ), + ] = None + qualifier: Annotated[ + Qualifier1 | None, + Field( + description='Optional qualifier disambiguating commitments to the same vendor metric measured under different methodologies or windows. Same closed key set as standard-scope entries; new keys ship explicitly.' + ), + ] = None + committed_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when this vendor metric became part of the contract.' + ), + ] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.committed_metric import ( - CommittedMetric, - CommittedMetric1, - CommittedMetric2, - Qualifier, - Qualifier1, - QualifierModel, -) - -# Explicit exports -__all__ = [ - "CommittedMetric", - "CommittedMetric1", - "CommittedMetric2", - "Qualifier", - "Qualifier1", - "QualifierModel", +CommittedMetric = Annotated[ + CommittedMetric1 | CommittedMetric2, + Field( + description="One metric in a package's binding reporting contract. Each entry uses `scope` as the discriminator and identifies a standard or vendor-defined metric that the seller has committed to populate in delivery reports.", + discriminator='scope', + title='Committed Metric', + ), ] diff --git a/src/adcp/types/domains/core/compact_task_input_required.py b/src/adcp/types/domains/core/compact_task_input_required.py index 68b2ed115..2973fa742 100644 --- a/src/adcp/types/domains/core/compact_task_input_required.py +++ b/src/adcp/types/domains/core/compact_task_input_required.py @@ -1,18 +1,25 @@ -"""Types declared by the AdCP ``core/compact_task_input_required`` schema. +# generated by datamodel-codegen: +# filename: core/compact_task_input_required.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.compact_task_input_required import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import context as context_1 +from . import error +from . import ext as ext_1 -from adcp.types.generated_poc.core.compact_task_input_required import CompactTaskInputRequired -# Explicit exports -__all__ = ["CompactTaskInputRequired"] +class CompactTaskInputRequired(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + reason: Annotated[str | None, Field(max_length=200, min_length=1)] = None + message: Annotated[str | None, Field(max_length=2000)] = None + errors: list[error.Error] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/compact_task_submitted.py b/src/adcp/types/domains/core/compact_task_submitted.py index 056cfdb57..56ea1ecc0 100644 --- a/src/adcp/types/domains/core/compact_task_submitted.py +++ b/src/adcp/types/domains/core/compact_task_submitted.py @@ -1,18 +1,26 @@ -"""Types declared by the AdCP ``core/compact_task_submitted`` schema. +# generated by datamodel-codegen: +# filename: core/compact_task_submitted.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.compact_task_submitted import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import context as context_1 +from . import error +from . import ext as ext_1 -from adcp.types.generated_poc.core.compact_task_submitted import CompactTaskSubmitted -# Explicit exports -__all__ = ["CompactTaskSubmitted"] +class CompactTaskSubmitted(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + status: Literal['submitted'] = 'submitted' + task_id: Annotated[str, Field(min_length=1)] + message: Annotated[str | None, Field(max_length=2000)] = None + errors: list[error.Error] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/compact_task_working.py b/src/adcp/types/domains/core/compact_task_working.py index 1205b1de1..6e5601ed3 100644 --- a/src/adcp/types/domains/core/compact_task_working.py +++ b/src/adcp/types/domains/core/compact_task_working.py @@ -1,18 +1,25 @@ -"""Types declared by the AdCP ``core/compact_task_working`` schema. +# generated by datamodel-codegen: +# filename: core/compact_task_working.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.compact_task_working import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import context as context_1 +from . import ext as ext_1 -from adcp.types.generated_poc.core.compact_task_working import CompactTaskWorking -# Explicit exports -__all__ = ["CompactTaskWorking"] +class CompactTaskWorking(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + percentage: Annotated[StrictFloat | None, Field(ge=0.0, le=100.0)] = None + current_step: Annotated[str | None, Field(max_length=500)] = None + total_steps: Annotated[SchemaInt | None, Field(ge=1)] = None + step_number: Annotated[SchemaInt | None, Field(ge=1)] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/content_rating.py b/src/adcp/types/domains/core/content_rating.py index 53fcf252a..8b8da4063 100644 --- a/src/adcp/types/domains/core/content_rating.py +++ b/src/adcp/types/domains/core/content_rating.py @@ -1,18 +1,24 @@ -"""Types declared by the AdCP ``core/content_rating`` schema. +# generated by datamodel-codegen: +# filename: core/content_rating.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.content_rating import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import content_rating_system -from adcp.types.generated_poc.core.content_rating import ContentRating -# Explicit exports -__all__ = ["ContentRating"] +class ContentRating(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + system: Annotated[ + content_rating_system.ContentRatingSystem, Field(description='Rating system used') + ] + rating: Annotated[ + str, Field(description="Rating value within the system (e.g., 'TV-PG', 'R', 'explicit')") + ] diff --git a/src/adcp/types/domains/core/context.py b/src/adcp/types/domains/core/context.py index 3da5df491..d22c456a9 100644 --- a/src/adcp/types/domains/core/context.py +++ b/src/adcp/types/domains/core/context.py @@ -1,18 +1,14 @@ -"""Types declared by the AdCP ``core/context`` schema. +# generated by datamodel-codegen: +# filename: core/context.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.context import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.context import ContextObject +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict + -# Explicit exports -__all__ = ["ContextObject"] +class ContextObject(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) diff --git a/src/adcp/types/domains/core/creative_approval_scope.py b/src/adcp/types/domains/core/creative_approval_scope.py index aeba5cacd..ed2909a68 100644 --- a/src/adcp/types/domains/core/creative_approval_scope.py +++ b/src/adcp/types/domains/core/creative_approval_scope.py @@ -1,18 +1,27 @@ -"""Types declared by the AdCP ``core/creative_approval_scope`` schema. +# generated by datamodel-codegen: +# filename: core/creative_approval_scope.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.creative_approval_scope import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import creative_approval_status +from . import indicator_scope -from adcp.types.generated_poc.core.creative_approval_scope import ScopedCreativeApproval -# Explicit exports -__all__ = ["ScopedCreativeApproval"] +class ScopedCreativeApproval(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + scope: Annotated[ + indicator_scope.IndicatorScope, + Field(description='Publisher or placement to which this approval outcome applies.'), + ] + approval_status: creative_approval_status.CreativeApprovalStatus + rejection_reason: Annotated[ + str | None, Field(description='Human-readable explanation when this scope is rejected.') + ] = None diff --git a/src/adcp/types/domains/core/creative_asset.py b/src/adcp/types/domains/core/creative_asset.py index f86a06566..c0378dd55 100644 --- a/src/adcp/types/domains/core/creative_asset.py +++ b/src/adcp/types/domains/core/creative_asset.py @@ -1,18 +1,158 @@ -"""Types declared by the AdCP ``core/creative_asset`` schema. +# generated by datamodel-codegen: +# filename: core/creative_asset.json +# timestamp: 2026-10-05T13:49:50+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.creative_asset import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, RootModel, StrictFloat, StringConstraints, model_validator + +from ..enums import creative_status +from . import creative_assets +from . import format_id as format_id_1 +from . import format_option_ref as format_option_ref_1 +from . import industry_identifier, placement_ref +from . import provenance as provenance_1 +from . import representation_selection as representation_selection_1 +from . import rights_constraint +from .assets import asset_union + + +class Input(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + name: Annotated[str, Field(description='Human-readable name for this preview variant')] + macros: Annotated[ + dict[str, str] | None, Field(description='Macro values to apply for this preview') + ] = None + context_description: Annotated[ + str | None, + Field(description='Natural language description of the context for AI-generated content'), + ] = None + + +class Assets(RootModel[list[asset_union.AssetVariant]]): + root: Annotated[list[asset_union.AssetVariant], Field(min_length=1)] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.creative_asset import Assets, CreativeAsset, Input +class CreativeAsset(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + creative_id: Annotated[ + str, + Field( + description='Unique identifier for the creative. Stable across legacy named-format and 3.1+ canonical-format paths — a creative registered against `format_id` retains the same `creative_id` when later viewed through a canonical-format flatten.' + ), + ] + name: Annotated[str, Field(description='Human-readable creative name')] + format_id: Annotated[ + format_id_1.FormatReferenceStructuredObject | None, + Field( + deprecated=True, + description='**DEPRECATED in 3.2.** Legacy named-format path retained for older 3.x peers. New creative assets use `format_kind` and optional `format_option_ref`.', + ), + ] = None + format_kind: Annotated[ + str | None, + Field( + description='Canonical format name this creative targets (e.g., `image`, `video_hosted`). Mutually exclusive with deprecated `format_id`.' + ), + ] = None + format_option_ref: Annotated[ + format_option_ref_1.FormatOptionReference | None, + Field( + description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product has multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the creative to a single declaration. Product-scoped refs require an enclosing target product/package context.' + ), + ] = None + representation_selection: Annotated[ + representation_selection_1.RepresentationSelection | None, + Field( + description='Readback lineage to the complete CreativeRepresentationSet revision and representation selected before this seller-bound creative was synced.' + ), + ] = None + assets: Annotated[ + dict[Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], asset_union.AssetVariant | Assets], + Field( + description='Assets required by the format, keyed by asset_id or canonical asset_group_id. Each slot value is either a single asset object or an array of asset objects (for slots with `min`/`max > 1` like carousel `cards` or responsive_creative `headlines`). Each asset value carries an `asset_type` discriminator that selects the matching asset schema, including reference assets such as `published_post` when a product accepts already-published post references.' + ), + ] + component_assets: Annotated[ + dict[Annotated[str, StringConstraints(pattern=r'^[a-z][a-z0-9_]*$')], creative_assets.CreativeAssets] | None, + Field( + description='Component-addressed canonical asset maps for `coordinated_placements`. Keys match coordinated component IDs. This field is preserved by creative-library sync and list readback; it MUST be absent for every other format kind.' + ), + ] = None + inputs: Annotated[ + list[Input] | None, + Field( + description='Preview contexts for generative formats - defines what scenarios to generate previews for' + ), + ] = None + tags: Annotated[ + list[str] | None, Field(description='User-defined tags for organization and searchability') + ] = None + status: Annotated[ + creative_status.CreativeStatus | None, + Field( + description="For generative creatives: set to 'approved' to finalize, 'rejected' to request regeneration with updated assets/message. Omit for non-generative creatives (system will set based on processing state)." + ), + ] = None + weight: Annotated[ + StrictFloat | None, + Field( + description='Optional delivery weight for creative rotation when uploading via create_media_buy or update_media_buy (0-100). If omitted, platform determines rotation. Only used during upload to media buy - not stored in creative library.', + ge=0.0, + le=100.0, + ), + ] = None + placement_refs: Annotated[ + list[placement_ref.PlacementReference] | None, + Field( + description="Optional structured product-context placement references where this uploaded creative should run when uploading via create_media_buy or update_media_buy. These items always use placement-ref product-context semantics, even when tolerated additional members make an item resemble placement-identity. Receivers match only against the target package's committed placement set; kind and seller_agent are non-authoritative for routing and MUST NOT expand or reinterpret that set. A receiver MUST reject a ref when the enclosing product and committed set do not yield one unambiguous match. New senders SHOULD include publisher_domain for publisher-catalog placements. If omitted, creative runs on all buyer-targetable placements. If both `placement_refs` and legacy `placement_ids` are present, `placement_refs` wins and receivers MUST ignore `placement_ids`. Only used during upload to media buy - not stored in creative library.", + min_length=1, + ), + ] = None + placement_ids: Annotated[ + list[str] | None, + Field( + deprecated=True, + description='Legacy shorthand array of placement IDs where this creative should run when uploading via create_media_buy or update_media_buy. New senders SHOULD use `placement_refs` because placement IDs are publisher-scoped and strings are ambiguous in multi-publisher products. If omitted, creative runs on all buyer-targetable placements. If `placement_refs` is also present, receivers MUST ignore this field. Only used during upload to media buy - not stored in creative library.', + min_length=1, + ), + ] = None + industry_identifiers: Annotated[ + list[industry_identifier.IndustryIdentifier] | None, + Field( + description='Industry-standard or market-specific identifiers for this creative (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). In broadcast and scheduled audio/video buying, these identifiers tie the creative to rotation instructions, clearance records, and traffic systems. A creative may have multiple identifiers when different systems reference the same asset. Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' + ), + ] = None + provenance: Annotated[ + provenance_1.Provenance | None, + Field( + description='Provenance metadata for this creative. Serves as the default provenance for all manifests and assets within this creative. A manifest or asset with its own provenance replaces this object entirely (no field-level merging).' + ), + ] = None + rights: Annotated[ + list[rights_constraint.RightsConstraint] | None, + Field( + description='Rights constraints that MUST survive sync, package assignment, and list readback. Buyer-carried constraints and references do not authorize serving; the seller evaluates them under media_buy.rights_attestations and its adcp.attestations policy.', + min_length=1, + ), + ] = None -# Explicit exports -__all__ = ["Assets", "CreativeAsset", "Input"] + @model_validator(mode='after') + def _require_schema_required_group(self) -> CreativeAsset: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('format_id',), ('format_kind',),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'CreativeAsset requires at least one of these field groups: format_id | format_kind' + ) diff --git a/src/adcp/types/domains/core/creative_assets.py b/src/adcp/types/domains/core/creative_assets.py index 7ea37e091..e4cdb5529 100644 --- a/src/adcp/types/domains/core/creative_assets.py +++ b/src/adcp/types/domains/core/creative_assets.py @@ -1,18 +1,32 @@ -"""Types declared by the AdCP ``core/creative_assets`` schema. +# generated by datamodel-codegen: +# filename: core/creative_assets.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.creative_assets import +from typing import Any, Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import Field, RootModel, StringConstraints -# ruff: noqa: E501, I001 -from __future__ import annotations +from .assets import asset_union + + +class CreativeAssets1(RootModel[list[asset_union.AssetVariant]]): + root: Annotated[list[asset_union.AssetVariant], Field(min_length=1)] -from adcp.types.generated_poc.core.creative_assets import CreativeAssets, CreativeAssets1 -# Explicit exports -__all__ = ["CreativeAssets", "CreativeAssets1"] +class CreativeAssets( + RootModel[dict[Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], asset_union.AssetVariant | CreativeAssets1]] +): + root: Annotated[ + dict[Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], asset_union.AssetVariant | CreativeAssets1], + Field( + description='Map of canonical asset-group or legacy asset identifiers to supplied creative assets. Values are either a single discriminated asset or a non-empty repeatable asset array.', + title='Creative Assets', + ), + ] + def __getattr__(self, name: str) -> Any: + """Proxy attribute access to the wrapped type.""" + if name.startswith('_'): + raise AttributeError(name) + return getattr(self.root, name) diff --git a/src/adcp/types/domains/core/creative_assignment.py b/src/adcp/types/domains/core/creative_assignment.py index 28207db8f..c2b5857fe 100644 --- a/src/adcp/types/domains/core/creative_assignment.py +++ b/src/adcp/types/domains/core/creative_assignment.py @@ -1,18 +1,70 @@ -"""Types declared by the AdCP ``core/creative_assignment`` schema. +# generated by datamodel-codegen: +# filename: core/creative_assignment.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.creative_assignment import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import placement_ref + + +class RotationMode(StrEnum): + weighted = 'weighted' + even = 'even' + sequential = 'sequential' + random = 'random' -from adcp.types.generated_poc.core.creative_assignment import CreativeAssignment, RotationMode -# Explicit exports -__all__ = ["CreativeAssignment", "RotationMode"] +class CreativeAssignment(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + creative_id: Annotated[str, Field(description='Unique identifier for the creative')] + weight: Annotated[ + StrictFloat | None, + Field( + description="Relative delivery weight for this creative (0–100). Valid when the package's effective rotation_mode is weighted, including the backward-compatible default when rotation_mode is omitted. Weights determine impression distribution proportionally — a creative with weight 2 gets twice the delivery of weight 1. When omitted, the creative receives equal weight with other unweighted creatives. A weight of 0 means the creative is assigned but paused (receives no delivery).", + ge=0.0, + le=100.0, + ), + ] = None + rotation_mode: Annotated[ + RotationMode | None, + Field( + description='Package-scoped rotation policy repeated on assignment rows for wire compatibility. Omission means weighted, preserving existing weight behavior. Every assignment in a package MUST resolve to the same effective mode: weighted uses relative weights; even balances delivery across eligible assignments; sequential cycles through sequence_position in ascending order within each group; random makes an independent uniform selection from eligible assignments. Sellers MUST reject conflicting effective modes rather than choose one by array order.' + ), + ] = None + group_id: Annotated[ + str | None, + Field( + description="Package-local creative pool identifier. The identifier has no meaning outside this package. Assignments that omit group_id belong to the package's default group; one eligible creative is selected from each applicable group per serving opportunity.", + min_length=1, + ), + ] = None + sequence_position: Annotated[ + SchemaInt | None, + Field( + description="One-based order within the assignment's package-local group. Required only for sequential rotation and unique within that group.", + ge=1, + ), + ] = None + placement_refs: Annotated[ + list[placement_ref.PlacementReference] | None, + Field( + description="Optional structured product-context refs routing this creative within already-purchased package inventory. These items always use placement-ref product-context semantics, even when tolerated additional members make an item resemble placement-identity. Receivers match only against the package's committed placement set; kind and seller_agent are non-authoritative for routing and MUST NOT expand or reinterpret that set. A receiver MUST reject a ref when the enclosing product and committed set do not yield one unambiguous match. This field never narrows purchased inventory; use targeting_overlay.placement_selection for that. Every ref MUST fall within the package's committed placement selection. New senders SHOULD include publisher_domain for publisher-catalog placements. When omitted, the creative runs across the purchased placements compatible with its format. If both placement_refs and legacy placement_ids are present, placement_refs wins.", + min_length=1, + ), + ] = None + placement_ids: Annotated[ + list[str] | None, + Field( + deprecated=True, + description='Legacy shorthand routing IDs within already-purchased inventory. This field never narrows purchased inventory; use targeting_overlay.placement_selection. New senders SHOULD use placement_refs because IDs are publisher-scoped. If placement_refs is also present, receivers MUST ignore this field.', + min_length=1, + ), + ] = None diff --git a/src/adcp/types/domains/core/creative_brief.py b/src/adcp/types/domains/core/creative_brief.py index 9d0dad900..5a5c4a3f4 100644 --- a/src/adcp/types/domains/core/creative_brief.py +++ b/src/adcp/types/domains/core/creative_brief.py @@ -1,32 +1,146 @@ -"""Types declared by the AdCP ``core/creative_brief`` schema. +# generated by datamodel-codegen: +# filename: core/creative_brief.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.creative_brief import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field + +from ..enums import disclosure_persistence, disclosure_position +from . import reference_asset + + +class Objective(StrEnum): + awareness = 'awareness' + consideration = 'consideration' + conversion = 'conversion' + retention = 'retention' + engagement = 'engagement' + + +class Messaging(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + headline: Annotated[str | None, Field(description='Primary headline')] = None + tagline: Annotated[str | None, Field(description='Supporting tagline or sub-headline')] = None + cta: Annotated[str | None, Field(description='Call-to-action text')] = None + key_messages: Annotated[ + list[str] | None, Field(description='Key messages to communicate in priority order') + ] = None + + +class Jurisdiction(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{2}(-[A-Z0-9]{1,3})?$'} + + +class RequiredDisclosure(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + text: Annotated[str, Field(description='The disclosure text that must appear in the creative')] + position: Annotated[ + disclosure_position.DisclosurePosition | None, + Field( + description='Where the disclosure should appear within the creative. prominent: clearly visible in the main creative area; footer: at the bottom of visual creatives; audio: spoken in audio/video creatives; subtitle: displayed in the subtitle or closed-caption track; overlay: superimposed on video content; end_card: displayed on video end card; pre_roll: spoken or displayed before main content; companion: in companion ad unit alongside primary creative' + ), + ] = None + jurisdictions: Annotated[ + list[Jurisdiction] | None, + Field( + description="Jurisdictions where this disclosure is required. ISO 3166-1 alpha-2 country codes or ISO 3166-2 subdivision codes (e.g., 'US', 'GB', 'US-NJ', 'CA-QC'). If omitted, the disclosure applies to all jurisdictions in the campaign.", + min_length=1, + ), + ] = None + regulation: Annotated[ + str | None, + Field( + description="The regulation or legal authority requiring this disclosure (e.g., 'SEC Rule 156', 'FCA COBS 4.5', 'FDA 21 CFR 202')" + ), + ] = None + min_duration_ms: Annotated[ + SchemaInt | None, + Field( + description='Minimum display duration in milliseconds. For video/audio disclosures, how long the disclosure must be visible or audible. For static formats, how long the disclosure must remain on screen before any auto-advance.', + ge=1, + ), + ] = None + language: Annotated[ + str | None, + Field( + description="Language of the disclosure text as a BCP 47 language tag (e.g., 'en', 'fr-CA', 'es'). When omitted, the disclosure is assumed to match the creative's language." + ), + ] = None + persistence: Annotated[ + disclosure_persistence.DisclosurePersistence | None, + Field( + description='How long the disclosure must persist during content playback or display. When omitted, the creative agent should use the most restrictive persistence mode supported by the target format.' + ), + ] = None + + +class Compliance(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + required_disclosures: Annotated[ + list[RequiredDisclosure] | None, + Field( + description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', + min_length=1, + ), + ] = None + prohibited_claims: Annotated[ + list[str] | None, + Field( + description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', + min_length=1, + ), + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.creative_brief import ( - Compliance, - CreativeBrief, - Jurisdiction, - Messaging, - Objective, - RequiredDisclosure, -) - -# Explicit exports -__all__ = [ - "Compliance", - "CreativeBrief", - "Jurisdiction", - "Messaging", - "Objective", - "RequiredDisclosure", -] +class CreativeBrief(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + name: Annotated[str, Field(description='Campaign or flight name for identification')] + objective: Annotated[ + Objective | None, + Field( + description='Campaign objective that guides creative tone and call-to-action strategy' + ), + ] = None + tone: Annotated[ + str | None, + Field( + description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" + ), + ] = None + audience: Annotated[ + str | None, Field(description='Target audience description for this campaign') + ] = None + territory: Annotated[ + str | None, + Field(description='Creative territory or positioning the campaign should occupy'), + ] = None + messaging: Annotated[ + Messaging | None, Field(description='Messaging framework for the campaign') + ] = None + reference_assets: Annotated[ + list[reference_asset.ReferenceAsset] | None, + Field( + description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' + ), + ] = None + compliance: Annotated[ + Compliance | None, + Field( + description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' + ), + ] = None diff --git a/src/adcp/types/domains/core/creative_consumption.py b/src/adcp/types/domains/core/creative_consumption.py index 65d3567ee..1028709f6 100644 --- a/src/adcp/types/domains/core/creative_consumption.py +++ b/src/adcp/types/domains/core/creative_consumption.py @@ -1,18 +1,34 @@ -"""Types declared by the AdCP ``core/creative_consumption`` schema. +# generated by datamodel-codegen: +# filename: core/creative_consumption.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.creative_consumption import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictFloat -from adcp.types.generated_poc.core.creative_consumption import CreativeConsumption -# Explicit exports -__all__ = ["CreativeConsumption"] +class CreativeConsumption(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + tokens: Annotated[ + SchemaInt | None, + Field(description='LLM or generation tokens consumed during creative generation.', ge=0), + ] = None + images_generated: Annotated[ + SchemaInt | None, Field(description='Number of images produced during generation.', ge=0) + ] = None + renders: Annotated[ + SchemaInt | None, + Field(description='Number of render passes performed (video, animation).', ge=0), + ] = None + duration_seconds: Annotated[ + StrictFloat | None, + Field( + description='Processing time billed, in seconds. For compute-time pricing models.', + ge=0.0, + ), + ] = None diff --git a/src/adcp/types/domains/core/creative_delivery_metrics.py b/src/adcp/types/domains/core/creative_delivery_metrics.py index c2bbd5fcd..3f5ce36b8 100644 --- a/src/adcp/types/domains/core/creative_delivery_metrics.py +++ b/src/adcp/types/domains/core/creative_delivery_metrics.py @@ -1,18 +1,33 @@ -"""Types declared by the AdCP ``core/creative_delivery_metrics`` schema. +# generated by datamodel-codegen: +# filename: core/creative_delivery_metrics.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.creative_delivery_metrics import +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from .delivery_metrics import DeliveryMetrics -from adcp.types.generated_poc.core.creative_delivery_metrics import CreativeDeliveryMetrics -# Explicit exports -__all__ = ["CreativeDeliveryMetrics"] +class CreativeDeliveryMetrics(DeliveryMetrics): + creative_id: Annotated[ + str, Field(description='Creative identifier matching the creative assignment') + ] + creative_name: Annotated[ + str | None, + Field( + description='Optional human-readable creative name current when the report is generated. Convenience metadata only: names may change and buyers MUST use creative_id as the stable identity.' + ), + ] = None + weight: Annotated[ + StrictFloat | None, + Field( + description='Observed delivery share for this creative within the package during the reporting period, expressed as a percentage (0-100). Reflects actual delivery distribution, not a configured setting.', + ge=0.0, + le=100.0, + ), + ] = None + impressions: Any + spend: Any diff --git a/src/adcp/types/domains/core/creative_filters.py b/src/adcp/types/domains/core/creative_filters.py index 7b695b815..380bb8dca 100644 --- a/src/adcp/types/domains/core/creative_filters.py +++ b/src/adcp/types/domains/core/creative_filters.py @@ -1,18 +1,138 @@ -"""Types declared by the AdCP ``core/creative_filters`` schema. +# generated by datamodel-codegen: +# filename: core/creative_filters.json +# timestamp: 2026-10-05T13:49:50+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.creative_filters import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import asset_content_type, creative_status, indicator_type +from . import account_ref +from . import ext as ext_1 +from . import format_id -from adcp.types.generated_poc.core.creative_filters import CreativeFilters -# Explicit exports -__all__ = ["CreativeFilters"] +class CreativeFilters(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + accounts: Annotated[ + list[account_ref.AccountReference] | None, + Field( + description='Filter creatives by owning accounts. Useful for agencies managing multiple client accounts.', + min_length=1, + ), + ] = None + statuses: Annotated[ + list[creative_status.CreativeStatus] | None, + Field(description='Filter by creative approval statuses', min_length=1), + ] = None + tags: Annotated[ + list[str] | None, + Field(description='Filter by creative tags (all tags must match)', min_length=1), + ] = None + tags_any: Annotated[ + list[str] | None, + Field(description='Filter by creative tags (any tag must match)', min_length=1), + ] = None + name_contains: Annotated[ + str | None, + Field(description='Filter by creative names containing this text (case-insensitive)'), + ] = None + creative_ids: Annotated[ + list[str] | None, + Field(description='Filter by specific creative IDs', max_length=100, min_length=1), + ] = None + created_after: Annotated[ + AwareDatetime | None, + Field(description='Filter creatives created after this date (ISO 8601)'), + ] = None + created_before: Annotated[ + AwareDatetime | None, + Field(description='Filter creatives created before this date (ISO 8601)'), + ] = None + updated_after: Annotated[ + AwareDatetime | None, + Field(description='Filter creatives last updated after this date (ISO 8601)'), + ] = None + updated_before: Annotated[ + AwareDatetime | None, + Field(description='Filter creatives last updated before this date (ISO 8601)'), + ] = None + assigned_to_packages: Annotated[ + list[str] | None, + Field( + description='Filter creatives assigned to any of these packages. Sales-agent-specific — standalone creative agents SHOULD ignore this filter.', + min_length=1, + ), + ] = None + media_buy_ids: Annotated[ + list[str] | None, + Field( + description='Filter creatives assigned to any of these media buys. Sales-agent-specific — standalone creative agents SHOULD ignore this filter.', + min_length=1, + ), + ] = None + unassigned: Annotated[ + StrictBool | None, + Field( + description='Filter for unassigned creatives when true, assigned creatives when false. Sales-agent-specific — standalone creative agents SHOULD ignore this filter.' + ), + ] = None + has_served: Annotated[ + StrictBool | None, + Field( + description='When true, return only creatives that have served at least one impression. When false, return only creatives that have never served.' + ), + ] = None + indicator_types: Annotated[ + list[indicator_type.IndicatorType] | None, + Field( + description='Return creatives with at least one package assignment carrying any requested current indicator type. Values within this field use OR logic; this field composes with other filters using AND logic. Sales-agent-specific: sellers support this filter only when media_buy.relationship_notifications.projection_tasks includes list_creatives. Other agents SHOULD ignore it and apply remaining filters. Buyers needing exact results MUST verify capability support and paginate the outer result set; assignment_projection: matching bounds nested rows.', + min_length=1, + ), + ] = None + concept_ids: Annotated[ + list[str] | None, + Field( + description='Filter by creative concept IDs. Concepts group related creatives across sizes and formats (e.g., Flashtalking concepts, Celtra campaign folders, CM360 creative groups).', + min_length=1, + ), + ] = None + format_ids: Annotated[ + list[format_id.FormatReferenceStructuredObject] | None, + Field( + deprecated=True, + description='Deprecated in AdCP 3.2; removed in AdCP 4.0. Filter legacy named-format creatives. Use `format_kinds` for canonical libraries.', + min_length=1, + ), + ] = None + format_kinds: Annotated[ + list[str] | None, + Field( + description='Filter by canonical format kinds. Returns creatives matching any supplied kind.', + min_length=1, + ), + ] = None + asset_types: Annotated[ + list[asset_content_type.AssetContentType] | None, + Field( + description="Filter by asset types present on direct object values in the creative's top-level `assets` map. A creative matches when any directly assigned object has an `asset_type` in this array (OR within this field); this filter is conjunctive with every other active filter (AND across fields). Do not inspect array-valued slots or recurse into nested asset fields such as `cards[].media`; broader traversal is deferred. Agents that do not implement this filter MUST ignore it and apply the remaining filters rather than reject the request. Exact asset-type values use the shared AssetContentType vocabulary; `published_post` selects existing-published-post reference creatives without relying on publisher-specific format IDs.", + min_length=1, + ), + ] = None + has_variables: Annotated[ + StrictBool | None, + Field( + description='When true, return only creatives with dynamic variables (DCO). When false, return only static creatives.' + ), + ] = None + ext: Annotated[ + ext_1.ExtensionObject | None, + Field( + description='Vendor-namespaced extension parameters for seller- or platform-specific creative filter criteria not covered by standard fields. Keys MUST be namespaced under a vendor or platform key (e.g., ext.gam, ext.platform_x). Sellers MUST treat all values as untrusted buyer input; avoid unbounded logging or labels, and do not interpolate values into caller-visible error strings, LLM prompts, SQL queries, or system commands without sanitization. Persistent use of an extension key across multiple buyers is a signal to propose standardization.' + ), + ] = None diff --git a/src/adcp/types/domains/core/creative_item.py b/src/adcp/types/domains/core/creative_item.py index eb01f7b5f..8d7210e69 100644 --- a/src/adcp/types/domains/core/creative_item.py +++ b/src/adcp/types/domains/core/creative_item.py @@ -1,18 +1,65 @@ -"""Types declared by the AdCP ``core/creative_item`` schema. +# generated by datamodel-codegen: +# filename: core/creative_item.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.creative_item import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.creative_item import CreativeItem, CreativeItem1, CreativeItem2 +class CreativeItem1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_kind: Annotated[ + Literal['media'], + Field(description='Discriminator indicating this is a media asset with content_uri'), + ] = 'media' + asset_type: Annotated[ + str, + Field( + description='Type of asset. Common types: thumbnail_image, product_image, featured_image, logo' + ), + ] + asset_id: Annotated[ + str, Field(description='Unique identifier for the asset within the creative') + ] + content_uri: Annotated[AnyUrl, Field(description='URL for media assets (images, videos, etc.)')] + + +class CreativeItem2(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_kind: Annotated[ + Literal['text'], + Field(description='Discriminator indicating this is a text asset with content'), + ] = 'text' + asset_type: Annotated[ + str, + Field( + description='Type of asset. Common types: headline, body_text, cta_text, price_text, sponsor_name, author_name, click_url' + ), + ] + asset_id: Annotated[ + str, Field(description='Unique identifier for the asset within the creative') + ] + content: Annotated[ + str | list[str], + Field( + description='Text content for text-based assets like headlines, body text, CTA text, etc.' + ), + ] + -# Explicit exports -__all__ = ["CreativeItem", "CreativeItem1", "CreativeItem2"] +CreativeItem = Annotated[ + CreativeItem1 | CreativeItem2, + Field( + description='Item within a multi-asset creative format. Used for carousel products, native ad components, and other formats composed of multiple distinct elements.', + discriminator='asset_kind', + title='Creative Item', + ), +] diff --git a/src/adcp/types/domains/core/creative_locale_policy.py b/src/adcp/types/domains/core/creative_locale_policy.py index 16a75a49a..947ce6956 100644 --- a/src/adcp/types/domains/core/creative_locale_policy.py +++ b/src/adcp/types/domains/core/creative_locale_policy.py @@ -1,18 +1,26 @@ -"""Types declared by the AdCP ``core/creative_locale_policy`` schema. +# generated by datamodel-codegen: +# filename: core/creative_locale_policy.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.creative_locale_policy import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import locale_tag -from adcp.types.generated_poc.core.creative_locale_policy import CreativeLocalePolicy -# Explicit exports -__all__ = ["CreativeLocalePolicy"] +class CreativeLocalePolicy(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + accepted_language_ranges: Annotated[ + list[locale_tag.LanguageTag], + Field( + description='Concrete canonical BCP 47 language ranges accepted by this format option. RFC 4647 Basic Filtering is directional: seller range fr accepts variant fr-CA, but seller range fr-CA does not accept variant fr or fr-FR. Use zxx explicitly for language-neutral creative; und means unknown and is not a wildcard.', + max_length=50, + min_length=1, + ), + ] diff --git a/src/adcp/types/domains/core/creative_localization.py b/src/adcp/types/domains/core/creative_localization.py index 98c6da6aa..ac8e26459 100644 --- a/src/adcp/types/domains/core/creative_localization.py +++ b/src/adcp/types/domains/core/creative_localization.py @@ -1,32 +1,123 @@ -"""Types declared by the AdCP ``core/creative_localization`` schema. +# generated by datamodel-codegen: +# filename: core/creative_localization.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.creative_localization import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, RootModel, StringConstraints + +from . import locale_tag, localized_creative_asset + + +class UnmatchedLocaleAction(StrEnum): + serve_default = 'serve_default' + do_not_serve = 'do_not_serve' + + +class Source(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + locale_variant_id: Annotated[ + str, + Field( + description='Buyer-assigned stable identity for the source locale variant. This is a separate namespace from build_variant_id and the served execution variant_id.', + max_length=255, + min_length=1, + ), + ] + locale: locale_tag.LanguageTag + + +class LocaleFallback(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + language_range: Annotated[ + locale_tag.LanguageTag, + Field( + description='Canonical concrete BCP 47 range matched against progressively truncated requested ranges. Wildcards are not supported.' + ), + ] + locale_variant_id: Annotated[ + str, + Field( + description='Source or target locale variant to serve when this explicit fallback rule matches.', + max_length=255, + min_length=1, + ), + ] + + +class Assets(RootModel[list[localized_creative_asset.LocalizedCreativeAsset]]): + root: Annotated[list[localized_creative_asset.LocalizedCreativeAsset], Field(min_length=1)] + + +class TargetVariant(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + locale_variant_id: Annotated[ + str, + Field( + description='Buyer-assigned stable identity for this target locale variant. The same value round-trips through sync_creatives, list_creatives, and localized delivery reporting.', + max_length=255, + min_length=1, + ), + ] + locale: locale_tag.LanguageTag + assets: Annotated[ + dict[ + Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], + localized_creative_asset.LocalizedCreativeAsset | Assets, + ], + Field( + description='Materialized locale-specific asset overrides keyed by the same slot IDs as the source creative. Missing slots inherit source assets.' + ), + ] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.creative_localization import ( - Assets, - CreativeLocalization, - LocaleFallback, - Source, - TargetVariant, - UnmatchedLocaleAction, -) - -# Explicit exports -__all__ = [ - "Assets", - "CreativeLocalization", - "LocaleFallback", - "Source", - "TargetVariant", - "UnmatchedLocaleAction", -] +class CreativeLocalization(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + source: Annotated[ + Source, + Field( + description="Identity and locale of the source variant. Its asset payload is the parent creative's top-level `assets` object. Source identifies production provenance; it is not implicitly the serving default." + ), + ] + target_variants: Annotated[ + list[TargetVariant], + Field( + description='Complete desired target-locale set for this creative. May be empty for a monolingual source-only creative. Every target contains materialized locale-specific asset overrides. Missing slots inherit source assets; after inheritance every resolved variant MUST satisfy the selected creative format.', + max_length=50, + min_length=0, + ), + ] + locale_fallbacks: Annotated[ + list[LocaleFallback] | None, + Field( + description='Optional explicit language-family substitutions evaluated only when strict RFC 4647 Lookup finds no equal available tag for the current requested preference. For each preference in order, the seller checks progressively truncated ranges from most to least specific and applies the first rule whose language_range equals that candidate. A rule may map a regional request to a different materialized regional variant, but no substitution is inferred when a rule is absent.', + max_length=50, + min_length=1, + ), + ] = None + default_locale_variant_id: Annotated[ + str, + Field( + description='The source or target locale_variant_id used when neither strict RFC 4647 Lookup nor an explicit locale_fallbacks rule matches and unmatched_locale_action is serve_default.', + max_length=255, + min_length=1, + ), + ] + unmatched_locale_action: Annotated[ + UnmatchedLocaleAction, + Field( + description='Required seller behavior when neither strict RFC 4647 Lookup nor an explicit locale_fallbacks rule matches. serve_default serves default_locale_variant_id; do_not_serve makes this creative ineligible for that opportunity.' + ), + ] diff --git a/src/adcp/types/domains/core/creative_localization_readback.py b/src/adcp/types/domains/core/creative_localization_readback.py index c42782da7..b8c16b238 100644 --- a/src/adcp/types/domains/core/creative_localization_readback.py +++ b/src/adcp/types/domains/core/creative_localization_readback.py @@ -1,36 +1,135 @@ -"""Types declared by the AdCP ``core/creative_localization_readback`` schema. +# generated by datamodel-codegen: +# filename: core/creative_localization_readback.json +# timestamp: 2026-10-03T00:36:50+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.creative_localization_readback import +from adcp.types._str_enum import StrEnum +from typing import Any, Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, RootModel, StringConstraints + +from . import locale_tag, localized_creative_asset + + +class UnmatchedLocaleAction(StrEnum): + serve_default = 'serve_default' + do_not_serve = 'do_not_serve' + + +class LocaleFallback(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + language_range: locale_tag.LanguageTag + locale_variant_id: Annotated[str, Field(max_length=255, min_length=1)] + + +class ResolvedAssets1(RootModel[list[localized_creative_asset.LocalizedCreativeAsset]]): + root: Annotated[list[localized_creative_asset.LocalizedCreativeAsset], Field(min_length=1)] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.creative_localization_readback import ( - CreativeLocalizationReadback, - LocaleFallback, - ResolvedAssets, - ResolvedAssets1, - UnmatchedLocaleAction, - Variants, - Variants1, - Variants2, -) - -# Explicit exports -__all__ = [ - "CreativeLocalizationReadback", - "LocaleFallback", - "ResolvedAssets", - "ResolvedAssets1", - "UnmatchedLocaleAction", - "Variants", - "Variants1", - "Variants2", +class ResolvedAssets( + RootModel[ + dict[ + Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], + localized_creative_asset.LocalizedCreativeAsset | ResolvedAssets1, + ] + ] +): + root: Annotated[ + dict[ + Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], + localized_creative_asset.LocalizedCreativeAsset | ResolvedAssets1, + ], + Field(description='Complete resolved assets for this locale, keyed by creative slot.'), + ] + def __getattr__(self, name: str) -> Any: + """Proxy attribute access to the wrapped type.""" + if name.startswith('_'): + raise AttributeError(name) + return getattr(self.root, name) + + + +class Variants1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + locale_variant_id: Annotated[ + str, + Field( + description='Buyer-assigned stable locale-variant identity from the request.', + max_length=255, + min_length=1, + ), + ] + locale: locale_tag.LanguageTag + role: Literal['source'] = 'source' + assets: ResolvedAssets + + +class Variants2(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + locale_variant_id: Annotated[ + str, + Field( + description='Buyer-assigned stable locale-variant identity from the request.', + max_length=255, + min_length=1, + ), + ] + locale: locale_tag.LanguageTag + role: Literal['target'] = 'target' + assets: ResolvedAssets + + +Variants = Annotated[ + Variants1 | Variants2, + Field(discriminator='role'), ] + + +class CreativeLocalizationReadback(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + default_locale_variant_id: Annotated[ + str, + Field( + description='The source or target locale variant used after strict Lookup and explicit fallback rules both miss when unmatched_locale_action is serve_default.', + max_length=255, + min_length=1, + ), + ] + unmatched_locale_action: Annotated[ + UnmatchedLocaleAction, + Field( + description='Final action after strict Lookup and explicit locale_fallbacks both miss.' + ), + ] + locale_matching: Annotated[ + Literal['rfc4647_lookup'], + Field( + description="Strict RFC 4647 section 3.4 Lookup applied to an ordered language-preference list supplied by the seller's serving environment. AdCP does not carry or define how that list is derived. The seller progressively truncates each requested range and matches only an available canonical tag equal to that range; it does not prefix-match sibling regional tags." + ), + ] = 'rfc4647_lookup' + locale_fallbacks: Annotated[ + list[LocaleFallback] | None, + Field( + description='Exact buyer-declared language-family substitutions preserved from sync. For each requested preference, rules are checked from the most specific progressively truncated range to the least specific only after strict Lookup finds no equal available tag for that preference.', + max_length=50, + min_length=1, + ), + ] = None + variants: Annotated[ + list[Variants], + Field( + description='The source variant followed by every target variant, or only the source for monolingual topology. Assets are fully resolved after source inheritance. The enclosing creative status applies to the set as a whole.', + max_length=51, + min_length=1, + ), + ] diff --git a/src/adcp/types/domains/core/creative_manifest.py b/src/adcp/types/domains/core/creative_manifest.py index 085cce67b..3e89216d3 100644 --- a/src/adcp/types/domains/core/creative_manifest.py +++ b/src/adcp/types/domains/core/creative_manifest.py @@ -1,18 +1,124 @@ -"""Types declared by the AdCP ``core/creative_manifest`` schema. +# generated by datamodel-codegen: +# filename: core/creative_manifest.json +# timestamp: 2026-10-05T14:33:12+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.creative_manifest import +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, RootModel, StringConstraints, model_validator -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import brand_ref, creative_assets +from . import ext as ext_1 +from . import format_id as format_id_1 +from . import format_option_ref as format_option_ref_1 +from . import industry_identifier +from . import provenance as provenance_1 +from . import representation_selection as representation_selection_1 +from . import rights_constraint +from .assets import asset_union + + +def _normalize_asset_models(value: Any) -> Any: + if isinstance(value, AdCPBaseModel): + return value.model_dump(mode='json', exclude_none=True) + if isinstance(value, list): + return [_normalize_asset_models(item) for item in value] + return value + + +class Assets(RootModel[list[asset_union.AssetVariant]]): + root: Annotated[list[asset_union.AssetVariant], Field(min_length=1)] + + +class CreativeManifest(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + + @model_validator(mode='before') + @classmethod + def _coerce_standalone_assets(cls, data: Any) -> Any: + if not isinstance(data, dict) or not isinstance(data.get('assets'), dict): + return data + return { + **data, + 'assets': {key: _normalize_asset_models(value) for key, value in data['assets'].items()}, + } -from adcp.types.generated_poc.core.creative_manifest import Assets, CreativeManifest + format_id: Annotated[ + format_id_1.FormatReferenceStructuredObject | None, + Field( + deprecated=True, + description='**DEPRECATED in 3.2.** Legacy named-format path retained for 3.x compatibility. New manifests use canonical `format_kind` and, when product routing requires it, `format_option_ref`. Mutually exclusive with format_kind.', + ), + ] = None + format_kind: Annotated[ + str | None, + Field( + description="Canonical 3.2 path. The canonical format name this manifest targets (e.g., `image`, `video_hosted`, `audio_vast`, `seller_rendered_stateful_display`, `coordinated_placements`). Selects the contract against which the seller validates the manifest's assets. Mutually exclusive with deprecated `format_id`." + ), + ] = None + format_option_ref: Annotated[ + format_option_ref_1.FormatOptionReference | None, + Field( + description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product carries multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the manifest to a single declaration. Product-scoped refs require an enclosing target product/package context.' + ), + ] = None + representation_selection: Annotated[ + representation_selection_1.RepresentationSelection | None, + Field( + description='Present when this seller-bound manifest was selected from a CreativeRepresentationSet. Preserves creative, complete revision digest, and selected representation lineage through sync and reporting.' + ), + ] = None + assets: Annotated[ + dict[Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], asset_union.AssetVariant | Assets], + Field( + description="Map of slot keys to actual asset content. Legacy named-format path: each key matches an `asset_id` from the format's `assets` array (e.g., 'banner_image', 'clickthrough_url', 'video_file', 'vast_tag'). 3.1+ canonical-format path: each key matches an `asset_group_id` from the format's `slots` declaration drawn from the canonical vocabulary registry (e.g., 'images_landscape', 'video', 'published_post', 'landing_page_url', 'vast_tag', 'script', 'creative_brief'). Either path produces the same envelope shape; only the slot-key vocabulary differs.\n\nEach slot value is **either** a single asset object (most slots — image, video, published_post, vast_tag, landing_page_url, etc.) **or** an array of asset objects (slots with `min`/`max` counts on the format declaration — `cards` on `image_carousel`, `headlines` / `descriptions` / `images_landscape` on `responsive_creative`, etc.). Single-vs-array shape is governed by the format's `slots[].min` and `slots[].max` parameters: when `max > 1` (or when the slot is conceptually a pool), the value MUST be an array; when the slot is single-valued, the value MUST be a single object. Each asset value (single or array element) carries an `asset_type` discriminator (image, video, audio, vast, daast, text, markdown, url, html, css, webhook, javascript, brief, catalog, published_post, zip, card) that selects the matching asset schema. Validators with OpenAPI-style discriminator support use `asset_type` to report errors against only the selected branch instead of all branches." + ), + ] + component_assets: Annotated[ + dict[Annotated[str, StringConstraints(pattern=r'^[a-z][a-z0-9_]*$')], creative_assets.CreativeAssets] | None, + Field( + description="Component-addressed asset maps for `coordinated_placements`. Each key MUST match one `params.components[].component_id`; its value supplies that component's canonical slots. Shared assets remain in top-level `assets` and are injected only into components named by `shared_slots[].consumed_by`. This namespace allows two components to use the same canonical slot name, such as `image_main`, without collision. It MUST be absent for non-`coordinated_placements` manifests." + ), + ] = None + brand: Annotated[ + brand_ref.BrandReference | None, + Field( + description="Brand identity reference (BrandRef — `domain` plus optional `brand_id` for house-of-brands; plus optional inline `brand_kit_override` for per-creative tweaks where brand.json is missing/stale). When present, the seller pulls master brand identity (logo, palette, fonts, voice, and visual guidelines) from the brand's brand.json automatically; supported fields present in `brand_kit_override` take precedence, and all other master identity fields continue to come from brand.json. Catalogs supply product or item payload. Catalog item asset groups — including an item-level `logo` for a property or franchise — are item identity selected through format field bindings; they do not override brand.json's master logo or other brand identity fields. v2 formats no longer redeclare brand_logo / brand_colors / brand_voice as explicit slots — brand identity is implicit context." + ), + ] = None + rights: Annotated[ + list[rights_constraint.RightsConstraint] | None, + Field( + description='Rights constraints attached to this creative. Buyer-carried fields are informational until a serving party evaluates an issuer-bound attestation reference under its own policy. Only a verified, unexpired, unrevoked, digest-matched evaluation can support serving authorization; verification_url is never authority.' + ), + ] = None + industry_identifiers: Annotated[ + list[industry_identifier.IndustryIdentifier] | None, + Field( + description='Industry-standard or market-specific identifiers for this specific manifest (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). When present, overrides creative-level identifiers. Use when different format versions of the same source creative have distinct traffic identifiers (e.g., the :15 and :30 cuts, or separate TV and radio versions). Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' + ), + ] = None + provenance: Annotated[ + provenance_1.Provenance | None, + Field( + description='Provenance metadata for this creative manifest. Serves as the default provenance for all assets in this manifest. An asset with its own provenance replaces this object entirely (no field-level merging).' + ), + ] = None + ext: ext_1.ExtensionObject | None = None -# Explicit exports -__all__ = ["Assets", "CreativeManifest"] + @model_validator(mode='after') + def _require_schema_required_group(self) -> CreativeManifest: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('format_id',), ('format_kind',),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'CreativeManifest requires at least one of these field groups: format_id | format_kind' + ) diff --git a/src/adcp/types/domains/core/creative_operation_format_declaration.py b/src/adcp/types/domains/core/creative_operation_format_declaration.py index c3b37d97b..939a09e67 100644 --- a/src/adcp/types/domains/core/creative_operation_format_declaration.py +++ b/src/adcp/types/domains/core/creative_operation_format_declaration.py @@ -1,21 +1,63 @@ -"""Types declared by the AdCP ``core/creative_operation_format_declaration`` schema. +# generated by datamodel-codegen: +# filename: core/creative_operation_format_declaration.json +# timestamp: 2026-10-05T13:49:50+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.creative_operation_format_declaration import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import channels +from . import creative_locale_policy, format_id, macro_resolution_capability, platform_extension_ref + + +class SellerPreference(StrEnum): + preferred = 'preferred' + accepted = 'accepted' + discouraged = 'discouraged' -from adcp.types.generated_poc.core.creative_operation_format_declaration import ( - CreativeOperationFormatDeclaration, - SellerPreference, -) -# Explicit exports -__all__ = ["CreativeOperationFormatDeclaration", "SellerPreference"] +class CreativeOperationFormatDeclaration(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description='Stable publisher- or product-declaration identity that this creative operation can satisfy. When publisher_domain is present, this field is required.' + ), + ] = None + publisher_domain: Annotated[ + str | None, + Field( + description='Publisher namespace for format_option_id when this operation claims compatibility with a publisher declaration.', + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Creative-route processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. These capabilities describe build, validation, or preview processing and do not grant seller production authority.', + min_length=1, + ), + ] = None + technical_requirements_complete: StrictBool | None = None + display_name: str | None = None + sample_render_url: AnyUrl | None = None + applies_to_channels: list[channels.MediaChannel] | None = None + seller_preference: SellerPreference | None = None + locale_policy: creative_locale_policy.CreativeLocalePolicy | None = None + canonical_formats_only: StrictBool | None = False + experimental: StrictBool | None = False + format_shape: str | None = None + v1_format_ref: Annotated[ + list[format_id.FormatReferenceStructuredObject] | None, Field(min_length=1) + ] = None + format_schema: platform_extension_ref.PlatformExtensionReference | None = None + format_kind: str + params: Annotated[ + dict[str, Any], + Field( + description='Canonical creative-shape parameters. Validate against the schema selected by format_kind; custom params validate against the fetched format_schema.' + ), + ] diff --git a/src/adcp/types/domains/core/creative_policy.py b/src/adcp/types/domains/core/creative_policy.py index adeed056f..e7e2b1f8b 100644 --- a/src/adcp/types/domains/core/creative_policy.py +++ b/src/adcp/types/domains/core/creative_policy.py @@ -1,22 +1,102 @@ -"""Types declared by the AdCP ``core/creative_policy`` schema. +# generated by datamodel-codegen: +# filename: core/creative_policy.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.creative_policy import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import co_branding_requirement, landing_page_requirement + + +class ProvenanceRequirements(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + require_digital_source_type: Annotated[ + StrictBool | None, + Field( + description='When true, the seller requires creatives to include a `digital_source_type` field in their provenance, set to a valid value from the `digital-source-type` enum (not null or absent). Submissions that omit this field are rejected with `PROVENANCE_DIGITAL_SOURCE_TYPE_MISSING`. Supports EU AI Act Art. 50 and CA SB 942 compliance workflows where AI disclosure metadata must be present at the protocol level.' + ), + ] = None + require_synthetic_depiction: Annotated[ + StrictBool | None, + Field( + description='When true, the seller requires creatives to include an assessed `synthetic_depiction` boolean in their resolved provenance. Both true and false satisfy the requirement; absence means unassessed and is rejected with `PROVENANCE_SYNTHETIC_DEPICTION_MISSING`. This gate requires a declaration only — it does not establish consent, legality, or independent verification.' + ), + ] = None + require_disclosure_metadata: Annotated[ + StrictBool | None, + Field( + description='When true, the seller requires creatives to include a `disclosure` object in their provenance with `disclosure.required` set to a boolean value (true or false). When `disclosure.required` is true, at least one entry in `disclosure.jurisdictions` is expected. Submissions that omit `disclosure.required` are rejected with `PROVENANCE_DISCLOSURE_MISSING`.' + ), + ] = None + require_embedded_provenance: Annotated[ + StrictBool | None, + Field( + description='When true, the seller requires creatives to include at least one `embedded_provenance` entry. For pipelines where sidecar metadata is stripped by intermediaries, this ensures provenance data persists through delivery. Submissions that omit `embedded_provenance` are rejected with `PROVENANCE_EMBEDDED_MISSING`.' + ), + ] = None + + +class AcceptedVerifier(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + agent_url: Annotated[ + AnyUrl, + Field( + description="URL of the governance agent. MUST use the `https://` scheme. The seller calls this URL via `get_creative_features` to verify a buyer's claim; the seller has already vetted the endpoint and accepts responsibility for outbound calls to it." + ), + ] + feature_id: Annotated[ + str | None, + Field( + description="Optional canonical `feature_id` the seller will request against this agent (e.g., `encypher.markers_present_v2`). When present, the buyer's `verify_agent.feature_id` SHOULD either match this value or be omitted. When absent, the seller selects a feature from the agent's `governance.creative_features` catalog at evaluation time. Resolves the selector ambiguity that would otherwise let two compliant receivers reach different verdicts." + ), + ] = None + providers: Annotated[ + list[str] | None, + Field( + description="Optional `provider` labels this agent verifies (e.g., `['Encypher', 'Digimarc']`). When present, sellers SHOULD only invoke this agent for `embedded_provenance[]` / `watermarks[]` entries whose `provider` field matches one of these labels — letting buyers pre-flight whether their attached evidence is verifiable against the seller's allowlist. When absent, the agent is treated as provider-agnostic.", + min_length=1, + ), + ] = None -from adcp.types.generated_poc.core.creative_policy import ( - AcceptedVerifier, - CreativePolicy, - ProvenanceRequirements, -) -# Explicit exports -__all__ = ["AcceptedVerifier", "CreativePolicy", "ProvenanceRequirements"] +class CreativePolicy(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + co_branding: Annotated[ + co_branding_requirement.CoBrandingRequirement, Field(description='Co-branding requirement') + ] + landing_page: Annotated[ + landing_page_requirement.LandingPageRequirement, + Field(description='Landing page requirements'), + ] + templates_available: Annotated[ + StrictBool, Field(description='Whether creative templates are provided') + ] + provenance_required: Annotated[ + StrictBool | None, + Field( + description='Whether creatives must include provenance metadata. When true, the seller requires buyers to attach provenance declarations to creative submissions. The seller may independently verify claims via get_creative_features.' + ), + ] = None + provenance_requirements: Annotated[ + ProvenanceRequirements | None, + Field( + description='Structured provenance requirements for creatives. Refines `provenance_required`: when `provenance_required` is true, the fields in this object specify which provenance features the seller requires. When `provenance_required` is false or absent, this object SHOULD be absent; if present, receivers MUST ignore it. Existing seller agents that do not read this object are unaffected; the wire shape does not change for them. Sellers that publish a requirement here MUST enforce it on creative submission: a `sync_creatives` request that omits a required field is rejected with the corresponding `PROVENANCE_*` error code (see error-code.json), and a creative whose provenance claim is contradicted by an independent verification (`get_creative_features` against a governance agent the seller operates or has allowlisted via `accepted_verifiers`) is rejected with `PROVENANCE_CLAIM_CONTRADICTED`. This is the structural-rejection surface; the truth-of-claim surface lives in `get_creative_features`. Field-level requirements are seller-enforced — JSON Schema validation does not check them.' + ), + ] = None + accepted_verifiers: Annotated[ + list[AcceptedVerifier] | None, + Field( + description='Governance agents the seller operates, has allowlisted, or otherwise trusts to verify provenance claims via `get_creative_features`. Buyers attaching a `verify_agent` pointer on `embedded_provenance[]` or `watermarks[]` MUST select an `agent_url` that appears in this list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments) - the buyer is *representing* that they used a verifier the seller will recognize, not asserting unilateral routing. Sellers MUST reject `sync_creatives` submissions whose `verify_agent.agent_url` does not match any entry here with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. The seller is the verifier-of-record: it is the seller, not the buyer, that decides which agent it will call. Publishing the list lets buyers pre-flight their creative shape against `get_products` and lets multiple buyers converge on the same verifier without coordinating with each other.', + min_length=1, + ), + ] = None diff --git a/src/adcp/types/domains/core/creative_representation.py b/src/adcp/types/domains/core/creative_representation.py index 9eb9f1b11..9b2021655 100644 --- a/src/adcp/types/domains/core/creative_representation.py +++ b/src/adcp/types/domains/core/creative_representation.py @@ -1,18 +1,72 @@ -"""Types declared by the AdCP ``core/creative_representation`` schema. +# generated by datamodel-codegen: +# filename: core/creative_representation.json +# timestamp: 2026-10-05T14:13:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.creative_representation import +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, model_validator + +from .creative_manifest import CreativeManifest + + +class Source(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + system: Annotated[str, Field(description='Source system namespace.', min_length=1)] + source_representation: Annotated[ + str, + Field( + description="Source system's human-readable name for this representation.", min_length=1 + ), + ] + source_id: Annotated[ + str | None, + Field(description='Optional immutable source-system identifier for this representation.'), + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.creative_representation import CreativeRepresentation, Source +class CreativeRepresentation(CreativeManifest): + model_config = ConfigDict( + extra='allow', + json_schema_extra={ + 'not': { + 'anyOf': [ + {'required': ['format_id']}, + {'required': ['format_option_ref']}, + {'required': ['representation_selection']}, + ] + } + }, + ) + representation_id: Annotated[ + str, + Field( + description='Stable representation identifier, unique within the enclosing CreativeRepresentationSet revision.', + min_length=1, + pattern='^[a-zA-Z0-9_-]+$', + ), + ] + source: Source + format_kind: Annotated[ + str, + Field( + description="Canonical 3.2 path. The canonical format name this manifest targets (e.g., `image`, `video_hosted`, `audio_vast`, `seller_rendered_stateful_display`, `coordinated_placements`). Selects the contract against which the seller validates the manifest's assets. Mutually exclusive with deprecated `format_id`." + ), + ] -# Explicit exports -__all__ = ["CreativeRepresentation", "Source"] + @model_validator(mode='before') + @classmethod + def _reject_seller_bound_manifest_fields(cls, data: Any) -> Any: + """Representations cannot carry seller-side manifest selectors.""" + if isinstance(data, dict): + forbidden = ('format_id', 'format_option_ref', 'representation_selection') + present = [field for field in forbidden if field in data] + if present: + raise ValueError( + 'creative representations must not include ' + ', '.join(present) + ) + return data diff --git a/src/adcp/types/domains/core/creative_representation_set.py b/src/adcp/types/domains/core/creative_representation_set.py index e62bc7d08..acb11b8f9 100644 --- a/src/adcp/types/domains/core/creative_representation_set.py +++ b/src/adcp/types/domains/core/creative_representation_set.py @@ -1,18 +1,53 @@ -"""Types declared by the AdCP ``core/creative_representation_set`` schema. +# generated by datamodel-codegen: +# filename: core/creative_representation_set.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.creative_representation_set import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import creative_representation, creative_revision_id +from . import provenance as provenance_1 -from adcp.types.generated_poc.core.creative_representation_set import CreativeRepresentationSet -# Explicit exports -__all__ = ["CreativeRepresentationSet"] +class CreativeRepresentationSet(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + creative_id: Annotated[ + str, + Field( + description='Stable logical creative identifier across all representations and later seller-bound manifests.', + min_length=1, + ), + ] + revision_id: Annotated[ + creative_revision_id.CreativeRevisionId, + Field( + description='Buyer-assigned immutable revision identity for this complete representation set. Changing any revision-bearing part of the set requires a new revision_id; selection does not.' + ), + ] + revision_content_digest: Annotated[ + str, + Field( + description='SHA-256 of the RFC 8785 JCS canonical revision content defined by this schema. The producer computes it over the complete representation set after removing exactly the top-level $schema, creative_id, revision_id, revision_content_digest, and name properties. `$schema` is transport/schema-location metadata and never mints creative content identity. Resolvers MUST recompute and reject a mismatch; selection readback carries the verified digest so downstream sellers bind the revision to the complete source set rather than only the selected manifest.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] + name: Annotated[str, Field(min_length=1)] + representations: Annotated[ + list[creative_representation.CreativeRepresentation], + Field( + description="Complete ordered set of equivalent representations. `representation_id` values MUST be unique within this array. Unsupported entries remain byte-for-byte unchanged; neither filtering nor selection changes this revision's canonical content.", + min_length=1, + ), + ] + provenance: Annotated[ + provenance_1.Provenance | None, + Field( + description='Default provenance for every retained representation and for the selected seller-bound output unless a representation supplies a narrower provenance object.' + ), + ] = None diff --git a/src/adcp/types/domains/core/creative_revision_id.py b/src/adcp/types/domains/core/creative_revision_id.py index e379156d0..ab06a0778 100644 --- a/src/adcp/types/domains/core/creative_revision_id.py +++ b/src/adcp/types/domains/core/creative_revision_id.py @@ -1,18 +1,16 @@ -"""Types declared by the AdCP ``core/creative_revision_id`` schema. +# generated by datamodel-codegen: +# filename: core/creative_revision_id.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.creative_revision_id import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.creative_revision_id import CreativeRevisionId +from adcp.types._scalar import ScalarStr + -# Explicit exports -__all__ = ["CreativeRevisionId"] +class CreativeRevisionId(ScalarStr): + __slots__ = () + _constraints = {'max_length': 255, 'min_length': 1} + _json_schema_extra = { + 'description': 'Buyer-assigned identity for one immutable input-content state of a durable creative. Scoped to the parent creative_id. Seller transcoding, normalization, and delivery representations do not change this identity.', + 'title': 'Creative Revision ID', + } diff --git a/src/adcp/types/domains/core/creative_variable.py b/src/adcp/types/domains/core/creative_variable.py index 9335f8413..1f53dc90c 100644 --- a/src/adcp/types/domains/core/creative_variable.py +++ b/src/adcp/types/domains/core/creative_variable.py @@ -1,18 +1,47 @@ -"""Types declared by the AdCP ``core/creative_variable`` schema. +# generated by datamodel-codegen: +# filename: core/creative_variable.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.creative_variable import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.creative_variable import CreativeVariable, VariableType +class VariableType(StrEnum): + text = 'text' + image = 'image' + video = 'video' + audio = 'audio' + url = 'url' + number = 'number' + boolean = 'boolean' + color = 'color' + date = 'date' + -# Explicit exports -__all__ = ["CreativeVariable", "VariableType"] +class CreativeVariable(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + variable_id: Annotated[str, Field(description='Variable identifier on the creative platform')] + name: Annotated[str, Field(description='Human-readable variable name')] + variable_type: Annotated[ + VariableType, + Field( + description='Data type of the variable. Each type represents a semantic content slot: text (headlines, body copy), image/video/audio (media URLs), url (clickthrough or tracking URLs), number (prices, counts), boolean (conditional flags like show_discount or is_raining), color (hex color values), date (ISO 8601 date-time for countdowns and offer expirations).' + ), + ] + default_value: Annotated[ + str | None, + Field( + description='Default value used when no dynamic value is provided at serve time. All types are string-encoded: text/image/video/audio/url as literal strings, number as decimal (e.g., "42.99"), boolean as "true"/"false", color as "#RRGGBB", date as ISO 8601 (e.g., "2026-12-25T00:00:00Z").' + ), + ] = None + required: Annotated[ + StrictBool | None, + Field(description='Whether this variable must have a value for the creative to serve'), + ] = False diff --git a/src/adcp/types/domains/core/creative_variant.py b/src/adcp/types/domains/core/creative_variant.py index 00f1b754a..5b0571c9e 100644 --- a/src/adcp/types/domains/core/creative_variant.py +++ b/src/adcp/types/domains/core/creative_variant.py @@ -1,22 +1,79 @@ -"""Types declared by the AdCP ``core/creative_variant`` schema. +# generated by datamodel-codegen: +# filename: core/creative_variant.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.creative_variant import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import creative_manifest, creative_revision_id +from . import ext as ext_1 +from . import identifier +from .delivery_metrics import DeliveryMetrics + + +class Artifact(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + property_id: Annotated[ + identifier.Identifier, Field(description='Property where the artifact appears') + ] + artifact_id: Annotated[str, Field(description='Artifact identifier within the property')] + + +class GenerationContext(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + context_type: Annotated[ + str | None, + Field( + description="Type of context that triggered generation (e.g., 'web_page', 'conversational', 'search', 'app', 'dooh')" + ), + ] = None + artifact: Annotated[ + Artifact | None, + Field( + description='Reference to the content-standards artifact that provided the generation context. Links this variant to the specific piece of content (article, video, podcast segment, etc.) where the ad was placed.' + ), + ] = None + ext: ext_1.ExtensionObject | None = None -from adcp.types.generated_poc.core.creative_variant import ( - Artifact, - CreativeVariant, - GenerationContext, -) -# Explicit exports -__all__ = ["Artifact", "CreativeVariant", "GenerationContext"] +class CreativeVariant(DeliveryMetrics): + variant_id: Annotated[ + str, + Field( + description='Agent-assigned served-execution identifier. The legacy contract scopes uniqueness to the agent and creative. When the agent advertises creative.supports_revisions, the identifier is agent-unique and MUST NOT be reused for another creative, source revision, locale, or rendered manifest. A revision-capable adapter whose native platform reuses an identifier maps each distinct execution to a distinct AdCP variant_id and may retain the native identifier in ext. When that agent supports variant preview, this ID is its unambiguous lookup key.' + ), + ] + revision_id: Annotated[ + creative_revision_id.CreativeRevisionId | None, + Field( + description='Buyer-authored input revision from which this served execution was derived. Required when that execution derives from a revision-aware creative. Historical reporting may legitimately return an older revision after list_creatives shows a newer current revision. Delivery optimization, generation, transcoding, or representation selection does not mint a revision.' + ), + ] = None + locale_variant_id: Annotated[ + str | None, + Field( + description='Buyer-assigned locale variant that supplied the served assets. Required for every delivered variant of a localized creative, including default fallback delivery. Omitted for unlocalized creatives.', + max_length=255, + min_length=1, + ), + ] = None + manifest: Annotated[ + creative_manifest.CreativeManifest | None, + Field( + description='The rendered creative manifest for this variant — the actual output that was served, not the input assets. In 3.2 it carries canonical `format_kind`, optional `format_option_ref`, and the resolved assets (specific headline, image, video, etc. the platform selected or generated). For Tier 2, shows which asset combination was picked. For Tier 3, contains the generated assets which may differ entirely from the input brand identity. Pass to preview_creative to re-render.' + ), + ] = None + generation_context: Annotated[ + GenerationContext | None, + Field( + description='Input signals that triggered generation of this variant (Tier 3). Describes why the platform created this specific variant. Platforms should provide summarized or anonymized signals rather than raw user input. For web contexts, may include page topic or URL. For conversational contexts, an anonymized content signal. For search, query category or intent. When the content context is managed through AdCP content standards, reference the artifact directly via the artifact field.' + ), + ] = None diff --git a/src/adcp/types/domains/core/daast_tracker_constraints.py b/src/adcp/types/domains/core/daast_tracker_constraints.py index 93aacfcc4..e28dfb3b8 100644 --- a/src/adcp/types/domains/core/daast_tracker_constraints.py +++ b/src/adcp/types/domains/core/daast_tracker_constraints.py @@ -1,24 +1,52 @@ -"""Types declared by the AdCP ``core/daast_tracker_constraints`` schema. +# generated by datamodel-codegen: +# filename: core/daast_tracker_constraints.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.daast_tracker_constraints import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import Field, RootModel -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import daast_version + + +class DaastEvent(StrEnum): + creativeView = 'creativeView' + start = 'start' + firstQuartile = 'firstQuartile' + midpoint = 'midpoint' + thirdQuartile = 'thirdQuartile' + complete = 'complete' + mute = 'mute' + unmute = 'unmute' + pause = 'pause' + resume = 'resume' + rewind = 'rewind' + skip = 'skip' + progress = 'progress' + close = 'close' + + +class DaastTarget(StrEnum): + linear = 'linear' + companion = 'companion' + + +class DaastOffset(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$'} + + +class DaastVersions(RootModel[list[daast_version.DaastVersion]]): + root: Annotated[list[daast_version.DaastVersion], Field(min_length=1)] -from adcp.types.generated_poc.core.daast_tracker_constraints import ( - DaastEvent, - DaastOffset, - DaastTarget, - DaastTrackerConstraints, - DaastVersions, -) -# Explicit exports -__all__ = ["DaastEvent", "DaastOffset", "DaastTarget", "DaastTrackerConstraints", "DaastVersions"] +class DaastTrackerConstraints(AdCPBaseModel): + daast_event: DaastEvent | None = None + target: DaastTarget | None = DaastTarget.linear + offset: DaastOffset | None = None + daast_versions: DaastVersions | None = None diff --git a/src/adcp/types/domains/core/data_provider_signal_selector.py b/src/adcp/types/domains/core/data_provider_signal_selector.py index 2f76bad37..8de6cfde2 100644 --- a/src/adcp/types/domains/core/data_provider_signal_selector.py +++ b/src/adcp/types/domains/core/data_provider_signal_selector.py @@ -1,32 +1,97 @@ -"""Types declared by the AdCP ``core/data_provider_signal_selector`` schema. +# generated by datamodel-codegen: +# filename: core/data_provider_signal_selector.json +# timestamp: 2026-10-04T00:53:27+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.data_provider_signal_selector import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field + + +class DataProviderSignalSelector1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + data_provider_domain: Annotated[ + str, + Field( + description="Domain where data provider's adagents.json is hosted (e.g., 'polk.com')", + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] + selection_type: Annotated[ + Literal['all'], + Field( + description='Discriminator indicating all signals from this data provider are included' + ), + ] = 'all' + + +class SignalId(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[a-zA-Z0-9_-]+$'} + + +class DataProviderSignalSelector2(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + data_provider_domain: Annotated[ + str, + Field( + description="Domain where data provider's adagents.json is hosted (e.g., 'polk.com')", + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] + selection_type: Annotated[ + Literal['by_id'], + Field(description='Discriminator indicating selection by specific signal IDs'), + ] = 'by_id' + signal_ids: Annotated[ + list[SignalId], + Field( + description="Specific signal IDs from the data provider's published signal definitions", + min_length=1, + ), + ] + + +class SignalTag(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[a-z0-9_-]+$'} + + +class DataProviderSignalSelector3(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + data_provider_domain: Annotated[ + str, + Field( + description="Domain where data provider's adagents.json is hosted (e.g., 'polk.com')", + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] + selection_type: Annotated[ + Literal['by_tag'], Field(description='Discriminator indicating selection by signal tags') + ] = 'by_tag' + signal_tags: Annotated[ + list[SignalTag], + Field( + description="Signal tags from the data provider's published signal definitions. Selector covers all signals with these tags", + min_length=1, + ), + ] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.data_provider_signal_selector import ( - DataProviderSignalSelector, - DataProviderSignalSelector1, - DataProviderSignalSelector2, - DataProviderSignalSelector3, - SignalId, - SignalTag, -) - -# Explicit exports -__all__ = [ - "DataProviderSignalSelector", - "DataProviderSignalSelector1", - "DataProviderSignalSelector2", - "DataProviderSignalSelector3", - "SignalId", - "SignalTag", +DataProviderSignalSelector = Annotated[ + DataProviderSignalSelector1 | DataProviderSignalSelector2 | DataProviderSignalSelector3, + Field( + description="Selects signals from a data provider's adagents.json signals[]. Used for product definitions and agent authorization. Supports three selection patterns: all signals, specific IDs, or by tags.", + discriminator='selection_type', + title='Data Provider Signal Selector', + ), ] diff --git a/src/adcp/types/domains/core/date_range.py b/src/adcp/types/domains/core/date_range.py index 29a8c77a7..6ce039d75 100644 --- a/src/adcp/types/domains/core/date_range.py +++ b/src/adcp/types/domains/core/date_range.py @@ -1,18 +1,19 @@ -"""Types declared by the AdCP ``core/date_range`` schema. +# generated by datamodel-codegen: +# filename: core/date_range.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.date_range import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from datetime import date +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -from adcp.types.generated_poc.core.date_range import DateRange -# Explicit exports -__all__ = ["DateRange"] +class DateRange(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + start: Annotated[date, Field(description='Start date (inclusive), ISO 8601')] + end: Annotated[date, Field(description='End date (inclusive), ISO 8601')] diff --git a/src/adcp/types/domains/core/datetime_range.py b/src/adcp/types/domains/core/datetime_range.py index ee7176366..cca442b54 100644 --- a/src/adcp/types/domains/core/datetime_range.py +++ b/src/adcp/types/domains/core/datetime_range.py @@ -1,18 +1,18 @@ -"""Types declared by the AdCP ``core/datetime_range`` schema. +# generated by datamodel-codegen: +# filename: core/datetime_range.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.datetime_range import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field -from adcp.types.generated_poc.core.datetime_range import DatetimeRange -# Explicit exports -__all__ = ["DatetimeRange"] +class DatetimeRange(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + start: Annotated[AwareDatetime, Field(description='Start timestamp (inclusive), ISO 8601')] + end: Annotated[AwareDatetime, Field(description='End timestamp (inclusive), ISO 8601')] diff --git a/src/adcp/types/domains/core/daypart_target.py b/src/adcp/types/domains/core/daypart_target.py index 9f0133d01..a6734ee28 100644 --- a/src/adcp/types/domains/core/daypart_target.py +++ b/src/adcp/types/domains/core/daypart_target.py @@ -1,18 +1,55 @@ -"""Types declared by the AdCP ``core/daypart_target`` schema. +# generated by datamodel-codegen: +# filename: core/daypart_target.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.daypart_target import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import day_of_week +from . import iana_timezone -from adcp.types.generated_poc.core.daypart_target import DaypartTarget -# Explicit exports -__all__ = ["DaypartTarget"] +class DaypartTarget(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + days: Annotated[ + list[day_of_week.DayOfWeek], + Field( + description='Days of week this window applies to. Use multiple days for compact targeting (e.g., monday-friday in one object).', + min_length=1, + ), + ] + start_hour: Annotated[ + SchemaInt, + Field( + description='Start hour (inclusive), 0-23 in 24-hour format. 0 = midnight, 6 = 6:00am, 18 = 6:00pm.', + ge=0, + le=23, + ), + ] + end_hour: Annotated[ + SchemaInt, + Field( + description='End hour (exclusive), 1-24 in 24-hour format. 10 = 10:00am, 24 = midnight. Must be greater than start_hour.', + ge=1, + le=24, + ), + ] + timezone: Annotated[ + Literal['inventory_local'] | iana_timezone.IanaTimezoneIdentifier | None, + Field( + description="Civil-time clock used to evaluate this window. 'inventory_local' evaluates the hours in the seller-assigned local timezone of each inventory unit that can deliver the impression, such as a screen, venue, station, or publisher property; it never means the buyer, account, or server timezone. A concrete IANA timezone identifier (for example, 'America/New_York', 'CET', or 'UTC') evaluates one shared civil-time clock across the targeted inventory. Omission defaults to 'inventory_local'. Buyers that begin with a user or account preference MUST resolve it to a concrete IANA identifier before sending the daypart; 'user_timezone' and 'account_timezone' are not wire values. For each candidate delivery instant, convert the instant into this clock and compare its resulting local day and hour with the half-open window: a skipped DST hour has no matching instants, while both occurrences of a repeated hour match. This delivery clock is independent of reporting_capabilities.timezone.", + validate_default=True, + ), + ] = 'inventory_local' + label: Annotated[ + str | None, + Field( + description="Optional human-readable name for this time window (e.g., 'Morning Drive', 'Prime Time')" + ), + ] = None diff --git a/src/adcp/types/domains/core/deadline_policy.py b/src/adcp/types/domains/core/deadline_policy.py index 24eab220b..1f01c52cd 100644 --- a/src/adcp/types/domains/core/deadline_policy.py +++ b/src/adcp/types/domains/core/deadline_policy.py @@ -1,18 +1,54 @@ -"""Types declared by the AdCP ``core/deadline_policy`` schema. +# generated by datamodel-codegen: +# filename: core/deadline_policy.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.deadline_policy import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.deadline_policy import DeadlinePolicy, MaterialStage +class MaterialStage(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + stage: Annotated[ + str, + Field( + description="Stage identifier. Standard values: 'draft' (needs seller processing), 'final' (production-ready).", + examples=['draft', 'final'], + ), + ] + lead_days: Annotated[ + SchemaInt, Field(description='Days before scheduled_at this stage is due', ge=0) + ] + label: Annotated[str | None, Field(description='What the seller needs at this stage')] = None + -# Explicit exports -__all__ = ["DeadlinePolicy", "MaterialStage"] +class DeadlinePolicy(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + booking_lead_days: Annotated[ + SchemaInt | None, + Field(description='Days before scheduled_at by which the placement must be booked', ge=0), + ] = None + cancellation_lead_days: Annotated[ + SchemaInt | None, + Field(description='Days before scheduled_at by which cancellation is penalty-free', ge=0), + ] = None + material_stages: Annotated[ + list[MaterialStage] | None, + Field( + description='Default material submission stages. Items MUST be in chronological order (earliest due first). Agents compute due_at as: installment.scheduled_at minus lead_days.', + min_length=1, + ), + ] = None + business_days_only: Annotated[ + StrictBool | None, + Field( + description='When true, lead_days counts business days (Mon-Fri) rather than calendar days. Defaults to false.' + ), + ] = False diff --git a/src/adcp/types/domains/core/delivery_breakdown_controls.py b/src/adcp/types/domains/core/delivery_breakdown_controls.py index b85494ae3..6dc9fb561 100644 --- a/src/adcp/types/domains/core/delivery_breakdown_controls.py +++ b/src/adcp/types/domains/core/delivery_breakdown_controls.py @@ -1,18 +1,35 @@ -"""Types declared by the AdCP ``core/delivery_breakdown_controls`` schema. +# generated by datamodel-codegen: +# filename: core/delivery_breakdown_controls.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.delivery_breakdown_controls import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import sort_direction as sort_direction_1 +from ..enums import sort_metric -from adcp.types.generated_poc.core.delivery_breakdown_controls import DeliveryBreakdownControls -# Explicit exports -__all__ = ["DeliveryBreakdownControls"] +class DeliveryBreakdownControls(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + limit: Annotated[ + SchemaInt | None, + Field(description='Maximum number of rows to return. Defaults to 25.', ge=1), + ] = 25 + sort_by: Annotated[ + sort_metric.SortMetric | None, + Field( + description='Metric used to order rows. Falls back to spend when unavailable at this row grain; on fallback sort_direction resets to desc. Rows lacking a value for the applied sort metric order last regardless of direction.' + ), + ] = sort_metric.SortMetric.spend + sort_direction: Annotated[ + sort_direction_1.SortDirection | None, + Field( + description='Direction for sort_by ordering. Sellers MUST apply the requested direction to the applied sort metric; direction has no availability fallback.' + ), + ] = sort_direction_1.SortDirection.desc diff --git a/src/adcp/types/domains/core/delivery_forecast.py b/src/adcp/types/domains/core/delivery_forecast.py index 2a18e31af..03c967c2f 100644 --- a/src/adcp/types/domains/core/delivery_forecast.py +++ b/src/adcp/types/domains/core/delivery_forecast.py @@ -1,18 +1,96 @@ -"""Types declared by the AdCP ``core/delivery_forecast`` schema. +# generated by datamodel-codegen: +# filename: core/delivery_forecast.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.delivery_forecast import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import demographic_system as demographic_system_1 +from ..enums import forecast_method +from ..enums import forecast_range_unit as forecast_range_unit_1 +from ..enums import reach_unit as reach_unit_1 +from . import ext as ext_1 +from . import forecast_point -from adcp.types.generated_poc.core.delivery_forecast import DeliveryForecast -# Explicit exports -__all__ = ["DeliveryForecast"] +class DeliveryForecast(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + points: Annotated[ + list[forecast_point.ForecastPoint], + Field( + description='Forecasted delivery data points. For spend curves (default), points at ascending budget levels show how metrics scale with spend. For availability forecasts, points represent total available inventory independent of budget. See forecast_range_unit for interpretation.', + min_length=1, + ), + ] + forecast_range_unit: Annotated[ + forecast_range_unit_1.ForecastRangeUnit | None, + Field( + description="How to interpret the points array. 'spend' (default when omitted): points at ascending budget levels. 'availability': total available inventory, budget omitted. 'reach_freq': points at ascending reach/frequency targets. 'weekly'/'daily': metrics are per-period values. 'clicks'/'conversions': points at ascending outcome targets. 'package': each point is a distinct inventory package." + ), + ] = None + method: Annotated[ + forecast_method.ForecastMethod, Field(description='Method used to produce this forecast') + ] + currency: Annotated[ + str, + Field( + description='ISO 4217 currency code for monetary values in this forecast (spend, budget)' + ), + ] + demographic_system: Annotated[ + demographic_system_1.DemographicSystem | None, + Field( + description='Measurement system for the demographic field. Ensures buyer and seller agree on demographic notation.' + ), + ] = None + demographic: Annotated[ + str | None, + Field( + description='Target demographic code within the specified demographic_system. For Nielsen: P18-49, M25-54, W35+. For BARB: ABC1 Adults, 16-34. For AGF: E 14-49.', + examples=['P18-49', 'A25-54', 'W35+', 'M18-34'], + ), + ] = None + measurement_source: Annotated[ + str | None, + Field( + description='Third-party measurement provider whose data was used to produce this forecast. Distinct from demographic_system, which specifies demographic notation — measurement_source identifies whose data produced the forecast numbers. Should be present when measured_impressions is used. Lowercase slug format.', + examples=[ + 'nielsen', + 'videoamp', + 'comscore', + 'geopath', + 'barb', + 'agf', + 'oztam', + 'kantar', + 'barc', + 'route', + 'rajar', + 'triton', + ], + max_length=64, + pattern='^[a-z0-9_]+$', + ), + ] = None + reach_unit: Annotated[ + reach_unit_1.ReachUnit | None, + Field( + description='Unit of measurement for reach and audience_size metrics in this forecast. Required for cross-channel forecast comparison.' + ), + ] = None + generated_at: Annotated[ + AwareDatetime | None, Field(description='When this forecast was computed') + ] = None + valid_until: Annotated[ + AwareDatetime | None, + Field( + description='When this forecast expires. After this time, the forecast should be refreshed. Forecast expiry does not affect proposal executability.' + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/delivery_metric_aggregate.py b/src/adcp/types/domains/core/delivery_metric_aggregate.py index 35d0c9126..ac7db5ec2 100644 --- a/src/adcp/types/domains/core/delivery_metric_aggregate.py +++ b/src/adcp/types/domains/core/delivery_metric_aggregate.py @@ -1,32 +1,209 @@ -"""Types declared by the AdCP ``core/delivery_metric_aggregate`` schema. +# generated by datamodel-codegen: +# filename: core/delivery_metric_aggregate.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.delivery_metric_aggregate import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictFloat + +from ..enums import attribution_methodology as attribution_methodology_1 +from ..enums import available_metric +from ..enums import completion_source as completion_source_1 +from ..enums import lift_dimension as lift_dimension_1 +from ..enums import viewability_standard as viewability_standard_1 +from . import brand_ref, duration, vendor_metric_id + + +class Qualifier(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + viewability_standard: Annotated[ + viewability_standard_1.ViewabilityStandard | None, + Field( + description='Viewability standard under which this row was measured. MRC and GroupM define materially different thresholds; never sum across standards.' + ), + ] = None + completion_source: Annotated[ + completion_source_1.CompletionSource | None, + Field( + description='Source of `completion_rate` attestation for this row — `seller_attested` from player/ad server, `vendor_attested` from a third-party measurement vendor. The two paths can yield different rates; never sum across sources.' + ), + ] = None + attribution_methodology: Annotated[ + attribution_methodology_1.AttributionMethodology | None, + Field( + description='Attribution methodology under which this outcome row was computed (`deterministic_purchase`, `probabilistic`, `panel_based`, `modeled`). Outcome metrics measured under different methodologies represent materially different numbers; never sum across methodologies.' + ), + ] = None + attribution_window: Annotated[ + duration.Duration | None, + Field( + description="Attribution window for this outcome row. Object-valued duration (`{interval, unit}`), not a shorthand string. Outcome metrics measured over different windows represent the same metric over different time periods; the partition keeps them as separate rows so buyers don't accidentally aggregate." + ), + ] = None + lift_dimension: Annotated[ + lift_dimension_1.LiftDimension | None, + Field( + description='Brand-lift dimension this row represents (awareness, consideration, favorability, purchase intent, or ad recall). Required when `metric_id` is `brand_lift`. Each dimension is a separate surveyed outcome with its own sample size and confidence interval; never sum across dimensions.' + ), + ] = None + + +class Field0(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + scope: Annotated[ + Literal['standard'], + Field(description='Standard metric from the closed `available-metric.json` enum.'), + ] = 'standard' + metric_id: Annotated[ + available_metric.AvailableMetric, + Field( + description='Identifier for a scalar standard metric. Container tokens and structured distribution identities are committed and selected through their canonical carriers, not represented as numeric aggregate rows.' + ), + ] + qualifier: Annotated[ + Qualifier | None, + Field( + description="Qualifier keys disambiguating this row from sibling rows under the same `metric_id`. Symmetric with `committed_metrics.qualifier` today; expected to diverge in future minors as transparency disclosures buyers don't commit to ship delivery-only. Closed (`additionalProperties: false`) — new qualifier keys ship explicitly." + ), + ] = None + value: Annotated[ + StrictFloat, + Field( + description='Aggregated metric value for this `(metric_id, qualifier)` partition. Heterogeneous by `metric_id` — rate metrics (`viewable_rate`, `completion_rate`) are 0.0–1.0; cost-per metrics (`cost_per_acquisition`, `cost_per_completed_view`) are currency amounts; count metrics (`impressions`, `clicks`) are non-negative integers as numbers; ratio metrics (`roas`) are non-negative numbers. Buyer agents MUST inspect `metric_id` before doing arithmetic — same dispatch convention as `committed_metrics`.' + ), + ] + measurable_impressions: Annotated[ + StrictFloat | None, + Field( + description='Coverage denominator for verification metrics (e.g., `viewable_rate`). Buyers compute coverage as `measurable_impressions / impressions` from the partition.', + ge=0.0, + ), + ] = None + viewable_impressions: Annotated[ + StrictFloat | None, Field(description='Component for `viewable_rate` (numerator).', ge=0.0) + ] = None + impressions: Annotated[ + StrictFloat | None, + Field( + description='Component for rate metrics whose denominator is total impressions (e.g., `completion_rate`, `engagement_rate`).', + ge=0.0, + ), + ] = None + completed_views: Annotated[ + StrictFloat | None, + Field(description='Component for `completion_rate` (numerator).', ge=0.0), + ] = None + spend: Annotated[ + StrictFloat | None, + Field( + description='Component for cost-per metrics (denominator-ish; the cost half of the ratio).', + ge=0.0, + ), + ] = None + conversions: Annotated[ + StrictFloat | None, + Field(description='Component for `cost_per_acquisition` and ROAS-family metrics.', ge=0.0), + ] = None + conversion_value: Annotated[ + StrictFloat | None, Field(description='Component for `roas` (numerator).', ge=0.0) + ] = None + clicks: Annotated[ + StrictFloat | None, + Field(description='Component for `cost_per_click` and click-rate metrics.', ge=0.0), + ] = None + + +class DeliveryMetricAggregate1(Field0): + scope: Literal['standard'] = 'standard' + + +class Qualifier3(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + viewability_standard: Annotated[ + viewability_standard_1.ViewabilityStandard | None, + Field( + description='Viewability standard under which this row was measured. MRC and GroupM define materially different thresholds; never sum across standards.' + ), + ] = None + completion_source: Annotated[ + completion_source_1.CompletionSource | None, + Field( + description='Attestation source for a vendor completion-style metric — seller_attested from player/ad server, vendor_attested from an independent measurement path. Applicability is defined by the vendor metric; never sum across sources.' + ), + ] = None + attribution_methodology: Annotated[ + attribution_methodology_1.AttributionMethodology | None, + Field( + description='Attribution methodology under which this outcome row was computed (`deterministic_purchase`, `probabilistic`, `panel_based`, `modeled`). Outcome metrics measured under different methodologies represent materially different numbers; never sum across methodologies.' + ), + ] = None + attribution_window: Annotated[ + duration.Duration | None, + Field( + description="Attribution window for this outcome row. Object-valued duration (`{interval, unit}`), not a shorthand string. Outcome metrics measured over different windows represent the same metric over different time periods; the partition keeps them as separate rows so buyers don't accidentally aggregate." + ), + ] = None + lift_dimension: Annotated[ + lift_dimension_1.LiftDimension | None, + Field( + description='Lift dimension this row represents (awareness, consideration, favorability, purchase intent, or ad recall) for vendor lift-style metrics. Applicability is defined by the vendor metric; each dimension is a separate surveyed outcome and rows under different dimensions must not be summed.' + ), + ] = None + + +class DeliveryMetricAggregate2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + scope: Annotated[ + Literal['vendor'], + Field(description='Vendor-defined metric, identified by the tuple `(vendor, metric_id)`.'), + ] = 'vendor' + vendor: Annotated[ + brand_ref.BrandReference, + Field( + description="Vendor that defines and computes this metric. The vendor's `brand.json` `agents[type='measurement']` is the canonical anchor; metric definitions live on `get_adcp_capabilities.measurement.metrics[]`." + ), + ] + metric_id: Annotated[ + vendor_metric_id.VendorMetricId, + Field(description="Identifier for the metric within the vendor's vocabulary."), + ] + qualifier: Annotated[ + Qualifier3 | None, + Field( + description='Optional qualifier keys disambiguating this vendor-metric row from sibling rows under the same (vendor, metric_id) — e.g., attribution_window on a vendor outcome metric. Same closed key set as the standard branch; new keys ship explicitly.' + ), + ] = None + value: Annotated[ + StrictFloat, + Field( + description="Aggregated vendor-attested value. Unit semantics defined by the vendor — see the vendor's measurement-agent metric definition." + ), + ] + measurable_impressions: Annotated[ + StrictFloat | None, + Field( + description="Coverage denominator — vendor measurement is rarely 100% of delivery (only impressions where the vendor's SDK fired or panel matched). Buyers compute coverage as `measurable_impressions / impressions`. Same convention as `vendor_metric_value.measurable_impressions`.", + ge=0.0, + ), + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.delivery_metric_aggregate import ( - DeliveryMetricAggregate, - DeliveryMetricAggregate1, - DeliveryMetricAggregate2, - Field0, - Qualifier, - Qualifier3, -) - -# Explicit exports -__all__ = [ - "DeliveryMetricAggregate", - "DeliveryMetricAggregate1", - "DeliveryMetricAggregate2", - "Field0", - "Qualifier", - "Qualifier3", +DeliveryMetricAggregate = Annotated[ + DeliveryMetricAggregate1 | DeliveryMetricAggregate2, + Field( + description='One delivery metric value partitioned by metric scope and qualifier. Its standard branch is used at package grain by `by_package[].metric_values`; both branches are retained for the deprecated cross-buy `aggregated_totals.metric_aggregates` compatibility field. Row-symmetric with `package.committed_metrics` and delivery `missing_metrics` so buyers can reconcile by `(scope, metric_id, qualifier)`.', + discriminator='scope', + title='Delivery Metric Aggregate', + ), ] diff --git a/src/adcp/types/domains/core/delivery_metrics.py b/src/adcp/types/domains/core/delivery_metrics.py index a779cb146..ebd4f2f33 100644 --- a/src/adcp/types/domains/core/delivery_metrics.py +++ b/src/adcp/types/domains/core/delivery_metrics.py @@ -1,62 +1,731 @@ -"""Types declared by the AdCP ``core/delivery_metrics`` schema. +# generated by datamodel-codegen: +# filename: core/delivery_metrics.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.delivery_metrics import +from datetime import date +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictBool, StrictFloat + +from ..enums import action_source as action_source_1 +from ..enums import event_type as event_type_1 +from ..enums import reach_unit as reach_unit_1 +from ..enums import view_threshold_basis, viewability_standard +from . import brand_ref, duration, placement_evidence, vendor_metric_value + + +class Kind(StrEnum): + cumulative = 'cumulative' + period = 'period' + rolling = 'rolling' + + +class QuartileData(AdCPBaseModel): + q1_views: Annotated[StrictFloat | None, Field(description='25% completion views', ge=0.0)] = ( + None + ) + q2_views: Annotated[StrictFloat | None, Field(description='50% completion views', ge=0.0)] = ( + None + ) + q3_views: Annotated[StrictFloat | None, Field(description='75% completion views', ge=0.0)] = ( + None + ) + q4_views: Annotated[StrictFloat | None, Field(description='100% completion views', ge=0.0)] = ( + None + ) + + +class VenueBreakdownItem(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + venue_id: Annotated[str, Field(description='Venue identifier')] + venue_name: Annotated[str | None, Field(description='Human-readable venue name')] = None + venue_type: Annotated[ + str | None, + Field(description="Venue type (e.g., 'airport', 'transit', 'retail', 'billboard')"), + ] = None + impressions: Annotated[ + SchemaInt, Field(description='Impressions delivered at this venue', ge=0) + ] + loop_plays: Annotated[SchemaInt | None, Field(description='Loop plays at this venue', ge=0)] = ( + None + ) + screens_used: Annotated[ + SchemaInt | None, Field(description='Number of screens used at this venue', ge=0) + ] = None + + +class DoohMetrics1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + loop_plays: Annotated[ + SchemaInt | None, Field(description='Number of times ad played in rotation', ge=0) + ] = None + screens_used: Annotated[ + SchemaInt | None, Field(description='Number of unique screens displaying the ad', ge=0) + ] = None + screen_time_seconds: Annotated[ + SchemaInt | None, Field(description='Total display time in seconds', ge=0) + ] = None + sov_achieved: Annotated[ + StrictFloat | None, + Field( + description='Actual share of voice delivered on a 0.0-1.0 scale. To compare this achieved value with the selected flat-rate DOOH parameters.sov_percentage, multiply sov_achieved by 100. Share is time-weighted: the sum of the delivered segment durations divided by the full loop duration. On equal-duration loops this equals the slot-count ratio. See also ooh_metrics.share_of_voice_contracted, which uses the same 0.0-1.0 scale.', + ge=0.0, + le=1.0, + ), + ] = None + calculation_notes: Annotated[ + str | None, + Field( + description="Per-row supplementary methodology notes for DOOH impression calculation (e.g., 'rotation-based; 6-second slot weighted by 70% audience overlap'). Free-form prose for context that doesn't fit the structured measurement-vendor surface. Canonical methodology declarations belong on the measurement vendor's `get_adcp_capabilities.measurement.metrics[]` block where they're discoverable once and inherited across delivery rows; this field is for row-specific context (a particular daypart's calculation, a venue-mix exception) rather than the seller's general methodology." + ), + ] = None + venue_breakdown: Annotated[ + list[VenueBreakdownItem] | None, Field(description='Per-venue performance breakdown') + ] = None + + +class IdType(StrEnum): + geopath = 'geopath' + route_frame = 'route_frame' + plant_face = 'plant_face' + other = 'other' + + +class Identifier(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + id: Annotated[str, Field(description='Panel/face identifier within the id_type namespace')] + id_type: Annotated[ + IdType, + Field( + description="Identifier scheme: geopath (US Geopath Spot ID), route_frame (UK Route frame ID), plant_face (the operator's own panel number — the identifier both sides of a direct contract know), other (markets without a listed scheme). New schemes ship as enum additions in minors." + ), + ] + + +class Panel(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + identifiers: Annotated[ + list[Identifier], + Field(description='All identifiers for this panel, one entry per scheme', min_length=1), + ] + name: Annotated[ + str | None, + Field( + description="Human-readable location description (e.g., 'I-95 N of Exit 12, right-hand read')" + ), + ] = None + + +class EstimationBasis(StrEnum): + currency_measured = 'currency_measured' + seller_modeled = 'seller_modeled' + + +class EventType(StrEnum): + posted = 'posted' + rotated = 'rotated' + repaired = 'repaired' + removed = 'removed' + + +class ViewedSecondsPercentiles(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + p25: Annotated[ + StrictFloat, Field(description='25th-percentile in-view duration in seconds.', ge=0.0) + ] + p50: Annotated[ + StrictFloat, + Field(description='Median (50th-percentile) in-view duration in seconds.', ge=0.0), + ] + p75: Annotated[ + StrictFloat, Field(description='75th-percentile in-view duration in seconds.', ge=0.0) + ] + p90: Annotated[ + StrictFloat, Field(description='90th-percentile in-view duration in seconds.', ge=0.0) + ] + p95: Annotated[ + StrictFloat, Field(description='95th-percentile in-view duration in seconds.', ge=0.0) + ] + + +class ViewedSecondsHistogramItem(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + lower_bound_seconds: Annotated[ + StrictFloat, + Field(description='Inclusive lower bound of this duration bucket, in seconds.', ge=0.0), + ] + upper_bound_seconds: Annotated[ + StrictFloat | None, + Field( + description='Exclusive upper bound of this duration bucket, in seconds. MUST be greater than `lower_bound_seconds`. Omit only on the final bucket to represent an unbounded upper range.', + ge=0.0, + ), + ] = None + impressions: Annotated[ + SchemaInt, + Field( + description='Number of measurable impressions whose in-view duration falls in this bucket.', + ge=0, + ), + ] + + +class ByActionSourceItem(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + action_source: Annotated[ + action_source_1.ActionSource, Field(description='Where the conversion occurred') + ] + event_source_id: Annotated[ + str | None, + Field( + description='Event source that produced these conversions (for disambiguation when multiple event sources are configured)' + ), + ] = None + count: Annotated[ + StrictFloat, Field(description='Number of conversions from this action source', ge=0.0) + ] + value: Annotated[ + StrictFloat | None, + Field(description='Total monetary value of conversions from this action source', ge=0.0), + ] = None + + +class DoohMetrics(DoohMetrics1): + pass + + +class ByEventTypeItem(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + event_type: Annotated[event_type_1.EventType, Field(description='The event type')] + event_source_id: Annotated[ + str | None, + Field( + description='Event source that produced these conversions (for disambiguation when multiple event sources are configured)' + ), + ] = None + count: Annotated[StrictFloat, Field(description='Number of events of this type', ge=0.0)] + value: Annotated[ + StrictFloat | None, Field(description='Total monetary value of events of this type', ge=0.0) + ] = None + + +class ReachWindow(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + kind: Annotated[ + Kind, + Field( + description="Window semantics. `cumulative` — uniques since campaign start; the value is the total unique count to date and MUST NOT be summed across rows (each later row supersedes the earlier value). `period` — uniques within a single non-overlapping reporting period (e.g., a daily snapshot for a specific calendar day). Adjacent `period` rows do not share audiences by construction, but the same person MAY appear across multiple periods, so MUST NOT be summed across rows to compute campaign reach. `rolling` — uniques within a trailing window ending at the row's reporting timestamp (e.g., trailing-7-day reach). Adjacent rolling rows overlap and MUST NOT be summed; each row's value stands alone." + ), + ] + period: Annotated[ + duration.Duration | None, + Field( + description='Duration of the measurement window. REQUIRED when `kind` is `period` or `rolling` — declares the snapshot length (e.g., `{"interval": 1, "unit": "days"}` for a daily snapshot) or the trailing-window length (e.g., `{"interval": 7, "unit": "days"}` for trailing-7-day rolling reach). When `kind` is `cumulative`, this field is implicit (campaign-to-date) and SHOULD be omitted.' + ), + ] = None + + +class TimeBasedView(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + threshold_seconds: Annotated[ + StrictFloat, + Field( + description='Continuous duration threshold in seconds an impression must meet to count as a view in this entry.', + gt=0.0, + ), + ] + basis: Annotated[ + view_threshold_basis.ViewThresholdBasis, + Field( + description='Whether the threshold clock runs on playback time or in-view time. Required because play-time and in-view counts at the same threshold are materially different numbers.' + ), + ] + views: Annotated[ + StrictFloat, + Field(description="Count of views meeting this entry's threshold and basis.", ge=0.0), + ] + standard: Annotated[ + viewability_standard.ViewabilityStandard | None, + Field( + description="Viewability standard governing the in-view clock for this entry. RECOMMENDED when basis is 'in_view' (MRC and GroupM thresholds differ); not applicable to play_time entries." + ), + ] = None + + +class Posting(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + panel_id: Annotated[ + str, Field(description='Panel reference matching one of panels[].identifiers[].id') + ] + event_type: Annotated[ + EventType | None, + Field( + description='What the record attests: initial posting, rotary rotation to a new face, repair/reposting after damage, or removal at flight end' + ), + ] = EventType.posted + occurred_at: Annotated[ + date, + Field( + description='Date the attested event happened. For posted events this is the posting date — display terms customarily run from the average posting date across units.' + ), + ] + evidence: Annotated[ + placement_evidence.PlacementEvidence | None, + Field( + description='Evidence artifact for this event (completion photograph); capture time and location ride the artifact' + ), + ] = None + + +class OohMetrics(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + panels: Annotated[ + list[Panel] | None, + Field( + description="Panels (faces) covered by this row. A panel commonly carries multiple identifiers at once — OOH contracts key line items on the measurement-currency panel number AND the operator's own panel number together." + ), + ] = None + posting_period_start: Annotated[ + date | None, Field(description='First in-charge date of the posting period this row covers') + ] = None + posting_period_end: Annotated[ + date | None, Field(description='Last date of the posting period this row covers') + ] = None + average_posted_date: Annotated[ + date | None, + Field( + description="Average actual posting date across the row's units — the date the display term runs from under OAAA Bulletin §2.2 / Poster §3.2(a) when materials were timely. Read with materials_timely to know whether that rule was in force." + ), + ] = None + materials_timely: Annotated[ + StrictBool | None, + Field( + description="Seller's assertion that the buyer delivered acceptable materials by the contractual deadline, which determines whether the §2.2 posting-completion rule (term runs from average posting date) applied. An assertion about the buyer's delivery, not a derived value — it is not recomputable from postings[] evidence, and is the one field in this block that isn't checkable against it." + ), + ] = None + share_of_voice_contracted: Annotated[ + StrictFloat | None, + Field( + description='Contracted share of voice where the buy is rotation-based (e.g., rotary bulletin programs) rather than an exclusive face, on a 0.0-1.0 scale. Share is time-weighted: the sum of contracted display durations divided by the full rotation duration. On equal-duration rotations this equals the slot-count ratio.', + ge=0.0, + le=1.0, + ), + ] = None + illuminated_hours: Annotated[ + StrictFloat | None, + Field( + description='Contracted daily illumination hours for the panels in this row. Determines the measured day-part basis (12/18/24-hour impressions) and the illumination-credit remedy when unmet.', + ge=0.0, + le=24.0, + ), + ] = None + estimated_impressions: Annotated[ + SchemaInt | None, + Field( + description="Modeled audience impressions for the panels and period in this row. This is the channel's delivery number — there is no event-counted alternative. The methodology tier MUST be declared in estimation_basis; provider identity is declared in the row-level measurement_source; the billing vendor is declared in measurement_terms.billing_measurement. The row's top-level impressions SHOULD carry the same value so cross-channel aggregation works without channel-specific logic.", + ge=0, + ), + ] = None + estimation_basis: Annotated[ + EstimationBasis | None, + Field( + description="Methodology tier of estimated_impressions: currency_measured (an audience measurement currency's estimate for the panel and period — Geopath, Route, MOVE, COMMB — with the provider named in the row-level measurement_source) or seller_modeled (the seller's own model — the honest fallback for markets without a measurement currency). Tier, not provider: currencies change over time, so provider identity is data on measurement_source, not values in this enum." + ), + ] = None + postings: Annotated[ + list[Posting] | None, + Field( + description='Posting records — the seller-attested settlement artifact proving each panel was posted for the period. Industry convention (OAAA model contracts, Proof of Performance §3.5): photo evidence per bulletin within five calendar days of posting and again after each rotary rotation; one representative close-up photograph per creative design variation for poster showings (no deadline attached). Distinct from the §2.2 posting-completion obligation (posting within five business days of the scheduled date), which governs average_posted_date, not evidence.' + ), + ] = None + calculation_notes: Annotated[ + str | None, + Field( + description="Row-specific methodology context that doesn't fit the structured fields (e.g., a partial-period proration or a market-specific estimate adjustment). Same role as dooh_metrics.calculation_notes — canonical methodology declarations belong on the measurement vendor's discoverable surfaces, not here." + ), + ] = None + + +class Viewability1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + vendor: Annotated[ + brand_ref.BrandReference | None, + Field( + description="Vendor that produced these viewability values. Optional but RECOMMENDED so the row is self-describing — buyer agents reading delivery in isolation can attribute the numbers without joining back to `package.committed_metrics` or `package.performance_standards`. The vendor's `brand.json` `agents[type='measurement']` is the discovery anchor; the metric definitions live on the agent's `get_adcp_capabilities.measurement.metrics[]` block. Same shape as `vendor_metric_value.vendor` for symmetry across vendor-attested surfaces." + ), + ] = None + measurable_impressions: Annotated[ + StrictFloat | None, + Field( + description='Impressions where viewability could be measured. Excludes environments without measurement capability (e.g., non-Intersection Observer browsers, certain app environments). Coverage denominator for `viewable_rate`, `viewed_seconds`, and both viewed-seconds distributions — every duration statistic is computed over this same measurable population.', + ge=0.0, + ), + ] = None + viewable_impressions: Annotated[ + StrictFloat | None, + Field( + description='Impressions that met the viewability threshold defined by the measurement standard.', + ge=0.0, + ), + ] = None + viewable_rate: Annotated[ + StrictFloat | None, + Field( + description='Viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.', + ge=0.0, + le=1.0, + ), + ] = None + viewed_seconds: Annotated[ + StrictFloat | None, + Field( + description="Average in-view duration per measurable impression, in seconds. Reporting-side counterpart to the `viewed_seconds` optimization metric in `optimization-goal.json`. Computed over `measurable_impressions`, not total impressions — the same denominator as `viewable_rate`. The viewability `standard` governs the threshold (e.g., MRC's 50% pixels for 1s display / 2s video) that defines when an impression is in view and therefore when the clock is running. Sellers reporting against a `viewed_seconds` optimization goal MUST populate this field.", + ge=0.0, + ), + ] = None + viewed_seconds_percentiles: Annotated[ + ViewedSecondsPercentiles | None, + Field( + description='Percentile summary of the per-impression in-view durations whose arithmetic mean is reported in `viewed_seconds`. This object MUST use the same reporting row, measurement vendor, viewability `standard`, and `measurable_impressions` population as `viewed_seconds`; sellers MUST omit it when `measurable_impressions` is zero. Percentiles use the nearest-rank definition: sort the N observed durations in ascending order and select rank `ceil(p × N)` (one-based) for percentile p. Values MUST be non-decreasing from p25 through p95. The structured metric identity `viewed_seconds_percentiles` makes this optional surface discoverable and requestable; it is not sortable and the nested object remains the canonical carrier.' + ), + ] = None + viewed_seconds_histogram: Annotated[ + list[ViewedSecondsHistogramItem] | None, + Field( + description='Bucketed counts of the per-impression in-view durations whose arithmetic mean is reported in `viewed_seconds`. Buckets MUST be ordered by ascending lower bound, MUST NOT overlap, and MUST partition every impression in the same `measurable_impressions` population exactly once; therefore the sum of `impressions` MUST equal `measurable_impressions`. Buckets need not be contiguous: a gap between consecutive bucket boundaries is permitted when no impressions fall within that range — the sum constraint enforces this implicitly, and validators MUST NOT independently require contiguity. Each bucket is half-open `[lower_bound_seconds, upper_bound_seconds)`; only the final bucket MAY omit `upper_bound_seconds`, representing an unbounded upper range. Sellers choose boundaries, but buyers MUST combine histograms only when the complete boundary sequence, measurement vendor, and viewability `standard` match. The structured metric identity `viewed_seconds_histogram` makes this optional surface discoverable and requestable; it is not sortable and this nested array remains the canonical carrier.', + min_length=1, + ), + ] = None + standard: Annotated[ + viewability_standard.ViewabilityStandard | None, + Field( + description='Viewability measurement standard applied to these metrics. Governs the in-view threshold for `viewable_rate`, `viewed_seconds`, and both viewed-seconds distributions.' + ), + ] = None + + +class Viewability(Viewability1): + pass -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.delivery_metrics import ( - ByActionSourceItem, - ByEventTypeItem, - DeliveryMetrics, - DoohMetrics, - DoohMetrics1, - EstimationBasis, - EventType, - IdType, - Identifier, - Kind, - OohMetrics, - Panel, - Posting, - QuartileData, - ReachWindow, - TimeBasedView, - VenueBreakdownItem, - Viewability, - Viewability1, - ViewedSecondsHistogramItem, - ViewedSecondsPercentiles, -) - -# Explicit exports -__all__ = [ - "ByActionSourceItem", - "ByEventTypeItem", - "DeliveryMetrics", - "DoohMetrics", - "DoohMetrics1", - "EstimationBasis", - "EventType", - "IdType", - "Identifier", - "Kind", - "OohMetrics", - "Panel", - "Posting", - "QuartileData", - "ReachWindow", - "TimeBasedView", - "VenueBreakdownItem", - "Viewability", - "Viewability1", - "ViewedSecondsHistogramItem", - "ViewedSecondsPercentiles", -] +class DeliveryMetrics(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + impressions: Annotated[ + StrictFloat | None, Field(description='Impressions delivered', ge=0.0) + ] = None + spend: Annotated[StrictFloat | None, Field(description='Amount spent', ge=0.0)] = None + clicks: Annotated[StrictFloat | None, Field(description='Total clicks', ge=0.0)] = None + ctr: Annotated[ + StrictFloat | None, + Field(description='Click-through rate (clicks/impressions)', ge=0.0, le=1.0), + ] = None + views: Annotated[ + StrictFloat | None, + Field( + description="Content engagements counted toward the billable view threshold. For video this is a platform-defined view event (e.g., 30 seconds or video midpoint); for audio/podcast it is a stream start; for other formats it follows the pricing model's view definition. When the package uses CPV pricing, spend = views × rate.", + ge=0.0, + ), + ] = None + completed_views: Annotated[ + StrictFloat | None, + Field( + description='Video/audio completions. When the package has a completed_views optimization goal with view_duration_seconds, completions are counted at that threshold rather than 100% completion.', + ge=0.0, + ), + ] = None + completion_rate: Annotated[ + StrictFloat | None, + Field( + description='Completion rate (completed_views/impressions). Null indicates the metric is not applicable to this package/buy (e.g. completion rate on a non-video buy).', + ge=0.0, + le=1.0, + ), + ] = None + conversions: Annotated[ + StrictFloat | None, + Field( + description='Total conversions attributed to this delivery. When by_event_type is present, this equals the sum of all by_event_type[].count entries.', + ge=0.0, + ), + ] = None + conversion_value: Annotated[ + StrictFloat | None, + Field( + description='Total monetary value of attributed conversions (in the reporting currency)', + ge=0.0, + ), + ] = None + commissionable_value: Annotated[ + StrictFloat | None, + Field( + description='Settled portion of attributed conversion value eligible for revenue-share commission, in the reporting currency. For revenue_share pricing, spend = round_currency(commissionable_value × commission_rate). This is distinct from conversion_value because taxes, shipping, discounts, returns, cancellations, or ineligible items may be excluded under the agreed commission basis.', + ge=0.0, + ), + ] = None + roas: Annotated[ + StrictFloat | None, + Field(description='Return on ad spend (conversion_value / spend)', ge=0.0), + ] = None + cost_per_acquisition: Annotated[ + StrictFloat | None, Field(description='Cost per conversion (spend / conversions)', ge=0.0) + ] = None + new_to_brand_rate: Annotated[ + StrictFloat | None, + Field( + description='Fraction of `conversions` (transactions) from first-time brand buyers, 0 = none, 1 = all. For retail-media unit-volume tracking of first-time buyers, see `new_to_brand_units` (count, not rate).', + ge=0.0, + le=1.0, + ), + ] = None + leads: Annotated[ + StrictFloat | None, + Field( + description="Leads generated (convenience alias for by_event_type where event_type='lead')", + ge=0.0, + ), + ] = None + incremental_sales_lift: Annotated[ + StrictFloat | None, + Field( + description="Incremental sales lift attributed to the campaign — sales above the control/holdout baseline. Reported as a fraction (0.15 = 15% lift) or as an absolute value depending on seller convention. The seller's `attribution_methodology` qualifier (typically `deterministic_purchase` or `modeled`) and `attribution_window` qualifier on the matching `committed_metrics` entry disambiguate the methodology and window.", + ge=0.0, + ), + ] = None + brand_lift: Annotated[ + StrictFloat | None, + Field( + description="Brand lift — measured change in a brand metric (awareness, consideration, favorability, purchase intent, or ad recall) attributed to the campaign. Typically panel-based or survey-based. Reported as a fraction (0.05 = 5% lift). **Multidimensional in production** — Kantar, Upwave, Cint, DV all report each dimension separately with its own sample size and confidence interval. The dimension flows through `qualifier.lift_dimension` on `committed_metrics` / `by_package[].metric_values` (`awareness` | `consideration` | `favorability` | `purchase_intent` | `ad_recall`); rows under different dimensions are different surveyed outcomes and must not be combined. Use `attribution_methodology: 'panel_based'` qualifier when the underlying methodology is a panel.", + ge=0.0, + ), + ] = None + foot_traffic: Annotated[ + StrictFloat | None, + Field( + description="Store visits attributed to ad exposure. Count of incremental visits over baseline. Typically uses location-data panel methodology (`attribution_methodology: 'panel_based'`) or deterministic loyalty-card match (`attribution_methodology: 'deterministic_purchase'`).", + ge=0.0, + ), + ] = None + conversion_lift: Annotated[ + StrictFloat | None, + Field( + description='Incremental conversions attributed to the campaign — conversions above the control/holdout baseline. Reported as a fraction (0.10 = 10% lift) or as an absolute count depending on seller convention. Distinct from `conversions` (raw count of attributed conversions); conversion_lift requires a control group and an incrementality methodology.', + ge=0.0, + ), + ] = None + brand_search_lift: Annotated[ + StrictFloat | None, + Field( + description='Lift in brand search query volume attributed to the campaign — measured via search-data partnerships (Google, Microsoft) or survey methodology. Reported as a fraction (0.20 = 20% lift in branded search).', + ge=0.0, + ), + ] = None + plays: Annotated[ + StrictFloat | None, + Field( + description="Number of times the ad creative was displayed or played on DOOH or broadcast inventory. Raw play count before any impression multiplier is applied. Mirrors `forecastable-metric.json`'s `plays` token for forecast↔delivery reconciliation. Distinct from `dooh_metrics.loop_plays` (scheduled-rotation count) and from `impressions` (multiplied audience figure).", + ge=0.0, + ), + ] = None + measurement_source: Annotated[ + str | None, + Field( + description="Third-party measurement provider whose data produced this row's audience numbers. Mirrors delivery-forecast.json's measurement_source so forecast and delivery reconcile on the same declaration — distinct from demographic_system, which specifies demographic notation. Makes measured-channel rows (radio, broadcast, OOH) self-describing: a reconciliation join can tie delivered numbers to the system that measured them without consulting out-of-band context. Lowercase slug format.", + examples=[ + 'nielsen', + 'nielsen_audio', + 'videoamp', + 'comscore', + 'geopath', + 'barb', + 'agf', + 'oztam', + 'kantar', + 'barc', + 'route', + 'rajar', + 'triton', + ], + max_length=64, + pattern='^[a-z0-9_]+$', + ), + ] = None + by_event_type: Annotated[ + list[ByEventTypeItem] | None, + Field( + description='Conversion metrics broken down by event type. Spend-derived metrics (ROAS, CPA) are only available at the package/totals level since spend cannot be attributed to individual event types.' + ), + ] = None + grps: Annotated[ + StrictFloat | None, Field(description='Gross Rating Points delivered (for CPP)', ge=0.0) + ] = None + reach: Annotated[ + StrictFloat | None, + Field( + description='Unique reach in the units specified by reach_unit. When reach_unit is omitted, units are unspecified — do not compare reach values across packages or media buys without a common reach_unit. The measurement window for this value is declared in `reach_window`; when `reach_window` is omitted, the window is unspecified and buyers MUST NOT sum reach across reports (the value MAY be a daily snapshot, a cumulative total, or something else).', + ge=0.0, + ), + ] = None + reach_unit: Annotated[ + reach_unit_1.ReachUnit | None, + Field( + description='Unit of measurement for the reach field. Aligns with the reach_unit declared on optimization goals and delivery forecasts. Required when reach is present to enable cross-platform comparison.' + ), + ] = None + reach_window: Annotated[ + ReachWindow | None, + Field( + description='Measurement window for the reported `reach` and `frequency` values in this row. Declares whether the values are a per-period snapshot, a trailing rolling window, or cumulative-to-date — without this declaration, buyers summing `reach` across rows (e.g., daily delivery reports) can silently double-count audiences. Sellers SHOULD populate this whenever `reach` is present.' + ), + ] = None + frequency: Annotated[ + StrictFloat | None, + Field( + description="Average frequency per reach unit, measured over the window declared in `reach_window`. When `reach_unit` is 'households', this is average exposures per household; when 'accounts', per logged-in account; etc. When `reach_window` is omitted, the window is unspecified — buyers MUST NOT compare or average frequency values across rows.", + ge=0.0, + ), + ] = None + quartile_data: Annotated[ + QuartileData | None, + Field( + description="Audio/video quartile completion data. Null indicates the metric is not applicable to this package/buy (e.g. quartile data on a non-video buy). Individual quartiles are addressable via the leaf metric identities `quartile_25` (q1_views), `quartile_50` (q2_views), `quartile_75` (q3_views), and `quartile_100` (q4_views) for declaration, commitments, aggregates, and breakdown sorting; this object remains the canonical carrier of the values. Quartiles are player-fired events (VAST firstQuartile/midpoint/thirdQuartile/complete). `quartile_100` counts 100%-of-duration completions and is distinct from `completed_views`, which counts completions at the seller's billable view threshold (`view_duration_seconds`) when one is set." + ), + ] = None + time_based_views: Annotated[ + list[TimeBasedView] | None, + Field( + description="Time-threshold video view counts. Each entry reports views that met a continuous duration threshold under a stated basis, rather than a completion percentage (percentage-based completion is quartile_data). Thresholds of 2 and 6 seconds are RECOMMENDED cross-platform reporting points; any seller-defined threshold is permitted. One entry per (threshold_seconds, basis) pair per reporting period — sellers MUST de-duplicate before emission and MUST NOT emit the same pair twice; buyers MAY treat duplicate pairs as a seller-side conformance bug. Entries under different bases are different metrics and MUST NOT be summed (see view-threshold-basis). Primarily an autoplay/skippable-video metric (social, olv, in-feed video); completion metrics remain the currency for lean-back CTV/cinema inventory. Distinct from `views` (the single billable-threshold scalar) and from `viewability.viewed_seconds` (average in-view duration, not a threshold count). Array entries are not individually sortable in breakdown sort_by. Disclosure-grade surface: (threshold_seconds, basis) is not part of the committed-metric qualifier vocabulary, so a `committed_metrics` entry for `time_based_views` contracts the array's presence, not specific thresholds." + ), + ] = None + dooh_metrics: Annotated[ + DoohMetrics1 | None, + Field(description='DOOH-specific metrics (only included for DOOH campaigns)'), + ] = None + ooh_metrics: Annotated[ + OohMetrics | None, + Field( + description='Classic (static) OOH metrics — printed bulletins, posters, transit, and street furniture (only included for ooh campaigns). Experimental in AdCP 3.2. Static units have no play event: the delivery number is a period-level modeled audience estimate whose methodology tier is declared in estimation_basis (provider identity rides the row-level measurement_source), and the settlement artifact is the posting record — it proves the posting period, not an airing.' + ), + ] = None + viewability: Annotated[ + Viewability1 | None, + Field( + description="Viewability metrics. Viewable rate should be calculated as viewable_impressions / measurable_impressions (not total impressions), since some environments cannot measure viewability. Includes `viewed_seconds` — average in-view duration — plus optional percentile and histogram distributions over that duration; all three use the same `measurable_impressions` population and are governed by the same viewability threshold (`standard`). Sellers SHOULD include `standard` whenever measured viewability values are reported because MRC and GroupM rows are not interchangeable. The numeric leaves are addressable via the leaf metric identities `viewable_rate`, `viewable_impressions`, `measurable_impressions`, and `viewed_seconds` for declaration, commitments, aggregates, and breakdown sorting. The structured distribution carriers require explicit `viewed_seconds_percentiles` and `viewed_seconds_histogram` identities for declaration, commitment, and selection; they are not numeric aggregate rows or sort keys. This object remains the canonical carrier of every value. When a buy reports under more than one standard, contract a specific standard via the `viewability_standard` qualifier on `committed_metrics`; when the package's `committed_metrics` carry a `viewability_standard` qualifier, sellers MUST populate `standard` on reported viewability objects so reconciliation can match the qualifier." + ), + ] = None + engagements: Annotated[ + StrictFloat | None, + Field( + description="Total engagements — direct interactions with the ad beyond viewing. Includes social reactions/comments/shares, story/unit opens, interactive overlay taps on CTV, companion banner interactions on audio. Platform-specific; corresponds to the 'engagements' optimization metric. Maps to DBCFM KPI_INTERACTIONS (Interaktionen) in the Reporting/Performance block.", + ge=0.0, + ), + ] = None + follows: Annotated[ + StrictFloat | None, + Field( + description='New followers, page likes, artist/podcast/channel follows, or free channel/feed subscribes attributed to this delivery. Paid subscriptions are conversion events with `event_type: subscribe`, not `follows`.', + ge=0.0, + ), + ] = None + saves: Annotated[ + StrictFloat | None, + Field( + description='Saves, bookmarks, playlist adds, pins attributed to this delivery.', ge=0.0 + ), + ] = None + profile_visits: Annotated[ + StrictFloat | None, + Field( + description="Visits to the brand's in-platform page (profile, artist page, channel, or storefront) attributed to this delivery. Does not include external website clicks.", + ge=0.0, + ), + ] = None + engagement_rate: Annotated[ + StrictFloat | None, + Field( + description='Platform-specific engagement rate (0.0 to 1.0). Typically engagements/impressions, but definition varies by platform.', + ge=0.0, + le=1.0, + ), + ] = None + cost_per_click: Annotated[ + StrictFloat | None, Field(description='Cost per click (spend / clicks)', ge=0.0) + ] = None + cost_per_completed_view: Annotated[ + StrictFloat | None, + Field( + description="Cost per completed view (spend / completed_views). Primary CPCV pricing scalar for video/audio inventory; the package's `pricing_model` is `cpcv` when this field is the billing basis.", + ge=0.0, + ), + ] = None + cpm: Annotated[ + StrictFloat | None, + Field( + description="Cost per thousand impressions, computed as (spend / impressions) × 1000. Universal pricing scalar across CTV, display, mobile/web video, native, audio, and DOOH inventory; the package's `pricing_model` is `cpm` when this field is the billing basis. Field name aligns with the canonical `cpm` token in `enums/pricing-model.json` and `pricing-options/cpm-option.json` so buyers cross-walk pricing model → reported scalar without a translation table.", + ge=0.0, + ), + ] = None + downloads: Annotated[ + StrictFloat | None, + Field( + description="Audio/podcast downloads (IAB Podcast Measurement Technical Guidelines 2.x methodology). Distinct from `views` — for podcast inventory this is the count of podcast episode downloads; for streaming audio it is the count of stream starts that meet the platform's download threshold. Prefer this over `views` for audio inventory.", + ge=0.0, + ), + ] = None + units_sold: Annotated[ + StrictFloat | None, + Field( + description='Items sold attributed to this delivery. Retail-media scalar distinct from `conversions` — a single conversion (transaction) may carry multiple `units_sold`. Used by retail media platforms where the buyer optimizes against unit movement, not transaction count. Attribution lookback windows are platform-specific (commonly 7/14/30 days, view-through and click-through variants); sellers SHOULD declare the window via `reporting_capabilities.measurement_windows` or `measurement_terms` rather than encoding it in this scalar.', + ge=0.0, + ), + ] = None + new_to_brand_units: Annotated[ + StrictFloat | None, + Field( + description='Units sold to first-time brand buyers (count, not rate). Retail-media scalar — the unit-volume parallel to the conversion-fraction `new_to_brand_rate`. Used by retail media platforms where new-customer acquisition unit volume is a primary KPI. Same attribution-window note as `units_sold` applies.', + ge=0.0, + ), + ] = None + by_action_source: Annotated[ + list[ByActionSourceItem] | None, + Field( + description='Conversion metrics broken down by action source (website, app, in_store, etc.). Useful for omnichannel sellers where conversions occur across digital and physical channels.' + ), + ] = None + vendor_metric_values: Annotated[ + list[vendor_metric_value.VendorMetricValue] | None, + Field( + description="Reported values for vendor-defined metrics that the product's `reporting_capabilities.vendor_metrics` declared. Each entry carries the vendor (BrandRef), the metric identifier within the vendor's vocabulary, the value, optional unit, and `measurable_impressions` as the coverage denominator — vendor measurement is rarely 100% of delivered impressions, since vendors only score impressions where their SDK fires or their panel matches. When a declared vendor metric is omitted from this array, buyers infer no measurement happened (no integration). One row per `(vendor.domain, vendor.brand_id, metric_id, qualifier)` per reporting period — the same vendor metric MAY appear in multiple rows only when each carries a distinct qualifier (e.g., 7-day and 30-day attribution windows); sellers MUST de-duplicate before emission and MUST NOT emit two rows with the same tuple; buyers MAY treat duplicate rows as a seller-side conformance bug. The structured `vendor_metric_values` array is the recommended path for vendor metrics; `additionalProperties: true` on this parent object is preserved so existing free-form vendor emissions remain conformant during migration." + ), + ] = None diff --git a/src/adcp/types/domains/core/delivery_provider.py b/src/adcp/types/domains/core/delivery_provider.py index eb880a338..af5d0fc85 100644 --- a/src/adcp/types/domains/core/delivery_provider.py +++ b/src/adcp/types/domains/core/delivery_provider.py @@ -1,18 +1,23 @@ -"""Types declared by the AdCP ``core/delivery_provider`` schema. +# generated by datamodel-codegen: +# filename: core/delivery_provider.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.delivery_provider import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -from adcp.types.generated_poc.core.delivery_provider import DeliveryProvider -# Explicit exports -__all__ = ["DeliveryProvider"] +class DeliveryProvider(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + domain: Annotated[ + str, + Field( + description="Lowercase dotted provider domain, such as a provider's operating domain. Single-label and localhost-style names are invalid.", + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$', + ), + ] diff --git a/src/adcp/types/domains/core/delivery_recipient.py b/src/adcp/types/domains/core/delivery_recipient.py index e8aedbc5e..4eadba187 100644 --- a/src/adcp/types/domains/core/delivery_recipient.py +++ b/src/adcp/types/domains/core/delivery_recipient.py @@ -1,18 +1,21 @@ -"""Types declared by the AdCP ``core/delivery_recipient`` schema. +# generated by datamodel-codegen: +# filename: core/delivery_recipient.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.delivery_recipient import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import delivery_recipient_cloud -from adcp.types.generated_poc.core.delivery_recipient import DeliveryRecipient -# Explicit exports -__all__ = ["DeliveryRecipient"] +class DeliveryRecipient(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + identity: Annotated[str, Field(max_length=512, min_length=1)] + cloud: delivery_recipient_cloud.DeliveryRecipientCloud | None = None + region: Annotated[str | None, Field(max_length=128, min_length=1)] = None diff --git a/src/adcp/types/domains/core/demographic_age_range.py b/src/adcp/types/domains/core/demographic_age_range.py index fb53061cf..a9ce5b885 100644 --- a/src/adcp/types/domains/core/demographic_age_range.py +++ b/src/adcp/types/domains/core/demographic_age_range.py @@ -1,18 +1,51 @@ -"""Types declared by the AdCP ``core/demographic_age_range`` schema. +# generated by datamodel-codegen: +# filename: core/demographic_age_range.json +# timestamp: 2026-10-04T01:14:11+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.demographic_age_range import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictBool, model_validator -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.demographic_age_range import DemographicAgeRange +class DemographicAgeRange(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + min: Annotated[ + SchemaInt | None, + Field( + description='Inclusive minimum age in completed years. Omit for an open lower bound.', + ge=0, + le=150, + ), + ] = None + max: Annotated[ + SchemaInt | None, + Field( + description='Inclusive maximum age in completed years. Omit for an open upper bound.', + ge=0, + le=150, + ), + ] = None + include_unknown: Annotated[ + StrictBool, + Field( + description='Whether delivery to people whose age is unavailable is part of this predicate. This field has no default and MUST be supplied.' + ), + ] -# Explicit exports -__all__ = ["DemographicAgeRange"] + @model_validator(mode='after') + def _require_schema_required_group(self) -> DemographicAgeRange: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('min',), ('max',),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'DemographicAgeRange requires at least one of these field groups: min | max' + ) diff --git a/src/adcp/types/domains/core/demographic_predicate.py b/src/adcp/types/domains/core/demographic_predicate.py index 8a04fa496..51cee67db 100644 --- a/src/adcp/types/domains/core/demographic_predicate.py +++ b/src/adcp/types/domains/core/demographic_predicate.py @@ -1,18 +1,17 @@ -"""Types declared by the AdCP ``core/demographic_predicate`` schema. +# generated by datamodel-codegen: +# filename: core/demographic_predicate.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.demographic_predicate import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import demographic_age_range -from adcp.types.generated_poc.core.demographic_predicate import DemographicPredicate -# Explicit exports -__all__ = ["DemographicPredicate"] +class DemographicPredicate(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + age: demographic_age_range.DemographicAgeRange diff --git a/src/adcp/types/domains/core/demographic_reporting_capability.py b/src/adcp/types/domains/core/demographic_reporting_capability.py index 2acb12313..746e36616 100644 --- a/src/adcp/types/domains/core/demographic_reporting_capability.py +++ b/src/adcp/types/domains/core/demographic_reporting_capability.py @@ -1,23 +1,118 @@ -"""Types declared by the AdCP ``core/demographic_reporting_capability`` schema. +# generated by datamodel-codegen: +# filename: core/demographic_reporting_capability.json +# timestamp: 2026-10-04T01:14:11+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.demographic_reporting_capability import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictBool, model_validator + +from ..enums import demographic_system as demographic_system_1 +from . import demographic_age_range + + +class ReportingMode(StrEnum): + exact_predicates = 'exact_predicates' + enumerated_intervals = 'enumerated_intervals' + + +class Interval(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + age: demographic_age_range.DemographicAgeRange + demographic: Annotated[ + str | None, + Field( + description='Optional native demographic label returned for this interval.', + min_length=1, + pattern='\\S', + ), + ] = None + demographic_system: demographic_system_1.DemographicSystem | None = None + + +class Age(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + reporting_modes: Annotated[ + list[ReportingMode], + Field( + description='Ways this product can produce age rows. exact_predicates accepts requested canonical age predicates within the declared bounds. enumerated_intervals reports only the exact intervals declared in intervals; buyers may aggregate adjacent complete intervals but MUST NOT treat a wider or narrower interval as the requested audience.', + min_length=1, + ), + ] + min_reportable_age: Annotated[ + SchemaInt | None, + Field( + description='Lowest explicit min or max bound accepted by exact_predicates. Required when that mode is declared.', + ge=0, + le=150, + ), + ] = None + max_reportable_age: Annotated[ + SchemaInt | None, + Field( + description='Highest explicit min or max bound accepted by exact_predicates. Required when that mode is declared.', + ge=0, + le=150, + ), + ] = None + supports_unbounded_min: Annotated[ + StrictBool | None, + Field( + description='Whether exact_predicates accepts a canonical age predicate with min omitted. Required when that mode is declared.' + ), + ] = None + supports_unbounded_max: Annotated[ + StrictBool | None, + Field( + description='Whether exact_predicates accepts a canonical age predicate with max omitted. Required when that mode is declared.' + ), + ] = None + intervals: Annotated[ + list[Interval] | None, + Field( + description='Fixed reportable age intervals for enumerated_intervals. Bounds are authoritative capability data, not a protocol-wide bucket taxonomy. Optional native labels identify the measurement-system code returned with the interval.', + min_length=1, + ), + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.demographic_reporting_capability import ( - Age, - DemographicReportingCapability, - Interval, - ReportingMode, -) +class DemographicReportingCapability(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + age: Annotated[ + Age | None, Field(description='Machine-comparable age ranges this product can report.') + ] = None + demographic_systems: Annotated[ + list[demographic_system_1.DemographicSystem] | None, + Field( + description='Measurement-system notations this product may return. A code remains opaque unless its capability interval and response row also carry a canonical age predicate.', + min_length=1, + ), + ] = None + may_suppress_small_cells: Annotated[ + StrictBool, + Field( + description='Whether privacy, policy, or measurement thresholds may suppress otherwise reportable demographic rows. When true, buyers must inspect by_demographic_suppressed before testing whether rows reconcile to package totals.' + ), + ] -# Explicit exports -__all__ = ["Age", "DemographicReportingCapability", "Interval", "ReportingMode"] + @model_validator(mode='after') + def _require_schema_required_group(self) -> DemographicReportingCapability: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('age',), ('demographic_systems',),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'DemographicReportingCapability requires at least one of these field groups: age | demographic_systems' + ) diff --git a/src/adcp/types/domains/core/demographic_targeting_capability.py b/src/adcp/types/domains/core/demographic_targeting_capability.py index b70c84747..ff4cd3a67 100644 --- a/src/adcp/types/domains/core/demographic_targeting_capability.py +++ b/src/adcp/types/domains/core/demographic_targeting_capability.py @@ -1,24 +1,115 @@ -"""Types declared by the AdCP ``core/demographic_targeting_capability`` schema. +# generated by datamodel-codegen: +# filename: core/demographic_targeting_capability.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.demographic_targeting_capability import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import age_determination_basis, age_verification_method +from . import demographic_age_range + + +class ExecutionMode(StrEnum): + continuous_bounds = 'continuous_bounds' + enumerated_intervals = 'enumerated_intervals' + signals = 'signals' + + +class UnknownHandling(StrEnum): + selectable = 'selectable' + always_excluded = 'always_excluded' + always_included = 'always_included' + + +class Interval(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + interval_id: Annotated[ + str, + Field( + description='Seller-scoped interval identifier echoed in demographic targeting readback.', + min_length=1, + ), + ] + age: demographic_age_range.DemographicAgeRange + + +class Age(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + execution_modes: Annotated[ + list[ExecutionMode], + Field( + description='Ways this product can execute canonical age intent. continuous_bounds accepts canonical predicates whose explicit integer bounds fall between min_supported_age and max_supported_age, inclusive; omitted bounds additionally require the corresponding supports_unbounded_min or supports_unbounded_max flag. enumerated_intervals accepts exact unions of the declared intervals; signals resolves through signals carrying authoritative demographic_predicate metadata.', + min_length=1, + ), + ] + min_supported_age: Annotated[ + SchemaInt | None, + Field( + description='Lowest integer accepted as an explicit min or max bound by continuous_bounds. This limit does not replace an omitted min: omission means no lower age restriction. Required when that execution mode is declared.', + ge=0, + le=150, + ), + ] = None + max_supported_age: Annotated[ + SchemaInt | None, + Field( + description='Highest integer accepted as an explicit min or max bound by continuous_bounds. This limit does not replace an omitted max: omission means no upper age restriction, so a request such as min 65 remains 65+ even when max_supported_age is 65. Required when that execution mode is declared.', + ge=0, + le=150, + ), + ] = None + supports_unbounded_min: Annotated[ + StrictBool | None, + Field( + description='Whether continuous_bounds accepts a predicate with min omitted. When true, the omitted min means no lower age restriction and MUST NOT be replaced by min_supported_age. When false, the seller MUST reject open-lower predicates. Required when continuous_bounds is declared.' + ), + ] = None + supports_unbounded_max: Annotated[ + StrictBool | None, + Field( + description='Whether continuous_bounds accepts a predicate with max omitted. When true, the omitted max means no upper age restriction and MUST NOT be replaced by max_supported_age; for example, min 65 remains 65+. When false, the seller MUST reject open-upper predicates. Required when continuous_bounds is declared.' + ), + ] = None + unknown_handling: Annotated[ + UnknownHandling, + Field( + description='How this product handles people whose age is unavailable. selectable honors either include_unknown value; always_excluded supports only false; always_included supports only true. A seller MUST reject a request incompatible with this declaration rather than silently changing it.' + ), + ] + supported_bases: Annotated[ + list[age_determination_basis.AgeDeterminationBasis] | None, + Field( + description='User-level age determination bases this product can use for exact execution. Sellers MUST publish only bases whose provenance they can preserve and audit; they MUST NOT infer a basis from an opaque platform age bucket. When omitted, the product makes no basis-aware targeting commitment and MUST reject requests that specify accepted_bases.', + min_length=1, + ), + ] = None + supported_verification_methods: Annotated[ + list[age_verification_method.AgeVerificationMethod] | None, + Field( + description='Age verification methods available when supported_bases contains verified. This is an availability declaration only: a method must still establish the entire requested age predicate. Threshold-only methods such as World ID cannot establish an arbitrary bounded range.', + min_length=1, + ), + ] = None + intervals: Annotated[ + list[Interval] | None, + Field( + description='Seller-owned age intervals available to enumerated_intervals execution. Bounds are authoritative capability data, not a protocol-wide bucket taxonomy. Exact compilation may union adjacent intervals but MUST NOT introduce gaps or extra ages.', + min_length=1, + ), + ] = None -from adcp.types.generated_poc.core.demographic_targeting_capability import ( - Age, - DemographicTargetingCapability, - ExecutionMode, - Interval, - UnknownHandling, -) -# Explicit exports -__all__ = ["Age", "DemographicTargetingCapability", "ExecutionMode", "Interval", "UnknownHandling"] +class DemographicTargetingCapability(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + age: Annotated[Age, Field(description='Age-targeting execution available for this product.')] diff --git a/src/adcp/types/domains/core/demographic_targeting_intent.py b/src/adcp/types/domains/core/demographic_targeting_intent.py index 37501ab05..cc75e5c63 100644 --- a/src/adcp/types/domains/core/demographic_targeting_intent.py +++ b/src/adcp/types/domains/core/demographic_targeting_intent.py @@ -1,21 +1,37 @@ -"""Types declared by the AdCP ``core/demographic_targeting_intent`` schema. +# generated by datamodel-codegen: +# filename: core/demographic_targeting_intent.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.demographic_targeting_intent import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import age_determination_basis, age_verification_method +from .demographic_age_range import DemographicAgeRange + + +class Age(DemographicAgeRange): + accepted_bases: Annotated[ + list[age_determination_basis.AgeDeterminationBasis] | None, + Field( + description='User-level age determination bases the buyer permits for this predicate. Sellers MUST use only these bases or reject the request; they MUST NOT silently fall back to another basis. When omitted, the buyer imposes no determination-basis constraint beyond product capability and any age_restriction. population_estimate is structurally unavailable because aggregate evidence cannot identify an eligible user.', + min_length=1, + ), + ] = None + accepted_verification_methods: Annotated[ + list[age_verification_method.AgeVerificationMethod] | None, + Field( + description='Verification methods acceptable when verified is an accepted basis. This field narrows verified demographic execution; age_restriction.accepted_methods independently governs legal eligibility and the two sets are intersected when both are present.', + min_length=1, + ), + ] = None -from adcp.types.generated_poc.core.demographic_targeting_intent import ( - Age, - DemographicTargetingIntent, -) -# Explicit exports -__all__ = ["Age", "DemographicTargetingIntent"] +class DemographicTargetingIntent(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + age: Age diff --git a/src/adcp/types/domains/core/demographic_targeting_resolution.py b/src/adcp/types/domains/core/demographic_targeting_resolution.py index a97688d78..20bc8cbdb 100644 --- a/src/adcp/types/domains/core/demographic_targeting_resolution.py +++ b/src/adcp/types/domains/core/demographic_targeting_resolution.py @@ -1,24 +1,97 @@ -"""Types declared by the AdCP ``core/demographic_targeting_resolution`` schema. +# generated by datamodel-codegen: +# filename: core/demographic_targeting_resolution.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.demographic_targeting_resolution import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import age_determination_basis, age_verification_method +from . import demographic_predicate, demographic_targeting_intent +from . import ext as ext_1 +from . import signal_ref + + +class IntervalId(ScalarStr): + __slots__ = () + _constraints = {'min_length': 1} + + +class Execution(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + type: Literal['continuous_bounds'] = 'continuous_bounds' + ext: ext_1.ExtensionObject | None = None + + +class Execution1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + type: Literal['enumerated_intervals'] = 'enumerated_intervals' + interval_ids: Annotated[ + list[IntervalId], + Field( + description='Seller-scoped interval identifiers whose union produced applied.', + min_length=1, + ), + ] + ext: ext_1.ExtensionObject | None = None + + +class Execution2(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + type: Literal['signals'] = 'signals' + signal_refs: Annotated[ + list[signal_ref.SignalRef], + Field( + description='Authoritative signal references whose demographic predicates were unioned to produce applied. Signal names alone never establish equivalence.', + min_length=1, + ), + ] + ext: ext_1.ExtensionObject | None = None -from adcp.types.generated_poc.core.demographic_targeting_resolution import ( - DemographicTargetingResolution, - Execution, - Execution1, - Execution2, - IntervalId, -) -# Explicit exports -__all__ = ["DemographicTargetingResolution", "Execution", "Execution1", "Execution2", "IntervalId"] +class DemographicTargetingResolution(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + requested: Annotated[ + demographic_targeting_intent.DemographicTargetingIntent, + Field( + description='Canonical demographic predicate and determination constraints requested by the buyer.' + ), + ] + applied: Annotated[ + demographic_predicate.DemographicPredicate, + Field(description='Canonical demographic predicate actually applied by the seller.'), + ] + equivalent: Annotated[ + Literal[True], + Field( + description='Always true for a stored package. requested and applied MUST denote exactly the same set, including unknown-age membership; sellers reject non-equivalent requests instead of storing an alternative.' + ), + ] + execution: Execution | Execution1 | Execution2 + applied_bases: Annotated[ + list[age_determination_basis.AgeDeterminationBasis] | None, + Field( + description='Effective user-level determination bases configured for this package after intersecting buyer accepted_bases, product supported_bases, and age_restriction. This is an auditable configuration readback, not proof that every impression used every listed basis.', + min_length=1, + ), + ] = None + applied_verification_methods: Annotated[ + list[age_verification_method.AgeVerificationMethod] | None, + Field( + description='Effective verification methods when applied_bases contains verified. A method name does not establish that a particular proof or claim was valid; runtime verification and claim entailment remain required.', + min_length=1, + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/deployment.py b/src/adcp/types/domains/core/deployment.py index ba9d988e6..533d40d74 100644 --- a/src/adcp/types/domains/core/deployment.py +++ b/src/adcp/types/domains/core/deployment.py @@ -1,18 +1,86 @@ -"""Types declared by the AdCP ``core/deployment`` schema. +# generated by datamodel-codegen: +# filename: core/deployment.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.deployment import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import activation_key as activation_key_1 + + +class Deployment1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + type: Annotated[ + Literal['platform'], + Field(description='Discriminator indicating this is a platform-based deployment'), + ] = 'platform' + platform: Annotated[str, Field(description='Platform identifier for DSPs')] + account: Annotated[str | None, Field(description='Account identifier if applicable')] = None + is_live: Annotated[ + StrictBool, Field(description='Whether signal is currently active on this deployment') + ] + activation_key: Annotated[ + activation_key_1.ActivationKey | None, + Field( + description='The key to use for targeting. Only present if is_live=true AND requester has access to this deployment.' + ), + ] = None + estimated_activation_duration_minutes: Annotated[ + StrictFloat | None, + Field( + description='Estimated time to activate if not live, or to complete activation if in progress', + ge=0.0, + ), + ] = None + deployed_at: Annotated[ + AwareDatetime | None, + Field(description='Timestamp when activation completed (if is_live=true)'), + ] = None + + +class Deployment2(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + type: Annotated[ + Literal['agent'], + Field(description='Discriminator indicating this is an agent URL-based deployment'), + ] = 'agent' + agent_url: Annotated[AnyUrl, Field(description='URL identifying the deployment agent')] + account: Annotated[str | None, Field(description='Account identifier if applicable')] = None + is_live: Annotated[ + StrictBool, Field(description='Whether signal is currently active on this deployment') + ] + activation_key: Annotated[ + activation_key_1.ActivationKey | None, + Field( + description='The key to use for targeting. Only present if is_live=true AND requester has access to this deployment.' + ), + ] = None + estimated_activation_duration_minutes: Annotated[ + StrictFloat | None, + Field( + description='Estimated time to activate if not live, or to complete activation if in progress', + ge=0.0, + ), + ] = None + deployed_at: Annotated[ + AwareDatetime | None, + Field(description='Timestamp when activation completed (if is_live=true)'), + ] = None -from adcp.types.generated_poc.core.deployment import Deployment, Deployment1, Deployment2 -# Explicit exports -__all__ = ["Deployment", "Deployment1", "Deployment2"] +Deployment = Annotated[ + Deployment1 | Deployment2, + Field( + description='A signal deployment to a specific deployment target with activation status and key', + discriminator='type', + title='Deployment', + ), +] diff --git a/src/adcp/types/domains/core/destination.py b/src/adcp/types/domains/core/destination.py index c9e2e9b78..cbb8703fe 100644 --- a/src/adcp/types/domains/core/destination.py +++ b/src/adcp/types/domains/core/destination.py @@ -1,18 +1,53 @@ -"""Types declared by the AdCP ``core/destination`` schema. +# generated by datamodel-codegen: +# filename: core/destination.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.destination import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.destination import Destination, Destination1, Destination2 +class Destination1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + type: Annotated[ + Literal['platform'], + Field(description='Discriminator indicating this is a platform-based deployment'), + ] = 'platform' + platform: Annotated[ + str, + Field(description="Platform identifier for DSPs (e.g., 'the-trade-desk', 'amazon-dsp')"), + ] + account: Annotated[ + str | None, Field(description='Optional account identifier on the platform') + ] = None + + +class Destination2(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + type: Annotated[ + Literal['agent'], + Field(description='Discriminator indicating this is an agent URL-based deployment'), + ] = 'agent' + agent_url: Annotated[ + AnyUrl, Field(description='URL identifying the deployment agent (for sales agents, etc.)') + ] + account: Annotated[ + str | None, Field(description='Optional account identifier on the agent') + ] = None + -# Explicit exports -__all__ = ["Destination", "Destination1", "Destination2"] +Destination = Annotated[ + Destination1 | Destination2, + Field( + description='A deployment target where signals can be activated (DSP, sales agent, etc.)', + discriminator='type', + title='Destination', + ), +] diff --git a/src/adcp/types/domains/core/destination_item.py b/src/adcp/types/domains/core/destination_item.py index 975c34f89..d7d8aa0eb 100644 --- a/src/adcp/types/domains/core/destination_item.py +++ b/src/adcp/types/domains/core/destination_item.py @@ -1,22 +1,88 @@ -"""Types declared by the AdCP ``core/destination_item`` schema. +# generated by datamodel-codegen: +# filename: core/destination_item.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.destination_item import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import ext as ext_1 +from . import offering_asset_group +from . import price as price_1 + + +class Location(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + lat: Annotated[ + StrictFloat, Field(description='Latitude in decimal degrees (WGS 84).', ge=-90.0, le=90.0) + ] + lng: Annotated[ + StrictFloat, + Field(description='Longitude in decimal degrees (WGS 84).', ge=-180.0, le=180.0), + ] + + +class DestinationType(StrEnum): + beach = 'beach' + mountain = 'mountain' + urban = 'urban' + cultural = 'cultural' + adventure = 'adventure' + wellness = 'wellness' + cruise = 'cruise' -from adcp.types.generated_poc.core.destination_item import ( - DestinationItem, - DestinationType, - Location, -) -# Explicit exports -__all__ = ["DestinationItem", "DestinationType", "Location"] +class DestinationItem(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + destination_id: Annotated[str, Field(description='Unique identifier for this destination.')] + name: Annotated[ + str, Field(description="Destination name (e.g., 'Barcelona', 'Bali', 'Swiss Alps').") + ] + description: Annotated[ + str | None, + Field(description='Destination description highlighting attractions and appeal.'), + ] = None + city: Annotated[str | None, Field(description='City name, if applicable.')] = None + region: Annotated[str | None, Field(description='State, province, or region name.')] = None + country: Annotated[ + str | None, Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + ] = None + location: Annotated[ + Location | None, Field(description='Geographic coordinates of the destination.') + ] = None + destination_type: Annotated[ + DestinationType | None, Field(description='Destination category.') + ] = None + price: Annotated[ + price_1.Price | None, Field(description='Starting price for a trip to this destination.') + ] = None + image_url: Annotated[AnyUrl | None, Field(description='Destination hero image URL.')] = None + url: Annotated[AnyUrl | None, Field(description='Destination landing page or booking URL.')] = ( + None + ) + rating: Annotated[ + StrictFloat | None, Field(description='Destination rating (1–5).', ge=1.0, le=5.0) + ] = None + tags: Annotated[ + list[str] | None, + Field( + description="Tags for filtering (e.g., 'family', 'romantic', 'solo', 'winter-sun').", + min_length=1, + ), + ] = None + assets: Annotated[ + list[offering_asset_group.OfferingAssetGroup] | None, + Field( + description="Typed creative asset pools for this destination. Uses the same OfferingAssetGroup structure as offering-type catalogs. Standard group IDs: 'images_landscape' (destination hero), 'images_vertical' (9:16 for Snap, Stories), 'images_square' (1:1). Enables formats to declare typed image requirements that map unambiguously to the right asset regardless of platform.", + min_length=1, + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/diagnostic_issue.py b/src/adcp/types/domains/core/diagnostic_issue.py index cdf3f97ec..d3412f7b0 100644 --- a/src/adcp/types/domains/core/diagnostic_issue.py +++ b/src/adcp/types/domains/core/diagnostic_issue.py @@ -1,18 +1,33 @@ -"""Types declared by the AdCP ``core/diagnostic_issue`` schema. +# generated by datamodel-codegen: +# filename: core/diagnostic_issue.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.diagnostic_issue import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.diagnostic_issue import DiagnosticIssue, Severity +class Severity(StrEnum): + error = 'error' + warning = 'warning' + info = 'info' + -# Explicit exports -__all__ = ["DiagnosticIssue", "Severity"] +class DiagnosticIssue(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + severity: Annotated[ + Severity, + Field( + description="'error': blocks optimization until resolved. 'warning': optimization works but effectiveness is reduced. 'info': suggestion for improvement." + ), + ] + message: Annotated[ + str, + Field(description='Human/agent-readable description of the issue and how to resolve it.'), + ] diff --git a/src/adcp/types/domains/core/downstream_connection_requirement.py b/src/adcp/types/domains/core/downstream_connection_requirement.py index 0d3cdf8f6..8477377df 100644 --- a/src/adcp/types/domains/core/downstream_connection_requirement.py +++ b/src/adcp/types/domains/core/downstream_connection_requirement.py @@ -1,32 +1,136 @@ -"""Types declared by the AdCP ``core/downstream_connection_requirement`` schema. +# generated by datamodel-codegen: +# filename: core/downstream_connection_requirement.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.downstream_connection_requirement import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field + + +class ConnectionType(StrEnum): + advertiser_account = 'advertiser_account' + publisher_identity = 'publisher_identity' + post_authorization = 'post_authorization' + + +class RequiredForItem(ScalarStr): + __slots__ = () + _constraints = {'min_length': 1} + _json_schema_extra = { + 'examples': ['list_creatives', 'sync_creatives', 'create_media_buy', 'get_media_buy_delivery', 'get_creative_delivery'], + } + + +class Scope(StrEnum): + account = 'account' + identity = 'identity' + post = 'post' + unknown = 'unknown' + + +class Status(StrEnum): + connected = 'connected' + missing = 'missing' + pending = 'pending' + expired = 'expired' + revoked = 'revoked' + not_required = 'not_required' + unknown = 'unknown' + + +class ResourceRef(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + platform_account_id: Annotated[ + str | None, + Field( + description='Provider-native advertiser or business account id, when safe to disclose.' + ), + ] = None + identity_id: Annotated[ + str | None, + Field( + description='Provider-native creator, page, channel, organization, or profile id, when safe to disclose.' + ), + ] = None + handle: Annotated[ + str | None, + Field(description='Provider-native public handle for the owning identity, when available.'), + ] = None + profile_url: Annotated[ + AnyUrl | None, Field(description='Public URL for the owning identity, when available.') + ] = None + post_id: Annotated[ + str | None, + Field( + description='Provider-native post id, when the grant is post-scoped or the failed request referenced a specific post.' + ), + ] = None + post_url: Annotated[ + AnyUrl | None, Field(description='Public URL for the referenced post, when available.') + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.downstream_connection_requirement import ( - ConnectionType, - DownstreamConnectionRequirement, - RequiredForItem, - ResourceRef, - Scope, - Status, -) - -# Explicit exports -__all__ = [ - "ConnectionType", - "DownstreamConnectionRequirement", - "RequiredForItem", - "ResourceRef", - "Scope", - "Status", -] +class DownstreamConnectionRequirement(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + provider: Annotated[ + str | None, + Field( + description='Stable provider or platform namespace, preferably lowercase. Examples: `social.example`, `shortvideo.example`, or a seller-defined namespace. Omit only when the requirement is provider-agnostic, or when an `authorization_url` fully routes the human to the correct provider-specific connection flow.' + ), + ] = None + connection_type: Annotated[ + ConnectionType, + Field( + description='Kind of downstream connection required. `advertiser_account` is the platform account used to buy/manage ads. `publisher_identity` is the creator, page, channel, organization, or profile that owns source posts. `post_authorization` is a post-scoped grant when the platform authorizes individual posts instead of, or in addition to, the owning identity.' + ), + ] + required_for: Annotated[ + list[RequiredForItem] | None, + Field( + description='Concrete AdCP protocol operation names that require this downstream connection. Sellers SHOULD include this in product declarations when the requirement is known ahead of time, and in AUTHORIZATION_REQUIRED details when it explains the failed operation. Prefer specific operation names such as `list_creatives`, `sync_creatives`, `create_media_buy`, `get_media_buy_delivery`, or `get_creative_delivery` over broad category labels such as `reporting`.' + ), + ] = None + scope: Annotated[Scope | None, Field(description='Granularity of the downstream grant.')] = None + status: Annotated[ + Status | None, + Field( + description='Current seller-observed state for this downstream connection when known. Product declarations MAY omit status or use `unknown`; AUTHORIZATION_REQUIRED details SHOULD use `missing`, `expired`, or `revoked` for the connection that blocked the call.' + ), + ] = None + connection_id: Annotated[ + str | None, + Field( + description='Seller-defined identifier for an already-created downstream connection. Omit when no connection exists yet or when exposing it would leak platform/account state.' + ), + ] = None + resource_ref: Annotated[ + ResourceRef | None, + Field( + description='Optional opaque provider-native resource hint, such as a platform account id, profile URL, handle, channel id, post id, or post URL. This is a hint for routing authorization, not proof that authorization exists.' + ), + ] = None + authorization_url: Annotated[ + AnyUrl | None, + Field( + description='Seller-hosted or provider-hosted URL where a human can complete or restore this downstream connection.' + ), + ] = None + authorization_instructions: Annotated[ + str | None, + Field( + description='Human-readable instructions for completing or restoring this downstream connection.' + ), + ] = None + expires_at: Annotated[ + AwareDatetime | None, + Field(description='Expiration time for the downstream grant, when known.'), + ] = None diff --git a/src/adcp/types/domains/core/duration.py b/src/adcp/types/domains/core/duration.py index a7b71fa1e..9fc909dbe 100644 --- a/src/adcp/types/domains/core/duration.py +++ b/src/adcp/types/domains/core/duration.py @@ -1,18 +1,35 @@ -"""Types declared by the AdCP ``core/duration`` schema. +# generated by datamodel-codegen: +# filename: core/duration.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.duration import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.duration import Duration, Unit +class Unit(StrEnum): + seconds = 'seconds' + minutes = 'minutes' + hours = 'hours' + days = 'days' + campaign = 'campaign' + -# Explicit exports -__all__ = ["Duration", "Unit"] +class Duration(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + interval: Annotated[ + SchemaInt, + Field(description="Number of time units. Must be 1 when unit is 'campaign'.", ge=1), + ] + unit: Annotated[ + Unit, + Field( + description="Time unit. 'seconds' for sub-minute precision. 'campaign' spans the full campaign flight." + ), + ] diff --git a/src/adcp/types/domains/core/education_item.py b/src/adcp/types/domains/core/education_item.py index ca25afe71..8871d79a5 100644 --- a/src/adcp/types/domains/core/education_item.py +++ b/src/adcp/types/domains/core/education_item.py @@ -1,18 +1,107 @@ -"""Types declared by the AdCP ``core/education_item`` schema. +# generated by datamodel-codegen: +# filename: core/education_item.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.education_item import +from datetime import date +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import ext as ext_1 +from . import offering_asset_group +from . import price as price_1 + + +class DegreeType(StrEnum): + certificate = 'certificate' + associate = 'associate' + bachelor = 'bachelor' + master = 'master' + doctorate = 'doctorate' + professional = 'professional' + bootcamp = 'bootcamp' + + +class Level(StrEnum): + beginner = 'beginner' + intermediate = 'intermediate' + advanced = 'advanced' + + +class Modality(StrEnum): + online = 'online' + in_person = 'in_person' + hybrid = 'hybrid' -from adcp.types.generated_poc.core.education_item import DegreeType, EducationItem, Level, Modality -# Explicit exports -__all__ = ["DegreeType", "EducationItem", "Level", "Modality"] +class EducationItem(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + program_id: Annotated[str, Field(description='Unique identifier for this program or course.')] + name: Annotated[ + str, + Field( + description="Program or course name (e.g., 'MSc Computer Science', 'Digital Marketing Certificate')." + ), + ] + school: Annotated[str, Field(description='Institution or provider name.')] + description: Annotated[ + str | None, + Field(description='Program description including curriculum highlights and outcomes.'), + ] = None + subject: Annotated[ + str | None, + Field( + description="Subject area or field of study (e.g., 'computer-science', 'business', 'healthcare')." + ), + ] = None + degree_type: Annotated[DegreeType | None, Field(description='Type of credential awarded.')] = ( + None + ) + level: Annotated[Level | None, Field(description='Difficulty or prerequisite level.')] = None + price: Annotated[price_1.Price | None, Field(description='Tuition or course fee.')] = None + duration: Annotated[ + str | None, + Field( + description="Program duration as a human-readable string (e.g., '4 weeks', '2 years', '6 months')." + ), + ] = None + start_date: Annotated[ + date | None, Field(description='Next available start date (ISO 8601 date).') + ] = None + language: Annotated[ + str | None, Field(description="Language of instruction (e.g., 'en', 'nl', 'es').") + ] = None + modality: Annotated[Modality | None, Field(description='Delivery format.')] = None + location: Annotated[ + str | None, + Field( + description="Campus or instruction location (e.g., 'Amsterdam, NL'). Omit for fully online programs." + ), + ] = None + image_url: Annotated[AnyUrl | None, Field(description='Program or institution image URL.')] = ( + None + ) + url: Annotated[AnyUrl | None, Field(description='Program landing page or enrollment URL.')] = ( + None + ) + tags: Annotated[ + list[str] | None, + Field( + description="Tags for filtering (e.g., 'stem', 'scholarship-available', 'evening-classes').", + min_length=1, + ), + ] = None + assets: Annotated[ + list[offering_asset_group.OfferingAssetGroup] | None, + Field( + description="Typed creative asset pools for this program. Uses the same OfferingAssetGroup structure as offering-type catalogs. Standard group IDs: 'images_landscape' (campus/program hero), 'images_vertical' (9:16 for Stories), 'logo' (institution logo). Enables formats to declare typed image requirements that map unambiguously to the right asset regardless of platform.", + min_length=1, + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/error.py b/src/adcp/types/domains/core/error.py index 30113f762..755bed423 100644 --- a/src/adcp/types/domains/core/error.py +++ b/src/adcp/types/domains/core/error.py @@ -1,25 +1,167 @@ -"""Types declared by the AdCP ``core/error`` schema. +# generated by datamodel-codegen: +# filename: core/error.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.error import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictBool, StrictFloat + + +class BuyerReason(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + code: Annotated[ + str, + Field( + description="Machine-readable buyer-actionable reason from the standard `enums/error-code.json` vocabulary or a producer-specific extension. Extensions MUST follow the standard `X_{VENDOR}_{CODE}` naming rule. The wire field is open for forward compatibility. Receivers MUST accept unknown values and use the enclosing `error.recovery` plus this object's `message` as the fallback.", + max_length=64, + min_length=1, + ), + ] + message: Annotated[ + str, + Field( + description='Buyer-safe explanation of the actionable failure. MUST NOT expose vendor identifiers, ad-server type names, internal object names, internal IDs, stack traces, or other producer-private implementation details.', + min_length=1, + ), + ] + + +class DiscriminatorItem(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + property_name: Annotated[ + str, + Field( + description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' + ), + ] + value: Annotated[ + str | StrictFloat | StrictBool | None, + Field( + description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." + ), + ] + + +class Issue(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + pointer: Annotated[ + str, + Field( + description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." + ), + ] + message: Annotated[ + str, + Field(description='Human-readable description of why this specific field was rejected.'), + ] + keyword: Annotated[ + str, + Field( + description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." + ), + ] + schemaPath: Annotated[ + str | None, + Field( + description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." + ), + ] = None + schema_id: Annotated[ + str | None, + Field( + description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." + ), + ] = None + discriminator: Annotated[ + list[DiscriminatorItem] | None, + Field( + description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." + ), + ] = None + + +class Recovery(StrEnum): + transient = 'transient' + correctable = 'correctable' + terminal = 'terminal' + + +class Source(StrEnum): + producer = 'producer' + sdk = 'sdk' -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.error import ( - BuyerReason, - DiscriminatorItem, - Error, - Issue, - Recovery, - Source, -) - -# Explicit exports -__all__ = ["BuyerReason", "DiscriminatorItem", "Error", "Issue", "Recovery", "Source"] +class Error(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + code: Annotated[ + str, + Field( + description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', + max_length=64, + min_length=1, + ), + ] + message: Annotated[str, Field(description='Human-readable error message')] + buyer_reason: Annotated[ + BuyerReason | None, + Field( + description='Optional buyer-actionable classification of the failure. Use this when the enclosing error code or message is too coarse or contains producer-internal context that must not cross the buyer trust boundary. `code` reuses the comprehensive standard vocabulary and recovery classifications published in `enums/error-code.json`, including policy, governance, account, commercial, inventory, and creative failures. The wire field remains open for forward compatibility. `message` MUST be safe to show to the buyer and MUST NOT contain vendor identifiers, ad-server type names, internal object names, internal IDs, stack traces, or other producer-private implementation details. When present, the enclosing `error.recovery` MUST classify the buyer-actionable reason. If both the enclosing code and buyer reason are registered, their standard recovery classifications MUST agree with each other and with `error.recovery`; sellers MUST choose the closest compatible enclosing code rather than retain a conflicting upstream transport wrapper. One buyer_reason classifies one error object. If a rejection contains independently actionable failures from different classes, sellers MUST emit separate error objects or omit buyer_reason rather than select a misleading single class.' + ), + ] = None + field: Annotated[ + str | None, + Field( + description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." + ), + ] = None + suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None + retry_after: Annotated[ + StrictFloat | None, + Field( + description='Seconds to wait before retrying the operation. AdCP 3.2 producers MUST emit an integer from 1 through 3600. The 3.x schema continues to accept finite fractional values for backward compatibility with earlier producers; consumers receiving one MUST round up to the next whole second before clamping so the retry is never scheduled earlier than intended. Non-finite values are treated as absent.', + ge=1.0, + le=3600.0, + ), + ] = None + issues: Annotated[ + list[Issue] | None, + Field( + description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' + ), + ] = None + details: Annotated[ + dict[str, Any] | None, + Field( + description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_OPTION",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' + ), + ] = None + recovery: Annotated[ + Recovery | None, + Field( + description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). AdCP 3.2 producers MUST populate `recovery` on every error; 3.1 producers SHOULD populate it. The shared 3.x schema intentionally does not add `recovery` to `required` so retained and live errors from earlier 3.x producers remain decodable. When `buyer_reason` is present, `recovery` is required and MUST classify that buyer-actionable reason. If the enclosing code and buyer reason are both registered, their standard recovery classifications MUST agree with each other and with this field. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. When a legacy error omits `recovery`, receivers use the registered classification for a known code and fall back to `transient` for an unknown code, subject to the bounded retry budget. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known top-level and buyer-reason codes; `error.recovery` on the wire is authoritative when present.' + ), + ] = None + source: Annotated[ + Source | None, + Field( + description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' + ), + ] = None + sdk_id: Annotated[ + str | None, + Field( + description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' + ), + ] = None diff --git a/src/adcp/types/domains/core/evaluator_spec.py b/src/adcp/types/domains/core/evaluator_spec.py index 62fc620eb..7ad721736 100644 --- a/src/adcp/types/domains/core/evaluator_spec.py +++ b/src/adcp/types/domains/core/evaluator_spec.py @@ -1,42 +1,220 @@ -"""Types declared by the AdCP ``core/evaluator_spec`` schema. +# generated by datamodel-codegen: +# filename: core/evaluator_spec.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.evaluator_spec import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AnyUrl, ConfigDict, Field, StrictFloat + +from ..content_standards import artifact +from . import ext as ext_1 +from . import feature_requirement as feature_requirement_1 + + +class Direction(StrEnum): + maximize = 'maximize' + minimize = 'minimize' + + +class RankByItem(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + feature_id: Annotated[ + str, + Field( + description='Creative feature to order by (discovered via get_adcp_capabilities; the same feature_id space the chosen evaluator form returns in eval.features[]).' + ), + ] + direction: Annotated[ + Direction | None, + Field( + description='Sort direction for this feature: `maximize` ranks higher feature values first (e.g. creative_quality_score), `minimize` ranks lower values first (e.g. predicted_cpa).' + ), + ] = Direction.maximize + + +class FeatureAgent(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + agent_url: Annotated[ + AnyUrl, + Field( + description="URL of the get_creative_features-capable agent the producing agent calls to obtain creative-feature values for the gate. MUST use https:// and MUST match an entry in the seller's `creative_policy.accepted_verifiers[].agent_url`; off-list → `EVALUATOR_AGENT_NOT_ACCEPTED`." + ), + ] + feature_id: Annotated[ + str | None, + Field( + description="Optional canonical feature_id the producing agent SHOULD request against this agent. When present it SHOULD match the agent's `accepted_verifiers[].feature_id` or be omitted; when absent the seller selects a feature at evaluation time. Resolves selector ambiguity exactly as the provenance verify_agent.feature_id does." + ), + ] = None + + +class EvalBudget(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + max_calls: Annotated[ + SchemaInt | None, + Field(description='Soft cap on the number of judge calls the evaluator should make.', ge=1), + ] = None + max_seconds: Annotated[ + StrictFloat | None, + Field(description='Soft cap on wall-clock seconds the evaluation should consume.', ge=0.0), + ] = None + + +class RankByItem1(RankByItem): + pass + + +class RankByItem2(RankByItem): + pass + + +class EvaluatorSpec2(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + feature_requirement: Annotated[ + list[feature_requirement_1.FeatureRequirement] | None, + Field( + description='Optional hard GATE over creative-feature values — the predicates a leaf MUST satisfy for the producing agent to recommend/return it. Reuses the feature-requirement shape (min_value/max_value for quantitative features like creative_quality_score, allowed_values for binary/categorical) — the same predicate vocabulary that gates property/audience filters, which its own schema names as an intended creative-gate reuse. A leaf that fails any predicate is DROPPED from the agent\'s best_of_n survivors before ranking — this is internal pruning of which leaves the agent recommends, not an AdCP-layer block of an already-produced billable leaf (what is produced/billed is governed by max_variants/max_creatives/max_spend). Distinct from `rank_by`: the gate is a pass/fail predicate (drop on fail), `rank_by` is an ordering over survivors. Each predicate\'s `if_not_covered` (exclude|include, default exclude) is the fail-open knob when the source cannot measure that feature. A pass/warn/fail verdict is expressed as a categorical string feature value gated via `allowed_values` (e.g. ["pass"] or ["pass","warn"]) — the buyer\'s predicate decides whether warn passes; the verdict is derived, never stored on creative-feature-result. Omit to leave evaluation advisory (no leaf is dropped).', + min_length=1, + ), + ] = None + rank_by: Annotated[ + list[RankByItem1] | None, + Field( + description='Optional RANK ordering over creative-feature values — an ordered list (most significant first) the agent uses to order the gate survivors into recommended/rank. An explicit {feature_id, direction} ordering rather than the feature-requirement predicate (which has no sort direction): the gate decides pass/fail, rank_by decides better/worse. Soft preference, never a gate: leaves are not dropped by rank_by, they are only ordered. Omit to let the evaluator/seller choose the ordering.', + min_length=1, + ), + ] = None + feature_agent: Annotated[ + FeatureAgent | None, + Field( + description="Optional buyer-attached pointer to a get_creative_features-capable creative-feature / governance agent the producing agent calls to evaluate each leaf (the gate's SOURCE of feature values). This is the buyer-represents → seller-calls pattern #5280 established for provenance, generalized to the evaluator gate: the buyer REPRESENTS which agent it used, but the seller is the verifier-of-record and decides which agent it actually calls. `agent_url` MUST appear (canonicalized per /docs/reference/url-canonicalization) in the seller's `creative_policy.accepted_verifiers[].agent_url`; an off-list agent is rejected with `EVALUATOR_AGENT_NOT_ACCEPTED` (mirrors PROVENANCE_VERIFIER_NOT_ACCEPTED) before any outbound call. The outbound evaluator call authenticates on the transport; this pointer MUST NOT carry API keys, bearer tokens, client secrets, authorization values, JWKs, JWKS documents, or JWKS URIs. Reuses the same allowlist mechanism — no new allowlist is introduced. Distinct from the `agent_url` oneOf form, which names the evaluator's source directly; `feature_agent` attaches the gate's measurement source alongside any of the three forms." + ), + ] = None + eval_budget: Annotated[ + EvalBudget | None, + Field( + description='Optional soft ceiling on evaluation effort. Advisory in v1 with no billing coupling. Well-known soft fields max_calls / max_seconds; open for evaluator-specific knobs.' + ), + ] = None + ext: ext_1.ExtensionObject | None = None + evaluator_id: Annotated[ + str, + Field( + description='Account-scoped house evaluator preset selected by the buyer. This id is pre-provisioned/account-arranged, not discovered from get_adcp_capabilities governance.creative_features. That catalog only discovers the feature vocabulary the preset emits. An unknown id degrades to seller-default ranking (advisory errors[] note), not a failure.' + ), + ] + + +class EvaluatorSpec3(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + feature_requirement: Annotated[ + list[feature_requirement_1.FeatureRequirement] | None, + Field( + description='Optional hard GATE over creative-feature values — the predicates a leaf MUST satisfy for the producing agent to recommend/return it. Reuses the feature-requirement shape (min_value/max_value for quantitative features like creative_quality_score, allowed_values for binary/categorical) — the same predicate vocabulary that gates property/audience filters, which its own schema names as an intended creative-gate reuse. A leaf that fails any predicate is DROPPED from the agent\'s best_of_n survivors before ranking — this is internal pruning of which leaves the agent recommends, not an AdCP-layer block of an already-produced billable leaf (what is produced/billed is governed by max_variants/max_creatives/max_spend). Distinct from `rank_by`: the gate is a pass/fail predicate (drop on fail), `rank_by` is an ordering over survivors. Each predicate\'s `if_not_covered` (exclude|include, default exclude) is the fail-open knob when the source cannot measure that feature. A pass/warn/fail verdict is expressed as a categorical string feature value gated via `allowed_values` (e.g. ["pass"] or ["pass","warn"]) — the buyer\'s predicate decides whether warn passes; the verdict is derived, never stored on creative-feature-result. Omit to leave evaluation advisory (no leaf is dropped).', + min_length=1, + ), + ] = None + rank_by: Annotated[ + list[RankByItem2] | None, + Field( + description='Optional RANK ordering over creative-feature values — an ordered list (most significant first) the agent uses to order the gate survivors into recommended/rank. An explicit {feature_id, direction} ordering rather than the feature-requirement predicate (which has no sort direction): the gate decides pass/fail, rank_by decides better/worse. Soft preference, never a gate: leaves are not dropped by rank_by, they are only ordered. Omit to let the evaluator/seller choose the ordering.', + min_length=1, + ), + ] = None + feature_agent: Annotated[ + FeatureAgent | None, + Field( + description="Optional buyer-attached pointer to a get_creative_features-capable creative-feature / governance agent the producing agent calls to evaluate each leaf (the gate's SOURCE of feature values). This is the buyer-represents → seller-calls pattern #5280 established for provenance, generalized to the evaluator gate: the buyer REPRESENTS which agent it used, but the seller is the verifier-of-record and decides which agent it actually calls. `agent_url` MUST appear (canonicalized per /docs/reference/url-canonicalization) in the seller's `creative_policy.accepted_verifiers[].agent_url`; an off-list agent is rejected with `EVALUATOR_AGENT_NOT_ACCEPTED` (mirrors PROVENANCE_VERIFIER_NOT_ACCEPTED) before any outbound call. The outbound evaluator call authenticates on the transport; this pointer MUST NOT carry API keys, bearer tokens, client secrets, authorization values, JWKs, JWKS documents, or JWKS URIs. Reuses the same allowlist mechanism — no new allowlist is introduced. Distinct from the `agent_url` oneOf form, which names the evaluator's source directly; `feature_agent` attaches the gate's measurement source alongside any of the three forms." + ), + ] = None + eval_budget: Annotated[ + EvalBudget | None, + Field( + description='Optional soft ceiling on evaluation effort. Advisory in v1 with no billing coupling. Well-known soft fields max_calls / max_seconds; open for evaluator-specific knobs.' + ), + ] = None + ext: ext_1.ExtensionObject | None = None + agent_url: Annotated[ + AnyUrl, + Field( + description="URL of an external get_creative_features-capable judge agent the seller calls to score the produced leaves. MUST match an entry in the seller's `creative_policy.accepted_verifiers[].agent_url` (off-list → `EVALUATOR_AGENT_NOT_ACCEPTED`); an on-list agent that is unreachable or rejects the producing agent's transport authentication degrades to seller-default ranking (advisory errors[] note), not a failure. Authentication and trust material for this call belongs on the transport or in account provisioning, not in the evaluator payload." + ), + ] + + +class Exemplars(AdCPBaseModel): + pass_: Annotated[ + list[artifact.Artifact] | None, + Field( + alias='pass', + description='Artifacts exemplifying variants the buyer considers good — the high end (≈1) of the calibrated prediction feature.', + ), + ] = None + fail: Annotated[ + list[artifact.Artifact] | None, + Field( + description='Artifacts exemplifying variants the buyer considers bad — the low end (≈0) of the calibrated prediction feature.' + ), + ] = None + + +class EvaluatorSpec1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + feature_requirement: Annotated[ + list[feature_requirement_1.FeatureRequirement] | None, + Field( + description='Optional hard GATE over creative-feature values — the predicates a leaf MUST satisfy for the producing agent to recommend/return it. Reuses the feature-requirement shape (min_value/max_value for quantitative features like creative_quality_score, allowed_values for binary/categorical) — the same predicate vocabulary that gates property/audience filters, which its own schema names as an intended creative-gate reuse. A leaf that fails any predicate is DROPPED from the agent\'s best_of_n survivors before ranking — this is internal pruning of which leaves the agent recommends, not an AdCP-layer block of an already-produced billable leaf (what is produced/billed is governed by max_variants/max_creatives/max_spend). Distinct from `rank_by`: the gate is a pass/fail predicate (drop on fail), `rank_by` is an ordering over survivors. Each predicate\'s `if_not_covered` (exclude|include, default exclude) is the fail-open knob when the source cannot measure that feature. A pass/warn/fail verdict is expressed as a categorical string feature value gated via `allowed_values` (e.g. ["pass"] or ["pass","warn"]) — the buyer\'s predicate decides whether warn passes; the verdict is derived, never stored on creative-feature-result. Omit to leave evaluation advisory (no leaf is dropped).', + min_length=1, + ), + ] = None + rank_by: Annotated[ + list[RankByItem] | None, + Field( + description='Optional RANK ordering over creative-feature values — an ordered list (most significant first) the agent uses to order the gate survivors into recommended/rank. An explicit {feature_id, direction} ordering rather than the feature-requirement predicate (which has no sort direction): the gate decides pass/fail, rank_by decides better/worse. Soft preference, never a gate: leaves are not dropped by rank_by, they are only ordered. Omit to let the evaluator/seller choose the ordering.', + min_length=1, + ), + ] = None + feature_agent: Annotated[ + FeatureAgent | None, + Field( + description="Optional buyer-attached pointer to a get_creative_features-capable creative-feature / governance agent the producing agent calls to evaluate each leaf (the gate's SOURCE of feature values). This is the buyer-represents → seller-calls pattern #5280 established for provenance, generalized to the evaluator gate: the buyer REPRESENTS which agent it used, but the seller is the verifier-of-record and decides which agent it actually calls. `agent_url` MUST appear (canonicalized per /docs/reference/url-canonicalization) in the seller's `creative_policy.accepted_verifiers[].agent_url`; an off-list agent is rejected with `EVALUATOR_AGENT_NOT_ACCEPTED` (mirrors PROVENANCE_VERIFIER_NOT_ACCEPTED) before any outbound call. The outbound evaluator call authenticates on the transport; this pointer MUST NOT carry API keys, bearer tokens, client secrets, authorization values, JWKs, JWKS documents, or JWKS URIs. Reuses the same allowlist mechanism — no new allowlist is introduced. Distinct from the `agent_url` oneOf form, which names the evaluator's source directly; `feature_agent` attaches the gate's measurement source alongside any of the three forms." + ), + ] = None + eval_budget: Annotated[ + EvalBudget | None, + Field( + description='Optional soft ceiling on evaluation effort. Advisory in v1 with no billing coupling. Well-known soft fields max_calls / max_seconds; open for evaluator-specific knobs.' + ), + ] = None + ext: ext_1.ExtensionObject | None = None + exemplars: Annotated[ + Exemplars, + Field( + description='Pass/fail examples that calibrate the single prediction feature (e.g. `predicted_performance` in [0,1]) the evaluator returns in eval.features[]. Artifact-based, mirroring content-standards.json calibration_exemplars.' + ), + ] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.evaluator_spec import ( - Direction, - EvalBudget, - EvaluatorSpec, - EvaluatorSpec1, - EvaluatorSpec2, - EvaluatorSpec3, - Exemplars, - FeatureAgent, - RankByItem, - RankByItem1, - RankByItem2, -) - -# Explicit exports -__all__ = [ - "Direction", - "EvalBudget", - "EvaluatorSpec", - "EvaluatorSpec1", - "EvaluatorSpec2", - "EvaluatorSpec3", - "Exemplars", - "FeatureAgent", - "RankByItem", - "RankByItem1", - "RankByItem2", +EvaluatorSpec = Annotated[ + EvaluatorSpec1 | EvaluatorSpec2 | EvaluatorSpec3, + Field( + description="Advisory buyer-attached pointer (#5280) declaring HOW produced build_creative variants should be evaluated and ranked. The rank-side of the get_creative_features feature oracle: the chosen form is the SOURCE of feature evaluation that yields creative-feature values for each leaf, `feature_requirement` is an optional hard GATE over those values, and `rank_by` is the RANK ordering over the survivors. Informs the agent's best_of_n recommended/rank and, when the agent advertises creative.supports_evaluator, populates a per-leaf `eval` block (creative-feature-result[]) on the response.\n\nGate-then-rank pipeline (per produced leaf in a best_of_n run): the producing agent (1) evaluates the leaf via the chosen form, obtaining creative-feature values; (2) when `feature_requirement` is present, applies those predicates and DROPS any leaf that fails (internal best_of_n pruning) — survivors only; (3) orders survivors by `rank_by`. The gate operates BEFORE return: it prunes which leaves the agent recommends/returns from its own best_of_n exploration, never an AdCP-layer block of an already-produced billable leaf — what is produced and billed is governed by max_variants/max_creatives/max_spend, not by the evaluator. With no `feature_requirement`, evaluation stays advisory: every produced leaf is returned and billed and ranking is a soft preference, so `rank_by` failures rank lower but are never dropped.\n\nWhen the chosen form is `feature_agent` (or `agent_url`), the seller calls an external get_creative_features-capable agent, so the pointer is subject to the seller's `creative_policy.accepted_verifiers[]` allowlist exactly as #5280 established for provenance verify_agent: the buyer REPRESENTS which on-list agent it used, the seller (verifier-of-record) CALLS it. An off-list agent is rejected with `EVALUATOR_AGENT_NOT_ACCEPTED` (mirrors PROVENANCE_VERIFIER_NOT_ACCEPTED) before any outbound call; an on-list agent that is unreachable or unknown degrades to seller-default ranking with an advisory errors[] note (non-terminal), NOT a failure.\n\nEvaluator credentials and caller-supplied trust material are never carried in this object, `context`, `ext`, or the creative payload. The producing creative agent authenticates its outbound call to the evaluator on the transport (RFC 9421 request signing/JWKS, mTLS, or a pre-provisioned Bearer/API-key credential). Credential- or trust-material payload keys (for example `api_key`, `client_secret`, `authorization`, `jwk`, `jwks`, or `jwks_uri`) are non-conforming and should be rejected with `CREDENTIAL_IN_ARGS`.\n\nProvide exactly one of three forms; an optional `feature_requirement` gate, an optional `rank_by` ordering, an optional `feature_agent` allowlisted pointer, and a shared optional eval_budget apply to whichever form is chosen. Feature discovery uses the existing governance.creative_features catalog on get_adcp_capabilities; evaluator outputs reuse creative-feature-result[] and do not mint a parallel verdict/result surface. `evaluator_id` is not discovered from governance.creative_features: it is a pre-provisioned, account-arranged preset whose emitted feature_ids still come from that catalog.\n\nExperimental (x-status: experimental): this whole evaluator surface — the `evaluator` input, the per-leaf `eval` response block, and `creative.supports_evaluator` — is new and not yet field-tested across parties. Sellers that implement it MUST list `creative.evaluator` in `experimental_features`. Reserved 3.x follow-ons that may reshape these fields: a separate `supports_evaluator_gate` capability and a hard MUST-enforce-gate semantic. Per docs/reference/experimental-status, it MAY change between 3.x releases with notice.", + title='Evaluator Spec', + ), ] diff --git a/src/adcp/types/domains/core/event.py b/src/adcp/types/domains/core/event.py index 6f373b762..96c9afd97 100644 --- a/src/adcp/types/domains/core/event.py +++ b/src/adcp/types/domains/core/event.py @@ -1,18 +1,61 @@ -"""Types declared by the AdCP ``core/event`` schema. +# generated by datamodel-codegen: +# filename: core/event.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.event import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import action_source as action_source_1 +from ..enums import event_type as event_type_1 +from . import event_custom_data, event_surface +from . import ext as ext_1 +from . import user_match as user_match_1 -from adcp.types.generated_poc.core.event import Event -# Explicit exports -__all__ = ["Event"] +class Event(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + event_id: Annotated[ + str, + Field( + description='Unique identifier for deduplication (scoped to event_type + event_source_id)', + max_length=256, + min_length=1, + ), + ] + event_type: Annotated[event_type_1.EventType, Field(description='Standard event type')] + event_time: Annotated[ + AwareDatetime, Field(description='ISO 8601 timestamp when the event occurred') + ] + user_match: Annotated[ + user_match_1.UserMatch | None, + Field(description='User identifiers for attribution matching'), + ] = None + custom_data: Annotated[ + event_custom_data.EventCustomData | None, + Field(description='Event-specific data (value, currency, items, etc.)'), + ] = None + action_source: Annotated[ + action_source_1.ActionSource | None, Field(description='Where the event originated') + ] = None + surface: Annotated[ + event_surface.EventSurface | None, + Field( + description='Optional structured surface context for the event, such as an owned channel, profile, feed, podcast, newsletter list, website, app, or store. Complements `action_source`; use it when optimization-relevant meaning would otherwise live only in platform-specific `ext` metadata.' + ), + ] = None + event_source_url: Annotated[ + AnyUrl | None, + Field( + description="URL where the event occurred (required when action_source is 'website')" + ), + ] = None + custom_event_name: Annotated[ + str | None, Field(description="Name for custom events (used when event_type is 'custom')") + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/event_custom_data.py b/src/adcp/types/domains/core/event_custom_data.py index 1c0112a0a..222cf806f 100644 --- a/src/adcp/types/domains/core/event_custom_data.py +++ b/src/adcp/types/domains/core/event_custom_data.py @@ -1,18 +1,86 @@ -"""Types declared by the AdCP ``core/event_custom_data`` schema. +# generated by datamodel-codegen: +# filename: core/event_custom_data.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.event_custom_data import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import ext as ext_1 + + +class Content(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + id: Annotated[str, Field(description='Product or content identifier')] + quantity: Annotated[SchemaInt | None, Field(description='Quantity of this item', ge=1)] = None + price: Annotated[ + StrictFloat | None, Field(description='Price per unit of this item', ge=0.0) + ] = None + brand: Annotated[str | None, Field(description='Brand name of this item')] = None -from adcp.types.generated_poc.core.event_custom_data import Content, EventCustomData -# Explicit exports -__all__ = ["Content", "EventCustomData"] +class EventCustomData(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + value: Annotated[ + StrictFloat | None, + Field( + description='Monetary value of the event. For an event source that declares value_currencies, currency is required and MUST appear in that list. Legacy sources without the new contract remain schema-compatible but are ineligible for canonical ROAS.', + ge=0.0, + ), + ] = None + currency: Annotated[ + str | None, + Field( + description='ISO 4217 currency code. For a source declaring value_currencies, monetary records MUST carry a currency from that list.', + pattern='^[A-Z]{3}$', + ), + ] = None + order_id: Annotated[str | None, Field(description='Unique order or transaction identifier')] = ( + None + ) + content_ids: Annotated[ + list[str] | None, + Field( + description="Item identifiers for catalog attribution. Values are matched against catalog items using the identifier type declared by the catalog's content_id_type field (e.g., SKUs, GTINs, or vertical-specific IDs like job_id)." + ), + ] = None + content_type: Annotated[ + str | None, + Field( + description="Category of content associated with the event (e.g., 'product', 'job', 'hotel'). Corresponds to the catalog type when used for catalog attribution." + ), + ] = None + content_name: Annotated[str | None, Field(description='Name of the product or content')] = None + content_category: Annotated[ + str | None, Field(description='Category of the product or content') + ] = None + num_items: Annotated[ + SchemaInt | None, Field(description='Number of items in the event', ge=0) + ] = None + search_string: Annotated[str | None, Field(description='Search query for search events')] = None + progress_percent: Annotated[ + StrictFloat | None, + Field( + description='Content progress percentage reached, primarily for `watch_milestone` events. Use 25, 50, 75, or 100 for quartiles, or another publisher-defined threshold. Do not use `value` for non-monetary progress.', + ge=0.0, + le=100.0, + ), + ] = None + progress_seconds: Annotated[ + StrictFloat | None, + Field( + description='Content progress duration reached in seconds, primarily for `watch_milestone` events. Use when the milestone is time-based rather than percentage-based.', + ge=0.0, + ), + ] = None + contents: Annotated[ + list[Content] | None, Field(description='Per-item details for e-commerce events') + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/event_source_health.py b/src/adcp/types/domains/core/event_source_health.py index b8f170f4b..01c4f5c1f 100644 --- a/src/adcp/types/domains/core/event_source_health.py +++ b/src/adcp/types/domains/core/event_source_health.py @@ -1,18 +1,86 @@ -"""Types declared by the AdCP ``core/event_source_health`` schema. +# generated by datamodel-codegen: +# filename: core/event_source_health.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.event_source_health import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import assessment_status +from . import diagnostic_issue + + +class Detail(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + score: Annotated[ + StrictFloat, + Field( + description='Seller-defined quality score. Scale varies by seller — only compare within the same seller.', + ge=0.0, + ), + ] + max_score: Annotated[ + StrictFloat, Field(description="Maximum possible score on this seller's scale.", ge=1.0) + ] + label: Annotated[ + str | None, + Field( + description="Seller's name for this score (e.g., 'Event Quality Score', 'Event Match Quality')." + ), + ] = None -from adcp.types.generated_poc.core.event_source_health import Detail, EventSourceHealth -# Explicit exports -__all__ = ["Detail", "EventSourceHealth"] +class EventSourceHealth(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + status: Annotated[ + assessment_status.AssessmentStatus, + Field( + description="Overall health status. Use this for cross-seller decisions — do not rely on detail.score for comparability. 'insufficient' covers the source-offline case (zero events received over the seller's evaluation window) — disambiguate via `events_received_24h: 0` and a stale `last_event_at`. Sellers MUST surface a corresponding impairment (reason_code: source_offline) on any active media buy whose conversion goals depend on an event source that has gone offline." + ), + ] + detail: Annotated[ + Detail | None, + Field( + description='Seller-specific scoring detail. Only present when the seller has a native quality score to relay. Buyer agents should use status (not detail) for cross-seller decisions. Detail is supplementary context for human review or advanced diagnostics.' + ), + ] = None + match_rate: Annotated[ + StrictFloat | None, + Field( + description='Fraction of events from this source that the seller successfully matched to ad interactions (0.0-1.0). Low match rates indicate weak user_match identifiers. Absent when the seller does not compute match rates.', + ge=0.0, + le=1.0, + ), + ] = None + last_event_at: Annotated[ + AwareDatetime | None, + Field( + description='ISO 8601 timestamp of the most recent event received from this source. Absent when no events have been received.' + ), + ] = None + evaluated_at: Annotated[ + AwareDatetime | None, + Field( + description='ISO 8601 timestamp of when this health assessment was computed. When health is derived from reporting data, this may lag real-time. Buyer agents can use this to decide whether to trust stale assessments or re-request.' + ), + ] = None + events_received_24h: Annotated[ + SchemaInt | None, + Field( + description='Number of events received from this source in the last 24 hours. Zero indicates the source is configured but not firing.', + ge=0, + ), + ] = None + issues: Annotated[ + list[diagnostic_issue.DiagnosticIssue] | None, + Field( + description='Actionable issues detected with this event source. Sellers should limit to the top 3-5 most actionable items. Buyer agents should sort by severity rather than relying on array position.' + ), + ] = None diff --git a/src/adcp/types/domains/core/event_surface.py b/src/adcp/types/domains/core/event_surface.py index f6985ac6f..1255d8a3c 100644 --- a/src/adcp/types/domains/core/event_surface.py +++ b/src/adcp/types/domains/core/event_surface.py @@ -1,18 +1,63 @@ -"""Types declared by the AdCP ``core/event_surface`` schema. +# generated by datamodel-codegen: +# filename: core/event_surface.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.event_surface import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import ext as ext_1 + + +class Category(StrEnum): + owned_property = 'owned_property' + website = 'website' + app = 'app' + offline = 'offline' + phone_call = 'phone_call' + chat = 'chat' + email = 'email' + in_store = 'in_store' + system_generated = 'system_generated' + other = 'other' -from adcp.types.generated_poc.core.event_surface import Category, EventSurface -# Explicit exports -__all__ = ["Category", "EventSurface"] +class EventSurface(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + category: Annotated[ + Category, + Field( + description='Generic surface category. `owned_property` covers durable creator or brand-controlled properties hosted by a platform, such as channels, profiles, feeds, lists, podcasts, or playlists.' + ), + ] + property_type: Annotated[ + str | None, + Field( + description='Open vocabulary describing the kind of property, for example `channel`, `profile`, `feed`, `list`, `podcast`, `playlist`, or `newsletter`. Required by convention when `category` is `owned_property`; optional for other categories.', + max_length=128, + min_length=1, + ), + ] = None + namespace: Annotated[ + str | None, + Field( + description='Platform, publisher, or system namespace for the property, such as `video_platform`, `short_video_app`, `audio_service`, or a seller-defined namespace. This is intentionally not an enum.', + max_length=128, + min_length=1, + ), + ] = None + property_id: Annotated[ + str | None, + Field( + description='Optional identifier for the property within `namespace`.', + max_length=256, + min_length=1, + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/experimental_feature_id.py b/src/adcp/types/domains/core/experimental_feature_id.py index 9cb59c286..637e6157d 100644 --- a/src/adcp/types/domains/core/experimental_feature_id.py +++ b/src/adcp/types/domains/core/experimental_feature_id.py @@ -1,18 +1,20 @@ -"""Types declared by the AdCP ``core/experimental_feature_id`` schema. +# generated by datamodel-codegen: +# filename: core/experimental_feature_id.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.experimental_feature_id import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.experimental_feature_id import ExperimentalFeatureId +from adcp.types._scalar import ScalarStr + -# Explicit exports -__all__ = ["ExperimentalFeatureId"] +class ExperimentalFeatureId(ScalarStr): + __slots__ = () + _constraints = { + 'max_length': 128, + 'min_length': 1, + 'pattern': '^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$', + } + _json_schema_extra = { + 'description': 'Dot-separated lowercase identifier for an experimental AdCP surface, such as protocol.principal or media_buy.reporting_delivery.', + 'title': 'Experimental Feature ID', + } diff --git a/src/adcp/types/domains/core/ext.py b/src/adcp/types/domains/core/ext.py index c249406c8..f2cd14185 100644 --- a/src/adcp/types/domains/core/ext.py +++ b/src/adcp/types/domains/core/ext.py @@ -1,18 +1,14 @@ -"""Types declared by the AdCP ``core/ext`` schema. +# generated by datamodel-codegen: +# filename: core/ext.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.ext import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.ext import ExtensionObject +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict + -# Explicit exports -__all__ = ["ExtensionObject"] +class ExtensionObject(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) diff --git a/src/adcp/types/domains/core/feature_requirement.py b/src/adcp/types/domains/core/feature_requirement.py index cc06dd27b..101e00bf0 100644 --- a/src/adcp/types/domains/core/feature_requirement.py +++ b/src/adcp/types/domains/core/feature_requirement.py @@ -1,18 +1,52 @@ -"""Types declared by the AdCP ``core/feature_requirement`` schema. +# generated by datamodel-codegen: +# filename: core/feature_requirement.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.feature_requirement import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.feature_requirement import FeatureRequirement, IfNotCovered +class IfNotCovered(StrEnum): + exclude = 'exclude' + include = 'include' + -# Explicit exports -__all__ = ["FeatureRequirement", "IfNotCovered"] +class FeatureRequirement(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + feature_id: Annotated[ + str, Field(description='Feature to evaluate (discovered via get_adcp_capabilities)') + ] + min_value: Annotated[ + StrictFloat | None, + Field(description='Minimum numeric value required (for quantitative features)'), + ] = None + max_value: Annotated[ + StrictFloat | None, + Field(description='Maximum numeric value allowed (for quantitative features)'), + ] = None + allowed_values: Annotated[ + list[Any] | None, + Field( + description='Values that pass the requirement (for binary/categorical features)', + min_length=1, + ), + ] = None + if_not_covered: Annotated[ + IfNotCovered | None, + Field( + description="How to handle properties where this feature is not covered. 'exclude' (default): property is removed from the list. 'include': property passes this requirement (fail-open)." + ), + ] = IfNotCovered.exclude + policy_id: Annotated[ + str | None, + Field( + description='Optional attribution — when this requirement encodes a specific buyer-chosen threshold authorized by a policy, policy_id references the authorizing PolicyEntry. Producers populate when the mechanism exists because of a specific policy (e.g., max_value: 15 on audience_children_composition because of uk_hfss); do NOT populate when the requirement is a general filter unrelated to any policy. Governance findings echo this policy_id when emitting denials traced to the requirement. See /docs/governance/policy-attribution.' + ), + ] = None diff --git a/src/adcp/types/domains/core/flight_item.py b/src/adcp/types/domains/core/flight_item.py index fdc5fec93..eacf6d9c7 100644 --- a/src/adcp/types/domains/core/flight_item.py +++ b/src/adcp/types/domains/core/flight_item.py @@ -1,18 +1,78 @@ -"""Types declared by the AdCP ``core/flight_item`` schema. +# generated by datamodel-codegen: +# filename: core/flight_item.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.flight_item import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import ext as ext_1 +from . import offering_asset_group +from . import price as price_1 + + +class Origin(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + airport_code: Annotated[ + str, + Field(description="IATA airport code (e.g., 'AMS', 'JFK', 'LHR').", pattern='^[A-Z]{3}$'), + ] + city: Annotated[str | None, Field(description="City name (e.g., 'Amsterdam', 'New York').")] = ( + None + ) + + +class Destination(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + airport_code: Annotated[str, Field(description='IATA airport code.', pattern='^[A-Z]{3}$')] + city: Annotated[str | None, Field(description='City name.')] = None -from adcp.types.generated_poc.core.flight_item import Destination, FlightItem, Origin -# Explicit exports -__all__ = ["Destination", "FlightItem", "Origin"] +class FlightItem(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + flight_id: Annotated[ + str, Field(description='Unique identifier for this flight route or offer.') + ] + origin: Annotated[Origin, Field(description='Departure airport or city.')] + destination: Annotated[Destination, Field(description='Arrival airport or city.')] + airline: Annotated[str | None, Field(description='Airline name or IATA airline code.')] = None + price: Annotated[price_1.Price | None, Field(description='Ticket price or starting fare.')] = ( + None + ) + description: Annotated[ + str | None, Field(description='Route description or promotional text.') + ] = None + departure_time: Annotated[ + AwareDatetime | None, Field(description='Departure date and time (ISO 8601).') + ] = None + arrival_time: Annotated[ + AwareDatetime | None, Field(description='Arrival date and time (ISO 8601).') + ] = None + image_url: Annotated[ + AnyUrl | None, Field(description='Promotional image URL (typically a destination photo).') + ] = None + url: Annotated[AnyUrl | None, Field(description='Booking page URL for this route.')] = None + tags: Annotated[ + list[str] | None, + Field( + description="Tags for filtering (e.g., 'direct', 'red-eye', 'business-class').", + min_length=1, + ), + ] = None + assets: Annotated[ + list[offering_asset_group.OfferingAssetGroup] | None, + Field( + description="Typed creative asset pools for this flight. Uses the same OfferingAssetGroup structure as offering-type catalogs. Standard group IDs: 'images_landscape' (destination hero), 'images_vertical' (9:16 for Stories), 'images_square' (1:1). Enables formats to declare typed image requirements that map unambiguously to the right asset regardless of platform.", + min_length=1, + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/forecast_dimension_audience.py b/src/adcp/types/domains/core/forecast_dimension_audience.py index 552347720..d523f7699 100644 --- a/src/adcp/types/domains/core/forecast_dimension_audience.py +++ b/src/adcp/types/domains/core/forecast_dimension_audience.py @@ -1,18 +1,28 @@ -"""Types declared by the AdCP ``core/forecast_dimension_audience`` schema. +# generated by datamodel-codegen: +# filename: core/forecast_dimension_audience.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.forecast_dimension_audience import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import audience_source as audience_source_1 -from adcp.types.generated_poc.core.forecast_dimension_audience import AudienceForecastDimension -# Explicit exports -__all__ = ["AudienceForecastDimension"] +class AudienceForecastDimension(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Annotated[Literal['audience'], Field(description='Dimension family discriminator.')] = 'audience' + audience_id: Annotated[ + str, Field(description='Audience segment identifier for this forecast row.') + ] + audience_source: Annotated[ + audience_source_1.AudienceSource, Field(description='Origin of the audience segment.') + ] + audience_name: Annotated[ + str | None, Field(description='Human-readable audience segment name.') + ] = None diff --git a/src/adcp/types/domains/core/forecast_dimension_device_platform.py b/src/adcp/types/domains/core/forecast_dimension_device_platform.py index 8bf540f9c..6264c7110 100644 --- a/src/adcp/types/domains/core/forecast_dimension_device_platform.py +++ b/src/adcp/types/domains/core/forecast_dimension_device_platform.py @@ -1,20 +1,25 @@ -"""Types declared by the AdCP ``core/forecast_dimension_device_platform`` schema. +# generated by datamodel-codegen: +# filename: core/forecast_dimension_device_platform.json +# timestamp: 2026-06-01T00:32:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.forecast_dimension_device_platform import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import device_platform as device_platform_1 -from adcp.types.generated_poc.core.forecast_dimension_device_platform import ( - DevicePlatformForecastDimension, -) -# Explicit exports -__all__ = ["DevicePlatformForecastDimension"] +class DevicePlatformForecastDimension(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Annotated[ + Literal['device_platform'], Field(description='Dimension family discriminator.') + ] = 'device_platform' + device_platform: Annotated[ + device_platform_1.DevicePlatform, + Field(description='Operating system or platform for this forecast row.'), + ] diff --git a/src/adcp/types/domains/core/forecast_dimension_device_type.py b/src/adcp/types/domains/core/forecast_dimension_device_type.py index a76458829..709e3a5f5 100644 --- a/src/adcp/types/domains/core/forecast_dimension_device_type.py +++ b/src/adcp/types/domains/core/forecast_dimension_device_type.py @@ -1,18 +1,22 @@ -"""Types declared by the AdCP ``core/forecast_dimension_device_type`` schema. +# generated by datamodel-codegen: +# filename: core/forecast_dimension_device_type.json +# timestamp: 2026-06-01T00:32:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.forecast_dimension_device_type import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import device_type as device_type_1 -from adcp.types.generated_poc.core.forecast_dimension_device_type import DeviceTypeForecastDimension -# Explicit exports -__all__ = ["DeviceTypeForecastDimension"] +class DeviceTypeForecastDimension(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Annotated[Literal['device_type'], Field(description='Dimension family discriminator.')] = 'device_type' + device_type: Annotated[ + device_type_1.DeviceType, Field(description='Device form factor for this forecast row.') + ] diff --git a/src/adcp/types/domains/core/forecast_dimension_geo.py b/src/adcp/types/domains/core/forecast_dimension_geo.py index 6567032f4..c53d9c428 100644 --- a/src/adcp/types/domains/core/forecast_dimension_geo.py +++ b/src/adcp/types/domains/core/forecast_dimension_geo.py @@ -1,18 +1,48 @@ -"""Types declared by the AdCP ``core/forecast_dimension_geo`` schema. +# generated by datamodel-codegen: +# filename: core/forecast_dimension_geo.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.forecast_dimension_geo import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import geo_level as geo_level_1 -from adcp.types.generated_poc.core.forecast_dimension_geo import GeoForecastDimension -# Explicit exports -__all__ = ["GeoForecastDimension"] +class GeoForecastDimension(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Annotated[Literal['geo'], Field(description='Dimension family discriminator.')] = 'geo' + geo_level: Annotated[ + geo_level_1.GeographicTargetingLevel, + Field(description='Geographic level for this forecast point.'), + ] + system: Annotated[ + str | None, + Field( + description="Classification system for metro or postal_area levels. Required when geo_level is 'metro' or 'postal_area'. Metro rows use metro-system enum values such as 'nielsen_dma'; native postal rows use country-local postal-system enum values such as 'zip' with country 'US'; deprecated legacy postal rows may use legacy-postal-system enum values such as 'us_zip'. Omit for country and region rows." + ), + ] = None + country: Annotated[ + str | None, + Field( + description='ISO 3166-1 alpha-2 country code. Required for native postal_area rows and omitted for legacy postal rows, metro rows, country rows, and region rows.', + pattern='^[A-Z]{2}$', + ), + ] = None + geo_code: Annotated[ + str, + Field( + description="Geographic code within the level and system. Country: ISO 3166-1 alpha-2 ('US'). Region: ISO 3166-2 with country prefix ('US-CA'). Metro/postal: system-specific code ('501', '10001')." + ), + ] + geo_name: Annotated[ + str | None, + Field( + description="Human-readable geographic name (e.g., 'United States', 'California', 'New York DMA')." + ), + ] = None diff --git a/src/adcp/types/domains/core/forecast_dimension_placement.py b/src/adcp/types/domains/core/forecast_dimension_placement.py index e75021948..d0baa3c53 100644 --- a/src/adcp/types/domains/core/forecast_dimension_placement.py +++ b/src/adcp/types/domains/core/forecast_dimension_placement.py @@ -1,18 +1,31 @@ -"""Types declared by the AdCP ``core/forecast_dimension_placement`` schema. +# generated by datamodel-codegen: +# filename: core/forecast_dimension_placement.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.forecast_dimension_placement import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import placement_ref as placement_ref_1 -from adcp.types.generated_poc.core.forecast_dimension_placement import PlacementForecastDimension -# Explicit exports -__all__ = ["PlacementForecastDimension"] +class PlacementForecastDimension(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Annotated[Literal['placement'], Field(description='Dimension family discriminator.')] = 'placement' + placement_ref: Annotated[ + placement_ref_1.PlacementReference, + Field( + description="Structured placement reference for this forecast row. References an entry from the product's placements array." + ), + ] + placement_name: Annotated[ + str | None, + Field( + description='Human-readable placement name, useful when the buyer has not resolved the placement catalog.' + ), + ] = None diff --git a/src/adcp/types/domains/core/forecast_dimension_signal.py b/src/adcp/types/domains/core/forecast_dimension_signal.py index d267a63c4..8e25eb220 100644 --- a/src/adcp/types/domains/core/forecast_dimension_signal.py +++ b/src/adcp/types/domains/core/forecast_dimension_signal.py @@ -1,21 +1,72 @@ -"""Types declared by the AdCP ``core/forecast_dimension_signal`` schema. +# generated by datamodel-codegen: +# filename: core/forecast_dimension_signal.json +# timestamp: 2026-10-04T01:14:11+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.forecast_dimension_signal import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictBool, StrictFloat, model_validator + +from . import signal_ref as signal_ref_1 + + +class Presence(StrEnum): + present = 'present' + absent = 'absent' -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.forecast_dimension_signal import ( - Presence, - SignalForecastDimension, -) +class SignalForecastDimension(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Annotated[Literal['signal'], Field(description='Dimension family discriminator.')] = 'signal' + signal_ref: Annotated[ + signal_ref_1.SignalRef | None, + Field( + description='Canonical signal reference for this forecast row. Required when the row needs to disambiguate product-local, data-provider, or signal-source identity. Product-relative forecasts SHOULD use signal_ref.' + ), + ] = None + signal_id: Annotated[ + str | None, + Field( + description='Signal identifier shorthand for this forecast row. Use only when the enclosing context already identifies the signal unambiguously, such as a coverage_forecast nested directly under one get_signals signal item. Otherwise use signal_ref.', + pattern='^[a-zA-Z0-9_-]+$', + ), + ] = None + signal_value: Annotated[ + str | StrictFloat | StrictBool | None, + Field( + description="Signal value bucket represented by this point. Use null with presence 'absent' to represent inventory where the signal is not present. Omit when the row describes any present value rather than one specific value." + ), + ] = None + presence: Annotated[ + Presence, + Field( + description="Whether the signal is present for this point. Use 'absent' for the explicit not-present bucket." + ), + ] + signal_name: Annotated[ + str | None, + Field( + description='Human-readable signal name, useful when the buyer has not resolved the signal definition.' + ), + ] = None + signal_value_name: Annotated[ + str | None, Field(description='Human-readable label for the signal value bucket.') + ] = None -# Explicit exports -__all__ = ["Presence", "SignalForecastDimension"] + @model_validator(mode='after') + def _require_schema_required_group(self) -> SignalForecastDimension: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('signal_ref',), ('signal_id',),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'SignalForecastDimension requires at least one of these field groups: signal_ref | signal_id' + ) diff --git a/src/adcp/types/domains/core/forecast_dimension_time.py b/src/adcp/types/domains/core/forecast_dimension_time.py index b8099de21..ef4f3838b 100644 --- a/src/adcp/types/domains/core/forecast_dimension_time.py +++ b/src/adcp/types/domains/core/forecast_dimension_time.py @@ -1,18 +1,27 @@ -"""Types declared by the AdCP ``core/forecast_dimension_time`` schema. +# generated by datamodel-codegen: +# filename: core/forecast_dimension_time.json +# timestamp: 2026-08-21T04:35:18+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.forecast_dimension_time import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field -from adcp.types.generated_poc.core.forecast_dimension_time import TimeForecastDimension -# Explicit exports -__all__ = ["TimeForecastDimension"] +class TimeForecastDimension(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Annotated[Literal['time'], Field(description='Dimension family discriminator.')] = 'time' + start_time: Annotated[ + AwareDatetime, + Field(description='Inclusive window start (RFC 3339 date-time with timezone offset).'), + ] + end_time: Annotated[ + AwareDatetime, + Field( + description='Exclusive window end (RFC 3339 date-time with timezone offset). MUST be after start_time.' + ), + ] diff --git a/src/adcp/types/domains/core/forecast_point.py b/src/adcp/types/domains/core/forecast_point.py index d26fb87eb..33eaa000d 100644 --- a/src/adcp/types/domains/core/forecast_point.py +++ b/src/adcp/types/domains/core/forecast_point.py @@ -1,24 +1,160 @@ -"""Types declared by the AdCP ``core/forecast_point`` schema. +# generated by datamodel-codegen: +# filename: core/forecast_point.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.forecast_point import +from typing import Annotated, Dict -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import availability_status as availability_status_1 +from ..enums import viewability_standard +from . import brand_ref, forecast_point_dimensions, forecast_range, forecast_vendor_metric_value +from .forecast_range import ForecastRange + + +class CoverageRate(ForecastRange): + low: Annotated[ + StrictFloat | None, + Field(description='Conservative (low-end) forecast value', ge=0.0, le=1.0), + ] = None + mid: Annotated[ + StrictFloat | None, + Field(description='Expected (most likely) forecast value', ge=0.0, le=1.0), + ] = None + high: Annotated[ + StrictFloat | None, + Field(description='Optimistic (high-end) forecast value', ge=0.0, le=1.0), + ] = None + + +class Metrics(AdCPBaseModel): + __pydantic_extra__: Dict[str, forecast_range.ForecastRange] + model_config = ConfigDict( + extra='allow', + ) + audience_size: forecast_range.ForecastRange | None = None + reach: forecast_range.ForecastRange | None = None + frequency: forecast_range.ForecastRange | None = None + impressions: forecast_range.ForecastRange | None = None + clicks: forecast_range.ForecastRange | None = None + spend: forecast_range.ForecastRange | None = None + views: forecast_range.ForecastRange | None = None + completed_views: forecast_range.ForecastRange | None = None + grps: forecast_range.ForecastRange | None = None + engagements: forecast_range.ForecastRange | None = None + follows: forecast_range.ForecastRange | None = None + saves: forecast_range.ForecastRange | None = None + profile_visits: forecast_range.ForecastRange | None = None + measured_impressions: forecast_range.ForecastRange | None = None + downloads: forecast_range.ForecastRange | None = None + plays: forecast_range.ForecastRange | None = None + coverage_rate: Annotated[ + CoverageRate | None, + Field( + description="Share of the declared forecast scope represented by this point. For signal coverage forecasts, this is the point's count divided by the coverage_forecast.scope denominator. Range 0.0 to 1.0." + ), + ] = None + + + + +class ViewableRate(CoverageRate): + pass + + +class Viewability(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + vendor: Annotated[ + brand_ref.BrandReference | None, + Field(description='Vendor expected to produce or verify these viewability values.'), + ] = None + measurable_impressions: Annotated[ + forecast_range.ForecastRange | None, + Field( + description='Forecasted impressions where viewability can be measured. Coverage denominator for viewable_rate and viewed_seconds.' + ), + ] = None + viewable_impressions: Annotated[ + forecast_range.ForecastRange | None, + Field( + description='Forecasted impressions expected to meet the viewability threshold defined by standard.' + ), + ] = None + viewable_rate: Annotated[ + ViewableRate | None, + Field( + description='Forecasted viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.' + ), + ] = None + viewed_seconds: Annotated[ + forecast_range.ForecastRange | None, + Field( + description='Forecasted average in-view duration per measurable impression, in seconds.' + ), + ] = None + standard: Annotated[ + viewability_standard.ViewabilityStandard | None, + Field(description='Viewability measurement standard applied to these forecasted values.'), + ] = None -from adcp.types.generated_poc.core.forecast_point import ( - CoverageRate, - ForecastPoint, - Metrics, - Viewability, - ViewableRate, -) -# Explicit exports -__all__ = ["CoverageRate", "ForecastPoint", "Metrics", "Viewability", "ViewableRate"] +class ForecastPoint(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + label: Annotated[ + str | None, + Field( + description="Human-readable name for this forecast point. Required when forecast_range_unit is 'package' so buyer agents can identify and reference individual packages. Optional for other forecast types.", + examples=['Primetime', 'Morning Drive', 'Large Format Transit'], + max_length=128, + ), + ] = None + budget: Annotated[ + StrictFloat | None, + Field( + description='Budget amount for this forecast point. Required for spend curves; omit for availability forecasts where the metrics represent total available inventory. For allocation-level forecasts, this is the absolute budget for that allocation (not the percentage). For proposal-level forecasts, this is the total proposal budget. When omitted, use metrics.spend to express the estimated cost of the available inventory.', + ge=0.0, + ), + ] = None + product_id: Annotated[ + str | None, + Field( + description='Optional product context for this forecast row. Usually omitted on product-level and allocation-level forecasts where the product is already implied. On proposal-level forecasts, populate when a dimensional row, especially a placement row, maps to a specific product allocation so buyers can turn the row into an executable package choice. Omit for true aggregate proposal rows spanning multiple products.' + ), + ] = None + dimensions: Annotated[ + forecast_point_dimensions.ForecastPointDimensions | None, + Field( + description='Dimension constraints represented by this forecast point, such as country, region, placement, device type, platform, audience, signal value, time window, or intersections such as placement x country or product x signal. Each item declares one dimension family; when multiple items are present, the point represents their intersection. Sellers MUST NOT emit more than one item for each `kind` on a point; consumers MUST NOT treat repeated kinds as OR semantics. Use multiple points with dimensions to expose country/placement/signal availability within one product, proposal, or signal coverage forecast without creating separate products solely for each dimension. Dimensions describe the forecast row and are independent of pricing_options.' + ), + ] = None + availability_status: Annotated[ + availability_status_1.AvailabilityStatus | None, + Field( + description="Bookability of the inventory this row describes, as of the forecast's generated_at. Most meaningful on rows with a time dimension in an availability forecast (forecast_range_unit 'availability'). This is a snapshot, not a hold: valid_until bounds freshness, and proposal finalization or purchase remains the commitment boundary. When omitted, the row makes no bookability claim. 'unavailable' rows may carry empty metrics." + ), + ] = None + metrics: Annotated[ + Metrics, + Field( + description='Forecasted metric values. Keys are forecastable-metric enum values for delivery/engagement or event-type enum values for outcomes. Values are ForecastRange objects (low/mid/high). Use { "mid": value } for point estimates. When budget is present, these are the expected metrics at that spend level. When budget is omitted, these represent total available inventory — use spend to express the estimated cost. Additional keys beyond the documented properties are allowed for event-type values (purchase, lead, app_install, etc.).' + ), + ] + viewability: Annotated[ + Viewability | None, + Field( + description='Forecasted viewability metrics. Mirrors delivery-metrics.viewability, but numeric values are ForecastRange objects because forecast rows may provide low/mid/high bounds. Use this for pre-buy viewability expectations by forecast point without folding measurement metrics into pricing_options.' + ), + ] = None + vendor_metric_values: Annotated[ + list[forecast_vendor_metric_value.ForecastVendorMetricValue] | None, + Field( + description="Forecasted values for vendor-defined metrics that the product's reporting_capabilities.vendor_metrics declared. Mirrors delivery-metrics.vendor_metric_values, but value and measurable_impressions use ForecastRange. These forecasted measurement values are independent of pricing_options." + ), + ] = None diff --git a/src/adcp/types/domains/core/forecast_point_dimensions.py b/src/adcp/types/domains/core/forecast_point_dimensions.py index 0597aff9f..e760b7340 100644 --- a/src/adcp/types/domains/core/forecast_point_dimensions.py +++ b/src/adcp/types/domains/core/forecast_point_dimensions.py @@ -1,18 +1,55 @@ -"""Types declared by the AdCP ``core/forecast_point_dimensions`` schema. +# generated by datamodel-codegen: +# filename: core/forecast_point_dimensions.json +# timestamp: 2026-08-21T04:35:18+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.forecast_point_dimensions import +from typing import Any, Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import Field, RootModel -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import ( + forecast_dimension_audience, + forecast_dimension_device_platform, + forecast_dimension_device_type, + forecast_dimension_geo, + forecast_dimension_placement, + forecast_dimension_signal, + forecast_dimension_time, +) -from adcp.types.generated_poc.core.forecast_point_dimensions import ForecastPointDimensions -# Explicit exports -__all__ = ["ForecastPointDimensions"] +class ForecastPointDimensions( + RootModel[ + list[ + forecast_dimension_geo.GeoForecastDimension + | forecast_dimension_placement.PlacementForecastDimension + | forecast_dimension_device_type.DeviceTypeForecastDimension + | forecast_dimension_device_platform.DevicePlatformForecastDimension + | forecast_dimension_audience.AudienceForecastDimension + | forecast_dimension_signal.SignalForecastDimension + | forecast_dimension_time.TimeForecastDimension + ] + ] +): + root: Annotated[ + list[ + forecast_dimension_geo.GeoForecastDimension + | forecast_dimension_placement.PlacementForecastDimension + | forecast_dimension_device_type.DeviceTypeForecastDimension + | forecast_dimension_device_platform.DevicePlatformForecastDimension + | forecast_dimension_audience.AudienceForecastDimension + | forecast_dimension_signal.SignalForecastDimension + | forecast_dimension_time.TimeForecastDimension + ], + Field( + description='Dimension constraints represented by a ForecastPoint. Use this when one product, proposal, or signal coverage forecast needs to expose availability or forecasted delivery by country, region, placement, device, audience, signal value, time window, or intersections such as placement x country without creating separate products solely for each slice. Each item declares one dimension family via `kind`; when multiple items are present, the point represents their intersection. Sellers MUST NOT emit more than one item for each `kind` in a point. Consumers MUST NOT treat repeated kinds as OR semantics; repeated peer values such as two countries are a seller conformance issue. Dimension values are descriptors of the forecast row and are independent of pricing_options.', + min_length=1, + title='Forecast Point Dimensions', + ), + ] + def __getattr__(self, name: str) -> Any: + """Proxy attribute access to the wrapped type.""" + if name.startswith('_'): + raise AttributeError(name) + return getattr(self.root, name) diff --git a/src/adcp/types/domains/core/forecast_range.py b/src/adcp/types/domains/core/forecast_range.py index eb0bba6cb..0873f3be7 100644 --- a/src/adcp/types/domains/core/forecast_range.py +++ b/src/adcp/types/domains/core/forecast_range.py @@ -1,18 +1,38 @@ -"""Types declared by the AdCP ``core/forecast_range`` schema. +# generated by datamodel-codegen: +# filename: core/forecast_range.json +# timestamp: 2026-10-04T01:14:11+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.forecast_range import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictFloat, model_validator -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.forecast_range import ForecastRange +class ForecastRange(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + low: Annotated[ + StrictFloat | None, Field(description='Conservative (low-end) forecast value', ge=0.0) + ] = None + mid: Annotated[ + StrictFloat | None, Field(description='Expected (most likely) forecast value', ge=0.0) + ] = None + high: Annotated[ + StrictFloat | None, Field(description='Optimistic (high-end) forecast value', ge=0.0) + ] = None -# Explicit exports -__all__ = ["ForecastRange"] + @model_validator(mode='after') + def _require_schema_required_group(self) -> ForecastRange: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('mid',), ('low', 'high'),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'ForecastRange requires at least one of these field groups: mid | low+high' + ) diff --git a/src/adcp/types/domains/core/forecast_rate_range.py b/src/adcp/types/domains/core/forecast_rate_range.py index b7c4eeaba..b91ad0e10 100644 --- a/src/adcp/types/domains/core/forecast_rate_range.py +++ b/src/adcp/types/domains/core/forecast_rate_range.py @@ -1,18 +1,26 @@ -"""Types declared by the AdCP ``core/forecast_rate_range`` schema. +# generated by datamodel-codegen: +# filename: core/forecast_rate_range.json +# timestamp: 2026-10-03T15:09:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.forecast_rate_range import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from .forecast_range import ForecastRange -from adcp.types.generated_poc.core.forecast_rate_range import ForecastRateRange -# Explicit exports -__all__ = ["ForecastRateRange"] +class ForecastRateRange(ForecastRange): + low: Annotated[ + StrictFloat | None, + Field(description='Conservative (low-end) forecast value', ge=0.0, le=1.0), + ] = None + mid: Annotated[ + StrictFloat | None, + Field(description='Expected (most likely) forecast value', ge=0.0, le=1.0), + ] = None + high: Annotated[ + StrictFloat | None, + Field(description='Optimistic (high-end) forecast value', ge=0.0, le=1.0), + ] = None diff --git a/src/adcp/types/domains/core/forecast_vendor_metric_value.py b/src/adcp/types/domains/core/forecast_vendor_metric_value.py index be2ff4226..9ce6f425b 100644 --- a/src/adcp/types/domains/core/forecast_vendor_metric_value.py +++ b/src/adcp/types/domains/core/forecast_vendor_metric_value.py @@ -1,18 +1,67 @@ -"""Types declared by the AdCP ``core/forecast_vendor_metric_value`` schema. +# generated by datamodel-codegen: +# filename: core/forecast_vendor_metric_value.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.forecast_vendor_metric_value import +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import brand_ref, forecast_range, vendor_metric_id -from adcp.types.generated_poc.core.forecast_vendor_metric_value import ForecastVendorMetricValue -# Explicit exports -__all__ = ["ForecastVendorMetricValue"] +class ForecastVendorMetricValue(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + vendor: Annotated[ + brand_ref.BrandReference, + Field( + description='Vendor that defines and forecasts this metric. Matches a reporting_capabilities.vendor_metrics declaration on the product.' + ), + ] + metric_id: Annotated[ + vendor_metric_id.VendorMetricId, + Field( + description="Identifier for the metric within the vendor's vocabulary. Matches a vendor_metrics[].metric_id declaration on the product." + ), + ] + value: Annotated[ + forecast_range.ForecastRange, + Field( + description="Forecasted vendor metric value. Unit semantics are vendor-defined; see unit and the vendor's measurement-agent metric definition." + ), + ] + unit: Annotated[ + str | None, + Field( + description="Unit of the value. Free-form to accommodate heterogeneous vendor metrics (e.g., 'score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'). When populated inline, SHOULD match the vendor's published unit.", + examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], + ), + ] = None + measurable_impressions: Annotated[ + forecast_range.ForecastRange | None, + Field( + description='Forecasted number of impressions the vendor expects to be able to measure. Coverage denominator for the vendor metric; buyers compute estimated coverage as measurable_impressions / impressions when both are present. For play-based channels (DOOH, cinema, place-based) forecast measurable_plays or measurable_play_seconds instead.' + ), + ] = None + measurable_plays: Annotated[ + forecast_range.ForecastRange | None, + Field( + description='Forecasted number of plays the vendor expects to be able to measure. Coverage denominator for channels where a play, not an impression, is the atomic observation unit; buyers compute estimated coverage as measurable_plays / plays when both are present.' + ), + ] = None + measurable_play_seconds: Annotated[ + forecast_range.ForecastRange | None, + Field( + description='Forecasted play-seconds (creative playout seconds summed across endpoints — screens, speakers, players) the vendor expects to be able to measure. Coverage denominator when the vendor meters exposure duration rather than discrete plays.' + ), + ] = None + breakdown: Annotated[ + dict[str, Any] | None, + Field( + description="Optional structured payload for vendor metrics that do not fit a single scalar. Forecast rows SHOULD use ForecastRange values inside breakdown when sub-values are numeric forecasts. Buyers MUST treat this object as opaque without consulting the vendor's documentation." + ), + ] = None diff --git a/src/adcp/types/domains/core/format.py b/src/adcp/types/domains/core/format.py index 978583ff7..b2132f820 100644 --- a/src/adcp/types/domains/core/format.py +++ b/src/adcp/types/domains/core/format.py @@ -1,206 +1,2409 @@ -"""Types declared by the AdCP ``core/format`` schema. +# generated by datamodel-codegen: +# filename: core/format.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.format import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any, Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AnyUrl, ConfigDict, Field, RootModel, StrictBool, StrictFloat -from adcp.types.generated_poc.core.format import ( - Accessibility, - Assets, - Assets10, - Assets11, - Assets12, - Assets13, - Assets14, - Assets15, - Assets16, - Assets17, - Assets18, - Assets19, - Assets20, - Assets21, - Assets22, - Assets23, - Assets24, - Assets25, - Assets26, - Assets27, - Assets28, - Assets29, - Assets30, - Assets31, - Assets32, - Assets33, - Assets34, - Assets35, - Assets36, - Assets37, - Assets38, - Assets39, - Assets40, - Assets41, - Assets42, - Assets43, - Assets44, - Assets45, - Assets46, - Assets47, - Assets48, - Assets49, - Assets50, - Assets51, - Assets9, - Assets94, - BaseGroupAsset, - BaseIndividualAsset, - CanonicalParameters, - CanonicalParameters1, - CanonicalParameters10, - CanonicalParameters11, - CanonicalParameters12, - CanonicalParameters13, - CanonicalParameters14, - CanonicalParameters15, - CanonicalParameters16, - CanonicalParameters17, - CanonicalParameters18, - CanonicalParameters19, - CanonicalParameters2, - CanonicalParameters20, - CanonicalParameters21, - CanonicalParameters22, - CanonicalParameters23, - CanonicalParameters24, - CanonicalParameters25, - CanonicalParameters26, - CanonicalParameters27, - CanonicalParameters28, - CanonicalParameters29, - CanonicalParameters3, - CanonicalParameters30, - CanonicalParameters31, - CanonicalParameters32, - CanonicalParameters33, - CanonicalParameters4, - CanonicalParameters5, - CanonicalParameters6, - CanonicalParameters7, - CanonicalParameters8, - CanonicalParameters9, - Dimensions, - Dimensions1, - DisclosureCapability, - Format, - FormatCard, - FormatCardDetailed, - Renders, - Renders1, - Responsive, - SelectionMode, - SellerPreference, +from ..enums import ( + available_metric, + channels, + dimension_unit, + disclosure_persistence, + disclosure_position, + format_id_parameter, + universal_macro, ) +from ..enums import wcag_level as wcag_level_1 +from ..formats.canonical import ( + agent_placement, + audio_daast, + audio_hosted, + audio_vast, + coordinated_placements, + display_tag, + html5, + image, + image_carousel, + native_in_feed, + responsive_creative, + seller_rendered_stateful_display, + sponsored_placement, + video_hosted, + video_vast, +) +from . import canonical_projection_ref, creative_locale_policy +from . import format_id as format_id_1 +from . import macro_resolution_capability, overlay, platform_extension_ref +from . import tracker_execution_contract as tracker_execution_contract_1 +from . import vendor_pricing_option +from .requirements import ( + audio_asset_requirements, + catalog_requirements, + css_asset_requirements, + daast_asset_requirements, + html_asset_requirements, + image_asset_requirements, + javascript_asset_requirements, + markdown_asset_requirements, + text_asset_requirements, + url_asset_requirements, + vast_asset_requirements, + video_asset_requirements, + webhook_asset_requirements, +) + + +class Responsive(AdCPBaseModel): + width: StrictBool + height: StrictBool + + +class Dimensions(AdCPBaseModel): + width: Annotated[ + StrictFloat | None, + Field(description='Fixed width. Interpretation depends on unit (default: pixels).', gt=0.0), + ] = None + height: Annotated[ + StrictFloat | None, + Field( + description='Fixed height. Interpretation depends on unit (default: pixels).', gt=0.0 + ), + ] = None + min_width: Annotated[ + StrictFloat | None, Field(description='Minimum width for responsive renders', gt=0.0) + ] = None + min_height: Annotated[ + StrictFloat | None, Field(description='Minimum height for responsive renders', gt=0.0) + ] = None + max_width: Annotated[ + StrictFloat | None, Field(description='Maximum width for responsive renders', gt=0.0) + ] = None + max_height: Annotated[ + StrictFloat | None, Field(description='Maximum height for responsive renders', gt=0.0) + ] = None + unit: Annotated[ + dimension_unit.DimensionUnit | None, + Field( + description="Unit of measurement for width/height values. Defaults to 'px' when absent. Print formats use 'inches' or 'cm'." + ), + ] = None + responsive: Annotated[ + Responsive | None, Field(description='Indicates which dimensions are responsive/fluid') + ] = None + aspect_ratio: Annotated[ + str | None, + Field( + description="Fixed aspect ratio constraint (e.g., '16:9', '4:3', '1:1', '1.91:1')", + pattern='^\\d+(\\.\\d+)?:\\d+(\\.\\d+)?$', + ), + ] = None + + +class Renders(AdCPBaseModel): + role: Annotated[ + str, + Field( + description="Semantic role of this rendered piece (e.g., 'primary', 'companion', 'mobile_variant')" + ), + ] + parameters_from_format_id: Annotated[ + StrictBool | None, + Field( + description='When true, parameters for this render (dimensions and/or duration) are specified in the format_id. Used for template formats that accept parameters. Mutually exclusive with specifying dimensions object explicitly.' + ), + ] = None + dimensions: Annotated[ + Dimensions, + Field( + description='Dimensions for this rendered piece. Defaults to pixels when unit is absent.' + ), + ] + + +class Dimensions1(Dimensions): + pass + + +class Renders1(AdCPBaseModel): + role: Annotated[ + str, + Field( + description="Semantic role of this rendered piece (e.g., 'primary', 'companion', 'mobile_variant')" + ), + ] + parameters_from_format_id: Annotated[ + Literal[True], + Field( + description='When true, parameters for this render (dimensions and/or duration) are specified in the format_id. Used for template formats that accept parameters. Mutually exclusive with specifying dimensions object explicitly.' + ), + ] + dimensions: Annotated[ + Dimensions1 | None, + Field( + description='Dimensions for this rendered piece. Defaults to pixels when unit is absent.' + ), + ] = None + + +class SelectionMode(StrEnum): + sequential = 'sequential' + optimize = 'optimize' + + +class DisclosureCapability(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + position: Annotated[ + disclosure_position.DisclosurePosition, Field(description='The disclosure position') + ] + persistence: Annotated[ + list[disclosure_persistence.DisclosurePersistence], + Field(description='Persistence modes this position supports', min_length=1), + ] + + +class SellerPreference(StrEnum): + preferred = 'preferred' + accepted = 'accepted' + discouraged = 'discouraged' + + +class CanonicalParameters17(AdCPBaseModel): + pass + + +class FormatCard(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + format_id: Annotated[ + format_id_1.FormatReferenceStructuredObject, + Field( + description='Creative format defining the card layout (typically format_card_standard)' + ), + ] + manifest: Annotated[ + dict[str, Any], + Field(description='Asset manifest for rendering the card, structure defined by the format'), + ] + + +class Accessibility(AdCPBaseModel): + wcag_level: Annotated[ + wcag_level_1.WcagLevel, + Field( + description='WCAG conformance level that this format achieves. For format-rendered creatives, the format guarantees this level. For opaque creatives, the format requires assets that self-certify to this level.' + ), + ] + requires_accessible_assets: Annotated[ + StrictBool | None, + Field( + description='When true, all assets with x-accessibility fields must include those fields. For inspectable assets (image, video, audio), this means providing accessibility metadata like alt_text or captions. For opaque assets (HTML, JavaScript), this means providing self-declared accessibility properties.' + ), + ] = None + + +class FormatCardDetailed(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + format_id: Annotated[ + format_id_1.FormatReferenceStructuredObject, + Field( + description='Creative format defining the detailed card layout (typically format_card_detailed)' + ), + ] + manifest: Annotated[ + dict[str, Any], + Field( + description='Asset manifest for rendering the detailed card, structure defined by the format' + ), + ] + + +class BaseIndividualAsset(AdCPBaseModel): + item_type: Annotated[ + Literal['individual'], + Field(description='Discriminator indicating this is an individual asset'), + ] = 'individual' + asset_id: Annotated[ + str, + Field( + description='Unique identifier for this asset. Creative manifests MUST use this exact value as the key in the assets object.' + ), + ] + asset_role: Annotated[ + str | None, + Field( + description="Descriptive label for this asset's purpose (e.g., 'hero_image', 'logo', 'third_party_tracking'). For documentation and UI display only — manifests key assets by asset_id, not asset_role." + ), + ] = None + required: Annotated[ + StrictBool, + Field( + description='Whether this asset is required (true) or optional (false). Required assets must be provided for a valid creative. Optional assets enhance the creative but are not mandatory.' + ), + ] + overlays: Annotated[ + list[overlay.Overlay] | None, + Field( + description="Publisher-controlled elements rendered on top of buyer content at this asset's position (e.g., video player controls, publisher logos). Creative agents should avoid placing critical content (CTAs, logos, key copy) within overlay bounds." + ), + ] = None + asset_group_id: Annotated[ + str | None, + Field( + description="Optional canonical asset_group_id this slot fills, drawn from /schemas/core/asset-group-vocabulary.json. Lets buyers and migration tools resolve v1 author-invented slot names (e.g., `click_url`) to canonical names (e.g., `landing_page_url`). Validators MAY soft-warn when a v1 slot's asset_id is a known alias but no asset_group_id is declared." + ), + ] = None + + +class BaseGroupAsset(AdCPBaseModel): + asset_id: Annotated[str, Field(description='Identifier for this asset within the group')] + asset_role: Annotated[ + str | None, + Field( + description="Descriptive label for this asset's purpose. For documentation and UI display only — manifests key assets by asset_id, not asset_role." + ), + ] = None + asset_group_id: Annotated[ + str | None, + Field( + description='Optional canonical asset_group_id this slot fills, drawn from /schemas/core/asset-group-vocabulary.json. Same semantics as on baseIndividualAsset — lets buyers and migration tools resolve v1 author-invented slot names to canonical names.' + ), + ] = None + required: Annotated[ + StrictBool, + Field(description='Whether this asset is required within each repetition of the group'), + ] + overlays: Annotated[ + list[overlay.Overlay] | None, + Field( + description="Publisher-controlled elements rendered on top of buyer content at this asset's position (e.g., carousel navigation arrows, slide indicators). Creative agents should avoid placing critical content within overlay bounds." + ), + ] = None + + +class Assets(BaseIndividualAsset): + item_type: Literal['individual'] = 'individual' + asset_type: Literal['image'] = 'image' + requirements: image_asset_requirements.ImageAssetRequirements | None = None + + +class Assets9(BaseIndividualAsset): + item_type: Literal['individual'] = 'individual' + asset_type: Literal['video'] = 'video' + requirements: video_asset_requirements.VideoAssetRequirements | None = None + + +class Assets10(BaseIndividualAsset): + item_type: Literal['individual'] = 'individual' + asset_type: Literal['audio'] = 'audio' + requirements: audio_asset_requirements.AudioAssetRequirements | None = None + + +class Assets11(BaseIndividualAsset): + item_type: Literal['individual'] = 'individual' + asset_type: Literal['text'] = 'text' + requirements: text_asset_requirements.TextAssetRequirements | None = None + + +class Assets12(BaseIndividualAsset): + item_type: Literal['individual'] = 'individual' + asset_type: Literal['markdown'] = 'markdown' + requirements: markdown_asset_requirements.MarkdownAssetRequirements | None = None + + +class Assets13(BaseIndividualAsset): + item_type: Literal['individual'] = 'individual' + asset_type: Literal['html'] = 'html' + requirements: html_asset_requirements.HtmlAssetRequirements | None = None + + +class Assets14(BaseIndividualAsset): + item_type: Literal['individual'] = 'individual' + asset_type: Literal['css'] = 'css' + requirements: css_asset_requirements.CssAssetRequirements | None = None + + +class Assets15(BaseIndividualAsset): + item_type: Literal['individual'] = 'individual' + asset_type: Literal['javascript'] = 'javascript' + requirements: javascript_asset_requirements.JavascriptAssetRequirements | None = None + + +class Assets16(BaseIndividualAsset): + item_type: Literal['individual'] = 'individual' + asset_type: Literal['zip'] = 'zip' + + +class Assets17(BaseIndividualAsset): + item_type: Literal['individual'] = 'individual' + asset_type: Literal['vast'] = 'vast' + requirements: vast_asset_requirements.VastAssetRequirements | None = None + + +class Assets18(BaseIndividualAsset): + item_type: Literal['individual'] = 'individual' + asset_type: Literal['daast'] = 'daast' + requirements: daast_asset_requirements.DaastAssetRequirements | None = None + + +class Assets19(BaseIndividualAsset): + item_type: Literal['individual'] = 'individual' + asset_type: Literal['url'] = 'url' + requirements: url_asset_requirements.UrlAssetRequirements | None = None + + +class Assets20(BaseIndividualAsset): + item_type: Literal['individual'] = 'individual' + asset_type: Literal['webhook'] = 'webhook' + requirements: webhook_asset_requirements.WebhookAssetRequirements | None = None + + +class Assets21(BaseIndividualAsset): + item_type: Literal['individual'] = 'individual' + asset_type: Literal['brief'] = 'brief' + + +class Assets23(BaseIndividualAsset): + item_type: Literal['individual'] = 'individual' + asset_type: Literal['display_tag'] = 'display_tag' + + +class Assets24(BaseIndividualAsset): + item_type: Literal['individual'] = 'individual' + asset_type: Literal['published_post'] = 'published_post' + + +class Assets25(BaseIndividualAsset): + item_type: Literal['individual'] = 'individual' + asset_type: Literal['card'] = 'card' + + +class Assets26(BaseIndividualAsset): + item_type: Literal['individual'] = 'individual' + asset_type: Literal['pixel_tracker'] = 'pixel_tracker' + + +class Assets27(BaseIndividualAsset): + item_type: Literal['individual'] = 'individual' + asset_type: Literal['vast_tracker'] = 'vast_tracker' + + +class Assets28(BaseIndividualAsset): + item_type: Literal['individual'] = 'individual' + asset_type: Literal['daast_tracker'] = 'daast_tracker' + + +class Assets31(BaseGroupAsset): + asset_type: Literal['image'] = 'image' + requirements: image_asset_requirements.ImageAssetRequirements | None = None + + +class Assets32(BaseGroupAsset): + asset_type: Literal['video'] = 'video' + requirements: video_asset_requirements.VideoAssetRequirements | None = None + + +class Assets33(BaseGroupAsset): + asset_type: Literal['audio'] = 'audio' + requirements: audio_asset_requirements.AudioAssetRequirements | None = None + + +class Assets34(BaseGroupAsset): + asset_type: Literal['text'] = 'text' + requirements: text_asset_requirements.TextAssetRequirements | None = None + + +class Assets35(BaseGroupAsset): + asset_type: Literal['markdown'] = 'markdown' + requirements: markdown_asset_requirements.MarkdownAssetRequirements | None = None + + +class Assets36(BaseGroupAsset): + asset_type: Literal['html'] = 'html' + requirements: html_asset_requirements.HtmlAssetRequirements | None = None + + +class Assets37(BaseGroupAsset): + asset_type: Literal['css'] = 'css' + requirements: css_asset_requirements.CssAssetRequirements | None = None + + +class Assets38(BaseGroupAsset): + asset_type: Literal['javascript'] = 'javascript' + requirements: javascript_asset_requirements.JavascriptAssetRequirements | None = None + + +class Assets39(BaseGroupAsset): + asset_type: Literal['zip'] = 'zip' + + +class Assets40(BaseGroupAsset): + asset_type: Literal['vast'] = 'vast' + requirements: vast_asset_requirements.VastAssetRequirements | None = None + + +class Assets41(BaseGroupAsset): + asset_type: Literal['daast'] = 'daast' + requirements: daast_asset_requirements.DaastAssetRequirements | None = None + -# Explicit exports -__all__ = [ - "Accessibility", - "Assets", - "Assets10", - "Assets11", - "Assets12", - "Assets13", - "Assets14", - "Assets15", - "Assets16", - "Assets17", - "Assets18", - "Assets19", - "Assets20", - "Assets21", - "Assets22", - "Assets23", - "Assets24", - "Assets25", - "Assets26", - "Assets27", - "Assets28", - "Assets29", - "Assets30", - "Assets31", - "Assets32", - "Assets33", - "Assets34", - "Assets35", - "Assets36", - "Assets37", - "Assets38", - "Assets39", - "Assets40", - "Assets41", - "Assets42", - "Assets43", - "Assets44", - "Assets45", - "Assets46", - "Assets47", - "Assets48", - "Assets49", - "Assets50", - "Assets51", - "Assets9", - "Assets94", - "BaseGroupAsset", - "BaseIndividualAsset", - "CanonicalParameters", - "CanonicalParameters1", - "CanonicalParameters10", - "CanonicalParameters11", - "CanonicalParameters12", - "CanonicalParameters13", - "CanonicalParameters14", - "CanonicalParameters15", - "CanonicalParameters16", - "CanonicalParameters17", - "CanonicalParameters18", - "CanonicalParameters19", - "CanonicalParameters2", - "CanonicalParameters20", - "CanonicalParameters21", - "CanonicalParameters22", - "CanonicalParameters23", - "CanonicalParameters24", - "CanonicalParameters25", - "CanonicalParameters26", - "CanonicalParameters27", - "CanonicalParameters28", - "CanonicalParameters29", - "CanonicalParameters3", - "CanonicalParameters30", - "CanonicalParameters31", - "CanonicalParameters32", - "CanonicalParameters33", - "CanonicalParameters4", - "CanonicalParameters5", - "CanonicalParameters6", - "CanonicalParameters7", - "CanonicalParameters8", - "CanonicalParameters9", - "Dimensions", - "Dimensions1", - "DisclosureCapability", - "Format", - "FormatCard", - "FormatCardDetailed", - "Renders", - "Renders1", - "Responsive", - "SelectionMode", - "SellerPreference", +class Assets42(BaseGroupAsset): + asset_type: Literal['url'] = 'url' + requirements: url_asset_requirements.UrlAssetRequirements | None = None + + +class Assets43(BaseGroupAsset): + asset_type: Literal['webhook'] = 'webhook' + requirements: webhook_asset_requirements.WebhookAssetRequirements | None = None + + +class Assets44(BaseGroupAsset): + asset_type: Literal['brief'] = 'brief' + + +class Assets46(BaseGroupAsset): + asset_type: Literal['display_tag'] = 'display_tag' + + +class Assets47(BaseGroupAsset): + asset_type: Literal['published_post'] = 'published_post' + + +class Assets48(BaseGroupAsset): + asset_type: Literal['card'] = 'card' + + +class Assets49(BaseGroupAsset): + asset_type: Literal['pixel_tracker'] = 'pixel_tracker' + + +class Assets50(BaseGroupAsset): + asset_type: Literal['vast_tracker'] = 'vast_tracker' + + +class Assets51(BaseGroupAsset): + asset_type: Literal['daast_tracker'] = 'daast_tracker' + + +class Assets22(BaseIndividualAsset): + item_type: Literal['individual'] = 'individual' + asset_type: Literal['catalog'] = 'catalog' + requirements: catalog_requirements.CatalogRequirements | None = None + + +class Assets45(BaseGroupAsset): + asset_type: Literal['catalog'] = 'catalog' + requirements: catalog_requirements.CatalogRequirements | None = None + + +Assets30 = Annotated[ + Assets31 + | Assets32 + | Assets33 + | Assets34 + | Assets35 + | Assets36 + | Assets37 + | Assets38 + | Assets39 + | Assets40 + | Assets41 + | Assets42 + | Assets43 + | Assets44 + | Assets45 + | Assets46 + | Assets47 + | Assets48 + | Assets49 + | Assets50 + | Assets51, + Field(discriminator='asset_type'), ] + + +class Assets29(AdCPBaseModel): + item_type: Annotated[ + Literal['repeatable_group'], + Field(description='Discriminator indicating this is a repeatable asset group'), + ] = 'repeatable_group' + asset_group_id: Annotated[ + str, Field(description="Identifier for this asset group (e.g., 'product', 'slide', 'card')") + ] + required: Annotated[ + StrictBool, + Field( + description='Whether this asset group is required. If true, at least min_count repetitions must be provided.' + ), + ] + min_count: Annotated[ + SchemaInt, + Field( + description='Minimum number of repetitions required (if group is required) or allowed (if optional)', + ge=0, + ), + ] + max_count: Annotated[ + SchemaInt, Field(description='Maximum number of repetitions allowed', ge=1) + ] + selection_mode: Annotated[ + SelectionMode | None, + Field( + description="How the platform uses repetitions of this group. 'sequential' means all items display in order (carousels, playlists). 'optimize' means the platform selects the best-performing combination from alternatives (asset group optimization like Meta Advantage+ or Google Pmax)." + ), + ] = SelectionMode.sequential + assets: Annotated[ + list[Assets30], Field(description='Assets within each repetition of this group') + ] + + +class CanonicalParameters1(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = None + v1_format_ref: Annotated[ + list[format_id_1.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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['image'] = 'image' + params: image.CanonicalFormatImage + + +class CanonicalParameters2(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = None + v1_format_ref: Annotated[ + list[format_id_1.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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['html5'] = 'html5' + params: html5.CanonicalFormatHtml5Banner + + +class CanonicalParameters3(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = None + v1_format_ref: Annotated[ + list[format_id_1.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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['display_tag'] = 'display_tag' + params: display_tag.CanonicalFormatDisplayTag + + +class CanonicalParameters4(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = None + v1_format_ref: Annotated[ + list[format_id_1.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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['image_carousel'] = 'image_carousel' + params: image_carousel.CanonicalFormatImageCarousel + + +class CanonicalParameters5(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = None + v1_format_ref: Annotated[ + list[format_id_1.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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['video_hosted'] = 'video_hosted' + params: video_hosted.CanonicalFormatHostedVideo + + +class CanonicalParameters6(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = None + v1_format_ref: Annotated[ + list[format_id_1.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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['video_vast'] = 'video_vast' + params: video_vast.CanonicalFormatVastVideo + + +class CanonicalParameters7(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = None + v1_format_ref: Annotated[ + list[format_id_1.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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['audio_hosted'] = 'audio_hosted' + params: audio_hosted.CanonicalFormatHostedAudio + + +class CanonicalParameters8(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = None + v1_format_ref: Annotated[ + list[format_id_1.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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['audio_vast'] = 'audio_vast' + params: audio_vast.CanonicalFormatVastAudio + + +class CanonicalParameters9(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = None + v1_format_ref: Annotated[ + list[format_id_1.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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['audio_daast'] = 'audio_daast' + params: audio_daast.CanonicalFormatDaastAudio + + +class CanonicalParameters10(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = None + v1_format_ref: Annotated[ + list[format_id_1.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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['sponsored_placement'] = 'sponsored_placement' + params: sponsored_placement.CanonicalFormatSponsoredPlacementRetailMediaCatalogDriven + + +class CanonicalParameters11(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = None + v1_format_ref: Annotated[ + list[format_id_1.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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['native_in_feed'] = 'native_in_feed' + params: native_in_feed.CanonicalFormatNativeInFeed + + +class CanonicalParameters12(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = None + v1_format_ref: Annotated[ + list[format_id_1.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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['responsive_creative'] = 'responsive_creative' + params: responsive_creative.CanonicalFormatResponsiveCreative + + +class CanonicalParameters13(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = None + v1_format_ref: Annotated[ + list[format_id_1.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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['agent_placement'] = 'agent_placement' + params: agent_placement.CanonicalFormatAgentPlacementAiSurfaceSponsoredPlacement + + +class CanonicalParameters14(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = None + v1_format_ref: Annotated[ + list[format_id_1.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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['seller_rendered_stateful_display'] = 'seller_rendered_stateful_display' + params: seller_rendered_stateful_display.CanonicalFormatSellerRenderedStatefulDisplay + + +class CanonicalParameters15(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = None + v1_format_ref: Annotated[ + list[format_id_1.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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['coordinated_placements'] = 'coordinated_placements' + params: coordinated_placements.CanonicalFormatCoordinatedPlacements + + +class CanonicalParameters16(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = None + v1_format_ref: Annotated[ + list[format_id_1.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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['custom'] = 'custom' + params: Annotated[ + dict[str, Any], + Field( + description="Custom shape's params. Validated against the schema fetched from `format_schema.uri` at the cached `format_schema.digest`." + ), + ] + + +class CanonicalParameters18(CanonicalParameters1, CanonicalParameters17): + pass + + +class CanonicalParameters19(CanonicalParameters2, CanonicalParameters17): + pass + + +class CanonicalParameters20(CanonicalParameters3, CanonicalParameters17): + pass + + +class CanonicalParameters21(CanonicalParameters4, CanonicalParameters17): + pass + + +class CanonicalParameters22(CanonicalParameters5, CanonicalParameters17): + pass + + +class CanonicalParameters23(CanonicalParameters6, CanonicalParameters17): + pass + + +class CanonicalParameters24(CanonicalParameters7, CanonicalParameters17): + pass + + +class CanonicalParameters25(CanonicalParameters8, CanonicalParameters17): + pass + + +class CanonicalParameters26(CanonicalParameters9, CanonicalParameters17): + pass + + +class CanonicalParameters27(CanonicalParameters10, CanonicalParameters17): + pass + + +class CanonicalParameters28(CanonicalParameters11, CanonicalParameters17): + pass + + +class CanonicalParameters29(CanonicalParameters12, CanonicalParameters17): + pass + + +class CanonicalParameters30(CanonicalParameters13, CanonicalParameters17): + pass + + +class CanonicalParameters31(CanonicalParameters14, CanonicalParameters17): + pass + + +class CanonicalParameters32(CanonicalParameters15, CanonicalParameters17): + pass + + +class CanonicalParameters33(CanonicalParameters16, CanonicalParameters17): + pass + + +class CanonicalParameters( + RootModel[ + CanonicalParameters18 + | CanonicalParameters19 + | CanonicalParameters20 + | CanonicalParameters21 + | CanonicalParameters22 + | CanonicalParameters23 + | CanonicalParameters24 + | CanonicalParameters25 + | CanonicalParameters26 + | CanonicalParameters27 + | CanonicalParameters28 + | CanonicalParameters29 + | CanonicalParameters30 + | CanonicalParameters31 + | CanonicalParameters32 + | CanonicalParameters33 + ] +): + root: Annotated[ + CanonicalParameters18 + | CanonicalParameters19 + | CanonicalParameters20 + | CanonicalParameters21 + | CanonicalParameters22 + | CanonicalParameters23 + | CanonicalParameters24 + | CanonicalParameters25 + | CanonicalParameters26 + | CanonicalParameters27 + | CanonicalParameters28 + | CanonicalParameters29 + | CanonicalParameters30 + | CanonicalParameters31 + | CanonicalParameters32 + | CanonicalParameters33, + Field( + description="**DEPRECATED in 3.1. Removed at 4.0.** Use `v1_format_ref` on the v2 `ProductFormatDeclaration` instead — the seller authors a v2 declaration (in `Product.format_options` or `creative.supported_formats`) and links it back to this v1 format via `v1_format_ref: { agent_url, id }`. The directional link from v2 → v1 is the same fact as `canonical_parameters` without the parallel-shape drift surface (v1 file and `canonical_parameters` were two declarations of the same thing; hand-authored, drifting silently).\n\nMigration: every seller currently authoring `canonical_parameters` SHOULD migrate to authoring a v2 declaration on the corresponding product (or capability) with `v1_format_ref` pointing back at this v1 format. v1 files become pure v1 again — no v2-shape mirroring.\n\n*Legacy behavior, retained for 3.1–3.x backward compatibility:* When `canonical` is set, this field carries the full ProductFormatDeclaration that the SDK projects this v1 format into. The `format_kind` MUST equal the `canonical` field value (validators enforce). When set, this is the authoritative source for SDK v1→v2 projection — the registry's structural-match parameter inference is bypassed. SDKs reading 3.1 catalogs MUST continue to honor `canonical_parameters` when present; 4.0+ SDKs MAY reject the field. New code SHOULD NOT emit this field. Seller execution authority is never projected through this deprecated field, so tracker_execution_contract and tracker_execution_contract_digest are forbidden.\n\n**Drift contract (still normative while supported).** Hand-authored `canonical_parameters` MUST satisfy the *narrows* relation against this v1 format's `requirements` and `assets[*]` shape (see canonical-formats.mdx 'Narrows — formal definition'). SDKs that read this v1 file SHOULD lint-time check the equivalence at build/load and emit `FORMAT_PROJECTION_FAILED` if the two disagree.", + ), + ] + def __getattr__(self, name: str) -> Any: + """Proxy attribute access to the wrapped type.""" + if name.startswith('_'): + raise AttributeError(name) + return getattr(self.root, name) + + + +class Format(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + format_id: Annotated[ + format_id_1.FormatReferenceStructuredObject, + Field( + description="This format's own identifier — a structured object {agent_url, id}, not a string. See /schemas/core/format-id.json for the full shape." + ), + ] + name: Annotated[str, Field(description='Human-readable format name')] + description: Annotated[ + str | None, + Field( + description='Plain text explanation of what this format does and what assets it requires' + ), + ] = None + example_url: Annotated[ + AnyUrl | None, + Field( + description='Optional URL to showcase page with examples and interactive demos of this format' + ), + ] = None + accepts_parameters: Annotated[ + list[format_id_parameter.FormatIdParameter] | None, + Field( + description='List of parameters this format accepts in format_id. Template formats define which parameters (dimensions, duration, etc.) can be specified when instantiating the format. Empty or omitted means this is a concrete format with fixed parameters.' + ), + ] = None + renders: Annotated[ + list[Renders | Renders1] | None, + Field( + description='Specification of rendered pieces for this format. Most formats produce a single render. Companion ad formats (video + banner), adaptive formats, and multi-placement formats produce multiple renders. Each render specifies its role and dimensions.', + min_length=1, + ), + ] = None + assets: Annotated[ + list[ + Assets + | Assets9 + | Assets10 + | Assets11 + | Assets12 + | Assets13 + | Assets14 + | Assets15 + | Assets16 + | Assets17 + | Assets18 + | Assets19 + | Assets20 + | Assets21 + | Assets22 + | Assets23 + | Assets24 + | Assets25 + | Assets26 + | Assets27 + | Assets28 + | Assets29 + ] + | None, + Field( + description="Array of all assets supported for this format. Each asset is identified by its asset_id, which must be used as the key in creative manifests. Use the 'required' boolean on each asset to indicate whether it's mandatory." + ), + ] = None + delivery: Annotated[ + dict[str, Any] | None, + Field(description='Delivery method specifications (e.g., hosted, VAST, third-party tags)'), + ] = None + supported_macros: Annotated[ + list[universal_macro.UniversalMacro | str] | None, + Field( + description='List of universal macros supported by this format (e.g., MEDIA_BUY_ID, CACHEBUSTER, DEVICE_ID). Used for validation and developer tooling. See docs/creative/universal-macros.mdx for full documentation.' + ), + ] = None + input_format_ids: Annotated[ + list[format_id_1.FormatReferenceStructuredObject] | None, + Field( + deprecated=True, + description='**DEPRECATED in 3.1. Removed at 4.0.** Use `list_transformers` instead — a transformer declares its own `input_format_ids`/`output_format_ids`, so build capability is a property of the transformer (the unit you select and that carries pricing), not a relationship hung on a format. Discover build capability via `list_transformers` (optionally filtered by `input_format_ids`/`output_format_ids`).\n\nMigration: sellers that expressed transform capability by hanging `input_format_ids` on a format SHOULD declare a transformer via `list_transformers` instead. Buyers SHOULD discover build capability via `list_transformers` rather than filtering formats.\n\n*Legacy behavior, retained for 3.1–3.x backward compatibility:* array of format IDs this format accepts as input creative manifests; when present, indicates this format can take existing creatives in these formats as input. SDKs reading 3.1 catalogs MUST continue to honor this field when present; 4.0+ SDKs MAY reject it. New code SHOULD NOT emit this field.', + ), + ] = None + output_format_ids: Annotated[ + list[format_id_1.FormatReferenceStructuredObject] | None, + Field( + deprecated=True, + description='**DEPRECATED in 3.1. Removed at 4.0.** Use `list_transformers` instead — a transformer declares its own `output_format_ids`, so what a builder can produce is a property of the transformer, not a relationship hung on a format. Discover via `list_transformers`.\n\nMigration: sellers that expressed multi-output build capability (e.g. a multi-publisher template) by hanging `output_format_ids` on a format SHOULD declare a transformer via `list_transformers` instead.\n\n*Legacy behavior, retained for 3.1–3.x backward compatibility:* array of format IDs this format can produce as output; when present, indicates this format can build creatives in these output formats. SDKs reading 3.1 catalogs MUST continue to honor this field when present; 4.0+ SDKs MAY reject it. New code SHOULD NOT emit this field.', + ), + ] = None + format_card: Annotated[ + FormatCard | None, + Field( + description='Optional standard visual card (300x400px) for displaying this format in user interfaces. Can be rendered via preview_creative or pre-generated.' + ), + ] = None + accessibility: Annotated[ + Accessibility | None, + Field( + description='Accessibility posture of this format. Declares the WCAG conformance level that creatives produced by this format will meet.' + ), + ] = None + supported_disclosure_positions: Annotated[ + list[disclosure_position.DisclosurePosition] | None, + Field( + description='Disclosure positions this format can render. Buyers use this to determine whether a format can satisfy their compliance requirements before submitting a creative. When omitted, the format makes no disclosure rendering guarantees — creative agents SHOULD treat this as incompatible with briefs that require specific disclosure positions. Values correspond to positions on creative-brief.json required_disclosures.', + min_length=1, + ), + ] = None + disclosure_capabilities: Annotated[ + list[DisclosureCapability] | None, + Field( + description='Structured disclosure capabilities per position with persistence modes. Declares which persistence behaviors each disclosure position supports, enabling persistence-aware matching against provenance render guidance and brief requirements. When present, supersedes supported_disclosure_positions for persistence-aware queries. The flat supported_disclosure_positions field is retained for backward compatibility. Each position MUST appear at most once; validators and agents SHOULD reject duplicates.', + min_length=1, + ), + ] = None + format_card_detailed: Annotated[ + FormatCardDetailed | None, + Field( + description='Optional detailed card with carousel and full specifications. Provides rich format documentation similar to ad spec pages.' + ), + ] = None + reported_metrics: Annotated[ + list[available_metric.AvailableMetric] | None, + Field( + description='Metrics this format can produce in delivery reporting. Buyers receive the intersection of format reported_metrics and product available_metrics. Under `enums/available-metric.json`, a container such as `viewability` subsumes numeric leaves such as `viewable_rate`; structured distributions match only their explicit `viewed_seconds_percentiles` or `viewed_seconds_histogram` identities. If omitted, the format defers entirely to product-level metric declarations.', + min_length=1, + ), + ] = None + pricing_options: Annotated[ + list[vendor_pricing_option.VendorPricingOption] | None, + Field( + deprecated=True, + description='**DEPRECATED in 3.1. Removed at 4.0.** Use `transformer.pricing_options` (via `list_transformers`) instead — pricing belongs on the transformer (the unit selected and billed), exactly as it belongs on a media-buy product. Once formats only describe output shape, format-level pricing is vestigial.\n\nMigration: transformation/generation agents that charged via `format.pricing_options` SHOULD move the same `vendor-pricing-option` entries onto the corresponding transformer. The applied option is echoed per-leaf on the build_creative response and reconciled via report_usage, unchanged.\n\n*Legacy behavior, retained for 3.1–3.x backward compatibility:* pricing options for this format, used by transformation/generation agents that charge per format adapted, per image generated, or per unit of work; present when the request included include_pricing=true and account. SDKs reading 3.1 catalogs MUST continue to honor this field when present; 4.0+ SDKs MAY reject it. New code SHOULD NOT emit this field.', + min_length=1, + ), + ] = None + canonical: Annotated[ + canonical_projection_ref.CanonicalProjectionReference | None, + Field( + description='Optional v2 canonical-projection annotation. Always an object — bare-string shorthand (`canonical: "image"`) is not supported; the minimal form is `canonical: { "kind": "image" }`. Carries `kind` (which canonical the v1 format projects to) plus optional `asset_source` and `slots_override` for cases where the v1 format\'s shape doesn\'t follow the canonical\'s defaults (e.g., generative entries whose input is `generation_prompt: text` instead of `image_main: image`).\n\nWhen set, SDKs use this annotation as the authoritative v1 → v2 mapping for this format, bypassing the [v1 canonical mapping registry](/schemas/registries/v1-canonical-mapping.json) lookup. Combined with the slot-level `asset_group_id` declarations on each `assets[i]` entry, a v1 format declaration with `canonical` set is fully self-describing for v1↔v2 translation.\n\nResolution order for SDK projection from v1 wire shape to v2 (per RFC #3305 amendment #3767):\n1. If this `canonical` field is set, use it (seller-declared, highest priority). Apply `asset_source` and `slots_override` from the projection ref when present; otherwise inherit the canonical\'s defaults.\n2. Else, look up `format_id` in the canonical mapping registry\'s `format_id_glob` entries.\n3. Else, attempt structural match against the registry\'s `structural` entries (asset types, slot shape, vast_versions, etc.).\n4. Else, fail closed: SDK MUST NOT emit `format_options` for products carrying this format. Surface `FORMAT_PROJECTION_FAILED` on the response `errors[]` suggesting the seller add an explicit `canonical` annotation or file a registry entry.\n\nWhen `canonical.kind` is `custom`, the seller MUST also declare `canonical_format_shape` and `canonical_format_schema` (parallel to ProductFormatDeclaration\'s `format_shape` and `format_schema`) so buyer SDKs can fetch the seller\'s custom format schema.\n\nSee `canonical-projection-ref.json` for full projection semantics and examples (default-slot case, generative case, brief-driven case).' + ), + ] = None + canonical_parameters: Annotated[ + CanonicalParameters | None, + Field( + deprecated=True, + description="**DEPRECATED in 3.1. Removed at 4.0.** Use `v1_format_ref` on the v2 `ProductFormatDeclaration` instead — the seller authors a v2 declaration (in `Product.format_options` or `creative.supported_formats`) and links it back to this v1 format via `v1_format_ref: { agent_url, id }`. The directional link from v2 → v1 is the same fact as `canonical_parameters` without the parallel-shape drift surface (v1 file and `canonical_parameters` were two declarations of the same thing; hand-authored, drifting silently).\n\nMigration: every seller currently authoring `canonical_parameters` SHOULD migrate to authoring a v2 declaration on the corresponding product (or capability) with `v1_format_ref` pointing back at this v1 format. v1 files become pure v1 again — no v2-shape mirroring.\n\n*Legacy behavior, retained for 3.1–3.x backward compatibility:* When `canonical` is set, this field carries the full ProductFormatDeclaration that the SDK projects this v1 format into. The `format_kind` MUST equal the `canonical` field value (validators enforce). When set, this is the authoritative source for SDK v1→v2 projection — the registry's structural-match parameter inference is bypassed. SDKs reading 3.1 catalogs MUST continue to honor `canonical_parameters` when present; 4.0+ SDKs MAY reject the field. New code SHOULD NOT emit this field. Seller execution authority is never projected through this deprecated field, so tracker_execution_contract and tracker_execution_contract_digest are forbidden.\n\n**Drift contract (still normative while supported).** Hand-authored `canonical_parameters` MUST satisfy the *narrows* relation against this v1 format's `requirements` and `assets[*]` shape (see canonical-formats.mdx 'Narrows — formal definition'). SDKs that read this v1 file SHOULD lint-time check the equivalence at build/load and emit `FORMAT_PROJECTION_FAILED` if the two disagree.", + ), + ] = None + + +# Backward compatibility for the pre-beta3 generated repeatable group name. +Assets94 = Assets29 diff --git a/src/adcp/types/domains/core/format_id.py b/src/adcp/types/domains/core/format_id.py index 23c477c4b..ef25372f8 100644 --- a/src/adcp/types/domains/core/format_id.py +++ b/src/adcp/types/domains/core/format_id.py @@ -1,18 +1,57 @@ -"""Types declared by the AdCP ``core/format_id`` schema. +# generated by datamodel-codegen: +# filename: core/format_id.json +# timestamp: 2026-10-05T03:58:57+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.format_id import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel, SchemaInt, WireUrl +from pydantic import ConfigDict, Field, StrictFloat -from adcp.types.generated_poc.core.format_id import FormatReferenceStructuredObject -# Explicit exports -__all__ = ["FormatReferenceStructuredObject"] +class FormatReferenceStructuredObject(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + agent_url: Annotated[ + WireUrl, + Field( + description="URL of the agent that defines this format (e.g., 'https://creative.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats). Callers comparing two `format-id` values MUST canonicalize `agent_url` per the AdCP URL canonicalization rules before treating two formats as the same. See docs/reference/url-canonicalization." + ), + ] + id: Annotated[ + str, + Field( + description="Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant.", + pattern='^[a-zA-Z0-9_-]+$', + ), + ] + width: Annotated[ + SchemaInt | None, + Field( + description='Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', + ge=1, + ), + ] = None + height: Annotated[ + SchemaInt | None, + Field( + description='Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', + ge=1, + ), + ] = None + duration_ms: Annotated[ + StrictFloat | None, + Field( + description='Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters.', + ge=1.0, + ), + ] = None + pixel_ratio: Annotated[ + StrictFloat | None, + Field( + description='Required intrinsic-pixel density for a parameterized visual format, expressed as intrinsic pixels per logical pixel. Requires `width` and `height`. Example: `{id: "display_image", width: 300, height: 250, pixel_ratio: 2}` identifies a 300×250 logical render supplied by a 600×500 image. Omit for the backward-compatible 1x variant.', + gt=0.0, + ), + ] = None diff --git a/src/adcp/types/domains/core/format_option_ref.py b/src/adcp/types/domains/core/format_option_ref.py index db7fc092c..1a2d5e5bb 100644 --- a/src/adcp/types/domains/core/format_option_ref.py +++ b/src/adcp/types/domains/core/format_option_ref.py @@ -1,22 +1,64 @@ -"""Types declared by the AdCP ``core/format_option_ref`` schema. +# generated by datamodel-codegen: +# filename: core/format_option_ref.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.format_option_ref import +from typing import Annotated, Any, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.format_option_ref import ( - FormatOptionReference, - FormatOptionReference1, - FormatOptionReference2, -) +class FormatOptionReference1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + scope: Annotated[ + Literal['publisher'], + Field( + description="Reference resolves against the named publisher's adagents.json top-level `formats[]` catalog." + ), + ] = 'publisher' + publisher_domain: Annotated[ + str, + Field( + description='Publisher domain where the adagents.json declaring this format option is hosted.', + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] + format_option_id: Annotated[ + str, + Field( + description="Stable format option ID from the publisher's adagents.json top-level `formats[]`, matching a publisher-catalog-backed entry in the target product's `format_options[]`." + ), + ] + + +class FormatOptionReference2(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + scope: Annotated[ + Literal['product'], + Field( + description="Reference resolves only against the target product's inline `format_options[]`." + ), + ] = 'product' + format_option_id: Annotated[ + str, + Field( + description="Stable format option ID from the target product's inline `format_options[]`." + ), + ] + publisher_domain: Any | None = None + -# Explicit exports -__all__ = ["FormatOptionReference", "FormatOptionReference1", "FormatOptionReference2"] +FormatOptionReference = Annotated[ + FormatOptionReference1 | FormatOptionReference2, + Field( + description='Discriminated reference to a product format option. The global canonical shape is still named by `format_kind`; this reference selects one concrete product `format_options[]` entry. `scope: "publisher"` identifies a publisher-declared catalog option by `{ publisher_domain, format_option_id }`. `scope: "product"` identifies a product-local option by `format_option_id`; the enclosing package/product context supplies the namespace.', + discriminator='scope', + title='Format Option Reference', + ), +] diff --git a/src/adcp/types/domains/core/format_shape_vocabulary.py b/src/adcp/types/domains/core/format_shape_vocabulary.py index efc0b61ed..780be1cf0 100644 --- a/src/adcp/types/domains/core/format_shape_vocabulary.py +++ b/src/adcp/types/domains/core/format_shape_vocabulary.py @@ -1,18 +1,19 @@ -"""Types declared by the AdCP ``core/format_shape_vocabulary`` schema. +# generated by datamodel-codegen: +# filename: core/format_shape_vocabulary.json +# timestamp: 2026-05-22T13:15:12+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.format_shape_vocabulary import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Any -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import Field, RootModel -from adcp.types.generated_poc.core.format_shape_vocabulary import AdcpFormatShapeVocabularyRegistry -# Explicit exports -__all__ = ["AdcpFormatShapeVocabularyRegistry"] +class AdcpFormatShapeVocabularyRegistry(RootModel[Any]): + root: Annotated[ + Any, + Field( + description='Canonical registry of `format_shape` values used on `ProductFormatDeclaration` when `format_kind: "custom"`. Captures recognized creative-structure patterns that are NOT yet first-class canonical formats — composed/coordinated/sponsorship shapes that high-end publishers and broadcast networks sell as headline products. Each registry entry names a global shape; the seller\'s actual structure lives in `format_schema` (URI+digest reference to the seller-hosted or AAO-mirrored schema) so buyer agents can fetch and validate against a real schema rather than reasoning over an opaque ext blob.\n\n**Two-layer extensibility:**\n- **Canonical** (`format_kind: image`, `video_vast`, etc.): full spec coverage, stable contract.\n- **Custom + format_shape + format_schema** (`format_kind: "custom"`): recognized pattern, classified against this vocabulary, but the params/slots structure is supplied by a fetchable schema rather than baked into AdCP.\n\nNon-canonical `format_shape` values remain valid (validators MAY soft-warn) so adopters CAN ship a shape that isn\'t yet in the registry — adding entries is a vocabulary PR, not a major-version bump. 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 (creates `/schemas/formats/canonical/.json`, adds the value to `canonical-format-kind.json`, retires the registry entry). See [adcp#3666](https://github.com/adcontextprotocol/adcp/issues/3666) for the promotion queue.\n\n**Promotion migration contract (normative).** Promotion is a wire-shape change for any consumer code branching on `format_kind == "custom"` — the same publisher\'s product now ships under `format_kind: ""`. To avoid silent breakage:\n\n1. **Transition window.** When the working group promotes a `format_shape` to a first-class `format_kind`, sellers MAY ship a product under BOTH shapes during the transition window — two declarations on the same product\'s `format_options` array, one `format_kind: "custom"` + `format_shape: ""` and one `format_kind: ""`. Transition window is at minimum 90 days; the promotion PR sets the calendar.\n2. **Consumer-SDK deprecation warning.** SDKs encountering a `format_kind: "custom"` declaration whose `format_shape` matches an entry promoted to a first-class canonical SHOULD emit a structured deprecation warning via their lint channel (same pattern as `FORMAT_PROJECTION_FAILED` cross-boundary visibility) carrying `{ format_shape, promoted_to: format_kind, promotion_release, transition_end }`. Adopters branching on `format_kind == "custom"` past `transition_end` silently lose that publisher\'s inventory; the deprecation warning is the early signal.\n3. **`promotion_status` lifecycle.** When promotion is scheduled, the entry\'s `promotion_status` SHOULD update from `tracking — see adcp#3666` to `promoted to in ; transition ends `. SDKs MAY read the registry at codegen / runtime to populate the deprecation warning\'s `transition_end`.\n4. **Producer-side hygiene.** After the transition window closes, sellers SHOULD drop the legacy `format_kind: "custom"` declaration and ship only the first-class canonical. Buyers MAY then assume `format_kind == "custom"` + `format_shape: ""` is a long-tail / non-promoted shape, not a promoted-canonical-shipped-under-the-old-name.\n\nWithout this contract, every promotion event silently breaks adopter code branching on `format_kind == "custom"`. With it, the breakage surfaces as a structured warning during the transition window and adopters can update their branching ahead of the cutover.', + title='AdCP Format Shape Vocabulary Registry', + ), + ] diff --git a/src/adcp/types/domains/core/frequency_cap.py b/src/adcp/types/domains/core/frequency_cap.py index d0732e648..06dbfdec0 100644 --- a/src/adcp/types/domains/core/frequency_cap.py +++ b/src/adcp/types/domains/core/frequency_cap.py @@ -1,18 +1,65 @@ -"""Types declared by the AdCP ``core/frequency_cap`` schema. +# generated by datamodel-codegen: +# filename: core/frequency_cap.json +# timestamp: 2026-10-04T01:14:11+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.frequency_cap import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictFloat, model_validator + +from ..enums import reach_unit +from . import duration -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.frequency_cap import FrequencyCap +class FrequencyCap(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + suppress: Annotated[ + duration.Duration | None, + Field( + description='Cooldown period between consecutive exposures to the same entity. Prevents back-to-back ad delivery (e.g. {"interval": 60, "unit": "minutes"} for a 1-hour cooldown). Preferred over suppress_minutes.' + ), + ] = None + suppress_minutes: Annotated[ + StrictFloat | None, + Field( + deprecated=True, + description='Deprecated — use suppress instead. Cooldown period in minutes between consecutive exposures to the same entity (e.g. 60 for a 1-hour cooldown).', + ge=0.0, + ), + ] = None + max_impressions: Annotated[ + SchemaInt | None, + Field( + description="Maximum number of impressions per entity per window. For duration windows, implementations typically use a rolling window. campaign applies across the owning field's full flight: the package flight for a targeting overlay, or the MediaBuy flight for a root cap.", + ge=1, + ), + ] = None + per: Annotated[ + reach_unit.ReachUnit | None, + Field( + description='Entity granularity for impression counting. Required when max_impressions is set.' + ), + ] = None + window: Annotated[ + duration.Duration | None, + Field( + description='Time window for the max_impressions cap (e.g. {"interval": 7, "unit": "days"} or {"interval": 1, "unit": "campaign"} for the full flight). Required when max_impressions is set.' + ), + ] = None -# Explicit exports -__all__ = ["FrequencyCap"] + @model_validator(mode='after') + def _require_schema_required_group(self) -> FrequencyCap: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('suppress',), ('suppress_minutes',), ('max_impressions',),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'FrequencyCap requires at least one of these field groups: suppress | suppress_minutes | max_impressions' + ) diff --git a/src/adcp/types/domains/core/frequency_cap_constraints.py b/src/adcp/types/domains/core/frequency_cap_constraints.py index 0677bf57c..b7e16aafd 100644 --- a/src/adcp/types/domains/core/frequency_cap_constraints.py +++ b/src/adcp/types/domains/core/frequency_cap_constraints.py @@ -1,18 +1,69 @@ -"""Types declared by the AdCP ``core/frequency_cap_constraints`` schema. +# generated by datamodel-codegen: +# filename: core/frequency_cap_constraints.json +# timestamp: 2026-10-02T23:28:30+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.frequency_cap_constraints import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, model_validator + +from ..enums import frequency_cap_control_mode, frequency_cap_mutable_field, reach_unit +from . import ext as ext_1 +from . import frequency_cap_impression_constraints, frequency_cap_interval_constraints -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.frequency_cap_constraints import FrequencyCapConstraints +class FrequencyCapConstraints(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + mutable_fields: Annotated[ + list[frequency_cap_mutable_field.FrequencyCapMutableField] | None, + Field( + description='Logical cap fields that can change after creation. An empty array means create-only. For example, [max_impressions] permits changing the count while keeping per and window fixed. Omission means update support is undeclared and the legacy broad meaning applies.' + ), + ] = None + supported_control_modes: Annotated[ + list[frequency_cap_control_mode.FrequencyCapControlMode] | None, + Field( + description='FrequencyCap shapes accepted by the product. max_impressions_and_suppress means both controls may appear together and are enforced with AND semantics.', + min_length=1, + ), + ] = None + supported_per_units: Annotated[ + list[reach_unit.ReachUnit] | None, + Field(description='Entity granularities accepted by max_impressions caps.', min_length=1), + ] = None + max_impressions_constraints: Annotated[ + frequency_cap_impression_constraints.FrequencyCapImpressionConstraints | None, + Field(description='Exact supported max_impressions presets or range.'), + ] = None + window_constraints: Annotated[ + list[frequency_cap_interval_constraints.FrequencyCapIntervalConstraints] | None, + Field( + description='Exact supported max_impressions.window intervals, one entry per duration unit.', + min_length=1, + ), + ] = None + suppression_constraints: Annotated[ + list[frequency_cap_interval_constraints.FrequencyCapIntervalConstraints] | None, + Field( + description='Exact supported suppress cooldown intervals, one entry per duration unit.', + min_length=1, + ), + ] = None + ext: ext_1.ExtensionObject | None = None -# Explicit exports -__all__ = ["FrequencyCapConstraints"] + @model_validator(mode='after') + def _require_schema_required_group(self) -> FrequencyCapConstraints: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('mutable_fields',), ('supported_control_modes',), ('supported_per_units',), ('max_impressions_constraints',), ('window_constraints',), ('suppression_constraints',),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'FrequencyCapConstraints requires at least one of these field groups: mutable_fields | supported_control_modes | supported_per_units | max_impressions_constraints | window_constraints | suppression_constraints' + ) diff --git a/src/adcp/types/domains/core/frequency_cap_duration_unit.py b/src/adcp/types/domains/core/frequency_cap_duration_unit.py index 6c2e3b22d..2464b11f3 100644 --- a/src/adcp/types/domains/core/frequency_cap_duration_unit.py +++ b/src/adcp/types/domains/core/frequency_cap_duration_unit.py @@ -1,18 +1,15 @@ -"""Types declared by the AdCP ``core/frequency_cap_duration_unit`` schema. +# generated by datamodel-codegen: +# filename: core/frequency_cap_duration_unit.json +# timestamp: 2026-09-14T14:16:02+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.frequency_cap_duration_unit import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.frequency_cap_duration_unit import FrequencyCapDurationUnit +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["FrequencyCapDurationUnit"] +class FrequencyCapDurationUnit(StrEnum): + seconds = 'seconds' + minutes = 'minutes' + hours = 'hours' + days = 'days' + campaign = 'campaign' diff --git a/src/adcp/types/domains/core/frequency_cap_impression_constraints.py b/src/adcp/types/domains/core/frequency_cap_impression_constraints.py index ff33cb782..e51a2b74d 100644 --- a/src/adcp/types/domains/core/frequency_cap_impression_constraints.py +++ b/src/adcp/types/domains/core/frequency_cap_impression_constraints.py @@ -1,21 +1,41 @@ -"""Types declared by the AdCP ``core/frequency_cap_impression_constraints`` schema. +# generated by datamodel-codegen: +# filename: core/frequency_cap_impression_constraints.json +# timestamp: 2026-10-04T01:14:11+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.frequency_cap_impression_constraints import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from adcp.types._scalar import ScalarInt +from pydantic import ConfigDict, Field, model_validator + +from . import ext as ext_1 + + +class AllowedValue(ScalarInt): + __slots__ = () + _constraints = {'ge': 1} -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.frequency_cap_impression_constraints import ( - AllowedValue, - FrequencyCapImpressionConstraints, -) +class FrequencyCapImpressionConstraints(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + minimum: Annotated[SchemaInt | None, Field(ge=1)] = None + maximum: Annotated[SchemaInt | None, Field(ge=1)] = None + allowed_values: Annotated[list[AllowedValue] | None, Field(min_length=1)] = None + ext: ext_1.ExtensionObject | None = None -# Explicit exports -__all__ = ["AllowedValue", "FrequencyCapImpressionConstraints"] + @model_validator(mode='after') + def _require_schema_required_group(self) -> FrequencyCapImpressionConstraints: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('allowed_values',), ('minimum', 'maximum'),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'FrequencyCapImpressionConstraints requires at least one of these field groups: allowed_values | minimum+maximum' + ) diff --git a/src/adcp/types/domains/core/frequency_cap_interval_constraints.py b/src/adcp/types/domains/core/frequency_cap_interval_constraints.py index d234b91d1..8c4b030ee 100644 --- a/src/adcp/types/domains/core/frequency_cap_interval_constraints.py +++ b/src/adcp/types/domains/core/frequency_cap_interval_constraints.py @@ -1,21 +1,43 @@ -"""Types declared by the AdCP ``core/frequency_cap_interval_constraints`` schema. +# generated by datamodel-codegen: +# filename: core/frequency_cap_interval_constraints.json +# timestamp: 2026-10-04T01:14:11+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.frequency_cap_interval_constraints import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from adcp.types._scalar import ScalarInt +from pydantic import ConfigDict, Field, model_validator + +from . import ext as ext_1 +from . import frequency_cap_duration_unit + + +class AllowedInterval(ScalarInt): + __slots__ = () + _constraints = {'ge': 1} -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.frequency_cap_interval_constraints import ( - AllowedInterval, - FrequencyCapIntervalConstraints, -) +class FrequencyCapIntervalConstraints(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + unit: frequency_cap_duration_unit.FrequencyCapDurationUnit + minimum_interval: Annotated[SchemaInt | None, Field(ge=1)] = None + maximum_interval: Annotated[SchemaInt | None, Field(ge=1)] = None + allowed_intervals: Annotated[list[AllowedInterval] | None, Field(min_length=1)] = None + ext: ext_1.ExtensionObject | None = None -# Explicit exports -__all__ = ["AllowedInterval", "FrequencyCapIntervalConstraints"] + @model_validator(mode='after') + def _require_schema_required_group(self) -> FrequencyCapIntervalConstraints: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('allowed_intervals',), ('minimum_interval', 'maximum_interval'),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'FrequencyCapIntervalConstraints requires at least one of these field groups: allowed_intervals | minimum_interval+maximum_interval' + ) diff --git a/src/adcp/types/domains/core/frequency_cap_requirements.py b/src/adcp/types/domains/core/frequency_cap_requirements.py index dcba74171..0a034bfd0 100644 --- a/src/adcp/types/domains/core/frequency_cap_requirements.py +++ b/src/adcp/types/domains/core/frequency_cap_requirements.py @@ -1,18 +1,44 @@ -"""Types declared by the AdCP ``core/frequency_cap_requirements`` schema. +# generated by datamodel-codegen: +# filename: core/frequency_cap_requirements.json +# timestamp: 2026-09-14T14:16:02+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.frequency_cap_requirements import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import frequency_cap_control_mode, frequency_cap_mutable_field, reach_unit +from . import frequency_cap_duration_unit -from adcp.types.generated_poc.core.frequency_cap_requirements import FrequencyCapRequirements -# Explicit exports -__all__ = ["FrequencyCapRequirements"] +class FrequencyCapRequirements(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + mutable_fields: Annotated[ + list[frequency_cap_mutable_field.FrequencyCapMutableField] | None, + Field( + description='Require each listed logical field to be mutable after creation. Matches a product whose mutable_fields contains every listed value, or a product that omits mutable_fields, or legacy frequency_cap: true. A product declaring mutable_fields: [] is create-only and never matches a non-empty list.', + min_length=1, + ), + ] = None + supported_control_modes: Annotated[ + list[frequency_cap_control_mode.FrequencyCapControlMode] | None, Field(min_length=1) + ] = None + supported_per_units: Annotated[list[reach_unit.ReachUnit] | None, Field(min_length=1)] = None + supported_window_units: Annotated[ + list[frequency_cap_duration_unit.FrequencyCapDurationUnit] | None, + Field( + description='Require each listed unit to be a supported max_impressions.window unit after seller-wide inheritance.', + min_length=1, + ), + ] = None + supported_suppression_units: Annotated[ + list[frequency_cap_duration_unit.FrequencyCapDurationUnit] | None, + Field( + description='Require each listed unit to be a supported suppress unit after seller-wide inheritance.', + min_length=1, + ), + ] = None diff --git a/src/adcp/types/domains/core/generation_credential.py b/src/adcp/types/domains/core/generation_credential.py index cc8af1243..88054752f 100644 --- a/src/adcp/types/domains/core/generation_credential.py +++ b/src/adcp/types/domains/core/generation_credential.py @@ -1,18 +1,48 @@ -"""Types declared by the AdCP ``core/generation_credential`` schema. +# generated by datamodel-codegen: +# filename: core/generation_credential.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.generation_credential import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import right_use +from . import ext as ext_1 -from adcp.types.generated_poc.core.generation_credential import GenerationCredential -# Explicit exports -__all__ = ["GenerationCredential"] +class GenerationCredential(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + provider: Annotated[ + str, + Field( + description="LLM or generation service provider identifier (e.g., 'midjourney', 'elevenlabs', 'stability')" + ), + ] + rights_key: Annotated[ + str, + Field( + description='Scoped API key or token for generating rights-cleared content. The provider validates this key at generation time to verify the caller is authorized.' + ), + ] + uses: Annotated[ + list[right_use.RightUse], + Field(description='Which rights uses this credential covers', min_length=1), + ] + expires_at: Annotated[ + AwareDatetime | None, + Field( + description='When this credential expires. Key lifetime is determined by the provider.' + ), + ] = None + endpoint: Annotated[ + AnyUrl | None, + Field( + description="Provider API endpoint to use with this credential, if different from the provider's default" + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/geo_breakdown_support.py b/src/adcp/types/domains/core/geo_breakdown_support.py index 8e828f25d..c69e3f8a1 100644 --- a/src/adcp/types/domains/core/geo_breakdown_support.py +++ b/src/adcp/types/domains/core/geo_breakdown_support.py @@ -1,18 +1,39 @@ -"""Types declared by the AdCP ``core/geo_breakdown_support`` schema. +# generated by datamodel-codegen: +# filename: core/geo_breakdown_support.json +# timestamp: 2026-10-03T15:09:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.geo_breakdown_support import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import metro_system +from . import postal_area_support -from adcp.types.generated_poc.core.geo_breakdown_support import GeographicBreakdownSupport -# Explicit exports -__all__ = ["GeographicBreakdownSupport"] +class GeographicBreakdownSupport(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + country: Annotated[ + StrictBool | None, + Field(description='Supports country-level geo breakdown (ISO 3166-1 alpha-2)'), + ] = None + region: Annotated[ + StrictBool | None, + Field(description='Supports region/state-level geo breakdown (ISO 3166-2)'), + ] = None + metro: Annotated[ + dict[metro_system.MetroAreaSystem, StrictBool] | None, + Field( + description='Metro area breakdown support. Keys are metro-system enum values; true means supported.' + ), + ] = None + postal_area: Annotated[ + postal_area_support.PostalAreaSupport | None, + Field( + description='Postal area breakdown support. Prefer the native country-keyed map where each ISO 3166-1 alpha-2 country lists supported country-local postal systems. Deprecated legacy country-fused postal-system boolean maps remain accepted for compatibility.' + ), + ] = None diff --git a/src/adcp/types/domains/core/geo_delivery_metrics.py b/src/adcp/types/domains/core/geo_delivery_metrics.py index e58253a68..05560e37c 100644 --- a/src/adcp/types/domains/core/geo_delivery_metrics.py +++ b/src/adcp/types/domains/core/geo_delivery_metrics.py @@ -1,18 +1,46 @@ -"""Types declared by the AdCP ``core/geo_delivery_metrics`` schema. +# generated by datamodel-codegen: +# filename: core/geo_delivery_metrics.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.geo_delivery_metrics import +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import geo_level as geo_level_1 +from .delivery_metrics import DeliveryMetrics -from adcp.types.generated_poc.core.geo_delivery_metrics import GeoDeliveryMetrics -# Explicit exports -__all__ = ["GeoDeliveryMetrics"] +class GeoDeliveryMetrics(DeliveryMetrics): + geo_level: Annotated[ + geo_level_1.GeographicTargetingLevel, + Field(description='Geographic level of this entry (country, region, metro, postal_area)'), + ] + system: Annotated[ + str | None, + Field( + description='Classification system for metro or postal_area levels. Metro rows use metro-system values. Native postal rows use country-local postal-system values with country; deprecated legacy postal rows may use legacy-postal-system values.' + ), + ] = None + country: Annotated[ + str | None, + Field( + description='ISO 3166-1 alpha-2 country code for native postal_area rows.', + pattern='^[A-Z]{2}$', + ), + ] = None + geo_code: Annotated[ + str, + Field( + description="Geographic code within the level and system. Country: ISO 3166-1 alpha-2 ('US'). Region: ISO 3166-2 with country prefix ('US-CA'). Metro/postal: system-specific code ('501', '10001')." + ), + ] + geo_name: Annotated[ + str | None, + Field( + description="Human-readable geographic name (e.g., 'United States', 'California', 'New York DMA')" + ), + ] = None + impressions: Any + spend: Any diff --git a/src/adcp/types/domains/core/geo_metro.py b/src/adcp/types/domains/core/geo_metro.py index d370ea1d5..af0904b2c 100644 --- a/src/adcp/types/domains/core/geo_metro.py +++ b/src/adcp/types/domains/core/geo_metro.py @@ -1,18 +1,29 @@ -"""Types declared by the AdCP ``core/geo_metro`` schema. +# generated by datamodel-codegen: +# filename: core/geo_metro.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.geo_metro import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import metro_system -from adcp.types.generated_poc.core.geo_metro import GeoMetro -# Explicit exports -__all__ = ["GeoMetro"] +class GeoMetro(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + system: Annotated[ + metro_system.MetroAreaSystem, + Field(description="Metro area classification system (e.g., 'nielsen_dma', 'uk_itl2')"), + ] + values: Annotated[ + list[str], + Field( + description="Metro codes within the system (e.g., ['501', '602'] for Nielsen DMAs)", + min_length=1, + ), + ] diff --git a/src/adcp/types/domains/core/geo_place_area.py b/src/adcp/types/domains/core/geo_place_area.py index 5eb429a4c..5268362c4 100644 --- a/src/adcp/types/domains/core/geo_place_area.py +++ b/src/adcp/types/domains/core/geo_place_area.py @@ -1,18 +1,55 @@ -"""Types declared by the AdCP ``core/geo_place_area`` schema. +# generated by datamodel-codegen: +# filename: core/geo_place_area.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.geo_place_area import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import ext as ext_1 +from . import geo_place_system, geo_place_type + + +class Value(ScalarStr): + __slots__ = () + _constraints = {'min_length': 1} -from adcp.types.generated_poc.core.geo_place_area import GeographicPlaceArea, Value -# Explicit exports -__all__ = ["GeographicPlaceArea", "Value"] +class GeographicPlaceArea(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + country: Annotated[ + str, + Field( + description='ISO 3166-1 alpha-2 country code containing the place.', + pattern='^[A-Z]{2}$', + ), + ] + system: geo_place_system.GeographicPlaceIdentifierSystem + system_version: Annotated[ + str | None, + Field( + description="Optional exact catalog version from the seller's declared supported_versions. When omitted, the seller applies catalog.current_version and MUST echo that version on persisted package state.", + min_length=1, + ), + ] = None + place_type: geo_place_type.GeographicPlaceType + values: Annotated[ + list[Value], + Field( + description='Stable place identifiers in the declared system. Display names are not valid targeting values.', + min_length=1, + ), + ] + value_labels: Annotated[ + dict[str, str] | None, + Field( + description='Optional human-readable diagnostic labels keyed by identifiers present in values. Extra keys are a conformance error. Labels are non-authoritative and MUST NOT be used to resolve or apply targeting.' + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/geo_place_catalog_capability.py b/src/adcp/types/domains/core/geo_place_catalog_capability.py index 3da40e779..f4be47dc1 100644 --- a/src/adcp/types/domains/core/geo_place_catalog_capability.py +++ b/src/adcp/types/domains/core/geo_place_catalog_capability.py @@ -1,21 +1,45 @@ -"""Types declared by the AdCP ``core/geo_place_catalog_capability`` schema. +# generated by datamodel-codegen: +# filename: core/geo_place_catalog_capability.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.geo_place_catalog_capability import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import AnyUrl, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import geo_place_resolver + + +class SupportedVersion(ScalarStr): + __slots__ = () + _constraints = {'min_length': 1} -from adcp.types.generated_poc.core.geo_place_catalog_capability import ( - GeographicPlaceCatalogCapability, - SupportedVersion, -) -# Explicit exports -__all__ = ["GeographicPlaceCatalogCapability", "SupportedVersion"] +class GeographicPlaceCatalogCapability(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + source: Annotated[ + AnyUrl | None, + Field( + description='Optional catalog dataset or derivative-source identifier. This records provenance/coverage and does not change the identifier namespace in system.' + ), + ] = None + current_version: Annotated[ + str, + Field( + description='Version applied when a buyer omits system_version. Sellers MUST echo this version on persisted package state.', + min_length=1, + ), + ] + supported_versions: Annotated[ + list[SupportedVersion], + Field( + description='Exact catalog versions accepted for new targeting or target-changing updates. Must include current_version. Removing a version does not mutate or silently invalidate targets already pinned to it.', + min_length=1, + ), + ] + resolver: geo_place_resolver.GeographicPlaceResolver diff --git a/src/adcp/types/domains/core/geo_place_catalog_entry.py b/src/adcp/types/domains/core/geo_place_catalog_entry.py index 47f28d3e6..6967d6b1f 100644 --- a/src/adcp/types/domains/core/geo_place_catalog_entry.py +++ b/src/adcp/types/domains/core/geo_place_catalog_entry.py @@ -1,23 +1,87 @@ -"""Types declared by the AdCP ``core/geo_place_catalog_entry`` schema. +# generated by datamodel-codegen: +# filename: core/geo_place_catalog_entry.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.geo_place_catalog_entry import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import ext as ext_1 +from . import geo_place_type + + +class ParentLabel(ScalarStr): + __slots__ = () + _constraints = {'min_length': 1} + + +class Status(StrEnum): + active = 'active' + removal_planned = 'removal_planned' + deprecated = 'deprecated' + + +class ReplacedByValue(ParentLabel): + pass -from adcp.types.generated_poc.core.geo_place_catalog_entry import ( - GeographicPlaceCatalogEntry, - ParentLabel, - ReplacedByValue, - Status, -) -# Explicit exports -__all__ = ["GeographicPlaceCatalogEntry", "ParentLabel", "ReplacedByValue", "Status"] +class GeographicPlaceCatalogEntry(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + value: Annotated[ + str, + Field( + description='Stable identifier in the response system and system_version.', min_length=1 + ), + ] + country: Annotated[str, Field(pattern='^[A-Z]{2}$')] + subdivision: Annotated[ + str | None, + Field( + description='ISO 3166-2 subdivision containing the place when the catalog has a subdivision mapping. Required by resolver semantics when the request constrained subdivision.', + pattern='^[A-Z]{2}-[A-Z0-9]{1,3}$', + ), + ] = None + place_type: geo_place_type.GeographicPlaceType + label: Annotated[ + str, + Field( + description='Human-readable display label; never an authoritative targeting key.', + min_length=1, + ), + ] + canonical_name: Annotated[ + str, + Field( + description='Required fully qualified display name suitable for distinguishing same-named results.', + min_length=1, + ), + ] + parent_labels: Annotated[ + list[ParentLabel], + Field( + description='Ordered human-readable parent hierarchy for disambiguation only. Must include at least the containing country.', + min_length=1, + ), + ] + status: Status + replaced_by_values: Annotated[ + list[ReplacedByValue] | None, + Field( + description='Replacement identifiers in the same system and response system_version.', + min_length=1, + ), + ] = None + valid_until: Annotated[ + AwareDatetime | None, + Field( + description='Known time after which the identifier must no longer be accepted for new targeting.' + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/geo_place_requirement.py b/src/adcp/types/domains/core/geo_place_requirement.py index e32628116..37c6239e6 100644 --- a/src/adcp/types/domains/core/geo_place_requirement.py +++ b/src/adcp/types/domains/core/geo_place_requirement.py @@ -1,22 +1,45 @@ -"""Types declared by the AdCP ``core/geo_place_requirement`` schema. +# generated by datamodel-codegen: +# filename: core/geo_place_requirement.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.geo_place_requirement import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field, StringConstraints -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import geo_place_system, geo_place_type + + +class SystemVersion(ScalarStr): + __slots__ = () + _constraints = {'min_length': 1} + + +class CatalogRequirement(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + countries: Annotated[ + dict[Annotated[str, StringConstraints(pattern=r'^[A-Z]{2}$')], list[geo_place_type.GeographicPlaceType]], + Field(min_length=1), + ] + system_versions: Annotated[ + list[SystemVersion] | None, + Field( + description='Optional exact catalog versions that must remain selectable. Versions are opaque strings, not ordered ranges.', + min_length=1, + ), + ] = None -from adcp.types.generated_poc.core.geo_place_requirement import ( - CatalogRequirement, - GeographicPlaceRequirement, - SystemVersion, -) -# Explicit exports -__all__ = ["CatalogRequirement", "GeographicPlaceRequirement", "SystemVersion"] +class GeographicPlaceRequirement(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + systems: Annotated[ + dict[geo_place_system.GeographicPlaceIdentifierSystem, CatalogRequirement], + Field(min_length=1), + ] diff --git a/src/adcp/types/domains/core/geo_place_resolver.py b/src/adcp/types/domains/core/geo_place_resolver.py index 203f7eb80..743e54b41 100644 --- a/src/adcp/types/domains/core/geo_place_resolver.py +++ b/src/adcp/types/domains/core/geo_place_resolver.py @@ -1,18 +1,38 @@ -"""Types declared by the AdCP ``core/geo_place_resolver`` schema. +# generated by datamodel-codegen: +# filename: core/geo_place_resolver.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.geo_place_resolver import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.geo_place_resolver import Auth, GeographicPlaceResolver +class Auth(StrEnum): + none = 'none' + seller_credentials = 'seller_credentials' + -# Explicit exports -__all__ = ["Auth", "GeographicPlaceResolver"] +class GeographicPlaceResolver(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + url: Annotated[ + AnyUrl, + Field( + description='HTTPS endpoint accepting the standard geo-place resolution query parameters.' + ), + ] + auth: Annotated[ + Auth, + Field( + description="Authentication mode. seller_credentials means use the same authorization credentials as the seller's AdCP endpoint and is valid only for a same-origin resolver URL; credentials MUST NOT be forwarded cross-origin." + ), + ] + protocol: Annotated[ + Literal['adcp_geo_place_resolver_v1'], + Field(description='Resolver request/response contract version.'), + ] = 'adcp_geo_place_resolver_v1' diff --git a/src/adcp/types/domains/core/geo_place_support.py b/src/adcp/types/domains/core/geo_place_support.py index 299b3c1c2..7293403ec 100644 --- a/src/adcp/types/domains/core/geo_place_support.py +++ b/src/adcp/types/domains/core/geo_place_support.py @@ -1,18 +1,26 @@ -"""Types declared by the AdCP ``core/geo_place_support`` schema. +# generated by datamodel-codegen: +# filename: core/geo_place_support.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.geo_place_support import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StringConstraints -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import geo_place_catalog_capability, geo_place_type -from adcp.types.generated_poc.core.geo_place_support import GeographicPlaceSystemSupport -# Explicit exports -__all__ = ["GeographicPlaceSystemSupport"] +class GeographicPlaceSystemSupport(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + countries: Annotated[ + dict[Annotated[str, StringConstraints(pattern=r'^[A-Z]{2}$')], list[geo_place_type.GeographicPlaceType]], + Field( + description='Supported place types keyed by ISO 3166-1 alpha-2 country. Only explicitly listed country/type pairs are supported.', + min_length=1, + ), + ] + catalog: geo_place_catalog_capability.GeographicPlaceCatalogCapability diff --git a/src/adcp/types/domains/core/geo_place_system.py b/src/adcp/types/domains/core/geo_place_system.py index 1d30e7935..5fba727fb 100644 --- a/src/adcp/types/domains/core/geo_place_system.py +++ b/src/adcp/types/domains/core/geo_place_system.py @@ -1,26 +1,47 @@ -"""Types declared by the AdCP ``core/geo_place_system`` schema. +# generated by datamodel-codegen: +# filename: core/geo_place_system.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.geo_place_system import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import AnyUrl, Field, RootModel + + +class GeographicPlaceIdentifierSystem1(StrEnum): + geonames = 'geonames' + google_ads = 'google_ads' + microsoft_ads = 'microsoft_ads' + + +class GeographicPlaceIdentifierSystem2(RootModel[AnyUrl]): + root: Annotated[ + AnyUrl, + Field( + description='Collision-safe identifier namespace for geographic places. Registered tokens have protocol-defined semantics. Unregistered systems MUST use an absolute HTTPS URI controlled by the catalog owner; consumers compare URI systems as exact opaque strings.', + examples=[ + 'geonames', + 'google_ads', + 'microsoft_ads', + 'https://seller.example/geo/catalogs/places', + ], + title='Geographic Place Identifier System', + ), + ] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.geo_place_system import ( - GeographicPlaceIdentifierSystem, - GeographicPlaceIdentifierSystem1, - GeographicPlaceIdentifierSystem2, -) - -# Explicit exports -__all__ = [ - "GeographicPlaceIdentifierSystem", - "GeographicPlaceIdentifierSystem1", - "GeographicPlaceIdentifierSystem2", +GeographicPlaceIdentifierSystem = Annotated[ + GeographicPlaceIdentifierSystem1 | GeographicPlaceIdentifierSystem2, + Field( + description='Collision-safe identifier namespace for geographic places. Registered tokens have protocol-defined semantics. Unregistered systems MUST use an absolute HTTPS URI controlled by the catalog owner; consumers compare URI systems as exact opaque strings.', + examples=[ + 'geonames', + 'google_ads', + 'microsoft_ads', + 'https://seller.example/geo/catalogs/places', + ], + title='Geographic Place Identifier System', + ), ] diff --git a/src/adcp/types/domains/core/geo_place_type.py b/src/adcp/types/domains/core/geo_place_type.py index 9e20baf17..3bc1f9929 100644 --- a/src/adcp/types/domains/core/geo_place_type.py +++ b/src/adcp/types/domains/core/geo_place_type.py @@ -1,22 +1,68 @@ -"""Types declared by the AdCP ``core/geo_place_type`` schema. +# generated by datamodel-codegen: +# filename: core/geo_place_type.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.geo_place_type import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import AnyUrl, Field, RootModel -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.geo_place_type import ( - GeographicPlaceType, - GeographicPlaceType1, - GeographicPlaceType2, -) +class GeographicPlaceType1(StrEnum): + airport = 'airport' + borough = 'borough' + city = 'city' + city_region = 'city_region' + commune = 'commune' + county = 'county' + district = 'district' + municipality = 'municipality' + neighborhood = 'neighborhood' + post_town = 'post_town' + prefecture = 'prefecture' + province = 'province' + quarter = 'quarter' + state = 'state' + territory = 'territory' + ward = 'ward' + + +class GeographicPlaceType2(RootModel[AnyUrl]): + root: Annotated[ + AnyUrl, + Field( + description='Canonical place classification. Registered tokens have protocol-defined meanings. Catalog-specific classifications without a registered mapping MUST use an absolute HTTPS URI controlled by the vocabulary owner; consumers compare URI types as exact opaque strings.', + examples=[ + 'city', + 'municipality', + 'borough', + 'neighborhood', + 'post_town', + 'city_region', + 'county', + 'https://seller.example/geo/place-types/trade-area', + ], + title='Geographic Place Type', + ), + ] + -# Explicit exports -__all__ = ["GeographicPlaceType", "GeographicPlaceType1", "GeographicPlaceType2"] +GeographicPlaceType = Annotated[ + GeographicPlaceType1 | GeographicPlaceType2, + Field( + description='Canonical place classification. Registered tokens have protocol-defined meanings. Catalog-specific classifications without a registered mapping MUST use an absolute HTTPS URI controlled by the vocabulary owner; consumers compare URI types as exact opaque strings.', + examples=[ + 'city', + 'municipality', + 'borough', + 'neighborhood', + 'post_town', + 'city_region', + 'county', + 'https://seller.example/geo/place-types/trade-area', + ], + title='Geographic Place Type', + ), +] diff --git a/src/adcp/types/domains/core/geo_region_requirement.py b/src/adcp/types/domains/core/geo_region_requirement.py index e5d0c3bf5..7e0b38671 100644 --- a/src/adcp/types/domains/core/geo_region_requirement.py +++ b/src/adcp/types/domains/core/geo_region_requirement.py @@ -1,23 +1,54 @@ -"""Types declared by the AdCP ``core/geo_region_requirement`` schema. +# generated by datamodel-codegen: +# filename: core/geo_region_requirement.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.geo_region_requirement import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field, StringConstraints -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.geo_region_requirement import ( - Countries, - Countries1, - GeographicRegionRequirement, - Value, -) +class Countries(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + all_values: Annotated[ + Literal[True], + Field( + description="Require every protocol-valid ISO 3166-2 subdivision active in the seller's returned support snapshot for this country to remain selectable." + ), + ] + + +class Value(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{2}-[A-Z0-9]{1,3}$'} + + +class Countries1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + values: Annotated[ + list[Value], + Field( + description='Exact finite set of ISO 3166-2 identifiers the buyer may select later.', + min_length=1, + ), + ] + -# Explicit exports -__all__ = ["Countries", "Countries1", "GeographicRegionRequirement", "Value"] +class GeographicRegionRequirement(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + countries: Annotated[ + dict[Annotated[str, StringConstraints(pattern=r'^[A-Z]{2}$')], Countries | Countries1], + Field( + description='Required ISO subdivision support keyed by ISO 3166-1 alpha-2 country.', + min_length=1, + ), + ] diff --git a/src/adcp/types/domains/core/geo_region_support.py b/src/adcp/types/domains/core/geo_region_support.py index c43390f5f..e43e5c62c 100644 --- a/src/adcp/types/domains/core/geo_region_support.py +++ b/src/adcp/types/domains/core/geo_region_support.py @@ -1,23 +1,84 @@ -"""Types declared by the AdCP ``core/geo_region_support`` schema. +# generated by datamodel-codegen: +# filename: core/geo_region_support.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.geo_region_support import +from datetime import date +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field, StringConstraints -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import ext as ext_1 + + +class Countries(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + all_values: Annotated[ + Literal[True], + Field( + description="Every protocol-valid ISO 3166-2 subdivision active in this declaration's support snapshot is selectable. Values introduced by a later catalog revision are not included automatically." + ), + ] + + +class Value(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{2}-[A-Z0-9]{1,3}$'} + + +class Countries3(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + values: Annotated[ + list[Value], + Field( + description='Exact finite selectable subset of ISO 3166-2 identifiers for this country.', + min_length=1, + ), + ] -from adcp.types.generated_poc.core.geo_region_support import ( - Countries, - Countries3, - GeographicRegionSupport, - Value, -) -# Explicit exports -__all__ = ["Countries", "Countries3", "GeographicRegionSupport", "Value"] +class GeographicRegionSupport(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + countries: Annotated[ + dict[Annotated[str, StringConstraints(pattern=r'^[A-Z]{2}$')], Countries | Countries3], + Field( + description='Selectable ISO subdivision values keyed by ISO 3166-1 alpha-2 country.', + min_length=1, + ), + ] + catalog_version: Annotated[ + str | None, + Field( + description='Optional opaque ISO subdivision catalog or seller-mapping version that identifies the support snapshot. It is provenance, not part of subdivision identity.', + min_length=1, + ), + ] = None + as_of: Annotated[ + date | None, + Field( + description='Optional date on which the seller last validated this support declaration.' + ), + ] = None + max_values_per_package: Annotated[ + SchemaInt | None, + Field( + description='Optional maximum number of region values selectable on one package.', ge=1 + ), + ] = None + max_packages: Annotated[ + SchemaInt | None, + Field( + description='Optional maximum number of independently region-targeted packages created from this configured product.', + ge=1, + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/get_geo_place_resolution_request.py b/src/adcp/types/domains/core/get_geo_place_resolution_request.py index 36af833d3..b2cb4aa68 100644 --- a/src/adcp/types/domains/core/get_geo_place_resolution_request.py +++ b/src/adcp/types/domains/core/get_geo_place_resolution_request.py @@ -1,20 +1,67 @@ -"""Types declared by the AdCP ``core/get_geo_place_resolution_request`` schema. +# generated by datamodel-codegen: +# filename: core/get_geo_place_resolution_request.json +# timestamp: 2026-10-04T01:14:11+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.get_geo_place_resolution_request import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, model_validator + +from . import geo_place_type, locale_tag -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.get_geo_place_resolution_request import ( - GetGeographicPlaceResolutionRequest, -) +class GetGeographicPlaceResolutionRequest(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + q: Annotated[ + str | None, + Field(description='Unresolved place name or alias supplied by the user.', min_length=1), + ] = None + value: Annotated[ + str | None, + Field( + description='Existing identifier to look up in the requested/current catalog version for lifecycle refresh or replacement discovery. Mutually exclusive with q.', + min_length=1, + ), + ] = None + country: Annotated[ + str, + Field( + description='ISO 3166-1 alpha-2 country code used to disambiguate the query.', + pattern='^[A-Z]{2}$', + ), + ] + subdivision: Annotated[ + str | None, + Field( + description='Optional ISO 3166-2 subdivision constraint.', + pattern='^[A-Z]{2}-[A-Z0-9]{1,3}$', + ), + ] = None + place_type: geo_place_type.GeographicPlaceType | None = None + system_version: Annotated[ + str | None, + Field(description='Optional exact supported catalog version to search.', min_length=1), + ] = None + locale: Annotated[ + locale_tag.LanguageTag | None, + Field(description='Optional BCP 47 language tag for returned labels.'), + ] = None + cursor: Annotated[str | None, Field(min_length=1)] = None + limit: Annotated[SchemaInt | None, Field(ge=1, le=100)] = 20 -# Explicit exports -__all__ = ["GetGeographicPlaceResolutionRequest"] + @model_validator(mode='after') + def _require_schema_required_group(self) -> GetGeographicPlaceResolutionRequest: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('q',), ('value',),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'GetGeographicPlaceResolutionRequest requires at least one of these field groups: q | value' + ) diff --git a/src/adcp/types/domains/core/get_geo_place_resolution_response.py b/src/adcp/types/domains/core/get_geo_place_resolution_response.py index e3be87d72..b1cef6059 100644 --- a/src/adcp/types/domains/core/get_geo_place_resolution_response.py +++ b/src/adcp/types/domains/core/get_geo_place_resolution_response.py @@ -1,20 +1,28 @@ -"""Types declared by the AdCP ``core/get_geo_place_resolution_response`` schema. +# generated by datamodel-codegen: +# filename: core/get_geo_place_resolution_response.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.get_geo_place_resolution_response import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import geo_place_catalog_entry, geo_place_system, get_geo_place_resolution_request -from adcp.types.generated_poc.core.get_geo_place_resolution_response import ( - GetGeographicPlaceResolutionResponse, -) -# Explicit exports -__all__ = ["GetGeographicPlaceResolutionResponse"] +class GetGeographicPlaceResolutionResponse(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + request: Annotated[ + get_geo_place_resolution_request.GetGeographicPlaceResolutionRequest, + Field( + description='Exact normalized request represented by this page. Pagination responses repeat the original query fields and may carry the page cursor.' + ), + ] + system: geo_place_system.GeographicPlaceIdentifierSystem + system_version: Annotated[str, Field(min_length=1)] + matches: list[geo_place_catalog_entry.GeographicPlaceCatalogEntry] + next_cursor: Annotated[str | None, Field(min_length=1)] = None diff --git a/src/adcp/types/domains/core/hotel_item.py b/src/adcp/types/domains/core/hotel_item.py index a3e6a2729..30f7a7e63 100644 --- a/src/adcp/types/domains/core/hotel_item.py +++ b/src/adcp/types/domains/core/hotel_item.py @@ -1,18 +1,122 @@ -"""Types declared by the AdCP ``core/hotel_item`` schema. +# generated by datamodel-codegen: +# filename: core/hotel_item.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.hotel_item import +from datetime import date +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AnyUrl, ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import ext as ext_1 +from . import offering_asset_group +from . import price as price_1 + + +class Location(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + lat: Annotated[ + StrictFloat, Field(description='Latitude in decimal degrees (WGS 84).', ge=-90.0, le=90.0) + ] + lng: Annotated[ + StrictFloat, + Field(description='Longitude in decimal degrees (WGS 84).', ge=-180.0, le=180.0), + ] + + +class Address(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + street: Annotated[str | None, Field(description='Street address.')] = None + city: Annotated[str | None, Field(description='City name.')] = None + region: Annotated[str | None, Field(description='State, province, or region.')] = None + postal_code: Annotated[str | None, Field(description='Postal or ZIP code.')] = None + country: Annotated[ + str | None, Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + ] = None -from adcp.types.generated_poc.core.hotel_item import Address, HotelItem, Location -# Explicit exports -__all__ = ["Address", "HotelItem", "Location"] +class HotelItem(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + hotel_id: Annotated[ + str, + Field( + description='Unique identifier for this property. Used to match remarketing events and inventory feeds to the correct hotel.' + ), + ] + name: Annotated[ + str, + Field(description="Property name (e.g., 'Grand Hotel Amsterdam', 'Seaside Resort & Spa')."), + ] + description: Annotated[ + str | None, Field(description='Property description highlighting features and location.') + ] = None + location: Annotated[Location, Field(description='Geographic coordinates of the property.')] + address: Annotated[ + Address | None, Field(description='Structured address for display and geocoding.') + ] = None + star_rating: Annotated[ + SchemaInt | None, Field(description='Official star rating (1–5).', ge=1, le=5) + ] = None + price: Annotated[ + price_1.Price | None, + Field(description="Nightly rate or starting price. Use period 'night' for nightly rates."), + ] = None + image_url: Annotated[AnyUrl | None, Field(description='Primary property image URL.')] = None + url: Annotated[AnyUrl | None, Field(description='Property landing page or booking URL.')] = None + phone: Annotated[str | None, Field(description='Property phone number in E.164 format.')] = None + amenities: Annotated[ + list[str] | None, + Field( + description="Property amenities (e.g., 'pool', 'wifi', 'spa', 'parking', 'restaurant').", + min_length=1, + ), + ] = None + check_in_time: Annotated[ + str | None, + Field( + description="Standard check-in time in HH:MM format (e.g., '15:00').", + pattern='^[0-2][0-9]:[0-5][0-9]$', + ), + ] = None + check_out_time: Annotated[ + str | None, + Field( + description="Standard check-out time in HH:MM format (e.g., '11:00').", + pattern='^[0-2][0-9]:[0-5][0-9]$', + ), + ] = None + tags: Annotated[ + list[str] | None, + Field( + description="Tags for filtering and targeting (e.g., 'boutique', 'family', 'business', 'luxury').", + min_length=1, + ), + ] = None + valid_from: Annotated[ + date | None, + Field( + description="Date from which this item is available or this rate applies (ISO 8601, e.g., '2025-03-01'). Used for seasonal availability windows in feed imports." + ), + ] = None + valid_to: Annotated[ + date | None, + Field( + description="Date until which this item is available or this rate applies (ISO 8601, e.g., '2025-09-30'). Used for seasonal availability windows in feed imports." + ), + ] = None + assets: Annotated[ + list[offering_asset_group.OfferingAssetGroup] | None, + Field( + description="Typed creative asset pools for this hotel. Uses the same OfferingAssetGroup structure as offering-type catalogs. Standard group IDs: 'images_landscape' (16:9 hero images), 'images_vertical' (9:16 for Snap, Stories), 'images_square' (1:1), 'logo'. Enables formats to declare typed image requirements that map unambiguously to the right asset regardless of platform.", + min_length=1, + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/iana_timezone.py b/src/adcp/types/domains/core/iana_timezone.py index 49d7274a5..314d05296 100644 --- a/src/adcp/types/domains/core/iana_timezone.py +++ b/src/adcp/types/domains/core/iana_timezone.py @@ -1,18 +1,20 @@ -"""Types declared by the AdCP ``core/iana_timezone`` schema. +# generated by datamodel-codegen: +# filename: core/iana_timezone.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.iana_timezone import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.iana_timezone import IanaTimezoneIdentifier +from adcp.types._scalar import ScalarStr + -# Explicit exports -__all__ = ["IanaTimezoneIdentifier"] +class IanaTimezoneIdentifier(ScalarStr): + __slots__ = () + _constraints = { + 'max_length': 255, + 'min_length': 1, + 'pattern': '^[A-Za-z0-9._+-]+(?:/[A-Za-z0-9._+-]+)*$', + } + _json_schema_extra = { + 'description': "Concrete timezone identifier in the implementation's supported IANA Time Zone Database, such as America/New_York, CET, or UTC.", + 'title': 'IANA Timezone Identifier', + } diff --git a/src/adcp/types/domains/core/identifier.py b/src/adcp/types/domains/core/identifier.py index 8bb5cb74c..afcd271d8 100644 --- a/src/adcp/types/domains/core/identifier.py +++ b/src/adcp/types/domains/core/identifier.py @@ -1,18 +1,27 @@ -"""Types declared by the AdCP ``core/identifier`` schema. +# generated by datamodel-codegen: +# filename: core/identifier.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.identifier import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import identifier_types -from adcp.types.generated_poc.core.identifier import Identifier -# Explicit exports -__all__ = ["Identifier"] +class Identifier(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Annotated[ + identifier_types.PropertyIdentifierTypes, Field(description='Type of identifier') + ] + value: Annotated[ + str, + Field( + description="The identifier value. For domain type: 'example.com' matches base domain plus www and m subdomains; 'edition.example.com' matches that specific subdomain; '*.example.com' matches ALL subdomains but NOT base domain" + ), + ] diff --git a/src/adcp/types/domains/core/impairment.py b/src/adcp/types/domains/core/impairment.py index 143a7f320..14d2e4a71 100644 --- a/src/adcp/types/domains/core/impairment.py +++ b/src/adcp/types/domains/core/impairment.py @@ -1,18 +1,102 @@ -"""Types declared by the AdCP ``core/impairment`` schema. +# generated by datamodel-codegen: +# filename: core/impairment.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.impairment import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import impairment_offline_state, impairment_reason_code + + +class ResourceType(StrEnum): + audience = 'audience' + creative = 'creative' + catalog_item = 'catalog_item' + event_source = 'event_source' + property = 'property' + + +class Transition(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + from_: Annotated[ + str | None, + Field( + alias='from', + description="Prior status of the resource (e.g., 'ready', 'approved', 'good'). Optional — sellers SHOULD include when known, MAY omit when the resource was discovered already in an offline state (e.g., a property depublished via brand.json crawl with no prior snapshot). Open string at the schema layer because each resource_type has its own serviceable-state vocabulary; the pattern constraint blocks free-form garbage, and the impairment.coherence assertion validates that 'from' is a known serviceable value for the resource_type.", + pattern='^[a-z][a-z0-9_]*$', + ), + ] = None + to: Annotated[ + impairment_offline_state.ImpairmentOfflineState, + Field( + description="Current (offline) status of the resource. Drawn from the resource_type's canonical lifecycle enum; see impairment-offline-state for per-value resource_type pairing. The pairing is validated by impairment.coherence." + ), + ] -from adcp.types.generated_poc.core.impairment import Impairment, ResourceType, Transition -# Explicit exports -__all__ = ["Impairment", "ResourceType", "Transition"] +class Impairment(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + impairment_id: Annotated[ + str, + Field( + description="Stable identifier for this impairment, used as the notification_id when the impairment fires via webhook. Stable across re-emissions of the same open impairment (e.g., the seller re-fires after the buyer's receiver was down) and across the closing fire that signals resolution. A new impairment for the same resource_id after closure receives a new impairment_id. Distinct from the per-fire idempotency_key issued at the webhook transport layer — see snapshot-and-log Rule 1. Receivers correlate webhook fires to current impairments[] state by impairment_id; receivers suppress duplicate transport-layer retries by idempotency_key. Seeing the same impairment_id with different idempotency_keys is a re-emission signal, not a retry — the buyer should treat it as a notice that something may have been missed." + ), + ] + resource_type: Annotated[ + ResourceType, + Field( + description="The kind of upstream dependency that transitioned to an offline state. Values are drawn from the x-entity vocabulary (see core/x-entity-types.json) and identify a buyer-referenced object with its own lifecycle that the seller can take offline. This is the subset of x-entity types for which a media buy's serving depends on the resource — not a new typology, just the impairment-relevant slice." + ), + ] + resource_id: Annotated[ + str, + Field( + description="Seller's identifier for the specific resource that transitioned. References the same id space as the corresponding sync_/list_ task responses (e.g., audience_id, creative_id)." + ), + ] + package_ids: Annotated[ + list[str], + Field( + description="Packages within this media buy whose delivery is degraded by the impairment. MUST list at least one package — cosmetic effects that do not degrade any package's ability to serve MUST NOT be reported as impairments.", + min_length=1, + ), + ] + transition: Annotated[ + Transition, + Field(description='The resource-level status transition that triggered this impairment.'), + ] + reason_code: Annotated[ + impairment_reason_code.ImpairmentReasonCode, + Field( + description='Categorical reason for the offline transition. Drives buyer-side remediation logic.' + ), + ] + reason: Annotated[ + str | None, + Field( + description='Human-readable explanation. Supplements reason_code with seller-specific detail.', + max_length=500, + ), + ] = None + observed_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the seller observed the resource transition to its offline state.' + ), + ] + remediation: Annotated[ + str | None, + Field( + description='Action the buyer can take to clear the impairment, if any. Free text. Absent when no buyer-side remediation is possible (e.g., seller-initiated withdrawal pending re-publication).', + max_length=500, + ), + ] = None diff --git a/src/adcp/types/domains/core/indicator.py b/src/adcp/types/domains/core/indicator.py index 6711a5c79..03c30f6a0 100644 --- a/src/adcp/types/domains/core/indicator.py +++ b/src/adcp/types/domains/core/indicator.py @@ -1,18 +1,40 @@ -"""Types declared by the AdCP ``core/indicator`` schema. +# generated by datamodel-codegen: +# filename: core/indicator.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.indicator import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import indicator_type +from . import ext as ext_1 +from . import indicator_scope -from adcp.types.generated_poc.core.indicator import Indicator -# Explicit exports -__all__ = ["Indicator"] +class Indicator(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + type: indicator_type.IndicatorType + detected_at: Annotated[ + AwareDatetime | None, + Field( + description='When the seller first detected the current uninterrupted occurrence of this indicator. Keep this value stable while the condition remains present. If the condition clears and is later detected again, use the new detection time. Optional because some upstream platforms expose current assessments without an original detection timestamp.' + ), + ] = None + scope: Annotated[ + list[indicator_scope.IndicatorScope] | None, + Field( + description='Optional narrower publisher or placement scope within the enclosing media buy, package, or package–creative assignment. Omit only when the seller evaluated and asserts the indicator across the whole enclosing resource/relationship. When partial indicators_evaluated_scope is declared, every returned indicator MUST include scope and every entry MUST fall within that coverage. This is scope, not source: the responding seller remains the source.', + min_length=1, + ), + ] = None + ext: Annotated[ + ext_1.ExtensionObject | None, + Field( + description='Seller- or provider-specific detail such as scores, thresholds, evaluation windows, methodology identifiers, or upstream attribution. Core consumers must not need ext to understand the broad meaning of type.' + ), + ] = None diff --git a/src/adcp/types/domains/core/indicator_bearing.py b/src/adcp/types/domains/core/indicator_bearing.py index b669fc46a..3d5b17295 100644 --- a/src/adcp/types/domains/core/indicator_bearing.py +++ b/src/adcp/types/domains/core/indicator_bearing.py @@ -1,18 +1,45 @@ -"""Types declared by the AdCP ``core/indicator_bearing`` schema. +# generated by datamodel-codegen: +# filename: core/indicator_bearing.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.indicator_bearing import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import indicator_type +from . import indicator, indicator_scope -from adcp.types.generated_poc.core.indicator_bearing import IndicatorBearingResourceState -# Explicit exports -__all__ = ["IndicatorBearingResourceState"] +class IndicatorBearingResourceState(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + indicators: Annotated[ + list[indicator.Indicator] | None, + Field( + description='Current seller assertions for the indicator types and publisher/placement coverage named by the sibling evaluation fields. Omitted means unknown or not evaluated. A present empty array means evaluated with no current assertion for indicator_types_evaluated in the evaluated scope.' + ), + ] = None + indicator_types_evaluated: Annotated[ + list[indicator_type.IndicatorType] | None, + Field( + description='Indicator types covered by this snapshot. Required whenever indicators is present. Types omitted from this list remain unknown even when indicators is empty. Every returned indicator.type MUST appear in this list.', + min_length=1, + ), + ] = None + indicators_as_of: Annotated[ + AwareDatetime | None, + Field( + description='When the seller completed this evaluation. Required whenever indicators is present, including an empty array. State changes require a strictly newer timestamp; equal-timestamp conflicts are invalid and buyers retain stored state.' + ), + ] = None + indicators_evaluated_scope: Annotated[ + list[indicator_scope.IndicatorScope] | None, + Field( + description='Optional publisher or placement coverage for this evaluation. Omit when the named indicator types were evaluated across the whole enclosing resource or relationship. Unlisted scopes remain unknown. When present, every returned indicator MUST include scope and every scope entry MUST be contained by this coverage.', + min_length=1, + ), + ] = None diff --git a/src/adcp/types/domains/core/indicator_scope.py b/src/adcp/types/domains/core/indicator_scope.py index cc2dd1877..4d13d271b 100644 --- a/src/adcp/types/domains/core/indicator_scope.py +++ b/src/adcp/types/domains/core/indicator_scope.py @@ -1,18 +1,29 @@ -"""Types declared by the AdCP ``core/indicator_scope`` schema. +# generated by datamodel-codegen: +# filename: core/indicator_scope.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.indicator_scope import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -from adcp.types.generated_poc.core.indicator_scope import IndicatorScope -# Explicit exports -__all__ = ["IndicatorScope"] +class IndicatorScope(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + publisher_domain: Annotated[ + str, + Field( + description="Domain where the publisher's adagents.json is hosted.", + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] + placement_id: Annotated[ + str | None, + Field( + description='Optional placement ID within publisher_domain. Omit to scope the assertion or evaluation to all delivery for the publisher in the enclosing object.' + ), + ] = None diff --git a/src/adcp/types/domains/core/indicators_changed_webhook.py b/src/adcp/types/domains/core/indicators_changed_webhook.py index 8895c95b7..9f5a27f27 100644 --- a/src/adcp/types/domains/core/indicators_changed_webhook.py +++ b/src/adcp/types/domains/core/indicators_changed_webhook.py @@ -1,22 +1,82 @@ -"""Types declared by the AdCP ``core/indicators_changed_webhook`` schema. +# generated by datamodel-codegen: +# filename: core/indicators_changed_webhook.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.indicators_changed_webhook import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import indicator_type +from . import ext as ext_1 + + +class RelationshipKind(StrEnum): + media_buy = 'media_buy' + package = 'package' + creative_assignment = 'creative_assignment' + + +class ChangeKind(StrEnum): + asserted = 'asserted' + cleared = 'cleared' + updated = 'updated' + invalidated = 'invalidated' + coverage_changed = 'coverage_changed' + assignment_removed = 'assignment_removed' -from adcp.types.generated_poc.core.indicators_changed_webhook import ( - ChangeKind, - IndicatorsChangedWebhook, - RelationshipKind, -) -# Explicit exports -__all__ = ["ChangeKind", "IndicatorsChangedWebhook", "RelationshipKind"] +class IndicatorsChangedWebhook(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + idempotency_key: Annotated[ + str, + Field( + description='Random fire identifier reused across retries. Receivers dedupe within the authenticated sender scope.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + notification_id: Annotated[ + str, + Field( + description='Stable identity for this logical snapshot change across re-emissions. A later semantic change receives a new value.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] + notification_type: Literal['indicators.changed'] = 'indicators.changed' + fired_at: AwareDatetime + subscriber_id: Annotated[ + str, Field(max_length=64, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,64}$') + ] + account_id: str + relationship_kind: Annotated[ + RelationshipKind, Field(description='Identifies which indicator-bearing snapshot changed.') + ] + media_buy_id: str + package_id: str | None = None + creative_id: str | None = None + change_kind: Annotated[ + ChangeKind, + Field( + description='Coarse invalidation reason. Buyers MUST reread rather than applying this value as state. invalidated is emitted when a material creative-content update retires the prior evaluation and the relationship becomes unknown until reevaluated. assignment_removed is emitted to indicator subscribers when deletion retires keys for a package–creative relationship.' + ), + ] + changed_indicator_types: Annotated[ + list[indicator_type.IndicatorType], + Field( + description='Types known to be affected. This is a reread hint, not a complete current set.', + min_length=1, + ), + ] + observed_at: Annotated[ + AwareDatetime, Field(description='When the seller observed the semantic snapshot change.') + ] + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/industry_identifier.py b/src/adcp/types/domains/core/industry_identifier.py index 096e519ce..4f49dc3ff 100644 --- a/src/adcp/types/domains/core/industry_identifier.py +++ b/src/adcp/types/domains/core/industry_identifier.py @@ -1,18 +1,26 @@ -"""Types declared by the AdCP ``core/industry_identifier`` schema. +# generated by datamodel-codegen: +# filename: core/industry_identifier.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.industry_identifier import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import creative_identifier_type -from adcp.types.generated_poc.core.industry_identifier import IndustryIdentifier -# Explicit exports -__all__ = ["IndustryIdentifier"] +class IndustryIdentifier(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + type: creative_identifier_type.CreativeIdentifierType + value: Annotated[ + str, + Field( + description="The identifier value (e.g., 'ABCD1234000H' for Ad-ID). Preserve the value exactly as the traffic or clearance system expects it.", + max_length=64, + ), + ] diff --git a/src/adcp/types/domains/core/insertion_order.py b/src/adcp/types/domains/core/insertion_order.py index 58c48d092..3829890d7 100644 --- a/src/adcp/types/domains/core/insertion_order.py +++ b/src/adcp/types/domains/core/insertion_order.py @@ -1,23 +1,79 @@ -"""Types declared by the AdCP ``core/insertion_order`` schema. +# generated by datamodel-codegen: +# filename: core/insertion_order.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.insertion_order import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.insertion_order import ( - InsertionOrder, - PaymentTerms, - Terms, - TotalBudget, -) +class TotalBudget(AdCPBaseModel): + amount: Annotated[StrictFloat, Field(ge=0.0)] + currency: Annotated[ + str, Field(description='ISO 4217 currency code', max_length=3, min_length=3) + ] + + +class PaymentTerms(StrEnum): + net_30 = 'net_30' + net_60 = 'net_60' + net_90 = 'net_90' + prepaid = 'prepaid' + due_on_receipt = 'due_on_receipt' + + +class Terms(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + advertiser: Annotated[ + str | None, Field(description='Advertiser name or identifier', max_length=500) + ] = None + publisher: Annotated[ + str | None, Field(description='Publisher name or identifier', max_length=500) + ] = None + total_budget: Annotated[TotalBudget | None, Field(description='Total committed budget')] = None + flight_start: Annotated[AwareDatetime | None, Field(description='Campaign start date')] = None + flight_end: Annotated[AwareDatetime | None, Field(description='Campaign end date')] = None + payment_terms: Annotated[PaymentTerms | None, Field(description='Payment terms')] = None + -# Explicit exports -__all__ = ["InsertionOrder", "PaymentTerms", "Terms", "TotalBudget"] +class InsertionOrder(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + io_id: Annotated[ + str, + Field( + description='Unique identifier for this insertion order. Referenced by io_acceptance on create_media_buy.', + max_length=255, + ), + ] + terms: Annotated[ + Terms | None, + Field( + description='Summary fields echoed from the committed proposal for agent verification. Buyer agents use these to confirm the IO matches what was negotiated before a human signs. These are read-only summaries, not negotiation surfaces — deal terms live on products and packages.' + ), + ] = None + terms_url: Annotated[ + AnyUrl | None, + Field( + description='URL to a human-readable document containing the full insertion order terms' + ), + ] = None + signing_url: Annotated[ + AnyUrl | None, + Field( + description='URL to an electronic signing service (e.g., DocuSign) for human signature workflows. When present, a human must sign before the buyer agent can proceed with create_media_buy.' + ), + ] = None + requires_signature: Annotated[ + StrictBool, + Field( + description='Whether the buyer must accept this IO before creating a media buy. When true, create_media_buy requires an io_acceptance referencing this io_id.' + ), + ] diff --git a/src/adcp/types/domains/core/installment.py b/src/adcp/types/domains/core/installment.py index 6951748db..6a5ccfe09 100644 --- a/src/adcp/types/domains/core/installment.py +++ b/src/adcp/types/domains/core/installment.py @@ -1,18 +1,123 @@ -"""Types declared by the AdCP ``core/installment`` schema. +# generated by datamodel-codegen: +# filename: core/installment.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.installment import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AwareDatetime, ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import derivative_type, installment_status +from . import ad_inventory_config +from . import collection_ref as collection_ref_1 +from . import content_rating as content_rating_1 +from . import ext as ext_1 +from . import installment_deadlines +from . import special as special_1 +from . import talent + + +class DerivativeOf(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + installment_id: Annotated[ + str, Field(description='The source installment this content is derived from') + ] + type: Annotated[ + derivative_type.DerivativeType, Field(description='What kind of derivative content this is') + ] -from adcp.types.generated_poc.core.installment import DerivativeOf, Installment -# Explicit exports -__all__ = ["DerivativeOf", "Installment"] +class Installment(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + installment_id: Annotated[ + str, Field(description='Unique identifier for this installment within the collection') + ] + collection_ref: Annotated[ + collection_ref_1.CollectionReference | None, + Field( + description='Canonical parent collection identity. Products spanning multiple collections or publisher namespaces MUST use this field.' + ), + ] = None + collection_id: Annotated[ + str | None, + Field( + deprecated=True, + description='Deprecated publisher-domain-free parent collection shorthand. Use collection_ref. It is unambiguous only when the enclosing product addresses one publisher namespace.', + ), + ] = None + name: Annotated[str | None, Field(description='Installment title')] = None + season: Annotated[ + str | None, Field(description="Season identifier (e.g., '1', '2024', 'spring_2026')") + ] = None + installment_number: Annotated[ + str | None, Field(description="Installment number within the season (e.g., '3', '47')") + ] = None + scheduled_at: Annotated[ + AwareDatetime | None, Field(description='When the installment airs or publishes (ISO 8601)') + ] = None + status: Annotated[ + installment_status.InstallmentStatus | None, + Field(description='Lifecycle status of the installment'), + ] = None + duration_seconds: Annotated[ + SchemaInt | None, Field(description='Expected duration of the installment in seconds', ge=0) + ] = None + flexible_end: Annotated[ + StrictBool | None, + Field(description='Whether the end time is approximate (live events, sports)'), + ] = None + valid_until: Annotated[ + AwareDatetime | None, + Field( + description='When this installment data expires and should be re-queried. Agents should re-query before committing budget to products with tentative installments.' + ), + ] = None + content_rating: Annotated[ + content_rating_1.ContentRating | None, + Field( + description="Installment-specific content rating. Overrides the collection's baseline content_rating when present." + ), + ] = None + topics: Annotated[ + list[str] | None, + Field( + description="Content topics for this installment. Uses the same taxonomy as the collection's genre_taxonomy when present. Enables installment-level brand safety evaluation beyond content_rating." + ), + ] = None + special: Annotated[ + special_1.Special | None, + Field( + description='Installment-specific event context. When present, this installment is anchored to a real-world event. Overrides the collection-level special when present.' + ), + ] = None + guest_talent: Annotated[ + list[talent.Talent] | None, + Field( + description="Installment-specific guests and talent. Additive to the collection's recurring talent." + ), + ] = None + ad_inventory: Annotated[ + ad_inventory_config.AdInventoryConfiguration | None, + Field( + description='Break-based ad inventory for this installment. For non-break formats (host reads, integrations), use product placements.' + ), + ] = None + deadlines: Annotated[ + installment_deadlines.InstallmentDeadlines | None, + Field( + description='Booking, cancellation, and material submission deadlines for this installment. Present when the installment has time-sensitive inventory that requires advance commitment or material delivery.' + ), + ] = None + derivative_of: Annotated[ + DerivativeOf | None, + Field( + description='When this installment is a clip, highlight, or recap derived from a full installment. The source installment_id must reference an installment within the same response.' + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/installment_deadlines.py b/src/adcp/types/domains/core/installment_deadlines.py index 5515b56c7..98619ea9a 100644 --- a/src/adcp/types/domains/core/installment_deadlines.py +++ b/src/adcp/types/domains/core/installment_deadlines.py @@ -1,18 +1,37 @@ -"""Types declared by the AdCP ``core/installment_deadlines`` schema. +# generated by datamodel-codegen: +# filename: core/installment_deadlines.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.installment_deadlines import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import material_deadline -from adcp.types.generated_poc.core.installment_deadlines import InstallmentDeadlines -# Explicit exports -__all__ = ["InstallmentDeadlines"] +class InstallmentDeadlines(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + booking_deadline: Annotated[ + AwareDatetime | None, + Field( + description='Last date/time to book a placement in this installment (ISO 8601). After this point, the seller will not accept new bookings.' + ), + ] = None + cancellation_deadline: Annotated[ + AwareDatetime | None, + Field( + description="Last date/time to cancel without penalty (ISO 8601). Cancellations after this point may incur fees per the seller's terms." + ), + ] = None + material_deadlines: Annotated[ + list[material_deadline.MaterialDeadline] | None, + Field( + description="Stages for creative material submission. Items MUST be in chronological order by due_at (earliest first). Typical pattern: 'draft' for raw materials the seller will process, 'final' for production-ready assets. Print example: draft artwork then press-ready PDF. Influencer example: talking points then approved script.", + min_length=1, + ), + ] = None diff --git a/src/adcp/types/domains/core/installment_delivery_metrics.py b/src/adcp/types/domains/core/installment_delivery_metrics.py index 8827f4deb..f8a824060 100644 --- a/src/adcp/types/domains/core/installment_delivery_metrics.py +++ b/src/adcp/types/domains/core/installment_delivery_metrics.py @@ -1,18 +1,30 @@ -"""Types declared by the AdCP ``core/installment_delivery_metrics`` schema. +# generated by datamodel-codegen: +# filename: core/installment_delivery_metrics.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.installment_delivery_metrics import +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import AwareDatetime, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import installment_ref as installment_ref_1 +from .delivery_metrics import DeliveryMetrics -from adcp.types.generated_poc.core.installment_delivery_metrics import InstallmentDeliveryMetrics -# Explicit exports -__all__ = ["InstallmentDeliveryMetrics"] +class InstallmentDeliveryMetrics(DeliveryMetrics): + installment_ref: installment_ref_1.InstallmentReference + installment_name: Annotated[ + str | None, + Field( + description='Current human-readable installment name. Convenience metadata only; installment_ref is stable identity.' + ), + ] = None + scheduled_at: Annotated[ + AwareDatetime | None, + Field( + description="Convenience echo of the installment's scheduled publication or air time." + ), + ] = None + impressions: Any + spend: Any diff --git a/src/adcp/types/domains/core/installment_property_delivery_metrics.py b/src/adcp/types/domains/core/installment_property_delivery_metrics.py index adcb868f1..483577736 100644 --- a/src/adcp/types/domains/core/installment_property_delivery_metrics.py +++ b/src/adcp/types/domains/core/installment_property_delivery_metrics.py @@ -1,20 +1,51 @@ -"""Types declared by the AdCP ``core/installment_property_delivery_metrics`` schema. +# generated by datamodel-codegen: +# filename: core/installment_property_delivery_metrics.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.installment_property_delivery_metrics import +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import AwareDatetime, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import identifier as identifier_1 +from . import installment_ref as installment_ref_1 +from . import property_ref as property_ref_1 +from .delivery_metrics import DeliveryMetrics -from adcp.types.generated_poc.core.installment_property_delivery_metrics import ( - InstallmentPropertyDeliveryMetrics, -) -# Explicit exports -__all__ = ["InstallmentPropertyDeliveryMetrics"] +class InstallmentPropertyDeliveryMetrics(DeliveryMetrics): + installment_ref: installment_ref_1.InstallmentReference + installment_name: Annotated[ + str | None, + Field(description='Current human-readable installment name. Convenience metadata only.'), + ] = None + scheduled_at: Annotated[ + AwareDatetime | None, + Field( + description="Convenience echo of the installment's scheduled publication or air time." + ), + ] = None + publisher_domain: Annotated[ + str, + Field( + description='Publisher or platform authority that namespaces the property identifier, including for an unregistered surface.', + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] + identifier: Annotated[ + identifier_1.Identifier, + Field(description='Operational identity of the property that delivered the installment.'), + ] + property_ref: Annotated[ + property_ref_1.PropertyReference | None, + Field( + description="Canonical publisher-scoped catalog identity when available. Its publisher_domain MUST equal the row's publisher_domain." + ), + ] = None + property_name: Annotated[ + str | None, + Field(description='Current human-readable property name. Convenience metadata only.'), + ] = None + impressions: Any + spend: Any diff --git a/src/adcp/types/domains/core/installment_ref.py b/src/adcp/types/domains/core/installment_ref.py index 3a9efde2b..528203dc1 100644 --- a/src/adcp/types/domains/core/installment_ref.py +++ b/src/adcp/types/domains/core/installment_ref.py @@ -1,18 +1,22 @@ -"""Types declared by the AdCP ``core/installment_ref`` schema. +# generated by datamodel-codegen: +# filename: core/installment_ref.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.installment_ref import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import collection_ref as collection_ref_1 -from adcp.types.generated_poc.core.installment_ref import InstallmentReference -# Explicit exports -__all__ = ["InstallmentReference"] +class InstallmentReference(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + collection_ref: collection_ref_1.CollectionReference + installment_id: Annotated[ + str, Field(description='Installment ID within the referenced collection.', min_length=1) + ] diff --git a/src/adcp/types/domains/core/inventory_list_application.py b/src/adcp/types/domains/core/inventory_list_application.py index 8643e16f9..0224d0156 100644 --- a/src/adcp/types/domains/core/inventory_list_application.py +++ b/src/adcp/types/domains/core/inventory_list_application.py @@ -1,32 +1,148 @@ -"""Types declared by the AdCP ``core/inventory_list_application`` schema. +# generated by datamodel-codegen: +# filename: core/inventory_list_application.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.inventory_list_application import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field + + +class Effect(StrEnum): + include = 'include' + exclude = 'exclude' + + +class Summary(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + matched: Annotated[ + SchemaInt, + Field( + description='Resolved entries that matched at least one property in this product.', ge=0 + ), + ] + unmatched: Annotated[ + SchemaInt, + Field(description='Resolved entries that did not match a property in this product.', ge=0), + ] + + +class InventoryListApplication1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + list_type: Annotated[ + Literal['property'], Field(description='The receipt describes a property list.') + ] = 'property' + effect: Annotated[ + Effect, + Field( + description='Whether matching properties were retained as an allowlist or removed as a blocklist.' + ), + ] + agent_url: Annotated[ + AnyUrl, Field(description='Agent URL from the effective property-list targeting reference.') + ] + list_id: Annotated[ + str, + Field( + description='Identifier from the effective property-list targeting reference.', + min_length=1, + ), + ] + resolved_at: Annotated[ + AwareDatetime, + Field( + description='Timestamp identifying the complete resolved-list snapshot the seller evaluated. If the list response supplied resolved_at, the seller MUST copy it; otherwise the seller records when it completed assembling the snapshot, including all fetched pages.' + ), + ] + evaluated_at: Annotated[ + AwareDatetime, + Field( + description="When the seller intersected that snapshot with this product's then-current inventory." + ), + ] + summary: Annotated[ + Summary, + Field( + description="Partition of every entry in the resolved list snapshot against this product's common pre-list inventory baseline." + ), + ] + + +class Summary2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + matched: Annotated[ + SchemaInt, + Field( + description='Resolved entries that matched at least one collection in this product.', + ge=0, + ), + ] + unmatched: Annotated[ + SchemaInt, + Field( + description='Resolved entries that did not match a collection in this product.', ge=0 + ), + ] + + +class InventoryListApplication2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + list_type: Annotated[ + Literal['collection'], Field(description='The receipt describes a collection list.') + ] = 'collection' + effect: Annotated[ + Effect, + Field( + description='Whether matching collections were retained as an allowlist or removed as a blocklist.' + ), + ] + agent_url: Annotated[ + AnyUrl, + Field(description='Agent URL from the effective collection-list targeting reference.'), + ] + list_id: Annotated[ + str, + Field( + description='Identifier from the effective collection-list targeting reference.', + min_length=1, + ), + ] + resolved_at: Annotated[ + AwareDatetime, + Field( + description='Timestamp identifying the complete resolved-list snapshot the seller evaluated. If the list response supplied resolved_at, the seller MUST copy it; otherwise the seller records when it completed assembling the snapshot, including all fetched pages.' + ), + ] + evaluated_at: Annotated[ + AwareDatetime, + Field( + description="When the seller intersected that snapshot with this product's then-current inventory." + ), + ] + summary: Annotated[ + Summary2, + Field( + description="Partition of every entry in the resolved list snapshot against this product's common pre-list inventory baseline." + ), + ] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.inventory_list_application import ( - Effect, - InventoryListApplication, - InventoryListApplication1, - InventoryListApplication2, - Summary, - Summary2, -) - -# Explicit exports -__all__ = [ - "Effect", - "InventoryListApplication", - "InventoryListApplication1", - "InventoryListApplication2", - "Summary", - "Summary2", +InventoryListApplication = Annotated[ + InventoryListApplication1 | InventoryListApplication2, + Field( + description='Seller-issued, product-scoped receipt showing how one effective property- or collection-list targeting reference intersected the inventory represented by a returned product. Each receipt compares its resolved entries independently against the same product inventory before any effective list is applied; product pricing and forecasts reflect the eligible inventory after all effective lists are composed. Sellers MUST NOT echo auth_token or other separately supplied fetch credentials; the closed receipt shape defines no credential field.', + discriminator='list_type', + title='Inventory List Application', + ), ] diff --git a/src/adcp/types/domains/core/job_item.py b/src/adcp/types/domains/core/job_item.py index d9c23022c..26139ed2f 100644 --- a/src/adcp/types/domains/core/job_item.py +++ b/src/adcp/types/domains/core/job_item.py @@ -1,24 +1,115 @@ -"""Types declared by the AdCP ``core/job_item`` schema. +# generated by datamodel-codegen: +# filename: core/job_item.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.job_item import +from datetime import date +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import ext as ext_1 +from . import offering_asset_group + + +class EmploymentType(StrEnum): + full_time = 'full_time' + part_time = 'part_time' + contract = 'contract' + temporary = 'temporary' + internship = 'internship' + freelance = 'freelance' + + +class ExperienceLevel(StrEnum): + entry_level = 'entry_level' + mid_level = 'mid_level' + senior = 'senior' + director = 'director' + executive = 'executive' + + +class Period(StrEnum): + hour = 'hour' + month = 'month' + year = 'year' + + +class Salary(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + min: Annotated[StrictFloat | None, Field(description='Minimum salary.', ge=0.0)] = None + max: Annotated[StrictFloat | None, Field(description='Maximum salary.', ge=0.0)] = None + currency: Annotated[str, Field(description='ISO 4217 currency code.', pattern='^[A-Z]{3}$')] + period: Annotated[Period, Field(description='Pay period.')] -from adcp.types.generated_poc.core.job_item import ( - EmploymentType, - ExperienceLevel, - JobItem, - Period, - Salary, -) -# Explicit exports -__all__ = ["EmploymentType", "ExperienceLevel", "JobItem", "Period", "Salary"] +class JobItem(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + job_id: Annotated[str, Field(description='Unique identifier for this job posting.')] + title: Annotated[ + str, Field(description="Job title (e.g., 'Senior Software Engineer', 'Marketing Manager').") + ] + company_name: Annotated[str, Field(description='Hiring company or organization name.')] + description: Annotated[ + str, + Field(description='Full job description including responsibilities and qualifications.'), + ] + location: Annotated[ + str | None, + Field( + description="Job location as a display string (e.g., 'Amsterdam, NL', 'Remote', 'New York, NY'). Use 'Remote' for fully remote positions." + ), + ] = None + employment_type: Annotated[EmploymentType | None, Field(description='Type of employment.')] = ( + None + ) + experience_level: Annotated[ + ExperienceLevel | None, Field(description='Required experience level.') + ] = None + salary: Annotated[ + Salary | None, + Field(description='Salary range. Specify min and/or max with currency and period.'), + ] = None + date_posted: Annotated[ + date | None, Field(description='Date the job was posted (ISO 8601 date).') + ] = None + valid_through: Annotated[ + date | None, Field(description='Application deadline (ISO 8601 date).') + ] = None + apply_url: Annotated[AnyUrl | None, Field(description='Direct application URL.')] = None + job_functions: Annotated[ + list[str] | None, + Field( + description="Job function categories (e.g., 'engineering', 'marketing', 'sales', 'finance').", + min_length=1, + ), + ] = None + industries: Annotated[ + list[str] | None, + Field( + description="Industry classifications (e.g., 'technology', 'healthcare', 'retail').", + min_length=1, + ), + ] = None + tags: Annotated[ + list[str] | None, + Field( + description="Tags for filtering (e.g., 'remote', 'visa-sponsorship', 'equity').", + min_length=1, + ), + ] = None + assets: Annotated[ + list[offering_asset_group.OfferingAssetGroup] | None, + Field( + description="Typed creative asset pools for this job. Uses the same OfferingAssetGroup structure as offering-type catalogs. Standard group IDs: 'images_landscape' (company/role hero), 'images_vertical' (9:16 for Stories), 'logo' (company logo). Enables formats to declare typed image requirements that map unambiguously to the right asset regardless of platform.", + min_length=1, + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/keyword_delivery_metrics.py b/src/adcp/types/domains/core/keyword_delivery_metrics.py index e7a053cc8..438c676f6 100644 --- a/src/adcp/types/domains/core/keyword_delivery_metrics.py +++ b/src/adcp/types/domains/core/keyword_delivery_metrics.py @@ -1,18 +1,19 @@ -"""Types declared by the AdCP ``core/keyword_delivery_metrics`` schema. +# generated by datamodel-codegen: +# filename: core/keyword_delivery_metrics.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.keyword_delivery_metrics import +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import match_type as match_type_1 +from .delivery_metrics import DeliveryMetrics -from adcp.types.generated_poc.core.keyword_delivery_metrics import KeywordDeliveryMetrics -# Explicit exports -__all__ = ["KeywordDeliveryMetrics"] +class KeywordDeliveryMetrics(DeliveryMetrics): + keyword: Annotated[str, Field(description='The targeted keyword')] + match_type: match_type_1.MatchType + impressions: Any + spend: Any diff --git a/src/adcp/types/domains/core/keyword_target.py b/src/adcp/types/domains/core/keyword_target.py index 56f66b0c0..bd0f95b11 100644 --- a/src/adcp/types/domains/core/keyword_target.py +++ b/src/adcp/types/domains/core/keyword_target.py @@ -1,18 +1,21 @@ -"""Types declared by the AdCP ``core/keyword_target`` schema. +# generated by datamodel-codegen: +# filename: core/keyword_target.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.keyword_target import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import match_type as match_type_1 -from adcp.types.generated_poc.core.keyword_target import KeywordTarget -# Explicit exports -__all__ = ["KeywordTarget"] +class KeywordTarget(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + keyword: Annotated[str, Field(min_length=1)] + match_type: match_type_1.MatchType + bid_price: Annotated[StrictFloat | None, Field(ge=0.0)] = None diff --git a/src/adcp/types/domains/core/limited_series.py b/src/adcp/types/domains/core/limited_series.py index dfeec0077..b89c41294 100644 --- a/src/adcp/types/domains/core/limited_series.py +++ b/src/adcp/types/domains/core/limited_series.py @@ -1,18 +1,25 @@ -"""Types declared by the AdCP ``core/limited_series`` schema. +# generated by datamodel-codegen: +# filename: core/limited_series.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.limited_series import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AwareDatetime, ConfigDict, Field -from adcp.types.generated_poc.core.limited_series import LimitedSeries -# Explicit exports -__all__ = ["LimitedSeries"] +class LimitedSeries(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + total_installments: Annotated[ + SchemaInt, Field(description='Planned number of installments in the series', ge=1) + ] + starts: Annotated[ + AwareDatetime | None, Field(description='When the series begins (ISO 8601)') + ] = None + ends: Annotated[AwareDatetime | None, Field(description='When the series ends (ISO 8601)')] = ( + None + ) diff --git a/src/adcp/types/domains/core/locale_tag.py b/src/adcp/types/domains/core/locale_tag.py index aa7e796c0..587349fcd 100644 --- a/src/adcp/types/domains/core/locale_tag.py +++ b/src/adcp/types/domains/core/locale_tag.py @@ -1,18 +1,21 @@ -"""Types declared by the AdCP ``core/locale_tag`` schema. +# generated by datamodel-codegen: +# filename: core/locale_tag.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.locale_tag import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.locale_tag import LanguageTag +from adcp.types._scalar import ScalarStr + -# Explicit exports -__all__ = ["LanguageTag"] +class LanguageTag(ScalarStr): + __slots__ = () + _constraints = { + 'max_length': 63, + 'min_length': 2, + 'pattern': '^(?:[a-z]{2,8}(?:-[A-Z][a-z]{3})?(?:-(?:[A-Z]{2}|[0-9]{3}))?(?:-(?:[a-z0-9]{5,8}|[0-9][a-z0-9]{3}))*(?:-[0-9a-wy-z](?:-[a-z0-9]{2,8})+)*(?:-x(?:-[a-z0-9]{1,8})+)?|x(?:-[a-z0-9]{1,8})+)$', + } + _json_schema_extra = { + 'description': 'A well-formed BCP 47 language tag used by AdCP only as language identity. Script and region may refine that identity; other valid BCP 47 subtags remain part of tag matching but do not make this a general locale-settings object. It does not determine currency, time zone, number/date formatting, market, or legal jurisdiction. The AdCP canonical wire profile requires lower-case language and variants, title-case script, and upper-case region (for example `en-US`, `zh-Hant-TW`, or `x-private`). RFC 5646 comparisons are case-insensitive and its case regularization is optional; AdCP intentionally requires this stricter single wire spelling and receivers MUST reject differently cased tags rather than silently normalizing them. The schema pattern enforces the AdCP casing profile and extension structure for commonly used tags; conforming receivers additionally validate the complete RFC 5646 grammar and registry rules. Every new AdCP field carrying BCP 47 language identity or a concrete language range MUST reference this schema instead of declaring independent string constraints.', + 'examples': ['en-US', 'es-ES', 'zh-Hant-TW', 'x-private'], + 'title': 'Language Tag', + } diff --git a/src/adcp/types/domains/core/localized_creative_asset.py b/src/adcp/types/domains/core/localized_creative_asset.py index d4dc17461..fe80934dd 100644 --- a/src/adcp/types/domains/core/localized_creative_asset.py +++ b/src/adcp/types/domains/core/localized_creative_asset.py @@ -1,64 +1,171 @@ -"""Types declared by the AdCP ``core/localized_creative_asset`` schema. +# generated by datamodel-codegen: +# filename: core/localized_creative_asset.json +# timestamp: 2026-10-04T04:53:46+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.localized_creative_asset import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Any, Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import Field, RootModel -from adcp.types.generated_poc.core.localized_creative_asset import ( - LocalizedCreativeAsset, - LocalizedCreativeAsset1, - LocalizedCreativeAsset10, - LocalizedCreativeAsset11, - LocalizedCreativeAsset12, - LocalizedCreativeAsset13, - LocalizedCreativeAsset14, - LocalizedCreativeAsset15, - LocalizedCreativeAsset16, - LocalizedCreativeAsset17, - LocalizedCreativeAsset18, - LocalizedCreativeAsset19, - LocalizedCreativeAsset2, - LocalizedCreativeAsset20, - LocalizedCreativeAsset21, - LocalizedCreativeAsset22, - LocalizedCreativeAsset3, - LocalizedCreativeAsset4, - LocalizedCreativeAsset5, - LocalizedCreativeAsset7, - LocalizedCreativeAsset8, - LocalizedCreativeAsset9, +from .assets.asset_union import ( + AudioAsset, + BriefAsset, + CardAsset, + CatalogAsset, + CssAsset, + DaastTrackerAsset, + HtmlAsset, + ImageAsset, + JavascriptAsset, + MarkdownAsset, + PixelTrackerAsset, + TextAsset, + UrlAsset, + VastTrackerAsset, + VideoAsset, + WebhookAsset, + ZipAsset, ) -# Explicit exports -__all__ = [ - "LocalizedCreativeAsset", - "LocalizedCreativeAsset1", - "LocalizedCreativeAsset10", - "LocalizedCreativeAsset11", - "LocalizedCreativeAsset12", - "LocalizedCreativeAsset13", - "LocalizedCreativeAsset14", - "LocalizedCreativeAsset15", - "LocalizedCreativeAsset16", - "LocalizedCreativeAsset17", - "LocalizedCreativeAsset18", - "LocalizedCreativeAsset19", - "LocalizedCreativeAsset2", - "LocalizedCreativeAsset20", - "LocalizedCreativeAsset21", - "LocalizedCreativeAsset22", - "LocalizedCreativeAsset3", - "LocalizedCreativeAsset4", - "LocalizedCreativeAsset5", - "LocalizedCreativeAsset7", - "LocalizedCreativeAsset8", - "LocalizedCreativeAsset9", -] + +class LocalizedCreativeAsset1(AdCPBaseModel): + pass + + +class LocalizedCreativeAsset3(VideoAsset, LocalizedCreativeAsset1): + pass + + +class LocalizedCreativeAsset4(AudioAsset, LocalizedCreativeAsset1): + pass + + +class LocalizedCreativeAsset7(TextAsset, LocalizedCreativeAsset1): + pass + + +class LocalizedCreativeAsset9(HtmlAsset, LocalizedCreativeAsset1): + pass + + +class LocalizedCreativeAsset10(JavascriptAsset, LocalizedCreativeAsset1): + pass + + +class LocalizedCreativeAsset11(ZipAsset, LocalizedCreativeAsset1): + pass + + +class LocalizedCreativeAsset12(WebhookAsset, LocalizedCreativeAsset1): + pass + + +class LocalizedCreativeAsset13(CssAsset, LocalizedCreativeAsset1): + pass + + +class LocalizedCreativeAsset14(LocalizedCreativeAsset1): + pass + + +class LocalizedCreativeAsset15(MarkdownAsset, LocalizedCreativeAsset1): + pass + + +class LocalizedCreativeAsset18(LocalizedCreativeAsset14): + pass + + +class LocalizedCreativeAsset2(ImageAsset, LocalizedCreativeAsset1): + pass + + +class LocalizedCreativeAsset5(LocalizedCreativeAsset14): + pass + + +class LocalizedCreativeAsset8(UrlAsset, LocalizedCreativeAsset1): + pass + + +class LocalizedCreativeAsset16(BriefAsset, LocalizedCreativeAsset1): + pass + + +class LocalizedCreativeAsset17(CatalogAsset, LocalizedCreativeAsset1): + pass + + +class LocalizedCreativeAsset19(CardAsset, LocalizedCreativeAsset1): + pass + + +class LocalizedCreativeAsset20(PixelTrackerAsset, LocalizedCreativeAsset1): + pass + + +class LocalizedCreativeAsset21(VastTrackerAsset, LocalizedCreativeAsset1): + pass + + +class LocalizedCreativeAsset22(DaastTrackerAsset, LocalizedCreativeAsset1): + pass + + +class LocalizedCreativeAsset( + RootModel[ + LocalizedCreativeAsset2 + | LocalizedCreativeAsset3 + | LocalizedCreativeAsset4 + | LocalizedCreativeAsset5 + | LocalizedCreativeAsset7 + | LocalizedCreativeAsset8 + | LocalizedCreativeAsset9 + | LocalizedCreativeAsset10 + | LocalizedCreativeAsset11 + | LocalizedCreativeAsset12 + | LocalizedCreativeAsset13 + | LocalizedCreativeAsset14 + | LocalizedCreativeAsset15 + | LocalizedCreativeAsset16 + | LocalizedCreativeAsset17 + | LocalizedCreativeAsset18 + | LocalizedCreativeAsset19 + | LocalizedCreativeAsset20 + | LocalizedCreativeAsset21 + | LocalizedCreativeAsset22 + ] +): + root: Annotated[ + LocalizedCreativeAsset2 + | LocalizedCreativeAsset3 + | LocalizedCreativeAsset4 + | LocalizedCreativeAsset5 + | LocalizedCreativeAsset7 + | LocalizedCreativeAsset8 + | LocalizedCreativeAsset9 + | LocalizedCreativeAsset10 + | LocalizedCreativeAsset11 + | LocalizedCreativeAsset12 + | LocalizedCreativeAsset13 + | LocalizedCreativeAsset14 + | LocalizedCreativeAsset15 + | LocalizedCreativeAsset16 + | LocalizedCreativeAsset17 + | LocalizedCreativeAsset18 + | LocalizedCreativeAsset19 + | LocalizedCreativeAsset20 + | LocalizedCreativeAsset21 + | LocalizedCreativeAsset22, + Field( + description='An asset inside a materialized creative locale variant. Text and markdown assets may omit language when they make no language claim. When language is present, it MUST use the shared AdCP BCP 47 wire profile; conformance additionally requires exact equality with the enclosing variant locale.', + title='Localized Creative Asset', + ), + ] + def __getattr__(self, name: str) -> Any: + """Proxy attribute access to the wrapped type.""" + if name.startswith('_'): + raise AttributeError(name) + return getattr(self.root, name) diff --git a/src/adcp/types/domains/core/macro_bearing_url.py b/src/adcp/types/domains/core/macro_bearing_url.py index 1ef55c5c7..edadb821a 100644 --- a/src/adcp/types/domains/core/macro_bearing_url.py +++ b/src/adcp/types/domains/core/macro_bearing_url.py @@ -1,22 +1,41 @@ -"""Types declared by the AdCP ``core/macro_bearing_url`` schema. +# generated by datamodel-codegen: +# filename: core/macro_bearing_url.json +# timestamp: 2026-10-04T00:53:27+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.macro_bearing_url import +from adcp.types._scalar import ScalarStr -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import AnyUrl, Field, RootModel + + +class MacroBearingUrl3(RootModel[AnyUrl]): + root: Annotated[ + AnyUrl, + Field( + description='Backward-compatible URL value that preserves the existing `uri-template` branch (including deep links, protocol-relative references, and RFC 6570 host variables) while additionally accepting absolute HTTP(S) URLs with byte-preserved macro delimiters not legal in a strict URI, including `%%...%%`, `[...]`, and `${...}`. Macro-bearing HTTP(S) URLs require a valid lexical authority and valid percent triplets; an AdCP verifier replaces each recognized token with an unreserved sentinel and validates the resulting absolute URI. Token semantics are established only by `macro_declarations`.', + title='Macro-bearing URL', + ), + ] + + +class MacroBearingUrl4(ScalarStr): + __slots__ = () + _constraints = { + 'pattern': '^https?://(?:[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?|\\[[0-9A-Fa-f:.]+\\])(?::[0-9]{1,5})?(?:[/?#](?:[^%\\s\\u0000-\\u001F\\u007F"<>`\\\\]|%[0-9A-Fa-f]{2}|%%[A-Za-z0-9_.:-]+%%)*)?$', + } + _json_schema_extra = { + 'description': 'Backward-compatible URL value that preserves the existing `uri-template` branch (including deep links, protocol-relative references, and RFC 6570 host variables) while additionally accepting absolute HTTP(S) URLs with byte-preserved macro delimiters not legal in a strict URI, including `%%...%%`, `[...]`, and `${...}`. Macro-bearing HTTP(S) URLs require a valid lexical authority and valid percent triplets; an AdCP verifier replaces each recognized token with an unreserved sentinel and validates the resulting absolute URI. Token semantics are established only by `macro_declarations`.', + 'title': 'Macro-bearing URL', + } -from adcp.types.generated_poc.core.macro_bearing_url import ( - MacroBearingUrl, - MacroBearingUrl3, - MacroBearingUrl4, -) -# Explicit exports -__all__ = ["MacroBearingUrl", "MacroBearingUrl3", "MacroBearingUrl4"] +MacroBearingUrl = Annotated[ + str | MacroBearingUrl3 | MacroBearingUrl4, + Field( + description='Backward-compatible URL value that preserves the existing `uri-template` branch (including deep links, protocol-relative references, and RFC 6570 host variables) while additionally accepting absolute HTTP(S) URLs with byte-preserved macro delimiters not legal in a strict URI, including `%%...%%`, `[...]`, and `${...}`. Macro-bearing HTTP(S) URLs require a valid lexical authority and valid percent triplets; an AdCP verifier replaces each recognized token with an unreserved sentinel and validates the resulting absolute URI. Token semantics are established only by `macro_declarations`.', + title='Macro-bearing URL', + ), +] diff --git a/src/adcp/types/domains/core/macro_declaration.py b/src/adcp/types/domains/core/macro_declaration.py index 79a242631..5d8879a08 100644 --- a/src/adcp/types/domains/core/macro_declaration.py +++ b/src/adcp/types/domains/core/macro_declaration.py @@ -1,23 +1,123 @@ -"""Types declared by the AdCP ``core/macro_declaration`` schema. +# generated by datamodel-codegen: +# filename: core/macro_declaration.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.macro_declaration import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AnyUrl, ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations +from .assets import asset_union + + +class Field1(StrEnum): + url = 'url' + markup = 'markup' + content = 'content' + ad_request_url = 'ad_request_url' + clickthrough_url = 'clickthrough_url' + + +class Location(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + field: Annotated[Field1, Field(description='Asset field containing this occurrence.')] + occurrence: Annotated[ + SchemaInt, + Field( + description='Zero-based occurrence of this exact token within the named field.', ge=0 + ), + ] + context: asset_union.MacroValueContext + + +class UnavailableBehavior(StrEnum): + preserve = 'preserve' + omit_parameter = 'omit_parameter' + dialect_sentinel = 'dialect_sentinel' + reject = 'reject' -from adcp.types.generated_poc.core.macro_declaration import ( - Field1, - Location, - MacroDeclaration, - UnavailableBehavior, -) -# Explicit exports -__all__ = ["Field1", "Location", "MacroDeclaration", "UnavailableBehavior"] +class MacroDeclaration(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + declaration_id: Annotated[ + str, + Field( + description='Identifier unique within the enclosing asset, used to correlate validation results.', + min_length=1, + pattern='^[A-Za-z0-9_-]+$', + ), + ] + token: Annotated[ + str, + Field( + description='Exact byte-preserved token expression at this occurrence, including delimiters.', + min_length=1, + ), + ] + dialect: asset_union.MacroDialect + dialect_namespace: Annotated[ + AnyUrl | None, + Field( + description='Authority-controlled registry or mapping URI. Required for IAB VAST, IAB DAAST, and vendor dialects.' + ), + ] = None + dialect_revision: Annotated[ + str | None, + Field( + description='Immutable registry/mapping revision, release, commit, or digest. Required for IAB VAST, IAB DAAST, and vendor dialects.', + min_length=1, + ), + ] = None + dialect_semantic: Annotated[ + str, + Field( + description='Exact semantic identifier in the cited dialect, such as `CACHEBUSTING`, `PLAYERSTATE`, or a documented vendor name. This is not inferred from token spelling.', + min_length=1, + ), + ] + mapping_status: asset_union.MacroMappingStatus + universal_semantic: Annotated[ + asset_union.UniversalMacro | None, + Field( + description='Verified AdCP universal meaning. Present only when mapping_status is `verified_universal`.' + ), + ] = None + operation: asset_union.MacroProcessingOperation + performed_by: Annotated[ + asset_union.MacroResolver | None, + Field( + description='Actor authorized to perform the declared translation or value resolution. Omitted for preservation.' + ), + ] = None + translation_target: Annotated[ + asset_union.MacroTranslationTarget | None, + Field( + description='Required only for `translate_to_native`. The output token contract that replaces this declaration after translation.' + ), + ] = None + location: Location + encoding: Annotated[ + asset_union.MacroEncoding, + Field( + description='Encoding for the concrete value. Translation/preservation uses `none` at depth zero.' + ), + ] + required: Annotated[ + StrictBool, + Field( + description='Whether terminal delivery must fail if the declared operation cannot complete.' + ), + ] + unavailable_behavior: Annotated[ + UnavailableBehavior, + Field( + description='Action when the responsible actor has no value. Omission is legal only for a complete URL query-value occurrence; dialect sentinels require a cited dialect rule.' + ), + ] diff --git a/src/adcp/types/domains/core/macro_encoding.py b/src/adcp/types/domains/core/macro_encoding.py index 9b085ab92..6d378c82e 100644 --- a/src/adcp/types/domains/core/macro_encoding.py +++ b/src/adcp/types/domains/core/macro_encoding.py @@ -1,18 +1,25 @@ -"""Types declared by the AdCP ``core/macro_encoding`` schema. +# generated by datamodel-codegen: +# filename: core/macro_encoding.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.macro_encoding import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.macro_encoding import Kind, MacroEncoding +class Kind(StrEnum): + none = 'none' + rfc3986 = 'rfc3986' + iab_vast_uri = 'iab_vast_uri' + -# Explicit exports -__all__ = ["Kind", "MacroEncoding"] +class MacroEncoding(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Kind + depth: Annotated[SchemaInt, Field(ge=0)] diff --git a/src/adcp/types/domains/core/macro_resolution_capability.py b/src/adcp/types/domains/core/macro_resolution_capability.py index b5b8a6aa9..de4877035 100644 --- a/src/adcp/types/domains/core/macro_resolution_capability.py +++ b/src/adcp/types/domains/core/macro_resolution_capability.py @@ -1,22 +1,47 @@ -"""Types declared by the AdCP ``core/macro_resolution_capability`` schema. +# generated by datamodel-codegen: +# filename: core/macro_resolution_capability.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.macro_resolution_capability import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import macro_dialect, macro_resolver, macro_value_context, universal_macro +from . import macro_encoding, macro_translation_target + + +class MappingStatus(StrEnum): + verified_universal = 'verified_universal' + dialect_defined = 'dialect_defined' + + +class Operation(StrEnum): + translate_to_native = 'translate_to_native' + resolve_value = 'resolve_value' -from adcp.types.generated_poc.core.macro_resolution_capability import ( - MacroProcessingCapability, - MappingStatus, - Operation, -) -# Explicit exports -__all__ = ["MacroProcessingCapability", "MappingStatus", "Operation"] +class MacroProcessingCapability(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + dialect: macro_dialect.MacroDialectFamily + dialect_namespace: AnyUrl | None = None + dialect_revision: Annotated[str | None, Field(min_length=1)] = None + dialect_semantic: Annotated[str, Field(min_length=1)] + mapping_status: MappingStatus + universal_semantic: universal_macro.UniversalMacro | None = None + operation: Operation + performed_by: macro_resolver.MacroProcessingActor + supported_contexts: Annotated[list[macro_value_context.MacroValueContext], Field(min_length=1)] + supported_encodings: Annotated[ + list[macro_encoding.MacroEncoding] | None, + Field( + description='Exact supported encoding profiles, not a maximum depth. Required for value resolution.', + min_length=1, + ), + ] = None + translation_target: macro_translation_target.MacroTranslationTarget | None = None diff --git a/src/adcp/types/domains/core/macro_resolution_result.py b/src/adcp/types/domains/core/macro_resolution_result.py index bf585c866..5529952a4 100644 --- a/src/adcp/types/domains/core/macro_resolution_result.py +++ b/src/adcp/types/domains/core/macro_resolution_result.py @@ -1,22 +1,64 @@ -"""Types declared by the AdCP ``core/macro_resolution_result`` schema. +# generated by datamodel-codegen: +# filename: core/macro_resolution_result.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.macro_resolution_result import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field, StrictBool -from adcp.types.generated_poc.core.macro_resolution_result import ( - MacroResolutionResult, - Status, - UnavailableBehavior, +from ..enums import ( + macro_dialect, + macro_mapping_status, + macro_processing_operation, + macro_resolution_reason, + macro_resolver, + universal_macro, ) +from . import macro_encoding + + +class UnavailableBehavior(StrEnum): + preserve = 'preserve' + omit_parameter = 'omit_parameter' + dialect_sentinel = 'dialect_sentinel' + reject = 'reject' + + +class Status(StrEnum): + resolvable = 'resolvable' + preserved_for_downstream = 'preserved_for_downstream' + unsupported = 'unsupported' + ambiguous = 'ambiguous' + -# Explicit exports -__all__ = ["MacroResolutionResult", "Status", "UnavailableBehavior"] +class MacroResolutionResult(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + declaration_id: Annotated[str, Field(min_length=1)] + asset_path: Annotated[ + str, Field(description='JSON Pointer to the asset carrying the declaration.', pattern='^/') + ] + token: Annotated[str, Field(min_length=1)] + dialect: macro_dialect.MacroDialectFamily + dialect_namespace: AnyUrl | None = None + dialect_revision: Annotated[str | None, Field(min_length=1)] = None + dialect_semantic: Annotated[str, Field(min_length=1)] + mapping_status: macro_mapping_status.MacroMappingStatus + universal_semantic: universal_macro.UniversalMacro | None = None + operation: macro_processing_operation.MacroProcessingOperation + performed_by: macro_resolver.MacroProcessingActor | None = None + requested_encoding: macro_encoding.MacroEncoding + required: StrictBool + unavailable_behavior: UnavailableBehavior + status: Status + reason: macro_resolution_reason.MacroResolutionReason + matched_encodings: Annotated[ + list[macro_encoding.MacroEncoding] | None, + Field(description='Exact advertised profiles considered for this match, when relevant.'), + ] = None + message: Annotated[str | None, Field(min_length=1)] = None diff --git a/src/adcp/types/domains/core/macro_translation_target.py b/src/adcp/types/domains/core/macro_translation_target.py index d43f2b24b..e3d4143ed 100644 --- a/src/adcp/types/domains/core/macro_translation_target.py +++ b/src/adcp/types/domains/core/macro_translation_target.py @@ -1,18 +1,29 @@ -"""Types declared by the AdCP ``core/macro_translation_target`` schema. +# generated by datamodel-codegen: +# filename: core/macro_translation_target.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.macro_translation_target import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import macro_dialect, macro_mapping_status, macro_resolver, universal_macro +from . import macro_encoding -from adcp.types.generated_poc.core.macro_translation_target import MacroTranslationTarget -# Explicit exports -__all__ = ["MacroTranslationTarget"] +class MacroTranslationTarget(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + token: Annotated[str, Field(min_length=1)] + dialect: macro_dialect.MacroDialectFamily + dialect_namespace: AnyUrl | None = None + dialect_revision: Annotated[str | None, Field(min_length=1)] = None + dialect_semantic: Annotated[str, Field(min_length=1)] + mapping_status: macro_mapping_status.MacroMappingStatus + universal_semantic: universal_macro.UniversalMacro | None = None + next_operation: Literal['resolve_value'] = 'resolve_value' + performed_by: macro_resolver.MacroProcessingActor + encoding: macro_encoding.MacroEncoding diff --git a/src/adcp/types/domains/core/material_deadline.py b/src/adcp/types/domains/core/material_deadline.py index b91f75f30..03a4deab2 100644 --- a/src/adcp/types/domains/core/material_deadline.py +++ b/src/adcp/types/domains/core/material_deadline.py @@ -1,18 +1,32 @@ -"""Types declared by the AdCP ``core/material_deadline`` schema. +# generated by datamodel-codegen: +# filename: core/material_deadline.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.material_deadline import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field -from adcp.types.generated_poc.core.material_deadline import MaterialDeadline -# Explicit exports -__all__ = ["MaterialDeadline"] +class MaterialDeadline(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + stage: Annotated[ + str, + Field( + description="Submission stage identifier. Use 'draft' for materials that need seller processing and 'final' for production-ready assets. Sellers may define additional stages.", + examples=['draft', 'final'], + ), + ] + due_at: Annotated[ + AwareDatetime, Field(description='When materials for this stage are due (ISO 8601)') + ] + label: Annotated[ + str | None, + Field( + description="What the seller needs at this stage (e.g., 'Talking points and brand guidelines', 'Press-ready PDF with bleed')" + ), + ] = None diff --git a/src/adcp/types/domains/core/mcp_webhook_payload.py b/src/adcp/types/domains/core/mcp_webhook_payload.py index 8b7f241d7..9fad1fac0 100644 --- a/src/adcp/types/domains/core/mcp_webhook_payload.py +++ b/src/adcp/types/domains/core/mcp_webhook_payload.py @@ -1,18 +1,100 @@ -"""Types declared by the AdCP ``core/mcp_webhook_payload`` schema. +# generated by datamodel-codegen: +# filename: core/mcp_webhook_payload.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.mcp_webhook_payload import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import adcp_protocol, task_status +from ..enums import task_type as task_type_1 +from . import async_response_data -from adcp.types.generated_poc.core.mcp_webhook_payload import McpWebhookPayload -# Explicit exports -__all__ = ["McpWebhookPayload"] +class McpWebhookPayload(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + idempotency_key: Annotated[ + str, + Field( + description='Sender-generated delivery key stable across RFC 8785 JCS-equivalent retries of the complete authenticated webhook payload. Publishers MUST generate a cryptographically random value (UUID v4 recommended), bind it immutably to the first canonical payload for the advertised delivery retry horizon, and use a fresh key for a changed payload or distinct delivery. Receivers scope the binding to the authenticated sender identity. Same key plus identical payload while active returns retryable 503; after durable acknowledgement it returns 2xx; same key plus a different canonical payload returns non-retryable 409. This is the transport delivery identity, not request idempotency or stable logical notification identity.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + notification_id: Annotated[ + str | None, + Field( + description='Optional event-layer identifier for one logical notification. Stable across re-emissions of the same logical event and distinct from the per-delivery `idempotency_key`. For terminal task webhooks, the authoritative terminal identity remains the authenticated seller plus the bound task_id; when notification_id is present, different delivery keys carrying the same value are re-emissions and MUST NOT republish terminal effects. For other event families, population and repair identity remain event-shape-dependent (see notification-type.json enumDescriptions): impairment aliases impairment_id, creative and account notifications use transition identifiers, wholesale events alias event.event_id, and capability changes use a revision-event identifier. Point-in-time delivery events (scheduled, final, delayed, adjusted, window_update) omit this field and dedupe by idempotency_key plus their delivery-report identity. Charset is constrained to `[A-Za-z0-9_.:-]`.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] = None + operation_id: Annotated[ + str, + Field( + description='Client-generated correlation identifier for the operation that produced this webhook. Buyers supply this value at webhook registration time via `push_notification_config.operation_id`; sellers MUST echo it verbatim in every webhook payload. Sellers MUST NOT derive `operation_id` by parsing `push_notification_config.url` — the URL is opaque to the seller. Receivers MAY dispatch endpoints by URL path or query string, but MUST correlate the operation using this payload field, not URL-derived values. See [Webhooks — Operation IDs and URL templates](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates) for the full normative wire contract.' + ), + ] + task_id: Annotated[ + str, + Field( + description='Unique identifier for this task. Use this to correlate webhook notifications with the original task submission.' + ), + ] + task_type: Annotated[ + task_type_1.TaskType, + Field( + description='Type of AdCP operation that triggered this webhook. Enables webhook handlers to route to appropriate processing logic.' + ), + ] + protocol: Annotated[ + adcp_protocol.AdcpProtocol | None, + Field( + description='AdCP protocol this task belongs to. Helps classify the operation type at a high level.' + ), + ] = None + status: Annotated[ + task_status.TaskStatus, + Field( + description='Current task status. Webhooks are triggered for status changes after initial submission.' + ), + ] + timestamp: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when this logical webhook delivery was first generated. Every retry under the same idempotency_key MUST repeat this exact body value, along with every other payload member; only transport/signature metadata such as a fresh RFC 9421 nonce or created parameter may change between attempts.' + ), + ] + message: Annotated[ + str | None, + Field( + description='Human-readable summary of the current task state. Provides context about what happened and what action may be needed.' + ), + ] = None + context_id: Annotated[ + str | None, + Field( + description='Compatibility metadata copied from the originating response when present. This value alone is not continuation authority and MUST NOT be used to resume input-required or auth-required work or to select session state.' + ), + ] = None + token: Annotated[ + str | None, + Field( + description='Authentication token echoed verbatim from [`PushNotificationConfig.token`](/schemas/core/push-notification-config.json). Receivers that configured a token MUST compare it to this value to validate request authenticity, and SHOULD use a constant-time equality check to mitigate timing attacks. Absent when no token was configured at registration. Length bounds mirror the config-side field — receivers MAY reject payloads whose token length falls outside the configured range as a defensive check, provided the length check is performed only after the configured token is known to exist for this subscription, and the length comparison is not used as a fast-path to short-circuit the constant-time compare on equal-length inputs. Receivers MUST NOT treat absence as an authenticity failure when no token was configured.', + max_length=4096, + min_length=16, + ), + ] = None + result: Annotated[ + async_response_data.AdcpAsyncResponseData | None, + Field( + description='Task-specific payload matching the status. For completed/failed, contains the full task response. For working/input-required/submitted, contains status-specific data. This is the data layer that AdCP specs - same structure used in A2A status.message.parts[].data.' + ), + ] = None diff --git a/src/adcp/types/domains/core/measurement_readiness.py b/src/adcp/types/domains/core/measurement_readiness.py index 7858ee75a..79db1b455 100644 --- a/src/adcp/types/domains/core/measurement_readiness.py +++ b/src/adcp/types/domains/core/measurement_readiness.py @@ -1,18 +1,50 @@ -"""Types declared by the AdCP ``core/measurement_readiness`` schema. +# generated by datamodel-codegen: +# filename: core/measurement_readiness.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.measurement_readiness import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import assessment_status, event_type +from . import diagnostic_issue -from adcp.types.generated_poc.core.measurement_readiness import MeasurementReadiness -# Explicit exports -__all__ = ["MeasurementReadiness"] +class MeasurementReadiness(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + status: Annotated[ + assessment_status.AssessmentStatus, + Field( + description="Overall measurement readiness level for this product given the buyer's event setup. 'insufficient' means the product cannot optimize effectively with the current setup." + ), + ] + required_event_types: Annotated[ + list[event_type.EventType] | None, + Field( + description='Event types this product needs for effective optimization. Buyers should ensure their event sources cover these types.', + min_length=1, + ), + ] = None + missing_event_types: Annotated[ + list[event_type.EventType] | None, + Field( + description='Event types this product requires that the buyer has not configured. Empty or absent when all required types are covered.' + ), + ] = None + issues: Annotated[ + list[diagnostic_issue.DiagnosticIssue] | None, + Field( + description='Actionable issues preventing full measurement readiness. Sellers should limit to the top 3-5 most actionable items. Buyer agents should sort by severity rather than relying on array position.' + ), + ] = None + notes: Annotated[ + str | None, + Field( + description='Seller explanation of the readiness assessment, recommendations for improvement, or context about what the buyer needs to change.' + ), + ] = None diff --git a/src/adcp/types/domains/core/measurement_terms.py b/src/adcp/types/domains/core/measurement_terms.py index 43b368072..5e8b7144e 100644 --- a/src/adcp/types/domains/core/measurement_terms.py +++ b/src/adcp/types/domains/core/measurement_terms.py @@ -1,22 +1,87 @@ -"""Types declared by the AdCP ``core/measurement_terms`` schema. +# generated by datamodel-codegen: +# filename: core/measurement_terms.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.measurement_terms import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import makegood_remedy +from . import brand_ref + + +class MakegoodPolicy(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + available_remedies: Annotated[ + list[makegood_remedy.MakegoodRemedy], + Field( + description='Remedy types the seller supports. Ordered by seller preference (first = preferred). Seller proposes from this list when a breach occurs; buyer accepts or disputes.', + min_length=1, + ), + ] + + +class BillingMeasurement(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + vendor: Annotated[ + brand_ref.BrandReference, + Field( + description="Vendor whose measurement of the billing metric is authoritative for invoicing (e.g., { domain: 'campaignmanager.google.com' } for buyer's DCM, { domain: 'admanager.google.com' } for seller's GAM)." + ), + ] + max_variance_percent: Annotated[ + StrictFloat | None, + Field( + description="Maximum acceptable variance between the billing vendor's count and the other party's count before resolution is triggered (e.g., 10 means a 10% divergence triggers review).", + ge=0.0, + lt=100.0, + ), + ] = None + measurement_window: Annotated[ + str | None, + Field( + description="Which measurement maturation stage the billing metric is reconciled against. References a window_id from the product's reporting_capabilities.measurement_windows. Examples: 'c7' for broadcast TV guarantees (live + 7 days DVR), 'final' for DOOH after IVT/fraud-check processing, 'post_sivt' for digital after sophisticated invalid-traffic filtering, 'downloads_30d' for podcast. When absent, billing is based on the seller's standard reporting without windowed maturation.", + examples=[ + 'live', + 'c3', + 'c7', + 'tentative', + 'final', + 'post_ivt', + 'post_sivt', + 'downloads_30d', + ], + ), + ] = None + finalization_deadline_hours: Annotated[ + SchemaInt | None, + Field( + description='Maximum hours by which the authoritative party MUST publish a final record (`is_final: true` / `finalized_at` on `get_media_buy_delivery`, or `final: true` / `finalized_at` on `report_usage`). **Anchor:** when `measurement_window` is set, hours are counted from the close of that window (e.g., 240h after `c7` close = ~10 days after the 7-day DVR accumulation completes); when `measurement_window` is absent, hours are counted from `reporting_period.end`. Picking a single anchor avoids ambiguity for windowed channels where `reporting_period.end` and window close differ by days. The deadline applies to whichever party is named in `vendor` — seller, buyer, or third-party vendor — symmetrically. When the deadline elapses without a final record, the counterparty MAY fall back to its own attestation for invoicing (seller falls back to seller-attested numbers via `get_media_buy_delivery`; buyer falls back to a buyer-attested `report_usage` push), and the breach is treated like any other measurement-terms breach under `makegood_policy`. Absent means no contractual deadline — finalization is best-effort and disagreements resolve out of band.', + ge=0, + ), + ] = None -from adcp.types.generated_poc.core.measurement_terms import ( - BillingMeasurement, - MakegoodPolicy, - MeasurementTerms, -) -# Explicit exports -__all__ = ["BillingMeasurement", "MakegoodPolicy", "MeasurementTerms"] +class MeasurementTerms(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + billing_measurement: Annotated[ + BillingMeasurement | None, + Field( + description="Which vendor's value for the billing metric governs invoicing. The billing metric is determined by the pricing_model on the selected pricing_option (e.g., impressions for CPM, completed views for CPCV, commissionable_value for revenue_share)." + ), + ] = None + makegood_policy: Annotated[ + MakegoodPolicy | None, + Field( + description='Remedies available when a performance standard or billing measurement variance is breached. Seller declares which remedy types they support. When a breach occurs, the seller proposes a remedy from this menu; the buyer accepts or disputes.' + ), + ] = None diff --git a/src/adcp/types/domains/core/measurement_window.py b/src/adcp/types/domains/core/measurement_window.py index 5c46c8add..073da0fae 100644 --- a/src/adcp/types/domains/core/measurement_window.py +++ b/src/adcp/types/domains/core/measurement_window.py @@ -1,18 +1,58 @@ -"""Types declared by the AdCP ``core/measurement_window`` schema. +# generated by datamodel-codegen: +# filename: core/measurement_window.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.measurement_window import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictBool -from adcp.types.generated_poc.core.measurement_window import MeasurementWindow -# Explicit exports -__all__ = ["MeasurementWindow"] +class MeasurementWindow(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + window_id: Annotated[ + str, + Field( + description="Identifier for this maturation stage. Standard broadcast values: 'live' (real-time viewers only), 'c3' (live + 3 days time-shifted), 'c7' (live + 7 days time-shifted). Standard values for other channels include 'tentative' (provisional data available quickly), 'final' (post-processing certified data), 'post_ivt' (digital after invalid-traffic filtering), 'post_sivt' (digital after sophisticated-IVT filtering), 'downloads_7d' / 'downloads_30d' (podcast download maturation). Sellers may define custom IDs.", + examples=['live', 'c3', 'c7', 'tentative', 'final', 'post_ivt', 'downloads_30d'], + max_length=50, + ), + ] + description: Annotated[ + str | None, + Field( + description='Human-readable description of what this window measures', + examples=[ + 'Live broadcast impressions only', + 'Live plus 7 days of time-shifted viewing', + 'Tentative plays before IVT and fraud-check processing', + 'Final plays after IVT and fraud-check processing', + 'Impressions after sophisticated invalid-traffic filtering', + ], + max_length=500, + ), + ] = None + duration_days: Annotated[ + SchemaInt, + Field( + description='Number of days of accumulation included in this window before processing begins. For broadcast, this is DVR accumulation (0 = live only, 3 = live + 3 days DVR, 7 = live + 7 days DVR). For channels without an accumulation period (DOOH tentative→final, digital IVT filtering), this is 0 — maturation is entirely vendor processing time captured in expected_availability_days.', + ge=0, + ), + ] + expected_availability_days: Annotated[ + SchemaInt | None, + Field( + description="Expected number of days after delivery before this window's data is available from the measurement vendor. Captures accumulation time plus vendor processing time. Examples: broadcast C7 from VideoAmp ~22 days (7-day accumulation + ~15-day processing); DOOH tentative plays same-day; DOOH final (post-IVT/fraud-check) ~1 day; digital post-SIVT ~2–3 days.", + ge=0, + ), + ] = None + is_guarantee_basis: Annotated[ + StrictBool | None, + Field( + description="Whether this window is the basis for delivery guarantees, reconciliation, and invoicing. A product typically has one guarantee basis window (e.g., C7 for most US broadcast, post-IVT final for DOOH). Buyers reconcile against the guarantee basis window's final numbers." + ), + ] = None diff --git a/src/adcp/types/domains/core/media_buy.py b/src/adcp/types/domains/core/media_buy.py index 467dcbd62..0fc9af67a 100644 --- a/src/adcp/types/domains/core/media_buy.py +++ b/src/adcp/types/domains/core/media_buy.py @@ -1,18 +1,178 @@ -"""Types declared by the AdCP ``core/media_buy`` schema. +# generated by datamodel-codegen: +# filename: core/media_buy.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.media_buy import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import canceled_by as canceled_by_1 +from ..enums import media_buy_health, media_buy_status +from ..enums import pacing as pacing_1 +from . import account as account_1 +from . import bidding_policy +from . import budget_allocation as budget_allocation_1 +from . import business_entity +from . import context as context_1 +from . import ext as ext_1 +from . import impairment, media_buy_frequency_cap, package + + +class Cancellation(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + canceled_at: Annotated[ + AwareDatetime, Field(description='ISO 8601 timestamp when this media buy was canceled.') + ] + canceled_by: Annotated[ + canceled_by_1.CanceledBy, + Field( + description="Which party initiated the cancellation. 'buyer' when canceled through control_media_buy or the 3.x update_media_buy facade; 'seller' when the seller cancels (e.g., policy violation, inventory withdrawal)." + ), + ] + reason: Annotated[ + str | None, + Field(description='Reason provided when the media buy was canceled.', max_length=500), + ] = None -from adcp.types.generated_poc.core.media_buy import Cancellation, MediaBuy -# Explicit exports -__all__ = ["Cancellation", "MediaBuy"] +class MediaBuy(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + media_buy_id: Annotated[str, Field(description="Seller's unique identifier for the media buy")] + name: Annotated[ + str | None, + Field( + description='Human-readable name for this media buy, shared by buyer and seller for trafficking UI display and operational communication. Sellers MUST include the persisted name on read surfaces such as get_media_buys when the media buy was created through AdCP with name. Sellers MAY omit name for media buys created outside AdCP or created without name. This display label is not an identifier or financial reference.', + max_length=255, + min_length=1, + pattern='\\S', + ), + ] = None + accepted_proposal_id: Annotated[ + str | None, + Field( + description='Current accepted commercial snapshot. Compact-lifecycle buyers pass this ID to refine_proposals after restart or handoff. Updated atomically when an amendment or negotiated cancellation is accepted.', + max_length=255, + min_length=1, + ), + ] = None + accepted_proposal_terms_digest: Annotated[ + str | None, + Field( + description='Digest of the current accepted proposal commercial_terms, allowing buyers and governance agents to verify the recovered snapshot.', + pattern='^sha256:[A-Za-z0-9_-]{43}$', + ), + ] = None + account: Annotated[ + account_1.Account | None, Field(description='Account billed for this media buy') + ] = None + status: media_buy_status.MediaBuyStatus + health: Annotated[ + media_buy_health.MediaBuyHealth | None, + Field( + description="Aggregate health based on open impairments[]. Orthogonal to status — a paused, pending, or active buy can each be impaired. Defaults to 'ok' when impairments[] is empty." + ), + ] = media_buy_health.MediaBuyHealth.ok + impairments: Annotated[ + list[impairment.Impairment] | None, + Field( + description="Open impairments — upstream dependency state changes that affect delivery for at least one package on this buy. Empty when health is 'ok'. Sellers MUST add an entry on next sync/poll response after a referenced resource transitions to an offline state, and MUST remove the entry (flipping health to 'ok' when the array empties) when the resource returns to a serviceable state. Staleness budget: the snapshot MUST reflect the impairment within 5 minutes of impairment.observed_at regardless of buyer poll cadence — sellers cannot rely on rare buyer polls to defer write propagation. See impairment.coherence assertion for the cross-resource invariant." + ), + ] = None + rejection_reason: Annotated[ + str | None, + Field( + description="Reason provided by the seller when status is 'rejected'. Present only when status is 'rejected'." + ), + ] = None + confirmed_at: Annotated[ + AwareDatetime | None, + Field( + description='ISO 8601 timestamp when the seller committed to this media buy. May be null until seller commitment occurs in deferred/manual approval flows. Once populated, remains stable through later pause, resume, activation, completion, cancellation, and reporting transitions.' + ), + ] + cancellation: Annotated[ + Cancellation | None, + Field(description="Cancellation metadata. Present only when status is 'canceled'."), + ] = None + total_budget: Annotated[ + StrictFloat, Field(description='Hard aggregate lifetime budget amount', ge=0.0) + ] + daily_budget_cap: Annotated[ + StrictFloat | None, + Field( + description='Current hard aggregate spend ceiling per calendar day. Sellers MUST echo this whenever an aggregate daily cap is set. It bounds total media-buy spend without allocating or reserving spend for packages.', + ge=0.0, + ), + ] = None + frequency_cap: Annotated[ + media_buy_frequency_cap.MediaBuyFrequencyCap | None, + Field( + description='Current hard MediaBuy-level cap. Sellers MUST echo it whenever set. Its counter aggregates exposures across all participating packages; each package targeting_overlay.frequency_cap remains independently binding.' + ), + ] = None + budget_cap_timezone: Annotated[ + str | None, + Field( + description='IANA timezone defining the shared calendar-day boundary for every aggregate and package daily cap on this media buy. Sellers MUST echo it whenever any daily cap is set.' + ), + ] = None + currency: Annotated[ + str | None, + Field( + description="Single ISO 4217 denomination for total_budget, every package budget/minimum, and every canonical BiddingPolicy monetary field. Every package's selected pricing option MUST declare this currency; packages requiring another currency belong in a separate media buy.", + pattern='^[A-Z]{3}$', + ), + ] = None + budget_allocation: Annotated[ + budget_allocation_1.BudgetAllocation | None, + Field( + description='Accepted cross-package budget allocation configuration. Omitted means fixed allocation for legacy buys.' + ), + ] = None + pacing: Annotated[ + pacing_1.Pacing | None, + Field( + description='Aggregate pacing strategy for total_budget across the media-buy flight.' + ), + ] = None + bidding: Annotated[ + bidding_policy.BiddingPolicy | None, + Field( + description='Media-buy-authored bidding policy. This is the complete default inherited by packages that omit package.bidding; `{automatic:true}` is an explicit authored automatic policy. In seller-optimized mode, cost_per/roas bind to the primary budget_allocation optimization goal. In fixed mode, inherited cost_per requires compatible package primary-goal result units and inherited roas requires value-bearing primary goals. Monetary fields use media_buy.currency; every affected pricing option MUST declare the same currency. Package overrides are permitted only where advertised; conflicts MUST be rejected atomically with BIDDING_PLACEMENT_CONFLICT.' + ), + ] = None + packages: Annotated[ + list[package.Package], Field(description='Array of packages within this media buy') + ] + context: Annotated[ + context_1.ContextObject | None, + Field( + description='Opaque media-buy-level correlation data echoed unchanged from the create_media_buy request. Sellers MUST include persisted context on read surfaces such as get_media_buys when the media buy was created through AdCP with context, so buyers can reconcile seller-assigned media_buy_id values with their own tracking state. Sellers MAY omit context for media buys created outside AdCP or created without context. Sellers MUST NOT parse this object for business logic.' + ), + ] = None + invoice_recipient: Annotated[ + business_entity.BusinessEntity | None, + Field( + description="Per-buy override for who receives the invoice. When provided, the seller invoices this entity instead of the account's default billing_entity. The seller MUST validate the invoice recipient is authorized for this account. When governance_agents are configured, the seller MUST include invoice_recipient in the check_governance request." + ), + ] = None + creative_deadline: Annotated[ + AwareDatetime | None, Field(description='ISO 8601 timestamp for creative upload deadline') + ] = None + revision: Annotated[ + SchemaInt, + Field( + description='Monotonically increasing optimistic concurrency token. Incremented on every mutating state change or update; reads, validation-only calls, and exact idempotency replays do not increment it. Callers SHOULD include this in update_media_buy requests intended to change state — when provided, sellers MUST reject with CONFLICT if the revision does not match the current value, and MUST enforce that comparison atomically with the write.', + ge=1, + ), + ] + created_at: Annotated[AwareDatetime | None, Field(description='Creation timestamp')] = None + updated_at: Annotated[AwareDatetime | None, Field(description='Last update timestamp')] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/media_buy_available_action.py b/src/adcp/types/domains/core/media_buy_available_action.py index 2c7471a22..e1527e69b 100644 --- a/src/adcp/types/domains/core/media_buy_available_action.py +++ b/src/adcp/types/domains/core/media_buy_available_action.py @@ -1,18 +1,63 @@ -"""Types declared by the AdCP ``core/media_buy_available_action`` schema. +# generated by datamodel-codegen: +# filename: core/media_buy_available_action.json +# timestamp: 2026-09-14T14:16:02+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.media_buy_available_action import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import media_buy_action_mode +from . import ( + applicable_package_id, + media_buy_available_action_id, + media_buy_change_term_id, + media_buy_legacy_terms_ref, + sla_window, +) + + +class Task(StrEnum): + control_media_buy = 'control_media_buy' + refine_proposals = 'refine_proposals' + sync_creatives = 'sync_creatives' -from adcp.types.generated_poc.core.media_buy_available_action import MediaBuyAvailableAction, Task -# Explicit exports -__all__ = ["MediaBuyAvailableAction", "Task"] +class MediaBuyAvailableAction(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + action: Annotated[ + media_buy_available_action_id.MediaBuyAvailableActionId, + Field(description='The action identifier.'), + ] + mode: Annotated[ + media_buy_action_mode.MediaBuyActionMode, + Field( + description='The single mode that applies right now on this buy for this action. Singular because the buy has a concrete state, exactly one mode applies. Buyer SDKs branch on this to decide whether to expect a synchronous response, conditional handling, or an asynchronous approval callback.' + ), + ] + task: Annotated[ + Task | None, + Field( + description='Compact-lifecycle task for this resolved action: operational control, commercial refinement, or creative lifecycle mutation.' + ), + ] = None + sla: Annotated[ + sla_window.SlaWindow | None, + Field( + description='Optional SLA commitment for this action on this buy. Absence means no commitment, not zero commitment.' + ), + ] = None + change_term_id: media_buy_change_term_id.MediaBuyChangeTermId | None = None + terms_ref: media_buy_legacy_terms_ref.MediaBuyTermsReference | None = None + applicable_package_ids: Annotated[ + list[applicable_package_id.ApplicablePackageId] | None, + Field( + description='For a package-scoped action, the exact packages currently eligible. Omission means every relevant package. Root actions omit this field.', + min_length=1, + ), + ] = None diff --git a/src/adcp/types/domains/core/media_buy_available_action_id.py b/src/adcp/types/domains/core/media_buy_available_action_id.py index c590361a2..d35554f9f 100644 --- a/src/adcp/types/domains/core/media_buy_available_action_id.py +++ b/src/adcp/types/domains/core/media_buy_available_action_id.py @@ -1,18 +1,20 @@ -"""Types declared by the AdCP ``core/media_buy_available_action_id`` schema. +# generated by datamodel-codegen: +# filename: core/media_buy_available_action_id.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.media_buy_available_action_id import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import media_buy_valid_action -from adcp.types.generated_poc.core.media_buy_available_action_id import MediaBuyAvailableActionId -# Explicit exports -__all__ = ["MediaBuyAvailableActionId"] +MediaBuyAvailableActionId = Annotated[ + media_buy_valid_action.MediaBuyValidAction | Literal['update_media_buy_frequency_cap'], + Field( + description='An action identifier for the structured available_actions surface. It contains every legacy valid_actions value plus structured-only actions introduced after that deprecated flat surface.', + title='Media Buy Available Action ID', + ), +] diff --git a/src/adcp/types/domains/core/media_buy_change_term_id.py b/src/adcp/types/domains/core/media_buy_change_term_id.py index b8607e595..073a73782 100644 --- a/src/adcp/types/domains/core/media_buy_change_term_id.py +++ b/src/adcp/types/domains/core/media_buy_change_term_id.py @@ -1,18 +1,16 @@ -"""Types declared by the AdCP ``core/media_buy_change_term_id`` schema. +# generated by datamodel-codegen: +# filename: core/media_buy_change_term_id.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.media_buy_change_term_id import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.media_buy_change_term_id import MediaBuyChangeTermId +from adcp.types._scalar import ScalarStr + -# Explicit exports -__all__ = ["MediaBuyChangeTermId"] +class MediaBuyChangeTermId(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Za-z0-9_.:-]+$'} + _json_schema_extra = { + 'description': 'The accepted proposal change_terms[].term_id from which this current-state action projection was derived.', + 'title': 'Media Buy Change Term ID', + } diff --git a/src/adcp/types/domains/core/media_buy_features.py b/src/adcp/types/domains/core/media_buy_features.py index 3c3963816..f41afaf1b 100644 --- a/src/adcp/types/domains/core/media_buy_features.py +++ b/src/adcp/types/domains/core/media_buy_features.py @@ -1,18 +1,80 @@ -"""Types declared by the AdCP ``core/media_buy_features`` schema. +# generated by datamodel-codegen: +# filename: core/media_buy_features.json +# timestamp: 2026-10-03T15:09:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.media_buy_features import +from typing import Annotated, Dict -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import bidding_policy_capability -from adcp.types.generated_poc.core.media_buy_features import MediaBuyFeatures -# Explicit exports -__all__ = ["MediaBuyFeatures"] +class MediaBuyFeatures(AdCPBaseModel): + __pydantic_extra__: Dict[str, StrictBool] + model_config = ConfigDict( + extra='allow', + ) + inline_creative_management: Annotated[ + StrictBool | None, + Field( + deprecated=True, + description='Deprecated 3.x compatibility capability for creatives provided inline in create_media_buy and update_media_buy package payloads. buy_products, accept_proposal, and control_media_buy never accept inline creatives. New integrations use the dedicated creative lifecycle. Removed in 4.0.', + ), + ] = None + property_list_filtering: Annotated[ + StrictBool | None, + Field( + description='Honors property_list parameter in get_products to filter results to buyer-approved properties' + ), + ] = None + catalog_management: Annotated[ + StrictBool | None, + Field( + description='Supports sync_catalogs task for catalog feed management with platform review and approval' + ), + ] = None + catalog_item_availability_updates: Annotated[ + StrictBool | None, + Field( + description='Supports buyer-pushed item_availability_updates and item_availability_queries on sync_catalogs for immediate suppression/restoration and current-state readback in buyer-managed catalogs. Seller declarations may be true only with catalog_management: true; buyer required_features filters may request this feature alone. Requests containing availability operations are synchronous and accept at most 1,000 combined update and query entries. A successful suppress covers selection, dynamic rendering, and cached or pre-generated creatives materialized from the item. Internal lineage MUST retain resolved_account_id, catalog_id, catalog_generation, and item_id. Static creatives supplied or promoted by the buyer without catalog lineage remain outside this automatic guarantee. Suppression persists across feed refreshes and ordinary upserts until restore, expires_at, or catalog deletion. A seller that does not declare true MUST reject availability operations with UNSUPPORTED_FEATURE before lookup or mutation and MUST NOT interpret them as discovery. This does not control seller-owned wholesale inventory or let restore bypass seller controls.' + ), + ] = None + committed_metrics_supported: Annotated[ + StrictBool | None, + Field( + description="Seller has per-package snapshot infrastructure for the reporting contract. When true, the seller MUST populate `package.committed_metrics` on committed `create_media_buy` responses where `confirmed_at` is non-null, MUST omit `package.committed_metrics` while `confirmed_at` is null for a provisional buy, and MUST honor append-only mid-flight metric additions via `update_media_buy`. The unified `committed_metrics` array (per the metric-accountability design) covers both standard and vendor-defined metric entries, so a single flag is load-bearing. Buyers filtering on this flag are detecting 'this seller can stamp the reporting contract,' which closes the audit gap from PR #3510 where absence of `committed_metrics` was indistinguishable between 'didn't snapshot' and 'snapshot infrastructure not implemented.'" + ), + ] = None + seller_optimized_budget: Annotated[ + StrictBool | None, + Field( + description="Supports the core seller-optimized shared-budget contract for budget_allocation.mode `seller_optimized`: one hard shared total_budget, seller allocation of that total across the buy's packages against budget_allocation.optimization_goals, media-buy-level pacing, and echo of the allocation configuration on buy read surfaces. Sellers declaring true MUST accept eligible explicit-package and proposal executions that use only these core controls and MUST enforce the aggregate budget. Core media-buy pacing: sellers declaring true MUST accept omitted media-buy pacing (which defaults to even when total_budget is present) and pacing `even` on seller-optimized buys; they MAY reject `asap` or `front_loaded` with `UNSUPPORTED_FEATURE` (error.field `pacing`) before any provider mutation, and MUST NOT silently coerce them to `even`. Package-level controls inside a seller-optimized buy are separate capabilities: package budget caps (seller_optimized_package_budgets), package minimum-spend targets (seller_optimized_min_spend_targets), and package pacing (seller_optimized_package_pacing). A seller declaring this feature but not one of those sub-capabilities MUST reject any request that would leave that package control on a seller-optimized buy with `UNSUPPORTED_FEATURE` before any over-subscription validation or provider mutation, and MUST NOT silently drop, soften, or coerce it. Over-subscription validation (`INVALID_REQUEST`) applies only to package controls the seller has declared; see seller_optimized_min_spend_targets. Product combinations may still be rejected when their currencies, optimization capabilities, pricing terms, or delivery constraints are incompatible. Sellers that do not declare this feature MUST reject any request carrying `budget_allocation.mode: 'seller_optimized'` with `UNSUPPORTED_FEATURE` before any provider mutation, and MUST NOT coerce the request to fixed allocation." + ), + ] = None + seller_optimized_package_budgets: Annotated[ + StrictBool | None, + Field( + description='Honors package `budget` as an optional hard lifetime package spend cap inside a seller-optimized buy: packages[].budget and new_packages[].budget on create_media_buy and update_media_buy, purchases[].budget on buy_products, package budget controls on control_media_buy, and max_spend_percentage on seller-optimized proposal allocations. The cap is a ceiling, not a reserved or current allocation, and package caps may sum above total_budget. Meaningful only with seller_optimized_budget: true; seller declarations may be true only with seller_optimized_budget: true, while buyer required_features filters may request this feature alone. A seller that declares seller_optimized_budget without this feature MUST reject a request that would leave a package budget on a seller-optimized buy, including an allocation-mode switch that retains fixed-mode package budgets (the buyer clears them with null in the same atomic update), with `UNSUPPORTED_FEATURE` before any over-subscription validation or provider mutation, and MUST NOT issue seller-optimized proposals carrying max_spend_percentage. Does not govern fixed allocation, where package budgets remain required.' + ), + ] = None + seller_optimized_min_spend_targets: Annotated[ + StrictBool | None, + Field( + description='Honors package `min_spend_target` as a soft lifetime minimum-spend target inside a seller-optimized buy: packages[].min_spend_target and new_packages[].min_spend_target on create_media_buy and update_media_buy, purchases[].min_spend_target on buy_products, package min_spend_target controls on control_media_buy, and min_spend_target_percentage on seller-optimized proposal allocations. The seller SHOULD attempt to deliver at least the target before allocating incremental spend elsewhere; it is not a billing or delivery guarantee. Meaningful only with seller_optimized_budget: true; seller declarations may be true only with seller_optimized_budget: true, while buyer required_features filters may request this feature alone. Sellers declaring this feature MUST reject package minimum-spend targets summing above total_budget with `INVALID_REQUEST` before mutation, and, when they also declare seller_optimized_package_budgets, MUST likewise reject a min_spend_target above its own package budget. A seller that declares seller_optimized_budget without this feature MUST reject a request carrying a numeric min_spend_target with `UNSUPPORTED_FEATURE` before any over-subscription validation or provider mutation, so an over-subscribed target sent to such a seller yields `UNSUPPORTED_FEATURE`, and MUST NOT issue seller-optimized proposals carrying min_spend_target_percentage.' + ), + ] = None + seller_optimized_package_pacing: Annotated[ + StrictBool | None, + Field( + description='Honors package `pacing` as subordinate per-package pacing inside a seller-optimized buy, in addition to the media-buy-level pacing covered by seller_optimized_budget: packages[].pacing and new_packages[].pacing on create_media_buy and update_media_buy, purchases[].pacing on buy_products, package pacing controls on control_media_buy, and allocation pacing on seller-optimized proposals. Package pacing MUST NOT cause delivery to exceed aggregate media-buy pacing. Meaningful only with seller_optimized_budget: true; seller declarations may be true only with seller_optimized_budget: true, while buyer required_features filters may request this feature alone. Package pacing equal to the effective media-buy pacing adds no subordinate constraint and does not require this feature; buyers SHOULD omit package pacing on seller-optimized buys unless this feature is advertised. A seller that declares seller_optimized_budget without this feature MUST reject a request that would leave package pacing differing from media-buy pacing on a seller-optimized buy, including an allocation-mode switch that retains such fixed-mode package pacing (the buyer can align it in the same update), with `UNSUPPORTED_FEATURE` before any provider mutation, and MUST NOT issue seller-optimized proposals carrying allocation pacing. Does not govern package pacing in fixed allocation.' + ), + ] = None + bidding_policy: Annotated[ + bidding_policy_capability.BiddingPolicyCapability | None, + Field( + description='Structured support for canonical bidding by authored scope, allocation context, mode, strength, and strength-qualified multi-field combination. Presence does not imply support for both scopes, both allocation modes, or every policy shape. Sellers MUST preserve every advertised semantic exactly and reject unadvertised policies rather than translating them.' + ), + ] = None diff --git a/src/adcp/types/domains/core/media_buy_frequency_cap.py b/src/adcp/types/domains/core/media_buy_frequency_cap.py index 6f5a60280..dff95d887 100644 --- a/src/adcp/types/domains/core/media_buy_frequency_cap.py +++ b/src/adcp/types/domains/core/media_buy_frequency_cap.py @@ -1,18 +1,11 @@ -"""Types declared by the AdCP ``core/media_buy_frequency_cap`` schema. +# generated by datamodel-codegen: +# filename: core/media_buy_frequency_cap.json +# timestamp: 2026-09-14T14:16:02+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.media_buy_frequency_cap import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.media_buy_frequency_cap import MediaBuyFrequencyCap +from .frequency_cap import FrequencyCap + -# Explicit exports -__all__ = ["MediaBuyFrequencyCap"] +class MediaBuyFrequencyCap(FrequencyCap): + pass diff --git a/src/adcp/types/domains/core/media_buy_frequency_cap_capability.py b/src/adcp/types/domains/core/media_buy_frequency_cap_capability.py index c9c362464..fd09c87c4 100644 --- a/src/adcp/types/domains/core/media_buy_frequency_cap_capability.py +++ b/src/adcp/types/domains/core/media_buy_frequency_cap_capability.py @@ -1,20 +1,25 @@ -"""Types declared by the AdCP ``core/media_buy_frequency_cap_capability`` schema. +# generated by datamodel-codegen: +# filename: core/media_buy_frequency_cap_capability.json +# timestamp: 2026-09-14T14:16:02+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.media_buy_frequency_cap_capability import +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import media_buy_frequency_cap_control_mode +from .frequency_cap_constraints import FrequencyCapConstraints -from adcp.types.generated_poc.core.media_buy_frequency_cap_capability import ( - MediaBuyFrequencyCapCapability, -) -# Explicit exports -__all__ = ["MediaBuyFrequencyCapCapability"] +class MediaBuyFrequencyCapCapability(FrequencyCapConstraints): + supported_control_modes: Annotated[ + list[media_buy_frequency_cap_control_mode.MediaBuyFrequencyCapControlMode], + Field( + description='FrequencyCap shapes accepted by the product. max_impressions_and_suppress means both controls may appear together and are enforced with AND semantics.', + min_length=1, + ), + ] + supported_per_units: Any + max_impressions_constraints: Any + window_constraints: Any diff --git a/src/adcp/types/domains/core/media_buy_frequency_cap_requirement.py b/src/adcp/types/domains/core/media_buy_frequency_cap_requirement.py index 25296dba4..620276de3 100644 --- a/src/adcp/types/domains/core/media_buy_frequency_cap_requirement.py +++ b/src/adcp/types/domains/core/media_buy_frequency_cap_requirement.py @@ -1,20 +1,19 @@ -"""Types declared by the AdCP ``core/media_buy_frequency_cap_requirement`` schema. +# generated by datamodel-codegen: +# filename: core/media_buy_frequency_cap_requirement.json +# timestamp: 2026-09-14T14:16:02+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.media_buy_frequency_cap_requirement import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import media_buy_frequency_cap_control_mode +from .frequency_cap_requirements import FrequencyCapRequirements -from adcp.types.generated_poc.core.media_buy_frequency_cap_requirement import ( - MediaBuyFrequencyCapRequirement, -) -# Explicit exports -__all__ = ["MediaBuyFrequencyCapRequirement"] +class MediaBuyFrequencyCapRequirement(FrequencyCapRequirements): + supported_control_modes: Annotated[ + list[media_buy_frequency_cap_control_mode.MediaBuyFrequencyCapControlMode] | None, + Field(min_length=1), + ] = None diff --git a/src/adcp/types/domains/core/media_buy_frequency_cap_support.py b/src/adcp/types/domains/core/media_buy_frequency_cap_support.py index ace3b6e95..c8c103793 100644 --- a/src/adcp/types/domains/core/media_buy_frequency_cap_support.py +++ b/src/adcp/types/domains/core/media_buy_frequency_cap_support.py @@ -1,20 +1,22 @@ -"""Types declared by the AdCP ``core/media_buy_frequency_cap_support`` schema. +# generated by datamodel-codegen: +# filename: core/media_buy_frequency_cap_support.json +# timestamp: 2026-09-14T14:16:02+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.media_buy_frequency_cap_support import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import media_buy_frequency_cap_control_mode +from .frequency_cap_constraints import FrequencyCapConstraints -from adcp.types.generated_poc.core.media_buy_frequency_cap_support import ( - MediaBuyFrequencyCapSupport, -) -# Explicit exports -__all__ = ["MediaBuyFrequencyCapSupport"] +class MediaBuyFrequencyCapSupport(FrequencyCapConstraints): + supported_control_modes: Annotated[ + list[media_buy_frequency_cap_control_mode.MediaBuyFrequencyCapControlMode] | None, + Field( + description='FrequencyCap shapes accepted by the product. max_impressions_and_suppress means both controls may appear together and are enforced with AND semantics.', + min_length=1, + ), + ] = None diff --git a/src/adcp/types/domains/core/media_buy_legacy_terms_ref.py b/src/adcp/types/domains/core/media_buy_legacy_terms_ref.py index 90088552e..a6463de92 100644 --- a/src/adcp/types/domains/core/media_buy_legacy_terms_ref.py +++ b/src/adcp/types/domains/core/media_buy_legacy_terms_ref.py @@ -1,18 +1,15 @@ -"""Types declared by the AdCP ``core/media_buy_legacy_terms_ref`` schema. +# generated by datamodel-codegen: +# filename: core/media_buy_legacy_terms_ref.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.media_buy_legacy_terms_ref import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.media_buy_legacy_terms_ref import MediaBuyTermsReference +from adcp.types._scalar import ScalarStr + -# Explicit exports -__all__ = ["MediaBuyTermsReference"] +class MediaBuyTermsReference(ScalarStr): + __slots__ = () + _json_schema_extra = { + 'description': 'Deprecated 3.1 opaque commercial-terms pointer. A 3.2 compatibility projection MAY echo change_term_id here for older buyers, but new buyers MUST prefer change_term_id and MUST NOT assume an arbitrary 3.1 value identifies an accepted change term.', + 'title': 'Media Buy Terms Reference', + } diff --git a/src/adcp/types/domains/core/media_buy_support.py b/src/adcp/types/domains/core/media_buy_support.py index 9f98bdc9b..dcf3c1975 100644 --- a/src/adcp/types/domains/core/media_buy_support.py +++ b/src/adcp/types/domains/core/media_buy_support.py @@ -1,18 +1,29 @@ -"""Types declared by the AdCP ``core/media_buy_support`` schema. +# generated by datamodel-codegen: +# filename: core/media_buy_support.json +# timestamp: 2026-09-14T14:16:02+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.media_buy_support import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import ext as ext_1 +from . import media_buy_frequency_cap_support -from adcp.types.generated_poc.core.media_buy_support import ProductMediaBuySupport -# Explicit exports -__all__ = ["ProductMediaBuySupport"] +class ProductMediaBuySupport(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + frequency_cap: Annotated[ + Literal[True] | None, + Field( + description="This product can participate in the seller's shared counter for a root MediaBuy.frequency_cap. A buy mixing this product with one that lacks this declaration, or whose resolved supported_per_units omits the root cap's per value, is rejected atomically." + ), + ] = None + frequency_cap_constraints: ( + media_buy_frequency_cap_support.MediaBuyFrequencyCapSupport | None + ) = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/media_buy_support_requirements.py b/src/adcp/types/domains/core/media_buy_support_requirements.py index 20e0789ec..5647803db 100644 --- a/src/adcp/types/domains/core/media_buy_support_requirements.py +++ b/src/adcp/types/domains/core/media_buy_support_requirements.py @@ -1,20 +1,27 @@ -"""Types declared by the AdCP ``core/media_buy_support_requirements`` schema. +# generated by datamodel-codegen: +# filename: core/media_buy_support_requirements.json +# timestamp: 2026-09-14T14:16:02+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.media_buy_support_requirements import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import media_buy_frequency_cap_requirement -from adcp.types.generated_poc.core.media_buy_support_requirements import ( - ProductMediaBuySupportRequirements, -) -# Explicit exports -__all__ = ["ProductMediaBuySupportRequirements"] +class ProductMediaBuySupportRequirements(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + frequency_cap: Annotated[ + Literal[True] | None, + Field( + description='Require product participation in one shared MediaBuy frequency-cap counter.' + ), + ] = None + frequency_cap_constraints: ( + media_buy_frequency_cap_requirement.MediaBuyFrequencyCapRequirement | None + ) = None diff --git a/src/adcp/types/domains/core/missing_metric.py b/src/adcp/types/domains/core/missing_metric.py index fbab8ec02..a9e5c46ac 100644 --- a/src/adcp/types/domains/core/missing_metric.py +++ b/src/adcp/types/domains/core/missing_metric.py @@ -1,23 +1,67 @@ -"""Types declared by the AdCP ``core/missing_metric`` schema. +# generated by datamodel-codegen: +# filename: core/missing_metric.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.missing_metric import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import attribution_methodology as attribution_methodology_1 +from ..enums import available_metric +from ..enums import completion_source as completion_source_1 +from ..enums import lift_dimension as lift_dimension_1 +from ..enums import viewability_standard as viewability_standard_1 +from . import brand_ref, duration, vendor_metric_id + + +class Qualifier(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + viewability_standard: viewability_standard_1.ViewabilityStandard | None = None + completion_source: completion_source_1.CompletionSource | None = None + attribution_methodology: attribution_methodology_1.AttributionMethodology | None = None + attribution_window: duration.Duration | None = None + lift_dimension: lift_dimension_1.LiftDimension | None = None + + +class MissingMetric1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + scope: Literal['standard'] = 'standard' + metric_id: available_metric.AvailableMetric + qualifier: Annotated[ + Qualifier | None, + Field( + description='Mirrors the qualifier on `committed_metrics` so the missing entry preserves the contract distinction (e.g., flagging MRC viewability as missing when only GroupM was reported, vendor-attested completion as missing when only seller-attested was reported, or deterministic_purchase attribution as missing when only probabilistic was reported). MUST match the qualifier on the corresponding `committed_metrics` entry the missing flag refers to.' + ), + ] = None + + +class MissingMetric2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + scope: Literal['vendor'] = 'vendor' + vendor: brand_ref.BrandReference + metric_id: vendor_metric_id.VendorMetricId + qualifier: Annotated[ + Qualifier | None, + Field( + description='Mirrors the qualifier on the corresponding vendor-scope `committed_metrics` entry. MUST match that entry so reconciliation joins on (vendor, metric_id, qualifier).' + ), + ] = None -from adcp.types.generated_poc.core.missing_metric import ( - MissingMetric, - MissingMetric1, - MissingMetric2, - Qualifier, -) -# Explicit exports -__all__ = ["MissingMetric", "MissingMetric1", "MissingMetric2", "Qualifier"] +MissingMetric = Annotated[ + MissingMetric1 | MissingMetric2, + Field( + description='One metric the binding reporting contract declared but that is not populated in a delivery report. Symmetric with `committed_metrics` and discriminated by `scope`.', + discriminator='scope', + title='Missing Metric', + ), +] diff --git a/src/adcp/types/domains/core/negative_keyword.py b/src/adcp/types/domains/core/negative_keyword.py index 2d1aec821..bbd836d29 100644 --- a/src/adcp/types/domains/core/negative_keyword.py +++ b/src/adcp/types/domains/core/negative_keyword.py @@ -1,18 +1,20 @@ -"""Types declared by the AdCP ``core/negative_keyword`` schema. +# generated by datamodel-codegen: +# filename: core/negative_keyword.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.negative_keyword import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import match_type as match_type_1 -from adcp.types.generated_poc.core.negative_keyword import NegativeKeyword -# Explicit exports -__all__ = ["NegativeKeyword"] +class NegativeKeyword(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + keyword: Annotated[str, Field(description='The keyword to exclude', min_length=1)] + match_type: match_type_1.MatchType diff --git a/src/adcp/types/domains/core/notification_config.py b/src/adcp/types/domains/core/notification_config.py index 95a5b1f11..4d4905261 100644 --- a/src/adcp/types/domains/core/notification_config.py +++ b/src/adcp/types/domains/core/notification_config.py @@ -1,23 +1,102 @@ -"""Types declared by the AdCP ``core/notification_config`` schema. +# generated by datamodel-codegen: +# filename: core/notification_config.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.notification_config import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import auth_scheme +from . import ext as ext_1 + + +class EventType(StrEnum): + creative_status_changed = 'creative.status_changed' + creative_assignment_changed = 'creative.assignment_changed' + indicators_changed = 'indicators.changed' + creative_purged = 'creative.purged' + account_status_changed = 'account.status_changed' + account_change_recorded = 'account.change_recorded' + product_created = 'product.created' + product_updated = 'product.updated' + product_priced = 'product.priced' + product_removed = 'product.removed' + signal_created = 'signal.created' + signal_updated = 'signal.updated' + signal_priced = 'signal.priced' + signal_removed = 'signal.removed' + wholesale_feed_bulk_change = 'wholesale_feed.bulk_change' + reporting_delivery_ready = 'reporting.delivery_ready' + reporting_status_changed = 'reporting.status_changed' + reporting_ledger_changed = 'reporting.ledger_changed' + + +class ProductPayloadView(StrEnum): + canonical = 'canonical' + legacy = 'legacy' + + +class Authentication(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + schemes: Annotated[list[auth_scheme.AuthenticationScheme], Field(max_length=1, min_length=1)] + credentials: Annotated[ + str | None, + Field( + description='Credentials for the legacy scheme. Bearer: token. HMAC-SHA256: shared secret. Minimum 32 characters. Exchanged out-of-band during onboarding. Write-only.', + min_length=32, + ), + ] = None -from adcp.types.generated_poc.core.notification_config import ( - Authentication, - EventType, - NotificationConfig, - ProductPayloadView, -) -# Explicit exports -__all__ = ["Authentication", "EventType", "NotificationConfig", "ProductPayloadView"] +class NotificationConfig(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + subscriber_id: Annotated[ + str, + Field( + description="Buyer-supplied identifier for this subscription endpoint. This is the stable logical key within one account's notification_configs[] set: re-sending the same subscriber_id for the same account replaces that subscriber's URL, event_types, authentication selector, and active flag rather than creating a duplicate. Echoed on every webhook payload and on every `webhook_activity[]` record fired against this config so the buyer can attribute fires across multiple endpoints. MUST be unique within the account's `notification_configs[]`. Sending two entries with the same `subscriber_id` in a single `sync_accounts` request array is rejected as a per-account validation failure with `INVALID_REQUEST` or `VALIDATION_ERROR`, and `error.field` MUST point at the duplicate entry. `subscriber_id` is the stable match key for the per-account declarative-replace diff. Always required (even with a single subscriber) so the SDK contract is uniform — no conditional required-when-multiple rules to trip up implementations. Format is opaque — recommended values are short kebab-case slugs (`buyer-primary`, `audit-bus`, `dx-team`).", + max_length=64, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,64}$', + ), + ] + url: Annotated[ + AnyUrl, + Field( + description='Webhook endpoint URL. Same wire contract as `push-notification-config.url` — `format: "uri"`, no destination-port allowlist enforced by the protocol, SSRF protection via the IP-range check defined in docs/building/by-layer/L1/security.mdx#webhook-url-validation-ssrf. Sellers MUST validate URL syntax, HTTPS usage, hostname normalization, and reserved-range rejection when writing any config, including `active: false` configs. Sellers MUST complete an activation challenge or equivalent proof-of-control before treating a new or changed active subscriber as active.' + ), + ] + event_types: Annotated[ + list[EventType], + Field( + description='Account-anchored notification types this subscriber wishes to receive on the registered `url`. The seller MUST NOT fire other types against this endpoint, and MUST NOT silently widen the filter when new account-anchored types are added. Creative lifecycle, assignment, indicator, account status, wholesale feed, reporting.delivery_ready, reporting.status_changed, and reporting.ledger_changed events are valid here; media-buy-anchored types (`scheduled`, `final`, `delayed`, `adjusted`, `window_update`, `impairment`) and agent-anchored types (`capabilities.changed`) are schema-invalid on this surface and sellers MUST reject those entries as per-account validation failures with `INVALID_REQUEST` or `VALIDATION_ERROR` and `error.field` pointing at the invalid `event_types` entry rather than silently dropping them.', + min_length=1, + ), + ] + product_payload_view: Annotated[ + ProductPayloadView | None, + Field( + description='Product webhook representation selected by this subscriber. Use canonical with lifecycle_tools.list_products; legacy is the default for 3.x get_products consumers. Sellers emit exactly canonical_product/canonical_pricing_options or product/pricing_options accordingly. Valid only when event_types includes a product.* event.' + ), + ] = ProductPayloadView.legacy + authentication: Annotated[ + Authentication | None, + Field( + deprecated=True, + description="Legacy authentication selector. Same precedence and semantics as `push-notification-config.authentication` — presence opts the seller into Bearer or HMAC-SHA256 signing; absence selects the default RFC 9421 webhook profile keyed off the seller's brand.json `agents[]` JWKS. The same signed-registration downgrade-resistance rules apply to accounts[].notification_configs[].authentication. Deprecated; removed in AdCP 4.0. Credentials are write-only and MUST NOT be echoed on `list_accounts` reads.", + ), + ] = None + active: Annotated[ + StrictBool | None, + Field( + description="When false, the seller persists the configuration but suppresses fires. Use to pause a noisy subscriber without losing the registration. Sellers MUST NOT skip persisting the entry when `active: false` — the buyer's next `sync_accounts` MUST observe the same array, otherwise the buyer cannot distinguish pause from drop. Paused configs may skip only the outbound proof challenge while inactive; sellers MUST still enforce URL parsing, HTTPS, hostname normalization, and reserved-range rejection at write time. Reactivation requires full SSRF validation with connect pinning plus proof-of-control for any tuple without current valid proof." + ), + ] = True + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/offering.py b/src/adcp/types/domains/core/offering.py index 445eaded7..ea6d21f8f 100644 --- a/src/adcp/types/domains/core/offering.py +++ b/src/adcp/types/domains/core/offering.py @@ -1,18 +1,143 @@ -"""Types declared by the AdCP ``core/offering`` schema. +# generated by datamodel-codegen: +# filename: core/offering.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.offering import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import metro_system +from . import ext as ext_1 +from . import offering_asset_group, postal_area + + +class Country(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{2}$'} + + +class Region(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{2}-[A-Z0-9]{1,3}$'} + + +class Metro(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + system: Annotated[ + metro_system.MetroAreaSystem, + Field(description="Metro area classification system (e.g., 'nielsen_dma', 'uk_itl2')"), + ] + values: Annotated[list[str], Field(description='Metro codes within the system', min_length=1)] + + +class GeoTargets(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + countries: Annotated[ + list[Country] | None, + Field( + description="Countries where this offering is relevant. ISO 3166-1 alpha-2 codes (e.g., 'US', 'NL', 'DE').", + min_length=1, + ), + ] = None + regions: Annotated[ + list[Region] | None, + Field( + description="Regions or states where this offering is relevant. ISO 3166-2 subdivision codes (e.g., 'NL-NH', 'US-CA').", + min_length=1, + ), + ] = None + metros: Annotated[ + list[Metro] | None, + Field( + description='Metro areas where this offering is relevant. Each entry specifies the classification system and target values.', + min_length=1, + ), + ] = None + postal_areas: Annotated[ + list[postal_area.PostalArea] | None, + Field( + description='Postal areas where this offering is relevant. Prefer the native country + postal system form. Deprecated legacy country-fused postal-system tokens remain accepted for compatibility.', + min_length=1, + ), + ] = None -from adcp.types.generated_poc.core.offering import Country, GeoTargets, Metro, Offering, Region -# Explicit exports -__all__ = ["Country", "GeoTargets", "Metro", "Offering", "Region"] +class Offering(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + offering_id: Annotated[ + str, + Field( + description='Unique identifier for this offering. Used by hosts to reference specific offerings in si_get_offering calls.' + ), + ] + name: Annotated[ + str, + Field( + description="Human-readable offering name (e.g., 'Winter Sale', 'Free Trial', 'Enterprise Platform')" + ), + ] + description: Annotated[str | None, Field(description="Description of what's being offered")] = ( + None + ) + tagline: Annotated[ + str | None, Field(description='Short promotional tagline for the offering') + ] = None + valid_from: Annotated[ + AwareDatetime | None, + Field( + description='When the offering becomes available. If not specified, offering is immediately available.' + ), + ] = None + valid_to: Annotated[ + AwareDatetime | None, + Field( + description='When the offering expires. If not specified, offering has no expiration.' + ), + ] = None + checkout_url: Annotated[ + AnyUrl | None, + Field( + description="URL for checkout/purchase flow when the brand doesn't support agentic checkout." + ), + ] = None + landing_url: Annotated[ + AnyUrl | None, + Field( + description="Landing page URL for this offering. For catalog-driven creatives, this is the per-item click-through destination that platforms map to the ad's link-out URL. Every offering in a catalog should have a landing_url unless the format provides its own destination logic." + ), + ] = None + assets: Annotated[ + list[offering_asset_group.OfferingAssetGroup] | None, + Field( + description='Structured asset groups for this offering. Each group carries a typed pool of creative assets (headlines, images, videos, etc.) identified by a group ID that matches format-level vocabulary.' + ), + ] = None + geo_targets: Annotated[ + GeoTargets | None, + Field( + description="Geographic scope of this offering. Declares where the offering is relevant — for location-specific offerings such as job vacancies, in-store promotions, or local events. Platforms use this to target geographically appropriate audiences and to filter out offerings irrelevant to a user's location. Uses the same geographic structures as targeting_overlay in create_media_buy." + ), + ] = None + keywords: Annotated[ + list[str] | None, + Field( + description='Keywords for matching this offering to user intent. Hosts use these for retrieval/relevance scoring.' + ), + ] = None + categories: Annotated[ + list[str] | None, + Field( + description="Categories this offering belongs to (e.g., 'measurement', 'identity', 'programmatic')" + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/offering_asset_group.py b/src/adcp/types/domains/core/offering_asset_group.py index aac2d7e46..cc091b559 100644 --- a/src/adcp/types/domains/core/offering_asset_group.py +++ b/src/adcp/types/domains/core/offering_asset_group.py @@ -1,18 +1,70 @@ -"""Types declared by the AdCP ``core/offering_asset_group`` schema. +# generated by datamodel-codegen: +# filename: core/offering_asset_group.json +# timestamp: 2026-10-03T00:36:50+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.offering_asset_group import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import asset_content_type +from . import ext as ext_1 +from .assets import ( + audio_asset, + css_asset, + daast_asset, + html_asset, + image_asset, + javascript_asset, + markdown_asset, + text_asset, + url_asset, + vast_asset, + video_asset, + webhook_asset, + zip_asset, +) + + +Items = Annotated[ + text_asset.TextAsset + | image_asset.ImageAsset + | video_asset.VideoAsset + | audio_asset.AudioAsset + | url_asset.UrlAsset + | html_asset.HtmlAsset + | markdown_asset.MarkdownAsset + | vast_asset.VastAsset + | daast_asset.DaastAsset + | css_asset.CssAsset + | javascript_asset.JavascriptAsset + | zip_asset.ZipAsset + | webhook_asset.WebhookAsset, + Field(discriminator='asset_type'), +] -from adcp.types.generated_poc.core.offering_asset_group import Items, OfferingAssetGroup -# Explicit exports -__all__ = ["Items", "OfferingAssetGroup"] +class OfferingAssetGroup(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_group_id: Annotated[ + str, + Field( + description="Identifies the creative role this group fills. Values are defined by each canonical format declaration's offering_asset_constraints — not protocol constants. Discover creative-agent declarations via get_adcp_capabilities creative.supported_formats[] and sales-product declarations via Product.format_options[] (e.g., 'headlines', 'images', or 'videos')." + ), + ] + asset_type: Annotated[ + asset_content_type.AssetContentType, + Field(description='The content type of all items in this group.'), + ] + items: Annotated[ + list[Items], + Field( + description='The assets in this group. Each item carries an `asset_type` discriminator that selects the matching asset schema. Note: the group-level `asset_type` declares the expected type; individual items must also self-tag so validators can narrow errors. Intentionally excludes `brief-asset` and `catalog-asset` — those are campaign-input metadata types, not delivery-ready creative assets suitable for a pooled offering group. See core/assets/asset-union.json for the full asset-variant union.', + min_length=1, + ), + ] + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/operator_identity.py b/src/adcp/types/domains/core/operator_identity.py index 0db9df340..149b12a0e 100644 --- a/src/adcp/types/domains/core/operator_identity.py +++ b/src/adcp/types/domains/core/operator_identity.py @@ -1,18 +1,31 @@ -"""Types declared by the AdCP ``core/operator_identity`` schema. +# generated by datamodel-codegen: +# filename: core/operator_identity.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.operator_identity import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import operator_unit as operator_unit_1 -from adcp.types.generated_poc.core.operator_identity import OperatorIdentity -# Explicit exports -__all__ = ["OperatorIdentity"] +class OperatorIdentity(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + operator: Annotated[ + str, + Field( + description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] + operator_unit: Annotated[ + operator_unit_1.OperatorUnit | None, + Field( + description='Optional operator-owned business unit, agency seat, or platform account. Its id participates in account identity; name is mutable display metadata. Omission from a complete desired replacement means the account should have no operator unit.' + ), + ] = None diff --git a/src/adcp/types/domains/core/operator_unit.py b/src/adcp/types/domains/core/operator_unit.py index 17f9efcb3..d3fe73828 100644 --- a/src/adcp/types/domains/core/operator_unit.py +++ b/src/adcp/types/domains/core/operator_unit.py @@ -1,18 +1,33 @@ -"""Types declared by the AdCP ``core/operator_unit`` schema. +# generated by datamodel-codegen: +# filename: core/operator_unit.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.operator_unit import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -from adcp.types.generated_poc.core.operator_unit import OperatorUnit -# Explicit exports -__all__ = ["OperatorUnit"] +class OperatorUnit(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + id: Annotated[ + str, + Field( + description='Stable identifier assigned by the operator. Numeric platform IDs and durable slugs are both valid. Scoped by the enclosing operator domain.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9][A-Za-z0-9._:/-]*$', + ), + ] + name: Annotated[ + str | None, + Field( + description='Human-readable seat or business-unit name, such as Nova EMEA. This label may change and is not part of the natural account key.', + max_length=200, + min_length=1, + ), + ] = None diff --git a/src/adcp/types/domains/core/opportunity_context.py b/src/adcp/types/domains/core/opportunity_context.py index 7901ad0fa..495781001 100644 --- a/src/adcp/types/domains/core/opportunity_context.py +++ b/src/adcp/types/domains/core/opportunity_context.py @@ -1,24 +1,89 @@ -"""Types declared by the AdCP ``core/opportunity_context`` schema. +# generated by datamodel-codegen: +# filename: core/opportunity_context.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.opportunity_context import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import date_range + + +class Phase(StrEnum): + exploratory = 'exploratory' + planning = 'planning' + active_sourcing = 'active_sourcing' + + +class Intent(StrEnum): + test = 'test' + speculative = 'speculative' + planning = 'planning' + live_rfp = 'live_rfp' + + +class Status(StrEnum): + open = 'open' + closed = 'closed' + + +class CloseReason(StrEnum): + accepted_with_seller = 'accepted_with_seller' + purchased_elsewhere = 'purchased_elsewhere' + selected_alternative = 'selected_alternative' + not_pursued = 'not_pursued' + budget_changed = 'budget_changed' + timing_changed = 'timing_changed' + other = 'other' -from adcp.types.generated_poc.core.opportunity_context import ( - CloseReason, - Intent, - OpportunityContext, - Phase, - Status, -) -# Explicit exports -__all__ = ["CloseReason", "Intent", "OpportunityContext", "Phase", "Status"] +class OpportunityContext(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + opportunity_id: Annotated[ + str, + Field( + description='Opaque buyer-assigned identifier for this planning cycle, scoped to the seller and account.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] + phase: Annotated[Phase | None, Field(description='Current stage of buyer planning.')] = None + intent: Annotated[ + Intent | None, + Field(description='How seriously the buyer is evaluating supply in this cycle.'), + ] = None + planning_horizon: Annotated[ + date_range.DateRange | None, + Field( + description='Inclusive calendar range in which the buyer expects the campaign to run.' + ), + ] = None + response_deadline: Annotated[ + AwareDatetime | None, + Field(description='Deadline by which the buyer needs a seller response.'), + ] = None + status: Annotated[ + Status | None, + Field( + description='Whether the planning cycle remains open. On calls after initial creation, omission means no status update and MUST NOT reopen or close the opportunity implicitly, except that successful create_media_buy proposal execution explicitly infers accepted closure.' + ), + ] = None + close_reason: Annotated[ + CloseReason | None, + Field(description='Why the opportunity closed. Required when status is closed.'), + ] = None + close_detail: Annotated[ + str | None, + Field( + description='Optional non-sensitive context about closure. MUST NOT identify a competitor or disclose confidential clearing terms.', + max_length=500, + min_length=1, + ), + ] = None diff --git a/src/adcp/types/domains/core/optimization_goal.py b/src/adcp/types/domains/core/optimization_goal.py index 01e584fb7..b824e1591 100644 --- a/src/adcp/types/domains/core/optimization_goal.py +++ b/src/adcp/types/domains/core/optimization_goal.py @@ -1,48 +1,310 @@ -"""Types declared by the AdCP ``core/optimization_goal`` schema. +# generated by datamodel-codegen: +# filename: core/optimization_goal.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.optimization_goal import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictFloat + +from ..enums import event_type as event_type_1 +from ..enums import reach_unit as reach_unit_1 +from ..enums import viewability_standard +from . import attribution_window as attribution_window_1 +from . import brand_ref, duration, vendor_metric_id + + +class Metric(StrEnum): + clicks = 'clicks' + views = 'views' + completed_views = 'completed_views' + viewed_seconds = 'viewed_seconds' + viewable_rate = 'viewable_rate' + attention_seconds = 'attention_seconds' + attention_score = 'attention_score' + engagements = 'engagements' + follows = 'follows' + saves = 'saves' + profile_visits = 'profile_visits' + reach = 'reach' + + +class TargetFrequency(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + min: Annotated[ + SchemaInt | None, + Field( + description='Minimum frequency for an entity to be considered meaningfully reached within the window. Impressions that would bring an entity below this threshold are treated as high-value (growing reach). When omitted, the seller uses their platform default (typically 1).', + ge=1, + ), + ] = None + max: Annotated[ + SchemaInt | None, + Field( + description='Frequency at which an entity is considered saturated within the window. Impressions toward entities at or above this threshold are treated as lower-value. When both min and max are present, max must be greater than or equal to min. When omitted, the seller determines the saturation point.', + ge=1, + ), + ] = None + window: Annotated[ + duration.Duration, + Field( + description='Time window over which frequency is measured (e.g. {"interval": 7, "unit": "days"} or {"interval": 1, "unit": "campaign"} for the full flight). Weekly windows are typical for brand campaigns; daily windows suit high-cadence direct response.' + ), + ] + + +class Target(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + kind: Literal['cost_per'] = 'cost_per' + value: Annotated[ + StrictFloat, Field(description='Target cost per metric unit in the buy currency', gt=0.0) + ] + + +class Target14(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + kind: Literal['threshold_rate'] = 'threshold_rate' + value: Annotated[ + StrictFloat, + Field( + description='Minimum per-impression value. Units depend on the metric: proportion (clicks, views, completed_views, viewable_rate), seconds (viewed_seconds, attention_seconds), or score (attention_score).', + gt=0.0, + ), + ] + + +class EventSource(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + event_source_id: Annotated[ + str, + Field( + description='Event source to include (must be configured on this account via sync_event_sources)', + min_length=1, + ), + ] + event_type: Annotated[ + event_type_1.EventType, + Field( + description='Event type to include from this source (e.g., purchase, lead, app_install, refund)' + ), + ] + custom_event_name: Annotated[ + str | None, + Field( + description="Required when event_type is 'custom'. Platform-specific name for the custom event." + ), + ] = None + value_field: Annotated[ + str | None, + Field( + description="Which field in the event's custom_data carries the monetary value. The seller must use this field for value extraction and aggregation when computing ROAS and conversion value metrics. Required on at least one entry when target.kind is 'per_ad_spend' or 'maximize_value' — sellers must reject these target kinds when no event source entry includes value_field. When canonical bidding.roas binds to this goal, every referenced value-bearing source MUST declare value_currencies containing the media-buy currency; the seller validates this at buy creation. Once that source contract is declared, log_event rejects monetary records whose currency is missing or absent from the list. For each buy, only records exactly matching its media-buy currency contribute to ROAS; records in other declared currencies remain available to matching buys, and sellers MUST NOT convert between them. When present without a value-oriented target, the seller may use it for delivery reporting but must not change the objective. Common values: 'value', 'order_total', 'profit_margin'. This is not passed as a parameter to underlying platform APIs — the seller maps it to their platform's value ingestion mechanism." + ), + ] = None + value_factor: Annotated[ + StrictFloat | None, + Field( + description="Unit-scaling multiplier the seller must apply to value_field before aggregation. Use -1 for refund events (negate the value), 0.01 for values in cents, -0.01 for refunds in cents. It MUST NOT be used for currency conversion. A value of 0 zeroes out this source's value contribution (the source still counts for event dedup). Defaults to 1. This is not passed as a parameter to underlying platform APIs — the seller applies it when computing aggregated value metrics." + ), + ] = 1 + + +class Target15(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + kind: Literal['cost_per'] = 'cost_per' + value: Annotated[ + StrictFloat, Field(description='Target cost per event in the buy currency', gt=0.0) + ] + + +class Target16(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + kind: Literal['per_ad_spend'] = 'per_ad_spend' + value: Annotated[ + StrictFloat, + Field(description='Target return ratio (e.g., 4.0 means $4 of value per $1 spent)', gt=0.0), + ] + + +class Target17(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + kind: Literal['maximize_value'] = 'maximize_value' + + +class Target18(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + kind: Literal['cost_per'] = 'cost_per' + value: Annotated[ + StrictFloat, + Field( + description='Target cost per metric unit in the buy currency. Units of the metric are vendor-defined.', + gt=0.0, + ), + ] + + +class Target19(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + kind: Literal['threshold_rate'] = 'threshold_rate' + value: Annotated[ + StrictFloat, + Field( + description='Minimum per-impression value. Units of the metric are vendor-defined.', + gt=0.0, + ), + ] + + +class OptimizationGoal9(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + kind: Literal['event'] = 'event' + event_sources: Annotated[ + list[EventSource], + Field( + description='Event source and type pairs that feed this goal. Each entry identifies a source and event type to include. When the seller supports multi_source_event_dedup (declared in get_adcp_capabilities), they deduplicate by event_id across all entries — the same business event from multiple sources counts once, using value_field and value_factor from the first matching entry. When multi_source_event_dedup is false or absent, buyers should use a single entry per goal; the seller will use only the first entry. All event sources must be configured via sync_event_sources.', + min_length=1, + ), + ] + target: Annotated[ + Target15 | Target16 | Target17 | None, + Field( + description='Target cost or return for this event goal. When omitted, the seller optimizes for maximum conversion count within budget — regardless of whether value_field is present on event sources. The presence of value_field alone does not change the optimization objective; it only makes value available for reporting. An explicit target of maximize_value or per_ad_spend is required to steer toward value.', + discriminator='kind', + ), + ] = None + attribution_window: Annotated[ + attribution_window_1.AttributionWindow | None, + Field( + description="Attribution window for this optimization goal — references the canonical `attribution-window` shape (post_click, post_view, model). Values must match an option declared in the seller's `conversion_tracking.attribution_windows` capability. Sellers MUST reject windows not in their declared capabilities. When the entire field is omitted, the seller uses their default window." + ), + ] = None + priority: Annotated[ + SchemaInt | None, + Field( + description='Relative priority among sibling goals. Lower numbers rank first. Goals without priority follow explicitly prioritized goals. Ties use array order, so the earliest goal at the lowest explicit priority is primary; when all priorities are omitted, the first goal is primary.', + ge=1, + ), + ] = None + + +class OptimizationGoal8(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + kind: Literal['metric'] = 'metric' + metric: Annotated[ + Metric, + Field( + description="Seller-native metric to optimize for. Delivery metrics: clicks (link clicks, swipe-throughs, CTA taps that navigate away), views (content views at the billable view threshold, as defined by delivery-metrics `views`; for viewability use viewable_rate), completed_views (video/audio completions — see view_duration_seconds), reach (unique audience reach — see reach_unit and target_frequency). Duration/score metrics: viewed_seconds (time in view per impression — reported back via `delivery-metrics.viewability.viewed_seconds`, governed by the viewability `standard`). Quality-rate metrics: viewable_rate (viewable / measurable impressions under the goal's `standard`; requires `standard`). Audience action metrics: engagements (any direct interaction with the ad unit beyond viewing — social reactions/comments/shares, story/unit opens, interactive overlay taps, companion banner interactions on audio and CTV), follows (new followers, page likes, artist/podcast/channel follows, or free channel/feed subscribes; paid subscriptions use event_type: subscribe), saves (saves, bookmarks, playlist adds, pins — signals of intent to return), profile_visits (visits to the brand's in-platform page — profile, artist page, channel, or storefront. Does not include external website clicks, which are covered by 'clicks'). **DEPRECATED values** (slated for removal at next major): `attention_seconds` and `attention_score` — these have no industry-graduated definition (DoubleVerify, IAS, Adelaide, TVision, Lumen each define them differently) and cannot be meaningfully optimized for without a vendor binding. Use `kind: 'vendor_metric'` with an explicit `vendor` and `metric_id` instead — that path binds the goal to a specific measurement vendor and reconciles to the same `(vendor, metric_id)` key in delivery's `vendor_metric_values[]`. Sellers MAY reject the deprecated values with `TERMS_REJECTED` and a suggestion to use the `vendor_metric` kind." + ), + ] + standard: Annotated[ + viewability_standard.ViewabilityStandard | None, + Field( + description="Viewability standard the goal is judged against. Required when metric is 'viewable_rate'; optional for 'viewed_seconds' (seller default standard when omitted); not allowed for other metrics. Must be in metric_optimization.supported_viewability_standards when declared. A goal below a same-standard viewability performance_standard never relaxes that standard." + ), + ] = None + vendor: Annotated[ + brand_ref.BrandReference | None, + Field( + description="Measurement vendor judging a viewable_rate or viewed_seconds goal; not allowed for other metrics. When omitted, the seller's default viewability measurement applies. Sellers MUST reject a vendor they cannot optimize against rather than substitute another." + ), + ] = None + reach_unit: Annotated[ + reach_unit_1.ReachUnit | None, + Field( + description="Unit for reach measurement. Required when metric is 'reach'. Must be a value declared in the product's metric_optimization.supported_reach_units." + ), + ] = None + target_frequency: Annotated[ + TargetFrequency | None, + Field( + description="Target frequency band for reach optimization. Only applicable when metric is 'reach'. Frames frequency as an optimization signal: the seller should treat impressions toward entities already within the [min, max] band as lower-value, and impressions toward unreached entities as higher-value. This shifts budget toward fresh reach rather than re-reaching known users. When omitted, the seller maximizes unique reach without a frequency constraint. A hard cap can still be layered via targeting_overlay.frequency_cap if a ceiling is needed." + ), + ] = None + view_duration_seconds: Annotated[ + StrictFloat | None, + Field( + description="Minimum video view duration in seconds that qualifies as a completed_view for this goal. Only applicable when metric is 'completed_views'. When omitted, the seller uses their platform default (typically 2–15 seconds). Common values: 2 (Snap/LinkedIn default), 6 (TikTok), 15 (Snap 15-second views, Meta ThruPlay). Sellers declare which durations they support in metric_optimization.supported_view_durations. Sellers must reject goals with unsupported values — silent rounding would create measurement discrepancies.", + gt=0.0, + ), + ] = None + target: Annotated[ + Target | Target14 | None, + Field( + description='Target for this metric. When omitted, the seller optimizes for maximum metric volume within budget.', + discriminator='kind', + ), + ] = None + priority: Annotated[ + SchemaInt | None, + Field( + description='Relative priority among sibling goals. Lower numbers rank first. Goals without priority follow explicitly prioritized goals. Ties use array order, so the earliest goal at the lowest explicit priority is primary; when all priorities are omitted, the first goal is primary.', + ge=1, + ), + ] = None + + +class OptimizationGoal10(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + kind: Literal['vendor_metric'] = 'vendor_metric' + vendor: Annotated[ + brand_ref.BrandReference, + Field( + description='Vendor that defines and computes this metric. Same shape as `vendor_metric_values.vendor`, `reporting_capabilities.vendor_metrics[].vendor`, and `vendor_metric_optimization.supported_metrics[].vendor` — symmetric across discovery, capability, commitment, optimization, and reporting surfaces.' + ), + ] + metric_id: Annotated[ + vendor_metric_id.VendorMetricId, + Field( + description="Identifier for the metric within the vendor's vocabulary (e.g., `attention_score`, `attention_seconds`, `gco2e_per_impression`, `awareness_lift`). MUST be present in the vendor's published `measurement.metrics[]` catalog and in the product's `vendor_metric_optimization.supported_metrics[]`." + ), + ] + target: Annotated[ + Target18 | Target19 | None, + Field( + description="Target for this vendor metric. When omitted, the seller optimizes for maximum metric volume / score within budget. `cost_per` and `threshold_rate` semantics mirror the same target kinds on the `metric` kind — units are vendor-defined and depend on the vendor's `measurement.metrics[]` declaration for this `metric_id`.", + discriminator='kind', + ), + ] = None + priority: Annotated[ + SchemaInt | None, + Field( + description='Relative priority among sibling goals. Lower numbers rank first. Goals without priority follow explicitly prioritized goals. Ties use array order, so the earliest goal at the lowest explicit priority is primary; when all priorities are omitted, the first goal is primary.', + ge=1, + ), + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.optimization_goal import ( - EventSource, - Metric, - OptimizationGoal, - OptimizationGoal10, - OptimizationGoal8, - OptimizationGoal9, - Target, - Target14, - Target15, - Target16, - Target17, - Target18, - Target19, - TargetFrequency, -) - -# Explicit exports -__all__ = [ - "EventSource", - "Metric", - "OptimizationGoal", - "OptimizationGoal10", - "OptimizationGoal8", - "OptimizationGoal9", - "Target", - "Target14", - "Target15", - "Target16", - "Target17", - "Target18", - "Target19", - "TargetFrequency", +OptimizationGoal = Annotated[ + OptimizationGoal8 | OptimizationGoal9 | OptimizationGoal10, + Field( + description='A single objective function: what to maximize or optimize, in what units, and in what priority order. Used on packages to optimize delivery within one package and on seller-optimized budget allocations to allocate spend across packages. Currency-bearing execution policy belongs in BiddingPolicy in 3.2. Legacy target.cost_per and target.per_ad_spend remain accepted only on package goals for migration and are deprecated. The primary goal is the earliest array entry among goals with the lowest explicit numeric priority; goals without priority follow all explicitly prioritized goals; when all priorities are omitted, the first entry is primary. This array-order tie-break makes duplicate priorities deterministic.', + discriminator='kind', + title='Optimization Goal', + ), ] diff --git a/src/adcp/types/domains/core/outcome_measurement.py b/src/adcp/types/domains/core/outcome_measurement.py index 5cc07f4e5..95e9e18cf 100644 --- a/src/adcp/types/domains/core/outcome_measurement.py +++ b/src/adcp/types/domains/core/outcome_measurement.py @@ -1,18 +1,45 @@ -"""Types declared by the AdCP ``core/outcome_measurement`` schema. +# generated by datamodel-codegen: +# filename: core/outcome_measurement.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.outcome_measurement import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import duration -from adcp.types.generated_poc.core.outcome_measurement import OutcomeMeasurement -# Explicit exports -__all__ = ["OutcomeMeasurement"] +class OutcomeMeasurement(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + type: Annotated[ + str, + Field( + description='Type of measurement', + examples=['incremental_sales_lift', 'brand_lift', 'foot_traffic'], + ), + ] + attribution: Annotated[ + str, + Field( + description='Attribution methodology', + examples=['deterministic_purchase', 'probabilistic'], + ), + ] + window: Annotated[ + duration.Duration | None, + Field( + description='Attribution window as a structured duration (e.g., {"interval": 30, "unit": "days"}).' + ), + ] = None + reporting: Annotated[ + str, + Field( + description='Reporting frequency and format', + examples=['weekly_dashboard', 'real_time_api'], + ), + ] diff --git a/src/adcp/types/domains/core/outcome_target_cost_per.py b/src/adcp/types/domains/core/outcome_target_cost_per.py index 086ac2ed1..9f17bf5a1 100644 --- a/src/adcp/types/domains/core/outcome_target_cost_per.py +++ b/src/adcp/types/domains/core/outcome_target_cost_per.py @@ -1,18 +1,38 @@ -"""Types declared by the AdCP ``core/outcome_target_cost_per`` schema. +# generated by datamodel-codegen: +# filename: core/outcome_target_cost_per.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.outcome_target_cost_per import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import outcome_target_cost_strength -from adcp.types.generated_poc.core.outcome_target_cost_per import OutcomeTargetCostPer -# Explicit exports -__all__ = ["OutcomeTargetCostPer"] +class OutcomeTargetCostPer(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + amount: Annotated[ + StrictFloat, + Field( + description="Average cost amount per goal result, denominated in currency. The answered commercial_terms.bidding.cost_per.amount MUST be greater than or equal to it: the requested amount when it is plannable, otherwise the lowest plannable amount. An amount is plannable when the seller can forecast goal volume at or below (cap) or around (target) it within the buyer's budget, spending at least offer_filters.budget_range.min when present. When the planned spend at the answered amount is below commercial_terms.total_budget, each forecast point MUST carry metrics.spend. A seller MAY return additional proposals at higher amounts under the same strength to show what volume a higher cost buys.", + gt=0.0, + ), + ] + currency: Annotated[ + str, + Field( + description="ISO 4217 currency of amount. BiddingPolicy.cost_per has no currency because it inherits the media-buy currency, and no media buy exists at request time, so the request states it. It becomes the answer's currency: every answering proposal's purchases[].pricing.currency (which bidding amounts use) and forecast.currency MUST equal it, as MUST commercial_terms.total_budget.currency and total_budget_guidance.currency when present; sellers MUST NOT convert currency. It MUST equal offer_filters.budget_range.currency when present. The seller plans within budget_range.max when present, else at or above budget_range.min, and total_budget MUST NOT exceed max or fall below min. A seller rejects a conflict with budget_range, pricing_currencies, the account currency, or the requested products' pricing currencies with INVALID_REQUEST naming criteria.outcome_target.cost_per.", + pattern='^[A-Z]{3}$', + ), + ] + strength: Annotated[ + outcome_target_cost_strength.OutcomeTargetCostStrength, + Field( + description="BiddingPolicy.cost_per strength: `cap` optimizes for an average at or below the amount and accepts underdelivery when necessary; `target` optimizes around the amount while balancing volume and spend. Neither is a per-result or per-auction guarantee. The answer MUST keep this strength: a cap of 3 the seller can only meet at 4.50 returns {amount: 4.50, strength: 'cap'}, never 'target'. A seller that will not plan at any amount under this strength rejects instead." + ), + ] diff --git a/src/adcp/types/domains/core/overlay.py b/src/adcp/types/domains/core/overlay.py index 9fd12f966..7e825a113 100644 --- a/src/adcp/types/domains/core/overlay.py +++ b/src/adcp/types/domains/core/overlay.py @@ -1,18 +1,88 @@ -"""Types declared by the AdCP ``core/overlay`` schema. +# generated by datamodel-codegen: +# filename: core/overlay.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.overlay import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.overlay import Bounds, Overlay, Unit, Visual +class Visual(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + url: Annotated[ + AnyUrl | None, + Field( + description='URL to a theme-neutral overlay graphic (SVG or PNG). Use when a single file works for all backgrounds, e.g. an SVG using CSS custom properties or currentColor.' + ), + ] = None + light: Annotated[ + AnyUrl | None, + Field( + description='URL to the overlay graphic for use on light/bright backgrounds (SVG or PNG)' + ), + ] = None + dark: Annotated[ + AnyUrl | None, + Field(description='URL to the overlay graphic for use on dark backgrounds (SVG or PNG)'), + ] = None + + +class Unit(StrEnum): + px = 'px' + fraction = 'fraction' + inches = 'inches' + cm = 'cm' + mm = 'mm' + pt = 'pt' + + +class Bounds(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + x: Annotated[StrictFloat, Field(description="Horizontal offset from the asset's left edge")] + y: Annotated[StrictFloat, Field(description="Vertical offset from the asset's top edge")] + width: Annotated[StrictFloat, Field(description='Width of the overlay', ge=0.0)] + height: Annotated[StrictFloat, Field(description='Height of the overlay', ge=0.0)] + unit: Annotated[ + Unit, + Field( + description="'px' = absolute pixels from asset top-left. 'fraction' = proportional to asset dimensions (0.0 = edge, 1.0 = opposite edge). 'inches', 'cm', 'mm', 'pt' (1/72 inch) = physical units for print overlays, measured from asset top-left." + ), + ] + -# Explicit exports -__all__ = ["Bounds", "Overlay", "Unit", "Visual"] +class Overlay(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + id: Annotated[ + str, + Field( + description="Identifier for this overlay (e.g., 'play_pause', 'volume', 'publisher_logo', 'carousel_prev', 'carousel_next')" + ), + ] + description: Annotated[ + str | None, + Field( + description='Human-readable explanation of what this overlay is and how buyers should account for it' + ), + ] = None + visual: Annotated[ + Visual | None, + Field( + description='Optional visual reference for this overlay element. Useful for creative agents compositing previews and for buyers understanding what will appear over their content. Must include at least one of: url, light, or dark.' + ), + ] = None + bounds: Annotated[ + Bounds, + Field( + description="Position and size of the overlay relative to the asset's own top-left corner. See 'unit' for coordinate interpretation." + ), + ] diff --git a/src/adcp/types/domains/core/package.py b/src/adcp/types/domains/core/package.py index 134a5e89f..3c9438943 100644 --- a/src/adcp/types/domains/core/package.py +++ b/src/adcp/types/domains/core/package.py @@ -1,18 +1,303 @@ -"""Types declared by the AdCP ``core/package`` schema. +# generated by datamodel-codegen: +# filename: core/package.json +# timestamp: 2026-10-05T13:49:50+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.package import +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import canceled_by as canceled_by_1 +from ..enums import pacing as pacing_1 +from ..pricing_options import price_breakdown as price_breakdown_1 +from . import audience_evidence_selection, bidding_policy, catalog, committed_metric +from . import context as context_1 +from . import creative_assignment +from . import ext as ext_1 +from . import format_id, format_option_ref +from . import measurement_terms as measurement_terms_1 +from . import ( + optimization_goal, + package_format_snapshot, + package_targeting_resolution, + performance_standard, + targeting, +) + + +class Cancellation(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + canceled_at: Annotated[ + AwareDatetime, Field(description='ISO 8601 timestamp when this package was canceled.') + ] + canceled_by: Annotated[ + canceled_by_1.CanceledBy, + Field(description='Which party initiated the package cancellation.'), + ] + reason: Annotated[ + str | None, Field(description='Reason the package was canceled.', max_length=500) + ] = None + acknowledged_at: Annotated[ + AwareDatetime | None, + Field( + description='ISO 8601 timestamp when the seller acknowledged the cancellation. Confirms inventory has been released and billing stopped. Absent until the seller processes the cancellation.' + ), + ] = None -from adcp.types.generated_poc.core.package import Cancellation, Package -# Explicit exports -__all__ = ["Cancellation", "Package"] +class Package(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + package_id: Annotated[str, Field(description="Seller's unique identifier for the package")] + product_id: Annotated[ + str | None, + Field( + description="ID of the product this package is based on. For packages created from an explicit create_media_buy package request, sellers MUST echo the request package's product_id on every response package object that represents that requested package." + ), + ] = None + audience_evidence_selections: Annotated[ + list[audience_evidence_selection.AudienceEvidenceSelection] | None, + Field( + description='Exact immutable audience-evidence snapshots that affected recommendation, eligibility, or package construction. A confirmed package MUST include a package_construction selection matching every buyer audience_evidence_pin and every snapshot used to satisfy package audience_evidence_requirements; this readback remains mandatory on subsequent package read surfaces. This is decision provenance only; applied targeting remains exclusively in targeting_overlay and targeting_resolution.demographics.', + min_length=1, + ), + ] = None + budget: Annotated[ + StrictFloat | None, + Field( + description='Hard lifetime spend cap for this package in the media-buy currency. Every selected pricing option in an AdCP-authored media buy MUST declare that same currency. In seller-optimized allocation mode this is a ceiling, not a current allocation. May be omitted when the package is bounded only by the shared media-buy total.', + ge=0.0, + ), + ] = None + min_spend_target: Annotated[ + StrictFloat | None, + Field( + description='Soft lifetime spend target accepted for this package under seller-optimized budget allocation. This is an allocation preference, not a billing or delivery guarantee.', + ge=0.0, + ), + ] = None + daily_budget_cap: Annotated[ + StrictFloat | None, + Field( + description="The hard package spend ceiling per shared media-buy cap day, in the media buy's currency. Sellers MUST echo this whenever a package daily cap is set. It is a subordinate ceiling, not a reserved or current allocation; the media buy's budget_cap_timezone defines its day boundary.", + ge=0.0, + ), + ] = None + pacing: pacing_1.Pacing | None = None + pricing_option_id: Annotated[ + str | None, + Field( + description="ID of the selected pricing option from the product's pricing_options array" + ), + ] = None + bid_price: Annotated[ + StrictFloat | None, + Field( + deprecated=True, + description='DEPRECATED legacy bidding representation. 3.2 sellers normalize accepted legacy input and SHOULD echo bidding instead. Removed in the next major.', + ge=0.0, + ), + ] = None + bidding: Annotated[ + bidding_policy.BiddingPolicy | None, + Field( + description='Package-authored bidding policy, echoed only when the buyer authored a package override. `{automatic:true}` is an explicit automatic-bidding override. Omission means the package inherits media-buy bidding or, when both scopes are absent, uses provider automatic delivery. Monetary fields are denominated in the media-buy currency. Sellers MUST NOT materialize inherited media-buy policy here.' + ), + ] = None + price_breakdown: Annotated[ + price_breakdown_1.PriceBreakdown | None, + Field( + description="Breakdown of the effective price for this package. On fixed-price packages, echoes the pricing option's breakdown. On auction packages, shows the clearing price breakdown including any commission or settlement terms." + ), + ] = None + impressions: Annotated[ + StrictFloat | None, Field(description='Impression goal for this package', ge=0.0) + ] = None + catalogs: Annotated[ + list[catalog.Catalog] | None, + Field( + description='Catalogs this package promotes. Each catalog MUST have a distinct type (e.g., one product catalog, one store catalog). This constraint is enforced at the application level — sellers MUST reject requests containing multiple catalogs of the same type with a validation_error. Echoed from the create_media_buy request.' + ), + ] = None + format_ids: Annotated[ + list[format_id.FormatReferenceStructuredObject] | None, + Field( + deprecated=True, + description='Deprecated in AdCP 3.2; removed in AdCP 4.0. Legacy named-format IDs supplied for this package on create_media_buy. Sellers SHOULD echo this field whenever the request included it, including dual-emission cases where `format_option_refs` was the winning selector, so read surfaces preserve the original wire contract. Omitted means the request did not carry legacy format_ids unless the seller cannot reconstruct legacy requests created before this field was persisted.', + ), + ] = None + format_option_refs: Annotated[ + list[format_option_ref.FormatOptionReference] | None, + Field( + description='Structured 3.1+ format option references supplied for this package on create_media_buy. Sellers SHOULD echo this field whenever the request included it. Publisher-catalog-backed options are identified by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options are identified by `{ scope: "product", format_option_id }` and resolve only against this package\'s target product. Omitted means the request did not carry format_option_refs unless the seller cannot reconstruct legacy requests created before this field was persisted.', + min_length=1, + ), + ] = None + format_kind: Annotated[ + str | None, + Field( + description='Direct canonical selector supplied for this package on create_media_buy. Sellers SHOULD echo this field whenever the request included it, including informational-echo cases where `format_ids` was the winning selector, so read surfaces preserve the original wire contract.' + ), + ] = None + params: Annotated[ + dict[str, Any] | None, + Field( + description='Parameters for the direct canonical selector in `format_kind`, echoed from the create_media_buy request whenever the request included it. Requires `format_kind`; omitted only when the request did not carry direct canonical params or when the seller cannot reconstruct legacy requests created before this field was persisted.' + ), + ] = None + targeting_overlay: Annotated[ + targeting.TargetingOverlay | None, + Field( + description='Complete effective targeting accepted for this package, including targeting bound through configured product selection plus package-specific targeting. Sellers MUST echo an applied package frequency_cap independently from any MediaBuy root cap. Sellers MUST also echo placement, property, and collection selection so buyers can audit purchased inventory: placements via placement_selection, collections via collection_selection (the committed concrete selectors, materialized even when the selection was produced through collection_list references).' + ), + ] = None + targeting_resolution: Annotated[ + package_targeting_resolution.PackageTargetingResolution | None, + Field( + description="Execution details for the package's accepted targeting. Sellers MUST include targeting_resolution.demographics whenever demographic targeting was requested or applied." + ), + ] = None + measurement_terms: Annotated[ + measurement_terms_1.MeasurementTerms | None, + Field( + description="Agreed billing measurement and makegood terms for this package. Reflects what was negotiated — may differ from the buyer's proposal or the product's defaults. When present, these terms are binding for the package's duration." + ), + ] = None + performance_standards: Annotated[ + list[performance_standard.PerformanceStandard] | None, + Field( + description='Agreed performance standards for this package. When any entry specifies a vendor, creatives assigned to this package MUST include corresponding tracker_script or tracker_pixel assets from that vendor.', + min_length=1, + ), + ] = None + committed_metrics: Annotated[ + list[committed_metric.CommittedMetric] | None, + Field( + description="The binding reporting contract for this package — what the seller has agreed to populate in delivery reports. Each entry carries an explicit `committed_at` timestamp, so the array also serves as the contract amendment ledger: day-1 commitments share `committed_at = create_media_buy.confirmed_at`; mid-flight additions carry their own timestamps. When `create_media_buy.confirmed_at` is null for a provisional buy, sellers MUST omit `committed_metrics` until commitment. The first response that sets `confirmed_at` MAY include the initial committed-metrics set, and each such entry's `committed_at` MUST equal `confirmed_at`. The `missing_metrics` field on `get_media_buy_delivery` reconciles against this list, filtering to entries where `committed_at < reporting_period.end` (a metric committed mid-flight is only audited from its commitment timestamp forward). Sellers stamp the day-1 set on the `create_media_buy` response; mid-flight additions are appended via `update_media_buy` (append-only — sellers MUST reject attempts to modify or remove existing entries with `validation_error`, suggested code: `IMMUTABLE_FIELD`). Optional in v1; absence means the seller does not provide an audit-grade contract and `missing_metrics` falls back to the product's live `available_metrics` (a known audit gap — buyers SHOULD treat absence as 'no audit-grade contract' rather than 'clean delivery'). Each entry uses an explicit `scope` discriminator: `standard` for entries from the closed `available-metric.json` enum, `vendor` for vendor-defined metrics anchored on a BrandRef. Standard entries are symmetric with `by_package[].metric_values`; vendor entries reconcile to `by_package[].vendor_metric_values`; both use `by_package[].missing_metrics` for gaps. The atomic key remains `(scope, metric_id, qualifier)`, with vendor identity included for vendor scope. Replaces the parallel-array design that shipped briefly in #3510.", + examples=[ + [ + { + 'scope': 'standard', + 'metric_id': 'impressions', + 'committed_at': '2026-04-29T10:53:00Z', + }, + { + 'scope': 'standard', + 'metric_id': 'spend', + 'committed_at': '2026-04-29T10:53:00Z', + }, + { + 'scope': 'standard', + 'metric_id': 'completed_views', + 'committed_at': '2026-04-29T10:53:00Z', + }, + { + 'scope': 'vendor', + 'vendor': {'domain': 'attentionvendor.example'}, + 'metric_id': 'attention_units', + 'committed_at': '2026-04-29T10:53:00Z', + }, + { + 'scope': 'standard', + 'metric_id': 'viewable_rate', + 'qualifier': {'viewability_standard': 'mrc'}, + 'committed_at': '2026-05-30T14:22:00Z', + }, + ] + ], + min_length=1, + ), + ] = None + creative_assignments: Annotated[ + list[creative_assignment.CreativeAssignment] | None, + Field( + description='Creative assets assigned to this package, including the committed package-scoped rotation policy. Omitted rotation_mode reads as weighted for backward compatibility; all assignments resolve to one effective mode, and sequential positions are unique within each package-local group.' + ), + ] = None + formats_to_provide: Annotated[ + list[package_format_snapshot.PackageFormatSnapshot] | None, + Field( + description='Immutable canonical creative contracts established for this package. Each entry is a PackageFormatSnapshot of the selected effective Product format declaration. A package whose selected format carries tracker_execution_contract MUST retain and return this checklist even after creative coverage is complete; the live Product is never substituted for the package snapshot.', + min_length=1, + ), + ] = None + formats_pending: Annotated[ + list[package_format_snapshot.PackageFormatSnapshot] | None, + Field( + description='PackageFormatSnapshot entries from formats_to_provide that do not yet have creative coverage through sync_creatives or inline assignment. Every entry MUST equal its formats_to_provide snapshot after RFC 8785 canonicalization and, when product_snapshot_digest is present, carry the identical digest. An empty emitted array means every required format is covered. Absence means readiness was not reported.' + ), + ] = None + format_ids_to_provide: Annotated[ + list[format_id.FormatReferenceStructuredObject] | None, + Field( + deprecated=True, + description='**DEPRECATED in 3.2.** Legacy named-format projection of formats_to_provide retained for older 3.x peers. New sellers emit canonical formats_to_provide declarations.', + ), + ] = None + format_ids_pending: Annotated[ + list[format_id.FormatReferenceStructuredObject] | None, + Field( + deprecated=True, + description='**DEPRECATED in 3.2.** Legacy named-format projection of formats_pending retained for older 3.x peers. New sellers emit canonical formats_pending declarations. An empty emitted array means every projected requirement is covered. Absence means legacy readiness was not reported and MUST NOT be interpreted as full coverage.', + ), + ] = None + optimization_goals: Annotated[ + list[optimization_goal.OptimizationGoal] | None, + Field( + description='Optimization targets for this package. The seller optimizes delivery toward these goals in priority order. Common pattern: event goals (purchase, install) as primary targets at priority 1; metric goals (clicks, views) as secondary proxy signals at priority 2+.', + min_length=1, + ), + ] = None + start_time: Annotated[ + AwareDatetime | None, + Field( + description="Flight start date/time for this package in ISO 8601 format. When omitted, the package inherits the media buy's start_time. Sellers SHOULD always include the resolved value in responses, even when inherited." + ), + ] = None + end_time: Annotated[ + AwareDatetime | None, + Field( + description="Flight end date/time for this package in ISO 8601 format. When omitted, the package inherits the media buy's end_time. Sellers SHOULD always include the resolved value in responses, even when inherited." + ), + ] = None + paused: Annotated[ + StrictBool | None, + Field( + description='Whether this package is paused by the buyer. Paused packages do not deliver impressions. Defaults to false.' + ), + ] = False + canceled: Annotated[ + StrictBool | None, + Field( + description='Whether this package has been canceled. Canceled packages stop delivery and cannot be reactivated. Defaults to false.' + ), + ] = False + cancellation: Annotated[ + Cancellation | None, + Field(description='Cancellation metadata. Present only when canceled is true.'), + ] = None + agency_estimate_number: Annotated[ + str | None, + Field( + description="Agency estimate or authorization number for this package. Echoed from the buyer's request. When present on the package, takes precedence over the media buy-level estimate number.", + max_length=100, + ), + ] = None + creative_deadline: Annotated[ + AwareDatetime | None, + Field( + description="ISO 8601 timestamp for creative upload or change deadline for this package. After this deadline, creative changes are rejected. When absent, the media buy's creative_deadline applies." + ), + ] = None + context: Annotated[ + context_1.ContextObject | None, + Field( + description='Opaque package-level correlation data echoed unchanged in responses, webhooks, and read surfaces. Buyers targeting mixed seller populations SHOULD include a per-package correlation value here, commonly context.buyer_ref, so responses from legacy sellers that do not echo product_id can still be mapped back to the requested product or line item. Sellers MUST preserve this object unchanged and MUST NOT parse it for business logic.' + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/package_delivery_metric_value.py b/src/adcp/types/domains/core/package_delivery_metric_value.py index 5eadd66cf..e54dda9c1 100644 --- a/src/adcp/types/domains/core/package_delivery_metric_value.py +++ b/src/adcp/types/domains/core/package_delivery_metric_value.py @@ -1,21 +1,62 @@ -"""Types declared by the AdCP ``core/package_delivery_metric_value`` schema. +# generated by datamodel-codegen: +# filename: core/package_delivery_metric_value.json +# timestamp: 2026-10-04T00:56:30+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.package_delivery_metric_value import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import attribution_methodology as attribution_methodology_1 +from ..enums import completion_source as completion_source_1 +from ..enums import lift_dimension as lift_dimension_1 +from ..enums import viewability_standard as viewability_standard_1 +from . import duration +from .delivery_metric_aggregate import Field0 + + +class Qualifier(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + viewability_standard: Annotated[ + viewability_standard_1.ViewabilityStandard | None, + Field( + description='Viewability standard under which this row was measured. MRC and GroupM define materially different thresholds; never sum across standards.' + ), + ] = None + completion_source: Annotated[ + completion_source_1.CompletionSource | None, + Field( + description='Source of `completion_rate` attestation for this row — `seller_attested` from player/ad server, `vendor_attested` from a third-party measurement vendor. The two paths can yield different rates; never sum across sources.' + ), + ] = None + attribution_methodology: Annotated[ + attribution_methodology_1.AttributionMethodology | None, + Field( + description='Attribution methodology under which this outcome row was computed (`deterministic_purchase`, `probabilistic`, `panel_based`, `modeled`). Outcome metrics measured under different methodologies represent materially different numbers; never sum across methodologies.' + ), + ] = None + attribution_window: Annotated[ + duration.Duration | None, + Field( + description="Attribution window for this outcome row. Object-valued duration (`{interval, unit}`), not a shorthand string. Outcome metrics measured over different windows represent the same metric over different time periods; the partition keeps them as separate rows so buyers don't accidentally aggregate." + ), + ] = None + lift_dimension: Annotated[ + lift_dimension_1.LiftDimension | None, + Field( + description='Brand-lift dimension this row represents (awareness, consideration, favorability, purchase intent, or ad recall). Required when `metric_id` is `brand_lift`. Each dimension is a separate surveyed outcome with its own sample size and confidence interval; never sum across dimensions.' + ), + ] = None -from adcp.types.generated_poc.core.package_delivery_metric_value import ( - PackageDeliveryMetricValue, - Qualifier, -) -# Explicit exports -__all__ = ["PackageDeliveryMetricValue", "Qualifier"] +class PackageDeliveryMetricValue(Field0): + qualifier: Annotated[ + Qualifier, + Field( + description="Qualifier keys disambiguating this row from sibling rows under the same `metric_id`. Symmetric with `committed_metrics.qualifier` today; expected to diverge in future minors as transparency disclosures buyers don't commit to ship delivery-only. Closed (`additionalProperties: false`) — new qualifier keys ship explicitly." + ), + ] diff --git a/src/adcp/types/domains/core/package_format_snapshot.py b/src/adcp/types/domains/core/package_format_snapshot.py index 7b28948ac..f7caf677d 100644 --- a/src/adcp/types/domains/core/package_format_snapshot.py +++ b/src/adcp/types/domains/core/package_format_snapshot.py @@ -1,90 +1,2457 @@ -"""Types declared by the AdCP ``core/package_format_snapshot`` schema. +# generated by datamodel-codegen: +# filename: core/package_format_snapshot.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.package_format_snapshot import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any, Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field, StrictBool -from adcp.types.generated_poc.core.package_format_snapshot import ( - PackageFormatSnapshot, - PackageFormatSnapshot1, - PackageFormatSnapshot10, - PackageFormatSnapshot11, - PackageFormatSnapshot12, - PackageFormatSnapshot13, - PackageFormatSnapshot14, - PackageFormatSnapshot15, - PackageFormatSnapshot16, - PackageFormatSnapshot17, - PackageFormatSnapshot18, - PackageFormatSnapshot19, - PackageFormatSnapshot2, - PackageFormatSnapshot20, - PackageFormatSnapshot21, - PackageFormatSnapshot22, - PackageFormatSnapshot23, - PackageFormatSnapshot24, - PackageFormatSnapshot25, - PackageFormatSnapshot26, - PackageFormatSnapshot27, - PackageFormatSnapshot28, - PackageFormatSnapshot29, - PackageFormatSnapshot3, - PackageFormatSnapshot30, - PackageFormatSnapshot31, - PackageFormatSnapshot32, - PackageFormatSnapshot33, - PackageFormatSnapshot4, - PackageFormatSnapshot5, - PackageFormatSnapshot6, - PackageFormatSnapshot7, - PackageFormatSnapshot8, - PackageFormatSnapshot9, - SellerPreference, +from ..enums import channels, daast_version, vast_version +from ..formats.canonical import ( + agent_placement, + audio_daast, + audio_hosted, + audio_vast, + coordinated_placements, + display_tag, + html5, + image, + image_carousel, + native_in_feed, + responsive_creative, + seller_rendered_stateful_display, + sponsored_placement, + video_hosted, + video_vast, ) +from . import ( + creative_locale_policy, + format_id, + macro_resolution_capability, + placement_ref, + platform_extension_ref, +) +from . import tracker_execution_contract as tracker_execution_contract_1 + + +class SellerPreference(StrEnum): + preferred = 'preferred' + accepted = 'accepted' + discouraged = 'discouraged' + + +class PackageFormatSnapshot17(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + product_id: Annotated[ + str | None, + Field( + description='Product whose effective selected format was snapshotted. Required whenever tracker_execution_contract is present.', + min_length=1, + ), + ] = None + placement_refs: Annotated[ + list[placement_ref.PlacementReference] | None, + Field( + description='Nonempty, duplicate-free effective placement set, sorted by publisher_domain then placement_id using ascending UTF-8 bytes without Unicode normalization. Omission binds the product-wide common effective contract; it never means an inferred list of all current placements.', + min_length=1, + ), + ] = None + execution_vast_version: Annotated[ + vast_version.VastVersion | None, + Field( + description='Exact VAST execution version selected for first-class VAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' + ), + ] = None + execution_daast_version: Annotated[ + daast_version.DaastVersion | None, + Field( + description='Exact DAAST execution version selected for first-class DAAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' + ), + ] = None + tracker_execution_contract_digest: Annotated[ + str | None, + Field( + description='SHA-256 of RFC 8785 canonical JSON for tracker_execution_contract alone. Required if and only if the snapshot contains that contract.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + product_snapshot_digest: Annotated[ + str | None, + Field( + description="Immutable SHA-256 digest of the package format snapshot's closed product-binding preimage. Required for every contract-bearing snapshot and paired with product_id whenever present.", + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + format_kind: Any + params: Any + + +class PackageFormatSnapshot1(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['image'] = 'image' + params: image.CanonicalFormatImage + + +class PackageFormatSnapshot2(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['html5'] = 'html5' + params: html5.CanonicalFormatHtml5Banner + + +class PackageFormatSnapshot3(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['display_tag'] = 'display_tag' + params: display_tag.CanonicalFormatDisplayTag + + +class PackageFormatSnapshot4(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['image_carousel'] = 'image_carousel' + params: image_carousel.CanonicalFormatImageCarousel + + +class PackageFormatSnapshot5(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['video_hosted'] = 'video_hosted' + params: video_hosted.CanonicalFormatHostedVideo + + +class PackageFormatSnapshot6(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['video_vast'] = 'video_vast' + params: video_vast.CanonicalFormatVastVideo + + +class PackageFormatSnapshot7(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['audio_hosted'] = 'audio_hosted' + params: audio_hosted.CanonicalFormatHostedAudio + + +class PackageFormatSnapshot8(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['audio_vast'] = 'audio_vast' + params: audio_vast.CanonicalFormatVastAudio + + +class PackageFormatSnapshot9(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['audio_daast'] = 'audio_daast' + params: audio_daast.CanonicalFormatDaastAudio + + +class PackageFormatSnapshot10(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['sponsored_placement'] = 'sponsored_placement' + params: sponsored_placement.CanonicalFormatSponsoredPlacementRetailMediaCatalogDriven + + +class PackageFormatSnapshot11(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['native_in_feed'] = 'native_in_feed' + params: native_in_feed.CanonicalFormatNativeInFeed + + +class PackageFormatSnapshot12(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['responsive_creative'] = 'responsive_creative' + params: responsive_creative.CanonicalFormatResponsiveCreative + + +class PackageFormatSnapshot13(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['agent_placement'] = 'agent_placement' + params: agent_placement.CanonicalFormatAgentPlacementAiSurfaceSponsoredPlacement + + +class PackageFormatSnapshot14(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['seller_rendered_stateful_display'] = 'seller_rendered_stateful_display' + params: seller_rendered_stateful_display.CanonicalFormatSellerRenderedStatefulDisplay + + +class PackageFormatSnapshot15(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['coordinated_placements'] = 'coordinated_placements' + params: coordinated_placements.CanonicalFormatCoordinatedPlacements + + +class PackageFormatSnapshot16(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['custom'] = 'custom' + params: Annotated[ + dict[str, Any], + Field( + description="Custom shape's params. Validated against the schema fetched from `format_schema.uri` at the cached `format_schema.digest`." + ), + ] + + +class PackageFormatSnapshot18(PackageFormatSnapshot1): + model_config = ConfigDict( + extra='allow', + ) + product_id: Annotated[ + str | None, + Field( + description='Product whose effective selected format was snapshotted. Required whenever tracker_execution_contract is present.', + min_length=1, + ), + ] = None + placement_refs: Annotated[ + list[placement_ref.PlacementReference] | None, + Field( + description='Nonempty, duplicate-free effective placement set, sorted by publisher_domain then placement_id using ascending UTF-8 bytes without Unicode normalization. Omission binds the product-wide common effective contract; it never means an inferred list of all current placements.', + min_length=1, + ), + ] = None + execution_vast_version: Annotated[ + vast_version.VastVersion | None, + Field( + description='Exact VAST execution version selected for first-class VAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' + ), + ] = None + execution_daast_version: Annotated[ + daast_version.DaastVersion | None, + Field( + description='Exact DAAST execution version selected for first-class DAAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' + ), + ] = None + tracker_execution_contract_digest: Annotated[ + str | None, + Field( + description='SHA-256 of RFC 8785 canonical JSON for tracker_execution_contract alone. Required if and only if the snapshot contains that contract.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + product_snapshot_digest: Annotated[ + str | None, + Field( + description="Immutable SHA-256 digest of the package format snapshot's closed product-binding preimage. Required for every contract-bearing snapshot and paired with product_id whenever present.", + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + format_kind: Literal['image'] = 'image' + params: image.CanonicalFormatImage + + +class PackageFormatSnapshot19(PackageFormatSnapshot2): + model_config = ConfigDict( + extra='allow', + ) + product_id: Annotated[ + str | None, + Field( + description='Product whose effective selected format was snapshotted. Required whenever tracker_execution_contract is present.', + min_length=1, + ), + ] = None + placement_refs: Annotated[ + list[placement_ref.PlacementReference] | None, + Field( + description='Nonempty, duplicate-free effective placement set, sorted by publisher_domain then placement_id using ascending UTF-8 bytes without Unicode normalization. Omission binds the product-wide common effective contract; it never means an inferred list of all current placements.', + min_length=1, + ), + ] = None + execution_vast_version: Annotated[ + vast_version.VastVersion | None, + Field( + description='Exact VAST execution version selected for first-class VAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' + ), + ] = None + execution_daast_version: Annotated[ + daast_version.DaastVersion | None, + Field( + description='Exact DAAST execution version selected for first-class DAAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' + ), + ] = None + tracker_execution_contract_digest: Annotated[ + str | None, + Field( + description='SHA-256 of RFC 8785 canonical JSON for tracker_execution_contract alone. Required if and only if the snapshot contains that contract.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + product_snapshot_digest: Annotated[ + str | None, + Field( + description="Immutable SHA-256 digest of the package format snapshot's closed product-binding preimage. Required for every contract-bearing snapshot and paired with product_id whenever present.", + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + format_kind: Literal['html5'] = 'html5' + params: html5.CanonicalFormatHtml5Banner + + +class PackageFormatSnapshot20(PackageFormatSnapshot3): + model_config = ConfigDict( + extra='allow', + ) + product_id: Annotated[ + str | None, + Field( + description='Product whose effective selected format was snapshotted. Required whenever tracker_execution_contract is present.', + min_length=1, + ), + ] = None + placement_refs: Annotated[ + list[placement_ref.PlacementReference] | None, + Field( + description='Nonempty, duplicate-free effective placement set, sorted by publisher_domain then placement_id using ascending UTF-8 bytes without Unicode normalization. Omission binds the product-wide common effective contract; it never means an inferred list of all current placements.', + min_length=1, + ), + ] = None + execution_vast_version: Annotated[ + vast_version.VastVersion | None, + Field( + description='Exact VAST execution version selected for first-class VAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' + ), + ] = None + execution_daast_version: Annotated[ + daast_version.DaastVersion | None, + Field( + description='Exact DAAST execution version selected for first-class DAAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' + ), + ] = None + tracker_execution_contract_digest: Annotated[ + str | None, + Field( + description='SHA-256 of RFC 8785 canonical JSON for tracker_execution_contract alone. Required if and only if the snapshot contains that contract.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + product_snapshot_digest: Annotated[ + str | None, + Field( + description="Immutable SHA-256 digest of the package format snapshot's closed product-binding preimage. Required for every contract-bearing snapshot and paired with product_id whenever present.", + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + format_kind: Literal['display_tag'] = 'display_tag' + params: display_tag.CanonicalFormatDisplayTag + + +class PackageFormatSnapshot21(PackageFormatSnapshot4): + model_config = ConfigDict( + extra='allow', + ) + product_id: Annotated[ + str | None, + Field( + description='Product whose effective selected format was snapshotted. Required whenever tracker_execution_contract is present.', + min_length=1, + ), + ] = None + placement_refs: Annotated[ + list[placement_ref.PlacementReference] | None, + Field( + description='Nonempty, duplicate-free effective placement set, sorted by publisher_domain then placement_id using ascending UTF-8 bytes without Unicode normalization. Omission binds the product-wide common effective contract; it never means an inferred list of all current placements.', + min_length=1, + ), + ] = None + execution_vast_version: Annotated[ + vast_version.VastVersion | None, + Field( + description='Exact VAST execution version selected for first-class VAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' + ), + ] = None + execution_daast_version: Annotated[ + daast_version.DaastVersion | None, + Field( + description='Exact DAAST execution version selected for first-class DAAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' + ), + ] = None + tracker_execution_contract_digest: Annotated[ + str | None, + Field( + description='SHA-256 of RFC 8785 canonical JSON for tracker_execution_contract alone. Required if and only if the snapshot contains that contract.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + product_snapshot_digest: Annotated[ + str | None, + Field( + description="Immutable SHA-256 digest of the package format snapshot's closed product-binding preimage. Required for every contract-bearing snapshot and paired with product_id whenever present.", + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + format_kind: Literal['image_carousel'] = 'image_carousel' + params: image_carousel.CanonicalFormatImageCarousel + + +class PackageFormatSnapshot22(PackageFormatSnapshot5): + model_config = ConfigDict( + extra='allow', + ) + product_id: Annotated[ + str | None, + Field( + description='Product whose effective selected format was snapshotted. Required whenever tracker_execution_contract is present.', + min_length=1, + ), + ] = None + placement_refs: Annotated[ + list[placement_ref.PlacementReference] | None, + Field( + description='Nonempty, duplicate-free effective placement set, sorted by publisher_domain then placement_id using ascending UTF-8 bytes without Unicode normalization. Omission binds the product-wide common effective contract; it never means an inferred list of all current placements.', + min_length=1, + ), + ] = None + execution_vast_version: Annotated[ + vast_version.VastVersion | None, + Field( + description='Exact VAST execution version selected for first-class VAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' + ), + ] = None + execution_daast_version: Annotated[ + daast_version.DaastVersion | None, + Field( + description='Exact DAAST execution version selected for first-class DAAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' + ), + ] = None + tracker_execution_contract_digest: Annotated[ + str | None, + Field( + description='SHA-256 of RFC 8785 canonical JSON for tracker_execution_contract alone. Required if and only if the snapshot contains that contract.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + product_snapshot_digest: Annotated[ + str | None, + Field( + description="Immutable SHA-256 digest of the package format snapshot's closed product-binding preimage. Required for every contract-bearing snapshot and paired with product_id whenever present.", + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + format_kind: Literal['video_hosted'] = 'video_hosted' + params: video_hosted.CanonicalFormatHostedVideo + + +class PackageFormatSnapshot23(PackageFormatSnapshot6): + model_config = ConfigDict( + extra='allow', + ) + product_id: Annotated[ + str | None, + Field( + description='Product whose effective selected format was snapshotted. Required whenever tracker_execution_contract is present.', + min_length=1, + ), + ] = None + placement_refs: Annotated[ + list[placement_ref.PlacementReference] | None, + Field( + description='Nonempty, duplicate-free effective placement set, sorted by publisher_domain then placement_id using ascending UTF-8 bytes without Unicode normalization. Omission binds the product-wide common effective contract; it never means an inferred list of all current placements.', + min_length=1, + ), + ] = None + execution_vast_version: Annotated[ + vast_version.VastVersion | None, + Field( + description='Exact VAST execution version selected for first-class VAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' + ), + ] = None + execution_daast_version: Annotated[ + daast_version.DaastVersion | None, + Field( + description='Exact DAAST execution version selected for first-class DAAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' + ), + ] = None + tracker_execution_contract_digest: Annotated[ + str | None, + Field( + description='SHA-256 of RFC 8785 canonical JSON for tracker_execution_contract alone. Required if and only if the snapshot contains that contract.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + product_snapshot_digest: Annotated[ + str | None, + Field( + description="Immutable SHA-256 digest of the package format snapshot's closed product-binding preimage. Required for every contract-bearing snapshot and paired with product_id whenever present.", + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + format_kind: Literal['video_vast'] = 'video_vast' + params: video_vast.CanonicalFormatVastVideo + + +class PackageFormatSnapshot24(PackageFormatSnapshot7): + model_config = ConfigDict( + extra='allow', + ) + product_id: Annotated[ + str | None, + Field( + description='Product whose effective selected format was snapshotted. Required whenever tracker_execution_contract is present.', + min_length=1, + ), + ] = None + placement_refs: Annotated[ + list[placement_ref.PlacementReference] | None, + Field( + description='Nonempty, duplicate-free effective placement set, sorted by publisher_domain then placement_id using ascending UTF-8 bytes without Unicode normalization. Omission binds the product-wide common effective contract; it never means an inferred list of all current placements.', + min_length=1, + ), + ] = None + execution_vast_version: Annotated[ + vast_version.VastVersion | None, + Field( + description='Exact VAST execution version selected for first-class VAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' + ), + ] = None + execution_daast_version: Annotated[ + daast_version.DaastVersion | None, + Field( + description='Exact DAAST execution version selected for first-class DAAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' + ), + ] = None + tracker_execution_contract_digest: Annotated[ + str | None, + Field( + description='SHA-256 of RFC 8785 canonical JSON for tracker_execution_contract alone. Required if and only if the snapshot contains that contract.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + product_snapshot_digest: Annotated[ + str | None, + Field( + description="Immutable SHA-256 digest of the package format snapshot's closed product-binding preimage. Required for every contract-bearing snapshot and paired with product_id whenever present.", + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + format_kind: Literal['audio_hosted'] = 'audio_hosted' + params: audio_hosted.CanonicalFormatHostedAudio + + +class PackageFormatSnapshot25(PackageFormatSnapshot8): + model_config = ConfigDict( + extra='allow', + ) + product_id: Annotated[ + str | None, + Field( + description='Product whose effective selected format was snapshotted. Required whenever tracker_execution_contract is present.', + min_length=1, + ), + ] = None + placement_refs: Annotated[ + list[placement_ref.PlacementReference] | None, + Field( + description='Nonempty, duplicate-free effective placement set, sorted by publisher_domain then placement_id using ascending UTF-8 bytes without Unicode normalization. Omission binds the product-wide common effective contract; it never means an inferred list of all current placements.', + min_length=1, + ), + ] = None + execution_vast_version: Annotated[ + vast_version.VastVersion | None, + Field( + description='Exact VAST execution version selected for first-class VAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' + ), + ] = None + execution_daast_version: Annotated[ + daast_version.DaastVersion | None, + Field( + description='Exact DAAST execution version selected for first-class DAAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' + ), + ] = None + tracker_execution_contract_digest: Annotated[ + str | None, + Field( + description='SHA-256 of RFC 8785 canonical JSON for tracker_execution_contract alone. Required if and only if the snapshot contains that contract.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + product_snapshot_digest: Annotated[ + str | None, + Field( + description="Immutable SHA-256 digest of the package format snapshot's closed product-binding preimage. Required for every contract-bearing snapshot and paired with product_id whenever present.", + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + format_kind: Literal['audio_vast'] = 'audio_vast' + params: audio_vast.CanonicalFormatVastAudio + + +class PackageFormatSnapshot26(PackageFormatSnapshot9): + model_config = ConfigDict( + extra='allow', + ) + product_id: Annotated[ + str | None, + Field( + description='Product whose effective selected format was snapshotted. Required whenever tracker_execution_contract is present.', + min_length=1, + ), + ] = None + placement_refs: Annotated[ + list[placement_ref.PlacementReference] | None, + Field( + description='Nonempty, duplicate-free effective placement set, sorted by publisher_domain then placement_id using ascending UTF-8 bytes without Unicode normalization. Omission binds the product-wide common effective contract; it never means an inferred list of all current placements.', + min_length=1, + ), + ] = None + execution_vast_version: Annotated[ + vast_version.VastVersion | None, + Field( + description='Exact VAST execution version selected for first-class VAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' + ), + ] = None + execution_daast_version: Annotated[ + daast_version.DaastVersion | None, + Field( + description='Exact DAAST execution version selected for first-class DAAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' + ), + ] = None + tracker_execution_contract_digest: Annotated[ + str | None, + Field( + description='SHA-256 of RFC 8785 canonical JSON for tracker_execution_contract alone. Required if and only if the snapshot contains that contract.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + product_snapshot_digest: Annotated[ + str | None, + Field( + description="Immutable SHA-256 digest of the package format snapshot's closed product-binding preimage. Required for every contract-bearing snapshot and paired with product_id whenever present.", + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + format_kind: Literal['audio_daast'] = 'audio_daast' + params: audio_daast.CanonicalFormatDaastAudio + + +class PackageFormatSnapshot27(PackageFormatSnapshot10): + model_config = ConfigDict( + extra='allow', + ) + product_id: Annotated[ + str | None, + Field( + description='Product whose effective selected format was snapshotted. Required whenever tracker_execution_contract is present.', + min_length=1, + ), + ] = None + placement_refs: Annotated[ + list[placement_ref.PlacementReference] | None, + Field( + description='Nonempty, duplicate-free effective placement set, sorted by publisher_domain then placement_id using ascending UTF-8 bytes without Unicode normalization. Omission binds the product-wide common effective contract; it never means an inferred list of all current placements.', + min_length=1, + ), + ] = None + execution_vast_version: Annotated[ + vast_version.VastVersion | None, + Field( + description='Exact VAST execution version selected for first-class VAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' + ), + ] = None + execution_daast_version: Annotated[ + daast_version.DaastVersion | None, + Field( + description='Exact DAAST execution version selected for first-class DAAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' + ), + ] = None + tracker_execution_contract_digest: Annotated[ + str | None, + Field( + description='SHA-256 of RFC 8785 canonical JSON for tracker_execution_contract alone. Required if and only if the snapshot contains that contract.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + product_snapshot_digest: Annotated[ + str | None, + Field( + description="Immutable SHA-256 digest of the package format snapshot's closed product-binding preimage. Required for every contract-bearing snapshot and paired with product_id whenever present.", + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + format_kind: Literal['sponsored_placement'] = 'sponsored_placement' + params: sponsored_placement.CanonicalFormatSponsoredPlacementRetailMediaCatalogDriven + + +class PackageFormatSnapshot28(PackageFormatSnapshot11): + model_config = ConfigDict( + extra='allow', + ) + product_id: Annotated[ + str | None, + Field( + description='Product whose effective selected format was snapshotted. Required whenever tracker_execution_contract is present.', + min_length=1, + ), + ] = None + placement_refs: Annotated[ + list[placement_ref.PlacementReference] | None, + Field( + description='Nonempty, duplicate-free effective placement set, sorted by publisher_domain then placement_id using ascending UTF-8 bytes without Unicode normalization. Omission binds the product-wide common effective contract; it never means an inferred list of all current placements.', + min_length=1, + ), + ] = None + execution_vast_version: Annotated[ + vast_version.VastVersion | None, + Field( + description='Exact VAST execution version selected for first-class VAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' + ), + ] = None + execution_daast_version: Annotated[ + daast_version.DaastVersion | None, + Field( + description='Exact DAAST execution version selected for first-class DAAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' + ), + ] = None + tracker_execution_contract_digest: Annotated[ + str | None, + Field( + description='SHA-256 of RFC 8785 canonical JSON for tracker_execution_contract alone. Required if and only if the snapshot contains that contract.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + product_snapshot_digest: Annotated[ + str | None, + Field( + description="Immutable SHA-256 digest of the package format snapshot's closed product-binding preimage. Required for every contract-bearing snapshot and paired with product_id whenever present.", + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + format_kind: Literal['native_in_feed'] = 'native_in_feed' + params: native_in_feed.CanonicalFormatNativeInFeed + + +class PackageFormatSnapshot29(PackageFormatSnapshot12): + model_config = ConfigDict( + extra='allow', + ) + product_id: Annotated[ + str | None, + Field( + description='Product whose effective selected format was snapshotted. Required whenever tracker_execution_contract is present.', + min_length=1, + ), + ] = None + placement_refs: Annotated[ + list[placement_ref.PlacementReference] | None, + Field( + description='Nonempty, duplicate-free effective placement set, sorted by publisher_domain then placement_id using ascending UTF-8 bytes without Unicode normalization. Omission binds the product-wide common effective contract; it never means an inferred list of all current placements.', + min_length=1, + ), + ] = None + execution_vast_version: Annotated[ + vast_version.VastVersion | None, + Field( + description='Exact VAST execution version selected for first-class VAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' + ), + ] = None + execution_daast_version: Annotated[ + daast_version.DaastVersion | None, + Field( + description='Exact DAAST execution version selected for first-class DAAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' + ), + ] = None + tracker_execution_contract_digest: Annotated[ + str | None, + Field( + description='SHA-256 of RFC 8785 canonical JSON for tracker_execution_contract alone. Required if and only if the snapshot contains that contract.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + product_snapshot_digest: Annotated[ + str | None, + Field( + description="Immutable SHA-256 digest of the package format snapshot's closed product-binding preimage. Required for every contract-bearing snapshot and paired with product_id whenever present.", + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + format_kind: Literal['responsive_creative'] = 'responsive_creative' + params: responsive_creative.CanonicalFormatResponsiveCreative + + +class PackageFormatSnapshot30(PackageFormatSnapshot13): + model_config = ConfigDict( + extra='allow', + ) + product_id: Annotated[ + str | None, + Field( + description='Product whose effective selected format was snapshotted. Required whenever tracker_execution_contract is present.', + min_length=1, + ), + ] = None + placement_refs: Annotated[ + list[placement_ref.PlacementReference] | None, + Field( + description='Nonempty, duplicate-free effective placement set, sorted by publisher_domain then placement_id using ascending UTF-8 bytes without Unicode normalization. Omission binds the product-wide common effective contract; it never means an inferred list of all current placements.', + min_length=1, + ), + ] = None + execution_vast_version: Annotated[ + vast_version.VastVersion | None, + Field( + description='Exact VAST execution version selected for first-class VAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' + ), + ] = None + execution_daast_version: Annotated[ + daast_version.DaastVersion | None, + Field( + description='Exact DAAST execution version selected for first-class DAAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' + ), + ] = None + tracker_execution_contract_digest: Annotated[ + str | None, + Field( + description='SHA-256 of RFC 8785 canonical JSON for tracker_execution_contract alone. Required if and only if the snapshot contains that contract.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + product_snapshot_digest: Annotated[ + str | None, + Field( + description="Immutable SHA-256 digest of the package format snapshot's closed product-binding preimage. Required for every contract-bearing snapshot and paired with product_id whenever present.", + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + format_kind: Literal['agent_placement'] = 'agent_placement' + params: agent_placement.CanonicalFormatAgentPlacementAiSurfaceSponsoredPlacement + + +class PackageFormatSnapshot31(PackageFormatSnapshot14): + model_config = ConfigDict( + extra='allow', + ) + product_id: Annotated[ + str | None, + Field( + description='Product whose effective selected format was snapshotted. Required whenever tracker_execution_contract is present.', + min_length=1, + ), + ] = None + placement_refs: Annotated[ + list[placement_ref.PlacementReference] | None, + Field( + description='Nonempty, duplicate-free effective placement set, sorted by publisher_domain then placement_id using ascending UTF-8 bytes without Unicode normalization. Omission binds the product-wide common effective contract; it never means an inferred list of all current placements.', + min_length=1, + ), + ] = None + execution_vast_version: Annotated[ + vast_version.VastVersion | None, + Field( + description='Exact VAST execution version selected for first-class VAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' + ), + ] = None + execution_daast_version: Annotated[ + daast_version.DaastVersion | None, + Field( + description='Exact DAAST execution version selected for first-class DAAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' + ), + ] = None + tracker_execution_contract_digest: Annotated[ + str | None, + Field( + description='SHA-256 of RFC 8785 canonical JSON for tracker_execution_contract alone. Required if and only if the snapshot contains that contract.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + product_snapshot_digest: Annotated[ + str | None, + Field( + description="Immutable SHA-256 digest of the package format snapshot's closed product-binding preimage. Required for every contract-bearing snapshot and paired with product_id whenever present.", + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + format_kind: Literal['seller_rendered_stateful_display'] = 'seller_rendered_stateful_display' + params: seller_rendered_stateful_display.CanonicalFormatSellerRenderedStatefulDisplay + + +class PackageFormatSnapshot32(PackageFormatSnapshot15): + model_config = ConfigDict( + extra='allow', + ) + product_id: Annotated[ + str | None, + Field( + description='Product whose effective selected format was snapshotted. Required whenever tracker_execution_contract is present.', + min_length=1, + ), + ] = None + placement_refs: Annotated[ + list[placement_ref.PlacementReference] | None, + Field( + description='Nonempty, duplicate-free effective placement set, sorted by publisher_domain then placement_id using ascending UTF-8 bytes without Unicode normalization. Omission binds the product-wide common effective contract; it never means an inferred list of all current placements.', + min_length=1, + ), + ] = None + execution_vast_version: Annotated[ + vast_version.VastVersion | None, + Field( + description='Exact VAST execution version selected for first-class VAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' + ), + ] = None + execution_daast_version: Annotated[ + daast_version.DaastVersion | None, + Field( + description='Exact DAAST execution version selected for first-class DAAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' + ), + ] = None + tracker_execution_contract_digest: Annotated[ + str | None, + Field( + description='SHA-256 of RFC 8785 canonical JSON for tracker_execution_contract alone. Required if and only if the snapshot contains that contract.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + product_snapshot_digest: Annotated[ + str | None, + Field( + description="Immutable SHA-256 digest of the package format snapshot's closed product-binding preimage. Required for every contract-bearing snapshot and paired with product_id whenever present.", + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + format_kind: Literal['coordinated_placements'] = 'coordinated_placements' + params: coordinated_placements.CanonicalFormatCoordinatedPlacements + + +class PackageFormatSnapshot33(PackageFormatSnapshot16): + model_config = ConfigDict( + extra='allow', + ) + product_id: Annotated[ + str | None, + Field( + description='Product whose effective selected format was snapshotted. Required whenever tracker_execution_contract is present.', + min_length=1, + ), + ] = None + placement_refs: Annotated[ + list[placement_ref.PlacementReference] | None, + Field( + description='Nonempty, duplicate-free effective placement set, sorted by publisher_domain then placement_id using ascending UTF-8 bytes without Unicode normalization. Omission binds the product-wide common effective contract; it never means an inferred list of all current placements.', + min_length=1, + ), + ] = None + execution_vast_version: Annotated[ + vast_version.VastVersion | None, + Field( + description='Exact VAST execution version selected for first-class VAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' + ), + ] = None + execution_daast_version: Annotated[ + daast_version.DaastVersion | None, + Field( + description='Exact DAAST execution version selected for first-class DAAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' + ), + ] = None + tracker_execution_contract_digest: Annotated[ + str | None, + Field( + description='SHA-256 of RFC 8785 canonical JSON for tracker_execution_contract alone. Required if and only if the snapshot contains that contract.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + product_snapshot_digest: Annotated[ + str | None, + Field( + description="Immutable SHA-256 digest of the package format snapshot's closed product-binding preimage. Required for every contract-bearing snapshot and paired with product_id whenever present.", + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + format_kind: Literal['custom'] = 'custom' + params: Annotated[dict[str, Any], Field(description="Custom shape's params. Validated against the schema fetched from `format_schema.uri` at the cached `format_schema.digest`.")] + -# Explicit exports -__all__ = [ - "PackageFormatSnapshot", - "PackageFormatSnapshot1", - "PackageFormatSnapshot10", - "PackageFormatSnapshot11", - "PackageFormatSnapshot12", - "PackageFormatSnapshot13", - "PackageFormatSnapshot14", - "PackageFormatSnapshot15", - "PackageFormatSnapshot16", - "PackageFormatSnapshot17", - "PackageFormatSnapshot18", - "PackageFormatSnapshot19", - "PackageFormatSnapshot2", - "PackageFormatSnapshot20", - "PackageFormatSnapshot21", - "PackageFormatSnapshot22", - "PackageFormatSnapshot23", - "PackageFormatSnapshot24", - "PackageFormatSnapshot25", - "PackageFormatSnapshot26", - "PackageFormatSnapshot27", - "PackageFormatSnapshot28", - "PackageFormatSnapshot29", - "PackageFormatSnapshot3", - "PackageFormatSnapshot30", - "PackageFormatSnapshot31", - "PackageFormatSnapshot32", - "PackageFormatSnapshot33", - "PackageFormatSnapshot4", - "PackageFormatSnapshot5", - "PackageFormatSnapshot6", - "PackageFormatSnapshot7", - "PackageFormatSnapshot8", - "PackageFormatSnapshot9", - "SellerPreference", +PackageFormatSnapshot = Annotated[ + PackageFormatSnapshot18 + | PackageFormatSnapshot19 + | PackageFormatSnapshot20 + | PackageFormatSnapshot21 + | PackageFormatSnapshot22 + | PackageFormatSnapshot23 + | PackageFormatSnapshot24 + | PackageFormatSnapshot25 + | PackageFormatSnapshot26 + | PackageFormatSnapshot27 + | PackageFormatSnapshot28 + | PackageFormatSnapshot29 + | PackageFormatSnapshot30 + | PackageFormatSnapshot31 + | PackageFormatSnapshot32 + | PackageFormatSnapshot33, + Field( + description="Immutable package-time snapshot of one selected Product format declaration. Product-format fields remain flattened at the root for 3.x compatibility; product, placement, exact execution-version, and digest fields bind the seller's package-time decision. A contract-bearing snapshot is durably identified even when no preview or observation feature is offered.", + title='Package Format Snapshot', + ), ] diff --git a/src/adcp/types/domains/core/package_signal_targeting.py b/src/adcp/types/domains/core/package_signal_targeting.py index 7b8223839..d96d92c3d 100644 --- a/src/adcp/types/domains/core/package_signal_targeting.py +++ b/src/adcp/types/domains/core/package_signal_targeting.py @@ -1,36 +1,171 @@ -"""Types declared by the AdCP ``core/package_signal_targeting`` schema. +# generated by datamodel-codegen: +# filename: core/package_signal_targeting.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.package_signal_targeting import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictFloat + +from . import activation_key as activation_key_1 +from . import signal_ref as signal_ref_1 + + +class PackageSignalTargeting4(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + pricing_option_id: Annotated[ + str | None, + Field( + description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." + ), + ] = None + signal_agent_segment_id: Annotated[ + str | None, + Field( + description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' + ), + ] = None + activation_key: Annotated[ + activation_key_1.ActivationKey | None, + Field( + description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.' + ), + ] = None + + +class PackageSignalTargeting1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + signal_ref: Annotated[signal_ref_1.SignalRef, Field(description='Named signal being targeted.')] + value_type: Annotated[Literal['binary'], Field(description='Discriminator for binary signals.')] = 'binary' + value: Annotated[ + Literal[True], + Field( + description='Binary package signal entries match users for whom the signal is true. Use the parent group operator for include/exclude.' + ), + ] + + +class PackageSignalTargeting2(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + signal_ref: Annotated[signal_ref_1.SignalRef, Field(description='Named signal being targeted.')] + value_type: Annotated[ + Literal['categorical'], Field(description='Discriminator for categorical signals.') + ] = 'categorical' + values: Annotated[ + list[str], + Field( + description='Values to target. Users with any of these values match the expression.', + min_length=1, + ), + ] + + +class PackageSignalTargeting3(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + signal_ref: Annotated[signal_ref_1.SignalRef, Field(description='Named signal being targeted.')] + value_type: Annotated[ + Literal['numeric'], Field(description='Discriminator for numeric signals.') + ] = 'numeric' + min_value: Annotated[ + StrictFloat | None, + Field( + description="Minimum value, inclusive. Omit for no minimum. Should be within the signal definition's range when declared." + ), + ] = None + max_value: Annotated[ + StrictFloat | None, + Field( + description="Maximum value, inclusive. Omit for no maximum. Should be within the signal definition's range when declared." + ), + ] = None + + +class PackageSignalTargeting5(PackageSignalTargeting1, PackageSignalTargeting4): + model_config = ConfigDict( + extra='allow', + ) + pricing_option_id: Annotated[ + str | None, + Field( + description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." + ), + ] = None + signal_agent_segment_id: Annotated[ + str | None, + Field( + description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' + ), + ] = None + activation_key: Annotated[ + activation_key_1.ActivationKey | None, + Field( + description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.' + ), + ] = None + + +class PackageSignalTargeting6(PackageSignalTargeting2, PackageSignalTargeting4): + model_config = ConfigDict( + extra='allow', + ) + pricing_option_id: Annotated[ + str | None, + Field( + description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." + ), + ] = None + signal_agent_segment_id: Annotated[ + str | None, + Field( + description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' + ), + ] = None + activation_key: Annotated[ + activation_key_1.ActivationKey | None, + Field( + description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.' + ), + ] = None + + +class PackageSignalTargeting7(PackageSignalTargeting3, PackageSignalTargeting4): + model_config = ConfigDict( + extra='allow', + ) + pricing_option_id: Annotated[ + str | None, + Field( + description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." + ), + ] = None + signal_agent_segment_id: Annotated[ + str | None, + Field( + description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' + ), + ] = None + activation_key: Annotated[ + activation_key_1.ActivationKey | None, + Field( + description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.' + ), + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.package_signal_targeting import ( - PackageSignalTargeting, - PackageSignalTargeting1, - PackageSignalTargeting2, - PackageSignalTargeting3, - PackageSignalTargeting4, - PackageSignalTargeting5, - PackageSignalTargeting6, - PackageSignalTargeting7, -) - -# Explicit exports -__all__ = [ - "PackageSignalTargeting", - "PackageSignalTargeting1", - "PackageSignalTargeting2", - "PackageSignalTargeting3", - "PackageSignalTargeting4", - "PackageSignalTargeting5", - "PackageSignalTargeting6", - "PackageSignalTargeting7", +PackageSignalTargeting = Annotated[ + PackageSignalTargeting5 | PackageSignalTargeting6 | PackageSignalTargeting7, + Field( + description="Buy-time selection of one seller-offered signal inside a package signal targeting group. The signal_ref uses scope 'product' for a product-local signal option, scope 'data_provider' for a signal defined in a data provider's published adagents.json signals[], or scope 'signal_source' for a source-native signal that is not published in adagents.json signals[]. The selected product's inline Product.signal_targeting_options, get_signals feed when inline options are omitted, and signal_targeting_rules define buy-time eligibility. Inclusion and exclusion are controlled by the parent group operator: use operator 'any' to include users matching the signal expression and operator 'none' to exclude users matching the signal expression. For binary signals, value MUST be true; do not use value=false for exclusion inside signal_targeting_groups. Use audience_include/audience_exclude only for buyer-managed first-party audiences registered through sync_audiences.", + title='Package Signal Targeting', + ), ] diff --git a/src/adcp/types/domains/core/package_signal_targeting_group.py b/src/adcp/types/domains/core/package_signal_targeting_group.py index 99f67d8ed..ccc23a195 100644 --- a/src/adcp/types/domains/core/package_signal_targeting_group.py +++ b/src/adcp/types/domains/core/package_signal_targeting_group.py @@ -1,21 +1,37 @@ -"""Types declared by the AdCP ``core/package_signal_targeting_group`` schema. +# generated by datamodel-codegen: +# filename: core/package_signal_targeting_group.json +# timestamp: 2026-05-26T03:43:17+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.package_signal_targeting_group import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import package_signal_targeting + + +class Operator(StrEnum): + any = 'any' + none = 'none' -from adcp.types.generated_poc.core.package_signal_targeting_group import ( - Operator, - PackageSignalTargetingGroup, -) -# Explicit exports -__all__ = ["Operator", "PackageSignalTargetingGroup"] +class PackageSignalTargetingGroup(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + operator: Annotated[ + Operator, + Field( + description="How to evaluate the signals in this group. 'any' is an OR include group. 'none' is an exclusion group equivalent to NOT (A OR B OR C)." + ), + ] + signals: Annotated[ + list[package_signal_targeting.PackageSignalTargeting], + Field( + description='Signal targeting entries evaluated by this group. Each entry uses the package signal targeting shape, including signal_ref, value expression, and optional pricing, execution-handle, or activation fields.', + min_length=1, + ), + ] diff --git a/src/adcp/types/domains/core/package_signal_targeting_groups.py b/src/adcp/types/domains/core/package_signal_targeting_groups.py index ca312e7c3..f65bc9d4d 100644 --- a/src/adcp/types/domains/core/package_signal_targeting_groups.py +++ b/src/adcp/types/domains/core/package_signal_targeting_groups.py @@ -1,20 +1,31 @@ -"""Types declared by the AdCP ``core/package_signal_targeting_groups`` schema. +# generated by datamodel-codegen: +# filename: core/package_signal_targeting_groups.json +# timestamp: 2026-05-26T03:43:17+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.package_signal_targeting_groups import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import package_signal_targeting_group -from adcp.types.generated_poc.core.package_signal_targeting_groups import ( - PackageSignalTargetingGroups, -) -# Explicit exports -__all__ = ["PackageSignalTargetingGroups"] +class PackageSignalTargetingGroups(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + operator: Annotated[ + Literal['all'], + Field( + description="Groups-level operator. Required even though v1 only supports 'all': every child group must be satisfied." + ), + ] = 'all' + groups: Annotated[ + list[package_signal_targeting_group.PackageSignalTargetingGroup], + Field( + description="Signal targeting groups to evaluate. Use operator 'any' for include groups and 'none' for exclusion groups.", + min_length=1, + ), + ] diff --git a/src/adcp/types/domains/core/package_targeting_resolution.py b/src/adcp/types/domains/core/package_targeting_resolution.py index d1da6b67a..2c7d8c445 100644 --- a/src/adcp/types/domains/core/package_targeting_resolution.py +++ b/src/adcp/types/domains/core/package_targeting_resolution.py @@ -1,18 +1,26 @@ -"""Types declared by the AdCP ``core/package_targeting_resolution`` schema. +# generated by datamodel-codegen: +# filename: core/package_targeting_resolution.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.package_targeting_resolution import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import demographic_targeting_resolution +from . import ext as ext_1 -from adcp.types.generated_poc.core.package_targeting_resolution import PackageTargetingResolution -# Explicit exports -__all__ = ["PackageTargetingResolution"] +class PackageTargetingResolution(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + demographics: Annotated[ + demographic_targeting_resolution.DemographicTargetingResolution, + Field( + description='Canonical demographic predicate and exact seller execution details. Include whenever demographic targeting was requested or applied.' + ), + ] + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/pagination_request.py b/src/adcp/types/domains/core/pagination_request.py index 626de5d42..95ae0f082 100644 --- a/src/adcp/types/domains/core/pagination_request.py +++ b/src/adcp/types/domains/core/pagination_request.py @@ -1,18 +1,24 @@ -"""Types declared by the AdCP ``core/pagination_request`` schema. +# generated by datamodel-codegen: +# filename: core/pagination_request.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.pagination_request import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field -from adcp.types.generated_poc.core.pagination_request import PaginationRequest -# Explicit exports -__all__ = ["PaginationRequest"] +class PaginationRequest(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + max_results: Annotated[ + SchemaInt | None, + Field(description='Maximum number of items to return per page', ge=1, le=100), + ] = 50 + cursor: Annotated[ + str | None, + Field(description='Opaque cursor from a previous response to fetch the next page'), + ] = None diff --git a/src/adcp/types/domains/core/pagination_response.py b/src/adcp/types/domains/core/pagination_response.py index 15021415b..741a459e8 100644 --- a/src/adcp/types/domains/core/pagination_response.py +++ b/src/adcp/types/domains/core/pagination_response.py @@ -1,18 +1,32 @@ -"""Types declared by the AdCP ``core/pagination_response`` schema. +# generated by datamodel-codegen: +# filename: core/pagination_response.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.pagination_response import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictBool -from adcp.types.generated_poc.core.pagination_response import PaginationResponse -# Explicit exports -__all__ = ["PaginationResponse"] +class PaginationResponse(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + has_more: Annotated[ + StrictBool, Field(description='Whether more results are available beyond this page') + ] + cursor: Annotated[ + str | None, + Field( + description='Opaque cursor to pass in the next request to fetch the next page. Only present when has_more is true.' + ), + ] = None + total_count: Annotated[ + SchemaInt | None, + Field( + description='Total number of items matching the query across all pages. Optional because not all backends can efficiently compute this.', + ge=0, + ), + ] = None diff --git a/src/adcp/types/domains/core/performance_feedback.py b/src/adcp/types/domains/core/performance_feedback.py index c0db92e66..e00dcc6b7 100644 --- a/src/adcp/types/domains/core/performance_feedback.py +++ b/src/adcp/types/domains/core/performance_feedback.py @@ -1,25 +1,157 @@ -"""Types declared by the AdCP ``core/performance_feedback`` schema. +# generated by datamodel-codegen: +# filename: core/performance_feedback.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.performance_feedback import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat + +from ..enums import attribution_methodology as attribution_methodology_1 +from ..enums import available_metric +from ..enums import completion_source as completion_source_1 +from ..enums import feedback_source as feedback_source_1 +from ..enums import lift_dimension as lift_dimension_1 +from ..enums import metric_type as metric_type_1 +from ..enums import viewability_standard as viewability_standard_1 +from . import brand_ref, duration, vendor_metric_id + + +class MeasurementPeriod(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + start: Annotated[ + AwareDatetime, Field(description='ISO 8601 start timestamp for measurement period') + ] + end: Annotated[ + AwareDatetime, Field(description='ISO 8601 end timestamp for measurement period') + ] + + +class Status(StrEnum): + accepted = 'accepted' + queued = 'queued' + applied = 'applied' + rejected = 'rejected' + + +class Qualifier(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + viewability_standard: viewability_standard_1.ViewabilityStandard | None = None + completion_source: completion_source_1.CompletionSource | None = None + attribution_methodology: attribution_methodology_1.AttributionMethodology | None = None + attribution_window: duration.Duration | None = None + lift_dimension: lift_dimension_1.LiftDimension | None = None + + +class Metric(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + scope: Annotated[ + Literal['standard'], + Field(description='Standard metric from the closed `available-metric.json` enum.'), + ] = 'standard' + metric_id: Annotated[ + available_metric.AvailableMetric, Field(description='Identifier for the standard metric.') + ] + qualifier: Annotated[ + Qualifier | None, + Field( + description='Disambiguator — same shape as `committed_metrics.qualifier`. Carry `viewability_standard` for MRC vs GroupM viewability feedback; `completion_source` for seller- vs vendor-attested completion feedback; `attribution_methodology` and `attribution_window` for outcome-metric feedback (the buyer pinning a specific methodology or window when the feedback is methodology-sensitive). Closed (`additionalProperties: false`); new keys ship explicitly in subsequent minors.' + ), + ] = None + + +class Metric7(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + scope: Annotated[ + Literal['vendor'], + Field(description='Vendor-defined metric, identified by the tuple `(vendor, metric_id)`.'), + ] = 'vendor' + vendor: Annotated[ + brand_ref.BrandReference, + Field( + description="Vendor that defines and computes this metric. Same identity discipline as `vendor_metric_value.vendor` and `committed_metrics` vendor-scope entries. Distinct from the row-level `vendor` field at the top of `performance-feedback`, which identifies the *source* of the feedback (the party producing it); this nested vendor identifies the *vendor that defines the metric* (which may or may not be the same party). Typically the same when third-party verification reports its own metric; can differ when a buyer's MMM tool reports on a separately-defined vendor metric." + ), + ] + metric_id: Annotated[ + vendor_metric_id.VendorMetricId, + Field(description="Identifier for the metric within the vendor's vocabulary."), + ] + qualifier: Annotated[ + Qualifier | None, + Field( + description='Optional disambiguator mirroring the vendor-scope qualifier on `committed_metrics` — same closed key set as standard-scope entries.' + ), + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.performance_feedback import ( - MeasurementPeriod, - Metric, - Metric7, - PerformanceFeedback, - Qualifier, - Status, -) - -# Explicit exports -__all__ = ["MeasurementPeriod", "Metric", "Metric7", "PerformanceFeedback", "Qualifier", "Status"] +class PerformanceFeedback(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + feedback_id: Annotated[ + str, Field(description='Unique identifier for this performance feedback submission') + ] + media_buy_id: Annotated[str, Field(description="Publisher's media buy identifier")] + package_id: Annotated[ + str | None, + Field( + description='Specific package within the media buy (if feedback is package-specific)' + ), + ] = None + creative_id: Annotated[ + str | None, Field(description='Specific creative asset (if feedback is creative-specific)') + ] = None + measurement_period: Annotated[ + MeasurementPeriod, Field(description='Time period for performance measurement') + ] + performance_index: Annotated[ + StrictFloat, + Field( + description='Normalized performance score (0.0 = no value, 1.0 = expected, >1.0 = above expected)', + ge=0.0, + ), + ] + metric_type: Annotated[ + metric_type_1.MetricType | None, + Field( + deprecated=True, + description='**Deprecated as of this minor.** The legacy free-form metric enum that mixes metrics, verification, and attribution into one list. New implementations SHOULD use `metric` (the discriminated `(scope, metric_id, qualifier)` row shape) and populate `metric_type` with a best-effort string for one-minor backwards compatibility. When both `metric` and `metric_type` are present, consumers MUST use `metric` for dispatch. Removed at the next major. See [docs/measurement/taxonomy](https://docs.adcontextprotocol.org/docs/measurement/taxonomy) for why the layered shape replaces the flat enum.', + ), + ] = None + metric: Annotated[ + Metric | Metric7 | None, + Field( + description='The metric this feedback row pertains to, using the same `(scope, metric_id, qualifier)` row shape as `committed_metrics` and package-level delivery values (`metric_values` or `vendor_metric_values`). Preferred over the legacy `metric_type` field for new implementations. Brings performance-feedback into the same atomic unit and dispatch model as the rest of the measurement surface — buyer agents reconcile feedback against the contract surface using the row-level join on `(scope, metric_id, qualifier)`. **Optional and may be omitted entirely for holistic feedback** (e.g., a trader flagging a campaign as underperforming without a specific metric in mind — `performance_index` plus the response narrative carry the signal). Senders SHOULD populate `metric` when the feedback is metric-specific so consumers can route it to the right optimization path; senders MAY omit it for general performance feedback.', + discriminator='scope', + ), + ] = None + feedback_source: Annotated[ + feedback_source_1.FeedbackSource, Field(description='Source of the performance data') + ] + vendor: Annotated[ + brand_ref.BrandReference | None, + Field( + description="Vendor that produced this feedback. SHOULD be populated when `feedback_source` is `third_party_measurement` or `verification_partner` AND a single attesting vendor exists — without it, the row is unattributed and consumers can't verify authorization, resolve metric definitions, or route disputes. OMIT for blended outputs where no single vendor owns the result: MMM mixes (Nielsen MMM, Analytic Partners, in-house mix models combining multiple vendor inputs), multi-touch attribution outputs that join across vendors, and clean-room outputs (LiveRamp, Habu, AWS Clean Rooms) where the clean room is not the measurement source. For these cases, leave `vendor` absent and use the response's narrative payload to describe provenance. Optional for `buyer_attribution` and `platform_analytics` (those sources are implicit from context). The vendor's `brand.json` `agents[type='measurement']` is the discovery anchor; metric definitions live on the agent's `get_adcp_capabilities.measurement.metrics[]` block. Same identity discipline as `vendor_metric_value.vendor` and `performance-standard.vendor`." + ), + ] = None + status: Annotated[Status, Field(description='Processing status of the performance feedback')] + submitted_at: Annotated[ + AwareDatetime, Field(description='ISO 8601 timestamp when feedback was submitted') + ] + applied_at: Annotated[ + AwareDatetime | None, + Field( + description='ISO 8601 timestamp when feedback was applied to optimization algorithms' + ), + ] = None diff --git a/src/adcp/types/domains/core/performance_feedback_assertion.py b/src/adcp/types/domains/core/performance_feedback_assertion.py index 1bc59243e..b7ee9f325 100644 --- a/src/adcp/types/domains/core/performance_feedback_assertion.py +++ b/src/adcp/types/domains/core/performance_feedback_assertion.py @@ -1,22 +1,163 @@ -"""Types declared by the AdCP ``core/performance_feedback_assertion`` schema. +# generated by datamodel-codegen: +# filename: core/performance_feedback_assertion.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.performance_feedback_assertion import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import feedback_source as feedback_source_1 +from ..enums import metric_type as metric_type_1 +from ..enums import performance_baseline +from . import brand_ref, datetime_range, performance_feedback_metric + + +class ConfidenceInterval(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + lower: StrictFloat + upper: StrictFloat + level: Annotated[StrictFloat, Field(gt=0.0, lt=1.0)] + + +class Evidence(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + sample_size: Annotated[SchemaInt | None, Field(ge=1)] = None + confidence_interval: Annotated[ + ConfidenceInterval | None, + Field( + description='Confidence interval on the performance_index scale. sample_size may be omitted when the producer cannot disclose it.' + ), + ] = None -from adcp.types.generated_poc.core.performance_feedback_assertion import ( - ConfidenceInterval, - Evidence, - PerformanceFeedbackAssertion, -) -# Explicit exports -__all__ = ["ConfidenceInterval", "Evidence", "PerformanceFeedbackAssertion"] +class PerformanceFeedbackAssertion(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + media_buy_id: Annotated[ + str, + Field( + description="Receiver-scoped media buy identifier. On the provider-to-orchestrator hop this is the orchestrator's measurement-facing identifier; on the orchestrator-to-seller hop it is the seller-assigned media buy identifier.", + min_length=1, + ), + ] + package_id: Annotated[ + str | None, + Field( + description='Receiver-scoped package identifier when the assertion is package-specific. The orchestrator maps its measurement-facing identifier to seller-local identifiers during fan-out.', + min_length=1, + ), + ] = None + creative_id: Annotated[ + str | None, + Field( + description='Receiver-scoped creative identifier when the assertion is creative-specific. The orchestrator maps its measurement-facing identifier to seller-local identifiers during fan-out.', + min_length=1, + ), + ] = None + measurement_period: Annotated[ + datetime_range.DatetimeRange, + Field(description='Period whose performance is summarized by this assertion.'), + ] + performance_index: Annotated[ + StrictFloat, + Field( + description='Normalized decision signal where 1.0 equals the named baseline, values below 1.0 underperform it, and values above 1.0 outperform it. Compact-contract producers (baseline present) MUST use observed divided by baseline for higher-is-better ratio metrics and baseline divided by observed for lower-is-better ratio metrics such as cost per acquisition.', + ge=0.0, + ), + ] + baseline: Annotated[ + performance_baseline.PerformanceBaseline | None, + Field( + description='Expectation represented by performance_index = 1.0. Compact-contract producers MUST populate this field; it remains optional in the schema so existing 3.x request payloads remain valid.' + ), + ] = None + metric: Annotated[ + performance_feedback_metric.PerformanceFeedbackMetric | None, + Field( + description='Metric this assertion describes. Omit only for holistic feedback that is not attributable to one metric.' + ), + ] = None + metric_type: Annotated[ + metric_type_1.MetricType | None, + Field( + deprecated=True, + description='Deprecated legacy metric classification retained for existing request payloads. New implementations SHOULD use metric; when both are present consumers MUST use metric for dispatch.', + ), + ] = None + producer: Annotated[ + brand_ref.BrandReference | None, + Field( + description='Brand that produced the analysis. The receiving orchestrator gateway verifies this reference against authenticated caller identity and preserves it during seller fan-out.' + ), + ] = None + vendor: Annotated[ + brand_ref.BrandReference | None, + Field( + deprecated=True, + description='Deprecated alias for producer retained for previously documented request payloads. When both are present consumers use producer.', + ), + ] = None + feedback_source: Annotated[ + feedback_source_1.FeedbackSource | None, + Field(description='Legacy categorical source hint retained for existing request payloads.'), + ] = None + methodology: Annotated[ + str | None, + Field( + description='Producer-scoped methodology identifier such as geo_incrementality, media_mix_model, or deterministic_attribution.', + max_length=100, + min_length=1, + ), + ] = None + methodology_version: Annotated[ + str | None, + Field( + description='Producer-defined version of the methodology used for this assertion.', + max_length=100, + min_length=1, + ), + ] = None + study_ref: Annotated[ + str | None, + Field( + description='Opaque producer-assigned study or model-run reference. Receivers use it for correlation only and MUST NOT interpret it as an experiment-execution instruction.', + max_length=255, + min_length=1, + ), + ] = None + evidence: Annotated[ + Evidence | None, + Field( + description='Small inline evidence summary used to weight the assertion without transporting the full study.' + ), + ] = None + evidence_ref: Annotated[ + AnyUrl | None, + Field( + description='Provider-hosted evidence or result reference for parties authorized to inspect the full study.' + ), + ] = None + as_of: Annotated[ + AwareDatetime | None, Field(description='When the producer computed this assertion.') + ] = None + final: Annotated[ + StrictBool | None, + Field( + description='Whether the producer expects this assertion to be revised as data matures.' + ), + ] = None + supersedes_feedback_id: Annotated[ + str | None, + Field( + description='Receiver-issued feedback_id of the earlier assertion this one replaces at the same hop.', + min_length=1, + ), + ] = None diff --git a/src/adcp/types/domains/core/performance_feedback_metric.py b/src/adcp/types/domains/core/performance_feedback_metric.py index e5dbffd50..b349e74cc 100644 --- a/src/adcp/types/domains/core/performance_feedback_metric.py +++ b/src/adcp/types/domains/core/performance_feedback_metric.py @@ -1,28 +1,72 @@ -"""Types declared by the AdCP ``core/performance_feedback_metric`` schema. +# generated by datamodel-codegen: +# filename: core/performance_feedback_metric.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.performance_feedback_metric import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field + +from ..enums import attribution_methodology as attribution_methodology_1 +from ..enums import available_metric +from ..enums import completion_source as completion_source_1 +from ..enums import lift_dimension as lift_dimension_1 +from ..enums import viewability_standard as viewability_standard_1 +from . import brand_ref, duration, vendor_metric_id + + +class Qualifier(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + viewability_standard: viewability_standard_1.ViewabilityStandard | None = None + completion_source: completion_source_1.CompletionSource | None = None + attribution_methodology: attribution_methodology_1.AttributionMethodology | None = None + attribution_window: duration.Duration | None = None + lift_dimension: lift_dimension_1.LiftDimension | None = None + + +class PerformanceFeedbackMetric1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + scope: Literal['standard'] = 'standard' + metric_id: available_metric.AvailableMetric + qualifier: Annotated[ + Qualifier | None, + Field( + description='Identity-affecting metric qualifiers. Statistical evidence and feedback-producer identity do not belong here.' + ), + ] = None + + +class PerformanceFeedbackMetric2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + scope: Literal['vendor'] = 'vendor' + vendor: Annotated[ + brand_ref.BrandReference, + Field( + description='Vendor that defines this metric. This can differ from the producer of the feedback assertion.' + ), + ] + metric_id: vendor_metric_id.VendorMetricId + qualifier: Annotated[ + Qualifier | None, + Field( + description='Optional disambiguator mirroring the vendor-scope qualifier on `committed_metrics` — same closed key set as standard-scope entries.' + ), + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.performance_feedback_metric import ( - PerformanceFeedbackMetric, - PerformanceFeedbackMetric1, - PerformanceFeedbackMetric2, - Qualifier, -) - -# Explicit exports -__all__ = [ - "PerformanceFeedbackMetric", - "PerformanceFeedbackMetric1", - "PerformanceFeedbackMetric2", - "Qualifier", +PerformanceFeedbackMetric = Annotated[ + PerformanceFeedbackMetric1 | PerformanceFeedbackMetric2, + Field( + description='Metric identity for a performance-feedback assertion. Uses the same standard/vendor discriminator and qualifier vocabulary as committed_metrics, missing_metrics, and package-level delivery values (metric_values or vendor_metric_values).', + discriminator='scope', + title='Performance Feedback Metric', + ), ] diff --git a/src/adcp/types/domains/core/performance_standard.py b/src/adcp/types/domains/core/performance_standard.py index 1a4aacfa9..9026fbe00 100644 --- a/src/adcp/types/domains/core/performance_standard.py +++ b/src/adcp/types/domains/core/performance_standard.py @@ -1,18 +1,43 @@ -"""Types declared by the AdCP ``core/performance_standard`` schema. +# generated by datamodel-codegen: +# filename: core/performance_standard.json +# timestamp: 2026-10-03T15:09:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.performance_standard import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import performance_standard_metric, viewability_standard +from . import brand_ref -from adcp.types.generated_poc.core.performance_standard import PerformanceStandard -# Explicit exports -__all__ = ["PerformanceStandard"] +class PerformanceStandard(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + metric: Annotated[ + performance_standard_metric.PerformanceStandardMetric, + Field(description='The performance metric this standard applies to.'), + ] + threshold: Annotated[ + StrictFloat, + Field( + description='Rate threshold as a decimal (e.g., 0.70 for 70%). Whether this is a floor or ceiling depends on the metric: for viewability, completion_rate, brand_safety, attention_score the actual rate must be >= threshold; for ivt the actual rate must be <= threshold.', + ge=0.0, + le=1.0, + ), + ] + standard: Annotated[ + viewability_standard.ViewabilityStandard | None, + Field( + description="Measurement standard. Required when metric is 'viewability' (MRC and GroupM define materially different thresholds). Omit for other metrics." + ), + ] = None + vendor: Annotated[ + brand_ref.BrandReference, + Field( + description="Vendor measuring this metric (e.g., { domain: 'doubleverify.com' }). The vendor's brand.json agents array (type: 'measurement') is the discovery point for their measurement agent. When specified on a confirmed package, creatives MUST include tracker_script or tracker_pixel assets from this vendor." + ), + ] diff --git a/src/adcp/types/domains/core/placement.py b/src/adcp/types/domains/core/placement.py index e1088bee2..c678776a6 100644 --- a/src/adcp/types/domains/core/placement.py +++ b/src/adcp/types/domains/core/placement.py @@ -1,32 +1,196 @@ -"""Types declared by the AdCP ``core/placement`` schema. +# generated by datamodel-codegen: +# filename: core/placement.json +# timestamp: 2026-10-04T01:14:11+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.placement import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from collections.abc import Sequence +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, model_validator -from adcp.types.generated_poc.core.placement import ( - Identifier, - Kind, - Mode, - Placement, - ProductDoohPlacementAttributes, - ProductDoohScreenResolution, +from ..enums import ( + audio_distribution_type, + dooh_motion_type, + identifier_types, + social_placement_surface, + sponsored_placement_type, + video_placement_type, ) +from . import format_id, product_format_declaration, seller_agent_ref + + +class Kind(StrEnum): + publisher_ref = 'publisher_ref' + seller_inline = 'seller_inline' + + +class Mode(StrEnum): + targetable = 'targetable' + included = 'included' + + +class Identifier(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + type: identifier_types.PropertyIdentifierTypes + value: Annotated[ + str, + Field( + description='Identifier value, optionally authority-prefixed for externally governed IDs (e.g., space:1234931339).' + ), + ] + + +class ProductDoohScreenResolution(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + width: Annotated[SchemaInt, Field(description='Screen width in pixels.', ge=1)] + height: Annotated[SchemaInt, Field(description='Screen height in pixels.', ge=1)] + + +class ProductDoohPlacementAttributes(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + slot_duration_seconds: Annotated[ + SchemaInt | None, + Field( + description='Scheduled duration of one ad slot in seconds. This is an inventory fact used for loop and share calculations, not the creative-duration contract.', + ge=1, + ), + ] = None + loop_duration_seconds: Annotated[ + SchemaInt | None, + Field( + description='Duration of the full ad loop rotation in seconds and the canonical source for loop duration.', + ge=1, + ), + ] = None + screen_resolution: ProductDoohScreenResolution | None = None + motion: Annotated[ + dooh_motion_type.DoohMotionType | None, + Field( + description='Physical motion capability of a visual DOOH screen, not an accepted-format declaration. Omit for audio-only placements.' + ), + ] = None + + +class Placement(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + kind: Annotated[ + Kind, + Field( + description='Placement authority discriminator. `publisher_ref` is publisher-catalog identity; `seller_inline` is sales-agent-authored identity.' + ), + ] + placement_id: Annotated[ + str, + Field( + description='Placement identifier. For publisher_ref it is scoped by publisher_domain and resolves in adagents.json. For seller_inline it is scoped by seller_agent, or by the enclosing seller and product for legacy rows.' + ), + ] + publisher_domain: Annotated[ + str | None, + Field( + description="For publisher_ref, the domain whose adagents.json declares the placement and part of canonical identity. For seller_inline, optional inventory-publisher attribution only; it does not grant the seller authority to mint IDs in that publisher's catalog namespace.", + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] = None + seller_agent: Annotated[ + seller_agent_ref.SellerAgentReference | None, + Field( + description='Sales agent that defines a seller_inline placement. Together with placement_id this is its self-contained identity. New 3.2 sellers SHOULD populate it; legacy product-context inline placements may omit it. Not used for publisher_ref.' + ), + ] = None + name: Annotated[ + str | None, + Field( + description='Human-readable name for the placement (e.g., \'Homepage Banner\', \'Article Sidebar\'). Required for `kind: "seller_inline"`. May be omitted for publisher-referenced placements because buyers resolve the name from the publisher declaration identified by `{publisher_domain, placement_id}`.' + ), + ] = None + description: Annotated[ + str | None, Field(description='Detailed description of where and how the placement appears') + ] = None + mode: Annotated[ + Mode, + Field( + description="Required product-level relationship to this placement. targetable means the buyer may include the publisher-scoped ref in targeting_overlay.placement_selection; a creative may be routed there only after it is purchased. included means fixed product inventory: it cannot be independently selected, but across discovery, create, and update a selected request exactly equal to the product's complete included placement set is an inherent restatement and may be echoed on the package without overlay_support.placement_selection. A product containing any included placement MUST NOT declare overlay_support.placement_selection; partial selection requires a separately selectable product configuration. During the migration window ending 2026-11-25, buyers MAY tolerate legacy products that omit mode and treat them as targetable; after that date buyers SHOULD fail closed." + ), + ] + tags: Annotated[ + list[str] | None, + Field( + description="Optional tags for grouping placements within a product (e.g., 'homepage', 'native', 'premium'). When the placement_id comes from the publisher registry, these should align with the registry tags unless the product is narrowing scope." + ), + ] = None + format_ids: Annotated[ + Sequence[format_id.FormatReferenceStructuredObject] | None, + Field( + deprecated=True, + description='Deprecated in AdCP 3.2; removed in AdCP 4.0. Legacy named-format placement narrowing. Can include concrete, template, or parameterized format IDs. When present on a product placement, this field narrows the product-level `format_ids` contract and MUST NOT introduce formats the product does not accept. Use canonical `format_options`.', + min_length=1, + ), + ] = None + format_options: Annotated[ + list[product_format_declaration.ProductFormatDeclaration] | None, + Field( + description="Canonical seller-side narrowing for this product placement. When present, these declarations are intersected with the product-level format_options and MUST NOT introduce a format outside that product upper bound. For kind publisher_ref, buyers MUST also resolve {publisher_domain, placement_id} in the publisher's adagents.json and intersect the publisher catalog constraint: use the public placement's format_options when present (resolving bare format_option_id references against same-file top-level formats[]), otherwise use applicable top-level formats[] scoped to that placement's properties. Omitting this inline field removes only the seller-inline layer; it does not bypass a publisher placement or property-scoped narrowing. The placement inherits the full product-level set only when no applicable publisher catalog narrowing exists. Unresolved publisher placement or format-option references fail closed. Locale policy participates in the same intersection: when the product policy is absent, a placement may introduce any concrete policy as a narrowing of the unconstrained option; when both are present, every placement accepted_language_range must be contained by a product range under RFC 4647 Basic Filtering (`fr-CA` narrows `fr`; `fr` does not narrow `fr-CA`). Buyers compute effective locale eligibility independently for each placement. Any effective locale-constrained route is canonical-only and has no projecting product or placement format_id.", + min_length=1, + ), + ] = None + video_placement_types: Annotated[ + list[video_placement_type.VideoPlacementType] | None, + Field( + description='Declared video placement types for this product placement, using IAB Tech Lab/OpenRTB 2.6 video.plcmt definitions with AdCP-native names. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', + min_length=1, + ), + ] = None + audio_distribution_types: Annotated[ + list[audio_distribution_type.AudioDistributionType] | None, + Field( + description='Declared audio distribution types for this product placement, using IAB Tech Lab/OpenRTB 2.6 audio.feed definitions with AdCP-native names. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', + min_length=1, + ), + ] = None + sponsored_placement_types: Annotated[ + list[sponsored_placement_type.SponsoredPlacementType] | None, + Field( + description='Declared sponsored-placement types for this product placement, distinguishing where the catalog-driven retail-media placement renders on the retailer surface. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', + min_length=1, + ), + ] = None + social_placement_surfaces: Annotated[ + list[social_placement_surface.SocialPlacementSurface] | None, + Field( + description='Declared social-placement surfaces for this product placement, distinguishing the in-app surface where the social placement renders. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', + min_length=1, + ), + ] = None + identifiers: Annotated[ + list[Identifier] | None, + Field( + description='Optional external inventory identifiers for this placement, using the same {type, value} shape as property identifiers. Externally governed IDs should be authority-prefixed (e.g., space:1234931339, geopath:30961, fcc:73953). Seller-local IDs are opaque values scoped by the surrounding publisher namespace. Useful for DOOH venue and installed-endpoint IDs, broadcast facility IDs, and any channel where placements map to externally registered inventory. For kind: publisher_ref, the effective identifier set is the union of the resolved publisher declaration and this product declaration, de-duplicated by exact (type, value); a product cannot suppress a publisher-declared identifier by omission.', + min_length=1, + ), + ] = None + dooh_placement_attributes: ProductDoohPlacementAttributes | None = None -# Explicit exports -__all__ = [ - "Identifier", - "Kind", - "Mode", - "Placement", - "ProductDoohPlacementAttributes", - "ProductDoohScreenResolution", -] + @model_validator(mode='after') + def _require_schema_required_group(self) -> Placement: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('name',), ('publisher_domain',),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'Placement requires at least one of these field groups: name | publisher_domain' + ) diff --git a/src/adcp/types/domains/core/placement_definition.py b/src/adcp/types/domains/core/placement_definition.py index 0a789c672..37d272494 100644 --- a/src/adcp/types/domains/core/placement_definition.py +++ b/src/adcp/types/domains/core/placement_definition.py @@ -1,30 +1,209 @@ -"""Types declared by the AdCP ``core/placement_definition`` schema. +# generated by datamodel-codegen: +# filename: core/placement_definition.json +# timestamp: 2026-10-04T01:14:11+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.placement_definition import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, model_validator -from adcp.types.generated_poc.core.placement_definition import ( - FormatOptions, - Identifier, - PlacementDefinition, - PublisherDoohPlacementAttributes, - PublisherDoohScreenResolution, +from ..enums import audio_distribution_type +from ..enums import channels as channels_1 +from ..enums import ( + dooh_motion_type, + identifier_types, + social_placement_surface, + sponsored_placement_type, + video_placement_type, ) +from . import creative_locale_policy +from . import ext as ext_1 +from . import presentation_ref as presentation_ref_1 +from . import preview_provider as preview_provider_1 +from . import product_format_declaration, property_id, property_tag + + +class Identifier(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + type: identifier_types.PropertyIdentifierTypes + value: Annotated[ + str, + Field( + description='Identifier value, optionally authority-prefixed for externally governed IDs (e.g., space:1234931339).' + ), + ] + + +class PublisherDoohScreenResolution(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + width: Annotated[SchemaInt, Field(description='Screen width in pixels.', ge=1)] + height: Annotated[SchemaInt, Field(description='Screen height in pixels.', ge=1)] + + +class PublisherDoohPlacementAttributes(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + slot_duration_seconds: Annotated[ + SchemaInt | None, + Field( + description='Default scheduled duration of one ad slot in seconds. This is an inventory fact used for loop and share calculations, not the creative-duration contract.', + ge=1, + ), + ] = None + loop_duration_seconds: Annotated[ + SchemaInt | None, + Field( + description='Duration of the full ad loop rotation in seconds and the canonical source for loop duration.', + ge=1, + ), + ] = None + screen_resolution: PublisherDoohScreenResolution | None = None + motion: Annotated[ + dooh_motion_type.DoohMotionType | None, + Field( + description='Physical motion capability of a visual DOOH screen, not an accepted-format declaration. Omit for audio-only placements.' + ), + ] = None + + +class FormatOptions(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + format_option_id: Annotated[ + str, Field(description="Matches a `format_option_id` in the file's top-level `formats[]`.") + ] + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional placement-local locale policy. When the resolved top-level format declares a policy, every placement range must be contained by one of its ranges; otherwise this introduces a narrowing of the unconstrained format. The resolved effective route remains canonical-only.' + ), + ] = None + + +class PlacementDefinition(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + placement_id: Annotated[ + str, Field(description='Stable placement identifier unique within this adagents.json file.') + ] + name: Annotated[ + str, + Field( + description="Human-readable placement name (e.g., 'Homepage Banner', 'Pre-roll', 'Sponsored Listing Slot 1')." + ), + ] + description: Annotated[ + str | None, Field(description='Description of where and how this placement appears.') + ] = None + tags: Annotated[ + list[str] | None, + Field( + description="Tags for grouping and querying placements across properties and products (e.g., 'homepage', 'native', 'premium', 'pre_roll')." + ), + ] = None + property_ids: Annotated[ + list[property_id.PropertyId] | None, + Field( + description='Property IDs in this adagents.json where this placement can appear.', + min_length=1, + ), + ] = None + property_tags: Annotated[ + list[property_tag.PropertyTag] | None, + Field( + description="Property tags in this adagents.json where this placement can appear. Useful for network-wide positions such as 'pre_roll' or 'homepage_native_feed'.", + min_length=1, + ), + ] = None + collection_ids: Annotated[ + list[str] | None, + Field( + description='Optional collection IDs in this adagents.json where this placement is valid. Use to narrow a placement to specific content programs carried on the selected properties.', + min_length=1, + ), + ] = None + channels: Annotated[ + list[channels_1.MediaChannel] | None, + Field( + description='Advertising channels where this placement can run. Products that reference the placement may narrow this set but should not broaden it.', + min_length=1, + ), + ] = None + presentation_ref: Annotated[ + presentation_ref_1.PlacementPresentationReference | None, + Field( + description="Optional publisher-specific declarative frame for representing this placement's real chrome offline. It composes around the selected creative rendering unless the publisher-delegated preview_provider route explicitly covers placement presentation. It MUST NOT be promoted to or copied onto a shared format entry." + ), + ] = None + preview_provider: Annotated[ + preview_provider_1.PublisherDesignatedPreviewProvider | None, + Field( + description="Optional publisher delegation to a callable AdCP preview provider for specific format options on this placement. This publisher-origin route is the only mechanism that grants preview authority inside the placement's scope; an agent's own rendering_origin description does not." + ), + ] = None + format_options: Annotated[ + list[FormatOptions | product_format_declaration.ProductFormatDeclaration] | None, + Field( + description="Optional 3.1+ canonical format-option declarations supported by this placement. This `adagents.json` placement surface supports two entry shapes: (1) reference an entry in the same file's top-level `formats[]` by `format_option_id` only — buyers resolve the full declaration from `formats[]` by matching `format_option_id` (recommended; avoids duplication). Top-level formats may be publisher-owned custom formats or narrowed canonical formats; their `format_kind` is the canonical anchor that the placement reference inherits. (2) carry an inline `ProductFormatDeclaration` directly — for placement-specific canonical narrowing that doesn't fit a reusable catalog entry. This bare-reference shape is placement-catalog specific; Product `format_options[]` entries are always full `ProductFormatDeclaration` objects with required `format_kind` and `params`.\n\nProduct-level formats remain the upper bound for a sellable product. Catalog placement formats describe placement support; when a product references the placement and also declares product-level formats, buyers use the intersection for that product placement. A catalog placement format that is absent from the product-level declaration is not accepted for that product unless the product explicitly includes it. When the product locale policy is absent, placement locale_policy may introduce any concrete narrowing; when both are present, every placement accepted_language_range must be contained by a product range under RFC 4647 Basic Filtering. An effective placement policy is canonical-only: its resolved catalog and matching product declarations cannot project through legacy format_ids.\n\n**Format-option reference shape.** A format-option reference entry SHOULD carry ONLY `format_option_id` — extra fields are allowed (`additionalProperties: true`) so adopters who want to attach a placement-local override like `display_name` or a narrower `locale_policy` don't get rejected by the branch boundary, but buyer SDKs MUST resolve the format from the top-level `formats[]` by `format_option_id` and apply additional fields on the entry as placement-level overrides (NOT as a partial inline declaration). If a publisher needs to materially narrow the format at the placement, use the inline-declaration form instead.\n\n**Resolution scope is same-file only.** `format_option_id` resolves only within this file's top-level `formats[]`; cross-file references are not supported by design because same-file resolution keeps validation bounded and prevents a file from squatting on or narrowing another publisher's format_option_id. When `format_options[]` references a `format_option_id` not declared in the file's top-level `formats[]`, validators MUST surface this as `FORMAT_OPTION_UNRESOLVED` on the response `errors[]`. Buyers MUST fail closed for that placement (drop the format from the placement's accepted set) rather than silently dropping the placement or guessing intent.", + min_length=1, + ), + ] = None + video_placement_types: Annotated[ + list[video_placement_type.VideoPlacementType] | None, + Field( + description='Declared video placement types for this publisher placement, using IAB Tech Lab/OpenRTB 2.6 video.plcmt definitions with AdCP-native names. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. Product-level placement declarations may narrow this set but SHOULD NOT broaden it. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', + min_length=1, + ), + ] = None + audio_distribution_types: Annotated[ + list[audio_distribution_type.AudioDistributionType] | None, + Field( + description='Declared audio distribution types for this publisher placement, using IAB Tech Lab/OpenRTB 2.6 audio.feed definitions with AdCP-native names. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. Product-level placement declarations may narrow this set but SHOULD NOT broaden it. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', + min_length=1, + ), + ] = None + sponsored_placement_types: Annotated[ + list[sponsored_placement_type.SponsoredPlacementType] | None, + Field( + description='Declared sponsored-placement types for this publisher placement, distinguishing where the catalog-driven retail-media placement renders on the retailer surface. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. Product-level placement declarations may narrow this set but SHOULD NOT broaden it. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', + min_length=1, + ), + ] = None + social_placement_surfaces: Annotated[ + list[social_placement_surface.SocialPlacementSurface] | None, + Field( + description='Declared social-placement surfaces for this publisher placement, distinguishing the in-app surface where the social placement renders. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. Product-level placement declarations may narrow this set but SHOULD NOT broaden it. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', + min_length=1, + ), + ] = None + identifiers: Annotated[ + list[Identifier] | None, + Field( + description='Optional external inventory identifiers for this placement, using the same {type, value} shape as property identifiers. Externally governed IDs should be authority-prefixed (e.g., space:1234931339, geopath:30961, fcc:73953). Seller-local IDs are opaque values scoped by the surrounding publisher namespace. Product-level placement declarations may carry additional identifiers but SHOULD NOT contradict publisher-declared identifiers for the same type.', + min_length=1, + ), + ] = None + dooh_placement_attributes: PublisherDoohPlacementAttributes | None = None + ext: ext_1.ExtensionObject | None = None -# Explicit exports -__all__ = [ - "FormatOptions", - "Identifier", - "PlacementDefinition", - "PublisherDoohPlacementAttributes", - "PublisherDoohScreenResolution", -] + @model_validator(mode='after') + def _require_schema_required_group(self) -> PlacementDefinition: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('property_ids',), ('property_tags',),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'PlacementDefinition requires at least one of these field groups: property_ids | property_tags' + ) diff --git a/src/adcp/types/domains/core/placement_delivery_metrics.py b/src/adcp/types/domains/core/placement_delivery_metrics.py index b0b010d4d..6b908e6b6 100644 --- a/src/adcp/types/domains/core/placement_delivery_metrics.py +++ b/src/adcp/types/domains/core/placement_delivery_metrics.py @@ -1,18 +1,42 @@ -"""Types declared by the AdCP ``core/placement_delivery_metrics`` schema. +# generated by datamodel-codegen: +# filename: core/placement_delivery_metrics.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.placement_delivery_metrics import +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import placement_identity as placement_identity_1 +from .delivery_metrics import DeliveryMetrics -from adcp.types.generated_poc.core.placement_delivery_metrics import PlacementDeliveryMetrics -# Explicit exports -__all__ = ["PlacementDeliveryMetrics"] +class PlacementDeliveryMetrics(DeliveryMetrics): + placement_identity: Annotated[ + placement_identity_1.PlacementIdentity | None, + Field( + description='Self-contained identity. publisher_ref resolves in adagents.json; seller_inline is scoped by seller_agent.' + ), + ] = None + placement_name: Annotated[ + str | None, + Field( + description='Current human-readable placement name. Convenience metadata only; placement_identity is stable identity.' + ), + ] = None + placement_id: Annotated[ + str, + Field( + description='Required flat compatibility identity. It MUST equal placement_identity.placement_id when placement_identity is present.' + ), + ] + publisher_domain: Annotated[ + str | None, + Field( + description='Legacy publisher attribution. For publisher_ref identity it MUST equal placement_identity.publisher_domain. For seller_inline it identifies inventory context only and is not identity authority.', + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] = None + impressions: Any + spend: Any diff --git a/src/adcp/types/domains/core/placement_evidence.py b/src/adcp/types/domains/core/placement_evidence.py index e0d27e304..7ef6fcb9e 100644 --- a/src/adcp/types/domains/core/placement_evidence.py +++ b/src/adcp/types/domains/core/placement_evidence.py @@ -1,18 +1,37 @@ -"""Types declared by the AdCP ``core/placement_evidence`` schema. +# generated by datamodel-codegen: +# filename: core/placement_evidence.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.placement_evidence import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictFloat -from adcp.types.generated_poc.core.placement_evidence import PlacementEvidence -# Explicit exports -__all__ = ["PlacementEvidence"] +class PlacementEvidence(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + url: Annotated[ + AnyUrl, + Field( + description='The evidence artifact — photograph, scanned tearsheet, or evidence package' + ), + ] + captured_at: Annotated[ + AwareDatetime | None, Field(description='When the artifact was captured') + ] = None + latitude: Annotated[ + StrictFloat | None, Field(description='Capture location latitude', ge=-90.0, le=90.0) + ] = None + longitude: Annotated[ + StrictFloat | None, Field(description='Capture location longitude', ge=-180.0, le=180.0) + ] = None + notes: Annotated[ + str | None, + Field( + description="Context that doesn't fit the structured fields (e.g., 'shot from the eastbound approach at 40mph equivalent')" + ), + ] = None diff --git a/src/adcp/types/domains/core/placement_identity.py b/src/adcp/types/domains/core/placement_identity.py index 6de009554..22bbeca3d 100644 --- a/src/adcp/types/domains/core/placement_identity.py +++ b/src/adcp/types/domains/core/placement_identity.py @@ -1,22 +1,60 @@ -"""Types declared by the AdCP ``core/placement_identity`` schema. +# generated by datamodel-codegen: +# filename: core/placement_identity.json +# timestamp: 2026-10-04T04:53:46+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.placement_identity import +from typing import Any, Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, RootModel -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import seller_agent_ref + + +class PlacementIdentity1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['publisher_ref'] = 'publisher_ref' + publisher_domain: Annotated[ + str, + Field( + description='Domain whose adagents.json declares placement_id.', + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] + placement_id: Annotated[str, Field(min_length=1)] + + +class PlacementIdentity2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['seller_inline'] = 'seller_inline' + seller_agent: Annotated[ + seller_agent_ref.SellerAgentReference, + Field(description='Sales agent that defines and maintains the inline placement namespace.'), + ] + placement_id: Annotated[ + str, + Field( + description="Stable placement ID within the defining sales agent's namespace. The agent MUST NOT reuse it for a different semantic placement.", + min_length=1, + ), + ] -from adcp.types.generated_poc.core.placement_identity import ( - PlacementIdentity, - PlacementIdentity1, - PlacementIdentity2, -) -# Explicit exports -__all__ = ["PlacementIdentity", "PlacementIdentity1", "PlacementIdentity2"] +class PlacementIdentity(RootModel[PlacementIdentity1 | PlacementIdentity2]): + root: Annotated[ + PlacementIdentity1 | PlacementIdentity2, + Field( + description='Self-contained identity for either a publisher-catalog placement or a sales-agent-defined inline placement. The discriminator names which authority owns placement_id.', + title='Placement Identity', + ), + ] + def __getattr__(self, name: str) -> Any: + """Proxy attribute access to the wrapped type.""" + if name.startswith('_'): + raise AttributeError(name) + return getattr(self.root, name) diff --git a/src/adcp/types/domains/core/placement_presentation.py b/src/adcp/types/domains/core/placement_presentation.py index 546e17dd2..39fb84e90 100644 --- a/src/adcp/types/domains/core/placement_presentation.py +++ b/src/adcp/types/domains/core/placement_presentation.py @@ -1,42 +1,142 @@ -"""Types declared by the AdCP ``core/placement_presentation`` schema. +# generated by datamodel-codegen: +# filename: core/placement_presentation.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.placement_presentation import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from adcp.types._scalar import ScalarStr +from pydantic import AnyUrl, ConfigDict, Field, field_validator + + +class Canvas(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + width: Annotated[SchemaInt, Field(ge=1, le=8192)] + height: Annotated[SchemaInt, Field(ge=1, le=8192)] + background_color: Annotated[str | None, Field(pattern='^#[0-9A-Fa-f]{6}$')] = '#ffffff' + + +class Fit(StrEnum): + contain = 'contain' + cover = 'cover' + stretch = 'stretch' + + +class CreativeSlot(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + x: Annotated[SchemaInt, Field(ge=0, le=8192)] + y: Annotated[SchemaInt, Field(ge=0, le=8192)] + width: Annotated[SchemaInt, Field(ge=1, le=8192)] + height: Annotated[SchemaInt, Field(ge=1, le=8192)] + fit: Annotated[ + Fit, + Field( + description='How the creative render is scaled into the slot. contain preserves the whole render, cover fills and crops, and stretch fills without preserving aspect ratio.' + ), + ] + clip: Annotated[ + Literal[True], + Field(description='Slot overflow is always clipped so composition is deterministic.'), + ] + + +class Rectangle(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + x: Annotated[SchemaInt, Field(ge=0, le=8192)] + y: Annotated[SchemaInt, Field(ge=0, le=8192)] + width: Annotated[SchemaInt, Field(ge=1, le=8192)] + height: Annotated[SchemaInt, Field(ge=1, le=8192)] + + +class ImageRef(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + uri: Annotated[ + AnyUrl, + Field( + description='Untrusted image asset. Fetch with public-address-only SSRF protection, no redirects, DNS pinning, and body/time limits; do not attach ambient credentials.' + ), + ] + digest: Annotated[ + str, + Field( + description='SHA-256 digest of the exact image bytes. Consumers MUST fail closed on mismatch.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] + + @field_validator('uri') + @classmethod + def _require_https_uri(cls, value: AnyUrl) -> AnyUrl: + if value.scheme != 'https': + raise ValueError('uri must use https') + return value + +class Layer(StrEnum): + behind_creative = 'behind_creative' + in_front_of_creative = 'in_front_of_creative' + + +class Color(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^#[0-9A-Fa-f]{6}$'} + + +class BoxDecoration(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['box'] = 'box' + layer: Layer + bounds: Rectangle + fill_color: Color + + +class TextDecoration(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['text'] = 'text' + layer: Layer + bounds: Rectangle + text: Annotated[str, Field(max_length=4096)] + text_color: Color + font_size: Annotated[SchemaInt, Field(ge=6, le=256)] + + +class ImageDecoration(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['image'] = 'image' + layer: Layer + bounds: Rectangle + image_ref: ImageRef + fit: Fit -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.placement_presentation import ( - BoxDecoration, - Canvas, - Color, - CreativeSlot, - Fit, - ImageDecoration, - ImageRef, - Layer, - PlacementPresentationDocument, - Rectangle, - TextDecoration, -) - -# Explicit exports -__all__ = [ - "BoxDecoration", - "Canvas", - "Color", - "CreativeSlot", - "Fit", - "ImageDecoration", - "ImageRef", - "Layer", - "PlacementPresentationDocument", - "Rectangle", - "TextDecoration", -] +class PlacementPresentationDocument(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + schema_version: Literal['1.0'] = '1.0' + canvas: Canvas + creative_slot: Annotated[ + CreativeSlot, + Field( + description='Rectangle into which the selected creative render is fitted and clipped without changing its manifest or renderer.' + ), + ] + decorations: Annotated[ + list[BoxDecoration | TextDecoration | ImageDecoration] | None, Field(max_length=100) + ] = None diff --git a/src/adcp/types/domains/core/placement_property_delivery_metrics.py b/src/adcp/types/domains/core/placement_property_delivery_metrics.py index 1a73d1bac..05f362f52 100644 --- a/src/adcp/types/domains/core/placement_property_delivery_metrics.py +++ b/src/adcp/types/domains/core/placement_property_delivery_metrics.py @@ -1,20 +1,54 @@ -"""Types declared by the AdCP ``core/placement_property_delivery_metrics`` schema. +# generated by datamodel-codegen: +# filename: core/placement_property_delivery_metrics.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.placement_property_delivery_metrics import +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import identifier as identifier_1 +from . import placement_identity as placement_identity_1 +from . import property_ref as property_ref_1 +from .delivery_metrics import DeliveryMetrics -from adcp.types.generated_poc.core.placement_property_delivery_metrics import ( - PlacementPropertyDeliveryMetrics, -) -# Explicit exports -__all__ = ["PlacementPropertyDeliveryMetrics"] +class PlacementPropertyDeliveryMetrics(DeliveryMetrics): + placement_id: Annotated[ + str, + Field( + description='Required flat compatibility ID. It MUST equal placement_identity.placement_id when placement_identity is present.' + ), + ] + placement_identity: Annotated[ + placement_identity_1.PlacementIdentity, + Field( + description='Required self-contained placement identity. Unlike the legacy-compatible by_placement dimension, the new by_placement_property dimension has no pre-3.2 row shape and always names the placement authority.' + ), + ] + placement_name: Annotated[ + str | None, Field(description='Current convenience name for the placement.') + ] = None + publisher_domain: Annotated[ + str, + Field( + description="Publisher or platform authority that namespaces the property identifier. This may differ from a publisher-catalog placement's publisher_domain.", + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] + identifier: Annotated[ + identifier_1.Identifier, + Field(description='Primary operational identifier of the property that delivered.'), + ] + property_ref: Annotated[ + property_ref_1.PropertyReference | None, + Field( + description="Canonical publisher-scoped property catalog identity when available. Its publisher_domain MUST equal the row's publisher_domain." + ), + ] = None + property_name: Annotated[ + str | None, Field(description='Current convenience name for the property.') + ] = None + impressions: Any + spend: Any diff --git a/src/adcp/types/domains/core/placement_ref.py b/src/adcp/types/domains/core/placement_ref.py index 731e16722..1a095e0dc 100644 --- a/src/adcp/types/domains/core/placement_ref.py +++ b/src/adcp/types/domains/core/placement_ref.py @@ -1,18 +1,29 @@ -"""Types declared by the AdCP ``core/placement_ref`` schema. +# generated by datamodel-codegen: +# filename: core/placement_ref.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.placement_ref import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -from adcp.types.generated_poc.core.placement_ref import PlacementReference -# Explicit exports -__all__ = ["PlacementReference"] +class PlacementReference(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + publisher_domain: Annotated[ + str | None, + Field( + description='Domain where the adagents.json declaring a publisher-catalog placement is hosted, or the inventory publisher associated with an inline placement. Omitted only for legacy single-publisher product-context references.', + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] = None + placement_id: Annotated[ + str, + Field( + description="Placement ID from the publisher's adagents.json placement catalog, or an inline seller-defined placement ID interpreted within the enclosing seller and product context." + ), + ] diff --git a/src/adcp/types/domains/core/placement_selection.py b/src/adcp/types/domains/core/placement_selection.py index af3e83aa6..911c8ad8e 100644 --- a/src/adcp/types/domains/core/placement_selection.py +++ b/src/adcp/types/domains/core/placement_selection.py @@ -1,22 +1,46 @@ -"""Types declared by the AdCP ``core/placement_selection`` schema. +# generated by datamodel-codegen: +# filename: core/placement_selection.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.placement_selection import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import ext as ext_1 +from . import placement_identity, placement_ref + + +class PlacementSelection2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + mode: Literal['default'] = 'default' + ext: ext_1.ExtensionObject | None = None + + +class PlacementSelection1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + mode: Literal['selected'] = 'selected' + placement_refs: Annotated[ + list[placement_ref.PlacementReference | placement_identity.PlacementIdentity], + Field( + description="Complete required placement set. A reference normally identifies a mode targetable placement. It MAY identify a mode included placement only when the set exactly equals the product's complete fixed included set, which is an inherent match rather than independent selection. Legacy publisher refs use {publisher_domain, placement_id}; authority-discriminated 3.2 identities use placement-identity.json so seller-inline inventory is selected by {seller_agent, placement_id}. An item that exactly matches placement-identity uses that canonical identity; otherwise a released-compatible item with publisher_domain and placement_id uses legacy product-context matching, and tolerated product metadata such as kind, name, or mode has no selection effect.", + min_length=1, + ), + ] + ext: ext_1.ExtensionObject | None = None -from adcp.types.generated_poc.core.placement_selection import ( - PlacementSelection, - PlacementSelection1, - PlacementSelection2, -) -# Explicit exports -__all__ = ["PlacementSelection", "PlacementSelection1", "PlacementSelection2"] +PlacementSelection = Annotated[ + PlacementSelection1 | PlacementSelection2, + Field( + description="Complete purchased placement set within a product. This is inventory targeting, not creative routing: creative_assignments[].placement_refs route a creative only within this purchased set. Requests use mode selected to name the complete required set or mode default to accept the product/seller default. Across discovery, create, and update, a selected set is satisfied inherently when it exactly equals a fixed product's complete included placement set; this exact restatement does not require overlay_support.placement_selection, while partial selection does. Included placements are never independently selectable. Products with any fixed mode:included placement cannot advertise selectable placement overlay support because the fixed placement would make a partial buyer-selected set incomplete; sellers expose separately selectable inventory when needed. Successful package readback MUST echo the committed selected set. Sellers reject unknown, duplicate, cross-publisher, incompatible, or partially applicable refs rather than silently changing the selection.", + discriminator='mode', + title='Placement Selection', + ), +] diff --git a/src/adcp/types/domains/core/planned_delivery.py b/src/adcp/types/domains/core/planned_delivery.py index d36c1bcfc..d30ef3fb2 100644 --- a/src/adcp/types/domains/core/planned_delivery.py +++ b/src/adcp/types/domains/core/planned_delivery.py @@ -1,18 +1,131 @@ -"""Types declared by the AdCP ``core/planned_delivery`` schema. +# generated by datamodel-codegen: +# filename: core/planned_delivery.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.planned_delivery import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import channels as channels_1 +from ..enums import pacing as pacing_1 +from . import audience_selector, bidding_policy +from . import budget_allocation as budget_allocation_1 +from . import ext as ext_1 +from . import frequency_cap as frequency_cap_1 + + +class Geo(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + countries: Annotated[ + list[str] | None, + Field(description='ISO 3166-1 alpha-2 country codes where ads will deliver.'), + ] = None + regions: Annotated[ + list[str] | None, Field(description='ISO 3166-2 subdivision codes where ads will deliver.') + ] = None -from adcp.types.generated_poc.core.planned_delivery import Geo, PlannedDelivery -# Explicit exports -__all__ = ["Geo", "PlannedDelivery"] +class PlannedDelivery(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + media_buy_id: Annotated[ + str | None, + Field( + description='Seller-assigned media buy identifier. Optional on a purchase-phase prepare/check because the service may not assign the identifier until commit; required on modification and delivery lifecycle checks.', + min_length=1, + ), + ] = None + proposal_id: Annotated[ + str | None, + Field( + description='Proposal snapshot being executed or currently governing the MediaBuy.', + max_length=255, + min_length=1, + ), + ] = None + proposal_terms_digest: Annotated[ + str | None, + Field( + description='Digest of the proposal commercial_terms. The governance agent compares it to the digest bound during the intent check.', + pattern='^sha256:[A-Za-z0-9_-]{43}$', + ), + ] = None + geo: Annotated[Geo | None, Field(description='Geographic targeting the seller will apply.')] = ( + None + ) + channels: Annotated[ + list[channels_1.MediaChannel] | None, + Field(description='Channels the seller will deliver on.'), + ] = None + start_time: Annotated[ + AwareDatetime | None, Field(description='Actual flight start the seller will use.') + ] = None + end_time: Annotated[ + AwareDatetime | None, Field(description='Actual flight end the seller will use.') + ] = None + frequency_cap: Annotated[ + frequency_cap_1.FrequencyCap | None, + Field(description='Frequency cap the seller will apply.'), + ] = None + audience_summary: Annotated[ + str | None, + Field(description='Human-readable summary of the audience the seller will target.'), + ] = None + audience_targeting: Annotated[ + list[audience_selector.AudienceSelector] | None, + Field( + description='Structured audience targeting the seller will activate. Each entry is either a signal reference or a descriptive criterion. When present, governance agents MUST use this for bias/fairness validation and SHOULD ignore audience_summary for validation purposes. The audience_summary field is a human-readable rendering of this array, not an independent declaration.', + min_length=1, + ), + ] = None + total_budget: Annotated[ + StrictFloat | None, + Field(description='Total budget the seller will deliver against.', ge=0.0), + ] = None + daily_budget_cap: Annotated[ + StrictFloat | None, + Field( + description='Hard aggregate daily spend ceiling the seller will enforce. Governance checks compare it with the authorized execution controls; it does not allocate spend to packages.', + ge=0.0, + ), + ] = None + budget_cap_timezone: Annotated[ + str | None, + Field( + description='IANA timezone defining the calendar-day boundary for every daily cap on the planned media buy.' + ), + ] = None + currency: Annotated[ + str | None, + Field( + description='ISO 4217 currency code for the budget. Governance execution checks require it whenever total_budget is present and require it to match the intent-authorized currency.', + pattern='^[A-Z]{3}$', + ), + ] = None + budget_allocation: Annotated[ + budget_allocation_1.BudgetAllocation | None, + Field( + description='Seller-accepted cross-package allocation authority and goals. Presence with seller_optimized mode means automatic within-buy reallocations are part of the committed delivery, not separate modification actions.' + ), + ] = None + pacing: Annotated[ + pacing_1.Pacing | None, + Field(description='Aggregate pacing strategy the seller will apply to total_budget.'), + ] = None + bidding: Annotated[ + bidding_policy.BiddingPolicy | None, + Field( + description='Seller-interpreted media-buy bidding policy used for governance and delivery transparency. Goal-bound controls follow budget-allocation scope semantics and monetary fields use the planned delivery currency. Package-authored overrides, including explicit automatic overrides, remain on packages rather than being copied into this aggregate field.' + ), + ] = None + enforced_policies: Annotated[ + list[str] | None, + Field(description='Registry policy IDs the seller will enforce for this delivery.'), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/platform_extension_ref.py b/src/adcp/types/domains/core/platform_extension_ref.py index a1f76c3d6..de8012c52 100644 --- a/src/adcp/types/domains/core/platform_extension_ref.py +++ b/src/adcp/types/domains/core/platform_extension_ref.py @@ -1,18 +1,29 @@ -"""Types declared by the AdCP ``core/platform_extension_ref`` schema. +# generated by datamodel-codegen: +# filename: core/platform_extension_ref.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.platform_extension_ref import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field -from adcp.types.generated_poc.core.platform_extension_ref import PlatformExtensionReference -# Explicit exports -__all__ = ["PlatformExtensionReference"] +class PlatformExtensionReference(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + uri: Annotated[ + AnyUrl, + Field( + description="HTTPS URL identifying the extension. `https://` is mandatory — `http://`, `file://`, `data:`, and other schemes are rejected at the schema layer (defense-in-depth on top of the fetch-contract normative rules). The URI base is the owning agent's URL; the path identifies the extension within that agent. Example: 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel'. The full fetch contract — SSRF allowlist, response-size cap, $ref sandbox, schema-compile bounds — is documented on `product-format-declaration.json#format_schema` and applies to ALL fetches of this reference shape regardless of whether the field is named `format_schema` (load-bearing for validation) or `platform_extensions` (informational); the *transport* rules are identical, only the *consumption* semantics differ." + ), + ] + digest: Annotated[ + str, + Field( + description='SHA-256 content digest of the extension definition (sha256:). Used to detect drift — if the agent revises the extension, the digest changes and cached definitions become invalid.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] diff --git a/src/adcp/types/domains/core/positive_postal_area_support.py b/src/adcp/types/domains/core/positive_postal_area_support.py index ecc4171ea..89e553e00 100644 --- a/src/adcp/types/domains/core/positive_postal_area_support.py +++ b/src/adcp/types/domains/core/positive_postal_area_support.py @@ -1,18 +1,25 @@ -"""Types declared by the AdCP ``core/positive_postal_area_support`` schema. +# generated by datamodel-codegen: +# filename: core/positive_postal_area_support.json +# timestamp: 2026-10-03T14:21:15+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.positive_postal_area_support import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from .postal_area_support import PostalAreaSupport -from adcp.types.generated_poc.core.positive_postal_area_support import PositivePostalAreaSupport -# Explicit exports -__all__ = ["PositivePostalAreaSupport"] +class PositivePostalAreaSupport(PostalAreaSupport): + us_zip: Annotated[Literal[True] | None, Field(deprecated=True)] = None + us_zip_plus_four: Annotated[Literal[True] | None, Field(deprecated=True)] = None + gb_outward: Annotated[Literal[True] | None, Field(deprecated=True)] = None + gb_full: Annotated[Literal[True] | None, Field(deprecated=True)] = None + ca_fsa: Annotated[Literal[True] | None, Field(deprecated=True)] = None + ca_full: Annotated[Literal[True] | None, Field(deprecated=True)] = None + de_plz: Annotated[Literal[True] | None, Field(deprecated=True)] = None + fr_code_postal: Annotated[Literal[True] | None, Field(deprecated=True)] = None + au_postcode: Annotated[Literal[True] | None, Field(deprecated=True)] = None + ch_plz: Annotated[Literal[True] | None, Field(deprecated=True)] = None + at_plz: Annotated[Literal[True] | None, Field(deprecated=True)] = None diff --git a/src/adcp/types/domains/core/postal_area.py b/src/adcp/types/domains/core/postal_area.py index cbb38ca98..b7d6aa29e 100644 --- a/src/adcp/types/domains/core/postal_area.py +++ b/src/adcp/types/domains/core/postal_area.py @@ -1,80 +1,451 @@ -"""Types declared by the AdCP ``core/postal_area`` schema. +# generated by datamodel-codegen: +# filename: core/postal_area.json +# timestamp: 2026-10-03T00:35:15+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.postal_area import +from adcp.types._str_enum import StrEnum +from typing import Any, Annotated, Literal + +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, RootModel, model_validator + +from ..enums import legacy_postal_system, postal_system + + +class System(StrEnum): + postal_code = 'postal_code' + zip = 'zip' + zip_plus_four = 'zip_plus_four' + outward = 'outward' + full = 'full' + fsa = 'fsa' + plz = 'plz' + code_postal = 'code_postal' + postcode = 'postcode' + cep = 'cep' + pin = 'pin' + custom = 'custom' + us_zip = 'us_zip' + us_zip_plus_four = 'us_zip_plus_four' + gb_outward = 'gb_outward' + gb_full = 'gb_full' + ca_fsa = 'ca_fsa' + ca_full = 'ca_full' + de_plz = 'de_plz' + fr_code_postal = 'fr_code_postal' + au_postcode = 'au_postcode' + ch_plz = 'ch_plz' + at_plz = 'at_plz' + zip_1 = 'zip' + zip_plus_four_1 = 'zip_plus_four' + + +class PostalArea11(AdCPBaseModel): + country: Annotated[ + Literal['US'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + ] = 'US' + system: Annotated[ + System, Field(description='Country-local postal code system.', title='Postal Code System') + ] + + +class System1(StrEnum): + postal_code = 'postal_code' + zip = 'zip' + zip_plus_four = 'zip_plus_four' + outward = 'outward' + full = 'full' + fsa = 'fsa' + plz = 'plz' + code_postal = 'code_postal' + postcode = 'postcode' + cep = 'cep' + pin = 'pin' + custom = 'custom' + us_zip = 'us_zip' + us_zip_plus_four = 'us_zip_plus_four' + gb_outward = 'gb_outward' + gb_full = 'gb_full' + ca_fsa = 'ca_fsa' + ca_full = 'ca_full' + de_plz = 'de_plz' + fr_code_postal = 'fr_code_postal' + au_postcode = 'au_postcode' + ch_plz = 'ch_plz' + at_plz = 'at_plz' + outward_1 = 'outward' + full_1 = 'full' + + +class PostalArea12(AdCPBaseModel): + country: Annotated[ + Literal['GB'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + ] = 'GB' + system: Annotated[ + System1, Field(description='Country-local postal code system.', title='Postal Code System') + ] + + +class System2(StrEnum): + postal_code = 'postal_code' + zip = 'zip' + zip_plus_four = 'zip_plus_four' + outward = 'outward' + full = 'full' + fsa = 'fsa' + plz = 'plz' + code_postal = 'code_postal' + postcode = 'postcode' + cep = 'cep' + pin = 'pin' + custom = 'custom' + us_zip = 'us_zip' + us_zip_plus_four = 'us_zip_plus_four' + gb_outward = 'gb_outward' + gb_full = 'gb_full' + ca_fsa = 'ca_fsa' + ca_full = 'ca_full' + de_plz = 'de_plz' + fr_code_postal = 'fr_code_postal' + au_postcode = 'au_postcode' + ch_plz = 'ch_plz' + at_plz = 'at_plz' + fsa_1 = 'fsa' + full_1 = 'full' + + +class PostalArea13(AdCPBaseModel): + country: Annotated[ + Literal['CA'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + ] = 'CA' + system: Annotated[ + System2, Field(description='Country-local postal code system.', title='Postal Code System') + ] + + +class Country(StrEnum): + DE = 'DE' + CH = 'CH' + AT = 'AT' + + +class System3(StrEnum): + postal_code = 'postal_code' + zip = 'zip' + zip_plus_four = 'zip_plus_four' + outward = 'outward' + full = 'full' + fsa = 'fsa' + plz = 'plz' + code_postal = 'code_postal' + postcode = 'postcode' + cep = 'cep' + pin = 'pin' + custom = 'custom' + us_zip = 'us_zip' + us_zip_plus_four = 'us_zip_plus_four' + gb_outward = 'gb_outward' + gb_full = 'gb_full' + ca_fsa = 'ca_fsa' + ca_full = 'ca_full' + de_plz = 'de_plz' + fr_code_postal = 'fr_code_postal' + au_postcode = 'au_postcode' + ch_plz = 'ch_plz' + at_plz = 'at_plz' + + +class PostalArea14(AdCPBaseModel): + country: Annotated[Country, Field(description='ISO 3166-1 alpha-2 country code.')] + system: Annotated[ + Literal['plz'], + Field(description='Country-local postal code system.', title='Postal Code System'), + ] = 'plz' + + +class PostalArea15(AdCPBaseModel): + country: Annotated[ + Literal['FR'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + ] = 'FR' + system: Annotated[ + Literal['code_postal'], + Field(description='Country-local postal code system.', title='Postal Code System'), + ] = 'code_postal' + + +class PostalArea16(AdCPBaseModel): + country: Annotated[ + Literal['AU'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + ] = 'AU' + system: Annotated[ + Literal['postcode'], + Field(description='Country-local postal code system.', title='Postal Code System'), + ] = 'postcode' + + +class PostalArea17(AdCPBaseModel): + country: Annotated[ + Literal['BR'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + ] = 'BR' + system: Annotated[ + Literal['cep'], + Field(description='Country-local postal code system.', title='Postal Code System'), + ] = 'cep' + + +class PostalArea18(AdCPBaseModel): + country: Annotated[ + Literal['IN'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + ] = 'IN' + system: Annotated[ + Literal['pin'], + Field(description='Country-local postal code system.', title='Postal Code System'), + ] = 'pin' + + +class PostalArea19(AdCPBaseModel): + country: Annotated[ + Literal['ZA'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + ] = 'ZA' + system: Annotated[ + Literal['postal_code'], + Field(description='Country-local postal code system.', title='Postal Code System'), + ] = 'postal_code' + + +class System9(StrEnum): + postal_code = 'postal_code' + zip = 'zip' + zip_plus_four = 'zip_plus_four' + outward = 'outward' + full = 'full' + fsa = 'fsa' + plz = 'plz' + code_postal = 'code_postal' + postcode = 'postcode' + cep = 'cep' + pin = 'pin' + custom = 'custom' + us_zip = 'us_zip' + us_zip_plus_four = 'us_zip_plus_four' + gb_outward = 'gb_outward' + gb_full = 'gb_full' + ca_fsa = 'ca_fsa' + ca_full = 'ca_full' + de_plz = 'de_plz' + fr_code_postal = 'fr_code_postal' + au_postcode = 'au_postcode' + ch_plz = 'ch_plz' + at_plz = 'at_plz' + postal_code_1 = 'postal_code' + custom_1 = 'custom' + + +class PostalArea110(AdCPBaseModel): + country: Annotated[ + str, Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + ] + system: Annotated[ + System9, Field(description='Country-local postal code system.', title='Postal Code System') + ] + + +class PostalArea2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + system: Annotated[ + legacy_postal_system.CountryFusedPostalCodeSystem, + Field( + deprecated=True, + description="Deprecated country-fused postal code system (e.g., 'us_zip', 'gb_outward'). Prefer country + postal-system.", + ), + ] + values: Annotated[ + list[str], Field(description='Postal codes within the legacy system.', min_length=1) + ] + + +class PostalArea111(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + country: Annotated[ + str, + Field( + description='ISO 3166-1 alpha-2 country code for the postal values.', + pattern='^[A-Z]{2}$', + ), + ] + system: Annotated[ + postal_system.PostalCodeSystem, + Field( + description="Country-local postal code system (e.g., 'zip', 'outward', 'plz', 'postal_code')." + ), + ] + values: Annotated[ + list[str], Field(description='Postal codes within the country and system.', min_length=1) + ] + + +class PostalArea112(PostalArea11): + model_config = ConfigDict( + extra='forbid', + ) + values: Annotated[ + list[str], Field(description='Postal codes within the country and system.', min_length=1) + ] + + +class PostalArea113(PostalArea12): + model_config = ConfigDict( + extra='forbid', + ) + values: Annotated[ + list[str], Field(description='Postal codes within the country and system.', min_length=1) + ] + + +class PostalArea114(PostalArea13): + model_config = ConfigDict( + extra='forbid', + ) + values: Annotated[ + list[str], Field(description='Postal codes within the country and system.', min_length=1) + ] + + +class PostalArea115(PostalArea14): + model_config = ConfigDict( + extra='forbid', + ) + values: Annotated[ + list[str], Field(description='Postal codes within the country and system.', min_length=1) + ] + + +class PostalArea116(PostalArea15): + model_config = ConfigDict( + extra='forbid', + ) + values: Annotated[ + list[str], Field(description='Postal codes within the country and system.', min_length=1) + ] + + +class PostalArea117(PostalArea16): + model_config = ConfigDict( + extra='forbid', + ) + values: Annotated[ + list[str], Field(description='Postal codes within the country and system.', min_length=1) + ] + + +class PostalArea118(PostalArea17): + model_config = ConfigDict( + extra='forbid', + ) + values: Annotated[ + list[str], Field(description='Postal codes within the country and system.', min_length=1) + ] + + +class PostalArea119(PostalArea18): + model_config = ConfigDict( + extra='forbid', + ) + values: Annotated[ + list[str], Field(description='Postal codes within the country and system.', min_length=1) + ] + + +class PostalArea120(PostalArea19): + model_config = ConfigDict( + extra='forbid', + ) + values: Annotated[ + list[str], Field(description='Postal codes within the country and system.', min_length=1) + ] + + +class PostalArea121(PostalArea110): + model_config = ConfigDict( + extra='forbid', + ) + values: Annotated[ + list[str], Field(description='Postal codes within the country and system.', min_length=1) + ] + + +class PostalArea1( + RootModel[ + PostalArea112 + | PostalArea113 + | PostalArea114 + | PostalArea115 + | PostalArea116 + | PostalArea117 + | PostalArea118 + | PostalArea119 + | PostalArea120 + | PostalArea121 + ] +): + root: Annotated[ + PostalArea112 + | PostalArea113 + | PostalArea114 + | PostalArea115 + | PostalArea116 + | PostalArea117 + | PostalArea118 + | PostalArea119 + | PostalArea120 + | PostalArea121, + Field( + description='Postal area values. Prefer the native country + postal system form. Deprecated legacy country-fused postal-system tokens remain accepted for compatibility.', + title='PostalArea', + ), + ] + def __getattr__(self, name: str) -> Any: + """Proxy attribute access to the wrapped type.""" + if name.startswith('_'): + raise AttributeError(name) + return getattr(self.root, name) -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.postal_area import ( - Country, - PostalArea, - PostalArea1, - PostalArea11, - PostalArea110, - PostalArea111, - PostalArea112, - PostalArea113, - PostalArea114, - PostalArea115, - PostalArea116, - PostalArea117, - PostalArea118, - PostalArea119, - PostalArea12, - PostalArea120, - PostalArea121, - PostalArea13, - PostalArea14, - PostalArea15, - PostalArea16, - PostalArea17, - PostalArea18, - PostalArea19, - PostalArea2, - System, - System1, - System2, - System3, - System9, -) - -# Explicit exports -__all__ = [ - "Country", - "PostalArea", - "PostalArea1", - "PostalArea11", - "PostalArea110", - "PostalArea111", - "PostalArea112", - "PostalArea113", - "PostalArea114", - "PostalArea115", - "PostalArea116", - "PostalArea117", - "PostalArea118", - "PostalArea119", - "PostalArea12", - "PostalArea120", - "PostalArea121", - "PostalArea13", - "PostalArea14", - "PostalArea15", - "PostalArea16", - "PostalArea17", - "PostalArea18", - "PostalArea19", - "PostalArea2", - "System", - "System1", - "System2", - "System3", - "System9", -] +class PostalArea(RootModel[PostalArea2 | PostalArea1]): + root: Annotated[ + PostalArea2 | PostalArea1, + Field( + description='Postal area values. Prefer the native country + postal system form. Deprecated legacy country-fused postal-system tokens remain accepted for compatibility.', + title='Postal Area', + ), + ] + @model_validator(mode='before') + @classmethod + def _validate_country_system_pairing(cls, value: Any) -> Any: + raw = value.get('root', value) if isinstance(value, dict) else value + country = raw.get('country') if isinstance(raw, dict) else getattr(raw, 'country', None) + if not isinstance(country, str): + return value + system = raw.get('system') if isinstance(raw, dict) else getattr(raw, 'system', None) + system = getattr(system, 'value', system) + if not isinstance(system, str): + return value + allowed_by_country = {'US': ('zip', 'zip_plus_four'), 'GB': ('outward', 'full'), 'CA': ('fsa', 'full'), 'DE': ('plz',), 'CH': ('plz',), 'AT': ('plz',), 'FR': ('code_postal',), 'AU': ('postcode',), 'BR': ('cep',), 'IN': ('pin',), 'ZA': ('postal_code',)} + allowed = allowed_by_country.get(country, ('postal_code', 'custom')) + if system not in allowed: + raise ValueError( + f"postal system {system!r} is not valid for country {country!r}; " + f"expected one of {list(allowed)!r}" + ) + return value + def __getattr__(self, name: str) -> Any: + """Proxy attribute access to the wrapped type.""" + if name.startswith('_'): + raise AttributeError(name) + return getattr(self.root, name) diff --git a/src/adcp/types/domains/core/postal_area_support.py b/src/adcp/types/domains/core/postal_area_support.py index cd991693f..2dc9e2910 100644 --- a/src/adcp/types/domains/core/postal_area_support.py +++ b/src/adcp/types/domains/core/postal_area_support.py @@ -1,24 +1,62 @@ -"""Types declared by the AdCP ``core/postal_area_support`` schema. +# generated by datamodel-codegen: +# filename: core/postal_area_support.json +# timestamp: 2026-10-03T15:09:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.postal_area_support import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Dict, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.postal_area_support import ( - CAEnum, - GBEnum, - ME, - PostalAreaSupport, - PostalAreaSupportAdditionalPropertyEnum, -) +class ME(StrEnum): + zip = 'zip' + zip_plus_four = 'zip_plus_four' + + +class GBEnum(StrEnum): + outward = 'outward' + full = 'full' + + +class CAEnum(StrEnum): + fsa = 'fsa' + full = 'full' + + +class PostalAreaSupportAdditionalPropertyEnum(StrEnum): + postal_code = 'postal_code' + custom = 'custom' + -# Explicit exports -__all__ = ["CAEnum", "GBEnum", "ME", "PostalAreaSupport", "PostalAreaSupportAdditionalPropertyEnum"] +class PostalAreaSupport(AdCPBaseModel): + __pydantic_extra__: Dict[ + str, list[PostalAreaSupportAdditionalPropertyEnum] + ] + model_config = ConfigDict( + extra='allow', + ) + US: Annotated[list[ME] | None, Field(min_length=1)] = None + GB: Annotated[list[GBEnum] | None, Field(min_length=1)] = None + CA: Annotated[list[CAEnum] | None, Field(min_length=1)] = None + DE: Annotated[list[Literal['plz']] | None, Field(min_length=1)] = None + CH: Annotated[list[Literal['plz']] | None, Field(min_length=1)] = None + AT: Annotated[list[Literal['plz']] | None, Field(min_length=1)] = None + FR: Annotated[list[Literal['code_postal']] | None, Field(min_length=1)] = None + AU: Annotated[list[Literal['postcode']] | None, Field(min_length=1)] = None + BR: Annotated[list[Literal['cep']] | None, Field(min_length=1)] = None + IN: Annotated[list[Literal['pin']] | None, Field(min_length=1)] = None + ZA: Annotated[list[Literal['postal_code']] | None, Field(min_length=1)] = None + us_zip: Annotated[StrictBool | None, Field(deprecated=True)] = None + us_zip_plus_four: Annotated[StrictBool | None, Field(deprecated=True)] = None + gb_outward: Annotated[StrictBool | None, Field(deprecated=True)] = None + gb_full: Annotated[StrictBool | None, Field(deprecated=True)] = None + ca_fsa: Annotated[StrictBool | None, Field(deprecated=True)] = None + ca_full: Annotated[StrictBool | None, Field(deprecated=True)] = None + de_plz: Annotated[StrictBool | None, Field(deprecated=True)] = None + fr_code_postal: Annotated[StrictBool | None, Field(deprecated=True)] = None + au_postcode: Annotated[StrictBool | None, Field(deprecated=True)] = None + ch_plz: Annotated[StrictBool | None, Field(deprecated=True)] = None + at_plz: Annotated[StrictBool | None, Field(deprecated=True)] = None diff --git a/src/adcp/types/domains/core/postal_country_system.py b/src/adcp/types/domains/core/postal_country_system.py index b53e73ffd..289c69f07 100644 --- a/src/adcp/types/domains/core/postal_country_system.py +++ b/src/adcp/types/domains/core/postal_country_system.py @@ -1,54 +1,267 @@ -"""Types declared by the AdCP ``core/postal_country_system`` schema. +# generated by datamodel-codegen: +# filename: core/postal_country_system.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.postal_country_system import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import Field + + +class System(StrEnum): + postal_code = 'postal_code' + zip = 'zip' + zip_plus_four = 'zip_plus_four' + outward = 'outward' + full = 'full' + fsa = 'fsa' + plz = 'plz' + code_postal = 'code_postal' + postcode = 'postcode' + cep = 'cep' + pin = 'pin' + custom = 'custom' + us_zip = 'us_zip' + us_zip_plus_four = 'us_zip_plus_four' + gb_outward = 'gb_outward' + gb_full = 'gb_full' + ca_fsa = 'ca_fsa' + ca_full = 'ca_full' + de_plz = 'de_plz' + fr_code_postal = 'fr_code_postal' + au_postcode = 'au_postcode' + ch_plz = 'ch_plz' + at_plz = 'at_plz' + zip_1 = 'zip' + zip_plus_four_1 = 'zip_plus_four' + + +class PostalCountrySystem1(AdCPBaseModel): + country: Annotated[ + Literal['US'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + ] = 'US' + system: Annotated[ + System, Field(description='Country-local postal code system.', title='Postal Code System') + ] + + +class System11(StrEnum): + postal_code = 'postal_code' + zip = 'zip' + zip_plus_four = 'zip_plus_four' + outward = 'outward' + full = 'full' + fsa = 'fsa' + plz = 'plz' + code_postal = 'code_postal' + postcode = 'postcode' + cep = 'cep' + pin = 'pin' + custom = 'custom' + us_zip = 'us_zip' + us_zip_plus_four = 'us_zip_plus_four' + gb_outward = 'gb_outward' + gb_full = 'gb_full' + ca_fsa = 'ca_fsa' + ca_full = 'ca_full' + de_plz = 'de_plz' + fr_code_postal = 'fr_code_postal' + au_postcode = 'au_postcode' + ch_plz = 'ch_plz' + at_plz = 'at_plz' + outward_1 = 'outward' + full_1 = 'full' + + +class PostalCountrySystem2(AdCPBaseModel): + country: Annotated[ + Literal['GB'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + ] = 'GB' + system: Annotated[ + System11, Field(description='Country-local postal code system.', title='Postal Code System') + ] + + +class System12(StrEnum): + postal_code = 'postal_code' + zip = 'zip' + zip_plus_four = 'zip_plus_four' + outward = 'outward' + full = 'full' + fsa = 'fsa' + plz = 'plz' + code_postal = 'code_postal' + postcode = 'postcode' + cep = 'cep' + pin = 'pin' + custom = 'custom' + us_zip = 'us_zip' + us_zip_plus_four = 'us_zip_plus_four' + gb_outward = 'gb_outward' + gb_full = 'gb_full' + ca_fsa = 'ca_fsa' + ca_full = 'ca_full' + de_plz = 'de_plz' + fr_code_postal = 'fr_code_postal' + au_postcode = 'au_postcode' + ch_plz = 'ch_plz' + at_plz = 'at_plz' + fsa_1 = 'fsa' + full_1 = 'full' + + +class PostalCountrySystem3(AdCPBaseModel): + country: Annotated[ + Literal['CA'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + ] = 'CA' + system: Annotated[ + System12, Field(description='Country-local postal code system.', title='Postal Code System') + ] + + +class Country(StrEnum): + DE = 'DE' + CH = 'CH' + AT = 'AT' + + +class System13(StrEnum): + postal_code = 'postal_code' + zip = 'zip' + zip_plus_four = 'zip_plus_four' + outward = 'outward' + full = 'full' + fsa = 'fsa' + plz = 'plz' + code_postal = 'code_postal' + postcode = 'postcode' + cep = 'cep' + pin = 'pin' + custom = 'custom' + us_zip = 'us_zip' + us_zip_plus_four = 'us_zip_plus_four' + gb_outward = 'gb_outward' + gb_full = 'gb_full' + ca_fsa = 'ca_fsa' + ca_full = 'ca_full' + de_plz = 'de_plz' + fr_code_postal = 'fr_code_postal' + au_postcode = 'au_postcode' + ch_plz = 'ch_plz' + at_plz = 'at_plz' + + +class PostalCountrySystem4(AdCPBaseModel): + country: Annotated[Country, Field(description='ISO 3166-1 alpha-2 country code.')] + system: Annotated[ + Literal['plz'], + Field(description='Country-local postal code system.', title='Postal Code System'), + ] = 'plz' + + +class PostalCountrySystem5(AdCPBaseModel): + country: Annotated[ + Literal['FR'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + ] = 'FR' + system: Annotated[ + Literal['code_postal'], + Field(description='Country-local postal code system.', title='Postal Code System'), + ] = 'code_postal' + + +class PostalCountrySystem6(AdCPBaseModel): + country: Annotated[ + Literal['AU'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + ] = 'AU' + system: Annotated[ + Literal['postcode'], + Field(description='Country-local postal code system.', title='Postal Code System'), + ] = 'postcode' + + +class PostalCountrySystem7(AdCPBaseModel): + country: Annotated[ + Literal['BR'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + ] = 'BR' + system: Annotated[ + Literal['cep'], + Field(description='Country-local postal code system.', title='Postal Code System'), + ] = 'cep' + + +class PostalCountrySystem8(AdCPBaseModel): + country: Annotated[ + Literal['IN'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + ] = 'IN' + system: Annotated[ + Literal['pin'], + Field(description='Country-local postal code system.', title='Postal Code System'), + ] = 'pin' + + +class PostalCountrySystem9(AdCPBaseModel): + country: Annotated[ + Literal['ZA'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + ] = 'ZA' + system: Annotated[ + Literal['postal_code'], + Field(description='Country-local postal code system.', title='Postal Code System'), + ] = 'postal_code' + + +class System19(StrEnum): + postal_code = 'postal_code' + zip = 'zip' + zip_plus_four = 'zip_plus_four' + outward = 'outward' + full = 'full' + fsa = 'fsa' + plz = 'plz' + code_postal = 'code_postal' + postcode = 'postcode' + cep = 'cep' + pin = 'pin' + custom = 'custom' + us_zip = 'us_zip' + us_zip_plus_four = 'us_zip_plus_four' + gb_outward = 'gb_outward' + gb_full = 'gb_full' + ca_fsa = 'ca_fsa' + ca_full = 'ca_full' + de_plz = 'de_plz' + fr_code_postal = 'fr_code_postal' + au_postcode = 'au_postcode' + ch_plz = 'ch_plz' + at_plz = 'at_plz' + postal_code_1 = 'postal_code' + custom_1 = 'custom' + + +class PostalCountrySystem10(AdCPBaseModel): + country: Annotated[ + str, Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + ] + system: Annotated[ + System19, Field(description='Country-local postal code system.', title='Postal Code System') + ] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.postal_country_system import ( - Country, - PostalCountrySystem, - PostalCountrySystem1, - PostalCountrySystem10, - PostalCountrySystem2, - PostalCountrySystem3, - PostalCountrySystem4, - PostalCountrySystem5, - PostalCountrySystem6, - PostalCountrySystem7, - PostalCountrySystem8, - PostalCountrySystem9, - System, - System11, - System12, - System13, - System19, -) - -# Explicit exports -__all__ = [ - "Country", - "PostalCountrySystem", - "PostalCountrySystem1", - "PostalCountrySystem10", - "PostalCountrySystem2", - "PostalCountrySystem3", - "PostalCountrySystem4", - "PostalCountrySystem5", - "PostalCountrySystem6", - "PostalCountrySystem7", - "PostalCountrySystem8", - "PostalCountrySystem9", - "System", - "System11", - "System12", - "System13", - "System19", +PostalCountrySystem = Annotated[ + PostalCountrySystem1 + | PostalCountrySystem2 + | PostalCountrySystem3 + | PostalCountrySystem4 + | PostalCountrySystem5 + | PostalCountrySystem6 + | PostalCountrySystem7 + | PostalCountrySystem8 + | PostalCountrySystem9 + | PostalCountrySystem10, + Field( + description='Valid country-local postal system pairing. Registered countries only accept their registered local systems; countries without a registered local system use postal_code or custom.', + title='Postal Country System', + ), ] diff --git a/src/adcp/types/domains/core/presentation_ref.py b/src/adcp/types/domains/core/presentation_ref.py index 0f1cdb3c2..af4450597 100644 --- a/src/adcp/types/domains/core/presentation_ref.py +++ b/src/adcp/types/domains/core/presentation_ref.py @@ -1,18 +1,46 @@ -"""Types declared by the AdCP ``core/presentation_ref`` schema. +# generated by datamodel-codegen: +# filename: core/presentation_ref.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.presentation_ref import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field, field_validator -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.presentation_ref import PlacementPresentationReference +class PlacementPresentationReference(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + uri: Annotated[ + AnyUrl, + Field( + description='Publisher-controlled HTTPS URL for the presentation metadata. Consumers MUST apply the same SSRF, redirect, response-size, timeout, and DNS-rebinding protections used for format_schema fetches.' + ), + ] + digest: Annotated[ + str, + Field( + description='SHA-256 content digest. Consumers cache by uri@digest and MUST fail closed on a digest mismatch.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] + media_type: Annotated[ + Literal['application/vnd.adcp.placement-presentation+json'], + Field(description='Media type of the referenced declarative presentation document.'), + ] = 'application/vnd.adcp.placement-presentation+json' + schema_version: Annotated[ + Literal['1.0'], + Field( + description='Version of /schemas/core/placement-presentation.json used to validate and compose the referenced document.' + ), + ] = '1.0' -# Explicit exports -__all__ = ["PlacementPresentationReference"] + @field_validator('uri') + @classmethod + def _require_https_uri(cls, value: AnyUrl) -> AnyUrl: + if value.scheme != 'https': + raise ValueError('uri must use https') + return value diff --git a/src/adcp/types/domains/core/preview_provider.py b/src/adcp/types/domains/core/preview_provider.py index 7621bc7af..db2e2065a 100644 --- a/src/adcp/types/domains/core/preview_provider.py +++ b/src/adcp/types/domains/core/preview_provider.py @@ -1,18 +1,62 @@ -"""Types declared by the AdCP ``core/preview_provider`` schema. +# generated by datamodel-codegen: +# filename: core/preview_provider.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.preview_provider import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field, StrictBool, field_validator + + +class Route(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + format_option_id: Annotated[ + str, + Field( + description='Format option in this adagents.json placement for which the delegation applies. It MUST resolve through the same-file top-level formats[] catalog or an inline placement format declaration.', + min_length=1, + ), + ] + capability_id: Annotated[ + str, + Field( + description="Agent-local preview capability advertised by the delegated provider. The provider's canonical format declaration MUST satisfy the resolved placement format option.", + pattern='^[a-zA-Z0-9_-]+$', + ), + ] + covers_placement_presentation: Annotated[ + StrictBool | None, + Field( + description="True only when the publisher delegates both creative rendering and the complete placement-specific frame to this route. When false or omitted, consumers compose any presentation_ref around the provider's creative render." + ), + ] = False -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.preview_provider import PublisherDesignatedPreviewProvider, Route +class PublisherDesignatedPreviewProvider(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + agent_url: Annotated[ + AnyUrl, + Field( + description='HTTPS URL of the delegated creative-agent endpoint. Buyers call get_adcp_capabilities and preview_creative on this endpoint. They MUST allow only public IPs, pin DNS resolution through connection, refuse redirects, cap time and response size, and attach provider credentials only after exact normalized-origin binding.' + ), + ] + authority: Annotated[ + Literal['publisher_designated'], + Field( + description="Explicitly states that authority comes from the publisher-hosted placement declaration. The provider's rendering_origin metadata is informational and remains non-authoritative elsewhere." + ), + ] = 'publisher_designated' + routes: Annotated[list[Route], Field(min_length=1)] -# Explicit exports -__all__ = ["PublisherDesignatedPreviewProvider", "Route"] + @field_validator('agent_url') + @classmethod + def _require_https_agent_url(cls, value: AnyUrl) -> AnyUrl: + if value.scheme != 'https': + raise ValueError('agent_url must use https') + return value diff --git a/src/adcp/types/domains/core/preview_renderer_metadata.py b/src/adcp/types/domains/core/preview_renderer_metadata.py index bc28f3819..1196ac26a 100644 --- a/src/adcp/types/domains/core/preview_renderer_metadata.py +++ b/src/adcp/types/domains/core/preview_renderer_metadata.py @@ -1,21 +1,50 @@ -"""Types declared by the AdCP ``core/preview_renderer_metadata`` schema. +# generated by datamodel-codegen: +# filename: core/preview_renderer_metadata.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.preview_renderer_metadata import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.preview_renderer_metadata import ( - PreviewRendererMetadata, - RenderingOrigin, -) +class RenderingOrigin(StrEnum): + platform_native = 'platform_native' + agent_approximation = 'agent_approximation' + -# Explicit exports -__all__ = ["PreviewRendererMetadata", "RenderingOrigin"] +class PreviewRendererMetadata(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + renderer_id: Annotated[ + str, Field(description='Stable implementation identifier.', min_length=1) + ] + version: Annotated[ + str, + Field( + description='Exact semantic version of the renderer implementation.', + pattern='^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$', + ), + ] + export: Annotated[ + str, + Field( + description='Renderer export or entry-point name used for this render.', min_length=1 + ), + ] + rendering_origin: Annotated[ + RenderingOrigin, + Field( + description='Informational implementation origin copied from the selected route. It does not grant authority.' + ), + ] + tracking_suppressed: Annotated[ + StrictBool, + Field( + description='True only when the produced output cannot initiate impression, click, billing, conversion, viewability, or asset-fetch side effects. Renderers that retain any remote asset URL or navigation MUST emit false.' + ), + ] diff --git a/src/adcp/types/domains/core/price.py b/src/adcp/types/domains/core/price.py index ba87b6ca8..2efecc705 100644 --- a/src/adcp/types/domains/core/price.py +++ b/src/adcp/types/domains/core/price.py @@ -1,18 +1,39 @@ -"""Types declared by the AdCP ``core/price`` schema. +# generated by datamodel-codegen: +# filename: core/price.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.price import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.price import Period, Price +class Period(StrEnum): + night = 'night' + month = 'month' + year = 'year' + one_time = 'one_time' + -# Explicit exports -__all__ = ["Period", "Price"] +class Price(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + amount: Annotated[ + StrictFloat, Field(description='Monetary amount in the specified currency.', ge=0.0) + ] + currency: Annotated[ + str, + Field( + description="ISO 4217 currency code (e.g., 'USD', 'EUR', 'GBP').", pattern='^[A-Z]{3}$' + ), + ] + period: Annotated[ + Period | None, + Field( + description="Billing period. 'night' for hotel rates, 'month' or 'year' for salaries and rentals, 'one_time' for purchase prices. Omit when the period is obvious from context (e.g., a vehicle price is always one-time)." + ), + ] = None diff --git a/src/adcp/types/domains/core/pricing_option.py b/src/adcp/types/domains/core/pricing_option.py index 195fba068..adef81621 100644 --- a/src/adcp/types/domains/core/pricing_option.py +++ b/src/adcp/types/domains/core/pricing_option.py @@ -1,18 +1,41 @@ -"""Types declared by the AdCP ``core/pricing_option`` schema. +# generated by datamodel-codegen: +# filename: core/pricing_option.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.pricing_option import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..pricing_options import ( + cpa_option, + cpc_option, + cpcv_option, + cpm_option, + cpp_option, + cpv_option, + flat_rate_option, + revenue_share_option, + time_option, + vcpm_option, +) -from adcp.types.generated_poc.core.pricing_option import PricingOption -# Explicit exports -__all__ = ["PricingOption"] +PricingOption = Annotated[ + cpm_option.CpmPricingOption + | vcpm_option.VcpmPricingOption + | cpc_option.CpcPricingOption + | cpcv_option.CpcvPricingOption + | cpv_option.CpvPricingOption + | cpp_option.CppPricingOption + | cpa_option.CpaPricingOption + | revenue_share_option.RevenueSharePricingOption + | flat_rate_option.FlatRatePricingOption + | time_option.TimeBasedPricingOption, + Field( + description="A seller's commercial pricing offer, discriminated by pricing_model. pricing_option_id is the immutable identity of its binding commercial terms: changing fixed price, floor, currency, model, or priced applicability requires a new ID rather than reinterpreting the old one. fixed_price is an agreed seller price; floor_price is the seller minimum for an auction-priced option; price_guidance is non-binding discovery guidance. Buyer execution instructions belong in BiddingPolicy. revenue_share is contingent pricing: its payable spend is calculated from settled commissionable_value and does not use fixed_price or auction bidding fields. The legacy max_bid boolean and package bid_price are deprecated in 3.2 and removed in the next major.", + discriminator='pricing_model', + title='Pricing Option', + ), +] diff --git a/src/adcp/types/domains/core/principal_changed_webhook.py b/src/adcp/types/domains/core/principal_changed_webhook.py index dff995db2..d95c37469 100644 --- a/src/adcp/types/domains/core/principal_changed_webhook.py +++ b/src/adcp/types/domains/core/principal_changed_webhook.py @@ -1,18 +1,95 @@ -"""Types declared by the AdCP ``core/principal_changed_webhook`` schema. +# generated by datamodel-codegen: +# filename: core/principal_changed_webhook.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.principal_changed_webhook import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import ext as ext_1 + + +class Reason(StrEnum): + destination_state_changed = 'destination_state_changed' + setup_expiring = 'setup_expiring' + setup_expired = 'setup_expired' + proof_invalidated = 'proof_invalidated' + declarations_intersection_changed = 'declarations_intersection_changed' + other = 'other' -from adcp.types.generated_poc.core.principal_changed_webhook import PrincipalChangedWebhook, Reason -# Explicit exports -__all__ = ["PrincipalChangedWebhook", "Reason"] +class PrincipalChangedWebhook(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + idempotency_key: Annotated[ + str, + Field( + description='Sender-generated key stable across retries of the same fire. Sellers MUST generate a cryptographically random value (UUID v4 recommended) per distinct fire and reuse it on every retry of the same fire. Receivers MUST dedupe by this key, scoped to the authenticated sender identity.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + notification_id: Annotated[ + str, + Field( + description='Stable identifier for this logical principal-state transition. Re-emissions of the same transition reuse this value under a new idempotency_key; a later distinct transition receives a new id.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] + notification_type: Annotated[ + Literal['principal.changed'], + Field( + description="Fixed notification type discriminator. Matches the value registered on the subscriber's `event_types`." + ), + ] = 'principal.changed' + fired_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the seller initiated this fire. Distinct from `changed_at`, which is when the seller recorded the state transition.' + ), + ] + subscriber_id: Annotated[ + str, + Field( + description="Identifies which caller-scoped notification_configs[] entry is receiving this fire. Echoed verbatim from the entry's subscriber_id.", + max_length=64, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,64}$', + ), + ] + agent_url: Annotated[ + AnyUrl, + Field( + description='Canonical seller agent URL whose principal state changed. Receivers connected to multiple agents use this to select which principal record to re-read.' + ), + ] + changed_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the seller recorded the principal-state transition.' + ), + ] + reason: Annotated[ + Reason, + Field( + description='Coarse reason for the invalidation. Advisory routing/debug metadata; receivers MUST re-read get_principal rather than inferring the new state from the reason.' + ), + ] + destination_id: Annotated[ + str | None, + Field( + description='Optional advisory hint naming the affected reporting destination for destination-scoped reasons. Receivers MAY use it for selective handling but MUST still treat the get_principal read as authoritative.', + max_length=64, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,64}$', + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/principal_declarations.py b/src/adcp/types/domains/core/principal_declarations.py index 716e99c58..a80f31bad 100644 --- a/src/adcp/types/domains/core/principal_declarations.py +++ b/src/adcp/types/domains/core/principal_declarations.py @@ -1,22 +1,53 @@ -"""Types declared by the AdCP ``core/principal_declarations`` schema. +# generated by datamodel-codegen: +# filename: core/principal_declarations.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.principal_declarations import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import experimental_feature_id + + +class AsyncAdcpVersion(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^\\d+\\.\\d+$'} + + +class WebhookSigningAlgorithm(StrEnum): + ed25519 = 'ed25519' + ecdsa_p256_sha256 = 'ecdsa-p256-sha256' -from adcp.types.generated_poc.core.principal_declarations import ( - AgentDeclarations, - AsyncAdcpVersion, - WebhookSigningAlgorithm, -) -# Explicit exports -__all__ = ["AgentDeclarations", "AsyncAdcpVersion", "WebhookSigningAlgorithm"] +class AgentDeclarations(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + async_adcp_versions: Annotated[ + list[AsyncAdcpVersion] | None, + Field( + description='AdCP minor versions, such as 3.2, whose asynchronous payload shapes (webhooks and other seller-initiated pushes) the caller can parse. The seller selects payload shapes from the accepted intersection; without a declaration the seller uses its advertised default.', + max_length=8, + min_length=1, + ), + ] = None + webhook_signing_algorithms: Annotated[ + list[WebhookSigningAlgorithm] | None, + Field( + description="RFC 9421 webhook-signing algorithms the caller can verify. The accepted intersection with the seller's webhook_signing.algorithms MUST be non-empty when the caller has any active webhook subscriber; an empty intersection fails the sync request with UNSUPPORTED_FEATURE because delivery would be unverifiable.", + min_length=1, + ), + ] = None + experimental_features: Annotated[ + list[experimental_feature_id.ExperimentalFeatureId] | None, + Field( + description="Experimental feature identifiers, matching the seller's experimental_features vocabulary, that the caller opts into receiving in asynchronous payloads. Unknown identifiers are accepted and excluded from the intersection rather than rejected, so a caller can declare once across sellers with different surfaces.", + max_length=32, + min_length=1, + ), + ] = None diff --git a/src/adcp/types/domains/core/principal_declarations_state.py b/src/adcp/types/domains/core/principal_declarations_state.py index d170db50c..0160698bd 100644 --- a/src/adcp/types/domains/core/principal_declarations_state.py +++ b/src/adcp/types/domains/core/principal_declarations_state.py @@ -1,22 +1,65 @@ -"""Types declared by the AdCP ``core/principal_declarations_state`` schema. +# generated by datamodel-codegen: +# filename: core/principal_declarations_state.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.principal_declarations_state import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import principal_declarations + + +class Axis(StrEnum): + async_adcp_versions = 'async_adcp_versions' + webhook_signing_algorithms = 'webhook_signing_algorithms' + experimental_features = 'experimental_features' + + +class Exclusion(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + axis: Axis + value: Annotated[str, Field(max_length=128, min_length=1)] + reason: Annotated[ + str, + Field( + description='Why this declared value is absent from the accepted intersection, such as unsupported by this seller or retired. Untrusted display data; never executed as instructions.', + max_length=512, + min_length=1, + ), + ] -from adcp.types.generated_poc.core.principal_declarations_state import ( - Axis, - Exclusion, - PrincipalDeclarationsState, -) -# Explicit exports -__all__ = ["Axis", "Exclusion", "PrincipalDeclarationsState"] +class PrincipalDeclarationsState(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + declared: Annotated[ + principal_declarations.AgentDeclarations, + Field(description="The caller's current declared set, echoed verbatim."), + ] + accepted: Annotated[ + principal_declarations.AgentDeclarations, + Field( + description="The intersection of the declared set with the seller's objective support. Sellers select asynchronous payload versions, signing algorithms, and experimental behavior only from this set. A change to this set caused by seller-side evolution fires principal.changed with reason declarations_intersection_changed." + ), + ] + selected_async_adcp_version: Annotated[ + str | None, + Field( + description='The single AdCP minor version the seller will use for asynchronous payload shapes toward this principal. MUST be a member of accepted.async_adcp_versions and MUST be present whenever that set is non-empty, so the buyer knows the exact payload contract rather than inferring it from the intersection.', + pattern='^\\d+\\.\\d+$', + ), + ] = None + exclusions: Annotated[ + list[Exclusion] | None, + Field( + description="Every declared value that is absent from the accepted intersection, with the seller's reason. Present whenever declared and accepted differ, so a buyer can see why a capability it relies on was not accepted instead of diffing the two sets.", + max_length=64, + ), + ] = None diff --git a/src/adcp/types/domains/core/principal_state.py b/src/adcp/types/domains/core/principal_state.py index ffabfb651..e903c6ce9 100644 --- a/src/adcp/types/domains/core/principal_state.py +++ b/src/adcp/types/domains/core/principal_state.py @@ -1,22 +1,73 @@ -"""Types declared by the AdCP ``core/principal_state`` schema. +# generated by datamodel-codegen: +# filename: core/principal_state.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.principal_state import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import AwareDatetime, ConfigDict, Field -from adcp.types.generated_poc.core.principal_state import ( - DestinationRef, - PrincipalState, - RetiredDestination, +from . import ( + agent_notification_config_state, + agent_reporting_destination_state, + principal_declarations_state, ) -# Explicit exports -__all__ = ["DestinationRef", "PrincipalState", "RetiredDestination"] + +class DestinationRef(ScalarStr): + __slots__ = () + _constraints = {'max_length': 255, 'min_length': 1} + + +class RetiredDestination(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + destination_id: Annotated[ + str, Field(max_length=64, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,64}$') + ] + destination_refs: Annotated[ + list[DestinationRef], + Field( + description='Retained generation references of the revoked destination, newest first, still resolvable for retained reporting history but ineligible for delivery and for new bindings.', + max_length=32, + min_length=1, + ), + ] + revoked_at: AwareDatetime | None = None + + +class PrincipalState(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + notification_configs: Annotated[ + list[agent_notification_config_state.AgentNotificationConfigState] | None, + Field( + description='Current agent-level webhook subscribers. authentication.credentials is always omitted because it is write-only.', + max_length=16, + ), + ] = None + reporting_destinations: Annotated[ + list[agent_reporting_destination_state.AgentReportingDestinationState] | None, + Field( + description='Current reusable reporting destination bindings and setup states. destination_id and destination_ref values MUST each be unique within this caller-scoped array; superseded generations of a current destination appear in its prior_destination_refs.', + max_length=64, + ), + ] = None + declarations: Annotated[ + principal_declarations_state.PrincipalDeclarationsState | None, + Field( + description='Declared consumption facts and the seller-computed accepted intersection. Present when and only when the seller supports the declarations section.' + ), + ] = None + retired_destinations: Annotated[ + list[RetiredDestination] | None, + Field( + description='Destinations the caller revoked by omitting them from a submitted reporting_destinations section, retained while any generation remains resolvable for reporting history. Enumerable only by the owning principal. Reusing a retired destination_id requires fresh registration and proof and produces a new generation.', + max_length=64, + ), + ] = None diff --git a/src/adcp/types/domains/core/product.py b/src/adcp/types/domains/core/product.py index 2653aa8f2..6f99f0624 100644 --- a/src/adcp/types/domains/core/product.py +++ b/src/adcp/types/domains/core/product.py @@ -1,74 +1,893 @@ -"""Types declared by the AdCP ``core/product`` schema. +# generated by datamodel-codegen: +# filename: core/product.json +# timestamp: 2026-10-04T01:14:11+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.product import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Any, Annotated, Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel, SchemaInt +from adcp.types._scalar import ScalarFloat, ScalarStr +from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, StrictBool, model_validator -from adcp.types.generated_poc.core.product import ( - AudienceActivation, - CatalogMatch, - ConversionTracking, - Country, - DeliveryMeasurement, - MatchedGtin, - MaterialSubmission, - MetricOptimization, - Product, - ProductCard, - ProductCardDetailed, - Provider, - PublisherDomain, - PublisherProperty, - PublisherProperty81, - PublisherProperty82, - PublisherProperty83, - PublisherProperty84, - PublisherProperty85, - PublisherProperty86, - PublisherProperty87, - Specification, - SupportedMetric, - SupportedTarget, - SupportedTarget5, - SupportedViewDuration, - TrustedMatch, +from ..enums import action_source, audio_distribution_type, catalog_type +from ..enums import channels as channels_1 +from ..enums import delivery_type as delivery_type_1 +from ..enums import exclusivity as exclusivity_1 +from ..enums import ( + reach_unit, + response_type, + social_placement_surface, + sponsored_placement_type, + uid_type, + video_placement_type, + viewability_standard, +) +from . import acceptance_policy_profile_ids as acceptance_policy_profile_ids_1 +from . import audience_activation_method +from . import audience_evidence as audience_evidence_1 +from . import audience_evidence_selection, brand_ref +from . import cancellation_policy as cancellation_policy_1 +from . import collection_selector +from . import creative_policy as creative_policy_1 +from . import data_provider_signal_selector, delivery_forecast, demographic_targeting_capability +from . import ext as ext_1 +from . import format_id, installment, inventory_list_application +from . import measurement_readiness as measurement_readiness_1 +from . import measurement_terms as measurement_terms_1 +from . import media_buy_support as media_buy_support_1 +from . import outcome_measurement as outcome_measurement_1 +from . import ( + performance_standard, + placement, + pricing_option, + product_allowed_action, + product_card_reference_asset, + product_execution_requirement, + product_format_declaration, + product_identity, + product_signal_targeting_option, + product_targeting_resolution, + property_id, + property_tag, ) +from . import reporting_capabilities as reporting_capabilities_1 +from . import signal_listing +from . import signal_targeting_rules as signal_targeting_rules_1 +from . import targeting_overlay_support +from . import vendor_metric_optimization as vendor_metric_optimization_1 +from .assets import image_asset + + +class PublisherDomain(ScalarStr): + __slots__ = () + _constraints = { + 'pattern': '^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + } + + +class PublisherProperty81(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + publisher_domain: Annotated[ + str | None, + Field( + description="Domain where publisher's adagents.json is hosted (e.g., 'cnn.com'). XOR with `publisher_domains` — exactly one MUST be present on each `publisher_properties[]` entry; both-present and neither-present both fail validation.", + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] = None + publisher_domains: Annotated[ + list[PublisherDomain] | None, + Field( + description="Compact form for fanning the same selector across many publishers (e.g., a managed network listing every publisher it represents). Each entry is the domain where that publisher's adagents.json is hosted. Each listed domain MUST be canonicalized to lowercase (the `pattern` already rejects uppercase). Mutually exclusive with `publisher_domain`. Each listed domain counts as explicitly scoped for the `managerdomain` fallback safety rule.", + min_length=1, + ), + ] = None + selection_type: Annotated[ + Literal['all'], + Field( + description='Discriminator indicating all properties from each addressed publisher are included' + ), + ] = 'all' + + +class PublisherProperty84(AdCPBaseModel): + pass + + +class PublisherProperty85(PublisherProperty81, PublisherProperty84): + pass + + +class SupportedMetric(StrEnum): + clicks = 'clicks' + views = 'views' + completed_views = 'completed_views' + viewed_seconds = 'viewed_seconds' + viewable_rate = 'viewable_rate' + attention_seconds = 'attention_seconds' + attention_score = 'attention_score' + engagements = 'engagements' + follows = 'follows' + saves = 'saves' + profile_visits = 'profile_visits' + reach = 'reach' + + +class SupportedViewDuration(ScalarFloat): + __slots__ = () + _constraints = {'gt': 0.0} + + +class SupportedTarget(StrEnum): + cost_per = 'cost_per' + threshold_rate = 'threshold_rate' + + +class SupportedTarget5(StrEnum): + cost_per = 'cost_per' + per_ad_spend = 'per_ad_spend' + maximize_value = 'maximize_value' + + +class ConversionTracking(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + action_sources: Annotated[ + list[action_source.ActionSource] | None, + Field( + description="Action sources relevant to this product (e.g. a retail media product might have 'in_store' and 'website', while a display product might only have 'website')", + min_length=1, + ), + ] = None + supported_targets: Annotated[ + list[SupportedTarget5] | None, + Field( + description='Target kinds available for event goals on this product. Values match target.kind on the optimization goal. cost_per: target cost per conversion event. per_ad_spend: target return on ad spend (requires value_field on event sources). maximize_value: maximize total conversion value without a specific ratio target (requires value_field). Only these target kinds are accepted — goals with unlisted target kinds will be rejected. A goal without a target implicitly maximizes conversion count within budget — no declaration needed for that mode. When omitted, buyers can still set target-less event goals.', + min_length=1, + ), + ] = None + platform_managed: Annotated[ + StrictBool | None, + Field( + description="Whether the seller provides its own always-on measurement (e.g. Amazon sales attribution for Amazon advertisers). When true, sync_event_sources response will include seller-managed event sources with managed_by='seller'." + ), + ] = None + + +class MatchedGtin(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[0-9]{8,14}$'} + + +class CatalogMatch(AdCPBaseModel): + matched_gtins: Annotated[ + list[MatchedGtin] | None, + Field( + description="GTINs from the buyer's catalog that are eligible on this product's inventory. Standard GTIN formats (GTIN-8 through GTIN-14). Only present for product-type catalogs with GTIN matching." + ), + ] = None + matched_ids: Annotated[ + list[str] | None, + Field( + description="Item IDs from the buyer's catalog that matched this product's inventory. The ID type depends on the catalog type and content_id_type (e.g., SKUs for product catalogs, job_ids for job catalogs, offering_ids for offering catalogs)." + ), + ] = None + matched_count: Annotated[ + SchemaInt | None, + Field(description="Number of catalog items that matched this product's inventory.", ge=0), + ] = None + submitted_count: Annotated[ + SchemaInt, + Field(description="Total catalog items evaluated from the buyer's catalog.", ge=0), + ] + + +class Specification(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + label: Annotated[str, Field(max_length=60)] + value: Annotated[str, Field(max_length=200)] + + +class Country(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{2}$'} + + +class MaterialSubmission(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + url: Annotated[ + AnyUrl | None, + Field(description='HTTPS URL for uploading or submitting physical creative materials'), + ] = None + email: Annotated[ + EmailStr | None, Field(description='Email address for creative material submission') + ] = None + instructions: Annotated[ + str | None, + Field( + description='Human-readable instructions for material submission (file naming conventions, shipping address, etc.)', + max_length=2000, + ), + ] = None + ext: ext_1.ExtensionObject | None = None + + +class PublisherProperty82(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + publisher_domain: Annotated[ + str, + Field( + description="Domain where publisher's adagents.json is hosted (e.g., 'cnn.com').", + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] + selection_type: Annotated[ + Literal['by_id'], + Field(description='Discriminator indicating selection by specific property IDs'), + ] = 'by_id' + property_ids: Annotated[ + list[property_id.PropertyId], + Field(description="Specific property IDs from the publisher's adagents.json", min_length=1), + ] + + +class PublisherProperty83(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + publisher_domain: Annotated[ + str | None, + Field( + description="Domain where publisher's adagents.json is hosted (e.g., 'cnn.com'). XOR with `publisher_domains` — exactly one MUST be present on each `publisher_properties[]` entry; both-present and neither-present both fail validation.", + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] = None + publisher_domains: Annotated[ + list[PublisherDomain] | None, + Field( + description="Compact form for fanning the same tag predicate across many publishers (canonical managed-network shape). Each entry is the domain where that publisher's adagents.json is hosted. Each listed domain MUST be canonicalized to lowercase (the `pattern` already rejects uppercase). Mutually exclusive with `publisher_domain`. Each listed domain counts as explicitly scoped for the `managerdomain` fallback safety rule.", + min_length=1, + ), + ] = None + selection_type: Annotated[ + Literal['by_tag'], Field(description='Discriminator indicating selection by property tags') + ] = 'by_tag' + property_tags: Annotated[ + list[property_tag.PropertyTag], + Field( + description="Property tags resolved against each addressed publisher's adagents.json, OR against the parent file's top-level `properties[]` when those properties carry a `publisher_domain` matching the selector. Selector covers all properties carrying any of these tags.", + min_length=1, + ), + ] + + +class PublisherProperty86(PublisherProperty82, PublisherProperty84): + pass + + +class PublisherProperty87(PublisherProperty83, PublisherProperty84): + pass + + +PublisherProperty = PublisherProperty85 | PublisherProperty86 | PublisherProperty87 + + +class MetricOptimization(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + supported_metrics: Annotated[ + list[SupportedMetric], + Field( + description='Metric kinds this product can optimize for. Buyers should only request metric goals for kinds listed here. **DEPRECATED values** (slated for removal at next major): `attention_seconds` and `attention_score` — declare vendor-attested attention/quality metrics via `vendor_metric_optimization.supported_metrics[]` with an explicit vendor binding instead. Sellers MAY reject the deprecated values with `TERMS_REJECTED` and a suggestion to use the `vendor_metric` kind.', + min_length=1, + ), + ] + supported_reach_units: Annotated[ + list[reach_unit.ReachUnit] | None, + Field( + description="Reach units this product can optimize for. Required when supported_metrics includes 'reach'. Buyers must set reach_unit to a value in this list on reach optimization goals — sellers reject unsupported values.", + min_length=1, + ), + ] = None + supported_view_durations: Annotated[ + list[SupportedViewDuration] | None, + Field( + description="Video view duration thresholds (in seconds) this product supports for completed_views goals. Only relevant when supported_metrics includes 'completed_views'. When absent, the seller uses their platform default. Buyers must set view_duration_seconds to a value in this list — sellers reject unsupported values." + ), + ] = None + supported_viewability_standards: Annotated[ + list[viewability_standard.ViewabilityStandard] | None, + Field( + description="Viewability standards this product can optimize viewable_rate goals against. Only relevant when supported_metrics includes 'viewable_rate'. When absent, buyers cannot assume a specific standard is supported and sellers reject unsupported values. Buyers must set the goal's standard to a value in this list when it is present.", + min_length=1, + ), + ] = None + supported_targets: Annotated[ + list[SupportedTarget] | None, + Field( + description='Target kinds available for metric goals on this product. Values match target.kind on the optimization goal. Only these target kinds are accepted — goals with unlisted target kinds will be rejected. When omitted, buyers can set target-less metric goals (maximize volume within budget) but cannot set specific targets.' + ), + ] = None + + +class Provider(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + agent_url: Annotated[ + AnyUrl, + Field( + description="Provider's agent URL from the registry. Canonical identifier for this TMP provider." + ), + ] + context_match: Annotated[ + StrictBool | None, + Field(description='Whether this provider handles context match for this product.'), + ] = False + identity_match: Annotated[ + StrictBool | None, + Field(description='Whether this provider handles identity match for this product.'), + ] = False + countries: Annotated[ + list[Country] | None, + Field( + description="ISO 3166-1 alpha-2 country codes this provider serves for identity match. The router uses this to select the correct regional provider based on the request's country field. Required when identity_match is true.", + min_length=1, + ), + ] = None + uid_types: Annotated[ + list[uid_type.UidType] | None, + Field( + description="Identity types this regional provider can resolve. The router filters providers whose uid_types includes the request's uid_type. Required when identity_match is true.", + min_length=1, + ), + ] = None + + +class TrustedMatch(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + context_match: Annotated[ + StrictBool, + Field( + description="Whether this product supports Context Match requests. When true, the publisher's TMP router will send context match requests to registered providers for this product's inventory." + ), + ] + identity_match: Annotated[ + StrictBool | None, + Field( + description="Whether this product supports Identity Match requests. When true, the publisher's TMP router will send identity match requests to evaluate user eligibility." + ), + ] = False + response_types: Annotated[ + list[response_type.TmpResponseType] | None, + Field(description='What the publisher can accept back from context match.', min_length=1), + ] = [response_type.TmpResponseType.activation] + dynamic_brands: Annotated[ + StrictBool | None, + Field( + description="Whether the buyer can select a brand at match time. When false (default), the brand must be specified on the media buy/package. When true, the buyer's offer can include any brand — the publisher applies approval rules at match time. Enables multi-brand agreements where the holding company or buyer agent selects brand based on context." + ), + ] = False + providers: Annotated[ + list[Provider] | None, + Field( + description="TMP providers integrated with this product's inventory. Each entry identifies a provider by agent_url (from the registry) and declares what match types it supports for this product. The product-level context_match and identity_match booleans declare what the product supports overall; the per-provider booleans declare which provider handles each match type. Enables buyer discovery: 'find products where a specific provider does context matching.'", + min_length=1, + ), + ] = None + + +class ProductCard(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + image: Annotated[ + image_asset.ImageAsset | None, + Field( + description='Hero image for the card. Recommended ~300x400 (4:3 portrait) for the standard card layout; receivers may scale.' + ), + ] = None + title: Annotated[ + str | None, Field(description='Card title (typically the product name).', max_length=60) + ] = None + description: Annotated[ + str | None, + Field(description='Short descriptive blurb shown below the title.', max_length=200), + ] = None + price_label: Annotated[ + str | None, + Field( + description="Formatted price or pricing summary (e.g., 'From $5 CPM', 'Auction floor $0.50 CPC'). Free-text — receivers render verbatim.", + max_length=30, + ), + ] = None + cta_label: Annotated[ + str | None, + Field( + description="Call-to-action button label (e.g., 'View details', 'Get proposal').", + max_length=25, + ), + ] = None + + +class DeliveryMeasurement(AdCPBaseModel): + vendors: Annotated[ + list[brand_ref.BrandReference] | None, + Field( + description="Measurement vendors used for this product, as structured `BrandRef` identities. Multiple entries when multiple vendors play different roles (e.g., the ad server plus a separate viewability vendor like IAS or DV; or a retail-media seller plus a third-party retail measurement vendor like Circana or NielsenIQ). Each vendor's `brand.json` `agents[type='measurement']` is the discovery anchor; metric definitions live on the agent's `get_adcp_capabilities.measurement.metrics[]` block. Distinct from `performance_standards[].vendor` which carries vendor identity for *committed* metrics with thresholds — this field carries vendor identity for the overall measurement story, including non-committed-but-reported metrics.", + min_length=1, + ), + ] = None + provider: Annotated[ + str | None, + Field( + deprecated=True, + description="**Deprecated as of this minor.** Free-form measurement provider description (e.g., 'Google Ad Manager with IAS viewability', 'Nielsen DAR', 'Geopath for DOOH impressions'). New implementations SHOULD use the structured `vendors` field instead. Retained for one-minor backwards compatibility; removed at the next major. When both `vendors` and `provider` are present, consumers MUST use `vendors` for vendor identity and treat `provider` as informational text.", + ), + ] = None + notes: Annotated[ + str | None, + Field( + description="Additional details about measurement methodology in plain language (e.g., 'MRC-accredited viewability. 50% in-view for 1s display / 2s video', 'Panel-based demographic measurement updated monthly'). Free-form prose for context that doesn't fit the structured `vendors` field." + ), + ] = None + + +class ProductCardDetailed(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + hero_image: Annotated[ + image_asset.ImageAsset | None, + Field(description='Primary hero image at the top of the detailed view.'), + ] = None + carousel_images: Annotated[ + list[image_asset.ImageAsset] | None, + Field(description='Additional images for a swipeable carousel below the hero.'), + ] = None + title: Annotated[str | None, Field(description='Page title (typically the product name).')] = ( + None + ) + description: Annotated[ + str | None, + Field( + description='Full descriptive copy. Markdown allowed in client renderers that support it; otherwise treat as plain text.' + ), + ] = None + specifications: Annotated[ + list[Specification] | None, + Field( + description="Structured key/value specifications (e.g., 'Aspect ratio: 9:16', 'Duration: 30s'). Each item is a labeled fact about the product." + ), + ] = None + price_label: Annotated[str | None, Field(description='Formatted price or pricing summary.')] = ( + None + ) + cta_label: Annotated[str | None, Field(description='Call-to-action button label.')] = None + reference_assets: Annotated[ + list[product_card_reference_asset.ProductCardReferenceAsset] | None, + Field( + description='Typed seller collateral for buyer planning — coverage maps, sample renders, environment photos, media kits. Distinct from hero_image/carousel_images, which are display-oriented.' + ), + ] = None + + +class AudienceActivation(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + methods: Annotated[ + list[audience_activation_method.AudienceActivationMethod], + Field( + description='Activation capabilities available on this product, as an unordered set. Entries are usually independent options; a clean_room entry may compose with dataset_query or platform_distribution to declare how a targetable result leaves the room or reaches the buying platform.', + min_length=1, + ), + ] + preferred_method: Annotated[ + audience_activation_method.AudienceActivationMethod | None, + Field( + description="The seller's preferred path when the buyer supports several. MUST also appear in methods." + ), + ] = None + notes: Annotated[ + str | None, + Field( + description='Free-text caveats (onboarding lead times, data-format constraints, regional restrictions).' + ), + ] = None + + +class Product(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + + + @model_validator(mode='before') + @classmethod + def _coerce_publisher_property_models(cls, data: Any) -> Any: + if isinstance(data, dict) and isinstance(data.get('publisher_properties'), list): + coerced = [] + changed = False + for item in data['publisher_properties']: + if hasattr(item, 'model_dump'): + coerced.append(item.model_dump(mode='json', exclude_none=True)) + changed = True + else: + coerced.append(item) + if changed: + data = dict(data) + data['publisher_properties'] = coerced + return data + product_id: Annotated[ + str, + Field( + description='Opaque identifier for this buyable product. For a non-custom wholesale product, sellers MUST reuse the ID for the same logical catalog offer within the seller and declared cache_scope across reads and wholesale-feed webhooks; feed and pricing versions communicate temporal catalog mutation, while retirement or replacement may end the identity. Concurrent or request-bound configurations whose effective targeting, disclosed targeting modifications, forecast assumptions, terms, or overlay support differ MUST use distinguishable configured product IDs. For is_custom: true, the ID identifies only the request-specific discovery/refinement lineage and is not stable across independent contexts. Sellers MUST keep every issued configured ID resolvable for its promised lifetime. Pricing variants within one logical product are distinguished by pricing_option_id: a seller MUST mint a new pricing_option_id whenever a binding fixed price, floor, currency, model, or priced applicability changes, and MUST NOT reinterpret an issued option ID at a new price. Selecting product_id plus pricing_option_id in create_media_buy accepts that returned configuration and commercial option.' + ), + ] + name: Annotated[str, Field(description='Human-readable product name')] + description: Annotated[ + str, Field(description='Detailed description of the product and its inventory') + ] + publisher_properties: Annotated[ + list[PublisherProperty], + Field( + description="SDK implementers MUST enforce singular-only at runtime: each entry uses the singular `publisher_domain` form; the compact `publisher_domains[]` form is rejected on products. Codegen toolchains (json-schema-to-typescript, quicktype, datamodel-code-generator, openapi-typescript-codegen) often flatten the `allOf + $ref + not.required` restriction below poorly and may drop the rejection constraint silently, emitting an unrestricted type — runtime enforcement is the safety net. Publisher properties covered by this product. Buyers fetch actual property definitions from each publisher's adagents.json and validate agent authorization. Selection patterns mirror the authorization patterns in adagents.json for consistency. The compact `publisher_domains[]` form is reserved for adagents.json `authorized_agents[].publisher_properties[]` so that buy-side traffic-and-pricing flatteners can always treat each entry as exactly one publisher.", + min_length=1, + ), + ] + channels: Annotated[ + list[channels_1.MediaChannel] | None, + Field( + description="Advertising channels this product is sold as. Products inherit from their properties' supported_channels but may narrow the scope. For example, a product covering YouTube properties might be sold as ['ctv'] even though those properties support ['olv', 'social', 'ctv']." + ), + ] = None + format_ids: Annotated[ + list[format_id.FormatReferenceStructuredObject] | None, + Field( + deprecated=True, + description='Deprecated in AdCP 3.2; removed in AdCP 4.0. Legacy named-format compatibility path. Products MUST carry `format_ids`, `format_options`, or both during the 3.x migration window. New products MUST author canonical `format_options[]`; sellers MAY additionally project those declarations to `format_ids` for legacy buyers. When both fields are present they MUST describe the same underlying formats, and buyers MUST prefer `format_options`. Do not author a new product from `format_ids` alone.', + ), + ] = None + format_options: Annotated[ + list[product_format_declaration.ProductFormatDeclaration] | None, + Field( + description="Canonical format-option path: one or more inline format declarations the product accepts. Each element narrows a canonical format with parameters, slots, platform_extensions, and optional locale_policy. New 3.2 products MUST carry format_options; a seller MAY additionally project the same declarations to deprecated format_ids for older 3.x peers. A declaration carrying locale_policy is canonical-only because legacy format_ids cannot preserve locale eligibility; no product or placement format_id may project to an effective locale-constrained option.\n\nWhen placements are published, product-level format_options are the union of formats deliverable somewhere in the product and the upper bound for every placement. A placement's effective accepted set is the intersection of every applicable layer: (1) the product format_options; (2) the product placement's inline format_options, when present; and (3) for kind publisher_ref, the named publisher's adagents.json catalog narrowing. Resolve layer 3 by locating the matching placements[] entry: use its format_options when present, resolving bare format_option_id references against that same file's top-level formats[]; otherwise use top-level formats[] applicable to the placement's property_ids/property_tags. An omitted optional layer is unconstrained, but an unresolved publisher placement or format-option reference MUST fail closed. A publisher-referenced placement without inline product format_options therefore does NOT inherit the full product union when the publisher catalog supplies narrower placement or property-scoped acceptance.\n\nMatch publisher-declared options by {publisher_domain, format_option_id}, match product-local options by format_option_id when publisher_domain is omitted, and otherwise match declarations with the same format_kind whose narrower parameters satisfy the broader declaration. A product- or placement-level declaration MUST NOT introduce a format outside the product upper bound. Locale policy follows the same intersection. If the product locale policy is absent, a placement may introduce any concrete policy as a narrowing of an unconstrained option; when both are present, every placement range must be contained by a product range under RFC 4647 Basic Filtering. Locale eligibility is checked independently for every placement where an assignment may serve.\n\nFor a product or package containing multiple included placements, a single creative intended for every placement MUST lie in the intersection of every selected placement's effective set. Distinct per-placement creatives MAY use the union, but the selected creative set MUST cover every included placement; uncovered inventory MUST be rejected or refined, never silently omitted. If a product spans multiple publishers but omits placements[], there is no public routing key for per-placement creatives: its format_options MUST therefore be the common intersection accepted across every selected publisher/property scope. A seller that needs a union of publisher-specific formats MUST publish placements[] with publisher-scoped identities and narrowing. Commercial terms such as price, floor, availability, and deal eligibility are product facts, not format parameters.", + min_length=1, + ), + ] = None + placements: Annotated[ + list[placement.Placement] | None, + Field( + description="Optional array of specific public placements within this product. Product placements declare `kind` to distinguish publisher-catalog placements (`publisher_ref`) from sales-agent-defined placements (`seller_inline`). Publisher references use canonical `{publisher_domain, placement_id}` identity and may omit name because adagents.json resolves it. New seller-inline placements SHOULD carry `seller_agent`; legacy rows without it remain scoped to the enclosing seller and product. A seller-inline publisher_domain is inventory attribution, not authority to mint an ID in that publisher's catalog namespace. Each placement MUST declare mode: targetable or included. Creative assignments route creatives only after placement inventory is purchased.", + min_length=1, + ), + ] = None + video_placement_types: Annotated[ + list[video_placement_type.VideoPlacementType] | None, + Field( + description='Declared video placement types that may be included in this product, using IAB Tech Lab/OpenRTB 2.6 video.plcmt definitions with AdCP-native names. Use on OLV, CTV, and other video products when buyers need to distinguish instream, accompanying-content, interstitial, and standalone/no-content inventory. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `video_placement_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', + min_length=1, + ), + ] = None + audio_distribution_types: Annotated[ + list[audio_distribution_type.AudioDistributionType] | None, + Field( + description='Declared audio distribution types that may be included in this product, using IAB Tech Lab/OpenRTB 2.6 audio.feed definitions with AdCP-native names. Use on radio, streaming-audio, podcast, gaming, and other audio products when buyers need to distinguish music streaming services, FM/AM broadcast, podcasts, catch-up radio, web radio, video-game audio, and text-to-speech inventory without changing the buyer-facing channel or adagents.json property type. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `audio_distribution_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', + min_length=1, + ), + ] = None + sponsored_placement_types: Annotated[ + list[sponsored_placement_type.SponsoredPlacementType] | None, + Field( + description='Declared sponsored-placement types that may be included in this product, distinguishing where catalog-driven retail-media placements render on the retailer surface (sponsored search, sponsored display, or sponsored native). Use on retail-media products when buyers need to distinguish search-keyed, display, and native in-grid sponsored inventory. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `sponsored_placement_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', + min_length=1, + ), + ] = None + social_placement_surfaces: Annotated[ + list[social_placement_surface.SocialPlacementSurface] | None, + Field( + description='Declared social-placement surfaces that may be included in this product, distinguishing the in-app surface where social placements render (feed, stories, short_video, explore, or search). Use on social products when buyers need to distinguish feed, story, short-video, and discovery surfaces. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `social_placement_surfaces`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', + min_length=1, + ), + ] = None + delivery_type: delivery_type_1.DeliveryType + exclusivity: Annotated[ + exclusivity_1.Exclusivity | None, + Field( + description="Whether this product offers exclusive access to its inventory. Defaults to 'none' when absent. Most relevant for guaranteed products tied to specific collections or placements." + ), + ] = None + pricing_options: Annotated[ + list[pricing_option.PricingOption], + Field( + description="Available pricing models for this product. Fixed prices and auction floors are binding for every later targeting selection permitted by this product's overlay_support; price_guidance remains non-binding. Declaring broad overlay support alongside a binding option is therefore a uniform-price promise, not permission to calculate a different price at create time. A seller with value-dependent rates MUST return a request-specific configured product after rediscovery with concrete targeting, split the inventory into separately priced products, or expose only non-binding guidance until it can issue a binding option. The seller MUST mint a new pricing_option_id whenever a binding price, floor, currency, model, or priced applicability changes. It MUST NOT silently reprice a create request or reuse the selected option ID with different terms.", + min_length=1, + ), + ] + forecast: Annotated[ + delivery_forecast.DeliveryForecast | None, + Field( + description="Forecasted delivery metrics for this product. Concrete discovery targeting scopes the forecast to those effective values. When discovery requested only required_overlay_support for a dimension, the forecast describes the product's discovery/default scope and is not a value-specific forecast for every later selection; buyers rediscover with concrete targeting_overlay values when they need that forecast." + ), + ] = None + outcome_measurement: Annotated[ + outcome_measurement_1.OutcomeMeasurement | None, + Field( + deprecated=True, + description='**Deprecated as of this minor.** Outcome capabilities (incremental sales lift, brand lift, foot traffic, etc.) are now declared via `reporting_capabilities.available_metrics` (the same path used for impressions, conversions, ROAS) with `qualifier.attribution_methodology` and `qualifier.attribution_window` carrying the methodology and window on commit. New implementations SHOULD use the unified pattern; this field is retained for one-minor backwards compatibility and removed at the next major. See `outcome-measurement.json` description for migration guidance.', + ), + ] = None + delivery_measurement: Annotated[ + DeliveryMeasurement | None, + Field( + description='Measurement vendors and methodology for delivery metrics. The buyer accepts the declared vendors as the source of truth for the buy. When absent, buyers should apply their own measurement defaults. Senders SHOULD populate `vendors` (structured BrandRef array) for new implementations; the legacy `provider` string field is deprecated and retained for one-minor backwards compatibility.' + ), + ] = None + measurement_terms: Annotated[ + measurement_terms_1.MeasurementTerms | None, + Field( + description="Seller's default billing measurement and makegood terms. Declares who counts the billing metric and what remedies apply when thresholds are breached. Buyers may propose different terms at media buy creation — sellers accept, reject (TERMS_REJECTED), or adjust per their policy." + ), + ] = None + performance_standards: Annotated[ + list[performance_standard.PerformanceStandard] | None, + Field( + description="Seller's default performance standards for this product: viewability, IVT, completion rate, brand safety, attention score. Buyers may propose different standards at media buy creation. When absent, no structured performance standards apply.", + min_length=1, + ), + ] = None + cancellation_policy: Annotated[ + cancellation_policy_1.CancellationPolicy | None, + Field( + description='Cancellation terms for this product. Declares the minimum notice period required before cancellation takes effect and any penalties for insufficient notice. Relevant for guaranteed delivery products. Buyers accept these terms by creating a media buy against the product.' + ), + ] = None + allowed_actions: Annotated[ + list[product_allowed_action.ProductAllowedAction] | None, + Field( + description='Actions buyers may perform on buys created against this product, scoped to statuses and modes. Advisory template — the authoritative per-buy capability is `available_actions[]` on the buy response, which resolves modes against current buy state, account tier, and negotiated terms. Buyers SHOULD use this for pre-flight product selection ("which products let me self-serve cancel within 72hr?") and read `available_actions[]` for runtime decisions. The array is uniquely keyed by `action` — sellers MUST NOT emit two entries with the same `action` value. Absence means the seller has not declared a structured action surface for this product — buyers fall back to `valid_actions[]` on buy responses for the flat string vocabulary.', + min_length=1, + ), + ] = None + reporting_capabilities: reporting_capabilities_1.ReportingCapabilities + creative_policy: creative_policy_1.CreativePolicy | None = None + is_custom: Annotated[ + StrictBool | None, + Field( + description='Whether this product is a request-specific configured offer rather than a reusable baseline product. Sellers MUST set true when targeting, disclosed resolution, pricing, forecast assumptions, inventory, or terms are bound for a particular discovery/refinement lineage. Products issued through targeting-aware discovery include expires_at even when exact acceptance omits targeting_resolution. For backward compatibility, is_custom alone does not make expires_at schema-required.' + ), + ] = None + property_targeting_allowed: Annotated[ + StrictBool | None, + Field( + description="Whether buyers can select a subset of this product's publisher_properties through targeting_overlay.property_list. When false, the product is fixed inventory: it matches requested property targeting only when its inherent property set already satisfies the request, or when a configured product discloses additional inventory through targeting_resolution." + ), + ] = False + data_provider_signals: Annotated[ + list[data_provider_signal_selector.DataProviderSignalSelector] | None, + Field( + deprecated=True, + description='Deprecated. Legacy/non-selectable metadata for data-provider signals already bundled into or associated with this product. This field does not provide buyer-selectable options, prices, or seller activation handles. Use included_signals for non-selectable product signal metadata, or signal_targeting_options for selectable package-level signal groups.', + ), + ] = None + included_signals: Annotated[ + list[signal_listing.SignalListing] | None, + Field( + description="Non-selectable signal metadata for signals already included in, bundled with, or planned into this product. These signals describe what the product is; buyers do not select them in packages[].targeting_overlay.signal_targeting_groups and this field does not imply package-level signal targeting. Use signal_ref scope 'data_provider' or 'signal_source' to reference externally defined signals without redefining their name or value_type. Use signal_ref scope 'product' with name and value_type when the included signal is defined only by this product.", + min_length=1, + ), + ] = None + signal_targeting_options: Annotated[ + list[product_signal_targeting_option.ProductSignalTargetingOption] | None, + Field( + description="Inline seller-offered signals that may be applied to packages for this product at create_media_buy time. Each entry references a named signal definition with signal_ref scope 'product' for a product-local signal option, scope 'data_provider' for an external signal definition published in adagents.json signals[] that the seller is authorized to apply, or scope 'signal_source' for a source-native signal. Product-local options define name and value_type inline; data-provider and signal-source options may omit those fields when the referenced definition or source is authoritative. Use this field when the selectable menu is product-specific, has product-specific pricing or activation handles, is the relevant subset for a brief/refine result, or should be rendered without an additional get_signals call. Wholesale products may omit this field and rely on get_signals for the selectable signal feed. Buyers select eligible signals through packages[].targeting_overlay.signal_targeting_groups when signal_targeting_rules allow; fixed/default entries are applied by the seller and echoed on the package state. Sellers MUST set signal_targeting_allowed to true whenever this field is present. Bundled, non-selectable signal metadata belongs in included_signals; legacy data_provider_signals may appear only for backwards compatibility.", + min_length=1, + ), + ] = None + signal_targeting_rules: Annotated[ + signal_targeting_rules_1.SignalTargetingRules | None, + Field( + description='Composition rules for selecting signals on this product. The selectable signal menu may come from inline signal_targeting_options or from get_signals when a wholesale product omits inline options. This is product-scoped because products may be backed by different ad servers with different Boolean targeting support and group limits.' + ), + ] = None + signal_targeting_allowed: Annotated[ + StrictBool | None, + Field( + description='Whether this product has a package-level signal_targeting_groups surface. When false (default), signals are bundled into the product terms and cannot be selected or explicitly echoed as package signal groups. When true, eligible signals from inline signal_targeting_options or from get_signals may be buyer-selected or seller-applied according to signal_targeting_rules and are represented through packages[].targeting_overlay.signal_targeting_groups. Editability is controlled by signal_targeting_rules; fixed/default-only products still set this to true when applied signal groups are echoed.' + ), + ] = False + demographic_targeting: Annotated[ + demographic_targeting_capability.DemographicTargetingCapability | None, + Field( + description='Exact demographic execution available for this product. Buyers MUST use this product-scoped declaration, not the seller-wide get_adcp_capabilities rollup, to preflight a demographic predicate.' + ), + ] = None + overlay_support: Annotated[ + targeting_overlay_support.TargetingOverlaySupport | None, + Field( + description='Binding product-scoped targeting dimensions the buyer may set independently on packages after discovery. Presence guarantees selectable capability subject to disclosed limits, not inventory or a forecast for every possible value. Targeting satisfied only through inherent product scope does not appear here. Returned products MUST cover every field requested through get_products.required_overlay_support. A later supported selection with no available inventory returns PRODUCT_UNAVAILABLE on create; an update outside the original priced envelope may return REQUOTE_REQUIRED.' + ), + ] = None + media_buy_support: Annotated[ + media_buy_support_1.ProductMediaBuySupport | None, + Field( + description='Binding product participation in shared MediaBuy-level controls. This is separate from overlay_support because a root frequency cap aggregates exposures across packages rather than targeting one package. Returned products MUST cover every field requested through required_media_buy_support.' + ), + ] = None + identity: product_identity.ProductIdentity | None = None + execution_requirements: Annotated[ + list[product_execution_requirement.ProductExecutionRequirement] | None, + Field( + description="Experimental (`media_buy.execution_requirements`). Account resources a package on this product needs before `create_media_buy` succeeds. Every entry is required. Account-independent: it does not change `cache_scope` and carries no account resource IDs or names. When present, it plus the `required_connections` of the package's selected format declarations is complete for the kinds in `product-execution-requirement.json`: a seller MUST NOT reject a package that satisfies all of them for lacking an undeclared kind. Absence means undeclared. A declaring seller MUST reject an unmet `event_source` or `catalog` entry on a buyer-supplied `create_media_buy` `packages[]` or `update_media_buy` `new_packages[]` entry, or an update that removes a satisfying binding, with `VALIDATION_ERROR`, `error.field` at the binding, and `error.details` per `error-details/execution-requirement-unmet.json`.", + min_length=1, + ), + ] = None + targeting_resolution: Annotated[ + product_targeting_resolution.ProductTargetingResolution | None, + Field( + description='Discovery-time targeting resolution bound to this configured product. modifications sparsely disclose product-specific differences from get_products.targeting_overlay. Request-level brief interpretation is returned once on GetProductsResponse.targeting_resolution. Exact structured overlay values are not repeated. Selecting product_id accepts the disclosed modifications; product forecast and pricing MUST reflect them.' + ), + ] = None + audience_evidence: Annotated[ + list[audience_evidence_1.AudienceEvidence] | None, + Field( + description='Immutable population-level evidence explaining why this inventory may suit an audience. This supports discovery, comparison, and planning only. It does not imply exact demographic targeting, user-level signal membership, or legal-age verification. Sellers MUST publish each distinct snapshot with a new snapshot_id and content_digest.', + min_length=1, + ), + ] = None + audience_evidence_selections: Annotated[ + list[audience_evidence_selection.AudienceEvidenceSelection] | None, + Field( + description='Exact evidence snapshots that satisfied required eligibility or affected seller ranking for this get_products result. When audience_evidence_requirements was supplied and evidence influenced inclusion or rank, sellers MUST return the relevant selections; an absent-evidence match under evidence_presence when_available has no selection. Product selections use decision_use recommendation or eligibility.', + min_length=1, + ), + ] = None + catalog_types: Annotated[ + list[catalog_type.CatalogType] | None, + Field( + description='Catalog types this product supports for catalog-driven campaigns. A sponsored product listing declares ["product"], a job board declares ["job", "offering"]. Buyers match synced catalogs to products via this field.', + min_length=1, + ), + ] = None + metric_optimization: Annotated[ + MetricOptimization | None, + Field( + description="Metric optimization capabilities for this product. Presence indicates the product supports optimization_goals with kind: 'metric'. No event source or conversion tracking setup required — the seller tracks these metrics natively." + ), + ] = None + vendor_metric_optimization: Annotated[ + vendor_metric_optimization_1.VendorMetricOptimization | None, + Field( + description="Vendor-attested metric optimization capabilities for this product. Presence indicates the product supports `optimization_goals` with `kind: 'vendor_metric'` — the seller's bidding stack can steer delivery toward a specific vendor's measurement (e.g., DV/IAS/Adelaide attention, Scope3 emissions, Kantar brand lift, retail-media partner metrics). Distinct from `metric_optimization` (seller-native metrics with no vendor binding) and from `reporting_capabilities.vendor_metrics` (which declares what the product can *report* rather than what it can *optimize against*). A product may report a vendor metric without being able to optimize for it. Buyers MUST verify the goal's `(vendor, metric_id)` is in `supported_metrics` AND that the package's `committed_metrics[]` includes a matching `{ scope: 'vendor', vendor, metric_id }` entry — optimization without committed reporting is unverifiable and is rejected at the wire level." + ), + ] = None + max_optimization_goals: Annotated[ + SchemaInt | None, + Field( + description='Maximum number of optimization_goals this product accepts on a package. When absent, no limit is declared. Most social platforms accept only 1 goal — buyers sending arrays longer than this value should expect the seller to use only the highest-priority (lowest priority number) goal.', + ge=1, + ), + ] = None + measurement_readiness: Annotated[ + measurement_readiness_1.MeasurementReadiness | None, + Field( + description="Assessment of whether the buyer's event source setup is sufficient for this product to optimize effectively. Only present when the seller can evaluate the buyer's account context. Buyers should check this before creating media buys with event-based optimization goals." + ), + ] = None + conversion_tracking: Annotated[ + ConversionTracking | None, + Field( + description="Conversion event tracking for this product. Presence indicates the product supports optimization_goals with kind: 'event'. Seller-level capabilities (supported event types, UID types, attribution windows) are declared in get_adcp_capabilities." + ), + ] = None + catalog_match: Annotated[ + CatalogMatch | None, + Field( + description='When the buyer provides a catalog on get_products, indicates which catalog items are eligible for this product. Only present for products where catalog matching is relevant (e.g., sponsored product listings, job boards, hotel ads).' + ), + ] = None + brief_relevance: Annotated[ + str | None, + Field( + description='Explanation of why this product matches the brief (only included when brief is provided)' + ), + ] = None + expires_at: Annotated[ + AwareDatetime | None, + Field( + description='Expiration timestamp. Required for request-specific configured products whose targeting resolution, price, forecast, inventory, or terms are time-bound. After this time, a seller that still recognizes the issued configured ID within the authenticated account and referenced discovery/refinement lineage rejects create_media_buy with PRODUCT_EXPIRED and the buyer re-runs get_products. Once the seller no longer retains an expiry tombstone, or whenever the ID belongs to another account or lineage, PRODUCT_NOT_FOUND applies instead; sellers are not required to retain tombstones indefinitely and MUST NOT disclose cross-tenant existence through error choice.' + ), + ] = None + product_card: Annotated[ + ProductCard | None, + Field( + description='Optional standard visual card for displaying this product in user interfaces (catalog browsers, dashboards, agent UIs). Distinct from `format` — product_card describes the UI rendering of the product itself, not the ad creative the product accepts. Typed inline; no format_id indirection. Receivers render the card directly from these fields.' + ), + ] = None + product_card_detailed: Annotated[ + ProductCardDetailed | None, + Field( + description='Optional detailed card with hero + carousel + structured specifications, for rich product presentation (media-kit-style pages, full product detail views). Distinct from `format` — describes the UI rendering of the product itself, not the ad creative the product accepts. Typed inline; no format_id indirection.' + ), + ] = None + collections: Annotated[ + list[collection_selector.CollectionSelector] | None, + Field( + description='Collections available in this product. Each entry references collections declared in an adagents.json by domain and collection ID. Buyers resolve full collection objects from the referenced adagents.json. Product selectors must name explicit collection_ids — the domain-only bulk-grant selector form is for authorization scoping, not product composition.', + min_length=1, + ), + ] = None + collection_targeting_allowed: Annotated[ + StrictBool | None, + Field( + description="Whether buyers can select a subset of this product's collections through targeting_overlay.collection_list or targeting_overlay.collection_selection. When false, the product is a fixed bundle (a collection_selection that exactly restates the complete bundle remains an inherent match); when true, collection selection is a product-scoped overlay capability." + ), + ] = False + list_applications: Annotated[ + list[inventory_list_application.InventoryListApplication] | None, + Field( + description='Product-scoped receipts for every effective property- or collection-list targeting reference. Sellers MUST return one receipt per application regardless of response field projection; exclusion applications receive a receipt even when summary.matched is zero, while zero matches for any inclusion application make the product ineligible and it is not returned. Each receipt uses the same pre-list product inventory baseline; pricing and forecast reflect inventory remaining after all effective lists are composed.', + min_length=1, + ), + ] = None + installments: Annotated[ + list[installment.Installment] | None, + Field( + description='Specific installments included in this product. Each installment references its parent through canonical collection_ref when the product spans multiple collections or publisher namespaces; collection_id remains a deprecated single-namespace shorthand. When absent with collections present, the product covers the collections broadly (run-of-collection).' + ), + ] = None + enforced_policies: Annotated[ + list[str] | None, + Field( + description='Registry policy IDs the seller enforces for this product. Enforcement level comes from the policy registry. Buyers can filter products by required policies.' + ), + ] = None + acceptance_policy_profile_ids: ( + acceptance_policy_profile_ids_1.AcceptancePolicyProfileIds | None + ) = None + trusted_match: Annotated[ + TrustedMatch | None, + Field( + description='Trusted Match Protocol capabilities for this product. When present, the product supports real-time contextual and/or identity matching via TMP. Buyers use this to determine what response types the publisher can accept and whether brands can be selected dynamically at match time.' + ), + ] = None + audience_activation: Annotated[ + AudienceActivation | None, + Field( + description="How buyer audience data can reach this product's targeting. Absence means undeclared — buyers SHOULD treat it as needs-clarification rather than non-support, except under an audience_activation_methods filter, where sellers MUST exclude undeclared products. Declare when the product accepts buyer audiences. Experimental: sellers declaring this MUST list media_buy.audience_activation in experimental_features on get_adcp_capabilities." + ), + ] = None + material_submission: Annotated[ + MaterialSubmission | None, + Field( + description="Instructions for submitting physical creative materials (print, static OOH, cinema). Present only for products requiring physical delivery outside the digital creative assignment flow. Buyer agents MUST validate url and email domains against the seller's known domains (from adagents.json) before submitting materials. Never auto-submit without human confirmation." + ), + ] = None + ext: ext_1.ExtensionObject | None = None -# Explicit exports -__all__ = [ - "AudienceActivation", - "CatalogMatch", - "ConversionTracking", - "Country", - "DeliveryMeasurement", - "MatchedGtin", - "MaterialSubmission", - "MetricOptimization", - "Product", - "ProductCard", - "ProductCardDetailed", - "Provider", - "PublisherDomain", - "PublisherProperty", - "PublisherProperty81", - "PublisherProperty82", - "PublisherProperty83", - "PublisherProperty84", - "PublisherProperty85", - "PublisherProperty86", - "PublisherProperty87", - "Specification", - "SupportedMetric", - "SupportedTarget", - "SupportedTarget5", - "SupportedViewDuration", - "TrustedMatch", -] + @model_validator(mode='after') + def _require_schema_required_group(self) -> Product: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('format_ids',), ('format_options',),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'Product requires at least one of these field groups: format_ids | format_options' + ) diff --git a/src/adcp/types/domains/core/product_allocation.py b/src/adcp/types/domains/core/product_allocation.py index 9cf3cc607..92e36e431 100644 --- a/src/adcp/types/domains/core/product_allocation.py +++ b/src/adcp/types/domains/core/product_allocation.py @@ -1,18 +1,97 @@ -"""Types declared by the AdCP ``core/product_allocation`` schema. +# generated by datamodel-codegen: +# filename: core/product_allocation.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.product_allocation import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import pacing as pacing_1 +from . import daypart_target, delivery_forecast +from . import ext as ext_1 -from adcp.types.generated_poc.core.product_allocation import ProductAllocation -# Explicit exports -__all__ = ["ProductAllocation"] +class ProductAllocation(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + product_id: Annotated[ + str, Field(description='ID of the product (must reference a product in the products array)') + ] + allocation_percentage: Annotated[ + StrictFloat | None, + Field( + description='Exact percentage of total budget allocated to this product in a fixed proposal. Percentages across all allocations MUST sum to 100. Must be absent in a seller-optimized proposal.', + ge=0.0, + le=100.0, + ), + ] = None + min_spend_target_percentage: Annotated[ + StrictFloat | None, + Field( + description='Soft minimum-spend target as a percentage of the executed total budget. Only valid in seller-optimized proposals, and sellers MUST NOT emit it unless they advertise media_buy.features.seller_optimized_min_spend_targets. The seller SHOULD attempt to reach it, but it is not a delivery guarantee. Minimum targets across allocations MUST sum to no more than 100.', + ge=0.0, + le=100.0, + ), + ] = None + max_spend_percentage: Annotated[ + StrictFloat | None, + Field( + description='Hard maximum share of the executed total budget that this product may spend. Only valid in seller-optimized proposals, and sellers MUST NOT emit it unless they advertise media_buy.features.seller_optimized_package_budgets. Maximums across allocations MUST collectively permit 100 percent of the budget to be spent.', + ge=0.0, + le=100.0, + ), + ] = None + pacing: Annotated[ + pacing_1.Pacing | None, + Field( + description='Recommended subordinate package pacing. On proposal execution this becomes pacing on the derived package and MUST NOT cause delivery to exceed aggregate proposal/media-buy pacing. On a committed proposal it is a firm delivery term. On a seller-optimized proposal, sellers MUST NOT emit it unless they advertise media_buy.features.seller_optimized_package_pacing.' + ), + ] = None + pricing_option_id: Annotated[ + str | None, + Field( + description="Selected pricing option ID from the product's pricing_options array. Required when the containing proposal is committed so create_media_buy executes the exact disclosed commercial terms; optional on legacy draft proposals." + ), + ] = None + rationale: Annotated[ + str | None, + Field(description='Explanation of why this product and allocation are recommended'), + ] = None + sequence: Annotated[ + SchemaInt | None, + Field(description='Optional ordering hint for multi-line-item plans (1-based)', ge=1), + ] = None + tags: Annotated[ + list[str] | None, + Field( + description="Categorical tags for this allocation (e.g., 'desktop', 'german', 'mobile') - useful for grouping/filtering allocations by dimension" + ), + ] = None + start_time: Annotated[ + AwareDatetime | None, + Field( + description='Recommended flight start date/time for this allocation in ISO 8601 format. Allows publishers to propose per-flight scheduling within a proposal. When omitted, the allocation applies to the full campaign date range.' + ), + ] = None + end_time: Annotated[ + AwareDatetime | None, + Field( + description='Recommended flight end date/time for this allocation in ISO 8601 format. Allows publishers to propose per-flight scheduling within a proposal. When omitted, the allocation applies to the full campaign date range.' + ), + ] = None + daypart_targets: Annotated[ + list[daypart_target.DaypartTarget] | None, + Field( + description="Recommended time windows for this allocation in spot-plan proposals. Each entry's timezone defaults to inventory_local when omitted, and entries MAY use different clocks.", + min_length=1, + ), + ] = None + forecast: Annotated[ + delivery_forecast.DeliveryForecast | None, + Field(description='Forecasted delivery metrics for this allocation'), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/product_allowed_action.py b/src/adcp/types/domains/core/product_allowed_action.py index d13015d42..3f1c2c29c 100644 --- a/src/adcp/types/domains/core/product_allowed_action.py +++ b/src/adcp/types/domains/core/product_allowed_action.py @@ -1,18 +1,58 @@ -"""Types declared by the AdCP ``core/product_allowed_action`` schema. +# generated by datamodel-codegen: +# filename: core/product_allowed_action.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.product_allowed_action import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import media_buy_action_mode, media_buy_status +from ..media_buy import change_term_constraints +from . import media_buy_available_action_id, sla_window -from adcp.types.generated_poc.core.product_allowed_action import ProductAllowedAction -# Explicit exports -__all__ = ["ProductAllowedAction"] +class ProductAllowedAction(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + action: Annotated[ + media_buy_available_action_id.MediaBuyAvailableActionId, + Field( + description='The action identifier. Accepts every legacy valid_actions value plus structured-only actions such as update_media_buy_frequency_cap.' + ), + ] + modes: Annotated[ + list[media_buy_action_mode.MediaBuyActionMode], + Field( + description='Modes available for this action on this product. A product may declare multiple modes (for example `self_serve` within tolerances, escalating to `requires_approval` outside) — the buy-side `available_actions[].mode` resolves to the singular mode in effect at mutation time. SDKs that see multiple modes MUST NOT assume which one will fire; they must read the resolved `mode` on the buy.', + min_length=1, + ), + ] + allowed_statuses: Annotated[ + list[media_buy_status.MediaBuyStatus] | None, + Field( + description='Media buy statuses in which this action is permitted. When absent, the action is permitted in all non-terminal statuses (`pending_creatives`, `pending_start`, `active`, `paused`).', + min_length=1, + ), + ] = None + sla: Annotated[ + sla_window.SlaWindow | None, + Field( + description='Optional SLA commitment for this action on this product. Absence means no commitment.' + ), + ] = None + constraints: Annotated[ + change_term_constraints.MediaBuyChangeTermConstraints | None, + Field( + description='Optional advisory machine-readable bounds buyers can use during product selection. The proposal must restate any binding bounds in commercial_terms.change_terms[].constraints.' + ), + ] = None + terms_ref: Annotated[ + str | None, + Field( + description='Optional advisory pointer to published commercial terms governing this product action. It is not a proposal change-term identity and never grants a binding change right; a proposal materializes binding rights under commercial_terms.change_terms[].term_id.' + ), + ] = None diff --git a/src/adcp/types/domains/core/product_audience_evidence_requirements.py b/src/adcp/types/domains/core/product_audience_evidence_requirements.py index 5b58620be..d3421157a 100644 --- a/src/adcp/types/domains/core/product_audience_evidence_requirements.py +++ b/src/adcp/types/domains/core/product_audience_evidence_requirements.py @@ -1,40 +1,115 @@ -"""Types declared by the AdCP ``core/product_audience_evidence_requirements`` schema. +# generated by datamodel-codegen: +# filename: core/product_audience_evidence_requirements.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.product_audience_evidence_requirements import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field, StrictBool, StrictFloat + +from ..enums import audience_evidence_methodology, audience_resolution_method, audience_subject_type +from . import brand_key +from . import ext as ext_1 +from .duration import Duration + + +class RequirementMode(StrEnum): + required = 'required' + preferred = 'preferred' + + +class EvidencePresence(StrEnum): + required = 'required' + when_available = 'when_available' + + +class AcceptedEvidenceType(StrEnum): + measured = 'measured' + forecast = 'forecast' + seller_declared = 'seller_declared' + + +class Unit(StrEnum): + seconds = 'seconds' + minutes = 'minutes' + hours = 'hours' + days = 'days' -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.product_audience_evidence_requirements import ( - AcceptedAttestationIssuers, - AcceptedAttestationIssuers1, - AcceptedAttestationIssuers2, - AcceptedAttestationIssuers3, - AcceptedEvidenceType, - EvidencePresence, - MaximumAge, - ProductAudienceEvidenceRequirements, - RequirementMode, - Unit, -) - -# Explicit exports -__all__ = [ - "AcceptedAttestationIssuers", - "AcceptedAttestationIssuers1", - "AcceptedAttestationIssuers2", - "AcceptedAttestationIssuers3", - "AcceptedEvidenceType", - "EvidencePresence", - "MaximumAge", - "ProductAudienceEvidenceRequirements", - "RequirementMode", - "Unit", +class MaximumAge(Duration): + unit: Annotated[ + Unit | None, + Field( + description="Time unit. 'seconds' for sub-minute precision. 'campaign' spans the full campaign flight." + ), + ] = None + + +class AcceptedAttestationIssuers1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Literal['brand'] = 'brand' + brand: brand_key.BrandKey + ext: ext_1.ExtensionObject | None = None + + +class AcceptedAttestationIssuers2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Literal['agent'] = 'agent' + agent_url: AnyUrl + ext: ext_1.ExtensionObject | None = None + + +class AcceptedAttestationIssuers3(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Literal['origin'] = 'origin' + origin: AnyUrl + ext: ext_1.ExtensionObject | None = None + + +AcceptedAttestationIssuers = Annotated[ + AcceptedAttestationIssuers1 | AcceptedAttestationIssuers2 | AcceptedAttestationIssuers3, + Field(discriminator='type'), ] + + +class ProductAudienceEvidenceRequirements(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + requirement_mode: RequirementMode + evidence_presence: EvidencePresence + accepted_methodologies: Annotated[ + list[audience_evidence_methodology.AudienceEvidenceMethodology] | None, Field(min_length=1) + ] = None + excluded_methodologies: Annotated[ + list[audience_evidence_methodology.AudienceEvidenceMethodology] | None, Field(min_length=1) + ] = None + accepted_evidence_types: Annotated[list[AcceptedEvidenceType] | None, Field(min_length=1)] = ( + None + ) + accepted_providers: Annotated[list[brand_key.BrandKey] | None, Field(min_length=1)] = None + excluded_providers: Annotated[list[brand_key.BrandKey] | None, Field(min_length=1)] = None + accepted_subject_types: Annotated[ + list[audience_subject_type.AudienceSubjectType] | None, Field(min_length=1) + ] = None + accepted_resolution_methods: Annotated[ + list[audience_resolution_method.AudienceResolutionMethod] | None, Field(min_length=1) + ] = None + minimum_confidence: Annotated[StrictFloat | None, Field(ge=0.0, le=1.0)] = None + maximum_age: MaximumAge | None = None + methodology_documentation_required: StrictBool | None = False + independent_attestation_required: StrictBool | None = False + accepted_attestation_issuers: Annotated[ + list[AcceptedAttestationIssuers] | None, Field(min_length=1) + ] = None + accepted_attestation_claim_types: Annotated[list[AnyUrl] | None, Field(min_length=1)] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/product_card_reference_asset.py b/src/adcp/types/domains/core/product_card_reference_asset.py index 535fa2f2a..b3586e47e 100644 --- a/src/adcp/types/domains/core/product_card_reference_asset.py +++ b/src/adcp/types/domains/core/product_card_reference_asset.py @@ -1,21 +1,52 @@ -"""Types declared by the AdCP ``core/product_card_reference_asset`` schema. +# generated by datamodel-codegen: +# filename: core/product_card_reference_asset.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.product_card_reference_asset import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from .assets import image_asset, markdown_asset, url_asset, video_asset + + +class Role(StrEnum): + coverage_map = 'coverage_map' + sample_render = 'sample_render' + environment_photo = 'environment_photo' + media_kit = 'media_kit' + logo = 'logo' + other = 'other' -from adcp.types.generated_poc.core.product_card_reference_asset import ( - ProductCardReferenceAsset, - Role, -) -# Explicit exports -__all__ = ["ProductCardReferenceAsset", "Role"] +class ProductCardReferenceAsset(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + role: Annotated[ + Role, + Field( + description='Semantic role of this asset. coverage_map: geographic or audience reach visualization; sample_render: mockup of ad placement in context; environment_photo: photo of the physical or digital environment; media_kit: downloadable media kit or spec sheet; logo: seller or property logo; other: seller-defined role (provide role_label).' + ), + ] + role_label: Annotated[ + str | None, + Field( + description="Human-readable label for the asset role. Required when role is 'other'; optional otherwise." + ), + ] = None + asset: Annotated[ + image_asset.ImageAsset + | video_asset.VideoAsset + | markdown_asset.MarkdownAsset + | url_asset.UrlAsset, + Field( + description='The asset payload, discriminated on asset_type (image, video, markdown, url).' + ), + ] + description: Annotated[ + str | None, Field(description='Optional human-readable context about this asset.') + ] = None diff --git a/src/adcp/types/domains/core/product_change_map.py b/src/adcp/types/domains/core/product_change_map.py index 4e1b22d02..1b6619e8b 100644 --- a/src/adcp/types/domains/core/product_change_map.py +++ b/src/adcp/types/domains/core/product_change_map.py @@ -1,18 +1,26 @@ -"""Types declared by the AdCP ``core/product_change_map`` schema. +# generated by datamodel-codegen: +# filename: core/product_change_map.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.product_change_map import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import Field, RootModel, StringConstraints -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.product_change_map import ProductChangeMap, ProductChangeMap1 +class ProductChangeMap1(StrEnum): + include = 'include' + omit = 'omit' + -# Explicit exports -__all__ = ["ProductChangeMap", "ProductChangeMap1"] +class ProductChangeMap(RootModel[dict[Annotated[str, StringConstraints(min_length=1)], ProductChangeMap1]]): + root: Annotated[ + dict[Annotated[str, StringConstraints(min_length=1)], ProductChangeMap1], + Field( + description='Product IDs mapped to deterministic membership actions. Object keys are product identifiers, so contradictory actions for one product cannot be represented.', + min_length=1, + title='Product Change Map', + ), + ] diff --git a/src/adcp/types/domains/core/product_execution_requirement.py b/src/adcp/types/domains/core/product_execution_requirement.py index 1ef45ebf1..71063d91a 100644 --- a/src/adcp/types/domains/core/product_execution_requirement.py +++ b/src/adcp/types/domains/core/product_execution_requirement.py @@ -1,34 +1,104 @@ -"""Types declared by the AdCP ``core/product_execution_requirement`` schema. +# generated by datamodel-codegen: +# filename: core/product_execution_requirement.json +# timestamp: 2026-10-04T00:53:27+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.product_execution_requirement import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field + +from ..enums import catalog_type, event_type +from . import ext as ext_1 +from .downstream_connection_requirement import DownstreamConnectionRequirement + + +class ProductExecutionRequirement1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + kind: Literal['event_source'] = 'event_source' + event_types: Annotated[ + list[event_type.EventType] | None, + Field( + description='Event types that satisfy the requirement (any-of): the satisfying `event_sources[]` entry MUST use one of these `event_type` values. Omit when any event type satisfies it.', + min_length=1, + ), + ] = None + ext: ext_1.ExtensionObject | None = None + + +class ProductExecutionRequirement2(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + kind: Literal['catalog'] = 'catalog' + catalog_types: Annotated[ + list[catalog_type.CatalogType], + Field( + description='Catalog types that satisfy the requirement (any-of). One catalog of any listed type satisfies it.', + min_length=1, + ), + ] + ext: ext_1.ExtensionObject | None = None + + +class Status(StrEnum): + connected = 'connected' + missing = 'missing' + pending = 'pending' + expired = 'expired' + revoked = 'revoked' + not_required = 'not_required' + unknown = 'unknown' + + +class RequiredForItem(ScalarStr): + __slots__ = () + _constraints = {'min_length': 1} + _json_schema_extra = { + 'examples': ['list_creatives', 'sync_creatives', 'create_media_buy', 'get_media_buy_delivery', 'get_creative_delivery'], + } + + +class Connection(DownstreamConnectionRequirement): + status: Annotated[ + Literal['unknown'], + Field( + description='Current seller-observed state for this downstream connection when known. Product declarations MAY omit status or use `unknown`; AUTHORIZATION_REQUIRED details SHOULD use `missing`, `expired`, or `revoked` for the connection that blocked the call.' + ), + ] = 'unknown' + required_for: Annotated[ + list[RequiredForItem] | None, + Field( + description='Concrete AdCP protocol operation names that require this downstream connection. Sellers SHOULD include this in product declarations when the requirement is known ahead of time, and in AUTHORIZATION_REQUIRED details when it explains the failed operation. Prefer specific operation names such as `list_creatives`, `sync_creatives`, `create_media_buy`, `get_media_buy_delivery`, or `get_creative_delivery` over broad category labels such as `reporting`.' + ), + ] = None + + +class ProductExecutionRequirement3(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + kind: Literal['downstream_connection'] = 'downstream_connection' + connection: Annotated[ + Connection, + Field( + description='The required connection. Account-independent: `status` is omitted or `unknown`; `resource_ref`, `connection_id`, and `expires_at` MUST be omitted; `required_for`, when present, includes `create_media_buy`.' + ), + ] + ext: ext_1.ExtensionObject | None = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.product_execution_requirement import ( - Connection, - ProductExecutionRequirement, - ProductExecutionRequirement1, - ProductExecutionRequirement2, - ProductExecutionRequirement3, - RequiredForItem, - Status, -) - -# Explicit exports -__all__ = [ - "Connection", - "ProductExecutionRequirement", - "ProductExecutionRequirement1", - "ProductExecutionRequirement2", - "ProductExecutionRequirement3", - "RequiredForItem", - "Status", +ProductExecutionRequirement = Annotated[ + ProductExecutionRequirement1 | ProductExecutionRequirement2 | ProductExecutionRequirement3, + Field( + description='Experimental. One account resource a package on the product needs before it can be created. `kind` determines the binding: `event_source` via `packages[].optimization_goals[].event_sources[]`, `catalog` via `packages[].catalogs[]`, `downstream_connection` by completing the connection. Account-independent: MUST NOT carry account-specific resource IDs or names. Alternatives inside one requirement are any-of.', + discriminator='kind', + examples=[{'kind': 'catalog', 'catalog_types': ['app']}], + title='Product Execution Requirement', + ), ] diff --git a/src/adcp/types/domains/core/product_filters.py b/src/adcp/types/domains/core/product_filters.py index 657e23b84..b10a9860f 100644 --- a/src/adcp/types/domains/core/product_filters.py +++ b/src/adcp/types/domains/core/product_filters.py @@ -1,86 +1,701 @@ -"""Types declared by the AdCP ``core/product_filters`` schema. +# generated by datamodel-codegen: +# filename: core/product_filters.json +# timestamp: 2026-10-05T13:49:50+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.product_filters import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from datetime import date +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any, Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel, SchemaInt +from adcp.types._scalar import ScalarStr +from pydantic import AnyUrl, ConfigDict, Field, StrictBool, StrictFloat -from adcp.types.generated_poc.core.product_filters import ( - AudienceActivationMethods, - AudienceActivationMethods1, - AudienceActivationMethods2, - AudienceActivationMethods3, - AudienceActivationMethods4, - AudienceActivationMethods5, - BudgetRange, - BuyerAgent, - Country, - Direction, - GeoProximityItem, - Geometry, - Keyword, - Metro, - PricingCurrency, - ProductFilters, - Provider, - Radius, - Region, - RequiredGeoTargetingItem, - RequiredVendorMetric, - SignalTargetingItem, - SignalTargetingItem1, - SignalTargetingItem2, - SignalTargetingItem3, - SignalTargetingItem4, - SignalTargetingItem5, - SignalTargetingItem6, - SignalTargetingItem7, - TargetingMode, - TravelTime, - TrustedMatch, - Type, +from ..enums import audio_distribution_type, available_metric +from ..enums import channels as channels_1 +from ..enums import cloud_storage_protocol +from ..enums import delivery_type as delivery_type_1 +from ..enums import distance_unit +from ..enums import exclusivity as exclusivity_1 +from ..enums import geo_level +from ..enums import match_type as match_type_1 +from ..enums import ( + metro_system, + pricing_structure, + response_type, + social_placement_surface, + sponsored_placement_type, ) +from ..enums import transport_mode as transport_mode_1 +from ..enums import travel_time_unit, video_placement_type +from . import audience_evidence_requirements as audience_evidence_requirements_1 +from . import brand_ref +from . import ext as ext_1 +from . import format_id, format_option_ref, media_buy_features, performance_standard, postal_area +from . import signal_id as signal_id_1 +from . import signal_ref as signal_ref_1 +from . import vendor_metric_id + + +class PricingCurrency(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{3}$'} + _json_schema_extra = {'description': "ISO 4217 currency code (e.g., 'USD', 'EUR', 'GBP')"} + + +class BudgetRange(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + min: Annotated[StrictFloat | None, Field(description='Minimum budget amount', ge=0.0)] = None + max: Annotated[StrictFloat | None, Field(description='Maximum budget amount', ge=0.0)] = None + currency: Annotated[ + str, + Field( + description="ISO 4217 currency code (e.g., 'USD', 'EUR', 'GBP')", pattern='^[A-Z]{3}$' + ), + ] + + +class Country(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{2}$'} + + +class Region(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{2}-[A-Z0-9]+$'} + + +class Metro(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + system: Annotated[ + metro_system.MetroAreaSystem, Field(description='Metro classification system') + ] + code: Annotated[ + str, Field(description="Metro code within the system (e.g., '501' for NYC DMA)") + ] + + +class Provider(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + agent_url: Annotated[AnyUrl, Field(description="Provider's agent URL from the registry.")] + context_match: Annotated[ + StrictBool | None, + Field(description='When true, require this provider to support context match.'), + ] = None + identity_match: Annotated[ + StrictBool | None, + Field(description='When true, require this provider to support identity match.'), + ] = None + + +class AudienceActivationMethods(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + pattern: Literal['sync_audiences'] = 'sync_audiences' + + +class BuyerAgent(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + agent_url: AnyUrl + + +class AudienceActivationMethods1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + pattern: Literal['tmp_identity_match'] = 'tmp_identity_match' + buyer_agent: Annotated[ + BuyerAgent | None, + Field( + description='Require this buyer agent on tmp_identity_match entries. Buyers typically filter on their own agent_url.' + ), + ] = None + + +class Direction(StrEnum): + buyer_to_seller = 'buyer_to_seller' + seller_to_buyer = 'seller_to_buyer' + + +class RequiredGeoTargetingItem(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + level: Annotated[ + geo_level.GeographicTargetingLevel, + Field(description='Geographic targeting level (country, region, metro, postal_area)'), + ] + country: Annotated[ + str | None, + Field( + description='ISO 3166-1 alpha-2 country code. Required for native postal_area system filters; not applicable to country, region, or metro filters.', + pattern='^[A-Z]{2}$', + ), + ] = None + system: Annotated[ + str | None, + Field( + description="Optional classification system within the level. Use for a specific metro system (e.g., 'nielsen_dma'), native postal_area system (e.g., 'zip' with country 'US'), or deprecated legacy postal alias (e.g., 'us_zip'). Not applicable for country/region which use ISO standards." + ), + ] = None + + +class TargetingMode(StrEnum): + include = 'include' + exclude = 'exclude' + + +class SignalTargetingItem4(AdCPBaseModel): + targeting_mode: Annotated[ + TargetingMode | None, + Field( + description="Desired use of this signal on the product. 'include' requires the product option to allow use in an any group. 'exclude' requires the product option to allow use in a none group." + ), + ] = TargetingMode.include + + +class Radius(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + value: Annotated[StrictFloat, Field(description='Radius distance', gt=0.0)] + unit: Annotated[distance_unit.DistanceUnit, Field(description='Distance unit')] + + +class Type(StrEnum): + Polygon = 'Polygon' + MultiPolygon = 'MultiPolygon' + + +class Geometry(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Annotated[Type, Field(description='GeoJSON geometry type')] + coordinates: Annotated[list[Any], Field(description='GeoJSON coordinates array')] + + +class Keyword(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + keyword: Annotated[str, Field(description='The keyword to target', min_length=1)] + match_type: match_type_1.MatchType | None = match_type_1.MatchType.broad + + +class TrustedMatch(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + providers: Annotated[ + list[Provider] | None, + Field( + description='Filter to products with specific TMP providers and match types. Each entry identifies a provider by agent_url and optionally requires specific match capabilities. Products must match at least one entry.', + min_length=1, + ), + ] = None + response_types: Annotated[ + list[response_type.TmpResponseType] | None, + Field( + description="Filter to products supporting specific TMP response types (e.g., 'activation', 'creative', 'catalog_items'). Products must support at least one of the listed types.", + min_length=1, + ), + ] = None + + +class SignalTargetingItem1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + signal_ref: Annotated[ + signal_ref_1.SignalRef | None, + Field(description='The signal to target. New targeting constraints SHOULD use signal_ref.'), + ] = None + signal_id: Annotated[ + signal_id_1.SignalId | None, + Field( + deprecated=True, + description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', + ), + ] = None + value_type: Annotated[Literal['binary'], Field(description='Discriminator for binary signals')] = 'binary' + value: Annotated[ + StrictBool, + Field( + description='Whether to include (true) or exclude (false) users matching this signal' + ), + ] + + +class SignalTargetingItem2(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + signal_ref: Annotated[ + signal_ref_1.SignalRef | None, + Field(description='The signal to target. New targeting constraints SHOULD use signal_ref.'), + ] = None + signal_id: Annotated[ + signal_id_1.SignalId | None, + Field( + deprecated=True, + description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', + ), + ] = None + value_type: Annotated[ + Literal['categorical'], Field(description='Discriminator for categorical signals') + ] = 'categorical' + values: Annotated[ + list[str], + Field( + description='Values to target. Users with any of these values will be included.', + min_length=1, + ), + ] + + +class SignalTargetingItem3(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + signal_ref: Annotated[ + signal_ref_1.SignalRef | None, + Field(description='The signal to target. New targeting constraints SHOULD use signal_ref.'), + ] = None + signal_id: Annotated[ + signal_id_1.SignalId | None, + Field( + deprecated=True, + description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', + ), + ] = None + value_type: Annotated[ + Literal['numeric'], Field(description='Discriminator for numeric signals') + ] = 'numeric' + min_value: Annotated[ + StrictFloat | None, + Field( + description="Minimum value (inclusive). Omit for no minimum. Must be <= max_value when both are provided. Should be >= signal's range.min if defined." + ), + ] = None + max_value: Annotated[ + StrictFloat | None, + Field( + description="Maximum value (inclusive). Omit for no maximum. Must be >= min_value when both are provided. Should be <= signal's range.max if defined." + ), + ] = None + + +class SignalTargetingItem5(SignalTargetingItem1, SignalTargetingItem4): + targeting_mode: Annotated[ + TargetingMode | None, + Field( + description="Desired use of this signal on the product. 'include' requires the product option to allow use in an any group. 'exclude' requires the product option to allow use in a none group." + ), + ] = TargetingMode.include + + +class SignalTargetingItem6(SignalTargetingItem2, SignalTargetingItem4): + targeting_mode: Annotated[ + TargetingMode | None, + Field( + description="Desired use of this signal on the product. 'include' requires the product option to allow use in an any group. 'exclude' requires the product option to allow use in a none group." + ), + ] = TargetingMode.include + + +class SignalTargetingItem7(SignalTargetingItem3, SignalTargetingItem4): + targeting_mode: Annotated[ + TargetingMode | None, + Field( + description="Desired use of this signal on the product. 'include' requires the product option to allow use in an any group. 'exclude' requires the product option to allow use in a none group." + ), + ] = TargetingMode.include + + +SignalTargetingItem = SignalTargetingItem5 | SignalTargetingItem6 | SignalTargetingItem7 + + +class TravelTime(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + value: Annotated[StrictFloat, Field(description='Travel time limit', ge=1.0)] + unit: travel_time_unit.TravelTimeUnit + + +class GeoProximityItem(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + lat: Annotated[ + StrictFloat | None, + Field(description='Latitude in decimal degrees (WGS 84)', ge=-90.0, le=90.0), + ] = None + lng: Annotated[ + StrictFloat | None, + Field(description='Longitude in decimal degrees (WGS 84)', ge=-180.0, le=180.0), + ] = None + label: Annotated[ + str | None, + Field(description="Human-readable label (e.g., 'Düsseldorf', 'Heathrow Airport')"), + ] = None + travel_time: Annotated[ + TravelTime | None, Field(description='Travel time limit for isochrone calculation') + ] = None + transport_mode: Annotated[ + transport_mode_1.TransportMode | None, + Field( + description='Transportation mode for isochrone calculation. Required when travel_time is provided.' + ), + ] = None + radius: Annotated[Radius | None, Field(description='Simple radius from the point')] = None + geometry: Annotated[ + Geometry | None, + Field(description='Pre-computed GeoJSON geometry defining the proximity boundary'), + ] = None + + +class AudienceActivationMethods2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + pattern: Literal['file_transfer'] = 'file_transfer' + transport: Annotated[ + cloud_storage_protocol.CloudStorageProtocol | None, + Field(description='Require this storage protocol (file_transfer only).'), + ] = None + directions: Annotated[ + list[Direction] | None, + Field( + description="Require a non-empty intersection with the method's declared directions (file_transfer only).", + min_length=1, + ), + ] = None + vendor: Annotated[ + brand_ref.BrandReference | None, + Field(description="Require the method's vendor to match this reference."), + ] = None + + +class AudienceActivationMethods3(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + pattern: Literal['dataset_query'] = 'dataset_query' + vendor: Annotated[ + brand_ref.BrandReference | None, + Field(description="Require the method's vendor to match this reference."), + ] = None + + +class AudienceActivationMethods4(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + pattern: Literal['clean_room'] = 'clean_room' + vendor: Annotated[ + brand_ref.BrandReference | None, + Field(description="Require the method's vendor to match this reference."), + ] = None + + +class AudienceActivationMethods5(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + pattern: Literal['platform_distribution'] = 'platform_distribution' + vendor: Annotated[ + brand_ref.BrandReference | None, + Field(description="Require the method's vendor to match this reference."), + ] = None + + +class RequiredVendorMetric(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + vendor: Annotated[ + brand_ref.BrandReference | None, + Field( + description='Pin to a specific vendor. Optional — omit to match any vendor offering this `metric_id`.' + ), + ] = None + metric_id: Annotated[ + vendor_metric_id.VendorMetricId | None, + Field( + description="Pin to an exact metric identifier within a vendor's vocabulary. Optional — omit to match any metric from the pinned vendor. Cross-vendor identifier matching only works after vendors converge on identifiers (typically post-standardization)." + ), + ] = None + -# Explicit exports -__all__ = [ - "AudienceActivationMethods", - "AudienceActivationMethods1", - "AudienceActivationMethods2", - "AudienceActivationMethods3", - "AudienceActivationMethods4", - "AudienceActivationMethods5", - "BudgetRange", - "BuyerAgent", - "Country", - "Direction", - "GeoProximityItem", - "Geometry", - "Keyword", - "Metro", - "PricingCurrency", - "ProductFilters", - "Provider", - "Radius", - "Region", - "RequiredGeoTargetingItem", - "RequiredVendorMetric", - "SignalTargetingItem", - "SignalTargetingItem1", - "SignalTargetingItem2", - "SignalTargetingItem3", - "SignalTargetingItem4", - "SignalTargetingItem5", - "SignalTargetingItem6", - "SignalTargetingItem7", - "TargetingMode", - "TravelTime", - "TrustedMatch", - "Type", -] +class ProductFilters(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + delivery_type: delivery_type_1.DeliveryType | None = None + exclusivity: Annotated[ + exclusivity_1.Exclusivity | None, + Field( + description="Filter by exclusivity level. Returns products matching the specified exclusivity (e.g., 'exclusive' returns only sole-sponsorship products)." + ), + ] = None + is_fixed_price: Annotated[ + StrictBool | None, + Field( + description='Legacy filter for fixed versus auction pricing availability. true returns options with fixed_price; false returns auction options whose price is established through bid_price. Contingent options such as revenue_share match neither value and MUST be omitted whenever this filter is present. Use pricing_structures to discover contingent pricing. Products with both fixed and auction options match both true and false, but sellers MUST return only entries matching the requested structure.' + ), + ] = None + pricing_structures: Annotated[ + list[pricing_structure.PricingStructure] | None, + Field( + description='Filter by how the payable price is determined. fixed selects options with fixed_price, auction selects options established through bid_price, and contingent selects options calculated from a measured business outcome after delivery (currently revenue_share). Products match when at least one pricing option has a requested structure. Sellers MUST return only matching pricing_options entries. When combined with is_fixed_price, both filters apply and the returned entries must satisfy both.', + min_length=1, + ), + ] = None + pricing_currencies: Annotated[ + list[PricingCurrency] | None, + Field( + description='Filter by currencies the buyer can use for the media product transaction, using ISO 4217 currency codes. Products match when they offer at least one product-level pricing_options entry in one of the requested currencies and any seller-applied or otherwise mandatory product-scoped signal charges are satisfiable in one of those currencies or have no incremental price. Mandatory custom signal pricing without currency is not satisfiable for this filter unless the seller can truthfully treat it as having no incremental price. Sellers MUST return only product pricing_options entries whose currency is in this list so buyers can select deterministically from discovery. This filter does not require pruning optional signal or vendor add-on pricing; buyers should avoid optional add-ons priced only in unsupported currencies.', + min_length=1, + ), + ] = None + format_ids: Annotated[ + list[format_id.FormatReferenceStructuredObject] | None, + Field( + deprecated=True, + description='Deprecated in AdCP 3.2; removed in AdCP 4.0. Filter by legacy named-format references. Use `format_kinds` or `format_option_refs`.', + min_length=1, + ), + ] = None + format_kinds: Annotated[ + list[str] | None, + Field( + description='Filter to products accepting any of these canonical format kinds.', + min_length=1, + ), + ] = None + format_option_refs: Annotated[ + list[format_option_ref.FormatOptionReference] | None, + Field( + description='Filter to products accepting any of these exact publisher- or product-scoped canonical format options.', + min_length=1, + ), + ] = None + standard_formats_only: Annotated[ + StrictBool | None, Field(description='Only return products accepting IAB standard formats') + ] = None + min_exposures: Annotated[ + SchemaInt | None, + Field(description='Minimum exposures/impressions needed for measurement validity', ge=1), + ] = None + start_date: Annotated[ + date | None, + Field( + description='Campaign start date (ISO 8601 date format: YYYY-MM-DD) for availability checks' + ), + ] = None + end_date: Annotated[ + date | None, + Field( + description='Campaign end date (ISO 8601 date format: YYYY-MM-DD) for availability checks' + ), + ] = None + budget_range: Annotated[ + BudgetRange | None, Field(description='Budget range to filter appropriate products') + ] = None + countries: Annotated[ + list[Country] | None, + Field( + deprecated=True, + description='DEPRECATED legacy coverage filter. On compact discovery tasks use criteria.offer_filters.countries. Return products whose inventory covers at least one requested area; this does not impose delivery targeting or require selectable targeting support. Retained get_products handlers MUST preserve the original coverage predicate.', + min_length=1, + ), + ] = None + regions: Annotated[ + list[Region] | None, + Field( + deprecated=True, + description='DEPRECATED legacy coverage filter. On compact discovery tasks use criteria.offer_filters.regions. Return products whose inventory covers at least one requested area; this does not impose delivery targeting or require selectable targeting support. Retained get_products handlers MUST preserve the original coverage predicate.', + min_length=1, + ), + ] = None + metros: Annotated[ + list[Metro] | None, + Field( + deprecated=True, + description='DEPRECATED legacy coverage filter. On compact discovery tasks use criteria.offer_filters.metros. Return products whose inventory covers at least one requested area; this does not impose delivery targeting or require selectable targeting support. Retained get_products handlers MUST preserve the original coverage predicate.', + min_length=1, + ), + ] = None + channels: Annotated[ + list[channels_1.MediaChannel] | None, + Field( + description="Filter by advertising channels (e.g., ['display', 'ctv', 'dooh'])", + min_length=1, + ), + ] = None + video_placement_types: Annotated[ + list[video_placement_type.VideoPlacementType] | None, + Field( + description='Filter product metadata by declared video placement types, using IAB Tech Lab/OpenRTB 2.6 video.plcmt definitions with AdCP-native names. A product matches when its declared array intersects the requested array. This is discovery classification only and does not promise delivery exclusively on a requested type; buyers needing exact placement inventory use targeting_overlay.placement_selection against targetable placements. This filter has set semantics for wholesale feed canonicalization.', + min_length=1, + ), + ] = None + audio_distribution_types: Annotated[ + list[audio_distribution_type.AudioDistributionType] | None, + Field( + description='Filter product metadata by declared audio distribution types, using IAB Tech Lab/OpenRTB 2.6 audio.feed definitions with AdCP-native names. A product matches when its declared array intersects the requested array. This is discovery classification only and does not promise delivery exclusively on a requested type. This filter has set semantics for wholesale feed canonicalization.', + min_length=1, + ), + ] = None + sponsored_placement_types: Annotated[ + list[sponsored_placement_type.SponsoredPlacementType] | None, + Field( + description='Filter retail-media product metadata by declared sponsored-placement types (sponsored search, sponsored display, or sponsored native). A product matches when its declared array intersects the requested array. This is discovery classification only and does not promise delivery exclusively on a requested type. This filter has set semantics for wholesale feed canonicalization.', + min_length=1, + ), + ] = None + social_placement_surfaces: Annotated[ + list[social_placement_surface.SocialPlacementSurface] | None, + Field( + description='Filter social-product metadata by declared placement surfaces (feed, stories, short_video, explore, or search). A product matches when its declared array intersects the requested array. This is discovery classification only and does not promise delivery exclusively on a requested surface; buyers needing an exact public placement use targeting_overlay.placement_selection. This filter has set semantics for wholesale feed canonicalization.', + min_length=1, + ), + ] = None + required_axe_integrations: Annotated[ + list[AnyUrl] | None, + Field( + deprecated=True, + description='Deprecated: Use trusted_match filter instead. Filter to products executable through specific agentic ad exchanges. URLs are canonical identifiers.', + min_length=1, + ), + ] = None + trusted_match: Annotated[ + TrustedMatch | None, + Field( + description='Filter products by Trusted Match Protocol capabilities. Only products with matching TMP support are returned.' + ), + ] = None + audience_activation_methods: Annotated[ + list[ + AudienceActivationMethods + | AudienceActivationMethods1 + | AudienceActivationMethods2 + | AudienceActivationMethods3 + | AudienceActivationMethods4 + | AudienceActivationMethods5 + ] + | None, + Field( + description="Filter to products whose audience_activation.methods matches at least one requested entry (OR across entries). Within an entry every specified field must match (AND); omitted optional fields are wildcards; directions matches on non-empty intersection, and a method that omits directions matches any requested directions; vendor matches on domain, plus brand_id when specified. Fields inapplicable to the requested pattern (e.g. transport on a clean_room entry) make that entry unsatisfiable. Sellers MUST exclude products with no audience_activation declaration when this filter is present; exclusions MAY be reported via filter_diagnostics.excluded_by. Experimental: part of the media_buy.audience_activation surface — buyers SHOULD check the seller's experimental_features before filtering on it; sellers that do not list the feature ignore this filter.", + min_length=1, + ), + ] = None + required_features: Annotated[ + media_buy_features.MediaBuyFeatures | None, + Field( + description='Filter to products from sellers supporting specific protocol features. Only features set to true are used for filtering.' + ), + ] = None + required_geo_targeting: Annotated[ + list[RequiredGeoTargetingItem] | None, + Field( + deprecated=True, + description='DEPRECATED. Use get_products.required_overlay_support, which is product-scoped and applies consistently to geographic and non-geographic targeting dimensions.', + min_length=1, + ), + ] = None + signal_targeting: Annotated[ + list[SignalTargetingItem] | None, + Field( + deprecated=True, + description='DEPRECATED legacy signal-option eligibility filter. Retained get_products handlers MUST preserve the signal identity, value predicate, and requested include/exclude capability. This does not activate delivery targeting. Native buyers use criteria.targeting_overlay.signal_targeting_groups only for concrete delivery selections, preserving exclusion through group operators; copying targeting_mode into targeting_overlay.signal_targeting does not preserve it.', + min_length=1, + ), + ] = None + postal_areas: Annotated[ + list[postal_area.PostalArea] | None, + Field( + deprecated=True, + description='DEPRECATED legacy coverage filter. On compact discovery tasks use criteria.offer_filters.postal_areas. Return products whose inventory covers at least one requested area; this does not impose delivery targeting or require selectable targeting support. Retained get_products handlers MUST preserve the original coverage predicate.', + min_length=1, + ), + ] = None + geo_proximity: Annotated[ + list[GeoProximityItem] | None, + Field( + deprecated=True, + description='DEPRECATED legacy coverage filter. On compact discovery tasks use criteria.offer_filters.geo_proximity. Return products whose inventory covers at least one requested area; this does not impose delivery targeting or require selectable targeting support. Retained get_products handlers MUST preserve the original coverage predicate.', + min_length=1, + ), + ] = None + required_performance_standards: Annotated[ + list[performance_standard.PerformanceStandard] | None, + Field( + description="Filter to products that can meet the buyer's performance standard requirements. Each entry specifies a metric, minimum threshold, and optionally a required vendor and standard. Products that cannot meet these thresholds or do not support the specified vendors are excluded. Use this to tell the seller upfront: 'I need DoubleVerify for viewability at 70% MRC.'", + min_length=1, + ), + ] = None + required_metrics: Annotated[ + list[available_metric.AvailableMetric] | None, + Field( + description="Filter to products whose `reporting_capabilities.available_metrics` is a superset of these metrics — i.e., products that commit to reporting all listed metrics in delivery responses. Use this for capability-level discovery (e.g., 'I need products that report `completed_views` for a CTV CPCV buy'); guarantee-level requirements with thresholds belong in `required_performance_standards` and `measurement_terms`. Sellers MUST silently exclude products that cannot meet this list (filter-not-fail; do not return an error). Under the container-subsumption rule in `enums/available-metric.json`, `viewability` satisfies numeric leaves such as `viewable_rate`; structured distributions require explicit `viewed_seconds_percentiles` or `viewed_seconds_histogram` declarations. The product's declared `available_metrics` becomes the binding reporting contract carried into the resulting media buy — the same metric vocabulary is used to compute `missing_metrics` on `get_media_buy_delivery`.", + examples=[ + ['completed_views'], + ['completed_views', 'completion_rate'], + ['impressions', 'spend', 'engagements'], + ], + min_length=1, + ), + ] = None + required_vendor_metrics: Annotated[ + list[RequiredVendorMetric] | None, + Field( + description="Filter to products whose `reporting_capabilities.vendor_metrics` matches these criteria. Each entry pins a `vendor` (matches any metric from that vendor), a `metric_id` (matches the metric across any vendor that uses that identifier), or both (specific vendor's specific metric). A product matches if its declared `vendor_metrics` covers ALL listed entries (AND across entries; pins within an entry are conjunctive). Cross-vendor discovery (e.g., 'I need attention measurement from any vendor that does it') is the buyer agent's responsibility — the agent resolves which vendors offer a category via the vendors' `brand.json` records, then enumerates them as filter entries. AdCP does not carry vendor-side metric metadata (category, methodology, standard alignment) in the filter surface; that lives at the vendor and is queried out-of-band. Sellers MUST silently exclude non-matching products (filter-not-fail; do not return an error) — same convention as the other `required_*` filters.", + examples=[ + [{'vendor': {'domain': 'attentionvendor.example'}}], + [ + { + 'vendor': {'domain': 'panelmeasurement.example'}, + 'metric_id': 'demographic_reach', + } + ], + [ + {'vendor': {'domain': 'attentionvendor.example'}}, + {'vendor': {'domain': 'secondattentionvendor.example'}}, + ], + ], + min_length=1, + ), + ] = None + keywords: Annotated[ + list[Keyword] | None, + Field( + deprecated=True, + description='DEPRECATED legacy product eligibility filter. Retained get_products handlers MUST preserve the requested keyword eligibility and match_type (default broad). This is not an instruction to add package keyword targeting. Native buyers use criteria.targeting_overlay.keyword_targets only when they intend a delivery constraint, or criteria.required_overlay_support.keyword_targets for future selectability.', + min_length=1, + ), + ] = None + audience_evidence_requirements: Annotated[ + audience_evidence_requirements_1.AudienceEvidenceRequirements | None, + Field( + description='Buyer policy for evaluating Product.audience_evidence. In required mode, sellers MUST apply the evidence_presence and admissibility semantics and exclude non-matching products; they MUST NOT ignore an unsupported hard requirement. In preferred mode, sellers use matches for ranking and explain the evidence selected. Buyers SHOULD inspect media_buy.audience_evidence capabilities before sending this object.' + ), + ] = None + ext: Annotated[ + ext_1.ExtensionObject | None, + Field( + description='Vendor-namespaced extension parameters for seller-specific filter criteria not covered by standard fields. Keys MUST be namespaced under a vendor or platform key (e.g., ext.gam, ext.platform_x). Sellers MUST treat all values as untrusted buyer input; do not interpolate into LLM prompts, SQL queries, or system commands without sanitization. Persistent use of an extension key across multiple buyers is a signal to propose standardization.' + ), + ] = None diff --git a/src/adcp/types/domains/core/product_format_declaration.py b/src/adcp/types/domains/core/product_format_declaration.py index 1cec054cc..7f0f65645 100644 --- a/src/adcp/types/domains/core/product_format_declaration.py +++ b/src/adcp/types/domains/core/product_format_declaration.py @@ -1,56 +1,1750 @@ -"""Types declared by the AdCP ``core/product_format_declaration`` schema. +# generated by datamodel-codegen: +# filename: core/product_format_declaration.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.product_format_declaration import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any, Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, Field, StrictBool -from adcp.types.generated_poc.core.product_format_declaration import ( - ProductFormatDeclaration, - ProductFormatDeclaration1, - ProductFormatDeclaration10, - ProductFormatDeclaration11, - ProductFormatDeclaration12, - ProductFormatDeclaration13, - ProductFormatDeclaration14, - ProductFormatDeclaration15, - ProductFormatDeclaration16, - ProductFormatDeclaration2, - ProductFormatDeclaration3, - ProductFormatDeclaration4, - ProductFormatDeclaration5, - ProductFormatDeclaration6, - ProductFormatDeclaration7, - ProductFormatDeclaration8, - ProductFormatDeclaration9, - SellerPreference, +from ..enums import channels +from ..formats.canonical import ( + agent_placement, + audio_daast, + audio_hosted, + audio_vast, + coordinated_placements, + display_tag, + html5, + image, + image_carousel, + native_in_feed, + responsive_creative, + seller_rendered_stateful_display, + sponsored_placement, + video_hosted, + video_vast, ) +from . import creative_locale_policy, format_id, macro_resolution_capability, platform_extension_ref +from . import tracker_execution_contract as tracker_execution_contract_1 + + +class SellerPreference(StrEnum): + preferred = 'preferred' + accepted = 'accepted' + discouraged = 'discouraged' + + +class ProductFormatDeclaration1(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['image'] = 'image' + params: image.CanonicalFormatImage + + +class ProductFormatDeclaration2(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['html5'] = 'html5' + params: html5.CanonicalFormatHtml5Banner + + +class ProductFormatDeclaration3(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['display_tag'] = 'display_tag' + params: display_tag.CanonicalFormatDisplayTag + + +class ProductFormatDeclaration4(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['image_carousel'] = 'image_carousel' + params: image_carousel.CanonicalFormatImageCarousel + + +class ProductFormatDeclaration5(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['video_hosted'] = 'video_hosted' + params: video_hosted.CanonicalFormatHostedVideo + + +class ProductFormatDeclaration6(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['video_vast'] = 'video_vast' + params: video_vast.CanonicalFormatVastVideo + + +class ProductFormatDeclaration7(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['audio_hosted'] = 'audio_hosted' + params: audio_hosted.CanonicalFormatHostedAudio + + +class ProductFormatDeclaration8(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['audio_vast'] = 'audio_vast' + params: audio_vast.CanonicalFormatVastAudio + + +class ProductFormatDeclaration9(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['audio_daast'] = 'audio_daast' + params: audio_daast.CanonicalFormatDaastAudio + + +class ProductFormatDeclaration10(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['sponsored_placement'] = 'sponsored_placement' + params: sponsored_placement.CanonicalFormatSponsoredPlacementRetailMediaCatalogDriven + + +class ProductFormatDeclaration11(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['native_in_feed'] = 'native_in_feed' + params: native_in_feed.CanonicalFormatNativeInFeed + + +class ProductFormatDeclaration12(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['responsive_creative'] = 'responsive_creative' + params: responsive_creative.CanonicalFormatResponsiveCreative + + +class ProductFormatDeclaration13(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['agent_placement'] = 'agent_placement' + params: agent_placement.CanonicalFormatAgentPlacementAiSurfaceSponsoredPlacement + + +class ProductFormatDeclaration14(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['seller_rendered_stateful_display'] = 'seller_rendered_stateful_display' + params: seller_rendered_stateful_display.CanonicalFormatSellerRenderedStatefulDisplay + + +class ProductFormatDeclaration15(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['coordinated_placements'] = 'coordinated_placements' + params: coordinated_placements.CanonicalFormatCoordinatedPlacements + + +class ProductFormatDeclaration16(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['custom'] = 'custom' + params: Annotated[ + dict[str, Any], + Field( + description="Custom shape's params. Validated against the schema fetched from `format_schema.uri` at the cached `format_schema.digest`." + ), + ] + -# Explicit exports -__all__ = [ - "ProductFormatDeclaration", - "ProductFormatDeclaration1", - "ProductFormatDeclaration10", - "ProductFormatDeclaration11", - "ProductFormatDeclaration12", - "ProductFormatDeclaration13", - "ProductFormatDeclaration14", - "ProductFormatDeclaration15", - "ProductFormatDeclaration16", - "ProductFormatDeclaration2", - "ProductFormatDeclaration3", - "ProductFormatDeclaration4", - "ProductFormatDeclaration5", - "ProductFormatDeclaration6", - "ProductFormatDeclaration7", - "ProductFormatDeclaration8", - "ProductFormatDeclaration9", - "SellerPreference", +ProductFormatDeclaration = Annotated[ + ProductFormatDeclaration1 + | ProductFormatDeclaration2 + | ProductFormatDeclaration3 + | ProductFormatDeclaration4 + | ProductFormatDeclaration5 + | ProductFormatDeclaration6 + | ProductFormatDeclaration7 + | ProductFormatDeclaration8 + | ProductFormatDeclaration9 + | ProductFormatDeclaration10 + | ProductFormatDeclaration11 + | ProductFormatDeclaration12 + | ProductFormatDeclaration13 + | ProductFormatDeclaration14 + | ProductFormatDeclaration15 + | ProductFormatDeclaration16, + Field( + description='Inline format declaration on a product. The `format_kind` discriminator names which canonical format the product narrows; `params` carries only the canonical creative-shape parameters (slots, dimensions, durations, codecs, character limits, platform_extensions, etc.). Processing metadata such as `macro_resolution_capabilities` belongs at this declaration\'s top level rather than inside `params`. Optional `format_option_id` (stable identifier for routing when a product\'s `format_options` contains multiple declarations sharing the same `format_kind`), optional `publisher_domain` (namespace for the format option when it comes from a publisher adagents.json catalog), `display_name` (seller-controlled human-readable label for dashboard and catalog UIs), and `applies_to_channels` (subset of the product\'s declared channels this declaration applies to — lets a multi-channel product carry distinct format_options per channel). Discriminated-union shape generates clean tagged unions in TypeScript and Pydantic codegen. Replaces v1\'s named-format pattern (where products referenced a separately-defined format file via compound `format_id`). Canonical product-bound declarations are the AdCP 3.2 authoring path; v1 named formats remain only as deprecated 3.x compatibility projections.\n\n**Closed-set semantics (normative).** `format_options[]` is the closed set of accepted formats for this product. Sellers MUST reject `create_media_buy` requests targeting any `format_kind` (or format option reference) not present in this list — typically with `UNSUPPORTED_FEATURE` or a seller-specific code; the rejection is structural, not negotiable. `seller_preference` modulates *within* the accepted set (a soft ranking hint between equally-acceptable options), it is NOT an enforcement axis. A product wanting to say \'this format is the only one that works\' lists exactly that one entry in `format_options[]`; everything else falls outside the set and is rejected by the closed-set rule.\n\n**Format matching vs satisfaction (normative).** Legacy named formats MUST be normalized to canonical declarations before comparison; do not exact-match raw `(agent_url, id)` pairs once a `format_id` has been projected through `canonical`, `v1_format_ref`, or the canonical mapping registry. Equivalence matching can treat a legacy fixed-size display ID and `format_kind: "image"` with matching `width`/`height` as the same underlying shape. Product satisfaction is stricter and directional: when this declaration specifies fixed constraints such as `width`, `height`, `duration_ms_exact`, or `duration_ms_range`, a buyer request or creative manifest MUST declare and satisfy those constraints. A broad request with no dimensions or duration does not satisfy a fixed-size or fixed-duration product; a broad product MAY accept a more specific creative unless another product constraint excludes it. Duration precedence is `duration_ms_exact` > `duration_ms_range`. Range constraints use containment: a range-based request satisfies this declaration only when every value it permits falls within this declaration\'s accepted range; overlap alone is insufficient. An exact value satisfies a range when the exact value falls inside the accepted interval. For hosted audio/video, a null range endpoint is unbounded: [null, 60000] means up to 60s, and [15000, null] means at least 15s; [null, null] is invalid because at least one endpoint must be bounded.\n\n**Custom format_kind** (`format_kind: "custom"`): for adopter-defined shapes that don\'t fit the 15 canonicals (branded content, cross-screen sponsorship, sponsorship lockup, newsletter sponsorship, AR lens, playable, live event sponsorship). When `format_kind` is `custom`, the declaration MUST carry `format_shape` (recognized global pattern from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json)) AND `format_schema` (URI+digest reference to a fetchable schema describing the actual `params` and `slots`). Buyer agents fetch the schema, validate manifests structurally, and reason about manifests without per-seller integration code. See [adcp#3666](https://github.com/adcontextprotocol/adcp/issues/3666) for the canonical promotion queue.', + discriminator='format_kind', + examples=[ + { + 'description': 'Meta Reels — narrows video_hosted (vertical orientation)', + 'data': { + 'format_kind': 'video_hosted', + 'params': { + 'orientation': 'vertical', + 'aspect_ratio': '9:16', + 'duration_ms_range': [3000, 90000], + 'min_width': 1080, + 'min_height': 1920, + 'max_file_size_mb': 200, + 'video_codecs': ['h264'], + 'audio_codecs': ['aac'], + 'headline_max_chars': 25, + 'primary_text_max_chars': 72, + 'captions': 'recommended', + 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'DOWNLOAD', 'SIGN_UP'], + 'composition_model': 'deterministic', + 'platform_extensions': [ + { + 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', + 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', + } + ], + }, + }, + }, + { + 'description': 'IAB Medium Rectangle (300x250) — narrows image', + 'data': { + 'format_kind': 'image', + 'params': { + 'width': 300, + 'height': 250, + 'max_file_size_kb': 200, + 'image_formats': ['jpg', 'png', 'gif'], + 'ssl_required': True, + 'composition_model': 'deterministic', + 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'GET_OFFER'], + }, + }, + }, + { + 'description': 'Québec French-only display image — same image shape with a seller-enforced creative locale constraint', + 'data': { + 'format_kind': 'image', + 'format_option_id': 'quebec_display_image', + 'display_name': 'Québec display image — French creative', + 'canonical_formats_only': True, + 'locale_policy': {'accepted_language_ranges': ['fr']}, + 'params': { + 'width': 300, + 'height': 250, + 'max_file_size_kb': 200, + 'image_formats': ['jpg', 'png'], + }, + }, + }, + { + 'description': "Podcast 30s host-read — narrows audio_hosted with a `script` slot the seller's host reads verbatim. No separate `inputs` map; the script lives in the manifest's `assets` like any other text asset.", + 'data': { + 'format_kind': 'audio_hosted', + 'params': { + 'duration_ms_exact': 30000, + 'audio_codecs': ['mp3', 'aac'], + 'audio_sample_rates': [44100, 48000], + 'audio_channels': ['stereo'], + 'loudness_lufs': -16, + 'asset_source': 'publisher_host_recorded', + 'buyer_asset_acceptance': 'rejected', + 'composition_model': 'deterministic', + 'slots': [ + { + 'asset_group_id': 'script', + 'required': True, + 'asset_type': 'text', + 'max_chars': 800, + }, + { + 'asset_group_id': 'offering_ref', + 'required': False, + 'asset_type': 'text', + }, + ], + 'production_window_business_days': 7, + }, + }, + }, + { + 'description': "StreamHaus Branded Feature — a still-custom branded-content format with format_schema pointing at the fictional seller's hosted schema. Buyer agents fetch the schema by uri@digest (cached, immutable) and validate the manifest structurally. `canonical_formats_only: true` is required for custom declarations because no v1 named format expresses this bespoke composition.", + 'data': { + 'format_kind': 'custom', + 'canonical_formats_only': True, + 'format_shape': 'branded_content', + 'format_schema': { + 'uri': 'https://ads.streamhaus.example/schemas/formats/branded_feature_v1', + 'digest': 'sha256:e1d4f6a9c2b5e8d1f4a7c0b3e6d9f2a5c8b1e4d7f0a3c6b9e2d5f8a1c4b7e0a3', + }, + 'format_option_id': 'streamhaus_branded_feature', + 'display_name': 'StreamHaus branded feature', + 'applies_to_channels': ['display'], + 'params': { + 'components': [ + {'placement_type': 'feature_header', 'required': True}, + {'placement_type': 'sponsored_story_card', 'required': True}, + {'placement_type': 'sponsorship_lockup', 'required': True}, + ], + 'exclusivity_window_hours': 12, + 'ssl_required': True, + }, + }, + }, + ], + title='Product Format Declaration', + ), ] diff --git a/src/adcp/types/domains/core/product_identity.py b/src/adcp/types/domains/core/product_identity.py index 293cddde4..1741ddb12 100644 --- a/src/adcp/types/domains/core/product_identity.py +++ b/src/adcp/types/domains/core/product_identity.py @@ -1,18 +1,29 @@ -"""Types declared by the AdCP ``core/product_identity`` schema. +# generated by datamodel-codegen: +# filename: core/product_identity.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.product_identity import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictBool -from adcp.types.generated_poc.core.product_identity import ProductIdentity -# Explicit exports -__all__ = ["ProductIdentity"] +class ProductIdentity(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + persistent_identifier: Annotated[ + StrictBool, + Field( + description='Whether delivery on this product has a persistent per-entity identifier suitable for identifier-backed reach, frequency, and frequency-cap enforcement. false does not prohibit modeled individuals or households measurement.' + ), + ] + reach_methodology: Annotated[ + str | None, + Field( + description="Human-readable methodology for this product's custom reach unit. Required when the product reports reach or frequency with reach_unit custom, including frequency-only reporting.", + min_length=1, + ), + ] = None diff --git a/src/adcp/types/domains/core/product_offer_filters.py b/src/adcp/types/domains/core/product_offer_filters.py index 5195eaabc..da9a509ab 100644 --- a/src/adcp/types/domains/core/product_offer_filters.py +++ b/src/adcp/types/domains/core/product_offer_filters.py @@ -1,50 +1,322 @@ -"""Types declared by the AdCP ``core/product_offer_filters`` schema. +# generated by datamodel-codegen: +# filename: core/product_offer_filters.json +# timestamp: 2026-10-05T13:49:50+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.product_offer_filters import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-04 01:19:07 UTC -""" +from datetime import date +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel, SchemaInt +from adcp.types._scalar import ScalarStr +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat -from adcp.types.generated_poc.core.product_offer_filters import ( - AvailabilityHorizon, - Country, - GeoProximityItem, - Geometry, - Metro, - PricingCurrency, - ProductOfferFilters, - Provider, - Radius, - Region, - RequiredPerformanceStandard, - RequiredVendorMetric, - TravelTime, - TrustedMatch, - Type, +from ..enums import audio_distribution_type, available_metric +from ..enums import channels as channels_1 +from ..enums import delivery_type as delivery_type_1 +from ..enums import distance_unit +from ..enums import exclusivity as exclusivity_1 +from ..enums import ( + metro_system, + performance_standard_metric, + pricing_structure, + response_type, + social_placement_surface, + sponsored_placement_type, ) +from ..enums import transport_mode as transport_mode_1 +from ..enums import travel_time_unit, video_placement_type, viewability_standard +from . import brand_key +from . import budget_range as budget_range_1 +from . import canonical_media_buy_features +from . import ext as ext_1 +from . import ( + format_option_ref, + postal_area, + product_audience_evidence_requirements, + property_list_ref, + vendor_metric_id, +) + + +class PricingCurrency(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{3}$'} + + +class AvailabilityHorizon(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + start_time: Annotated[ + AwareDatetime, + Field(description='Inclusive horizon start (RFC 3339 date-time with timezone offset).'), + ] + end_time: Annotated[ + AwareDatetime, + Field( + description='Exclusive horizon end (RFC 3339 date-time with timezone offset). MUST be after start_time.' + ), + ] + + +class Country(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{2}$'} + + +class Region(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{2}-[A-Z0-9]{1,3}$'} + + +class Metro(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + system: Annotated[ + metro_system.MetroAreaSystem, Field(description='Metro classification system') + ] + code: Annotated[ + str, Field(description="Metro code within the system (e.g., '501' for NYC DMA)") + ] + + +class Radius(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + value: Annotated[StrictFloat, Field(description='Radius distance.', gt=0.0)] + unit: Annotated[distance_unit.DistanceUnit, Field(description='Distance unit.')] + + +class Type(StrEnum): + Polygon = 'Polygon' + MultiPolygon = 'MultiPolygon' + + +class Geometry(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Annotated[Type, Field(description='GeoJSON geometry type.')] + coordinates: Annotated[ + list[Any], + Field( + description='GeoJSON coordinates array. For Polygon: array of linear rings. For MultiPolygon: array of polygons.' + ), + ] + + +class Provider(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + agent_url: AnyUrl + context_match: StrictBool | None = None + identity_match: StrictBool | None = None + + +class TravelTime(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + value: Annotated[StrictFloat, Field(description='Travel time limit.', ge=1.0)] + unit: travel_time_unit.TravelTimeUnit + + +class GeoProximityItem(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + lat: Annotated[ + StrictFloat | None, + Field( + description='Latitude in decimal degrees (WGS 84). Required for travel_time and radius methods.', + ge=-90.0, + le=90.0, + ), + ] = None + lng: Annotated[ + StrictFloat | None, + Field( + description='Longitude in decimal degrees (WGS 84). Required for travel_time and radius methods.', + ge=-180.0, + le=180.0, + ), + ] = None + label: Annotated[ + str | None, + Field( + description="Human-readable label for this entry (e.g., 'Düsseldorf', 'Heathrow Airport', 'Primary trade area')." + ), + ] = None + travel_time: Annotated[ + TravelTime | None, + Field( + description='Travel time limit for isochrone calculation. The platform resolves this to a geographic boundary based on actual transportation networks.' + ), + ] = None + transport_mode: Annotated[ + transport_mode_1.TransportMode | None, + Field( + description='Transportation mode for isochrone calculation. Required when travel_time is provided.' + ), + ] = None + radius: Annotated[ + Radius | None, + Field( + description='Simple radius from the point. The platform draws a circle of this distance around the coordinates.' + ), + ] = None + geometry: Annotated[ + Geometry | None, + Field( + description='Pre-computed GeoJSON geometry defining the proximity boundary. Use when the buyer has already calculated isochrones (via TravelTime, Mapbox, etc.) or has custom boundaries. When geometry is provided, lat/lng are not required.' + ), + ] = None + ext: ext_1.ExtensionObject | None = None + + +class TrustedMatch(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + providers: Annotated[list[Provider] | None, Field(min_length=1)] = None + response_types: Annotated[list[response_type.TmpResponseType] | None, Field(min_length=1)] = ( + None + ) + + +class RequiredPerformanceStandard(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + metric: performance_standard_metric.PerformanceStandardMetric + threshold: Annotated[StrictFloat, Field(ge=0.0, le=1.0)] + standard: viewability_standard.ViewabilityStandard | None = None + vendor: brand_key.BrandKey + + +class RequiredVendorMetric(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + vendor: brand_key.BrandKey | None = None + metric_id: vendor_metric_id.VendorMetricId | None = None + -# Explicit exports -__all__ = [ - "AvailabilityHorizon", - "Country", - "GeoProximityItem", - "Geometry", - "Metro", - "PricingCurrency", - "ProductOfferFilters", - "Provider", - "Radius", - "Region", - "RequiredPerformanceStandard", - "RequiredVendorMetric", - "TravelTime", - "TrustedMatch", - "Type", -] +class ProductOfferFilters(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + delivery_type: delivery_type_1.DeliveryType | None = None + exclusivity: exclusivity_1.Exclusivity | None = None + is_fixed_price: Annotated[ + StrictBool | None, + Field( + description='Filter fixed-price versus auction offers. Contingent pricing matches neither value.' + ), + ] = None + pricing_structures: Annotated[ + list[pricing_structure.PricingStructure] | None, Field(min_length=1) + ] = None + pricing_currencies: Annotated[list[PricingCurrency] | None, Field(min_length=1)] = None + format_kinds: Annotated[list[str] | None, Field(min_length=1)] = None + format_option_refs: Annotated[ + list[format_option_ref.FormatOptionReference] | None, Field(min_length=1) + ] = None + standard_formats_only: StrictBool | None = None + min_exposures: Annotated[SchemaInt | None, Field(ge=1)] = None + start_date: Annotated[ + date | None, + Field( + description='Fixed-flight availability filter: with end_date, declares the exact flight the buyer intends to run. Returned products MUST be able to serve that flight, and pricing and forecasts are scoped to it. Mutually exclusive with availability_horizon.' + ), + ] = None + end_date: Annotated[ + date | None, + Field( + description='Fixed-flight availability filter end. See start_date. Mutually exclusive with availability_horizon.' + ), + ] = None + availability_horizon: Annotated[ + AvailabilityHorizon | None, + Field( + description="Flexible-window availability discovery: the buyer is open to any bookable window inside [start_time, end_time) and asks the seller to describe when the returned inventory can run, instead of filtering to one exact flight. Sellers that support this field partition the horizon into time-dimensioned forecast rows (forecast-dimension-time) carrying availability_status; sellers that cannot cover the full horizon signal the gap via the response's incomplete[] mechanism. Unlike start_date/end_date this is not an eligibility filter — products remain returnable when only part of the horizon is open. The resulting availability is a snapshot bounded by the forecast's valid_until, never a hold. Mutually exclusive with start_date and end_date, which declare a fixed flight; buyers that already know their dates use those instead." + ), + ] = None + budget_range: budget_range_1.BudgetRange | None = None + countries: Annotated[ + list[Country] | None, + Field( + description="Filter by country coverage using ISO 3166-1 alpha-2 codes (e.g., ['US', 'CA', 'GB']). Returns products whose geographic coverage includes at least one of the specified countries. This is a product attribute filter, not a delivery-targeting instruction.", + min_length=1, + ), + ] = None + regions: Annotated[ + list[Region] | None, + Field( + description='Inventory coverage in the specified ISO 3166-2 subdivisions: OR within this field, AND across offer filters. Does not add delivery targeting, require targeting support, or rescope pricing or forecasts.', + min_length=1, + ), + ] = None + metros: Annotated[ + list[Metro] | None, + Field( + description='Inventory coverage in the specified metro areas (system and code): OR within this field, AND across offer filters. Does not add delivery targeting, require targeting support, or rescope pricing or forecasts.', + min_length=1, + ), + ] = None + postal_areas: Annotated[ + list[postal_area.PostalArea] | None, + Field( + description='Inventory coverage in the specified postal areas: OR within this field, AND across offer filters. Does not add delivery targeting, require targeting support, or rescope pricing or forecasts.', + min_length=1, + ), + ] = None + geo_proximity: Annotated[ + list[GeoProximityItem] | None, + Field( + description='Inventory coverage in the specified proximity boundaries: OR within this field, AND across offer filters. Does not add delivery targeting, require targeting support, or rescope pricing or forecasts.', + min_length=1, + ), + ] = None + property_list: Annotated[ + property_list_ref.PropertyListReference | None, + Field( + description='Reference to an externally managed property list. When provided, the seller filters products to only those available on properties in the list. This narrows which publisher inventory is returned; it is a product attribute filter, not a delivery-targeting instruction.' + ), + ] = None + channels: Annotated[list[channels_1.MediaChannel] | None, Field(min_length=1)] = None + video_placement_types: Annotated[ + list[video_placement_type.VideoPlacementType] | None, Field(min_length=1) + ] = None + audio_distribution_types: Annotated[ + list[audio_distribution_type.AudioDistributionType] | None, Field(min_length=1) + ] = None + sponsored_placement_types: Annotated[ + list[sponsored_placement_type.SponsoredPlacementType] | None, Field(min_length=1) + ] = None + social_placement_surfaces: Annotated[ + list[social_placement_surface.SocialPlacementSurface] | None, Field(min_length=1) + ] = None + trusted_match: TrustedMatch | None = None + required_features: Annotated[ + canonical_media_buy_features.CanonicalMediaBuyFeatures | None, + Field(description='Canonical protocol features the seller must support.'), + ] = None + required_performance_standards: Annotated[ + list[RequiredPerformanceStandard] | None, Field(min_length=1) + ] = None + required_metrics: Annotated[ + list[available_metric.AvailableMetric] | None, Field(min_length=1) + ] = None + required_vendor_metrics: Annotated[list[RequiredVendorMetric] | None, Field(min_length=1)] = ( + None + ) + audience_evidence_requirements: ( + product_audience_evidence_requirements.ProductAudienceEvidenceRequirements | None + ) = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/product_signal_targeting_option.py b/src/adcp/types/domains/core/product_signal_targeting_option.py index bbeeab895..fe0551364 100644 --- a/src/adcp/types/domains/core/product_signal_targeting_option.py +++ b/src/adcp/types/domains/core/product_signal_targeting_option.py @@ -1,22 +1,73 @@ -"""Types declared by the AdCP ``core/product_signal_targeting_option`` schema. +# generated by datamodel-codegen: +# filename: core/product_signal_targeting_option.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.product_signal_targeting_option import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import signal_ref, vendor_pricing_option +from .signal_listing import SignalListing + + +class ActivationStatus(StrEnum): + ready = 'ready' + requires_activation = 'requires_activation' + + +class AllowedTargetingMode(StrEnum): + include = 'include' + exclude = 'exclude' -from adcp.types.generated_poc.core.product_signal_targeting_option import ( - ActivationStatus, - AllowedTargetingMode, - ProductSignalTargetingOption, -) -# Explicit exports -__all__ = ["ActivationStatus", "AllowedTargetingMode", "ProductSignalTargetingOption"] +class ProductSignalTargetingOption(SignalListing): + model_config = ConfigDict( + extra='allow', + ) + signal_agent_segment_id: Annotated[ + str | None, + Field( + description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the seller exposes a distinct runtime or activation handle that buyers must echo in packages[].targeting_overlay.signal_targeting_groups.groups[].signals[].signal_agent_segment_id. Buyers SHOULD echo this handle verbatim rather than reconstructing identity from categorical values; providers MAY namespace handles so cross-provider identity stays legible without a shared taxonomy registry.' + ), + ] = None + activation_status: Annotated[ + ActivationStatus | None, + Field( + description="Whether this signal option is ready to select on create_media_buy for the requesting account. 'ready' means the buyer can select it directly. 'requires_activation' means the buyer must activate the signal first or include an activation_key the seller accepts." + ), + ] = ActivationStatus.ready + allowed_targeting_modes: Annotated[ + list[AllowedTargetingMode] | None, + Field( + description="How this signal may be used when composing package-level signal targeting groups. 'include' means the signal may appear in an 'any' child group. 'exclude' means the signal may appear in a 'none' child group. Omit when the signal is include-only. This field declares the allowed buy-time group operator; binary package signal entries still use value=true in both include and exclude groups.", + min_length=1, + ), + ] = [AllowedTargetingMode.include] + default_selected: Annotated[ + StrictBool | None, + Field( + description="Whether the seller recommends or preselects this signal when composing this product. Buyers may remove it unless signal_targeting_rules.selection_mode is 'fixed'. When selection_mode is 'fixed', sellers apply default_selected signals even if the buyer omits signal_targeting_groups and MUST echo the applied entries on the resulting package state." + ), + ] = False + selection_group: Annotated[ + str | None, + Field( + description='Optional product-defined composability bucket for signal options, such as alternative audience tiers, a key-value targeting plane, or an audience-segment targeting plane. Signals in the same selection_group are expected to be OR-combinable inside one child group for a given targeting mode, subject to signal_targeting_rules. Use different selection_group values when the product requires separate ANDed clauses, such as signal sets backed by different platform targeting primitives that cannot be collapsed into one child group. selection_group is a product-option grouping key, not a reference to one child object in packages[].targeting_overlay.signal_targeting_groups.groups[]. Sellers can use signal_targeting_rules.max_selected_per_group and signal_targeting_rules.selection_group_rules with selection_group to guide and validate storefront composition.' + ), + ] = None + pricing_options: Annotated[ + list[vendor_pricing_option.VendorPricingOption] | None, + Field( + description='Signal pricing options available when this signal is selected on this product. Product-scoped pricing is authoritative for this product; if get_signals exposes a different default rate card, use this product-scoped price when composing the buy. Buyers pass the selected pricing_option_id in packages[].targeting_overlay.signal_targeting_groups.groups[].signals[].pricing_option_id. Omit when the signal is bundled into the product price or has no incremental cost.', + min_length=1, + ), + ] = None + signal_ref: Annotated[ + signal_ref.SignalRef, + Field( + description="Canonical signal reference. Use scope 'product' for a product-local signal defined by this listing; use scope 'data_provider' with data_provider_domain for a signal defined in a data provider's published adagents.json signals[]; use scope 'signal_source' with signal_source_url for a source-native signal." + ), + ] diff --git a/src/adcp/types/domains/core/product_targeting_resolution.py b/src/adcp/types/domains/core/product_targeting_resolution.py index 410a6d4c0..43d742ffc 100644 --- a/src/adcp/types/domains/core/product_targeting_resolution.py +++ b/src/adcp/types/domains/core/product_targeting_resolution.py @@ -1,18 +1,34 @@ -"""Types declared by the AdCP ``core/product_targeting_resolution`` schema. +# generated by datamodel-codegen: +# filename: core/product_targeting_resolution.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.product_targeting_resolution import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import ext as ext_1 +from . import targeting_modification -from adcp.types.generated_poc.core.product_targeting_resolution import ProductTargetingResolution -# Explicit exports -__all__ = ["ProductTargetingResolution"] +class ProductTargetingResolution(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + modifications: Annotated[ + list[targeting_modification.TargetingModification], + Field( + description='Ordered changes applied to the requested targeting_overlay. Sellers apply entries in array order and MUST validate the complete resulting overlay before returning the product.', + min_length=1, + ), + ] + effective_targeting_digest: Annotated[ + str | None, + Field( + description='Optional digest of the canonical effective targeting overlay. Until AdCP defines a cross-implementation canonicalization algorithm, this value is an opaque seller audit handle and buyers MUST NOT attempt to reproduce or compare it across sellers.', + pattern='^sha256:[0-9a-f]{64}$', + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/property.py b/src/adcp/types/domains/core/property.py index f3312367f..76dbc6284 100644 --- a/src/adcp/types/domains/core/property.py +++ b/src/adcp/types/domains/core/property.py @@ -1,18 +1,68 @@ -"""Types declared by the AdCP ``core/property`` schema. +# generated by datamodel-codegen: +# filename: core/property.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.property import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import channels, identifier_types +from ..enums import property_type as property_type_1 +from . import property_id as property_id_1 +from . import property_tag + + +class Identifier(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + type: Annotated[ + identifier_types.PropertyIdentifierTypes, + Field(description='Type of identifier for this property'), + ] + value: Annotated[ + str, + Field( + description="The identifier value. For domain type: 'example.com' matches base domain plus www and m subdomains; 'edition.example.com' matches that specific subdomain; '*.example.com' matches ALL subdomains but NOT base domain" + ), + ] -from adcp.types.generated_poc.core.property import Identifier, Property -# Explicit exports -__all__ = ["Identifier", "Property"] +class Property(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + property_id: Annotated[ + property_id_1.PropertyId | None, + Field( + description='Unique identifier for this property (optional). Enables referencing properties by ID instead of repeating full objects.' + ), + ] = None + property_type: Annotated[ + property_type_1.PropertyType, Field(description='Type of advertising property') + ] + name: Annotated[str, Field(description='Human-readable property name')] + identifiers: Annotated[ + list[Identifier], Field(description='Array of identifiers for this property', min_length=1) + ] + tags: Annotated[ + list[property_tag.PropertyTag] | None, + Field( + description='Tags for categorization and grouping (e.g., network membership, content categories)' + ), + ] = None + supported_channels: Annotated[ + list[channels.MediaChannel] | None, + Field( + description="Advertising channels this property supports (e.g., ['display', 'olv', 'social']). Publishers declare which channels their inventory aligns with. Properties may support multiple channels. See the Media Channel Taxonomy for definitions." + ), + ] = None + publisher_domain: Annotated[ + str | None, + Field( + description='Domain where adagents.json should be checked for authorization validation. Optional in adagents.json (file location implies domain).' + ), + ] = None diff --git a/src/adcp/types/domains/core/property_delivery_metrics.py b/src/adcp/types/domains/core/property_delivery_metrics.py index 5a0fab605..c6987ce92 100644 --- a/src/adcp/types/domains/core/property_delivery_metrics.py +++ b/src/adcp/types/domains/core/property_delivery_metrics.py @@ -1,18 +1,43 @@ -"""Types declared by the AdCP ``core/property_delivery_metrics`` schema. +# generated by datamodel-codegen: +# filename: core/property_delivery_metrics.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.property_delivery_metrics import +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import identifier as identifier_1 +from . import property_ref as property_ref_1 +from .delivery_metrics import DeliveryMetrics -from adcp.types.generated_poc.core.property_delivery_metrics import PropertyDeliveryMetrics -# Explicit exports -__all__ = ["PropertyDeliveryMetrics"] +class PropertyDeliveryMetrics(DeliveryMetrics): + publisher_domain: Annotated[ + str, + Field( + description='Publisher or platform authority that namespaces the operational identifier, including when the surface is not registered in adagents.json.', + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] + identifier: Annotated[ + identifier_1.Identifier, + Field( + description='Primary operational identifier of the property that delivered. Required even when catalog enrichment is unavailable.' + ), + ] + property_ref: Annotated[ + property_ref_1.PropertyReference | None, + Field( + description="Canonical publisher-scoped catalog identity when the delivered property resolves to adagents.json. Its publisher_domain MUST equal the row's publisher_domain." + ), + ] = None + property_name: Annotated[ + str | None, + Field( + description='Current human-readable property name. Convenience metadata only; property_ref is stable identity.' + ), + ] = None + impressions: Any + spend: Any diff --git a/src/adcp/types/domains/core/property_id.py b/src/adcp/types/domains/core/property_id.py index 070cb7fef..514c9e7ed 100644 --- a/src/adcp/types/domains/core/property_id.py +++ b/src/adcp/types/domains/core/property_id.py @@ -1,18 +1,17 @@ -"""Types declared by the AdCP ``core/property_id`` schema. +# generated by datamodel-codegen: +# filename: core/property_id.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.property_id import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.property_id import PropertyId +from adcp.types._scalar import ScalarStr + -# Explicit exports -__all__ = ["PropertyId"] +class PropertyId(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[a-z0-9_]+$'} + _json_schema_extra = { + 'description': 'Identifier for a publisher property. Must be lowercase alphanumeric with underscores only.', + 'examples': ['cnn_ctv_app', 'homepage', 'mobile_ios', 'instagram'], + 'title': 'Property ID', + } diff --git a/src/adcp/types/domains/core/property_list_ref.py b/src/adcp/types/domains/core/property_list_ref.py index 6bd56dd9f..9373a7a11 100644 --- a/src/adcp/types/domains/core/property_list_ref.py +++ b/src/adcp/types/domains/core/property_list_ref.py @@ -1,18 +1,26 @@ -"""Types declared by the AdCP ``core/property_list_ref`` schema. +# generated by datamodel-codegen: +# filename: core/property_list_ref.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.property_list_ref import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field -from adcp.types.generated_poc.core.property_list_ref import PropertyListReference -# Explicit exports -__all__ = ["PropertyListReference"] +class PropertyListReference(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + agent_url: Annotated[AnyUrl, Field(description='URL of the agent managing the property list')] + list_id: Annotated[ + str, Field(description='Identifier for the property list within the agent', min_length=1) + ] + auth_token: Annotated[ + str | None, + Field( + description='JWT or other authorization token for accessing the list. Optional if the list is public or caller has implicit access.' + ), + ] = None diff --git a/src/adcp/types/domains/core/property_ref.py b/src/adcp/types/domains/core/property_ref.py index 0caeff3c8..993790e32 100644 --- a/src/adcp/types/domains/core/property_ref.py +++ b/src/adcp/types/domains/core/property_ref.py @@ -1,18 +1,29 @@ -"""Types declared by the AdCP ``core/property_ref`` schema. +# generated by datamodel-codegen: +# filename: core/property_ref.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.property_ref import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import property_id as property_id_1 -from adcp.types.generated_poc.core.property_ref import PropertyReference -# Explicit exports -__all__ = ["PropertyReference"] +class PropertyReference(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + publisher_domain: Annotated[ + str, + Field( + description='Domain where the adagents.json declaring this property is hosted.', + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] + property_id: Annotated[ + property_id_1.PropertyId, + Field(description="Property ID from the publisher's adagents.json property catalog."), + ] diff --git a/src/adcp/types/domains/core/property_tag.py b/src/adcp/types/domains/core/property_tag.py index 21d5a8a3b..f8a83e99d 100644 --- a/src/adcp/types/domains/core/property_tag.py +++ b/src/adcp/types/domains/core/property_tag.py @@ -1,18 +1,17 @@ -"""Types declared by the AdCP ``core/property_tag`` schema. +# generated by datamodel-codegen: +# filename: core/property_tag.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.property_tag import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.property_tag import PropertyTag +from adcp.types._scalar import ScalarStr + -# Explicit exports -__all__ = ["PropertyTag"] +class PropertyTag(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[a-z0-9_]+$'} + _json_schema_extra = { + 'description': 'Tag for categorizing publisher properties. Must be lowercase alphanumeric with underscores only.', + 'examples': ['ctv', 'premium', 'news', 'sports', 'meta_network', 'social_media'], + 'title': 'Property Tag', + } diff --git a/src/adcp/types/domains/core/proposal.py b/src/adcp/types/domains/core/proposal.py index 4495db148..04b10b2aa 100644 --- a/src/adcp/types/domains/core/proposal.py +++ b/src/adcp/types/domains/core/proposal.py @@ -1,18 +1,116 @@ -"""Types declared by the AdCP ``core/proposal`` schema. +# generated by datamodel-codegen: +# filename: core/proposal.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.proposal import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import pacing as pacing_1 +from ..enums import proposal_status as proposal_status_1 +from . import budget_allocation as budget_allocation_1 +from . import delivery_forecast +from . import ext as ext_1 +from . import insertion_order as insertion_order_1 +from . import media_buy_frequency_cap, product_allocation + + +class TotalBudgetGuidance(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + min: Annotated[StrictFloat | None, Field(description='Minimum recommended budget', ge=0.0)] = ( + None + ) + recommended: Annotated[ + StrictFloat | None, Field(description='Recommended budget for optimal performance', ge=0.0) + ] = None + max: Annotated[ + StrictFloat | None, Field(description='Maximum budget before diminishing returns', ge=0.0) + ] = None + currency: Annotated[str | None, Field(description='ISO 4217 currency code')] = None -from adcp.types.generated_poc.core.proposal import Proposal, TotalBudgetGuidance -# Explicit exports -__all__ = ["Proposal", "TotalBudgetGuidance"] +class Proposal(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + proposal_id: Annotated[ + str, + Field( + description='Unique identifier for this proposal. Used to finalize a draft proposal and to execute a committed proposal via create_media_buy.', + max_length=255, + ), + ] + name: Annotated[ + str, Field(description='Human-readable name for this media plan proposal', max_length=500) + ] + description: Annotated[ + str | None, + Field( + description='Explanation of the proposal strategy and what it achieves', max_length=2000 + ), + ] = None + allocations: Annotated[ + list[product_allocation.ProductAllocation], + Field( + description='Products and budget constraints in this plan. Fixed proposals require allocation_percentage on every entry and percentages MUST sum to 100. Seller-optimized proposals forbid exact allocation_percentage and may instead supply min_spend_target_percentage and max_spend_percentage, each only when the seller advertises the matching package-control capability (seller_optimized_min_spend_targets, seller_optimized_package_budgets). Publishers are responsible for validating cross-entry sums; buyers SHOULD validate them before execution.', + min_length=1, + ), + ] + budget_allocation: Annotated[ + budget_allocation_1.BudgetAllocation | None, + Field( + description='How the executed total budget is allocated across proposal products. Omit for legacy fixed proposals.' + ), + ] = None + pacing: Annotated[ + pacing_1.Pacing | None, + Field( + description='Recommended aggregate pacing for the executed media-buy budget. On a committed proposal this is part of the firm delivery terms.' + ), + ] = None + frequency_cap: Annotated[ + media_buy_frequency_cap.MediaBuyFrequencyCap | None, + Field( + description='Aggregate cap bound into this legacy proposal. It is authoritative when the proposal is executed and uses one counter across its packages.' + ), + ] = None + proposal_status: Annotated[ + proposal_status_1.ProposalStatus | None, + Field( + description="Lifecycle status of this proposal and the per-proposal source of truth for whether finalization is required before create_media_buy. When absent, the proposal is ready to buy (backward compatible). 'draft' means indicative pricing — finalize via refine before purchasing. 'committed' means firm pricing with inventory reserved until expires_at and executable via create_media_buy." + ), + ] = None + expires_at: Annotated[ + AwareDatetime | None, + Field( + description='When this proposal expires and can no longer be executed. For draft proposals, indicates when indicative pricing becomes stale. For committed proposals, indicates when the inventory hold lapses — the buyer must call create_media_buy before this time.' + ), + ] = None + insertion_order: Annotated[ + insertion_order_1.InsertionOrder | None, + Field( + description='Formal insertion order attached to a committed proposal. Present when the seller requires a signed agreement before the media buy can proceed. The buyer references the io_id in io_acceptance on create_media_buy.' + ), + ] = None + total_budget_guidance: Annotated[ + TotalBudgetGuidance | None, Field(description='Optional budget guidance for this proposal') + ] = None + brief_alignment: Annotated[ + str | None, + Field( + description='Explanation of how this proposal aligns with the campaign brief', + max_length=2000, + ), + ] = None + forecast: Annotated[ + delivery_forecast.DeliveryForecast | None, + Field( + description='Aggregate forecasted delivery metrics for the entire proposal. When both proposal-level and allocation-level forecasts are present, the proposal-level forecast is authoritative for total delivery estimation.' + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/protocol_envelope.py b/src/adcp/types/domains/core/protocol_envelope.py index 5f2f95a5d..7f75dfd17 100644 --- a/src/adcp/types/domains/core/protocol_envelope.py +++ b/src/adcp/types/domains/core/protocol_envelope.py @@ -1,18 +1,90 @@ -"""Types declared by the AdCP ``core/protocol_envelope`` schema. +# generated by datamodel-codegen: +# filename: core/protocol_envelope.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.protocol_envelope import +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import task_status +from . import context as context_1 +from . import error +from . import push_notification_config as push_notification_config_1 -from adcp.types.generated_poc.core.protocol_envelope import ProtocolEnvelope -# Explicit exports -__all__ = ["ProtocolEnvelope"] +class ProtocolEnvelope(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + context_id: Annotated[ + str | None, + Field( + description='Transport-managed conversation identifier. On A2A, this maps to the native Message/Task `contextId` used to associate messages with a conversation; it is not carried inside the AdCP DataPart. On MCP, a request-body `context_id`, where admitted by the selected request schema, is a compatibility-only field: servers MUST ignore it, callers MUST NOT rely on it for continuity, and it MUST NOT select session state, identity, account, authorization, task continuation, or idempotency scope. MCP continuity, if provided, comes from the transport session. Distinct from `context` (per-request opaque echo, see below) and from `task_id` (AdCP operation tracking).' + ), + ] = None + context: Annotated[ + context_1.ContextObject | None, + Field( + description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (transport-managed A2A conversation correlation or MCP compatibility metadata) — `context` is caller-owned echo and never selects transport state. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.' + ), + ] = None + task_id: Annotated[ + str | None, + Field( + description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' + ), + ] = None + status: Annotated[ + task_status.TaskStatus, + Field( + description='Current AdCP task state or structured outcome. Indicates whether the task completed, is in progress, was submitted for async processing, failed, requires user input, or returned a typed business rejection. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) normally emit `status: "completed"`; a task-specific rejection arm emits `status: "rejected"` without turning the transport into a failure. Async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative AdCP response state.' + ), + ] = task_status.TaskStatus.completed + message: Annotated[ + str | None, + Field( + description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' + ), + ] = None + timestamp: Annotated[ + AwareDatetime | None, + Field( + description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' + ), + ] = None + replayed: Annotated[ + StrictBool | None, + Field( + description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. `replayed` appears only when the request actually resolved through the idempotency cache. Pure reads may ignore an optional `idempotency_key`; when a seller voluntarily caches keyed reads, those responses use the same replay indicator and full cache contract." + ), + ] = False + adcp_error: Annotated[ + error.Error | None, + Field( + description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures." + ), + ] = None + push_notification_config: Annotated[ + push_notification_config_1.PushNotificationConfig | None, + Field( + description='AdCP application-layer webhook configuration for async task updates over MCP, A2A, or REST. Echoed from the request to confirm webhook settings. It is distinct from transport-native progress or A2A TaskPushNotificationConfig delivery and can outlive the originating transport session.' + ), + ] = None + governance_context: Annotated[ + str | None, + Field( + description='Opaque authorization context issued only by an approved check_governance decision. Buyers attach it to governed requests across protocol roles (media buys, rights acquisitions, signal activations, creative services); receiving services persist it and forward it on subsequent execution and lifecycle checks. The context is the authoritative plan binding at service boundaries, so a service MUST NOT require a separate plan_id.\n\nGovernance agents MUST emit a compact JWS per the AdCP JWS profile. Verifiers validate standard authorization claims such as signature, issuer, audience, expiry, and replay protection, but intermediaries MUST NOT interpret embedded governance state for business logic. A conditions or denied verdict never carries an authorization context.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.', + max_length=4096, + min_length=1, + pattern='^[\\x20-\\x7E]+$', + ), + ] = None + payload: Annotated[ + dict[str, Any] | None, + Field( + description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' + ), + ] = None diff --git a/src/adcp/types/domains/core/provenance.py b/src/adcp/types/domains/core/provenance.py index 3cf974133..e24dc3e9c 100644 --- a/src/adcp/types/domains/core/provenance.py +++ b/src/adcp/types/domains/core/provenance.py @@ -1,50 +1,333 @@ -"""Types declared by the AdCP ``core/provenance`` schema. +# generated by datamodel-codegen: +# filename: core/provenance.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.provenance import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat -from adcp.types.generated_poc.core.provenance import ( - AiTool, - C2pa, - DeclaredBy, - Disclosure, - EmbeddedProvenanceItem, - HumanOversight, - Jurisdiction, - Provenance, - RenderGuidance, - Result, - Role, - VerificationItem, - VerifyAgent, - VerifyAgent18, - Watermark, +from ..enums import c2pa_watermark_action +from ..enums import digital_source_type as digital_source_type_1 +from ..enums import ( + disclosure_persistence, + disclosure_position, + embedded_provenance_method, + watermark_media_type, ) +from . import ext as ext_1 + + +class AiTool(AdCPBaseModel): + name: Annotated[ + str, + Field( + description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" + ), + ] + version: Annotated[ + str | None, + Field( + description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." + ), + ] = None + provider: Annotated[ + str | None, + Field( + description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" + ), + ] = None + + +class HumanOversight(StrEnum): + none = 'none' + prompt_only = 'prompt_only' + selected = 'selected' + edited = 'edited' + directed = 'directed' + + +class Role(StrEnum): + creator = 'creator' + advertiser = 'advertiser' + agency = 'agency' + platform = 'platform' + tool = 'tool' + + +class DeclaredBy(AdCPBaseModel): + agent_url: Annotated[ + AnyUrl | None, + Field(description='URL of the agent or service that declared this provenance'), + ] = None + role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] + + +class C2pa(AdCPBaseModel): + manifest_url: Annotated[ + AnyUrl, Field(description='URL to the C2PA manifest store for this content') + ] + + +class VerifyAgent(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + agent_url: Annotated[ + AnyUrl, + Field( + description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." + ), + ] + feature_id: Annotated[ + str | None, + Field( + description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." + ), + ] = None + + +class EmbeddedProvenanceItem(AdCPBaseModel): + method: Annotated[ + embedded_provenance_method.EmbeddedProvenanceMethod, + Field(description='How provenance data is carried within the content'), + ] + standard: Annotated[ + str | None, + Field( + description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" + ), + ] = None + provider: Annotated[ + str, + Field( + description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." + ), + ] + verify_agent: Annotated[ + VerifyAgent | None, + Field( + description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." + ), + ] = None + embedded_at: Annotated[ + AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') + ] = None + + +class VerifyAgent18(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + agent_url: Annotated[ + AnyUrl, + Field( + description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." + ), + ] + feature_id: Annotated[ + str | None, + Field( + description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." + ), + ] = None + + +class RenderGuidance(AdCPBaseModel): + persistence: Annotated[ + disclosure_persistence.DisclosurePersistence | None, + Field( + description='How long the disclosure must persist during content playback or display' + ), + ] = None + min_duration_ms: Annotated[ + SchemaInt | None, + Field( + description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", + ge=1, + ), + ] = None + positions: Annotated[ + list[disclosure_position.DisclosurePosition] | None, + Field( + description='Preferred disclosure positions in priority order. The first position a format supports should be used.', + min_length=1, + ), + ] = None + ext: ext_1.ExtensionObject | None = None + + +class Jurisdiction(AdCPBaseModel): + country: Annotated[ + str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") + ] + region: Annotated[ + str | None, + Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), + ] = None + regulation: Annotated[ + str, + Field( + description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" + ), + ] + label_text: Annotated[ + str | None, + Field( + description='Required disclosure label text for this jurisdiction, in the local language' + ), + ] = None + render_guidance: Annotated[ + RenderGuidance | None, + Field( + description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." + ), + ] = None + + +class Disclosure(AdCPBaseModel): + required: Annotated[ + StrictBool, + Field( + description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." + ), + ] + jurisdictions: Annotated[ + list[Jurisdiction] | None, + Field(description='Jurisdictions where disclosure obligations apply', min_length=1), + ] = None + + +class Result(StrEnum): + authentic = 'authentic' + ai_generated = 'ai_generated' + ai_modified = 'ai_modified' + inconclusive = 'inconclusive' + + +class VerificationItem(AdCPBaseModel): + verified_by: Annotated[ + str, + Field( + description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" + ), + ] + verified_time: Annotated[ + AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') + ] = None + result: Annotated[Result, Field(description='Verification outcome')] + confidence: Annotated[ + StrictFloat | None, + Field( + description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 + ), + ] = None + details_url: Annotated[ + AnyUrl | None, Field(description='URL to the full verification report') + ] = None + + +class Watermark(AdCPBaseModel): + media_type: Annotated[ + watermark_media_type.WatermarkMediaType, + Field(description='Media category of the watermarked content'), + ] + provider: Annotated[ + str, + Field( + description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." + ), + ] + verify_agent: Annotated[ + VerifyAgent18 | None, + Field( + description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." + ), + ] = None + c2pa_action: Annotated[ + c2pa_watermark_action.C2PaWatermarkAction | None, + Field(description='C2PA action classification for this watermark'), + ] = None + embedded_at: Annotated[ + AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') + ] = None + -# Explicit exports -__all__ = [ - "AiTool", - "C2pa", - "DeclaredBy", - "Disclosure", - "EmbeddedProvenanceItem", - "HumanOversight", - "Jurisdiction", - "Provenance", - "RenderGuidance", - "Result", - "Role", - "VerificationItem", - "VerifyAgent", - "VerifyAgent18", - "Watermark", -] +class Provenance(AdCPBaseModel): + digital_source_type: Annotated[ + digital_source_type_1.DigitalSourceType | None, + Field( + description='IPTC-aligned classification of AI involvement in producing this content' + ), + ] = None + synthetic_depiction: Annotated[ + StrictBool | None, + Field( + description='Assessed declaration of whether the content synthetically depicts a real or fictional person performing or appearing in a way that was generated or materially manipulated rather than captured as depicted. `true` covers both a fully synthetic performer and material manipulation of a real performer; `false` is an assessed declaration that the content does not contain such a depiction. Absence means the content has not been assessed for synthetic depiction. This field does not claim consent, legality, or independent verification, and receivers MUST NOT derive it solely from `digital_source_type`.' + ), + ] = None + ai_tool: Annotated[ + AiTool | None, + Field( + description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' + ), + ] = None + human_oversight: Annotated[ + HumanOversight | None, + Field( + description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' + ), + ] = None + declared_by: Annotated[ + DeclaredBy | None, + Field( + description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' + ), + ] = None + declared_at: Annotated[ + AwareDatetime | None, + Field( + description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' + ), + ] = None + created_time: Annotated[ + AwareDatetime | None, + Field(description='When this content was created or generated (ISO 8601)'), + ] = None + c2pa: Annotated[ + C2pa | None, + Field( + description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' + ), + ] = None + embedded_provenance: Annotated[ + list[EmbeddedProvenanceItem] | None, + Field( + description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', + min_length=1, + ), + ] = None + watermarks: Annotated[ + list[Watermark] | None, + Field( + description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', + min_length=1, + ), + ] = None + disclosure: Annotated[ + Disclosure | None, + Field( + description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' + ), + ] = None + verification: Annotated[ + list[VerificationItem] | None, + Field( + description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', + min_length=1, + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/publisher_property_selector.py b/src/adcp/types/domains/core/publisher_property_selector.py index b232ab8b5..5b4cc5862 100644 --- a/src/adcp/types/domains/core/publisher_property_selector.py +++ b/src/adcp/types/domains/core/publisher_property_selector.py @@ -1,30 +1,107 @@ -"""Types declared by the AdCP ``core/publisher_property_selector`` schema. +# generated by datamodel-codegen: +# filename: core/publisher_property_selector.json +# timestamp: 2026-10-04T00:53:27+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.publisher_property_selector import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field + +from . import property_id, property_tag + + +class PublisherDomain(ScalarStr): + __slots__ = () + _constraints = { + 'pattern': '^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + } + + +class PublisherPropertySelector1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + publisher_domain: Annotated[ + str | None, + Field( + description="Domain where publisher's adagents.json is hosted (e.g., 'cnn.com'). XOR with `publisher_domains` — exactly one MUST be present on each `publisher_properties[]` entry; both-present and neither-present both fail validation.", + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] = None + publisher_domains: Annotated[ + list[PublisherDomain] | None, + Field( + description="Compact form for fanning the same selector across many publishers (e.g., a managed network listing every publisher it represents). Each entry is the domain where that publisher's adagents.json is hosted. Each listed domain MUST be canonicalized to lowercase (the `pattern` already rejects uppercase). Mutually exclusive with `publisher_domain`. Each listed domain counts as explicitly scoped for the `managerdomain` fallback safety rule.", + min_length=1, + ), + ] = None + selection_type: Annotated[ + Literal['all'], + Field( + description='Discriminator indicating all properties from each addressed publisher are included' + ), + ] = 'all' + + +class PublisherPropertySelector2(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + publisher_domain: Annotated[ + str, + Field( + description="Domain where publisher's adagents.json is hosted (e.g., 'cnn.com').", + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] + selection_type: Annotated[ + Literal['by_id'], + Field(description='Discriminator indicating selection by specific property IDs'), + ] = 'by_id' + property_ids: Annotated[ + list[property_id.PropertyId], + Field(description="Specific property IDs from the publisher's adagents.json", min_length=1), + ] + + +class PublisherPropertySelector3(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + publisher_domain: Annotated[ + str | None, + Field( + description="Domain where publisher's adagents.json is hosted (e.g., 'cnn.com'). XOR with `publisher_domains` — exactly one MUST be present on each `publisher_properties[]` entry; both-present and neither-present both fail validation.", + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] = None + publisher_domains: Annotated[ + list[PublisherDomain] | None, + Field( + description="Compact form for fanning the same tag predicate across many publishers (canonical managed-network shape). Each entry is the domain where that publisher's adagents.json is hosted. Each listed domain MUST be canonicalized to lowercase (the `pattern` already rejects uppercase). Mutually exclusive with `publisher_domain`. Each listed domain counts as explicitly scoped for the `managerdomain` fallback safety rule.", + min_length=1, + ), + ] = None + selection_type: Annotated[ + Literal['by_tag'], Field(description='Discriminator indicating selection by property tags') + ] = 'by_tag' + property_tags: Annotated[ + list[property_tag.PropertyTag], + Field( + description="Property tags resolved against each addressed publisher's adagents.json, OR against the parent file's top-level `properties[]` when those properties carry a `publisher_domain` matching the selector. Selector covers all properties carrying any of these tags.", + min_length=1, + ), + ] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.publisher_property_selector import ( - PublisherDomain, - PublisherPropertySelector, - PublisherPropertySelector1, - PublisherPropertySelector2, - PublisherPropertySelector3, -) - -# Explicit exports -__all__ = [ - "PublisherDomain", - "PublisherPropertySelector", - "PublisherPropertySelector1", - "PublisherPropertySelector2", - "PublisherPropertySelector3", +PublisherPropertySelector = Annotated[ + PublisherPropertySelector1 | PublisherPropertySelector2 | PublisherPropertySelector3, + Field( + description="Selects properties from a publisher's adagents.json. Used for both product definitions and agent authorization. Supports three selection patterns: all properties, specific IDs, or by tags. Each selector targets one publisher via `publisher_domain` (string) or a fan-out across many publishers that share the same selector via `publisher_domains` (array). Exactly one of `publisher_domain` or `publisher_domains` MUST be present. When `publisher_domains` is used, the selector is logically equivalent to repeating the same entry once per listed domain.", + discriminator='selection_type', + title='Publisher Property Selector', + ), ] diff --git a/src/adcp/types/domains/core/push_notification_config.py b/src/adcp/types/domains/core/push_notification_config.py index bb9a0d414..1dd3b1cd7 100644 --- a/src/adcp/types/domains/core/push_notification_config.py +++ b/src/adcp/types/domains/core/push_notification_config.py @@ -1,21 +1,66 @@ -"""Types declared by the AdCP ``core/push_notification_config`` schema. +# generated by datamodel-codegen: +# filename: core/push_notification_config.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.push_notification_config import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import auth_scheme + + +class Authentication(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + schemes: Annotated[ + list[auth_scheme.AuthenticationScheme], + Field( + description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", + max_length=1, + min_length=1, + ), + ] + credentials: Annotated[ + str, + Field( + description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', + min_length=32, + ), + ] -from adcp.types.generated_poc.core.push_notification_config import ( - Authentication, - PushNotificationConfig, -) -# Explicit exports -__all__ = ["Authentication", "PushNotificationConfig"] +class PushNotificationConfig(AdCPBaseModel): + url: Annotated[ + AnyUrl, + Field( + description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' + ), + ] + operation_id: Annotated[ + str | None, + Field( + description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. For 3.x schema compatibility the member remains optional, but a seller MUST reject a task that registers an AdCP webhook without it using `INVALID_REQUEST`; otherwise the required webhook envelope cannot be emitted.", + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] = None + token: Annotated[ + str | None, + Field( + description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", + max_length=4096, + min_length=16, + ), + ] = None + authentication: Annotated[ + Authentication | None, + Field( + deprecated=True, + description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).', + ), + ] = None diff --git a/src/adcp/types/domains/core/real_estate_item.py b/src/adcp/types/domains/core/real_estate_item.py index 0abb882ce..338963531 100644 --- a/src/adcp/types/domains/core/real_estate_item.py +++ b/src/adcp/types/domains/core/real_estate_item.py @@ -1,26 +1,120 @@ -"""Types declared by the AdCP ``core/real_estate_item`` schema. +# generated by datamodel-codegen: +# filename: core/real_estate_item.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.real_estate_item import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AnyUrl, ConfigDict, Field, StrictFloat + +from . import ext as ext_1 +from . import offering_asset_group +from . import price as price_1 + + +class Address(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + street: Annotated[str | None, Field(description='Street address.')] = None + city: Annotated[str | None, Field(description='City name.')] = None + region: Annotated[str | None, Field(description='State, province, or region.')] = None + postal_code: Annotated[str | None, Field(description='Postal or ZIP code.')] = None + country: Annotated[ + str | None, Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + ] = None + + +class PropertyType(StrEnum): + house = 'house' + apartment = 'apartment' + condo = 'condo' + townhouse = 'townhouse' + land = 'land' + commercial = 'commercial' + + +class ListingType(StrEnum): + for_sale = 'for_sale' + for_rent = 'for_rent' + + +class Unit(StrEnum): + sqft = 'sqft' + sqm = 'sqm' + + +class Area(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + value: Annotated[StrictFloat, Field(description='Area value.', ge=0.0)] + unit: Annotated[Unit, Field(description='Area unit.')] + + +class Location(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + lat: Annotated[ + StrictFloat, Field(description='Latitude in decimal degrees (WGS 84).', ge=-90.0, le=90.0) + ] + lng: Annotated[ + StrictFloat, + Field(description='Longitude in decimal degrees (WGS 84).', ge=-180.0, le=180.0), + ] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.real_estate_item import ( - Address, - Area, - ListingType, - Location, - PropertyType, - RealEstateItem, - Unit, -) - -# Explicit exports -__all__ = ["Address", "Area", "ListingType", "Location", "PropertyType", "RealEstateItem", "Unit"] +class RealEstateItem(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + listing_id: Annotated[str, Field(description='Unique identifier for this property listing.')] + title: Annotated[ + str, Field(description="Listing title (e.g., 'Spacious 3BR Apartment in Jordaan').") + ] + address: Annotated[Address, Field(description='Property address.')] + price: Annotated[ + price_1.Price | None, + Field(description="Property price or rental rate. Use period 'month' for rentals."), + ] = None + property_type: Annotated[PropertyType | None, Field(description='Type of property.')] = None + listing_type: Annotated[ + ListingType | None, Field(description='Whether the property is for sale or rent.') + ] = None + bedrooms: Annotated[SchemaInt | None, Field(description='Number of bedrooms.', ge=0)] = None + bathrooms: Annotated[ + StrictFloat | None, + Field( + description='Number of bathrooms (e.g., 2.5 for two full and one half bath).', ge=0.0 + ), + ] = None + area: Annotated[Area | None, Field(description='Property size.')] = None + description: Annotated[str | None, Field(description='Property description.')] = None + location: Annotated[ + Location | None, Field(description='Geographic coordinates of the property.') + ] = None + image_url: Annotated[AnyUrl | None, Field(description='Primary property image URL.')] = None + url: Annotated[AnyUrl | None, Field(description='Listing page URL.')] = None + neighborhood: Annotated[str | None, Field(description='Neighborhood or area name.')] = None + year_built: Annotated[SchemaInt | None, Field(description='Year the property was built.')] = ( + None + ) + tags: Annotated[ + list[str] | None, + Field( + description="Tags for filtering (e.g., 'garden', 'parking', 'renovated', 'waterfront').", + min_length=1, + ), + ] = None + assets: Annotated[ + list[offering_asset_group.OfferingAssetGroup] | None, + Field( + description="Typed creative asset pools for this property listing. Uses the same OfferingAssetGroup structure as offering-type catalogs. Standard group IDs: 'images_landscape' (exterior/interior hero), 'images_vertical' (9:16 for Stories), 'images_square' (1:1). Enables formats to declare typed image requirements that map unambiguously to the right asset regardless of platform.", + min_length=1, + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/reference_asset.py b/src/adcp/types/domains/core/reference_asset.py index f0093247e..877e9bfa4 100644 --- a/src/adcp/types/domains/core/reference_asset.py +++ b/src/adcp/types/domains/core/reference_asset.py @@ -1,18 +1,42 @@ -"""Types declared by the AdCP ``core/reference_asset`` schema. +# generated by datamodel-codegen: +# filename: core/reference_asset.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.reference_asset import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.reference_asset import ReferenceAsset, Role +class Role(StrEnum): + style_reference = 'style_reference' + product_shot = 'product_shot' + mood_board = 'mood_board' + example_creative = 'example_creative' + logo = 'logo' + strategy_doc = 'strategy_doc' + storyboard = 'storyboard' + -# Explicit exports -__all__ = ["ReferenceAsset", "Role"] +class ReferenceAsset(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + url: Annotated[ + AnyUrl, Field(description='URL to the reference asset (image, video, or document)') + ] + role: Annotated[ + Role, + Field( + description='How the creative agent should use this asset. style_reference: match the visual style; product_shot: include this product; mood_board: overall look and feel; example_creative: example of a similar execution; logo: logo to use; strategy_doc: strategy or planning document for context; storyboard: sequential visual direction for video or multi-scene creative' + ), + ] + description: Annotated[ + str | None, + Field( + description='Human-readable description of the asset and how it should inform creative generation' + ), + ] = None diff --git a/src/adcp/types/domains/core/reference_renderer.py b/src/adcp/types/domains/core/reference_renderer.py index cab26f27b..f9b6a454d 100644 --- a/src/adcp/types/domains/core/reference_renderer.py +++ b/src/adcp/types/domains/core/reference_renderer.py @@ -1,18 +1,86 @@ -"""Types declared by the AdCP ``core/reference_renderer`` schema. +# generated by datamodel-codegen: +# filename: core/reference_renderer.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.reference_renderer import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field, field_validator -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.reference_renderer import Provenance, ReferenceRenderer +class Provenance(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + source_repository: Annotated[ + AnyUrl, + Field( + description='Allowlisted source repository that the npm provenance attestation MUST identify.' + ), + ] + workflow_path: Annotated[ + str, + Field( + description='Repository-relative GitHub Actions workflow path that npm provenance buildDefinition.externalParameters.workflow.path MUST identify.', + pattern='^\\.github/workflows/[A-Za-z0-9._/-]+\\.ya?ml$', + ), + ] + + @field_validator('source_repository') + @classmethod + def _require_github_source_repository(cls, value: AnyUrl) -> AnyUrl: + if value.scheme != 'https' or value.host != 'github.com' or value.port != 443: + raise ValueError('source_repository must use https://github.com/') + if value.username is not None or value.password is not None: + raise ValueError('source_repository must not contain credentials') + return value -# Explicit exports -__all__ = ["Provenance", "ReferenceRenderer"] +class ReferenceRenderer(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + runtime: Annotated[ + Literal['browser-esm'], + Field( + description='Execution contract for the referenced package. browser-esm means a browser-safe ECMAScript module that accepts canonical manifest data and returns an inert presentation without Node.js APIs, ambient credentials, delivery tracking, or undeclared network access. Non-JavaScript clients use a hosted preview_creative provider or display the manifest.' + ), + ] = 'browser-esm' + package: Annotated[ + str, + Field( + description='npm package name, scoped or unscoped. The package is resolved from the npm registry; the AdCP registry does not proxy its executable contents.', + pattern='^(?:@[a-z0-9][a-z0-9._~-]*/)?[a-z0-9][a-z0-9._~-]*$', + ), + ] + version: Annotated[ + str, + Field( + description='Exact semantic version. Ranges and tags such as latest are forbidden so the registry entry is reproducible. Package semantic versioning identifies the pinned distribution artifact; it is independent of any one format revision because one package may expose renderers for multiple formats.', + pattern='^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$', + ), + ] + export: Annotated[ + str, + Field( + description="Named package export that implements the renderer contract for this enclosing format entry. Compatibility is bound at the export-to-entry edge, not to matching version labels: registry review and contract fixtures verify that the export implements the entry's input contract. When that input contract changes, the registry MUST rerun those fixtures and MAY retain the existing export and package pin when they still pass. One package version MAY expose different named exports for different formats or input contracts.", + min_length=1, + ), + ] + format_revision: Annotated[ + str | None, + Field( + deprecated=True, + description="Deprecated compatibility annotation retained for previously published registry entries. Renderer package versions and community format revisions have independent lifecycles, so consumers MUST NOT require this value to equal the enclosing entry's format_revision or use matching values as evidence of compatibility. Registry review and contract fixtures bind the named export to the enclosing format's input contract. New entries SHOULD omit this field.", + pattern='^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)$', + ), + ] = None + integrity: Annotated[ + str, + Field( + description='Subresource Integrity value for the exact npm package tarball. Consumers MUST compare this value before loading code, require the provenance subject digest to match the same tarball, and fail closed on mismatch.', + pattern='^(?:sha256-[A-Za-z0-9+/]{43}=|sha384-[A-Za-z0-9+/]{64}|sha512-[A-Za-z0-9+/]{86}==)$', + ), + ] + provenance: Provenance diff --git a/src/adcp/types/domains/core/registry_event.py b/src/adcp/types/domains/core/registry_event.py index 8187db3ac..6dcdbb0cd 100644 --- a/src/adcp/types/domains/core/registry_event.py +++ b/src/adcp/types/domains/core/registry_event.py @@ -1,148 +1,1345 @@ -"""Types declared by the AdCP ``core/registry_event`` schema. +# generated by datamodel-codegen: +# filename: core/registry_event.json +# timestamp: 2026-10-05T13:49:50+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.registry_event import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any, Literal +from uuid import UUID -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from adcp.types._scalar import ScalarStr +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, RootModel, StrictBool + +from ..enums import adcp_protocol, collection_kind, distribution_identifier_type +from . import agent_signing_key +from . import collection as collection_1 +from . import collection_selector, format_id, identifier +from . import property as property_1 +from . import property_id, property_tag, publisher_property_selector + + +class EventType(StrEnum): + property_created = 'property.created' + property_updated = 'property.updated' + property_merged = 'property.merged' + property_stale = 'property.stale' + property_reactivated = 'property.reactivated' + collection_created = 'collection.created' + collection_updated = 'collection.updated' + collection_merged = 'collection.merged' + collection_removed = 'collection.removed' + agent_discovered = 'agent.discovered' + agent_removed = 'agent.removed' + agent_profile_updated = 'agent.profile_updated' + agent_compliance_changed = 'agent.compliance_changed' + agent_verification_earned = 'agent.verification_earned' + agent_verification_lost = 'agent.verification_lost' + publisher_adagents_discovered = 'publisher.adagents_discovered' + publisher_adagents_changed = 'publisher.adagents_changed' + authorization_granted = 'authorization.granted' + authorization_revoked = 'authorization.revoked' + authorization_modified = 'authorization.modified' + + +class EntityType(StrEnum): + property = 'property' + collection = 'collection' + agent = 'agent' + publisher = 'publisher' + authorization = 'authorization' + + +class Payload(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + alias_rid: Annotated[ + UUID, Field(description='Retired property_rid that now aliases to canonical_rid.') + ] + canonical_rid: Annotated[ + UUID, Field(description='Canonical property_rid that consumers should retain.') + ] + evidence: Annotated[ + str | None, + Field( + description='Registry evidence source for the merge, such as adagents_json or manual_review.' + ), + ] = None + + +class RegistryEvent3(AdCPBaseModel): + event_id: Annotated[ + UUID, + Field( + description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' + ), + ] + event_type: Annotated[ + Literal['property.merged'], + Field(description='Discriminator. Determines the shape of payload.'), + ] = 'property.merged' + entity_type: Annotated[ + Literal['property'], Field(description='Entity class touched by this event.') + ] = 'property' + entity_id: Annotated[ + str, + Field( + description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' + ), + ] + payload: Annotated[ + Payload, + Field( + description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' + ), + ] + actor: Annotated[ + str, + Field( + description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' + ), + ] + created_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' + ), + ] + + +class Payload1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + property_rid: UUID + last_resolved_at: AwareDatetime | None = None + reason: str | None = None + + +class RegistryEvent4(AdCPBaseModel): + event_id: Annotated[ + UUID, + Field( + description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' + ), + ] + event_type: Annotated[ + Literal['property.stale'], + Field(description='Discriminator. Determines the shape of payload.'), + ] = 'property.stale' + entity_type: Annotated[ + Literal['property'], Field(description='Entity class touched by this event.') + ] = 'property' + entity_id: Annotated[ + str, + Field( + description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' + ), + ] + payload: Annotated[ + Payload1, + Field( + description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' + ), + ] + actor: Annotated[ + str, + Field( + description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' + ), + ] + created_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' + ), + ] + + +class Payload3(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + alias_rid: Annotated[ + UUID, Field(description='Retired collection_rid that now aliases to canonical_rid.') + ] + canonical_rid: Annotated[ + UUID, Field(description='Canonical collection_rid that consumers should retain.') + ] + evidence: Annotated[ + str | None, + Field( + description='Registry evidence source for the merge, such as adagents_json or manual_review.' + ), + ] = None + + +class RegistryEvent8(AdCPBaseModel): + event_id: Annotated[ + UUID, + Field( + description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' + ), + ] + event_type: Annotated[ + Literal['collection.merged'], + Field(description='Discriminator. Determines the shape of payload.'), + ] = 'collection.merged' + entity_type: Annotated[ + Literal['collection'], Field(description='Entity class touched by this event.') + ] = 'collection' + entity_id: Annotated[ + str, + Field( + description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' + ), + ] + payload: Annotated[ + Payload3, + Field( + description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' + ), + ] + actor: Annotated[ + str, + Field( + description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' + ), + ] + created_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' + ), + ] + + +class Status(StrEnum): + active = 'active' + stale = 'stale' + removed = 'removed' + + +class Tracks(StrEnum): + pass_ = 'pass' # nosec B105: Fixed protocol compliance track verdict. + fail = 'fail' + partial = 'partial' + skip = 'skip' + silent = 'silent' + warning = 'warning' + unknown = 'unknown' + skipped = 'skipped' + + +class GradingProfile(StrEnum): + legacy = 'legacy' + spec = 'spec' + + +class AuthorizationType(StrEnum): + property_ids = 'property_ids' + property_tags = 'property_tags' + inline_properties = 'inline_properties' + publisher_properties = 'publisher_properties' + signal_ids = 'signal_ids' + signal_tags = 'signal_tags' + + +class DelegationType(StrEnum): + direct = 'direct' + delegated = 'delegated' + ad_network = 'ad_network' + + +class Evidence(StrEnum): + adagents_json = 'adagents_json' + agent_claim = 'agent_claim' + community = 'community' + override = 'override' + + +class Domain(ScalarStr): + __slots__ = () + _constraints = { + 'pattern': '^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + } + + +class BadgeRole(RootModel[adcp_protocol.AdcpProtocol]): + root: adcp_protocol.AdcpProtocol + + +class StringArray(RootModel[list[str]]): + root: list[str] + + +class ChangedFields(RootModel[list[str]]): + root: Annotated[ + list[str], + Field( + description='Advisory list of changed top-level field names from the source document. Example values for publisher.adagents_changed include "formats", "placements", and "authorized_agents"; for agent.profile_updated these are profile field names such as "channels" or "format_kinds". Producers SHOULD enumerate every changed semantic field, explicitly including formats and placements for catalog-only revisions (additive from 3.2). Consumers MAY use this for targeted index refresh but MUST re-resolve every depended-on field when reading an older retained event that lacks this list.', + min_length=1, + ), + ] + + +class Classification(StrEnum): + property = 'property' + ad_infra = 'ad_infra' + publisher_mask = 'publisher_mask' + network = 'network' + unclassified = 'unclassified' + + +class PropertySource(StrEnum): + authoritative = 'authoritative' + enriched = 'enriched' + contributed = 'contributed' + + +class CollectionIdentifier(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + publisher_domain: Annotated[ + Domain, + Field(description='Distribution platform domain, such as youtube.com or spotify.com.'), + ] + type: distribution_identifier_type.DistributionIdentifierType + value: str + + +class Type(StrEnum): + sales = 'sales' + creative = 'creative' + signals = 'signals' + governance = 'governance' + measurement = 'measurement' + unknown = 'unknown' + + +class Market(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{2}$'} + + +class AgentProfilePayload(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + agent_url: AnyUrl | None = None + name: str | None = None + type: Type | None = None + channels: StringArray | None = None + property_types: StringArray | None = None + markets: list[Market] | None = None + categories: StringArray | None = None + category_taxonomy: str | None = None + format_ids: Annotated[ + list[format_id.FormatReferenceStructuredObject] | None, + Field( + deprecated=True, + description='Deprecated in AdCP 3.2; removed in AdCP 4.0. Named-format profile projection. Use canonical `format_kinds` and `format_option_refs`.', + ), + ] = None + format_kinds: list[str] | None = None + tags: StringArray | None = None + delivery_types: StringArray | None = None + property_count: Annotated[SchemaInt | None, Field(ge=0)] = None + publisher_count: Annotated[SchemaInt | None, Field(ge=0)] = None + has_tmp: StrictBool | None = None + updated_at: AwareDatetime | None = None + + +class Country(Market): + pass + + +class Countries(RootModel[list[Country]]): + root: Annotated[list[Country], Field(min_length=1)] + + +class ComplianceStatus(StrEnum): + passing = 'passing' + degraded = 'degraded' + failing = 'failing' + unknown = 'unknown' + + +class StoryboardStatus(StrEnum): + passing = 'passing' + failing = 'failing' + partial = 'partial' + untested = 'untested' + skipped = 'skipped' + not_selected = 'not_selected' + unknown = 'unknown' + + +class RegistryEvent10(AdCPBaseModel): + event_id: Annotated[ + UUID, + Field( + description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' + ), + ] + event_type: Annotated[ + Literal['agent.discovered'], + Field(description='Discriminator. Determines the shape of payload.'), + ] = 'agent.discovered' + entity_type: Annotated[ + Literal['agent'], Field(description='Entity class touched by this event.') + ] = 'agent' + entity_id: Annotated[ + str, + Field( + description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' + ), + ] + payload: Annotated[ + AgentProfilePayload, + Field( + description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' + ), + ] + actor: Annotated[ + str, + Field( + description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' + ), + ] + created_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' + ), + ] + + +class Payload5(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + agent_url: AnyUrl + removed_from_publishers: Annotated[list[Domain] | None, Field(min_length=1)] = None + + +class RegistryEvent11(AdCPBaseModel): + event_id: Annotated[ + UUID, + Field( + description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' + ), + ] + event_type: Annotated[ + Literal['agent.removed'], + Field(description='Discriminator. Determines the shape of payload.'), + ] = 'agent.removed' + entity_type: Annotated[ + Literal['agent'], Field(description='Entity class touched by this event.') + ] = 'agent' + entity_id: Annotated[ + str, + Field( + description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' + ), + ] + payload: Annotated[ + Payload5, + Field( + description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' + ), + ] + actor: Annotated[ + str, + Field( + description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' + ), + ] + created_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' + ), + ] + + +class Payload6(AgentProfilePayload): + changed_fields: ChangedFields | None = None + agent_url: Any + + +class RegistryEvent12(AdCPBaseModel): + event_id: Annotated[ + UUID, + Field( + description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' + ), + ] + event_type: Annotated[ + Literal['agent.profile_updated'], + Field(description='Discriminator. Determines the shape of payload.'), + ] = 'agent.profile_updated' + entity_type: Annotated[ + Literal['agent'], Field(description='Entity class touched by this event.') + ] = 'agent' + entity_id: Annotated[ + str, + Field( + description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' + ), + ] + payload: Annotated[ + Payload6, + Field( + description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' + ), + ] + actor: Annotated[ + str, + Field( + description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' + ), + ] + created_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' + ), + ] + + +class Storyboard(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + storyboard_id: str + status: StoryboardStatus + steps_passed: Annotated[SchemaInt | None, Field(ge=0)] = None + steps_total: Annotated[SchemaInt | None, Field(ge=0)] = None + + +class Payload7(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + agent_url: AnyUrl + previous_status: ComplianceStatus + current_status: ComplianceStatus + headline: str | None = None + tracks: Annotated[ + dict[str, Tracks], Field(description='Map of compliance track id to track status.') + ] + storyboards_passing: Annotated[SchemaInt, Field(ge=0)] + storyboards_total: Annotated[SchemaInt, Field(ge=0)] + storyboards: list[Storyboard] | None = None + + +class RegistryEvent13(AdCPBaseModel): + event_id: Annotated[ + UUID, + Field( + description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' + ), + ] + event_type: Annotated[ + Literal['agent.compliance_changed'], + Field(description='Discriminator. Determines the shape of payload.'), + ] = 'agent.compliance_changed' + entity_type: Annotated[ + Literal['agent'], Field(description='Entity class touched by this event.') + ] = 'agent' + entity_id: Annotated[ + str, + Field( + description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' + ), + ] + payload: Annotated[ + Payload7, + Field( + description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' + ), + ] + actor: Annotated[ + str, + Field( + description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' + ), + ] + created_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' + ), + ] + + +class Payload8(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + agent_url: AnyUrl + role: BadgeRole + verified_specialisms: Annotated[list[str], Field(min_length=1)] + adcp_version: str | None = None + grading_profile: Annotated[ + GradingProfile | None, + Field( + description='Grading profile that produced the badge. Historical events emitted before profile selection may omit this field and are Legacy.' + ), + ] = None + + +class RegistryEvent14(AdCPBaseModel): + event_id: Annotated[ + UUID, + Field( + description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' + ), + ] + event_type: Annotated[ + Literal['agent.verification_earned'], + Field(description='Discriminator. Determines the shape of payload.'), + ] = 'agent.verification_earned' + entity_type: Annotated[ + Literal['agent'], Field(description='Entity class touched by this event.') + ] = 'agent' + entity_id: Annotated[ + str, + Field( + description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' + ), + ] + payload: Annotated[ + Payload8, + Field( + description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' + ), + ] + actor: Annotated[ + str, + Field( + description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' + ), + ] + created_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' + ), + ] + + +class Payload9(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + agent_url: AnyUrl + role: BadgeRole + reason: str + adcp_version: str | None = None + grading_profile: Annotated[ + GradingProfile | None, + Field( + description='Grading profile in force when the badge was lost. Historical events emitted before profile selection may omit this field and are Legacy.' + ), + ] = None + + +class RegistryEvent15(AdCPBaseModel): + event_id: Annotated[ + UUID, + Field( + description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' + ), + ] + event_type: Annotated[ + Literal['agent.verification_lost'], + Field(description='Discriminator. Determines the shape of payload.'), + ] = 'agent.verification_lost' + entity_type: Annotated[ + Literal['agent'], Field(description='Entity class touched by this event.') + ] = 'agent' + entity_id: Annotated[ + str, + Field( + description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' + ), + ] + payload: Annotated[ + Payload9, + Field( + description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' + ), + ] + actor: Annotated[ + str, + Field( + description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' + ), + ] + created_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' + ), + ] + + +class Payload10(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + publisher_domain: Domain | None = None + domain: Annotated[ + Domain | None, + Field( + deprecated=True, + description='Legacy alias for publisher_domain retained for early feed examples.', + ), + ] = None + properties_added: Annotated[SchemaInt | None, Field(ge=0)] = None + properties_removed: Annotated[SchemaInt | None, Field(ge=0)] = None + agents_added: list[AnyUrl] | None = None + agents_removed: list[AnyUrl] | None = None + agent_count: Annotated[SchemaInt | None, Field(ge=0)] = None + property_count: Annotated[SchemaInt | None, Field(ge=0)] = None + collection_count: Annotated[SchemaInt | None, Field(ge=0)] = None + format_count: Annotated[ + SchemaInt | None, + Field(description='Number of top-level formats[] declarations after this revision.', ge=0), + ] = None + placement_count: Annotated[ + SchemaInt | None, + Field( + description='Number of top-level placements[] declarations after this revision.', ge=0 + ), + ] = None + changed_fields: ChangedFields | None = None + discovery_method: str | None = None + manager_domain: str | None = None + source: str | None = None + + +class RegistryEvent16(AdCPBaseModel): + event_id: Annotated[ + UUID, + Field( + description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' + ), + ] + event_type: Annotated[ + Literal['publisher.adagents_discovered'], + Field(description='Discriminator. Determines the shape of payload.'), + ] = 'publisher.adagents_discovered' + entity_type: Annotated[ + Literal['publisher'], Field(description='Entity class touched by this event.') + ] = 'publisher' + entity_id: Annotated[ + str, + Field( + description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' + ), + ] + payload: Annotated[ + Payload10, + Field( + description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' + ), + ] + actor: Annotated[ + str, + Field( + description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' + ), + ] + created_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' + ), + ] + + +class Payload12(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + id: Annotated[ + UUID | None, + Field( + description='Registry authorization row id when the event is backed by a materialized effective authorization row.' + ), + ] = None + agent_url: AnyUrl + agent_url_canonical: Annotated[ + str | None, + Field(description='Registry-canonicalized form of agent_url for equality checks.'), + ] = None + publisher_domain: Domain + authorization_type: Annotated[ + AuthorizationType | None, + Field( + description='When present, identifies the adagents.json authorization variant represented by this payload.' + ), + ] = None + authorized_for: str | None = None + property_ids: Annotated[list[property_id.PropertyId] | None, Field(min_length=1)] = None + property_tags: Annotated[list[property_tag.PropertyTag] | None, Field(min_length=1)] = None + properties: Annotated[list[property_1.Property] | None, Field(min_length=1)] = None + publisher_properties: Annotated[ + list[publisher_property_selector.PublisherPropertySelector] | None, Field(min_length=1) + ] = None + property_rid: Annotated[ + UUID | None, + Field( + description='Catalog property_rid for materialized per-property authorization rows. Null for publisher-wide rows.' + ), + ] = None + property_id_slug: Annotated[ + str | None, + Field( + description='Publisher-local property id for materialized per-property authorization rows.' + ), + ] = None + placement_ids: Annotated[list[str] | None, Field(min_length=1)] = None + placement_tags: Annotated[list[str] | None, Field(min_length=1)] = None + collections: Annotated[ + list[collection_selector.CollectionSelector] | None, + Field( + description='Collection constraints from authorized_agents[*].collections. When set, the event does NOT authorize the property unqualified — consumers building a local authorization index MUST scope the grant to these selectors and fail closed when a query carries no collection scope. A selector without collection_ids is a bulk grant for all collections declared at that publisher_domain.', + min_length=1, + ), + ] = None + countries: Countries | None = None + delegation_type: DelegationType | None = None + exclusive: StrictBool | None = None + signing_keys: Annotated[ + list[agent_signing_key.AgentSigningKey] | None, + Field( + description='Publisher-attested signing keys copied from adagents.json when the registry has them. Advisory in feed events; verifiers MUST re-fetch the authoritative publisher artifact before treating keys as a trust anchor.', + min_length=1, + ), + ] = None + effective_from: AwareDatetime | None = None + effective_until: AwareDatetime | None = None + evidence: Evidence | None = None + disputed: StrictBool | None = None + created_by: str | None = None + expires_at: AwareDatetime | None = None + created_at: AwareDatetime | None = None + updated_at: AwareDatetime | None = None + override_applied: StrictBool | None = None + override_reason: str | None = None + + +class RegistryEvent18(AdCPBaseModel): + event_id: Annotated[ + UUID, + Field( + description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' + ), + ] + event_type: Annotated[ + Literal['authorization.granted'], + Field(description='Discriminator. Determines the shape of payload.'), + ] = 'authorization.granted' + entity_type: Annotated[ + Literal['authorization'], Field(description='Entity class touched by this event.') + ] = 'authorization' + entity_id: Annotated[ + str, + Field( + description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' + ), + ] + payload: Annotated[ + Payload12, + Field( + description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' + ), + ] + actor: Annotated[ + str, + Field( + description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' + ), + ] + created_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' + ), + ] + + +class Payload13(Payload12): + pass + + +class RegistryEvent19(AdCPBaseModel): + event_id: Annotated[ + UUID, + Field( + description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' + ), + ] + event_type: Annotated[ + Literal['authorization.revoked'], + Field(description='Discriminator. Determines the shape of payload.'), + ] = 'authorization.revoked' + entity_type: Annotated[ + Literal['authorization'], Field(description='Entity class touched by this event.') + ] = 'authorization' + entity_id: Annotated[ + str, + Field( + description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' + ), + ] + payload: Annotated[ + Payload13, + Field( + description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' + ), + ] + actor: Annotated[ + str, + Field( + description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' + ), + ] + created_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' + ), + ] + + +class Payload14(Payload12): + pass + + +class RegistryEvent20(AdCPBaseModel): + event_id: Annotated[ + UUID, + Field( + description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' + ), + ] + event_type: Annotated[ + Literal['authorization.modified'], + Field(description='Discriminator. Determines the shape of payload.'), + ] = 'authorization.modified' + entity_type: Annotated[ + Literal['authorization'], Field(description='Entity class touched by this event.') + ] = 'authorization' + entity_id: Annotated[ + str, + Field( + description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' + ), + ] + payload: Annotated[ + Payload14, + Field( + description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' + ), + ] + actor: Annotated[ + str, + Field( + description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' + ), + ] + created_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' + ), + ] + + +class PublisherAdagentsPayload(Payload10): + pass + + +class PropertyPayload(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + property_rid: UUID | None = None + classification: Classification | None = None + source: PropertySource | None = None + identifiers: Annotated[list[identifier.Identifier] | None, Field(min_length=1)] = None + publisher_domain: Domain | None = None + property: Annotated[ + property_1.Property | None, + Field(description='Optional full post-change property object when available.'), + ] = None + changed_fields: ChangedFields | None = None + + +class AuthorizationPayload(Payload12): + pass + + +class CompliancePayload(Payload7): + pass + + +class RegistryEvent1(AdCPBaseModel): + event_id: Annotated[ + UUID, + Field( + description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' + ), + ] + event_type: Annotated[ + Literal['property.created'], + Field(description='Discriminator. Determines the shape of payload.'), + ] = 'property.created' + entity_type: Annotated[ + Literal['property'], Field(description='Entity class touched by this event.') + ] = 'property' + entity_id: Annotated[ + str, + Field( + description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' + ), + ] + payload: Annotated[ + PropertyPayload, + Field( + description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' + ), + ] + actor: Annotated[ + str, + Field( + description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' + ), + ] + created_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' + ), + ] + + +class RegistryEvent2(AdCPBaseModel): + event_id: Annotated[ + UUID, + Field( + description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' + ), + ] + event_type: Annotated[ + Literal['property.updated'], + Field(description='Discriminator. Determines the shape of payload.'), + ] = 'property.updated' + entity_type: Annotated[ + Literal['property'], Field(description='Entity class touched by this event.') + ] = 'property' + entity_id: Annotated[ + str, + Field( + description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' + ), + ] + payload: Annotated[ + PropertyPayload, + Field( + description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' + ), + ] + actor: Annotated[ + str, + Field( + description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' + ), + ] + created_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' + ), + ] + + +class Payload2(PropertyPayload): + reactivated_at: AwareDatetime | None = None + property_rid: Any + + +class RegistryEvent5(AdCPBaseModel): + event_id: Annotated[ + UUID, + Field( + description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' + ), + ] + event_type: Annotated[ + Literal['property.reactivated'], + Field(description='Discriminator. Determines the shape of payload.'), + ] = 'property.reactivated' + entity_type: Annotated[ + Literal['property'], Field(description='Entity class touched by this event.') + ] = 'property' + entity_id: Annotated[ + str, + Field( + description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' + ), + ] + payload: Annotated[ + Payload2, + Field( + description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' + ), + ] + actor: Annotated[ + str, + Field( + description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' + ), + ] + created_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' + ), + ] + + +class Payload11(PublisherAdagentsPayload): + properties_added: Annotated[SchemaInt | None, Field(ge=0)] = None + properties_removed: Annotated[SchemaInt | None, Field(ge=0)] = None + agents_added: list[AnyUrl] | None = None + agents_removed: list[AnyUrl] | None = None + changed_fields: ChangedFields | None = None + + +class RegistryEvent17(AdCPBaseModel): + event_id: Annotated[ + UUID, + Field( + description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' + ), + ] + event_type: Annotated[ + Literal['publisher.adagents_changed'], + Field(description='Discriminator. Determines the shape of payload.'), + ] = 'publisher.adagents_changed' + entity_type: Annotated[ + Literal['publisher'], Field(description='Entity class touched by this event.') + ] = 'publisher' + entity_id: Annotated[ + str, + Field( + description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' + ), + ] + payload: Annotated[ + Payload11, + Field( + description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' + ), + ] + actor: Annotated[ + str, + Field( + description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' + ), + ] + created_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' + ), + ] + + +class CollectionPayload(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + collection_rid: UUID | None = None + publisher_domain: Domain | None = None + collection_id: Annotated[ + str | None, + Field(description='Publisher-local collection_id from the authoritative adagents.json.'), + ] = None + name: str | None = None + kind: collection_kind.CollectionKind | None = None + source: PropertySource | None = None + status: Status | None = None + identifiers: Annotated[ + list[CollectionIdentifier] | None, + Field(description='Distribution identifiers that alias this collection across platforms.'), + ] = None + collection: Annotated[ + collection_1.Collection | None, + Field(description='Optional full post-change collection object when available.'), + ] = None + changed_fields: ChangedFields | None = None + + +class RegistryEvent6(AdCPBaseModel): + event_id: Annotated[ + UUID, + Field( + description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' + ), + ] + event_type: Annotated[ + Literal['collection.created'], + Field(description='Discriminator. Determines the shape of payload.'), + ] = 'collection.created' + entity_type: Annotated[ + Literal['collection'], Field(description='Entity class touched by this event.') + ] = 'collection' + entity_id: Annotated[ + str, + Field( + description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' + ), + ] + payload: Annotated[ + CollectionPayload, + Field( + description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' + ), + ] + actor: Annotated[ + str, + Field( + description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' + ), + ] + created_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' + ), + ] + + +class RegistryEvent7(AdCPBaseModel): + event_id: Annotated[ + UUID, + Field( + description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' + ), + ] + event_type: Annotated[ + Literal['collection.updated'], + Field(description='Discriminator. Determines the shape of payload.'), + ] = 'collection.updated' + entity_type: Annotated[ + Literal['collection'], Field(description='Entity class touched by this event.') + ] = 'collection' + entity_id: Annotated[ + str, + Field( + description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' + ), + ] + payload: Annotated[ + CollectionPayload, + Field( + description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' + ), + ] + actor: Annotated[ + str, + Field( + description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' + ), + ] + created_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' + ), + ] + + +class Payload4(CollectionPayload): + status: Literal['removed'] = 'removed' # type: ignore[assignment] + collection_rid: Any + publisher_domain: Any + + +class RegistryEvent9(AdCPBaseModel): + event_id: Annotated[ + UUID, + Field( + description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' + ), + ] + event_type: Annotated[ + Literal['collection.removed'], + Field(description='Discriminator. Determines the shape of payload.'), + ] = 'collection.removed' + entity_type: Annotated[ + Literal['collection'], Field(description='Entity class touched by this event.') + ] = 'collection' + entity_id: Annotated[ + str, + Field( + description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' + ), + ] + payload: Annotated[ + Payload4, + Field( + description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' + ), + ] + actor: Annotated[ + str, + Field( + description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' + ), + ] + created_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' + ), + ] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.registry_event import ( - AgentProfilePayload, - AuthorizationPayload, - AuthorizationType, - BadgeRole, - ChangedFields, - Classification, - CollectionIdentifier, - CollectionPayload, - CompliancePayload, - ComplianceStatus, - Countries, - Country, - DelegationType, - Domain, - EntityType, - EventType, - Evidence, - GradingProfile, - Market, - Payload, - Payload1, - Payload10, - Payload11, - Payload12, - Payload13, - Payload14, - Payload2, - Payload3, - Payload4, - Payload5, - Payload6, - Payload7, - Payload8, - Payload9, - PropertyPayload, - PropertySource, - PublisherAdagentsPayload, - RegistryEvent, - RegistryEvent1, - RegistryEvent10, - RegistryEvent11, - RegistryEvent12, - RegistryEvent13, - RegistryEvent14, - RegistryEvent15, - RegistryEvent16, - RegistryEvent17, - RegistryEvent18, - RegistryEvent19, - RegistryEvent2, - RegistryEvent20, - RegistryEvent3, - RegistryEvent4, - RegistryEvent5, - RegistryEvent6, - RegistryEvent7, - RegistryEvent8, - RegistryEvent9, - Status, - Storyboard, - StoryboardStatus, - StringArray, - Tracks, - Type, -) - -# Explicit exports -__all__ = [ - "AgentProfilePayload", - "AuthorizationPayload", - "AuthorizationType", - "BadgeRole", - "ChangedFields", - "Classification", - "CollectionIdentifier", - "CollectionPayload", - "CompliancePayload", - "ComplianceStatus", - "Countries", - "Country", - "DelegationType", - "Domain", - "EntityType", - "EventType", - "Evidence", - "GradingProfile", - "Market", - "Payload", - "Payload1", - "Payload10", - "Payload11", - "Payload12", - "Payload13", - "Payload14", - "Payload2", - "Payload3", - "Payload4", - "Payload5", - "Payload6", - "Payload7", - "Payload8", - "Payload9", - "PropertyPayload", - "PropertySource", - "PublisherAdagentsPayload", - "RegistryEvent", - "RegistryEvent1", - "RegistryEvent10", - "RegistryEvent11", - "RegistryEvent12", - "RegistryEvent13", - "RegistryEvent14", - "RegistryEvent15", - "RegistryEvent16", - "RegistryEvent17", - "RegistryEvent18", - "RegistryEvent19", - "RegistryEvent2", - "RegistryEvent20", - "RegistryEvent3", - "RegistryEvent4", - "RegistryEvent5", - "RegistryEvent6", - "RegistryEvent7", - "RegistryEvent8", - "RegistryEvent9", - "Status", - "Storyboard", - "StoryboardStatus", - "StringArray", - "Tracks", - "Type", +RegistryEvent = Annotated[ + RegistryEvent1 + | RegistryEvent2 + | RegistryEvent3 + | RegistryEvent4 + | RegistryEvent5 + | RegistryEvent6 + | RegistryEvent7 + | RegistryEvent8 + | RegistryEvent9 + | RegistryEvent10 + | RegistryEvent11 + | RegistryEvent12 + | RegistryEvent13 + | RegistryEvent14 + | RegistryEvent15 + | RegistryEvent16 + | RegistryEvent17 + | RegistryEvent18 + | RegistryEvent19 + | RegistryEvent20, + Field( + description='A single cursor-ordered event from the AgenticAdvertising.org registry change feed. Events are denormalized so RegistrySync clients can update local property, agent, publisher, and authorization indexes without re-reading the full registry. The discriminator is event_type; each branch defines the event-specific payload shape. See specs/registry-change-feed.md.', + discriminator='event_type', + title='Registry Event', + ), ] diff --git a/src/adcp/types/domains/core/registry_feed_response.py b/src/adcp/types/domains/core/registry_feed_response.py index e2184ae98..7ec6e80bf 100644 --- a/src/adcp/types/domains/core/registry_feed_response.py +++ b/src/adcp/types/domains/core/registry_feed_response.py @@ -1,18 +1,62 @@ -"""Types declared by the AdCP ``core/registry_feed_response`` schema. +# generated by datamodel-codegen: +# filename: core/registry_feed_response.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.registry_feed_response import +from typing import Annotated +from uuid import UUID -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AwareDatetime, ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import registry_event + + +class Freshness(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + generated_at: Annotated[ + AwareDatetime, Field(description='Server timestamp when this feed page was generated.') + ] + latest_event_created_at: Annotated[ + AwareDatetime | None, + Field( + description='Newest event creation timestamp currently visible in the feed for the requested type filter. Null when no matching event exists inside retention.' + ), + ] + lag_seconds: Annotated[ + SchemaInt | None, + Field( + description='Seconds between generated_at and latest_event_created_at. Null when no matching event exists.', + ge=0, + ), + ] + retention_days: Annotated[ + SchemaInt, + Field(description='Number of days the registry retains feed cursors and events.', ge=1), + ] -from adcp.types.generated_poc.core.registry_feed_response import Freshness, RegistryFeedResponse -# Explicit exports -__all__ = ["Freshness", "RegistryFeedResponse"] +class RegistryFeedResponse(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + events: list[registry_event.RegistryEvent] + cursor: Annotated[ + UUID | None, + Field( + description='Pass this value as cursor on the next request to continue polling. Null only when the feed has no events and no prior cursor.' + ), + ] + has_more: Annotated[ + StrictBool, + Field(description='True when more events are immediately available after cursor.'), + ] + freshness: Annotated[ + Freshness, + Field( + description='Consumer-visible feed freshness metadata for the requested type filter.' + ), + ] diff --git a/src/adcp/types/domains/core/reporting_adjustment.py b/src/adcp/types/domains/core/reporting_adjustment.py index ca8d9ee53..b0bc57966 100644 --- a/src/adcp/types/domains/core/reporting_adjustment.py +++ b/src/adcp/types/domains/core/reporting_adjustment.py @@ -1,22 +1,82 @@ -"""Types declared by the AdCP ``core/reporting_adjustment`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_adjustment.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.reporting_adjustment import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import reporting_control_total + + +class ReasonCode(StrEnum): + invalid_traffic = 'invalid_traffic' + late_attribution = 'late_attribution' + source_correction = 'source_correction' + mapping_correction = 'mapping_correction' + commercial_adjustment = 'commercial_adjustment' + other = 'other' + + +class AccountingPeriod(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + start: AwareDatetime + end: AwareDatetime -from adcp.types.generated_poc.core.reporting_adjustment import ( - AccountingPeriod, - ReasonCode, - ReportingAdjustment, -) -# Explicit exports -__all__ = ["AccountingPeriod", "ReasonCode", "ReportingAdjustment"] +class ReportingAdjustment(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + reporting_adjustment_id: Annotated[ + str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') + ] + adjusts_reporting_revision_id: Annotated[ + str, + Field( + description='Exact immutable official revision whose billing-purpose evidence/control totals are corrected. External billing systems MAY retain this identifier as supporting evidence.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] + reason_code: Annotated[ + ReasonCode, + Field(description='Stable machine-readable reason for the post-official correction.'), + ] + reason_detail: Annotated[ + str | None, + Field( + description='Human-readable explanation. Treat as untrusted data, never agent or LLM instructions.', + max_length=1024, + min_length=1, + ), + ] = None + accounting_period: Annotated[ + AccountingPeriod, + Field( + description='Period derived from the pinned billing calendar and correction policy. It is evidence metadata only: it does not authorize reopening books or altering invoices.' + ), + ] + control_total_deltas: Annotated[ + list[reporting_control_total.ReportingControlTotal], + Field( + description="Signed deltas to apply to the named official control totals. Names and units use the adjusted revision's pinned report definition. Names MUST be unique.", + min_length=1, + ), + ] + canonical_adjustment_sha256: Annotated[ + str | None, + Field( + description='SHA-256 of the RFC 8785 JCS serialization of this adjustment with canonical_adjustment_sha256 omitted. Reconciled Billing consumers recompute this digest before accepting or rejecting the adjustment.', + pattern='^[A-Fa-f0-9]{64}$', + ), + ] = None + correction_observed_at: AwareDatetime + created_at: AwareDatetime diff --git a/src/adcp/types/domains/core/reporting_adjustment_receipt.py b/src/adcp/types/domains/core/reporting_adjustment_receipt.py index 8e98e1659..4efbdd996 100644 --- a/src/adcp/types/domains/core/reporting_adjustment_receipt.py +++ b/src/adcp/types/domains/core/reporting_adjustment_receipt.py @@ -1,22 +1,59 @@ -"""Types declared by the AdCP ``core/reporting_adjustment_receipt`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_adjustment_receipt.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.reporting_adjustment_receipt import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.reporting_adjustment_receipt import ( - ReportingAdjustmentReceipt, - ReportingAdjustmentRejectionCode, - Status, -) +class Status(StrEnum): + accepted = 'accepted' + rejected = 'rejected' + + +class ReportingAdjustmentRejectionCode(ScalarStr): + __slots__ = () + _constraints = {'max_length': 128, 'min_length': 1, 'pattern': '^[A-Z][A-Z0-9_]*$'} + -# Explicit exports -__all__ = ["ReportingAdjustmentReceipt", "ReportingAdjustmentRejectionCode", "Status"] +class ReportingAdjustmentReceipt(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + reporting_receipt_id: Annotated[ + str, Field(max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$') + ] + reporting_adjustment_id: Annotated[ + str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') + ] + adjusts_reporting_revision_id: Annotated[ + str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') + ] + supersedes_reporting_receipt_id: Annotated[ + str | None, + Field( + description='Optional immutable rejected receipt replaced by this new receipt for the same adjustment. Accepted current receipts are terminal.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] = None + status: Status + observed_adjustment_sha256: Annotated[ + str, + Field( + description='Digest recomputed from the adjustment using its canonical evidence rule.', + pattern='^[A-Fa-f0-9]{64}$', + ), + ] + rejection_codes: Annotated[ + list[ReportingAdjustmentRejectionCode] | None, Field(min_length=1) + ] = None + observed_at: AwareDatetime + received_at: AwareDatetime | None = None diff --git a/src/adcp/types/domains/core/reporting_canonical_content_digest.py b/src/adcp/types/domains/core/reporting_canonical_content_digest.py index 05750daa2..84d8f7272 100644 --- a/src/adcp/types/domains/core/reporting_canonical_content_digest.py +++ b/src/adcp/types/domains/core/reporting_canonical_content_digest.py @@ -1,20 +1,27 @@ -"""Types declared by the AdCP ``core/reporting_canonical_content_digest`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_canonical_content_digest.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.reporting_canonical_content_digest import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field -from adcp.types.generated_poc.core.reporting_canonical_content_digest import ( - ReportingCanonicalContentDigest, -) -# Explicit exports -__all__ = ["ReportingCanonicalContentDigest"] +class ReportingCanonicalContentDigest(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + regex_engine="python-re", + ) + algorithm: Literal['sha256'] = 'sha256' + value: Annotated[str, Field(pattern='^[A-Fa-f0-9]{64}$')] + canonicalization_id: Annotated[str, Field(max_length=128, min_length=1)] + canonicalization_uri: Annotated[ + AnyUrl, + Field( + description='Location of the exact immutable canonicalization contract. Consumers verify canonicalization_sha256 before applying it.' + ), + ] + canonicalization_sha256: Annotated[str, Field(pattern='^[A-Fa-f0-9]{64}$')] diff --git a/src/adcp/types/domains/core/reporting_canonicalization_contract.py b/src/adcp/types/domains/core/reporting_canonicalization_contract.py index 9ae2e9e2e..c33e43797 100644 --- a/src/adcp/types/domains/core/reporting_canonicalization_contract.py +++ b/src/adcp/types/domains/core/reporting_canonicalization_contract.py @@ -1,32 +1,94 @@ -"""Types declared by the AdCP ``core/reporting_canonicalization_contract`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_canonicalization_contract.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.reporting_canonicalization_contract import +from typing import Annotated, Any, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field + + +class EmptyReport(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + name: Annotated[str, Field(max_length=128, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,128}$')] + purpose: Literal['empty_report'] = 'empty_report' + input_rows: Annotated[list[Any], Field(max_length=0)] + canonical_utf8_base64: Annotated[ + Literal['W10='], Field(description='Base64 of the exact UTF-8 bytes for [].') + ] = 'W10=' + sha256: Literal['4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945'] = '4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945' + + +class OrderingEncoding(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + name: Annotated[str, Field(max_length=128, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,128}$')] + purpose: Literal['ordering_encoding'] = 'ordering_encoding' + input_rows: Annotated[list[dict[str, Any]], Field(min_length=2)] + canonical_utf8_base64: Annotated[ + str, Field(description='Base64 of the exact expected canonical UTF-8 bytes.', min_length=1) + ] + sha256: Annotated[str, Field(pattern='^[A-Fa-f0-9]{64}$')] + + +class AdditionalItem(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + name: Annotated[str, Field(max_length=128, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,128}$')] + purpose: Literal['additional'] = 'additional' + input_rows: list[dict[str, Any]] + canonical_utf8_base64: Annotated[ + str, Field(description='Base64 of the exact expected canonical UTF-8 bytes.', min_length=1) + ] + sha256: Annotated[str, Field(pattern='^[A-Fa-f0-9]{64}$')] + + +class GoldenVectors(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + empty_report: Annotated[EmptyReport, Field(title='EmptyReportGoldenVector')] + ordering_encoding: Annotated[OrderingEncoding, Field(title='OrderingEncodingGoldenVector')] + additional: list[AdditionalItem] | None = None + + +class ReportingPrimaryKey(ScalarStr): + __slots__ = () + _constraints = {'max_length': 128, 'min_length': 1} + _json_schema_extra = {'title': 'Reporting Primary Key'} -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.reporting_canonicalization_contract import ( - AdditionalItem, - EmptyReport, - GoldenVectors, - OrderingEncoding, - ReportingCanonicalizationContract, - ReportingPrimaryKey, -) - -# Explicit exports -__all__ = [ - "AdditionalItem", - "EmptyReport", - "GoldenVectors", - "OrderingEncoding", - "ReportingCanonicalizationContract", - "ReportingPrimaryKey", -] +class ReportingCanonicalizationContract(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + contract_version: Literal['1.0'] = '1.0' + media_type: Literal['application/vnd.adcp.reporting-canonicalization+json'] = 'application/vnd.adcp.reporting-canonicalization+json' + algorithm: Literal['adcp_jcs_rows_v1'] = 'adcp_jcs_rows_v1' + schema_sha256: Annotated[ + str, + Field( + description='Digest of the exact row schema to which this contract applies.', + pattern='^[A-Fa-f0-9]{64}$', + ), + ] + primary_keys: Annotated[ + list[ReportingPrimaryKey], + Field( + description="Ordered scalar fields used to sort rows and reject duplicate logical rows. This MUST equal the offering's primary_keys.", + min_length=1, + ), + ] + golden_vectors: Annotated[ + GoldenVectors, + Field( + description='Named cross-language conformance cases: exactly one empty_report vector, exactly one ordering_encoding vector, and an optional list of additional vectors.' + ), + ] diff --git a/src/adcp/types/domains/core/reporting_capabilities.py b/src/adcp/types/domains/core/reporting_capabilities.py index a77e019d3..36f5a4fa2 100644 --- a/src/adcp/types/domains/core/reporting_capabilities.py +++ b/src/adcp/types/domains/core/reporting_capabilities.py @@ -1,22 +1,219 @@ -"""Types declared by the AdCP ``core/reporting_capabilities`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_capabilities.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.reporting_capabilities import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictBool -from adcp.types.generated_poc.core.reporting_capabilities import ( - DateRangeSupport, - ReportingCapabilities, - VendorMetric, +from ..enums import available_metric, reporting_frequency +from ..enums import vendor_relationship as vendor_relationship_1 +from . import ( + brand_ref, + demographic_reporting_capability, + geo_breakdown_support, + measurement_window, + reporting_delivery_offering_id, + spot_reporting_capability, + vendor_metric_id, ) -# Explicit exports -__all__ = ["DateRangeSupport", "ReportingCapabilities", "VendorMetric"] + +class DateRangeSupport(StrEnum): + date_range = 'date_range' + lifetime_only = 'lifetime_only' + + +class VendorMetric(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + vendor: Annotated[ + brand_ref.BrandReference, + Field( + description="Vendor that defines and computes this metric. The vendor's `brand.json` is the discovery anchor for the measurement agent (entry with `type: 'measurement'` in the `agents[]` array); the metric's standard alignment, accreditations, and methodology live at that agent's `get_adcp_capabilities.measurement.metrics[]` and are not duplicated inline here." + ), + ] + metric_id: Annotated[ + vendor_metric_id.VendorMetricId, + Field( + description="Identifier for the metric within the vendor's vocabulary (e.g., `attention_units`, `gco2e_per_impression`, `demographic_reach`)." + ), + ] + vendor_relationship: Annotated[ + vendor_relationship_1.VendorRelationship | None, + Field( + description="Relationship between this product's seller and `vendor` — `first_party` (the seller or its own measurement agent produced the number), `affiliated` (distinct legal entity under common ownership or control: parent/subsidiary, controlling stake, joint venture, disclosed board overlap), or `third_party` (independent vendor). Defined on structural facts only — a commercial dependency does not make a vendor `affiliated`; see the enum. A property of the (seller, vendor) pair, which is why it is declared here rather than on the vendor's catalog. REQUIRED whenever `vendor` resolves to the seller's own `brand.json` or to an entity under common ownership or control with the seller — the self-measurement case is permitted only when it is disclosed, so a seller MUST NOT leave the field absent and let a buyer guess. RECOMMENDED for unrelated third-party vendors. Absence means *undeclared*: buyers MUST treat an absent value as unknown, never as `third_party`, and MAY exclude undeclared rows from reconciliation. This is a relationship disposition, not a trust ranking — quality and independence-of-incentive questions belong to the vendor catalog's `accreditations[]` and `standard_reference`, not to this field. Methodology (sensor, panel, model, instrument) is NOT declared here — it lives at the vendor catalog's `description`, `methodology_url`, `methodology_version`, `standard_reference`, and `accreditations[]`. MAY be echoed on `vendor_metric_values[].vendor_relationship` in delivery." + ), + ] = None + + +class ReportingCapabilities(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + available_reporting_frequencies: Annotated[ + list[reporting_frequency.ReportingFrequency], + Field(description='Supported reporting frequency options', min_length=1), + ] + expected_delay_minutes: Annotated[ + SchemaInt, + Field( + description='Expected delay in minutes before reporting data becomes available (e.g., 240 for 4-hour delay)', + examples=[240, 300, 1440], + ge=0, + ), + ] + timezone: Annotated[ + str, + Field( + description="Timezone for this product's reporting periods. Use 'UTC' or an IANA timezone (e.g., 'America/New_York'). This explicit reporting clock may equal Account.timezone or differ when the upstream platform reports on a separate boundary, so buyers MUST NOT infer it from Account.timezone. It is the reporting timezone for this product's delivery reporting: get_media_buy_delivery start_date, end_date, and daily_breakdown dates are calendar dates in it, and reporting_period boundaries and daily, weekly, or monthly windows fall on its calendar boundaries. Buyers MUST use this value for daily/monthly report alignment.", + examples=['UTC', 'America/New_York', 'Europe/London', 'America/Los_Angeles'], + ), + ] + supports_webhooks: Annotated[ + StrictBool, + Field(description='Whether this product supports webhook-based reporting notifications'), + ] + reporting_delivery_offering_ids: Annotated[ + list[reporting_delivery_offering_id.ReportingDeliveryOfferingId] | None, + Field( + description='Product-scoped subset of get_adcp_capabilities.media_buy.reporting_delivery.offerings[].offering_id that packages using this product can satisfy. This binds seller-wide managed-delivery offerings to product/package eligibility. An empty array explicitly declares no managed offering; absence means product-level applicability is unknown and MUST NOT be inferred from the seller-wide list. Account, seat, credential, or provider constraints may narrow support further during sync_accounts validation.' + ), + ] = None + available_metrics: Annotated[ + list[available_metric.AvailableMetric], + Field( + description="Metrics available in reporting. Impressions and spend are always implicitly included. When a creative format declares reported_metrics, buyers receive the intersection of these product-level metrics and the format's reported_metrics.", + examples=[ + ['impressions', 'spend', 'clicks', 'completed_views'], + ['impressions', 'spend', 'conversions'], + ], + ), + ] + vendor_metrics: Annotated[ + list[VendorMetric] | None, + Field( + description="Vendor-defined metrics this product can report, beyond the closed `available_metrics` enum. Each entry is a pointer (`{ vendor, metric_id }`) into the vendor's metric catalog — the canonical definition (standard alignment, accreditations, methodology, unit, human-readable description) lives at the vendor's `get_adcp_capabilities.measurement.metrics[]`, queried once per vendor when needed. Use this for proprietary metrics like attention scores, emissions, panel-based demographics, or platform-native social metrics not yet in the standard enum. Sellers populate values in delivery via `delivery-metrics.json#/properties/vendor_metric_values`. The metric is identified by the tuple `(vendor, metric_id)`; identifiers are namespaced by the vendor, so the same `metric_id` may mean different things in different vendors' vocabularies. Semantic uniqueness key is `(vendor.domain, vendor.brand_id, metric_id)`; sellers MUST de-duplicate before emission and MUST NOT declare the same vendor metric twice. Buyers MAY treat duplicate `(vendor, metric_id)` rows as a seller-side conformance bug. (JSON Schema `uniqueItems` is not used here because BrandRef carries optional fields whose absence/presence would defeat deep-equal — uniqueness is on the semantic key, enforced at build/validation time on the seller side.) Promotion path: when the industry converges on a metric via a published standard, the spec adds it to the closed `available_metrics` enum and the vendor extensions become historical aliases. The `vendor` MAY resolve to the selling party's own `brand.json` — a seller MAY be its own measurement vendor (DOOH sensor networks, retail-media closed loops, walled gardens) provided it publishes the metric in an `agents[type='measurement']` catalog like any other vendor and declares the relationship via `vendor_relationship`; the catalog contract is not relaxed for first-party measurement." + ), + ] = None + supports_creative_breakdown: Annotated[ + StrictBool | None, + Field( + description='Whether this product supports creative-level metric breakdowns in delivery reporting (by_creative within by_package)' + ), + ] = None + supports_format_breakdown: Annotated[ + StrictBool | None, + Field( + description='Whether this product supports canonical creative-format breakdowns in GET delivery reporting (by_format within by_package, keyed by format_kind). This is independent from supports_creative_breakdown because a seller may expose aggregate format-grain reporting without exposing individual creative performance.' + ), + ] = None + supports_keyword_breakdown: Annotated[ + StrictBool | None, + Field( + description='Whether this product supports keyword-level metric breakdowns in delivery reporting (by_keyword within by_package)' + ), + ] = None + supports_geo_breakdown: Annotated[ + geo_breakdown_support.GeographicBreakdownSupport | None, + Field( + description='Geographic breakdown support for this product. Declares which geo levels and systems are available for by_geo reporting within by_package.' + ), + ] = None + supports_device_type_breakdown: Annotated[ + StrictBool | None, + Field( + description='Whether this product supports device type breakdowns in delivery reporting (by_device_type within by_package)' + ), + ] = None + supports_device_platform_breakdown: Annotated[ + StrictBool | None, + Field( + description='Whether this product supports device platform breakdowns in delivery reporting (by_device_platform within by_package)' + ), + ] = None + supports_audience_breakdown: Annotated[ + StrictBool | None, + Field( + description='Whether this product supports audience segment breakdowns in delivery reporting (by_audience within by_package)' + ), + ] = None + supports_demographic_breakdown: Annotated[ + demographic_reporting_capability.DemographicReportingCapability | None, + Field( + description='Product-scoped demographic breakdown support for by_demographic reporting. Declares reportable age ranges and measurement systems independently from demographic targeting execution.' + ), + ] = None + supports_placement_breakdown: Annotated[ + StrictBool | None, + Field( + description='Whether this product supports placement breakdowns in delivery reporting (by_placement within by_package)' + ), + ] = None + supports_property_breakdown: Annotated[ + StrictBool | None, + Field( + description='Whether this product supports property breakdowns in delivery reporting (by_property within by_package).' + ), + ] = None + supports_collection_breakdown: Annotated[ + StrictBool | None, + Field( + description='Whether this product supports collection breakdowns in delivery reporting (by_collection within by_package).' + ), + ] = None + supports_installment_breakdown: Annotated[ + StrictBool | None, + Field( + description='Whether this product supports installment breakdowns in delivery reporting (by_installment within by_package).' + ), + ] = None + supports_collection_property_breakdown: Annotated[ + StrictBool | None, + Field( + description='Whether this product supports collection × property intersection reporting (by_collection_property within by_package).' + ), + ] = None + supports_installment_property_breakdown: Annotated[ + StrictBool | None, + Field( + description='Whether this product supports installment × property intersection reporting (by_installment_property within by_package).' + ), + ] = None + supports_placement_property_breakdown: Annotated[ + StrictBool | None, + Field( + description='Whether this product supports placement × property intersection reporting (by_placement_property within by_package).' + ), + ] = None + supports_spot_breakdown: Annotated[ + spot_reporting_capability.SpotReportingCapability | None, + Field( + description='Spot-level as-run airing-log support and metrics available at spot grain for broadcast TV, radio, and other scheduled inventory.' + ), + ] = None + date_range_support: Annotated[ + DateRangeSupport, + Field( + description="Whether delivery data can be filtered to arbitrary date ranges. 'date_range' means the platform supports start_date/end_date parameters. 'lifetime_only' means the platform returns campaign lifetime totals and date range parameters are not accepted." + ), + ] + windowed_pull_granularities: Annotated[ + list[reporting_frequency.ReportingFrequency] | None, + Field( + description='Granularities at which this product honors per-window pulls on get_media_buy_delivery (via request `time_granularity` + `include_window_breakdown: true`). Closes the GET-side half of the snapshot/log two-paths-parity contract for data-bearing events: a buyer who missed a webhook fire at any granularity listed here can reconstruct an identical payload by polling. Capability-scoped MUST — sellers MUST honor pulls at any granularity declared here, and MUST return UNSUPPORTED_GRANULARITY for pulls outside the set. Sellers MAY emit higher-frequency webhooks than they expose for pull (common where the webhook is a Kafka tap and historical reads go through a warehouse with coarser granularity); buyers see the gap up front via this capability and treat the webhook as primary for those frequencies. Absent or empty means the product only supports cumulative date-range pulls and full per-window recovery via GET is unavailable — see snapshot-and-log Rule 4.', + examples=[['daily'], ['hourly', 'daily'], ['hourly', 'daily', 'monthly']], + ), + ] = None + measurement_windows: Annotated[ + list[measurement_window.MeasurementWindow] | None, + Field( + description='Measurement maturation stages available for this product. Used by any channel where billing-grade data is produced in phases rather than arriving final on day one. Examples: broadcast/linear TV (Live → C3 → C7 DVR accumulation), DOOH (tentative plays → post-IVT/fraud-check final), digital with IVT filtering (raw → GIVT filtered → SIVT filtered), podcast (7-day downloads → 30-day downloads). Each window defines an accumulation period and expected data availability. When present, delivery reports reference a specific window_id. Sellers whose data is final on first delivery typically omit this.', + min_length=1, + ), + ] = None diff --git a/src/adcp/types/domains/core/reporting_consumer_status.py b/src/adcp/types/domains/core/reporting_consumer_status.py index d3842f856..b8f56908b 100644 --- a/src/adcp/types/domains/core/reporting_consumer_status.py +++ b/src/adcp/types/domains/core/reporting_consumer_status.py @@ -1,24 +1,164 @@ -"""Types declared by the AdCP ``core/reporting_consumer_status`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_consumer_status.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.reporting_consumer_status import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.reporting_consumer_status import ( - ConsumerStatus, - FailureCode, - MismatchCode, - Period, - ReportingConsumerStatus, -) +class Period(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + start: AwareDatetime + end: AwareDatetime + source_timezone: Annotated[str, Field(min_length=1)] + + +class ConsumerStatus(StrEnum): + received = 'received' + obligation_missing = 'obligation_missing' + revision_missing = 'revision_missing' + unreadable = 'unreadable' + content_mismatch = 'content_mismatch' + + +class MismatchCode(StrEnum): + scope_media_buy_missing = 'scope_media_buy_missing' + coverage_short = 'coverage_short' + metric_missing = 'metric_missing' + schema_nonconformant = 'schema_nonconformant' + currency_mismatch = 'currency_mismatch' + period_mismatch = 'period_mismatch' + + +class FailureCode(StrEnum): + access_denied = 'access_denied' + resource_not_found = 'resource_not_found' + integrity_mismatch = 'integrity_mismatch' + reader_incompatible = 'reader_incompatible' + transport_failed = 'transport_failed' + -# Explicit exports -__all__ = ["ConsumerStatus", "FailureCode", "MismatchCode", "Period", "ReportingConsumerStatus"] +class ReportingConsumerStatus(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + reporting_status_id: Annotated[ + str, + Field( + description='Consumer-issued immutable identity for this status statement. Exact retries reuse the ID and content; changed status uses a new ID and supersedes_reporting_status_id.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + supersedes_reporting_status_id: Annotated[ + str | None, + Field( + description="The authenticated consumer's current status leaf replaced by this statement. It must identify the same account, configuration generation, report definition, and period.", + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] = None + delivery_config_id: Annotated[ + str, Field(max_length=64, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,64}$') + ] + delivery_config_version: Annotated[SchemaInt, Field(ge=1)] + report_definition_id: Annotated[ + str, + Field( + description='Exact immutable report definition accepted with the configuration generation, preventing unlike reporting promises from sharing a status chain.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] + period: Annotated[ + Period, + Field( + description='Expected half-open reporting period derived from the accepted configuration generation. This identity works even when the seller omitted the corresponding obligation.' + ), + ] + reporting_obligation_id: Annotated[ + str | None, + Field( + description='Seller-issued obligation identity when one was visible. Omitted when the consumer is reporting a missing obligation.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] = None + reporting_revision_id: Annotated[ + str | None, + Field( + description='Exact revision successfully consumed or found unreadable. Omitted when no required revision was available.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] = None + observed_revision_content_sha256: Annotated[ + str | None, + Field( + description='revision_content_sha256 independently recomputed from the exact consumed Core revision binding. Required for received and content_mismatch, where it proves which exact revision content the consumer read; unlike a Reconciled Billing receipt it carries no materialization evidence, row totals, canonical digest, or billing acceptance.', + pattern='^[A-Fa-f0-9]{64}$', + ), + ] = None + consumer_status: Annotated[ + ConsumerStatus, + Field( + description='received means the exact revision content was successfully consumed; obligation_missing means the independently expected period was absent from the seller ledger; revision_missing means the obligation existed but no required revision was available after expected_at; unreadable means a named revision was advertised but its exact content could not be consumed; content_mismatch means the exact revision content was read but contradicts a fact the accepted configuration generation already fixed, named by the closed mismatch_code. None of these values reconciles billing evidence, and content_mismatch in particular is not a measurement dispute.' + ), + ] + status_as_of: Annotated[ + AwareDatetime, + Field( + description='When the consumer established this status. For received, this is when the named revision first became consumable to this consumer; sellers use it as buyer-attributed arrival evidence rather than silently substituting publication time.' + ), + ] + mismatch_code: Annotated[ + MismatchCode | None, + Field( + description="Closed reason the consumed revision contradicts the accepted configuration generation. Each value is decidable from the obligation, the pinned report definition, and the revision itself, with no reference to either party's own measurement. scope_media_buy_missing: a media buy frozen in the obligation's media_buy_ids denominator is absent from the revision and is not represented by an explicit zero row, so the revision cannot distinguish zero delivery from an omitted buy. coverage_short: the revision covers fewer packages than the obligation's frozen coverage.covered_package_ids claims. metric_missing: a metric named in the pinned report definition's metrics[].name is absent from the revision. schema_nonconformant: rows do not validate against the reporting profile's pinned schema_uri and schema_sha256. currency_mismatch: a value's unit disagrees with the unit the pinned report definition fixed for that metric, or a control total's unit disagrees with the profile-defined unit for that name. period_mismatch: the revision carries a time dimension declared by the pinned grain whose values fall outside the obligation's half-open period. Precedence when more than one applies: schema_nonconformant is used only when the failure is structural validation against the pinned schema; a metric that is simply absent uses metric_missing even when the pinned schema declares it required. Each names a contract fact already fixed by the accepted generation, never a difference of opinion about counts. Agents dispatch on this value, not on prose." + ), + ] = None + failure_code: Annotated[ + FailureCode | None, + Field( + description='Typed reason a named revision was unreadable. Agents dispatch on this value, not prose or provider response bodies.' + ), + ] = None + consumer_commit_ref: Annotated[ + str | None, + Field( + description='Optional opaque, non-secret consumer checkpoint, transaction, or load reference. It is operational evidence, not authorization, a credential, a URL, or instructions; receivers compare or display it as inert text and never dereference or execute it.', + max_length=512, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,512}$', + ), + ] = None + seller_ledger_snapshot_id: Annotated[ + str | None, + Field( + description='Optional seller-issued get_reporting_status snapshot on which this statement was based. It is evidence context, not consumer authority over that snapshot.', + max_length=255, + min_length=1, + ), + ] = None + seller_ledger_as_of: Annotated[ + AwareDatetime | None, + Field( + description='ledger_as_of echoed from seller_ledger_snapshot_id. Present if and only if seller_ledger_snapshot_id is present.' + ), + ] = None + recorded_at: Annotated[ + AwareDatetime | None, + Field(description='When the seller durably recorded this immutable statement.'), + ] = None diff --git a/src/adcp/types/domains/core/reporting_control_total.py b/src/adcp/types/domains/core/reporting_control_total.py index 8a7b0848d..fdbd69365 100644 --- a/src/adcp/types/domains/core/reporting_control_total.py +++ b/src/adcp/types/domains/core/reporting_control_total.py @@ -1,22 +1,69 @@ -"""Types declared by the AdCP ``core/reporting_control_total`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_control_total.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.reporting_control_total import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.reporting_control_total import ( - ReportingControlTotal, - ReportingControlTotal1, - ReportingControlTotal2, -) +class ReportingControlTotal1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + name: Annotated[ + str, Field(max_length=128, min_length=1, pattern='^[A-Za-z][A-Za-z0-9_.:-]{0,127}$') + ] + value: Annotated[ + str, + Field( + description='Canonical base-10 integer with no exponent, grouping separator, decimal point, or insignificant leading zeroes.', + pattern='^-?(?:0|[1-9][0-9]*)$', + ), + ] + value_type: Literal['integer'] = 'integer' + unit: Annotated[ + str | None, + Field( + description='Profile-defined unit such as impressions or an ISO 4217 currency code.', + max_length=32, + min_length=1, + ), + ] = None + + +class ReportingControlTotal2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + name: Annotated[ + str, Field(max_length=128, min_length=1, pattern='^[A-Za-z][A-Za-z0-9_.:-]{0,127}$') + ] + value: Annotated[ + str, + Field( + description='Canonical base-10 decimal with no exponent, grouping separator, or insignificant leading zeroes.', + pattern='^-?(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?$', + ), + ] + value_type: Literal['decimal'] = 'decimal' + unit: Annotated[ + str | None, + Field( + description='Profile-defined unit such as impressions or an ISO 4217 currency code.', + max_length=32, + min_length=1, + ), + ] = None + -# Explicit exports -__all__ = ["ReportingControlTotal", "ReportingControlTotal1", "ReportingControlTotal2"] +ReportingControlTotal = Annotated[ + ReportingControlTotal1 | ReportingControlTotal2, + Field( + description='One profile-defined aggregate used to reconcile a reporting revision without rereading every row. Names and units are defined by the immutable report definition. Values use canonical strings so currency and large integer comparisons are exact across SDKs.', + title='Reporting Control Total', + ), +] diff --git a/src/adcp/types/domains/core/reporting_coverage.py b/src/adcp/types/domains/core/reporting_coverage.py index 824a4fc5f..b3ada0b0c 100644 --- a/src/adcp/types/domains/core/reporting_coverage.py +++ b/src/adcp/types/domains/core/reporting_coverage.py @@ -1,32 +1,79 @@ -"""Types declared by the AdCP ``core/reporting_coverage`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_coverage.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.reporting_coverage import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import AwareDatetime, ConfigDict, Field + + +class Status(StrEnum): + full = 'full' + partial = 'partial' + none = 'none' + unknown = 'unknown' + + +class Reason(StrEnum): + offering_unsupported = 'offering_unsupported' + account_entitlement_unavailable = 'account_entitlement_unavailable' + credential_scope_insufficient = 'credential_scope_insufficient' + provider_limitation = 'provider_limitation' + capability_unknown = 'capability_unknown' + + +class ReportingMediaBuyId(ScalarStr): + __slots__ = () + _constraints = {'min_length': 1} + _json_schema_extra = {'title': 'Reporting Media Buy ID'} + + +class ReportingPackageId(ScalarStr): + __slots__ = () + _constraints = {'min_length': 1} + _json_schema_extra = {'title': 'Reporting Package ID'} + + +class Limitation(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + reason: Reason + media_buy_id: ReportingMediaBuyId + package_ids: Annotated[list[ReportingPackageId] | None, Field(min_length=1)] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.reporting_coverage import ( - Limitation, - Reason, - ReportingCoverage, - ReportingMediaBuyId, - ReportingPackageId, - Status, -) - -# Explicit exports -__all__ = [ - "Limitation", - "Reason", - "ReportingCoverage", - "ReportingMediaBuyId", - "ReportingPackageId", - "Status", -] +class ReportingCoverage(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + status: Status + evaluated_at: AwareDatetime + media_buy_ids: Annotated[ + list[ReportingMediaBuyId], + Field( + description='Exact media-buy denominator, including unsupported and unknown buys. An empty array is an explicitly evaluated zero-buy scope.' + ), + ] + fully_covered_media_buy_ids: list[ReportingMediaBuyId] + partially_covered_media_buy_ids: list[ReportingMediaBuyId] + unsupported_media_buy_ids: list[ReportingMediaBuyId] + unknown_media_buy_ids: list[ReportingMediaBuyId] + package_ids: Annotated[ + list[ReportingPackageId], + Field(description='Exact package denominator for the evaluated media buys.'), + ] + covered_package_ids: list[ReportingPackageId] + unsupported_package_ids: list[ReportingPackageId] + unknown_package_ids: list[ReportingPackageId] + limitations: Annotated[ + list[Limitation], + Field( + description='Stable reasons that some requested scope is not covered by the exact selected offering. These are capability facts, not delivery failures.' + ), + ] diff --git a/src/adcp/types/domains/core/reporting_dataset_share_destination.py b/src/adcp/types/domains/core/reporting_dataset_share_destination.py index 582e9695b..c67138f1b 100644 --- a/src/adcp/types/domains/core/reporting_dataset_share_destination.py +++ b/src/adcp/types/domains/core/reporting_dataset_share_destination.py @@ -1,32 +1,85 @@ -"""Types declared by the AdCP ``core/reporting_dataset_share_destination`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_dataset_share_destination.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.reporting_dataset_share_destination import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field + + +class ReportingDatasetShareDestination1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + mode: Literal['existing'] = 'existing' + destination_ref: Annotated[ + str, + Field( + description='Seller-issued immutable recipient/destination-generation reference returned by sync_agent_configuration, an earlier sync, or bilateral setup.', + max_length=255, + min_length=1, + ), + ] + + +class Provider(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + domain: Annotated[ + str, Field(pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$') + ] + + +class ReportingCloud(StrEnum): + aws = 'aws' + azure = 'azure' + gcp = 'gcp' + + +class Recipient(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + identity: Annotated[str, Field(max_length=512, min_length=1)] + cloud: ReportingCloud | None = None + region: Annotated[str | None, Field(max_length=128, min_length=1)] = None + + +class ReportingDatasetShareDestination2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + mode: Literal['provision'] = 'provision' + provider: Annotated[ + Provider, + Field(description='Data-sharing platform, such as databricks.com or snowflake.com.'), + ] + access_mode: Annotated[ + str, + Field( + description='Provider access family, such as databricks_to_databricks, open_sharing, or secure_data_sharing.', + max_length=64, + min_length=1, + pattern='^[a-z][a-z0-9_.-]*$', + ), + ] + recipient: Annotated[ + Recipient, + Field( + description='Intended buyer principal. The identity is interpreted by the provider and access mode; for example, a Databricks sharing identifier, Snowflake organization/account pair, or Open Sharing recipient email. It is an identifier, never a credential.' + ), + ] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.reporting_dataset_share_destination import ( - Provider, - Recipient, - ReportingCloud, - ReportingDatasetShareDestination, - ReportingDatasetShareDestination1, - ReportingDatasetShareDestination2, -) - -# Explicit exports -__all__ = [ - "Provider", - "Recipient", - "ReportingCloud", - "ReportingDatasetShareDestination", - "ReportingDatasetShareDestination1", - "ReportingDatasetShareDestination2", +ReportingDatasetShareDestination = Annotated[ + ReportingDatasetShareDestination1 | ReportingDatasetShareDestination2, + Field( + description="Recipient configuration for a producer-hosted reporting share. The caller either references an existing seller-issued immutable recipient/destination generation or asks the seller to provision one for the named recipient. A destination_ref is owned by the stable authenticated principal's relationship with this seller and may be reused across accounts; each account delivery configuration separately authorizes disclosure of its feed and scope. Changing proof-bound recipient coordinates or the accepted delivery contract produces a new destination_ref. No bearer profile, token, private key, password, or other credential may appear here.", + title='Reporting Dataset Share Destination', + ), ] diff --git a/src/adcp/types/domains/core/reporting_delivery_capabilities.py b/src/adcp/types/domains/core/reporting_delivery_capabilities.py index 8b91ad7aa..6770cc64f 100644 --- a/src/adcp/types/domains/core/reporting_delivery_capabilities.py +++ b/src/adcp/types/domains/core/reporting_delivery_capabilities.py @@ -1,21 +1,169 @@ -"""Types declared by the AdCP ``core/reporting_delivery_capabilities`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_delivery_capabilities.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.reporting_delivery_capabilities import +from typing import Any +from pydantic import SerializerFunctionWrapHandler, model_serializer, model_validator -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AnyUrl, ConfigDict, EmailStr, Field, StrictBool + +from . import reporting_delivery_offering, reporting_reliability_statistics + + +class OperationsContact(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + regex_engine="python-re", + ) + url: Annotated[ + AnyUrl | None, + Field( + description='HTTPS page a human uses to open or track a reporting issue, such as a support portal or status page. Same hardened origin shape as the offering document URIs: never an IP literal, userinfo URL, loopback host, AdCP task endpoint, webhook target, or credentialed link.' + ), + ] = None + email: Annotated[ + EmailStr | None, + Field( + description='Monitored operations mailbox for reporting escalations. A role address, not an individual.', + max_length=254, + ), + ] = None + + +class ReportingDeliveryCapabilities(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + supported: Literal[True] + reliable_reporting_version: Annotated[ + Literal['1.0'] | None, + Field( + description='Explicit adoption declaration for the proper-name AdCP 3.2 Reliable Reporting contract. Presence, together with supported: true and the media_buy.reporting_delivery experimental feature gate, is the affirmative machine-readable answer. Absence denotes the earlier experimental managed-reporting shape.' + ), + ] = None + managed_delivery: Annotated[ + StrictBool | None, + Field( + description='Tier flag: this seller supports managed file, dataset-share, or warehouse delivery. Offerings whose method names a delivery pattern require this tier. When false or absent, every offering is API-delivered and Core-only.' + ), + ] = None + reconciled_billing: Annotated[ + StrictBool | None, + Field( + description='Tier flag: this seller supports canonical-digest verification and authenticated consumer receipts for both report materializations and post-official adjustments through receipt_task. Offerings with reconciliation_mode consumer_receipt and billing-grade canonicalization require this tier.' + ), + ] = None + configuration_task: Literal['sync_accounts'] | None = None + status_task: Literal['get_reporting_status'] | None = None + consumer_status_task: Annotated[ + Literal['sync_reporting_status'] | None, + Field( + description='Opt-in consumer-status loop during the published migration window, becoming required Core in the next eligible minor after that window. Buyers call this seller-hosted task to record whether each expected reporting period was received, missing, or unreadable. Buyers expose no reverse endpoint, and the status is not a billing receipt.' + ), + ] = None + revision_content_task: Annotated[ + Literal['get_media_buy_delivery'] | None, + Field( + description='Reliable Reporting exact-content read: callers select reporting_revision_id and receive immutable revision metadata plus authoritative canonical reporting_rows.' + ), + ] = None + receipt_task: Annotated[ + Literal['sync_reporting_receipts'] | None, + Field( + description='Required when reconciled_billing is true: the task consumers call to submit and read back authenticated revision and adjustment receipts.' + ), + ] = None + readiness_notification: Annotated[ + Literal['reporting.delivery_ready'] | None, + Field( + description='Optional managed-delivery-only positive-readiness doorbell. It names a materialization at a destination, so Core sellers MUST omit it.' + ), + ] = None + status_notification: Annotated[ + Literal['reporting.status_changed'] | None, + Field( + description='Optional tier-independent invalidation doorbell for health transitions in either direction, including clock-driven waiting-to-delayed and delayed-to-action_required. Valid for Core: it names no destination. Polling status_task remains the authoritative recovery path whether or not this is offered.' + ), + ] = None + ledger_notification: Annotated[ + Literal['reporting.ledger_changed'] | None, + Field( + description='Optional tier-independent invalidation for every newly committed revision or post-official adjustment, even when health does not change. Receivers repair through get_reporting_status changes_after; polling remains authoritative.' + ), + ] = None + offerings: Annotated[ + list[reporting_delivery_offering.ReportingDeliveryOffering], + Field( + description='Atomic supported feed/profile/schedule/finality/method combinations. offering_id values MUST be unique.', + min_length=1, + ), + ] + automated_recovery_window_seconds: Annotated[ + SchemaInt, + Field( + description='Maximum late interval during which a due obligation may remain delayed while automated recovery continues before action_required.', + ge=0, + ), + ] + status_retention_days: Annotated[ + SchemaInt, + Field( + description='Minimum period for which obligation, revision, and materialization metadata remain queryable.', + ge=1, + ), + ] + consumer_mismatch_escalation_seconds: Annotated[ + SchemaInt | None, + Field( + description="Maximum interval after a CONSUMER_STATUS_MISMATCH issue's opened_at during which the seller may keep that issue at a non-escalated recommended_action. After it, the issue MUST be action_required with a contact_ recommended_action naming the diagnosed responsible_party. Declaring it requires operations_contact so the escalation has a destination. Absence means the seller publishes no escalation commitment; it never means an unbounded one.", + ge=0, + ), + ] = None + operations_contact: Annotated[ + OperationsContact | None, + Field( + description='Optional non-secret human escalation path for reporting issues the protocol cannot resolve. It is display metadata for an operator, not an AdCP endpoint: agents MUST NOT dereference, probe, or send protocol traffic to these values, and they carry no authorization. Required when consumer_mismatch_escalation_seconds is advertised.' + ), + ] = None + reliability_statistics: Annotated[ + list[reporting_reliability_statistics.ReportingReliabilityStatistics] | None, + Field( + description='Optional evidence-scoped observed performance for advertised offerings. offering_id values MUST be unique and name offerings in this capability block.' + ), + ] = None + resource_retention_days: Annotated[ + SchemaInt | None, + Field( + description='Minimum period after publication for which at least one verified exact materialization remains readable to every still-authorized intended consumer.', + ge=1, + ), + ] = None + supports_webhook_activity: StrictBool | None = None + authorization_revocation_seconds: Annotated[ + SchemaInt | None, + Field( + description="Maximum delay after caller/account authorization ends before seller-controlled transport access, provider grants, and write credentials are revoked. It cannot revoke a buyer's access to data already written into a buyer-owned destination.", + ge=0, + ), + ] = None -from adcp.types.generated_poc.core.reporting_delivery_capabilities import ( - OperationsContact, - ReportingDeliveryCapabilities, -) + @model_validator(mode='after') + def _validate_reporting_tiers(self) -> ReportingDeliveryCapabilities: + if self.reconciled_billing is True and self.managed_delivery is not True: + raise ValueError('reconciled_billing requires managed_delivery') + if self.readiness_notification is not None and self.managed_delivery is not True: + raise ValueError('readiness_notification requires managed_delivery') + if self.receipt_task is not None and self.reconciled_billing is not True: + raise ValueError('receipt_task requires reconciled_billing') + return self -# Explicit exports -__all__ = ["OperationsContact", "ReportingDeliveryCapabilities"] + @model_serializer(mode='wrap') + def _omit_absent_reporting_promises( + self, handler: SerializerFunctionWrapHandler + ) -> dict[str, Any]: + return {key: value for key, value in handler(self).items() if value is not None} diff --git a/src/adcp/types/domains/core/reporting_delivery_config.py b/src/adcp/types/domains/core/reporting_delivery_config.py index 8e5ea482d..44ab8c951 100644 --- a/src/adcp/types/domains/core/reporting_delivery_config.py +++ b/src/adcp/types/domains/core/reporting_delivery_config.py @@ -1,23 +1,164 @@ -"""Types declared by the AdCP ``core/reporting_delivery_config`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_delivery_config.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.reporting_delivery_config import +from collections.abc import Mapping +from typing import Any +from pydantic import SerializerFunctionWrapHandler, model_serializer, model_validator -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AwareDatetime, ConfigDict, Field, StrictBool -from adcp.types.generated_poc.core.reporting_delivery_config import ( - AuthoritativeParty, - CoverageRequirement, - ReportingDeliveryConfiguration, - Scope, +from ..enums import reporting_finality +from . import ( + reporting_coverage, + reporting_delivery_method, + reporting_delivery_offering, + reporting_reconciliation_mode, + reporting_schedule, ) -# Explicit exports -__all__ = ["AuthoritativeParty", "CoverageRequirement", "ReportingDeliveryConfiguration", "Scope"] + +class Scope(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + all_media_buys: Literal[True] | None = None + media_buy_ids: Annotated[ + list[reporting_coverage.ReportingMediaBuyId] | None, Field(min_length=1) + ] = None + + @model_validator(mode='before') + @classmethod + def _select_reporting_scope(cls, value: Any) -> Any: + if not isinstance(value, Mapping): + return value + if 'all_media_buys' in value and 'media_buy_ids' in value: + raise ValueError('reporting scope requires exactly one selector') + if 'all_media_buys' in value and value['all_media_buys'] is not True: + raise ValueError('all_media_buys must be true') + if 'media_buy_ids' in value: + if value['media_buy_ids'] is None: + raise ValueError('media_buy_ids must be a nonempty unique list') + return value + # Preserve the typed empty-scope convenience without mutating its input. + return {**value, 'all_media_buys': True} + + @model_validator(mode='after') + def _unique_reporting_scope(self) -> Scope: + if self.media_buy_ids is not None: + if len(self.media_buy_ids) != len(set(self.media_buy_ids)): + raise ValueError('media_buy_ids must be unique') + return self + + @model_serializer(mode='wrap') + def _serialize_reporting_scope(self, handler: SerializerFunctionWrapHandler) -> dict[str, Any]: + value: dict[str, Any] = handler(self) + for name in ('all_media_buys', 'media_buy_ids'): + if value.get(name) is None: + value.pop(name, None) + return value + + +class CoverageRequirement(StrEnum): + full = 'full' + allow_partial = 'allow_partial' + + +class AuthoritativeParty(StrEnum): + seller = 'seller' + consumer = 'consumer' + + +class ReportingDeliveryConfiguration(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + delivery_config_id: Annotated[ + str, + Field( + description='Caller-selected stable identifier, unique within the authenticated caller and account.', + max_length=64, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,64}$', + ), + ] + delivery_config_version: Annotated[ + SchemaInt, + Field( + description='Caller-selected immutable semantic generation. Increment when feed/profile/scope/finality/schedule/method/destination changes; lifecycle fields may change in place.', + ge=1, + ), + ] + offering_id: Annotated[ + str, + Field( + description='Atomic reporting offering advertised by the seller that binds feed, profile, schedule, finality, and delivery support.', + max_length=128, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,128}$', + ), + ] + active: Annotated[ + StrictBool, + Field( + description='Whether new reporting obligations should use this configuration. Inactive configurations remain visible for historical resolution.' + ), + ] + feed_purpose: reporting_delivery_offering.ReportingFeedPurpose + report_definition_id: Annotated[ + str, + Field( + description='Exact immutable semantic definition selected from the offering. This makes the expected obligation identity independently derivable and prevents attribution, timezone, source-mapping, or restatement-policy drift behind a profile label.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] + reporting_profile: Annotated[ + str, + Field( + description='Versioned semantic profile for the aggregate report, such as media_buy_delivery_v1. It MUST match the selected offering.', + max_length=128, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,128}$', + ), + ] + scope: Annotated[Scope, Field(description='Media buys covered by this configuration.')] + coverage_requirement: Annotated[ + CoverageRequirement, + Field( + description='Whether every package in the resolved media-buy scope must support the exact selected offering. full fails closed when any package is unsupported or unknown. allow_partial permits publication only for the explicitly covered package denominator; every revision and status response still exposes partial coverage and MUST NOT present covered-subset totals as whole-buy totals.' + ), + ] + required_finality: Annotated[ + reporting_finality.ReportingFinality, + Field( + description='Finality the durable path must ultimately provide. Snapshot delivery may still precede an official requirement.' + ), + ] + reconciliation_mode: Annotated[ + reporting_reconciliation_mode.ReportingReconciliationMode, + Field( + description='Whether producer-side delivery evidence is sufficient or the selected consumer must submit an authenticated matching receipt. A seller-authoritative billing feed MUST use consumer_receipt.' + ), + ] + authoritative_party: Annotated[ + AuthoritativeParty | None, + Field( + description="Reserved: which party's count of this feed is authoritative. seller (the default, and the only value any 3.2 seller accepts) means the seller produces every revision and the consumer may only attest to what it consumed. consumer is reserved for the buyer-deposited billing revision task scoped to a later minor; until that task exists sellers MUST reject it with UNSUPPORTED_FEATURE. Reserving the field now keeps a future buyer-basis billing feed additive instead of breaking the billing-feed constraints. See https://github.com/adcontextprotocol/adcp/issues/7440." + ), + ] = AuthoritativeParty.seller + schedule: reporting_schedule.ReportingSchedule + method: reporting_delivery_method.ReportingDeliveryMethod | None = None + revocation_effective_at: Annotated[ + AwareDatetime | None, + Field( + description='Optional requested cutoff for deactivation. No new publication may begin after the applied cutoff; historical access is limited to the contracted recovery window.' + ), + ] = None diff --git a/src/adcp/types/domains/core/reporting_delivery_config_state.py b/src/adcp/types/domains/core/reporting_delivery_config_state.py index a7110a77c..dfe7b6cd4 100644 --- a/src/adcp/types/domains/core/reporting_delivery_config_state.py +++ b/src/adcp/types/domains/core/reporting_delivery_config_state.py @@ -1,28 +1,91 @@ -"""Types declared by the AdCP ``core/reporting_delivery_config_state`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_delivery_config_state.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.reporting_delivery_config_state import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field + +from . import reporting_coverage, reporting_delivery_config, reporting_status_issue + + +class Action(StrEnum): + grant_access = 'grant_access' + activate_recipient = 'activate_recipient' + authorize_provider = 'authorize_provider' + repair_access = 'repair_access' + + +class Setup(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + action: Action + message: Annotated[ + str, + Field( + description='Untrusted display text only. SDKs and agents dispatch only on the closed action value and never execute embedded links or instructions.', + max_length=2000, + min_length=1, + ), + ] + url: AnyUrl | None = None + expires_at: AwareDatetime | None = None + + +class ReportingDeliveryConfigLifecycleState(StrEnum): + pending_validation = 'pending_validation' + pending_setup = 'pending_setup' + ready = 'ready' + action_required = 'action_required' + inactive = 'inactive' -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.reporting_delivery_config_state import ( - Action, - ReportingDeliveryConfigLifecycleState, - ReportingDeliveryConfigurationState, - Setup, -) - -# Explicit exports -__all__ = [ - "Action", - "ReportingDeliveryConfigLifecycleState", - "ReportingDeliveryConfigurationState", - "Setup", -] +class ReportingDeliveryConfigurationState(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + configuration: reporting_delivery_config.ReportingDeliveryConfiguration + state: ReportingDeliveryConfigLifecycleState + destination_ref: Annotated[ + str | None, + Field( + description='Seller-issued immutable destination-generation reference. It is caller-scoped and reusable across separately authorized account configurations; it is not itself account authority or a bearer grant. Present when and only when the configuration selects a managed-delivery offering; a Core (API-delivered) configuration becomes ready with no destination at all.', + max_length=255, + min_length=1, + ), + ] = None + validated_at: AwareDatetime | None = None + activated_at: AwareDatetime | None = None + deactivated_at: AwareDatetime | None = None + publication_stopped_at: Annotated[ + AwareDatetime | None, + Field( + description='Applied schedule boundary at or after deactivation. No obligation whose period starts at or after this cutoff is created; earlier obligations remain owed through their SLA and recovery lifecycle.' + ), + ] = None + seller_managed_access_ends_at: Annotated[ + AwareDatetime | None, + Field( + description='End of historical access to a producer-hosted share/resource for a still-authorized principal after voluntary deactivation. Inapplicable to data already written into a buyer-owned destination.' + ), + ] = None + current_coverage: Annotated[ + reporting_coverage.ReportingCoverage | None, + Field( + description='Current effective product/package coverage for the selected offering and resolved account. This setup-time view may change as media buys or provider capabilities change; each period obligation later freezes its own authoritative coverage.' + ), + ] = None + setup: Annotated[ + Setup | None, + Field( + description='Secret-free next step when provider-side authorization or recipient activation cannot be completed automatically.' + ), + ] = None + issues: Annotated[ + list[reporting_status_issue.ReportingStatusIssue] | None, Field(min_length=1) + ] = None diff --git a/src/adcp/types/domains/core/reporting_delivery_method.py b/src/adcp/types/domains/core/reporting_delivery_method.py index 667cc46cf..ab901db2a 100644 --- a/src/adcp/types/domains/core/reporting_delivery_method.py +++ b/src/adcp/types/domains/core/reporting_delivery_method.py @@ -1,32 +1,103 @@ -"""Types declared by the AdCP ``core/reporting_delivery_method`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_delivery_method.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.reporting_delivery_method import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field + +from . import reporting_dataset_share_destination, reporting_write_destination + + +class Format(StrEnum): + jsonl = 'jsonl' + csv = 'csv' + parquet = 'parquet' + avro = 'avro' + orc = 'orc' + + +class ReportingOrchestration(StrEnum): + producer_managed = 'producer_managed' + consumer_managed = 'consumer_managed' + + +class ReportingDeliveryMethod1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + pattern: Annotated[ + Literal['file_transfer'], + Field( + description='Immutable file/object publication with a manifest-last commit boundary.' + ), + ] = 'file_transfer' + transport: Annotated[ + str, + Field( + description='Storage transport such as s3, gcs, azure_blob, or sftp.', + max_length=64, + min_length=1, + pattern='^[a-z][a-z0-9_.-]*$', + ), + ] + orchestration: ReportingOrchestration + destination: reporting_write_destination.ReportingWriteDestination + format: Annotated[Format, Field(description='Physical file format.')] + + +class ReportingDeliveryMethod2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + pattern: Annotated[ + Literal['dataset_share'], + Field( + description="Producer-hosted relation or share read through the intended recipient's access path." + ), + ] = 'dataset_share' + transport: Annotated[ + str, + Field( + description='Sharing transport such as delta_sharing, snowflake_secure_sharing, or bigquery_authorized_view.', + max_length=64, + min_length=1, + pattern='^[a-z][a-z0-9_.-]*$', + ), + ] + orchestration: ReportingOrchestration + destination: reporting_dataset_share_destination.ReportingDatasetShareDestination + + +class ReportingDeliveryMethod3(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + pattern: Annotated[ + Literal['warehouse_materialization'], + Field(description='Exact-revision publication into a warehouse relation or partition.'), + ] = 'warehouse_materialization' + transport: Annotated[ + str, + Field( + description='Warehouse or transfer transport such as bigquery, snowflake, databricks_sql, or gam_bigquery_transfer.', + max_length=64, + min_length=1, + pattern='^[a-z][a-z0-9_.-]*$', + ), + ] + orchestration: ReportingOrchestration + destination: reporting_write_destination.ReportingWriteDestination -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.reporting_delivery_method import ( - Format, - ReportingDeliveryMethod, - ReportingDeliveryMethod1, - ReportingDeliveryMethod2, - ReportingDeliveryMethod3, - ReportingOrchestration, -) - -# Explicit exports -__all__ = [ - "Format", - "ReportingDeliveryMethod", - "ReportingDeliveryMethod1", - "ReportingDeliveryMethod2", - "ReportingDeliveryMethod3", - "ReportingOrchestration", +ReportingDeliveryMethod = Annotated[ + ReportingDeliveryMethod1 | ReportingDeliveryMethod2 | ReportingDeliveryMethod3, + Field( + description='Provider-neutral durable reporting delivery method. The caller may request protocol-managed provisioning or reuse an existing seller-issued binding. Transport names are open so new platforms do not require an AdCP enum change. Credentials, bearer profiles, and private keys MUST NOT appear. Sellers implementing this schema MUST advertise media_buy.reporting_delivery in experimental_features.', + title='Reporting Delivery Method', + ), ] diff --git a/src/adcp/types/domains/core/reporting_delivery_offering.py b/src/adcp/types/domains/core/reporting_delivery_offering.py index 7d991f532..02ad6a866 100644 --- a/src/adcp/types/domains/core/reporting_delivery_offering.py +++ b/src/adcp/types/domains/core/reporting_delivery_offering.py @@ -1,38 +1,221 @@ -"""Types declared by the AdCP ``core/reporting_delivery_offering`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_delivery_offering.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.reporting_delivery_offering import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field -from adcp.types.generated_poc.core.reporting_delivery_offering import ( - DestinationMode, - Format, - Method, - ProducerIdentity, - Provider, - ReportingDeliveryOffering, - ReportingDeliveryPattern, - ReportingFeedPurpose, - ReportingProfile, +from ..enums import reporting_finality +from . import ( + reporting_canonicalization_contract, + reporting_dataset_share_destination, + reporting_delivery_method, + reporting_reconciliation_mode, + reporting_resource, + reporting_schedule_offering, ) -# Explicit exports -__all__ = [ - "DestinationMode", - "Format", - "Method", - "ProducerIdentity", - "Provider", - "ReportingDeliveryOffering", - "ReportingDeliveryPattern", - "ReportingFeedPurpose", - "ReportingProfile", -] + +class ReportingProfile(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + regex_engine="python-re", + ) + id: Annotated[str, Field(max_length=128, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,128}$')] + version: Annotated[str, Field(max_length=64, min_length=1)] + schema_uri: Annotated[ + AnyUrl, + Field( + description='Authenticated seller/provider or AdCP-registry HTTPS origin only; never an IP literal, userinfo URL, redirect target, or mutable validation authority.' + ), + ] + schema_sha256: Annotated[ + str, + Field( + description='Digest of the exact schema bytes. SDKs verify this before parsing and cache by digest.', + pattern='^[A-Fa-f0-9]{64}$', + ), + ] + schema_dialect: Annotated[ + Literal['https://json-schema.org/draft/2020-12/schema'], + Field( + description="Closed SDK-bundled dialect. The SDK never resolves a metaschema over the network, and the fetched document's $schema MUST equal this value." + ), + ] = 'https://json-schema.org/draft/2020-12/schema' + schema_ref_policy: Annotated[ + Literal['local_fragment_only'], + Field( + description='The fetched schema is a self-contained bundle. Every $ref is a local # fragment; remote and relative-document dependencies are forbidden.' + ), + ] = 'local_fragment_only' + grain: Annotated[ + str, + Field( + description='Stable description of what one logical row represents.', + max_length=128, + min_length=1, + ), + ] + primary_keys: Annotated[ + list[reporting_canonicalization_contract.ReportingPrimaryKey], Field(min_length=1) + ] + canonicalization_id: Annotated[ + str | None, + Field( + description='Rules for stable logical row ordering, value encoding, nulls, and schema used by canonical_content_digest.', + max_length=128, + min_length=1, + ), + ] = None + canonicalization_contract_version: Literal['1.0'] = '1.0' + canonicalization_media_type: Literal['application/vnd.adcp.reporting-canonicalization+json'] = ( + 'application/vnd.adcp.reporting-canonicalization+json' + ) + canonicalization_uri: Annotated[ + AnyUrl | None, + Field( + description='Retrievable exact canonicalization contract on the authenticated seller/provider or AdCP-registry origin. SDKs apply the same bounded, redirect-free SSRF controls as schema_uri and verify canonicalization_sha256 before use.' + ), + ] = None + canonicalization_sha256: Annotated[ + str | None, + Field( + description='Digest of the exact canonicalization contract identified by canonicalization_id.', + pattern='^[A-Fa-f0-9]{64}$', + ), + ] = None + + +class DestinationMode(StrEnum): + provision = 'provision' + existing = 'existing' + + +class Provider(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + domain: Annotated[ + str, Field(pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$') + ] + + +class Format(StrEnum): + jsonl = 'jsonl' + csv = 'csv' + parquet = 'parquet' + avro = 'avro' + orc = 'orc' + + +class ProducerIdentity(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + provider: Provider + identity: Annotated[str, Field(max_length=512, min_length=1)] + cloud: reporting_dataset_share_destination.ReportingCloud | None = None + region: Annotated[str | None, Field(max_length=128, min_length=1)] = None + + +class ReportingFeedPurpose(StrEnum): + pacing = 'pacing' + analytics = 'analytics' + billing = 'billing' + + +class ReportingDeliveryPattern(StrEnum): + file_transfer = 'file_transfer' + dataset_share = 'dataset_share' + warehouse_materialization = 'warehouse_materialization' + + +class Method(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + pattern: ReportingDeliveryPattern + transport: Annotated[str, Field(max_length=64, min_length=1, pattern='^[a-z][a-z0-9_.-]*$')] + orchestration: reporting_delivery_method.ReportingOrchestration + destination_modes: Annotated[list[DestinationMode], Field(min_length=1)] + provider: Provider | None = None + format: Format | None = None + access_mode: Annotated[ + str | None, Field(max_length=64, min_length=1, pattern='^[a-z][a-z0-9_.-]*$') + ] = None + producer_identity: Annotated[ + ProducerIdentity | None, + Field( + description='Seller principal a buyer grants access to for this exact buyer-hosted destination offering.' + ), + ] = None + reader_compatibility: list[reporting_resource.ReportingReaderCompatibilityItem] | None = None + + +class ReportingDeliveryOffering(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + regex_engine="python-re", + ) + offering_id: Annotated[ + str, Field(max_length=128, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,128}$') + ] + feed_purpose: ReportingFeedPurpose + report_definition_id: Annotated[ + str, + Field( + description='Immutable semantic definition for metric, grain, attribution, action-report-time, timezone/calendar, source/API mapping, and restatement/finality policy. Configurations and revisions MUST echo this exact value.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] + report_definition_uri: Annotated[ + AnyUrl, + Field( + description='Retrievable immutable reporting-report-definition.json document on the authenticated seller/provider or AdCP-registry origin.' + ), + ] + report_definition_sha256: Annotated[ + str, + Field( + description='Digest of the exact report-definition bytes. SDKs verify this before parsing and cache by digest.', + pattern='^[A-Fa-f0-9]{64}$', + ), + ] + reporting_profile: Annotated[ + ReportingProfile, + Field( + description='Machine-readable semantic and validation contract for delivered rows. The canonicalization_* fields describe the external-materialization canonical-digest contract and are required only for offerings under the reconciled_billing tier; Core and managed-delivery offerings omit those fields but every Core revision still carries the fixed RFC 8785/JCS revision-binding digest.' + ), + ] + schedule: Annotated[ + reporting_schedule_offering.ReportingScheduleOffering, + Field( + description='Period and availability SLA this offering can honor. For example, PT1H with snapshot finality explicitly advertises hourly provisional snapshots; a separate P1D official offering advertises daily finalized reporting.' + ), + ] + supported_finality: Annotated[ + list[reporting_finality.ReportingFinality], + Field( + description='Finality classes available under this exact report definition, schedule, and delivery method. snapshot is an explicit provisional capability, not inferred from poll frequency. Use separate atomic offerings when snapshot and official schedules or methods differ.', + min_length=1, + ), + ] + reconciliation_mode: Annotated[ + reporting_reconciliation_mode.ReportingReconciliationMode, + Field( + description='Receipt contract included in this atomic offering. Billing offerings MUST require consumer_receipt.' + ), + ] + method: Annotated[ + Method | None, + Field( + description='Managed delivery method for this offering. Omit for a Core (API-delivered) offering: rows flow through existing get_media_buy_delivery and reporting_webhook transports and no destination is involved. Present only when the seller advertises managed_delivery.' + ), + ] = None diff --git a/src/adcp/types/domains/core/reporting_delivery_offering_id.py b/src/adcp/types/domains/core/reporting_delivery_offering_id.py index b26d34931..ea8629836 100644 --- a/src/adcp/types/domains/core/reporting_delivery_offering_id.py +++ b/src/adcp/types/domains/core/reporting_delivery_offering_id.py @@ -1,18 +1,16 @@ -"""Types declared by the AdCP ``core/reporting_delivery_offering_id`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_delivery_offering_id.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.reporting_delivery_offering_id import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.reporting_delivery_offering_id import ReportingDeliveryOfferingId +from adcp.types._scalar import ScalarStr + -# Explicit exports -__all__ = ["ReportingDeliveryOfferingId"] +class ReportingDeliveryOfferingId(ScalarStr): + __slots__ = () + _constraints = {'max_length': 128, 'min_length': 1, 'pattern': '^[A-Za-z0-9_.:-]{1,128}$'} + _json_schema_extra = { + 'description': 'Identifier of one seller-advertised managed reporting-delivery offering, matching get_adcp_capabilities.media_buy.reporting_delivery.offerings[].offering_id. Shared by the legacy and canonical product reporting capabilities.', + 'title': 'Reporting Delivery Offering ID', + } diff --git a/src/adcp/types/domains/core/reporting_delivery_ready_webhook.py b/src/adcp/types/domains/core/reporting_delivery_ready_webhook.py index 972a31023..5aa2ee923 100644 --- a/src/adcp/types/domains/core/reporting_delivery_ready_webhook.py +++ b/src/adcp/types/domains/core/reporting_delivery_ready_webhook.py @@ -1,21 +1,63 @@ -"""Types declared by the AdCP ``core/reporting_delivery_ready_webhook`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_delivery_ready_webhook.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.reporting_delivery_ready_webhook import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import reporting_finality +from . import reporting_delivery_offering + + +class Readiness(StrEnum): + available = 'available' + delivered = 'delivered' -from adcp.types.generated_poc.core.reporting_delivery_ready_webhook import ( - Readiness, - ReportingDeliveryReadyWebhook, -) -# Explicit exports -__all__ = ["Readiness", "ReportingDeliveryReadyWebhook"] +class ReportingDeliveryReadyWebhook(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + idempotency_key: Annotated[ + str, + Field( + description='Stable across transport retries of this fire; new for a later re-emission.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + notification_id: Annotated[ + str, + Field( + description='Stable for this logical materialization-ready event across re-emissions.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] + notification_type: Literal['reporting.delivery_ready'] = 'reporting.delivery_ready' + fired_at: AwareDatetime + subscriber_id: Annotated[ + str, Field(max_length=64, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,64}$') + ] + account_id: Annotated[str, Field(min_length=1)] + delivery_config_id: Annotated[ + str, Field(max_length=64, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,64}$') + ] + delivery_config_version: Annotated[SchemaInt, Field(ge=1)] + reporting_revision_id: Annotated[ + str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') + ] + reporting_materialization_id: Annotated[ + str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') + ] + readiness: Readiness + finality: reporting_finality.ReportingFinality + data_through: AwareDatetime | None + feed_purpose: reporting_delivery_offering.ReportingFeedPurpose diff --git a/src/adcp/types/domains/core/reporting_file_compression.py b/src/adcp/types/domains/core/reporting_file_compression.py index 46d2e7d9d..c36473583 100644 --- a/src/adcp/types/domains/core/reporting_file_compression.py +++ b/src/adcp/types/domains/core/reporting_file_compression.py @@ -1,18 +1,14 @@ -"""Types declared by the AdCP ``core/reporting_file_compression`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_file_compression.json +# timestamp: 2026-09-01T19:37:00+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.reporting_file_compression import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.reporting_file_compression import ReportingFileCompression +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["ReportingFileCompression"] +class ReportingFileCompression(StrEnum): + none = 'none' + gzip = 'gzip' + zstd = 'zstd' + snappy = 'snappy' diff --git a/src/adcp/types/domains/core/reporting_file_entry.py b/src/adcp/types/domains/core/reporting_file_entry.py index 892998f04..e8e092cc5 100644 --- a/src/adcp/types/domains/core/reporting_file_entry.py +++ b/src/adcp/types/domains/core/reporting_file_entry.py @@ -1,18 +1,24 @@ -"""Types declared by the AdCP ``core/reporting_file_entry`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_file_entry.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.reporting_file_entry import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import reporting_file_object_ref, reporting_native_version_ref -from adcp.types.generated_poc.core.reporting_file_entry import ReportingFileEntry -# Explicit exports -__all__ = ["ReportingFileEntry"] +class ReportingFileEntry(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + object_ref: reporting_file_object_ref.ReportingFileObjectReference + native_version_ref: reporting_native_version_ref.ReportingNativeVersionReference | None = None + size_bytes: Annotated[SchemaInt, Field(ge=0)] + sha256: Annotated[str, Field(pattern='^[A-Fa-f0-9]{64}$')] + row_count: Annotated[SchemaInt, Field(ge=0)] + partition: Annotated[dict[str, str] | None, Field(max_length=32)] = None diff --git a/src/adcp/types/domains/core/reporting_file_manifest.py b/src/adcp/types/domains/core/reporting_file_manifest.py index a78b12533..6bc62f075 100644 --- a/src/adcp/types/domains/core/reporting_file_manifest.py +++ b/src/adcp/types/domains/core/reporting_file_manifest.py @@ -1,22 +1,55 @@ -"""Types declared by the AdCP ``core/reporting_file_manifest`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_file_manifest.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.reporting_file_manifest import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import reporting_control_total, reporting_file_compression, reporting_file_entry + + +class Period(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + start: AwareDatetime + end: AwareDatetime + source_timezone: Annotated[str, Field(min_length=1)] + + +class Format(StrEnum): + jsonl = 'jsonl' + csv = 'csv' + parquet = 'parquet' + avro = 'avro' + orc = 'orc' -from adcp.types.generated_poc.core.reporting_file_manifest import ( - Format, - Period, - ReportingFileManifest, -) -# Explicit exports -__all__ = ["Format", "Period", "ReportingFileManifest"] +class ReportingFileManifest(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + manifest_version: Literal['1.0'] = '1.0' + complete: Literal[True] + reporting_revision_id: Annotated[ + str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') + ] + reporting_obligation_id: Annotated[ + str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') + ] + reporting_materialization_id: Annotated[ + str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') + ] + period: Period + format: Format + compression: reporting_file_compression.ReportingFileCompression + files: Annotated[list[reporting_file_entry.ReportingFileEntry], Field(min_length=1)] + total_size_bytes: Annotated[SchemaInt, Field(ge=0)] + row_count: Annotated[SchemaInt, Field(ge=0)] + control_totals: list[reporting_control_total.ReportingControlTotal] + created_at: AwareDatetime diff --git a/src/adcp/types/domains/core/reporting_file_object_ref.py b/src/adcp/types/domains/core/reporting_file_object_ref.py index e5615025f..0f87cf664 100644 --- a/src/adcp/types/domains/core/reporting_file_object_ref.py +++ b/src/adcp/types/domains/core/reporting_file_object_ref.py @@ -1,18 +1,16 @@ -"""Types declared by the AdCP ``core/reporting_file_object_ref`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_file_object_ref.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.reporting_file_object_ref import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.reporting_file_object_ref import ReportingFileObjectReference +from adcp.types._scalar import ScalarStr + -# Explicit exports -__all__ = ["ReportingFileObjectReference"] +class ReportingFileObjectReference(ScalarStr): + __slots__ = () + _constraints = {'max_length': 1024, 'min_length': 1} + _json_schema_extra = { + 'description': 'Credential-free, destination-relative reference to one reporting data object. For an object store, this is the decoded object key relative to the configured destination, not an absolute URI and not a URI with a version query parameter. Providers URI-encode this value only when constructing their own storage request. The 1024-character limit accommodates an S3 object key of up to 1024 UTF-8 bytes because every Unicode character occupies at least one UTF-8 byte; JSON Schema maxLength counts characters rather than bytes.', + 'title': 'Reporting File Object Reference', + } diff --git a/src/adcp/types/domains/core/reporting_ledger_changed_webhook.py b/src/adcp/types/domains/core/reporting_ledger_changed_webhook.py index 85ee30004..8976a6482 100644 --- a/src/adcp/types/domains/core/reporting_ledger_changed_webhook.py +++ b/src/adcp/types/domains/core/reporting_ledger_changed_webhook.py @@ -1,21 +1,64 @@ -"""Types declared by the AdCP ``core/reporting_ledger_changed_webhook`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_ledger_changed_webhook.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.reporting_ledger_changed_webhook import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import reporting_finality +from . import ext as ext_1 + + +class ChangeKind(StrEnum): + revision_published = 'revision_published' + adjustment_published = 'adjustment_published' -from adcp.types.generated_poc.core.reporting_ledger_changed_webhook import ( - ChangeKind, - ReportingLedgerChangedWebhook, -) -# Explicit exports -__all__ = ["ChangeKind", "ReportingLedgerChangedWebhook"] +class ReportingLedgerChangedWebhook(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + idempotency_key: Annotated[ + str, + Field( + description='Random per distinct fire and stable across transport retries, scoped to the authenticated sender.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + notification_id: Annotated[ + str, + Field( + description='Stable per committed ledger record; re-emissions reuse it.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] + notification_type: Literal['reporting.ledger_changed'] = 'reporting.ledger_changed' + fired_at: AwareDatetime + subscriber_id: Annotated[ + str, Field(max_length=64, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,64}$') + ] + account_id: Annotated[str, Field(min_length=1)] + change_kind: ChangeKind + reporting_revision_id: Annotated[ + str | None, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') + ] = None + supersedes_reporting_revision_id: Annotated[ + str | None, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') + ] = None + finality: reporting_finality.ReportingFinality | None = None + reporting_adjustment_id: Annotated[ + str | None, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') + ] = None + adjusts_reporting_revision_id: Annotated[ + str | None, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/reporting_materialization.py b/src/adcp/types/domains/core/reporting_materialization.py index edfabf111..995f13884 100644 --- a/src/adcp/types/domains/core/reporting_materialization.py +++ b/src/adcp/types/domains/core/reporting_materialization.py @@ -1,22 +1,94 @@ -"""Types declared by the AdCP ``core/reporting_materialization`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_materialization.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.reporting_materialization import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import reporting_delivery_offering, reporting_resource, reporting_verification + + +class Method(StrEnum): + file_transfer = 'file_transfer' + dataset_share = 'dataset_share' + warehouse_materialization = 'warehouse_materialization' + + +class Status(StrEnum): + pending = 'pending' + available = 'available' + delivered = 'delivered' + failed = 'failed' -from adcp.types.generated_poc.core.reporting_materialization import ( - Method, - ReportingMaterialization, - Status, -) -# Explicit exports -__all__ = ["Method", "ReportingMaterialization", "Status"] +class ReportingMaterialization(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + reporting_materialization_id: Annotated[ + str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') + ] + reporting_revision_id: Annotated[ + str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') + ] + reporting_obligation_id: Annotated[ + str, + Field( + description='Destination-specific obligation this materialization attempts to satisfy.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] + delivery_config_id: Annotated[ + str, + Field( + description='Durable configuration that requested this materialization.', + max_length=64, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,64}$', + ), + ] + delivery_config_version: Annotated[SchemaInt, Field(ge=1)] + destination_ref: Annotated[ + str, + Field( + description='Immutable caller-owned destination generation selected by the account-authorized obligation. It may be reused by the same caller across other independently authorized accounts.', + max_length=255, + min_length=1, + ), + ] + feed_purpose: reporting_delivery_offering.ReportingFeedPurpose + method: Method + transport: Annotated[ + str | None, Field(max_length=64, min_length=1, pattern='^[a-z][a-z0-9_.-]*$') + ] = None + attempt: Annotated[SchemaInt, Field(ge=1)] + status: Annotated[ + Status, + Field( + description='Lifecycle of this attempt. pending may transition once to available, delivered, or failed; terminal evidence is immutable. Staleness is evaluated in get_reporting_status health, not stored as a materialization state.' + ), + ] + ready_at: Annotated[ + AwareDatetime | None, + Field(description='When consumer-path or destination verification completed.'), + ] = None + failed_at: AwareDatetime | None = None + failure_code: Annotated[ + str | None, + Field( + description='Stable safe failure classification. MUST NOT include credentials or provider response bodies.', + max_length=128, + min_length=1, + pattern='^[A-Z][A-Z0-9_]*$', + ), + ] = None + resource: reporting_resource.ReportingResource | None = None + verification: reporting_verification.ReportingVerification | None = None + created_at: AwareDatetime diff --git a/src/adcp/types/domains/core/reporting_native_version_ref.py b/src/adcp/types/domains/core/reporting_native_version_ref.py index 3c59d7736..cc4aa8921 100644 --- a/src/adcp/types/domains/core/reporting_native_version_ref.py +++ b/src/adcp/types/domains/core/reporting_native_version_ref.py @@ -1,20 +1,16 @@ -"""Types declared by the AdCP ``core/reporting_native_version_ref`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_native_version_ref.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.reporting_native_version_ref import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.reporting_native_version_ref import ( - ReportingNativeVersionReference, -) +from adcp.types._scalar import ScalarStr + -# Explicit exports -__all__ = ["ReportingNativeVersionReference"] +class ReportingNativeVersionReference(ScalarStr): + __slots__ = () + _constraints = {'max_length': 1024, 'min_length': 1} + _json_schema_extra = { + 'description': 'Credential-free, decoded provider-native immutable version reference, such as an S3 VersionId, GCS generation, table version, transaction, snapshot, manifest generation, job, or run. This is not URI-encoded or concatenated into an object_ref; URI-encode it only when constructing a provider request. The 1024-character limit accommodates a provider value of up to 1024 UTF-8 bytes because every Unicode character occupies at least one UTF-8 byte; JSON Schema maxLength counts characters rather than bytes.', + 'title': 'Reporting Native Version Reference', + } diff --git a/src/adcp/types/domains/core/reporting_obligation.py b/src/adcp/types/domains/core/reporting_obligation.py index c21052db1..bc2a9fa25 100644 --- a/src/adcp/types/domains/core/reporting_obligation.py +++ b/src/adcp/types/domains/core/reporting_obligation.py @@ -1,23 +1,195 @@ -"""Types declared by the AdCP ``core/reporting_obligation`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_obligation.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.reporting_obligation import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AwareDatetime, ConfigDict, Field -from adcp.types.generated_poc.core.reporting_obligation import ( - Period, - ProductionStatus, - ReconciliationStatus, - ReportingObligation, +from ..enums import reporting_finality, reporting_health +from . import ( + reporting_coverage, + reporting_delivery_offering, + reporting_reconciliation_mode, + reporting_schedule, + reporting_status_issue, ) -# Explicit exports -__all__ = ["Period", "ProductionStatus", "ReconciliationStatus", "ReportingObligation"] + +class Period(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + start: AwareDatetime + end: AwareDatetime + source_timezone: Annotated[str, Field(min_length=1)] + + +class ReconciliationStatus(StrEnum): + not_required = 'not_required' + pending = 'pending' + accepted = 'accepted' + rejected = 'rejected' + + +class ProductionStatus(StrEnum): + not_due = 'not_due' + pending = 'pending' + published = 'published' + failed = 'failed' + + +class ReportingObligation(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + reporting_obligation_id: Annotated[ + str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') + ] + delivery_config_id: Annotated[ + str, Field(max_length=64, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,64}$') + ] + delivery_config_version: Annotated[SchemaInt, Field(ge=1)] + report_definition_id: Annotated[ + str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') + ] + feed_purpose: reporting_delivery_offering.ReportingFeedPurpose + reporting_profile: Annotated[str, Field(max_length=128, min_length=1)] + account_id: Annotated[str, Field(min_length=1)] + media_buy_ids: Annotated[ + list[reporting_coverage.ReportingMediaBuyId], + Field( + description='Exact frozen media-buy denominator resolved for this period, including buys with zero rows. An empty array is the definitive zero-buy set; omission is never used to mean all, empty, or unknown.' + ), + ] + scope_resolved_at: Annotated[ + AwareDatetime, + Field( + description='Instant at which the configured scope was resolved and frozen for this obligation. For all_media_buys, include every caller-authorized account media buy whose effective flight overlaps the half-open period and was known by this cutoff. Later-created or backdated buys do not rewrite this obligation.' + ), + ] + coverage: Annotated[ + reporting_coverage.ReportingCoverage, + Field( + description='Immutable effective coverage of the exact selected offering at this period boundary. Delivery health is evaluated separately over the covered denominator.' + ), + ] + period: Period + expected_at: AwareDatetime + schedule: Annotated[ + reporting_schedule.ReportingSchedule, + Field(description='Resolved immutable schedule generation that created this obligation.'), + ] + destination_ref: Annotated[ + str | None, + Field( + description='Immutable caller-owned destination generation selected by this account-authorized obligation. The account/configuration join—not possession of this reusable reference—authorizes disclosure. Present when and only when the obligation’s configuration selects a managed-delivery offering; Core (API-delivered) obligations omit every destination and materialization field.', + max_length=255, + min_length=1, + ), + ] = None + required_finality: reporting_finality.ReportingFinality + reconciliation_mode: reporting_reconciliation_mode.ReportingReconciliationMode + reconciliation_status: Annotated[ + ReconciliationStatus, + Field( + description='Consumer agreement state for the current required revision. A later superseding revision returns a receipt-required obligation to pending until that revision is accepted.' + ), + ] + health: reporting_health.ReportingHealth + production_status: Annotated[ + ProductionStatus, + Field( + description='Whether any revision has been produced for this obligation. published includes zero-row revisions.' + ), + ] + revision_count: Annotated[ + SchemaInt, + Field( + description='Number of revision records for this obligation in the consistent ledger snapshot.', + ge=0, + ), + ] + consumer_status_count: Annotated[ + SchemaInt | None, + Field( + description='Complete number of immutable authenticated consumer status statements associated with this obligation in the ledger snapshot, whether originally joined by reporting_obligation_id or by the exact configuration-generation, report-definition, and period key before the obligation existed. Core status-sync history is counted independently from Reconciled Billing receipts.', + ge=0, + ), + ] = None + current_consumer_status_id: Annotated[ + str | None, + Field( + description='Current unsuperseded consumer status statement associated with this obligation by seller ID or its exact logical period key. Omitted when consumer_status_count is zero.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] = None + adjustment_count: Annotated[ + SchemaInt | None, + Field( + description='Number of immutable post-official reporting adjustment records for this obligation in the consistent ledger snapshot.', + ge=0, + ), + ] = None + materialization_count: Annotated[ + SchemaInt | None, + Field( + description="Number of materialization records for this obligation's revisions in the consistent ledger snapshot. Present iff the obligation is managed-delivery (destination_ref present).", + ge=0, + ), + ] = None + successful_materialization_count: Annotated[ + SchemaInt | None, + Field( + description='Number of available/delivered verified materializations in the consistent ledger snapshot. Present iff the obligation is managed-delivery (destination_ref present).', + ge=0, + ), + ] = None + receipt_count: Annotated[ + SchemaInt | None, + Field( + description='Complete number of authenticated receipts associated with this obligation in the ledger snapshot. Present iff reconciliation_mode is consumer_receipt.', + ge=0, + ), + ] = None + accepted_receipt_count: Annotated[ + SchemaInt | None, + Field( + description='Number of accepted receipts. At most one current accepted receipt per consumer and revision contributes to reconciliation_status. Present iff reconciliation_mode is consumer_receipt.', + ge=0, + ), + ] = None + adjustment_receipt_count: Annotated[ + SchemaInt | None, + Field( + description="Number of authenticated receipts for adjustments targeting this obligation's official revision. Reconciled Billing only.", + ge=0, + ), + ] = None + accepted_adjustment_receipt_count: Annotated[ + SchemaInt | None, + Field( + description='Number of accepted adjustment receipts. Reconciled Billing buyers do not post an adjustment until their exact digest is accepted.', + ge=0, + ), + ] = None + pending_adjustment_count: Annotated[ + SchemaInt | None, + Field( + description='Number of applicable adjustments without an accepted receipt. Reconciled Billing complete/healthy requires zero.', + ge=0, + ), + ] = None + issues: list[reporting_status_issue.ReportingStatusIssue] + resource_retained_until: Annotated[ + AwareDatetime | None, + Field( + description='Minimum time through which at least one verified materialization for a completed obligation remains readable. Managed-delivery only; Core revisions are retained per status_retention_days and readable through the existing API transports.' + ), + ] = None diff --git a/src/adcp/types/domains/core/reporting_receipt.py b/src/adcp/types/domains/core/reporting_receipt.py index 334aeca2d..4deba8528 100644 --- a/src/adcp/types/domains/core/reporting_receipt.py +++ b/src/adcp/types/domains/core/reporting_receipt.py @@ -1,18 +1,78 @@ -"""Types declared by the AdCP ``core/reporting_receipt`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_receipt.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.reporting_receipt import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from adcp.types._scalar import ScalarStr +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import ( + reporting_canonical_content_digest, + reporting_control_total, + reporting_native_version_ref, + reporting_verification_profile, +) + + +class Status(StrEnum): + accepted = 'accepted' + rejected = 'rejected' + + +class RejectionCode(ScalarStr): + __slots__ = () + _constraints = {'max_length': 128, 'min_length': 1, 'pattern': '^[A-Z][A-Z0-9_]*$'} -from adcp.types.generated_poc.core.reporting_receipt import RejectionCode, ReportingReceipt, Status -# Explicit exports -__all__ = ["RejectionCode", "ReportingReceipt", "Status"] +class ReportingReceipt(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + reporting_receipt_id: Annotated[ + str, Field(max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$') + ] + reporting_obligation_id: Annotated[ + str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') + ] + reporting_revision_id: Annotated[ + str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') + ] + reporting_materialization_id: Annotated[ + str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') + ] + supersedes_reporting_receipt_id: Annotated[ + str | None, + Field( + description="Optional immutable rejected receipt replaced by this new receipt. It MUST name the caller's current rejected receipt for this obligation and revision; accepted current receipts are terminal.", + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] = None + status: Status + verification_profile: reporting_verification_profile.ReportingVerificationProfile + observed_row_count: Annotated[SchemaInt, Field(ge=0)] + observed_control_totals: list[reporting_control_total.ReportingControlTotal] + observed_canonical_content_digest: ( + reporting_canonical_content_digest.ReportingCanonicalContentDigest | None + ) = None + observed_manifest_sha256: Annotated[str | None, Field(pattern='^[A-Fa-f0-9]{64}$')] = None + observed_native_version_ref: ( + reporting_native_version_ref.ReportingNativeVersionReference | None + ) = None + consumer_commit_ref: Annotated[ + str | None, + Field( + description='Optional non-secret consumer checkpoint, transaction, or load identifier. It is evidence for operations, not authorization or a credential.', + max_length=512, + min_length=1, + ), + ] = None + rejection_codes: Annotated[list[RejectionCode] | None, Field(min_length=1)] = None + observed_at: AwareDatetime + received_at: AwareDatetime | None = None diff --git a/src/adcp/types/domains/core/reporting_reconciliation_mode.py b/src/adcp/types/domains/core/reporting_reconciliation_mode.py index 05122368b..d9738e503 100644 --- a/src/adcp/types/domains/core/reporting_reconciliation_mode.py +++ b/src/adcp/types/domains/core/reporting_reconciliation_mode.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``core/reporting_reconciliation_mode`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_reconciliation_mode.json +# timestamp: 2026-09-01T19:37:00+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.reporting_reconciliation_mode import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.reporting_reconciliation_mode import ReportingReconciliationMode +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["ReportingReconciliationMode"] +class ReportingReconciliationMode(StrEnum): + delivery_only = 'delivery_only' + consumer_receipt = 'consumer_receipt' diff --git a/src/adcp/types/domains/core/reporting_reliability_statistics.py b/src/adcp/types/domains/core/reporting_reliability_statistics.py index 59a9c97bf..53b9e1e70 100644 --- a/src/adcp/types/domains/core/reporting_reliability_statistics.py +++ b/src/adcp/types/domains/core/reporting_reliability_statistics.py @@ -1,32 +1,103 @@ -"""Types declared by the AdCP ``core/reporting_reliability_statistics`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_reliability_statistics.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.reporting_reliability_statistics import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field + + +class AdjustmentMagnitudeItem(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + control_total_name: Annotated[str, Field(max_length=128, min_length=1)] + unit: Annotated[str, Field(max_length=32, min_length=1)] + sample_count: Annotated[SchemaInt, Field(ge=1)] + p50_absolute_delta: Annotated[str, Field(pattern='^(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?$')] + p95_absolute_delta: Annotated[str, Field(pattern='^(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?$')] + + +class Basis(StrEnum): + reporting_ledger = 'reporting_ledger' + third_party_attestation = 'third_party_attestation' + + +class Evidence(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + regex_engine="python-re", + ) + basis: Basis + generated_at: AwareDatetime + attestation_uri: AnyUrl | None = None + attestation_sha256: Annotated[str | None, Field(pattern='^[A-Fa-f0-9]{64}$')] = None + + +class LatencyPercentiles(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + p50: Annotated[SchemaInt, Field(ge=0)] + p95: Annotated[SchemaInt, Field(ge=0)] + + +class ReportingReliabilityMeasurementPeriod(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + start: AwareDatetime + end: AwareDatetime -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.reporting_reliability_statistics import ( - AdjustmentMagnitudeItem, - Basis, - Evidence, - LatencyPercentiles, - ReportingReliabilityMeasurementPeriod, - ReportingReliabilityStatistics, -) - -# Explicit exports -__all__ = [ - "AdjustmentMagnitudeItem", - "Basis", - "Evidence", - "LatencyPercentiles", - "ReportingReliabilityMeasurementPeriod", - "ReportingReliabilityStatistics", -] +class ReportingReliabilityStatistics(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + offering_id: Annotated[ + str, Field(max_length=128, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,128}$') + ] + measurement_period: ReportingReliabilityMeasurementPeriod + obligations_due: Annotated[ + SchemaInt, Field(description='Denominator for on-time performance.', ge=1) + ] + obligations_on_time: Annotated[ + SchemaInt, + Field( + description='Obligations whose required revision was published by expected_at. Divide by obligations_due for the on-time rate.', + ge=0, + ), + ] + official_revisions_published: Annotated[ + SchemaInt, Field(description='Denominator for the official-adjustment rate.', ge=0) + ] + official_revisions_adjusted: Annotated[ + SchemaInt, + Field( + description='Distinct official revisions receiving at least one adjustment. Divide by official_revisions_published for the adjustment rate.', + ge=0, + ), + ] + publication_latency_seconds: Annotated[ + LatencyPercentiles, + Field( + description='Observed seconds from period.end to publication of the required revision.' + ), + ] + adjustment_latency_seconds: Annotated[ + LatencyPercentiles | None, + Field( + description='Observed seconds from official finalized_at to the first adjustment, required when official_revisions_adjusted is nonzero.' + ), + ] = None + adjustment_magnitude: Annotated[ + list[AdjustmentMagnitudeItem] | None, + Field( + description='Optional absolute adjustment-magnitude percentiles for comparable control totals such as spend.' + ), + ] = None + evidence: Evidence diff --git a/src/adcp/types/domains/core/reporting_report_definition.py b/src/adcp/types/domains/core/reporting_report_definition.py index 1d0fdf6ea..29d118e15 100644 --- a/src/adcp/types/domains/core/reporting_report_definition.py +++ b/src/adcp/types/domains/core/reporting_report_definition.py @@ -1,46 +1,179 @@ -"""Types declared by the AdCP ``core/reporting_report_definition`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_report_definition.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.reporting_report_definition import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field + + +class ContractVersion(StrEnum): + field_1_0 = '1.0' + field_1_1 = '1.1' + + +class Provider(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + domain: Annotated[ + str, Field(pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$') + ] + + +class Source(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + provider: Provider + system: Annotated[str, Field(max_length=128, min_length=1)] + api_version: Annotated[str, Field(max_length=128, min_length=1)] + query_semantics: Annotated[ + dict[str, Any], + Field( + description='Canonical JSON object containing every source option that can change the numbers, including attribution settings, action-report-time, filters, and mapping version.' + ), + ] + + +class Aggregation(StrEnum): + sum = 'sum' + count = 'count' # type: ignore[assignment] + min = 'min' + max = 'max' + average = 'average' + ratio = 'ratio' + last = 'last' + custom = 'custom' + + +class Metric(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + name: Annotated[str, Field(max_length=128, min_length=1)] + source_expression: Annotated[str, Field(max_length=2048, min_length=1)] + aggregation: Aggregation + unit: Annotated[str | None, Field(max_length=64, min_length=1)] = None + + +class Dimension(ScalarStr): + __slots__ = () + _constraints = {'max_length': 128, 'min_length': 1} + + +class RestatementPolicy(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + regex_engine="python-re", + ) + source_requery_duration: Annotated[ + str, + Field( + pattern='^P(?=\\d|T)(?=.*\\d)(?:\\d+Y)?(?:\\d+M)?(?:\\d+D)?(?:T(?=\\d)(?:\\d+H)?(?:\\d+M)?(?:\\d+S)?)?$' + ), + ] + emit_only_on_content_change: Literal[True] + official_correction_mode: Annotated[ + Literal['adjustments_only'], + Field( + description='Once an official revision is published it is immutable and cannot be superseded. Later source corrections are separate reporting-adjustment records applied to a later accounting period.' + ), + ] = 'adjustments_only' + + +class FinalityPolicies(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + finality_policy_id: Annotated[ + str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') + ] + basis: Literal['source_final'] = 'source_final' + source_signal: Annotated[str, Field(max_length=512, min_length=1)] + + +class FinalityPolicies1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + regex_engine="python-re", + ) + finality_policy_id: Annotated[ + str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') + ] + basis: Literal['contractual_cutoff'] = 'contractual_cutoff' + duration_after_period_end: Annotated[ + str, + Field( + pattern='^P(?=\\d|T)(?=.*\\d)(?:\\d+Y)?(?:\\d+M)?(?:\\d+D)?(?:T(?=\\d)(?:\\d+H)?(?:\\d+M)?(?:\\d+S)?)?$' + ), + ] + + +class FinalityPolicies2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + regex_engine="python-re", + ) + finality_policy_id: Annotated[ + str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') + ] + basis: Literal['stabilized'] = 'stabilized' + minimum_age: Annotated[ + str, + Field( + pattern='^P(?=\\d|T)(?=.*\\d)(?:\\d+Y)?(?:\\d+M)?(?:\\d+D)?(?:T(?=\\d)(?:\\d+H)?(?:\\d+M)?(?:\\d+S)?)?$' + ), + ] + unchanged_for: Annotated[ + str, + Field( + pattern='^P(?=\\d|T)(?=.*\\d)(?:\\d+Y)?(?:\\d+M)?(?:\\d+D)?(?:T(?=\\d)(?:\\d+H)?(?:\\d+M)?(?:\\d+S)?)?$' + ), + ] + + +class ReportCalendarTimezoneBasis(StrEnum): + utc = 'utc' + account_timezone = 'account_timezone' + schedule_timezone = 'schedule_timezone' + configured_timezone = 'configured_timezone' + + +class Calendar(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + timezone_basis: ReportCalendarTimezoneBasis + timezone: Annotated[str | None, Field(max_length=255, min_length=1)] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.reporting_report_definition import ( - Aggregation, - Calendar, - ContractVersion, - Dimension, - FinalityPolicies, - FinalityPolicies1, - FinalityPolicies2, - Metric, - Provider, - ReportCalendarTimezoneBasis, - ReportingReportDefinition, - RestatementPolicy, - Source, -) - -# Explicit exports -__all__ = [ - "Aggregation", - "Calendar", - "ContractVersion", - "Dimension", - "FinalityPolicies", - "FinalityPolicies1", - "FinalityPolicies2", - "Metric", - "Provider", - "ReportCalendarTimezoneBasis", - "ReportingReportDefinition", - "RestatementPolicy", - "Source", -] +class ReportingReportDefinition(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + contract_version: Annotated[ + ContractVersion, + Field( + description='1.1 adds immutable official closes with adjustments_only correction semantics for Reliable Reporting 1.0. Version 1.0 remains accepted for compatibility with the preceding experimental managed-reporting contract.' + ), + ] + media_type: Literal['application/vnd.adcp.reporting-definition+json'] = 'application/vnd.adcp.reporting-definition+json' + report_definition_id: Annotated[ + str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') + ] + reporting_profile: Annotated[str, Field(max_length=128, min_length=1)] + grain: Annotated[str, Field(max_length=128, min_length=1)] + source: Source + calendar: Calendar + metrics: Annotated[list[Metric], Field(min_length=1)] + dimensions: list[Dimension] + restatement_policy: RestatementPolicy + finality_policies: Annotated[ + list[FinalityPolicies | FinalityPolicies1 | FinalityPolicies2], Field(min_length=1) + ] diff --git a/src/adcp/types/domains/core/reporting_resource.py b/src/adcp/types/domains/core/reporting_resource.py index 2c654c492..8cd4072f7 100644 --- a/src/adcp/types/domains/core/reporting_resource.py +++ b/src/adcp/types/domains/core/reporting_resource.py @@ -1,23 +1,85 @@ -"""Types declared by the AdCP ``core/reporting_resource`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_resource.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.reporting_resource import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import reporting_native_version_ref + + +class Kind(StrEnum): + manifest = 'manifest' + dataset = 'dataset' + warehouse_relation = 'warehouse_relation' + + +class Immutability(StrEnum): + immutable_location = 'immutable_location' + native_version = 'native_version' + + +class ReportingReaderCompatibilityItem(ScalarStr): + __slots__ = () + _constraints = {'max_length': 128, 'min_length': 1} + _json_schema_extra = {'title': 'Reporting Reader Compatibility Item'} -from adcp.types.generated_poc.core.reporting_resource import ( - Immutability, - Kind, - ReportingReaderCompatibilityItem, - ReportingResource, -) -# Explicit exports -__all__ = ["Immutability", "Kind", "ReportingReaderCompatibilityItem", "ReportingResource"] +class ReportingResource(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + resource_ref: Annotated[ + str, + Field( + description='Seller-issued opaque reference to this exact authenticated resource descriptor.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] + kind: Annotated[ + Kind, Field(description='Shape through which the durable revision is consumed.') + ] + location: Annotated[ + str, + Field( + description='Non-secret provider-native object, relation, or share identifier. MUST NOT contain an activation URL, signed URL, bearer token, password, private key, or embedded credential.', + max_length=2048, + min_length=1, + ), + ] + native_version_ref: reporting_native_version_ref.ReportingNativeVersionReference | None = None + manifest_version: Annotated[ + Literal['1.0'], + Field(description='Version of reporting-file-manifest.json used by a manifest resource.'), + ] = '1.0' + manifest_sha256: Annotated[ + str | None, + Field( + description='SHA-256 over the exact manifest bytes. Consumers verify this before parsing the manifest.', + pattern='^[A-Fa-f0-9]{64}$', + ), + ] = None + immutability: Annotated[ + Immutability, + Field(description='How this descriptor selects the exact immutable materialization.'), + ] + expires_at: Annotated[ + AwareDatetime, + Field( + description='Mandatory finite lower-bound endpoint through which this exact resource remains resolvable; it cannot be earlier than the advertised retention contract.' + ), + ] + reader_compatibility: Annotated[ + list[ReportingReaderCompatibilityItem] | None, + Field( + description='Reader features or format constraints required to consume this resource. Readiness verification MUST use a representative supported reader.' + ), + ] = None diff --git a/src/adcp/types/domains/core/reporting_revision.py b/src/adcp/types/domains/core/reporting_revision.py index d4eab0e15..fd3e216c6 100644 --- a/src/adcp/types/domains/core/reporting_revision.py +++ b/src/adcp/types/domains/core/reporting_revision.py @@ -1,23 +1,169 @@ -"""Types declared by the AdCP ``core/reporting_revision`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_revision.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.reporting_revision import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import reporting_finality +from . import reporting_canonical_content_digest, reporting_control_total, reporting_coverage + + +class Period(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + start: AwareDatetime + end: AwareDatetime + source_timezone: Annotated[str, Field(min_length=1)] + + +class FinalityBasis(StrEnum): + source_final = 'source_final' + contractual_cutoff = 'contractual_cutoff' + stabilized = 'stabilized' + + +class DataThroughPrecision(StrEnum): + exact = 'exact' + lower_bound = 'lower_bound' + unknown = 'unknown' -from adcp.types.generated_poc.core.reporting_revision import ( - DataThroughPrecision, - FinalityBasis, - Period, - ReportingRevision, -) -# Explicit exports -__all__ = ["DataThroughPrecision", "FinalityBasis", "Period", "ReportingRevision"] +class ReportingRevision(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + regex_engine="python-re", + ) + reporting_revision_id: Annotated[ + str, + Field( + description='Portable AdCP identity for this immutable report publication. Distinct from package delivery_revision_id and provider-native versions.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] + revision_content_sha256: Annotated[ + str, + Field( + description='SHA-256 of the immutable RFC 8785 JCS binding object containing reporting_revision_id, row_count, control_totals, and reporting_rows. Reliable Reporting 1.0 Core revisions include it and exact reads return the identical value.', + pattern='^[A-Fa-f0-9]{64}$', + ), + ] + report_definition_id: Annotated[ + str, + Field( + description='Identity or canonical fingerprint of immutable metric, grain, attribution, breakdown, action-definition, profile, and calendar/timezone semantics.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] + report_definition_uri: AnyUrl + report_definition_sha256: Annotated[str, Field(pattern='^[A-Fa-f0-9]{64}$')] + reporting_profile: Annotated[str, Field(max_length=128, min_length=1)] + schema_version: Annotated[str, Field(max_length=64, min_length=1)] + schema_uri: Annotated[ + AnyUrl, + Field( + description='Machine-readable schema on the authenticated seller/provider or AdCP-registry origin.' + ), + ] + schema_sha256: Annotated[ + str, + Field( + description='Digest of the exact schema bytes used to validate this immutable revision.', + pattern='^[A-Fa-f0-9]{64}$', + ), + ] + schema_dialect: Annotated[ + Literal['https://json-schema.org/draft/2020-12/schema'], + Field(description='Closed SDK-bundled dialect; the metaschema is never network-fetched.'), + ] = 'https://json-schema.org/draft/2020-12/schema' + schema_ref_policy: Annotated[ + Literal['local_fragment_only'], + Field( + description='The fetched schema is self-contained and every $ref is a local # fragment.' + ), + ] = 'local_fragment_only' + account_id: Annotated[str, Field(min_length=1)] + media_buy_ids: Annotated[ + list[reporting_coverage.ReportingMediaBuyId], + Field( + description='Exact frozen media-buy denominator inherited from the obligation, including buys with zero rows. An empty array proves a zero-buy period rather than an unknown denominator.' + ), + ] + coverage: Annotated[ + reporting_coverage.ReportingCoverage, + Field( + description='Frozen product/package denominator represented by this logical content. The same coverage follows the revision to every destination.' + ), + ] + period: Annotated[ + Period, Field(description='Half-open reporting interval with its source calendar boundary.') + ] + finality: reporting_finality.ReportingFinality + finality_basis: Annotated[ + FinalityBasis | None, + Field( + description='Why an official revision is considered final: an authoritative source signal, a versioned contractual cutoff, or a versioned stabilization rule.' + ), + ] = None + finality_policy_id: Annotated[ + str | None, + Field( + description='Immutable policy/version reference that defines the selected finality basis. It MUST be bound by report_definition_id.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] = None + finalized_at: Annotated[ + AwareDatetime | None, + Field( + description='When the producer applied the declared finality basis to this official revision.' + ), + ] = None + observed_at: Annotated[ + AwareDatetime, + Field(description='When the seller obtained or committed this source observation.'), + ] + data_through: Annotated[ + AwareDatetime | None, + Field( + description='Latest event time conservatively included, or null when precision is unknown.' + ), + ] + data_through_precision: DataThroughPrecision + supersedes_reporting_revision_id: Annotated[ + str | None, + Field( + description='Immediately superseded snapshot revision of the same logical slice. An official revision is terminal and MUST NOT be named here; later corrections use reporting-adjustment records.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] = None + row_count: Annotated[ + SchemaInt, + Field( + description='Logical row count, including zero for a successfully evaluated empty report.', + ge=0, + ), + ] + control_totals: Annotated[ + list[reporting_control_total.ReportingControlTotal], + Field( + description='Profile-defined totals computed from the canonical logical revision. Names MUST be unique.' + ), + ] + canonical_content_digest: ( + reporting_canonical_content_digest.ReportingCanonicalContentDigest | None + ) = None + created_at: AwareDatetime diff --git a/src/adcp/types/domains/core/reporting_schedule.py b/src/adcp/types/domains/core/reporting_schedule.py index ca92ebaf5..8226a2ba3 100644 --- a/src/adcp/types/domains/core/reporting_schedule.py +++ b/src/adcp/types/domains/core/reporting_schedule.py @@ -1,21 +1,54 @@ -"""Types declared by the AdCP ``core/reporting_schedule`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_schedule.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.reporting_schedule import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.reporting_schedule import ( - ReportingSchedule, - ReportingScheduleAlignment, -) +class ReportingScheduleAlignment(StrEnum): + utc = 'utc' + account_timezone = 'account_timezone' + source_timezone = 'source_timezone' + billing_cycle = 'billing_cycle' + -# Explicit exports -__all__ = ["ReportingSchedule", "ReportingScheduleAlignment"] +class ReportingSchedule(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + regex_engine="python-re", + ) + period_duration: Annotated[ + str, + Field( + description='Strictly positive ISO 8601 duration of each reporting period, such as PT15M, P1D, or P1M.', + pattern='^P(?=.*[1-9])(?=\\d|T)(?:\\d+Y)?(?:\\d+M)?(?:\\d+D)?(?:T(?=\\d)(?:\\d+H)?(?:\\d+M)?(?:\\d+S)?)?$', + ), + ] + alignment: ReportingScheduleAlignment + period_anchor: Annotated[ + AwareDatetime | None, + Field( + description='Required for billing_cycle alignment. This immutable instant anchors the recurring half-open billing periods so producer and consumer derive the same month, quarter, or other contractual cycle.' + ), + ] = None + period_timezone: Annotated[ + str | None, + Field( + description='Required IANA timezone for source_timezone and billing_cycle calendar arithmetic. A numeric UTC offset is not sufficient because it does not define DST transitions.', + max_length=255, + min_length=1, + ), + ] = None + delivery_sla: Annotated[ + str, + Field( + description='Non-negative maximum time after period end before the required revision is due. PT0S means due at period close; expected_at equals the resolved period end plus this duration.', + pattern='^P(?=\\d|T)(?=.*\\d)(?:\\d+Y)?(?:\\d+M)?(?:\\d+D)?(?:T(?=\\d)(?:\\d+H)?(?:\\d+M)?(?:\\d+S)?)?$', + ), + ] diff --git a/src/adcp/types/domains/core/reporting_schedule_offering.py b/src/adcp/types/domains/core/reporting_schedule_offering.py index 6bc863d36..26d0a633a 100644 --- a/src/adcp/types/domains/core/reporting_schedule_offering.py +++ b/src/adcp/types/domains/core/reporting_schedule_offering.py @@ -1,22 +1,57 @@ -"""Types declared by the AdCP ``core/reporting_schedule_offering`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_schedule_offering.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.reporting_schedule_offering import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import reporting_schedule + + +class PeriodAnchorPolicy(StrEnum): + fixed = 'fixed' + configurable = 'configurable' + + +class PeriodTimezonePolicy(StrEnum): + fixed = 'fixed' + account_resolved = 'account_resolved' -from adcp.types.generated_poc.core.reporting_schedule_offering import ( - PeriodAnchorPolicy, - PeriodTimezonePolicy, - ReportingScheduleOffering, -) -# Explicit exports -__all__ = ["PeriodAnchorPolicy", "PeriodTimezonePolicy", "ReportingScheduleOffering"] +class ReportingScheduleOffering(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + regex_engine="python-re", + ) + period_duration: Annotated[ + str, + Field( + pattern='^P(?=.*[1-9])(?=\\d|T)(?:\\d+Y)?(?:\\d+M)?(?:\\d+D)?(?:T(?=\\d)(?:\\d+H)?(?:\\d+M)?(?:\\d+S)?)?$' + ), + ] + alignment: reporting_schedule.ReportingScheduleAlignment + period_anchor_policy: Annotated[ + PeriodAnchorPolicy | None, + Field( + description='For billing_cycle only. fixed requires the advertised anchor and timezone; configurable lets each authorized account configuration select them.' + ), + ] = None + period_timezone_policy: Annotated[ + PeriodTimezonePolicy | None, + Field( + description="For source_timezone only. fixed advertises one exact upstream IANA timezone; account_resolved requires the seller to resolve and echo the account's upstream reporting timezone during configuration." + ), + ] = None + period_anchor: AwareDatetime | None = None + period_timezone: Annotated[str | None, Field(max_length=255, min_length=1)] = None + delivery_sla: Annotated[ + str, + Field( + pattern='^P(?=\\d|T)(?=.*\\d)(?:\\d+Y)?(?:\\d+M)?(?:\\d+D)?(?:T(?=\\d)(?:\\d+H)?(?:\\d+M)?(?:\\d+S)?)?$' + ), + ] diff --git a/src/adcp/types/domains/core/reporting_status_changed_webhook.py b/src/adcp/types/domains/core/reporting_status_changed_webhook.py index 4770c2ba4..3378c62cc 100644 --- a/src/adcp/types/domains/core/reporting_status_changed_webhook.py +++ b/src/adcp/types/domains/core/reporting_status_changed_webhook.py @@ -1,21 +1,83 @@ -"""Types declared by the AdCP ``core/reporting_status_changed_webhook`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_status_changed_webhook.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.reporting_status_changed_webhook import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from adcp.types._scalar import ScalarStr +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import reporting_health +from . import ext as ext_1 +from . import reporting_delivery_offering + + +class IssueId(ScalarStr): + __slots__ = () + _constraints = {'max_length': 255, 'min_length': 1, 'pattern': '^[A-Za-z0-9_.:-]{1,255}$'} -from adcp.types.generated_poc.core.reporting_status_changed_webhook import ( - IssueId, - ReportingStatusChangedWebhook, -) -# Explicit exports -__all__ = ["IssueId", "ReportingStatusChangedWebhook"] +class ReportingStatusChangedWebhook(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + idempotency_key: Annotated[ + str, + Field( + description='Sender-generated key stable across retries of the same fire. Sellers MUST generate a cryptographically random value (UUID v4 recommended) per distinct fire and reuse it on every retry. Receivers MUST dedupe by this key, scoped to the authenticated sender identity.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + notification_id: Annotated[ + str, + Field( + description='Stable per logical health transition; re-emissions reuse it, and a later distinct transition receives a new id.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] + notification_type: Literal['reporting.status_changed'] = 'reporting.status_changed' + fired_at: AwareDatetime + subscriber_id: Annotated[ + str, + Field( + description='Identifies which account-level notification_configs[] entry is receiving this fire.', + max_length=64, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,64}$', + ), + ] + account_id: Annotated[str, Field(min_length=1)] + delivery_config_id: Annotated[ + str, Field(max_length=64, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,64}$') + ] + delivery_config_version: Annotated[SchemaInt, Field(ge=1)] + feed_purpose: reporting_delivery_offering.ReportingFeedPurpose + reporting_obligation_id: Annotated[ + str | None, + Field( + description='Present when the transition is obligation-scoped; absent for configuration-level transitions.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] = None + health: Annotated[ + reporting_health.ReportingHealth, + Field(description='The health state after this transition.'), + ] + previous_health: reporting_health.ReportingHealth | None = None + issue_ids: Annotated[ + list[IssueId] | None, + Field( + description='Stable identifiers of the open issues that caused or survived this transition, matching issues[].issue_id on get_reporting_status, so consumers can project AdCP reporting issues into durable work items. Empty or absent on recovery transitions.', + max_length=16, + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/reporting_status_issue.py b/src/adcp/types/domains/core/reporting_status_issue.py index d00106cd4..3c4a1b334 100644 --- a/src/adcp/types/domains/core/reporting_status_issue.py +++ b/src/adcp/types/domains/core/reporting_status_issue.py @@ -1,32 +1,132 @@ -"""Types declared by the AdCP ``core/reporting_status_issue`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_status_issue.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.reporting_status_issue import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AwareDatetime, ConfigDict, Field + +from . import reporting_coverage, reporting_delivery_offering + + +class Code(StrEnum): + REPORT_OVERDUE = 'REPORT_OVERDUE' + PRODUCTION_FAILED = 'PRODUCTION_FAILED' + DELIVERY_FAILED = 'DELIVERY_FAILED' + ACCESS_REQUIRED = 'ACCESS_REQUIRED' + CONFIGURATION_REQUIRED = 'CONFIGURATION_REQUIRED' + REPORTING_COVERAGE_INCOMPLETE = 'REPORTING_COVERAGE_INCOMPLETE' + RESOURCE_EXPIRED = 'RESOURCE_EXPIRED' + READER_INCOMPATIBLE = 'READER_INCOMPATIBLE' + HISTORY_UNAVAILABLE = 'HISTORY_UNAVAILABLE' + RECEIPT_REQUIRED = 'RECEIPT_REQUIRED' + RECEIPT_REJECTED = 'RECEIPT_REJECTED' + ADJUSTMENT_RECEIPT_REQUIRED = 'ADJUSTMENT_RECEIPT_REQUIRED' + ADJUSTMENT_RECEIPT_REJECTED = 'ADJUSTMENT_RECEIPT_REJECTED' + CONSUMER_STATUS_MISMATCH = 'CONSUMER_STATUS_MISMATCH' + + +class IssueState(StrEnum): + open = 'open' + acknowledged = 'acknowledged' + resolved = 'resolved' + waived = 'waived' + + +class ResponsibleParty(StrEnum): + buyer = 'buyer' + seller = 'seller' + provider = 'provider' + + +class RecommendedAction(StrEnum): + wait_for_retry = 'wait_for_retry' + contact_buyer = 'contact_buyer' + contact_seller = 'contact_seller' + contact_provider = 'contact_provider' + repair_access = 'repair_access' + update_configuration = 'update_configuration' + change_reporting_scope = 'change_reporting_scope' + use_supported_reader = 'use_supported_reader' + + +class ReportingStatusSeverity(StrEnum): + delayed = 'delayed' + action_required = 'action_required' -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.reporting_status_issue import ( - Code, - IssueState, - RecommendedAction, - ReportingStatusIssue, - ReportingStatusSeverity, - ResponsibleParty, -) - -# Explicit exports -__all__ = [ - "Code", - "IssueState", - "RecommendedAction", - "ReportingStatusIssue", - "ReportingStatusSeverity", - "ResponsibleParty", -] +class ReportingStatusIssue(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + issue_id: Annotated[ + str, + Field( + description='Seller-issued stable identifier for this logical issue: re-emissions and later polls of the same unresolved condition reuse it, and resolution retires it, so consumers can project AdCP reporting issues into durable work items. A recurrence after resolution receives a new id.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] + code: Code + severity: ReportingStatusSeverity + opened_at: Annotated[ + AwareDatetime | None, + Field( + description='When the seller first observed this logical condition, carried unchanged across every re-emission until the issue is retired. It anchors the escalation clock advertised as consumer_mismatch_escalation_seconds and lets a consumer age an issue without keeping its own first-seen table. Required when code is CONSUMER_STATUS_MISMATCH.' + ), + ] = None + issue_state: Annotated[ + IssueState | None, + Field( + description='Optional seller-maintained lifecycle for this issue_id. open is the default when omitted. acknowledged means a human on responsible_party has taken it up but the condition persists. resolved means the underlying condition no longer holds; a recurrence uses a new issue_id. waived means the parties agreed off-protocol to disregard this exact issue even though its underlying condition may still hold. Only open and acknowledged issues appear in issues[]; retiring an issue removes it from the projection rather than publishing it at resolved or waived, so a reader that treats a nonempty issues[] as degradation stays correct. A CONSUMER_STATUS_MISMATCH waiver follows the bilateral, exact-scope requirements in consumer_mismatch_lifecycle.' + ), + ] = None + external_ref: Annotated[ + str | None, + Field( + description="Optional opaque, non-secret correlation string for the party's own tracker — a ticket key, incident ID, or case number. Untrusted display text only. The character class excludes whitespace and the solidus, so the value cannot express a URL or a sentence; receivers compare, store, and display it as inert text and never dereference, resolve, or execute it. It confers no authorization and MUST NOT be used to look up state across accounts or callers.", + max_length=128, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,128}$', + ), + ] = None + responsible_party: ResponsibleParty + recommended_action: RecommendedAction + message: Annotated[ + str | None, + Field( + description='Untrusted display text only. SDKs and agents dispatch exclusively on closed code/recommended_action values and never execute embedded links or instructions.', + max_length=500, + ), + ] = None + reporting_obligation_id: Annotated[ + str | None, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') + ] = None + reporting_status_id: Annotated[ + str | None, + Field( + description='Current authenticated consumer status statement that caused this mismatch.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] = None + delivery_config_id: Annotated[ + str | None, Field(max_length=64, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,64}$') + ] = None + delivery_config_version: Annotated[SchemaInt | None, Field(ge=1)] = None + feed_purpose: reporting_delivery_offering.ReportingFeedPurpose | None = None + media_buy_ids: Annotated[ + list[reporting_coverage.ReportingMediaBuyId] | None, Field(min_length=1) + ] = None + package_ids: Annotated[ + list[reporting_coverage.ReportingPackageId] | None, Field(min_length=1) + ] = None + period_start: AwareDatetime | None = None + period_end: AwareDatetime | None = None + expected_at: AwareDatetime | None = None diff --git a/src/adcp/types/domains/core/reporting_verification.py b/src/adcp/types/domains/core/reporting_verification.py index 1c1c4f5a9..d10af955d 100644 --- a/src/adcp/types/domains/core/reporting_verification.py +++ b/src/adcp/types/domains/core/reporting_verification.py @@ -1,32 +1,104 @@ -"""Types declared by the AdCP ``core/reporting_verification`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_verification.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.reporting_verification import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AwareDatetime, ConfigDict, Field -from adcp.types.generated_poc.core.reporting_verification import ( - NativeCommitEvidence, - ObservedThrough, - PhysicalChecksums, - PhysicalChecksums1, - ReportingVerification, - VerificationPath, +from . import ( + reporting_canonical_content_digest, + reporting_control_total, + reporting_file_object_ref, + reporting_native_version_ref, + reporting_verification_profile, ) -# Explicit exports -__all__ = [ - "NativeCommitEvidence", - "ObservedThrough", - "PhysicalChecksums", - "PhysicalChecksums1", - "ReportingVerification", - "VerificationPath", -] + +class VerificationPath(StrEnum): + producer = 'producer' + representative_consumer = 'representative_consumer' + destination = 'destination' + + +class PhysicalChecksums(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + object_ref: reporting_file_object_ref.ReportingFileObjectReference + algorithm: Literal['sha256'] = 'sha256' + value: Annotated[str, Field(pattern='^[A-Fa-f0-9]{64}$')] + + +class PhysicalChecksums1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + object_ref: reporting_file_object_ref.ReportingFileObjectReference + algorithm: Literal['sha512'] = 'sha512' + value: Annotated[str, Field(pattern='^[A-Fa-f0-9]{128}$')] + + +class ObservedThrough(StrEnum): + representative_consumer = 'representative_consumer' + destination = 'destination' + + +class NativeCommitEvidence(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + native_version_ref: reporting_native_version_ref.ReportingNativeVersionReference + observed_through: ObservedThrough + + +class ReportingVerification(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + verified_at: Annotated[ + AwareDatetime, + Field( + description='When the producer completed verification through the claimed consumer/destination path.' + ), + ] + verification_path: Annotated[ + VerificationPath, + Field( + description='Path on which verification succeeded. dataset_share readiness requires representative_consumer; delivered warehouse state requires destination.' + ), + ] + verification_profile: reporting_verification_profile.ReportingVerificationProfile + row_count: Annotated[ + SchemaInt, + Field( + description='Verified row count. Zero explicitly distinguishes an empty committed revision from a missing revision.', + ge=0, + ), + ] + control_totals: Annotated[ + list[reporting_control_total.ReportingControlTotal], + Field( + description='Profile-defined totals recomputed through verification_path. Names MUST be unique.' + ), + ] + canonical_content_digest: ( + reporting_canonical_content_digest.ReportingCanonicalContentDigest | None + ) = None + physical_checksums: Annotated[ + list[PhysicalChecksums | PhysicalChecksums1] | None, + Field( + description='Method-specific byte/object checksums. Different encodings of the same logical revision normally have different values.', + min_length=1, + ), + ] = None + native_commit_evidence: Annotated[ + NativeCommitEvidence | None, + Field( + description='Provider-native immutable version evidence observed through the named consumer or destination path.' + ), + ] = None diff --git a/src/adcp/types/domains/core/reporting_verification_profile.py b/src/adcp/types/domains/core/reporting_verification_profile.py index 99a9579fa..2b6208222 100644 --- a/src/adcp/types/domains/core/reporting_verification_profile.py +++ b/src/adcp/types/domains/core/reporting_verification_profile.py @@ -1,20 +1,13 @@ -"""Types declared by the AdCP ``core/reporting_verification_profile`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_verification_profile.json +# timestamp: 2026-09-01T19:37:00+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.reporting_verification_profile import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.reporting_verification_profile import ( - ReportingVerificationProfile, -) +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["ReportingVerificationProfile"] +class ReportingVerificationProfile(StrEnum): + native_commit = 'native_commit' + manifest_checksums = 'manifest_checksums' + canonical_digest = 'canonical_digest' diff --git a/src/adcp/types/domains/core/reporting_verification_profile_set.py b/src/adcp/types/domains/core/reporting_verification_profile_set.py index 9389902d2..8b6ec2b01 100644 --- a/src/adcp/types/domains/core/reporting_verification_profile_set.py +++ b/src/adcp/types/domains/core/reporting_verification_profile_set.py @@ -1,21 +1,27 @@ -"""Types declared by the AdCP ``core/reporting_verification_profile_set`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_verification_profile_set.json +# timestamp: 2026-09-01T19:37:00+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.reporting_verification_profile_set import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import Field, RootModel -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.reporting_verification_profile_set import ( - ReportingVerificationProfileSet, - ReportingVerificationProfileSetEnum, -) +class ReportingVerificationProfileSetEnum(StrEnum): + native_commit = 'native_commit' + manifest_checksums = 'manifest_checksums' + canonical_digest = 'canonical_digest' + -# Explicit exports -__all__ = ["ReportingVerificationProfileSet", "ReportingVerificationProfileSetEnum"] +class ReportingVerificationProfileSet(RootModel[list[ReportingVerificationProfileSetEnum]]): + root: Annotated[ + list[ReportingVerificationProfileSetEnum], + Field( + description="Verification profiles the destination can accept. native_commit requires provider-native transaction/version evidence plus counts and control totals; manifest_checksums requires a committed file manifest with cryptographic checksums; canonical_digest requires recomputation of the canonical logical-content digest. A reporting feed selects one profile from this allowed set according to the seller offering and the feed's strictness requirements.", + min_length=1, + title='Reporting Verification Profile Set', + ), + ] diff --git a/src/adcp/types/domains/core/reporting_webhook.py b/src/adcp/types/domains/core/reporting_webhook.py index fff9c47d2..f20d22909 100644 --- a/src/adcp/types/domains/core/reporting_webhook.py +++ b/src/adcp/types/domains/core/reporting_webhook.py @@ -1,22 +1,73 @@ -"""Types declared by the AdCP ``core/reporting_webhook`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_webhook.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.reporting_webhook import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import auth_scheme, available_metric + + +class Authentication(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + schemes: Annotated[ + list[auth_scheme.AuthenticationScheme], + Field( + description="Array of authentication schemes. ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD use the RFC 9421 webhook signing profile instead.", + max_length=1, + min_length=1, + ), + ] + credentials: Annotated[ + str, + Field( + description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', + min_length=32, + ), + ] + + +class ReportingFrequency(StrEnum): + hourly = 'hourly' + daily = 'daily' + monthly = 'monthly' -from adcp.types.generated_poc.core.reporting_webhook import ( - Authentication, - ReportingFrequency, - ReportingWebhook, -) -# Explicit exports -__all__ = ["Authentication", "ReportingFrequency", "ReportingWebhook"] +class ReportingWebhook(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + url: Annotated[AnyUrl, Field(description='Webhook endpoint URL for reporting notifications')] + token: Annotated[ + str | None, + Field( + description='Optional client-provided token for webhook validation. Echoed back in webhook payload to validate request authenticity.', + min_length=16, + ), + ] = None + authentication: Annotated[ + Authentication, + Field( + deprecated=True, + description="Legacy authentication configuration for webhook delivery (A2A-compatible). Opts the receiver into Bearer or HMAC-SHA256 signing. Both schemes are deprecated; the preferred signing profile for new integrations is RFC 9421, where the seller signs with a key published at its brand.json agents[] entry and the buyer verifies against the seller's JWKS — no shared secret crosses the wire (see docs/building/implementation/security.mdx#webhook-callbacks). This field is required in AdCP 3.x; the requirement is removed in AdCP 4.0 when the default RFC 9421 path becomes the only path.", + ), + ] + reporting_frequency: Annotated[ + ReportingFrequency, + Field( + description='Frequency for automated reporting delivery. Must be supported by all products in the media buy.' + ), + ] + requested_metrics: Annotated[ + list[available_metric.AvailableMetric] | None, + Field( + description="Optional list of metrics to include in webhook notifications. If omitted, all available metrics are included; an empty array has the same meaning as omission (it does not narrow to impressions and spend only). impressions and spend are always included regardless of this list. Must be a subset of the product's available_metrics. Subset evaluation and leaf resolution follow `enums/available-metric.json`: a numeric leaf may be covered by its container, while each structured distribution must be explicitly available. Requesting any nested identity selects its canonical carrier inside the payload's nested object. Same narrowing semantics as get_media_buy_delivery's requested_metrics (which additionally requires at least one entry when present)." + ), + ] = None diff --git a/src/adcp/types/domains/core/reporting_write_destination.py b/src/adcp/types/domains/core/reporting_write_destination.py index 1a52be7f2..e14248287 100644 --- a/src/adcp/types/domains/core/reporting_write_destination.py +++ b/src/adcp/types/domains/core/reporting_write_destination.py @@ -1,28 +1,68 @@ -"""Types declared by the AdCP ``core/reporting_write_destination`` schema. +# generated by datamodel-codegen: +# filename: core/reporting_write_destination.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.reporting_write_destination import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field + + +class ReportingWriteDestination1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + mode: Literal['existing'] = 'existing' + destination_ref: Annotated[ + str, + Field( + description='Seller-issued immutable destination-generation reference returned by sync_agent_configuration, an earlier sync, or bilateral setup.', + max_length=255, + min_length=1, + ), + ] + + +class Provider(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + domain: Annotated[ + str, Field(pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$') + ] + + +class ReportingWriteDestination2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + mode: Literal['provision'] = 'provision' + provider: Annotated[Provider, Field(description='Platform hosting the destination.')] + location: Annotated[ + str, + Field( + description='Provider-native bucket, prefix, project/dataset, catalog/schema, or equivalent locator. It MUST NOT contain an embedded credential or signed URL.', + max_length=2048, + min_length=1, + ), + ] + access_mode: Annotated[ + str | None, + Field( + description='Optional provider access family used for capability matching.', + max_length=64, + min_length=1, + pattern='^[a-z][a-z0-9_.-]*$', + ), + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.reporting_write_destination import ( - Provider, - ReportingWriteDestination, - ReportingWriteDestination1, - ReportingWriteDestination2, -) - -# Explicit exports -__all__ = [ - "Provider", - "ReportingWriteDestination", - "ReportingWriteDestination1", - "ReportingWriteDestination2", +ReportingWriteDestination = Annotated[ + ReportingWriteDestination1 | ReportingWriteDestination2, + Field( + description="Storage or warehouse destination for durable reporting. The caller either references an existing seller-issued immutable destination generation or asks the seller to validate and bind a provider-native location. A destination_ref is owned by the stable authenticated principal's relationship with this seller and may be reused across accounts; each account delivery configuration separately authorizes its feed and scope. Changing proof-bound coordinates or the accepted delivery contract produces a new destination_ref. Access grants name advertised producer identities; credentials never transit AdCP.", + title='Reporting Write Destination', + ), ] diff --git a/src/adcp/types/domains/core/representation_destination.py b/src/adcp/types/domains/core/representation_destination.py index 9ae7379da..b811381a9 100644 --- a/src/adcp/types/domains/core/representation_destination.py +++ b/src/adcp/types/domains/core/representation_destination.py @@ -1,18 +1,47 @@ -"""Types declared by the AdCP ``core/representation_destination`` schema. +# generated by datamodel-codegen: +# filename: core/representation_destination.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.representation_destination import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import daast_version, vast_version +from . import placement_ref, product_format_declaration -from adcp.types.generated_poc.core.representation_destination import RepresentationDestination -# Explicit exports -__all__ = ["RepresentationDestination"] +class RepresentationDestination(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + product_id: Annotated[ + str, Field(description='Product owned by the destination seller.', min_length=1) + ] + format_option: Annotated[ + product_format_declaration.ProductFormatDeclaration, + Field( + description='Exact effective destination declaration. The full declaration is carried so an option without format_option_id remains addressable. The destination seller verifies it against the current product and applicable placement/publisher narrowings.' + ), + ] + placement_refs: Annotated[ + list[placement_ref.PlacementReference] | None, + Field( + description='Optional placements the derived manifest must satisfy. Omission means every placement in the intended product route, not an unconstrained destination.', + min_length=1, + ), + ] = None + execution_vast_version: Annotated[ + vast_version.VastVersion | None, + Field( + description='Exact VAST execution version selected for seller-assembled first-class VAST trackers when no sibling exact-version VAST document supplies it. Must be accepted by format_option and is preserved in selection lineage.' + ), + ] = None + execution_daast_version: Annotated[ + daast_version.DaastVersion | None, + Field( + description='Exact DAAST execution version selected for seller-assembled first-class DAAST trackers when no sibling exact-version DAAST document supplies it. Must be accepted by format_option and is preserved in selection lineage.' + ), + ] = None diff --git a/src/adcp/types/domains/core/representation_rejection.py b/src/adcp/types/domains/core/representation_rejection.py index ad192af4b..8af9d760f 100644 --- a/src/adcp/types/domains/core/representation_rejection.py +++ b/src/adcp/types/domains/core/representation_rejection.py @@ -1,18 +1,32 @@ -"""Types declared by the AdCP ``core/representation_rejection`` schema. +# generated by datamodel-codegen: +# filename: core/representation_rejection.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.representation_rejection import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.representation_rejection import Code, RepresentationRejection +class Code(StrEnum): + incompatible_format_kind = 'incompatible_format_kind' + format_option_mismatch = 'format_option_mismatch' + unsupported_delivery_type = 'unsupported_delivery_type' + vast_version_mismatch = 'vast_version_mismatch' + macro_unsupported = 'macro_unsupported' + tracker_contract_mismatch = 'tracker_contract_mismatch' + asset_requirement_failed = 'asset_requirement_failed' + other = 'other' + -# Explicit exports -__all__ = ["Code", "RepresentationRejection"] +class RepresentationRejection(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + representation_id: Annotated[str, Field(min_length=1)] + code: Code + message: Annotated[str, Field(min_length=1)] + details: dict[str, Any] | None = None diff --git a/src/adcp/types/domains/core/representation_selection.py b/src/adcp/types/domains/core/representation_selection.py index abb78a112..0778984a0 100644 --- a/src/adcp/types/domains/core/representation_selection.py +++ b/src/adcp/types/domains/core/representation_selection.py @@ -1,21 +1,64 @@ -"""Types declared by the AdCP ``core/representation_selection`` schema. +# generated by datamodel-codegen: +# filename: core/representation_selection.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.representation_selection import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import daast_version, representation_selection_strategy, vast_version +from . import creative_revision_id + + +class ResolvedBy(StrEnum): + buyer = 'buyer' + seller = 'seller' -from adcp.types.generated_poc.core.representation_selection import ( - RepresentationSelection, - ResolvedBy, -) -# Explicit exports -__all__ = ["RepresentationSelection", "ResolvedBy"] +class RepresentationSelection(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + creative_id: Annotated[str, Field(min_length=1)] + revision_id: creative_revision_id.CreativeRevisionId + revision_content_digest: Annotated[ + str, + Field( + description='Verified digest from the complete source representation set. A downstream seller uses this value, not the selected manifest bytes alone, to enforce immutable revision reuse.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] + selected_representation_id: Annotated[str, Field(min_length=1)] + strategy: Annotated[ + representation_selection_strategy.RepresentationSelectionStrategy, + Field(description='Deterministic strategy applied after compatibility filtering.'), + ] + selected_output_digest: Annotated[ + str, + Field( + description='Digest of the derived seller-bound manifest projection, computed with RFC 8785 JCS after removing exactly top-level `$schema`, `representation_selection`, `creative_id`, `revision_id`, `name`, `tags`, `status`, `weight`, `placement_refs`, `placement_ids`, and `inputs`. Every other field, including unknown delivery fields, is included. The identical exclusion list applies to the returned CreativeManifest and its later CreativeAsset sync wrapper. A selected sync item cannot carry localization in this version. This is the review/execution fingerprint for the projection, distinct from revision_content_digest, which binds the complete source set.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] + execution_vast_version: Annotated[ + vast_version.VastVersion | None, + Field( + description='Exact VAST version verified for seller-assembled first-class VAST trackers. This is selection lineage and part of the execution review identity; it is not included in selected_output_digest.' + ), + ] = None + execution_daast_version: Annotated[ + daast_version.DaastVersion | None, + Field( + description='Exact DAAST version verified for seller-assembled first-class DAAST trackers. This is selection lineage and part of the execution review identity; it is not included in selected_output_digest.' + ), + ] = None + resolved_by: Annotated[ + ResolvedBy, + Field( + description='Who performed deterministic compatibility resolution. A buyer may resolve locally from seller discovery. Seller resolution is valid only on the destination sales agent when it advertised representation_resolution; an independent creative agent cannot claim seller-bound compatibility.' + ), + ] diff --git a/src/adcp/types/domains/core/requirements/__init__.py b/src/adcp/types/domains/core/requirements/__init__.py index a9d83b7d2..27a34857c 100644 --- a/src/adcp/types/domains/core/requirements/__init__.py +++ b/src/adcp/types/domains/core/requirements/__init__.py @@ -1,7 +1,3 @@ -"""Public mirror of the AdCP ``core/requirements`` schema directory. - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:34:03 UTC -""" - -__all__: list[str] = [] +# generated by datamodel-codegen: +# filename: .schema_temp +# timestamp: 2026-02-12T02:11:19+00:00 diff --git a/src/adcp/types/domains/core/requirements/asset_requirements.py b/src/adcp/types/domains/core/requirements/asset_requirements.py index 36e10964a..13a6f7d3d 100644 --- a/src/adcp/types/domains/core/requirements/asset_requirements.py +++ b/src/adcp/types/domains/core/requirements/asset_requirements.py @@ -1,18 +1,44 @@ -"""Types declared by the AdCP ``core/requirements/asset_requirements`` schema. +# generated by datamodel-codegen: +# filename: core/requirements/asset_requirements.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.requirements.asset_requirements import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import ( + audio_asset_requirements, + css_asset_requirements, + daast_asset_requirements, + html_asset_requirements, + image_asset_requirements, + javascript_asset_requirements, + markdown_asset_requirements, + text_asset_requirements, + url_asset_requirements, + vast_asset_requirements, + video_asset_requirements, + webhook_asset_requirements, +) -from adcp.types.generated_poc.core.requirements.asset_requirements import AssetRequirements -# Explicit exports -__all__ = ["AssetRequirements"] +AssetRequirements = Annotated[ + image_asset_requirements.ImageAssetRequirements + | video_asset_requirements.VideoAssetRequirements + | audio_asset_requirements.AudioAssetRequirements + | text_asset_requirements.TextAssetRequirements + | markdown_asset_requirements.MarkdownAssetRequirements + | html_asset_requirements.HtmlAssetRequirements + | css_asset_requirements.CssAssetRequirements + | javascript_asset_requirements.JavascriptAssetRequirements + | vast_asset_requirements.VastAssetRequirements + | daast_asset_requirements.DaastAssetRequirements + | url_asset_requirements.UrlAssetRequirements + | webhook_asset_requirements.WebhookAssetRequirements, + Field( + description='Technical requirements for creative assets. The applicable schema is determined by the sibling asset_type field.', + title='Asset Requirements', + ), +] diff --git a/src/adcp/types/domains/core/requirements/audio_asset_requirements.py b/src/adcp/types/domains/core/requirements/audio_asset_requirements.py index 7c7f78336..673dd0b39 100644 --- a/src/adcp/types/domains/core/requirements/audio_asset_requirements.py +++ b/src/adcp/types/domains/core/requirements/audio_asset_requirements.py @@ -1,23 +1,72 @@ -"""Types declared by the AdCP ``core/requirements/audio_asset_requirements`` schema. +# generated by datamodel-codegen: +# filename: core/requirements/audio_asset_requirements.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.requirements.audio_asset_requirements import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from adcp.types._scalar import ScalarInt +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.requirements.audio_asset_requirements import ( - AudioAssetRequirements, - Channel, - Format, - SampleRate, -) +class Format(StrEnum): + mp3 = 'mp3' + aac = 'aac' + wav = 'wav' + ogg = 'ogg' + flac = 'flac' + + +class SampleRate(ScalarInt): + __slots__ = () + _constraints = {'ge': 1} + + +class Channel(StrEnum): + mono = 'mono' + stereo = 'stereo' + -# Explicit exports -__all__ = ["AudioAssetRequirements", "Channel", "Format", "SampleRate"] +class AudioAssetRequirements(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + min_duration_ms: Annotated[ + SchemaInt | None, Field(description='Minimum duration in milliseconds', ge=1) + ] = None + max_duration_ms: Annotated[ + SchemaInt | None, Field(description='Maximum duration in milliseconds', ge=1) + ] = None + formats: Annotated[list[Format] | None, Field(description='Accepted audio file formats')] = None + max_file_size_kb: Annotated[ + SchemaInt | None, Field(description='Maximum file size in kilobytes', ge=1) + ] = None + sample_rates: Annotated[ + list[SampleRate] | None, + Field(description='Accepted sample rates in Hz (e.g., [44100, 48000])'), + ] = None + channels: Annotated[ + list[Channel] | None, Field(description='Accepted audio channel configurations') + ] = None + min_bitrate_kbps: Annotated[ + SchemaInt | None, Field(description='Minimum audio bitrate in kilobits per second', ge=1) + ] = None + max_bitrate_kbps: Annotated[ + SchemaInt | None, Field(description='Maximum audio bitrate in kilobits per second', ge=1) + ] = None + loudness_lufs: Annotated[ + StrictFloat | None, + Field( + description='Target integrated loudness in LUFS. LKFS is the equivalent unit name under ITU-R BS.1770; values expressed in LUFS and LKFS are directly comparable without conversion.' + ), + ] = None + loudness_tolerance_db: Annotated[ + StrictFloat | None, + Field(description='Acceptable deviation from the loudness_lufs target in dB.', ge=0.0), + ] = None + true_peak_dbfs: Annotated[ + StrictFloat | None, Field(description='Maximum true-peak level in dBFS.') + ] = None diff --git a/src/adcp/types/domains/core/requirements/catalog_field_binding.py b/src/adcp/types/domains/core/requirements/catalog_field_binding.py index 6713c48f2..c3d6bfcd6 100644 --- a/src/adcp/types/domains/core/requirements/catalog_field_binding.py +++ b/src/adcp/types/domains/core/requirements/catalog_field_binding.py @@ -1,30 +1,144 @@ -"""Types declared by the AdCP ``core/requirements/catalog_field_binding`` schema. +# generated by datamodel-codegen: +# filename: core/requirements/catalog_field_binding.json +# timestamp: 2026-10-03T00:36:50+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.requirements.catalog_field_binding import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field + +from .. import ext as ext_1 + + +class ScalarBinding(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + kind: Literal['scalar'] = 'scalar' + asset_id: Annotated[ + str, + Field( + description="The asset_id from the format's assets array. Identifies which individual template slot this binding applies to." + ), + ] + catalog_field: Annotated[ + str, + Field( + description="Dot-notation path to the field on the catalog item (e.g., 'name', 'price.amount', 'location.city')." + ), + ] + ext: ext_1.ExtensionObject | None = None + + +class AssetPoolBinding(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + kind: Literal['asset_pool'] = 'asset_pool' + asset_id: Annotated[ + str, + Field( + description="The asset_id from the format's assets array. Identifies which individual template slot this binding applies to." + ), + ] + asset_group_id: Annotated[ + str, + Field( + description="The asset_group_id on the catalog item's assets array to pull from (e.g., 'images_landscape', 'images_vertical', 'logo')." + ), + ] + ext: ext_1.ExtensionObject | None = None + + +PerItemBindings = Annotated[ + ScalarBinding | AssetPoolBinding, + Field(discriminator='kind'), +] + + +class CatalogFieldBinding1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + kind: Literal['catalog_group'] = 'catalog_group' + format_group_id: Annotated[ + str, + Field(description="The asset_group_id of a repeatable_group in the format's assets array."), + ] + catalog_item: Annotated[ + Literal[True], + Field( + description="Each repetition of the format's repeatable_group maps to one item from the catalog." + ), + ] + per_item_bindings: Annotated[ + list[PerItemBindings] | None, + Field( + description='Scalar and asset pool bindings that apply within each repetition of the group. Nested catalog_group bindings are not permitted.', + min_length=1, + ), + ] = None + ext: ext_1.ExtensionObject | None = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.requirements.catalog_field_binding import ( - AssetPoolBinding, - CatalogFieldBinding, - CatalogFieldBinding1, - PerItemBindings, - ScalarBinding, -) - -# Explicit exports -__all__ = [ - "AssetPoolBinding", - "CatalogFieldBinding", - "CatalogFieldBinding1", - "PerItemBindings", - "ScalarBinding", +CatalogFieldBinding = Annotated[ + ScalarBinding | AssetPoolBinding | CatalogFieldBinding1, + Field( + description="Maps a format template slot to a catalog item field or typed asset pool. The 'kind' field identifies the binding variant. All bindings are optional — agents can still infer mappings without them.", + discriminator='kind', + examples=[ + { + 'description': 'Scalar binding — hotel name to headline slot', + 'data': {'kind': 'scalar', 'asset_id': 'headline', 'catalog_field': 'name'}, + }, + { + 'description': 'Scalar binding — nested field (nightly rate)', + 'data': { + 'kind': 'scalar', + 'asset_id': 'price_badge', + 'catalog_field': 'price.amount', + }, + }, + { + 'description': 'Asset pool binding — hero image from landscape pool', + 'data': { + 'kind': 'asset_pool', + 'asset_id': 'hero_image', + 'asset_group_id': 'images_landscape', + }, + }, + { + 'description': 'Asset pool binding — Snap vertical background from vertical pool', + 'data': { + 'kind': 'asset_pool', + 'asset_id': 'snap_background', + 'asset_group_id': 'images_vertical', + }, + }, + { + 'description': 'Catalog group binding — carousel where each slide is one hotel', + 'data': { + 'kind': 'catalog_group', + 'format_group_id': 'slide', + 'catalog_item': True, + 'per_item_bindings': [ + {'kind': 'scalar', 'asset_id': 'title', 'catalog_field': 'name'}, + { + 'kind': 'scalar', + 'asset_id': 'price', + 'catalog_field': 'price.amount', + }, + { + 'kind': 'asset_pool', + 'asset_id': 'image', + 'asset_group_id': 'images_landscape', + }, + ], + }, + }, + ], + title='Catalog Field Binding', + ), ] diff --git a/src/adcp/types/domains/core/requirements/catalog_requirements.py b/src/adcp/types/domains/core/requirements/catalog_requirements.py index 27a4fc66b..81471366d 100644 --- a/src/adcp/types/domains/core/requirements/catalog_requirements.py +++ b/src/adcp/types/domains/core/requirements/catalog_requirements.py @@ -1,18 +1,72 @@ -"""Types declared by the AdCP ``core/requirements/catalog_requirements`` schema. +# generated by datamodel-codegen: +# filename: core/requirements/catalog_requirements.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.requirements.catalog_requirements import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations +from ...enums import catalog_type as catalog_type_1 +from ...enums import feed_format +from . import catalog_field_binding, offering_asset_constraint -from adcp.types.generated_poc.core.requirements.catalog_requirements import CatalogRequirements -# Explicit exports -__all__ = ["CatalogRequirements"] +class CatalogRequirements(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + catalog_type: Annotated[ + catalog_type_1.CatalogType, + Field(description='The catalog type this requirement applies to'), + ] + required: Annotated[ + StrictBool | None, + Field( + description='Whether this catalog type must be present. When true, creatives using this format must reference a synced catalog of this type.' + ), + ] = True + min_items: Annotated[ + SchemaInt | None, + Field( + description='Minimum number of items the catalog must contain for this format to render properly (e.g., a carousel might require at least 3 products)', + ge=1, + ), + ] = None + max_items: Annotated[ + SchemaInt | None, + Field( + description='Maximum number of items the format can render. Items beyond this limit are ignored. Useful for fixed-slot layouts (e.g., a 3-product card) or feed-size constraints.', + ge=1, + ), + ] = None + required_fields: Annotated[ + list[str] | None, + Field( + description="Fields that must be present and non-empty on every item in the catalog. Field names are catalog-type-specific (e.g., 'title', 'price', 'image_url' for product catalogs; 'store_id', 'quantity' for inventory feeds).", + min_length=1, + ), + ] = None + feed_formats: Annotated[ + list[feed_format.FeedFormat] | None, + Field( + description='Accepted feed formats for this catalog type. When specified, the synced catalog must use one of these formats. When omitted, any format is accepted.', + min_length=1, + ), + ] = None + offering_asset_constraints: Annotated[ + list[offering_asset_constraint.OfferingAssetConstraint] | None, + Field( + description="Per-item creative asset requirements. Declares what asset groups (headlines, images, videos) each catalog item must provide in its assets array, along with count bounds and per-asset technical constraints. Applicable to 'offering' and all vertical catalog types (hotel, flight, job, etc.) whose items carry typed assets.", + min_length=1, + ), + ] = None + field_bindings: Annotated[ + list[catalog_field_binding.CatalogFieldBinding] | None, + Field( + description='Explicit mappings from format template slots to catalog item fields or typed asset pools. Optional — creative agents can infer mappings without them, but bindings make the relationship self-describing and enable validation. Covers scalar fields (asset_id → catalog_field), asset pools (asset_id → asset_group_id on the catalog item), and repeatable groups that iterate over catalog items.', + min_length=1, + ), + ] = None diff --git a/src/adcp/types/domains/core/requirements/css_asset_requirements.py b/src/adcp/types/domains/core/requirements/css_asset_requirements.py index 2a6e7326d..96bdaef76 100644 --- a/src/adcp/types/domains/core/requirements/css_asset_requirements.py +++ b/src/adcp/types/domains/core/requirements/css_asset_requirements.py @@ -1,18 +1,19 @@ -"""Types declared by the AdCP ``core/requirements/css_asset_requirements`` schema. +# generated by datamodel-codegen: +# filename: core/requirements/css_asset_requirements.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.requirements.css_asset_requirements import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field -from adcp.types.generated_poc.core.requirements.css_asset_requirements import CssAssetRequirements -# Explicit exports -__all__ = ["CssAssetRequirements"] +class CssAssetRequirements(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + max_file_size_kb: Annotated[ + SchemaInt | None, Field(description='Maximum file size in kilobytes', ge=1) + ] = None diff --git a/src/adcp/types/domains/core/requirements/daast_asset_requirements.py b/src/adcp/types/domains/core/requirements/daast_asset_requirements.py index af6df8b81..1b591a10b 100644 --- a/src/adcp/types/domains/core/requirements/daast_asset_requirements.py +++ b/src/adcp/types/domains/core/requirements/daast_asset_requirements.py @@ -1,20 +1,30 @@ -"""Types declared by the AdCP ``core/requirements/daast_asset_requirements`` schema. +# generated by datamodel-codegen: +# filename: core/requirements/daast_asset_requirements.json +# timestamp: 2026-08-28T20:03:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.requirements.daast_asset_requirements import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ...enums import daast_version as daast_version_1 +from .. import daast_tracker_constraints -from adcp.types.generated_poc.core.requirements.daast_asset_requirements import ( - DaastAssetRequirements, -) -# Explicit exports -__all__ = ["DaastAssetRequirements"] +class DaastAssetRequirements(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + daast_version: Annotated[ + daast_version_1.DaastVersion | None, + Field( + deprecated=True, + description='Deprecated one-element alias for `daast_versions`. Producers use either the singular legacy alias or the plural 3.2 field, never both.', + ), + ] = None + daast_versions: Annotated[ + daast_tracker_constraints.DaastVersions | None, + Field(description='Accepted DAAST version set for this asset requirement.'), + ] = None diff --git a/src/adcp/types/domains/core/requirements/html_asset_requirements.py b/src/adcp/types/domains/core/requirements/html_asset_requirements.py index 91ccdbb54..303810f4c 100644 --- a/src/adcp/types/domains/core/requirements/html_asset_requirements.py +++ b/src/adcp/types/domains/core/requirements/html_asset_requirements.py @@ -1,21 +1,46 @@ -"""Types declared by the AdCP ``core/requirements/html_asset_requirements`` schema. +# generated by datamodel-codegen: +# filename: core/requirements/html_asset_requirements.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.requirements.html_asset_requirements import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.requirements.html_asset_requirements import ( - HtmlAssetRequirements, - Sandbox, -) +class Sandbox(StrEnum): + none = 'none' + iframe = 'iframe' + safeframe = 'safeframe' + fencedframe = 'fencedframe' + -# Explicit exports -__all__ = ["HtmlAssetRequirements", "Sandbox"] +class HtmlAssetRequirements(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + max_file_size_kb: Annotated[ + SchemaInt | None, + Field(description='Maximum file size in kilobytes for the HTML asset', ge=1), + ] = None + sandbox: Annotated[ + Sandbox | None, + Field( + description="Sandbox environment the HTML must be compatible with. 'none' = direct DOM access, 'iframe' = standard iframe isolation, 'safeframe' = IAB SafeFrame container, 'fencedframe' = Privacy Sandbox fenced frame" + ), + ] = Sandbox.none + external_resources_allowed: Annotated[ + StrictBool | None, + Field( + description='Whether the HTML creative can load external resources (scripts, images, fonts, etc.). When false, all resources must be inlined or bundled.' + ), + ] = None + allowed_external_domains: Annotated[ + list[str] | None, + Field( + description='List of domains the HTML creative may reference for external resources. Only applicable when external_resources_allowed is true.' + ), + ] = None diff --git a/src/adcp/types/domains/core/requirements/image_asset_requirements.py b/src/adcp/types/domains/core/requirements/image_asset_requirements.py index 33d9e543b..f0a643f4e 100644 --- a/src/adcp/types/domains/core/requirements/image_asset_requirements.py +++ b/src/adcp/types/domains/core/requirements/image_asset_requirements.py @@ -1,25 +1,159 @@ -"""Types declared by the AdCP ``core/requirements/image_asset_requirements`` schema. +# generated by datamodel-codegen: +# filename: core/requirements/image_asset_requirements.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.requirements.image_asset_requirements import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from adcp.types._scalar import ScalarFloat +from pydantic import ConfigDict, Field, StrictBool, StrictFloat + +from ...enums import dimension_unit + + +class PixelRatio(ScalarFloat): + __slots__ = () + _constraints = {'gt': 0.0} + + +class Format(StrEnum): + jpg = 'jpg' + jpeg = 'jpeg' + png = 'png' + gif = 'gif' + webp = 'webp' + svg = 'svg' + avif = 'avif' + tiff = 'tiff' + pdf = 'pdf' + eps = 'eps' + + +class Bleed(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + uniform: Annotated[StrictFloat, Field(description='Same bleed on all four sides', ge=0.0)] + + +class Bleed1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + top: Annotated[StrictFloat, Field(ge=0.0)] + right: Annotated[StrictFloat, Field(ge=0.0)] + bottom: Annotated[StrictFloat, Field(ge=0.0)] + left: Annotated[StrictFloat, Field(ge=0.0)] + + +class ColorSpace(StrEnum): + rgb = 'rgb' + cmyk = 'cmyk' + grayscale = 'grayscale' -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.requirements.image_asset_requirements import ( - Bleed, - Bleed1, - ColorSpace, - Format, - ImageAssetRequirements, - PixelRatio, -) - -# Explicit exports -__all__ = ["Bleed", "Bleed1", "ColorSpace", "Format", "ImageAssetRequirements", "PixelRatio"] +class ImageAssetRequirements(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + min_width: Annotated[ + StrictFloat | None, + Field( + description='Minimum width. Interpretation depends on unit (default: pixels). For exact dimensions, set min_width = max_width.', + gt=0.0, + ), + ] = None + max_width: Annotated[ + StrictFloat | None, + Field( + description='Maximum width. Interpretation depends on unit (default: pixels). For exact dimensions, set min_width = max_width.', + gt=0.0, + ), + ] = None + min_height: Annotated[ + StrictFloat | None, + Field( + description='Minimum height. Interpretation depends on unit (default: pixels). For exact dimensions, set min_height = max_height.', + gt=0.0, + ), + ] = None + max_height: Annotated[ + StrictFloat | None, + Field( + description='Maximum height. Interpretation depends on unit (default: pixels). For exact dimensions, set min_height = max_height.', + gt=0.0, + ), + ] = None + pixel_ratios: Annotated[ + list[PixelRatio] | None, + Field( + description='Accepted intrinsic-pixel densities for this image slot. One submitted image matching any listed ratio satisfies the slot; the array does not require one asset per ratio. Absence imposes no density constraint beyond the existing dimension requirements.', + min_length=1, + ), + ] = None + parameters_from_format_id: Annotated[ + StrictBool | None, + Field( + description="When true on a legacy template format, logical width, logical height, and pixel ratio are supplied by the parameterized format_id. The image asset's intrinsic dimensions MUST equal logical dimensions multiplied by pixel_ratio (which defaults to 1 when omitted)." + ), + ] = None + unit: Annotated[ + dimension_unit.DimensionUnit | None, + Field( + description="Unit of measurement for width/height values. Defaults to 'px' when absent. Print formats use 'inches' or 'cm'." + ), + ] = None + aspect_ratio: Annotated[ + str | None, + Field( + description="Required aspect ratio (e.g., '16:9', '1:1', '1.91:1')", + pattern='^\\d+(\\.\\d+)?:\\d+(\\.\\d+)?$', + ), + ] = None + formats: Annotated[list[Format] | None, Field(description='Accepted image file formats')] = None + min_dpi: Annotated[ + SchemaInt | None, + Field( + description='Minimum resolution in dots per inch. Always in DPI regardless of the dimension unit. Standard print requires 300 DPI, newspaper 150 DPI.', + ge=1, + ), + ] = None + bleed: Annotated[ + Bleed | Bleed1 | None, + Field( + description='Required bleed area beyond the trim size. The submitted image must be larger than the declared dimensions: total width = trim width + left bleed + right bleed, total height = trim height + top bleed + bottom bleed. For uniform bleed: total = trim + (2 * uniform). Uses the same unit as the parent dimensions.' + ), + ] = None + color_space: Annotated[ + ColorSpace | None, Field(description='Required color space. Print typically requires CMYK.') + ] = None + max_file_size_kb: Annotated[ + SchemaInt | None, Field(description='Maximum file size in kilobytes', ge=1) + ] = None + transparency_required: Annotated[ + StrictBool | None, + Field( + description='Whether the image must support transparency (requires PNG, WebP, or GIF)' + ), + ] = None + animation_allowed: Annotated[ + StrictBool | None, + Field(description='Whether animated images (GIF, animated WebP) are accepted'), + ] = None + max_animation_duration_ms: Annotated[ + SchemaInt | None, + Field( + description='Maximum animation duration in milliseconds (if animation_allowed is true)', + ge=0, + ), + ] = None + max_weight_grams: Annotated[ + SchemaInt | None, + Field( + description='Maximum weight in grams for the finished physical piece (print inserts, flyers). Affects postage calculations and production constraints. Only applicable to print channels.', + gt=0, + ), + ] = None diff --git a/src/adcp/types/domains/core/requirements/javascript_asset_requirements.py b/src/adcp/types/domains/core/requirements/javascript_asset_requirements.py index efd7a7183..a2757e7e9 100644 --- a/src/adcp/types/domains/core/requirements/javascript_asset_requirements.py +++ b/src/adcp/types/domains/core/requirements/javascript_asset_requirements.py @@ -1,21 +1,46 @@ -"""Types declared by the AdCP ``core/requirements/javascript_asset_requirements`` schema. +# generated by datamodel-codegen: +# filename: core/requirements/javascript_asset_requirements.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.requirements.javascript_asset_requirements import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.requirements.javascript_asset_requirements import ( - JavascriptAssetRequirements, - ModuleType, -) +class ModuleType(StrEnum): + script = 'script' + module = 'module' + iife = 'iife' + -# Explicit exports -__all__ = ["JavascriptAssetRequirements", "ModuleType"] +class JavascriptAssetRequirements(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + max_file_size_kb: Annotated[ + SchemaInt | None, + Field(description='Maximum file size in kilobytes for the JavaScript asset', ge=1), + ] = None + module_type: Annotated[ + ModuleType | None, + Field( + description="Required JavaScript module format. 'script' = classic script, 'module' = ES modules, 'iife' = immediately invoked function expression" + ), + ] = None + strict_mode_required: Annotated[ + StrictBool | None, Field(description='Whether the JavaScript must use strict mode') + ] = None + external_resources_allowed: Annotated[ + StrictBool | None, + Field(description='Whether the JavaScript can load external resources dynamically'), + ] = None + allowed_external_domains: Annotated[ + list[str] | None, + Field( + description='List of domains the JavaScript may reference for external resources. Only applicable when external_resources_allowed is true.' + ), + ] = None diff --git a/src/adcp/types/domains/core/requirements/markdown_asset_requirements.py b/src/adcp/types/domains/core/requirements/markdown_asset_requirements.py index 816e74443..ff057afdc 100644 --- a/src/adcp/types/domains/core/requirements/markdown_asset_requirements.py +++ b/src/adcp/types/domains/core/requirements/markdown_asset_requirements.py @@ -1,20 +1,19 @@ -"""Types declared by the AdCP ``core/requirements/markdown_asset_requirements`` schema. +# generated by datamodel-codegen: +# filename: core/requirements/markdown_asset_requirements.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.requirements.markdown_asset_requirements import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field -from adcp.types.generated_poc.core.requirements.markdown_asset_requirements import ( - MarkdownAssetRequirements, -) -# Explicit exports -__all__ = ["MarkdownAssetRequirements"] +class MarkdownAssetRequirements(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + max_length: Annotated[SchemaInt | None, Field(description='Maximum character length', ge=1)] = ( + None + ) diff --git a/src/adcp/types/domains/core/requirements/offering_asset_constraint.py b/src/adcp/types/domains/core/requirements/offering_asset_constraint.py index a1fdc6bf2..752c31af6 100644 --- a/src/adcp/types/domains/core/requirements/offering_asset_constraint.py +++ b/src/adcp/types/domains/core/requirements/offering_asset_constraint.py @@ -1,20 +1,49 @@ -"""Types declared by the AdCP ``core/requirements/offering_asset_constraint`` schema. +# generated by datamodel-codegen: +# filename: core/requirements/offering_asset_constraint.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.requirements.offering_asset_constraint import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations +from ...enums import asset_content_type +from .. import ext as ext_1 +from . import asset_requirements as asset_requirements_1 -from adcp.types.generated_poc.core.requirements.offering_asset_constraint import ( - OfferingAssetConstraint, -) -# Explicit exports -__all__ = ["OfferingAssetConstraint"] +class OfferingAssetConstraint(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_group_id: Annotated[ + str, + Field( + description="The asset group this constraint applies to. Values are canonical-format vocabulary — each declaration chooses its own group IDs (e.g., 'headlines', 'images', 'videos'). Buyers discover them through Product.format_options[], publisher adagents.json formats[], or creative.supported_formats[] according to context." + ), + ] + asset_type: Annotated[ + asset_content_type.AssetContentType, + Field(description='The expected content type for this group.'), + ] + required: Annotated[ + StrictBool | None, + Field( + description='Whether this asset group must be present in each offering. Defaults to true.' + ), + ] = True + min_count: Annotated[ + SchemaInt | None, Field(description='Minimum number of items required in this group.', ge=1) + ] = None + max_count: Annotated[ + SchemaInt | None, Field(description='Maximum number of items allowed in this group.', ge=1) + ] = None + asset_requirements: Annotated[ + asset_requirements_1.AssetRequirements | None, + Field( + description='Technical requirements for each item in this group (e.g., max_length for text, min_width/aspect_ratio for images). Applies uniformly to all items in the group.' + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/requirements/text_asset_requirements.py b/src/adcp/types/domains/core/requirements/text_asset_requirements.py index e8cc3ca60..9ae90b27e 100644 --- a/src/adcp/types/domains/core/requirements/text_asset_requirements.py +++ b/src/adcp/types/domains/core/requirements/text_asset_requirements.py @@ -1,18 +1,44 @@ -"""Types declared by the AdCP ``core/requirements/text_asset_requirements`` schema. +# generated by datamodel-codegen: +# filename: core/requirements/text_asset_requirements.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.requirements.text_asset_requirements import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field -from adcp.types.generated_poc.core.requirements.text_asset_requirements import TextAssetRequirements -# Explicit exports -__all__ = ["TextAssetRequirements"] +class TextAssetRequirements(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + min_length: Annotated[SchemaInt | None, Field(description='Minimum character length', ge=0)] = ( + None + ) + max_length: Annotated[SchemaInt | None, Field(description='Maximum character length', ge=1)] = ( + None + ) + min_lines: Annotated[SchemaInt | None, Field(description='Minimum number of lines', ge=1)] = ( + None + ) + max_lines: Annotated[SchemaInt | None, Field(description='Maximum number of lines', ge=1)] = ( + None + ) + character_pattern: Annotated[ + str | None, + Field( + description="Regex pattern defining allowed characters (e.g., '^[a-zA-Z0-9 .,!?-]+$')" + ), + ] = None + prohibited_terms: Annotated[ + list[str] | None, Field(description='List of prohibited words or phrases') + ] = None + allowed_values: Annotated[ + list[str] | None, + Field( + description='Closed set of permitted string values for this text slot. When present, a conformant implementation MUST reject any submitted content not in this list with `CREATIVE_VALUE_NOT_ALLOWED` (echoing the offending field path in `error.field` and the allowed list in `error.details.allowed_values`). Matching is case-sensitive; producers MUST supply exact casing. If the submitted value contains unresolved template tokens (e.g., {{product_name}}), validation against allowed_values MUST be deferred until interpolation is complete. All declared constraints (allowed_values, character_pattern, max_length, etc.) are conjunctive — submitted content must satisfy every applicable constraint simultaneously.', + min_length=1, + ), + ] = None diff --git a/src/adcp/types/domains/core/requirements/url_asset_requirements.py b/src/adcp/types/domains/core/requirements/url_asset_requirements.py index ab051fefc..32f541574 100644 --- a/src/adcp/types/domains/core/requirements/url_asset_requirements.py +++ b/src/adcp/types/domains/core/requirements/url_asset_requirements.py @@ -1,22 +1,53 @@ -"""Types declared by the AdCP ``core/requirements/url_asset_requirements`` schema. +# generated by datamodel-codegen: +# filename: core/requirements/url_asset_requirements.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.requirements.url_asset_requirements import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.requirements.url_asset_requirements import ( - Protocol, - Role, - UrlAssetRequirements, -) +class Role(StrEnum): + clickthrough = 'clickthrough' + landing_page = 'landing_page' + impression_tracker = 'impression_tracker' + click_tracker = 'click_tracker' + viewability_tracker = 'viewability_tracker' + third_party_tracker = 'third_party_tracker' + + +class Protocol(StrEnum): + https = 'https' + http = 'http' + -# Explicit exports -__all__ = ["Protocol", "Role", "UrlAssetRequirements"] +class UrlAssetRequirements(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + role: Annotated[ + Role | None, + Field( + description="Purpose this URL slot serves in the format — distinct from `url_type` on the manifest-side asset (which declares the receiver's invocation mechanism). A slot can be `click_tracker` (purpose) and accept a `tracker_pixel` (mechanism) URL, or `clickthrough` (purpose) and accept a `clickthrough` (mechanism) URL. Complements `asset_role` (human-readable label) by providing a machine-readable enum and serves as the receiver's fallback signal when a manifest URL asset omits `url_type`." + ), + ] = None + protocols: Annotated[ + list[Protocol] | None, + Field(description='Allowed URL protocols. HTTPS is recommended for all ad URLs.'), + ] = None + allowed_domains: Annotated[ + list[str] | None, Field(description='List of allowed domains for the URL') + ] = None + max_length: Annotated[ + SchemaInt | None, Field(description='Maximum URL length in characters', ge=1) + ] = None + macro_support: Annotated[ + StrictBool | None, + Field( + description="Coarse slot gate for macro-bearing URLs. `false` is a hard prohibition on macro tokens. `true` permits tokens but does not prove any dialect semantic, resolver, context, or encoding capability; exact support comes from the selected format option's macro_resolution_capabilities." + ), + ] = None diff --git a/src/adcp/types/domains/core/requirements/vast_asset_requirements.py b/src/adcp/types/domains/core/requirements/vast_asset_requirements.py index ac25037cf..f778813a2 100644 --- a/src/adcp/types/domains/core/requirements/vast_asset_requirements.py +++ b/src/adcp/types/domains/core/requirements/vast_asset_requirements.py @@ -1,18 +1,36 @@ -"""Types declared by the AdCP ``core/requirements/vast_asset_requirements`` schema. +# generated by datamodel-codegen: +# filename: core/requirements/vast_asset_requirements.json +# timestamp: 2026-08-28T20:03:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.requirements.vast_asset_requirements import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ...enums import vast_version as vast_version_1 +from .. import vast_media_file_requirements -from adcp.types.generated_poc.core.requirements.vast_asset_requirements import VastAssetRequirements -# Explicit exports -__all__ = ["VastAssetRequirements"] +class VastAssetRequirements(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + vast_version: Annotated[ + vast_version_1.VastVersion | None, + Field( + deprecated=True, + description='Deprecated one-element alias for `vast_versions`. Producers use either the singular legacy alias or the plural 3.2 field, never both.', + ), + ] = None + vast_versions: Annotated[ + list[vast_version_1.VastVersion] | None, + Field(description='Accepted VAST version set for this asset requirement.', min_length=1), + ] = None + media_file_requirements: Annotated[ + vast_media_file_requirements.VastMediafileRequirements | None, + Field( + description='Technical acceptance constraints for the alternative MediaFile renditions in each applicable resolved InLine linear creative.' + ), + ] = None diff --git a/src/adcp/types/domains/core/requirements/video_asset_requirements.py b/src/adcp/types/domains/core/requirements/video_asset_requirements.py index 4e24af448..9bb6b1477 100644 --- a/src/adcp/types/domains/core/requirements/video_asset_requirements.py +++ b/src/adcp/types/domains/core/requirements/video_asset_requirements.py @@ -1,32 +1,166 @@ -"""Types declared by the AdCP ``core/requirements/video_asset_requirements`` schema. +# generated by datamodel-codegen: +# filename: core/requirements/video_asset_requirements.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.requirements.video_asset_requirements import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from adcp.types._scalar import ScalarFloat, ScalarInt +from pydantic import ConfigDict, Field, StrictBool, StrictFloat + +from ...enums import audio_channel_layout +from ...enums import frame_rate_type as frame_rate_type_1 +from ...enums import gop_type as gop_type_1 +from ...enums import moov_atom_position as moov_atom_position_1 +from ...enums import scan_type as scan_type_1 + + +class Container(StrEnum): + mp4 = 'mp4' + webm = 'webm' + mov = 'mov' + avi = 'avi' + mkv = 'mkv' + + +class Codec(StrEnum): + h264 = 'h264' + h265 = 'h265' + vp8 = 'vp8' + vp9 = 'vp9' + av1 = 'av1' + prores = 'prores' + + +class FrameRate(ScalarFloat): + __slots__ = () + _constraints = {'ge': 1.0} + + +class AudioCodec(StrEnum): + aac = 'aac' + pcm = 'pcm' + ac3 = 'ac3' + eac3 = 'eac3' + mp3 = 'mp3' + opus = 'opus' + vorbis = 'vorbis' + flac = 'flac' + + +class AudioSampleRate(ScalarInt): + __slots__ = () + _constraints = {'ge': 1} -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.requirements.video_asset_requirements import ( - AudioCodec, - AudioSampleRate, - Codec, - Container, - FrameRate, - VideoAssetRequirements, -) - -# Explicit exports -__all__ = [ - "AudioCodec", - "AudioSampleRate", - "Codec", - "Container", - "FrameRate", - "VideoAssetRequirements", -] +class VideoAssetRequirements(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + min_width: Annotated[SchemaInt | None, Field(description='Minimum width in pixels', ge=1)] = ( + None + ) + max_width: Annotated[SchemaInt | None, Field(description='Maximum width in pixels', ge=1)] = ( + None + ) + min_height: Annotated[SchemaInt | None, Field(description='Minimum height in pixels', ge=1)] = ( + None + ) + max_height: Annotated[SchemaInt | None, Field(description='Maximum height in pixels', ge=1)] = ( + None + ) + aspect_ratio: Annotated[ + str | None, + Field(description="Required aspect ratio (e.g., '16:9', '9:16')", pattern='^\\d+:\\d+$'), + ] = None + min_duration_ms: Annotated[ + SchemaInt | None, Field(description='Minimum duration in milliseconds', ge=1) + ] = None + max_duration_ms: Annotated[ + SchemaInt | None, Field(description='Maximum duration in milliseconds', ge=1) + ] = None + containers: Annotated[ + list[Container] | None, Field(description='Accepted video container formats') + ] = None + codecs: Annotated[list[Codec] | None, Field(description='Accepted video codecs')] = None + max_file_size_kb: Annotated[ + SchemaInt | None, + Field(description='Maximum file size, where 1 KB is exactly 1,000 bytes', ge=1), + ] = None + min_bitrate_kbps: Annotated[ + SchemaInt | None, Field(description='Minimum video bitrate in kilobits per second', ge=1) + ] = None + max_bitrate_kbps: Annotated[ + SchemaInt | None, Field(description='Maximum video bitrate in kilobits per second', ge=1) + ] = None + frame_rates: Annotated[ + list[FrameRate] | None, + Field(description='Accepted frame rates in frames per second (e.g., [24, 30, 60])'), + ] = None + audio_required: Annotated[ + StrictBool | None, Field(description='Whether the video must include an audio track') + ] = None + frame_rate_type: Annotated[ + frame_rate_type_1.FrameRateType | None, + Field( + description='Required frame rate type. Broadcast and SSAI require constant frame rate for seamless splicing.' + ), + ] = None + scan_type: Annotated[ + scan_type_1.ScanType | None, + Field(description='Required scan type. Modern delivery requires progressive scan.'), + ] = None + gop_type: Annotated[ + gop_type_1.GopType | None, + Field( + description='Required GOP structure. SSAI and broadcast require closed GOPs for clean splice points.' + ), + ] = None + min_gop_interval_seconds: Annotated[ + StrictFloat | None, Field(description='Minimum keyframe interval in seconds', ge=0.0) + ] = None + max_gop_interval_seconds: Annotated[ + StrictFloat | None, + Field( + description='Maximum keyframe interval in seconds. SSAI typically requires 1-2 second intervals.', + ge=0.0, + ), + ] = None + moov_atom_position: Annotated[ + moov_atom_position_1.MoovAtomPosition | None, + Field( + description="Required moov atom position in MP4 container. 'start' enables progressive download without buffering the entire file." + ), + ] = None + audio_codecs: Annotated[ + list[AudioCodec] | None, + Field(description="Accepted audio codecs (e.g., ['aac', 'pcm', 'ac3'])"), + ] = None + audio_sample_rates: Annotated[ + list[AudioSampleRate] | None, + Field(description='Accepted audio sample rates in Hz (e.g., [44100, 48000])'), + ] = None + audio_channels: Annotated[ + list[audio_channel_layout.AudioChannelLayout] | None, + Field(description='Accepted audio channel configurations'), + ] = None + loudness_lufs: Annotated[ + StrictFloat | None, + Field( + description='Target integrated loudness in LUFS (e.g., -24 for broadcast, -16 for streaming)' + ), + ] = None + loudness_tolerance_db: Annotated[ + StrictFloat | None, + Field( + description='Acceptable deviation from loudness_lufs target in dB (e.g., 2 means -22 to -26 LUFS for a -24 target)', + ge=0.0, + ), + ] = None + true_peak_dbfs: Annotated[ + StrictFloat | None, + Field(description='Maximum true peak level in dBFS (e.g., -2 for broadcast)'), + ] = None diff --git a/src/adcp/types/domains/core/requirements/webhook_asset_requirements.py b/src/adcp/types/domains/core/requirements/webhook_asset_requirements.py index a725fe70e..bd1e92bab 100644 --- a/src/adcp/types/domains/core/requirements/webhook_asset_requirements.py +++ b/src/adcp/types/domains/core/requirements/webhook_asset_requirements.py @@ -1,21 +1,23 @@ -"""Types declared by the AdCP ``core/requirements/webhook_asset_requirements`` schema. +# generated by datamodel-codegen: +# filename: core/requirements/webhook_asset_requirements.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.requirements.webhook_asset_requirements import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.requirements.webhook_asset_requirements import ( - Method, - WebhookAssetRequirements, -) +class Method(StrEnum): + GET = 'GET' + POST = 'POST' + -# Explicit exports -__all__ = ["Method", "WebhookAssetRequirements"] +class WebhookAssetRequirements(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + methods: Annotated[list[Method] | None, Field(description='Allowed HTTP methods')] = None diff --git a/src/adcp/types/domains/core/response.py b/src/adcp/types/domains/core/response.py index 4bb60745d..4898fb1a2 100644 --- a/src/adcp/types/domains/core/response.py +++ b/src/adcp/types/domains/core/response.py @@ -1,18 +1,29 @@ -"""Types declared by the AdCP ``core/response`` schema. +# generated by datamodel-codegen: +# filename: core/response.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.response import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Any -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -from adcp.types.generated_poc.core.response import ProtocolResponse -# Explicit exports -__all__ = ["ProtocolResponse"] +class ProtocolResponse(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + message: Annotated[str, Field(description='Human-readable summary')] + context_id: Annotated[ + str | None, + Field( + description='Transport-managed conversation identifier. Maps to native contextId on A2A; compatibility metadata only on MCP and not a continuation or authorization mechanism.' + ), + ] = None + data: Annotated[ + Any | None, + Field( + description='AdCP task-specific response data (see individual task response schemas)' + ), + ] = None diff --git a/src/adcp/types/domains/core/response_payload_jws_envelope.py b/src/adcp/types/domains/core/response_payload_jws_envelope.py index 1dc763360..17e1f4f20 100644 --- a/src/adcp/types/domains/core/response_payload_jws_envelope.py +++ b/src/adcp/types/domains/core/response_payload_jws_envelope.py @@ -1,22 +1,87 @@ -"""Types declared by the AdCP ``core/response_payload_jws_envelope`` schema. +# generated by datamodel-codegen: +# filename: core/response_payload_jws_envelope.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.response_payload_jws_envelope import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AnyUrl, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.response_payload_jws_envelope import ( - ResponsePayload, - ResponsePayloadJwsEnvelope, - Task, -) +class Task(StrEnum): + verify_brand_claim = 'verify_brand_claim' + verify_brand_claims = 'verify_brand_claims' + + +class ResponsePayload(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + typ: Annotated[ + Literal['adcp-response-payload+jws'], + Field(description='Type discriminator preventing cross-profile replay.'), + ] + task: Annotated[Task, Field(description='Designated task whose response payload is signed.')] + brand_domain: Annotated[ + str, + Field( + description='Brand tenant whose policy store produced the answer. The signer MUST derive this from server-side tenant resolution, not caller-supplied request fields.', + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] + agent_url: Annotated[ + AnyUrl, + Field( + description='Canonical URL of the responding brand agent entry whose response-signing key verifies this envelope.' + ), + ] + request_hash: Annotated[ + str, + Field( + description='sha256: prefix plus unpadded base64url SHA-256 of the canonical request-binding object for this call.', + pattern='^sha256:[A-Za-z0-9_-]{43}$', + ), + ] + iat: Annotated[SchemaInt, Field(description='Issued-at time as Unix epoch seconds.', ge=0)] + exp: Annotated[ + SchemaInt, + Field( + description='Expiration time as Unix epoch seconds. Online verifiers reject envelopes after this time, allowing only implementation-defined clock skew.', + ge=0, + ), + ] + response: Annotated[ + dict[str, Any], + Field( + description='Canonical task-body success response payload being attested. Any unsigned task-body fields on the outer response, excluding signed_response and protocol/version envelope fields, MUST match this object.' + ), + ] + -# Explicit exports -__all__ = ["ResponsePayload", "ResponsePayloadJwsEnvelope", "Task"] +class ResponsePayloadJwsEnvelope(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + protected: Annotated[ + str, + Field( + description='Base64url-encoded JWS protected header. The decoded header MUST include alg, kid, and typ: adcp-response-payload+jws, and MUST NOT include the RFC 7797 b64 header. Verifiers enforce the key purpose by resolving kid to a JWK with adcp_use: response-signing.', + pattern='^[A-Za-z0-9_-]+$', + ), + ] + payload: Annotated[ + ResponsePayload, + Field( + description='Decoded signed payload. Signers compute the JWS payload bytes from the RFC 8785/JCS canonicalization of this object.' + ), + ] + signature: Annotated[ + str, + Field( + description='Base64url-encoded JWS signature over the protected header and canonicalized payload.', + pattern='^[A-Za-z0-9_-]+$', + ), + ] diff --git a/src/adcp/types/domains/core/rights_attestation_evaluation.py b/src/adcp/types/domains/core/rights_attestation_evaluation.py index e8d8d7cbd..ebd0bc596 100644 --- a/src/adcp/types/domains/core/rights_attestation_evaluation.py +++ b/src/adcp/types/domains/core/rights_attestation_evaluation.py @@ -1,40 +1,227 @@ -"""Types declared by the AdCP ``core/rights_attestation_evaluation`` schema. +# generated by datamodel-codegen: +# filename: core/rights_attestation_evaluation.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.rights_attestation_evaluation import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field + +from . import brand_ref +from . import ext as ext_1 +from .attestation_evaluation import AttestationEvaluation +from .attestation_reference import AttestationReference + + +class Subject(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Literal['agent'] = 'agent' + resource_type: Literal['https://adcontextprotocol.org/claims/subjects/rights-grant'] = 'https://adcontextprotocol.org/claims/subjects/rights-grant' + agent_url: Annotated[ + AnyUrl, Field(description='Canonical HTTPS endpoint of the agent the claim concerns.') + ] + ext: ext_1.ExtensionObject | None = None + + +class Subject19(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Literal['resource'] = 'resource' + resource_type: Annotated[ + Literal['https://adcontextprotocol.org/claims/subjects/rights-grant'], + Field( + description='Open, absolute URI naming the subject vocabulary, such as https://adcontextprotocol.org/claims/subjects/signal. AdCP does not maintain an exhaustive enum.' + ), + ] = 'https://adcontextprotocol.org/claims/subjects/rights-grant' + namespace: Annotated[ + AnyUrl, + Field( + description='Absolute URI identifying the namespace in which id is unique. This may be an AdCP agent endpoint, a catalog origin, or a domain-specific namespace URI.' + ), + ] + id: Annotated[ + str, + Field( + description='Stable identifier for the subject within namespace. It MUST NOT be compared without resource_type and namespace.', + max_length=1024, + min_length=1, + ), + ] + content_digest: Annotated[ + str, + Field( + description='Optional SHA-256 pin for the exact content or immutable snapshot identified by this resource subject. This is part of the complete typed subject identity and is distinct from AttestationReference.content_digest, which pins credential bytes.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] + ext: ext_1.ExtensionObject | None = None + + +class ActionBinding(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + action_type: Annotated[ + Literal['https://adcontextprotocol.org/actions/rights-grant-evaluation'], + Field( + description='Absolute URI naming the action vocabulary, such as an AdCP governance-check or audience-evidence snapshot type.' + ), + ] = 'https://adcontextprotocol.org/actions/rights-grant-evaluation' + action_id: Annotated[ + str, + Field( + description="Stable identifier of the consuming action within the domain consumer's namespace.", + max_length=1024, + min_length=1, + ), + ] + action_digest: Annotated[ + str, + Field( + description='SHA-256 digest of the domain-defined canonical action preimage.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] + + +class Evaluation(AttestationEvaluation): + action_binding: Annotated[ + ActionBinding, + Field( + description='Optional binding to the consuming action or readback. Domain consumers that rely on an evaluation MUST carry either an action id or an action digest so the result cannot be transplanted to an unrelated decision.' + ), + ] + + +class Issuer(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Annotated[ + Literal['brand'], Field(description='The issuer is identified by an AdCP BrandRef.') + ] = 'brand' + brand: brand_ref.BrandReference + ext: ext_1.ExtensionObject | None = None + + +class Issuer5(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Annotated[ + Literal['agent'], + Field( + description='The issuer is an AdCP agent identified by its canonical HTTPS endpoint.' + ), + ] = 'agent' + brand: brand_ref.BrandReference + agent_url: Annotated[ + AnyUrl, + Field( + description='Canonical HTTPS endpoint of the issuing agent. Evaluators compare it using AdCP URL canonicalization rules.' + ), + ] + ext: ext_1.ExtensionObject | None = None + + +class Issuer6(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Annotated[ + Literal['origin'], + Field( + description='The issuer is identified by a canonical HTTPS origin because no AdCP brand or agent identity applies.' + ), + ] = 'origin' + brand: brand_ref.BrandReference + origin: Annotated[ + AnyUrl, + Field( + description='Canonical HTTPS origin with no path, query, fragment, or userinfo. This identifies the issuer; it does not authorize a fetch.' + ), + ] + ext: ext_1.ExtensionObject | None = None + + +class Subject10(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Literal['brand'] = 'brand' + resource_type: Literal['https://adcontextprotocol.org/claims/subjects/rights-grant'] = 'https://adcontextprotocol.org/claims/subjects/rights-grant' + brand: brand_ref.BrandReference + ext: ext_1.ExtensionObject | None = None + + +class Reference(AttestationReference): + issuer: Annotated[ + Issuer | Issuer5 | Issuer6 | None, + Field( + description='Canonical identity of the party that issued an attestation credential. The discriminator selects an existing AdCP identity when one exists and falls back to an HTTPS origin for other attestors. This identity is a claim carried by the presentation; evaluators MUST match it against their configured trust policy and verify it from the resolved or embedded credential before relying on it.', + discriminator='type', + examples=[ + { + 'type': 'brand', + 'brand': {'domain': 'nova-brands.example', 'brand_id': 'nova_motors'}, + }, + {'type': 'agent', 'agent_url': 'https://attestor.example/adcp'}, + {'type': 'origin', 'origin': 'https://credentials.example'}, + ], + title='Attestation Issuer', + ), + ] = None + claim_type: Annotated[ + Literal['https://adcontextprotocol.org/claims/rights/grant'], + Field( + description='Open, absolute URI identifying the claim vocabulary. The URI is an identifier and need not be dereferenceable. AdCP does not maintain an enum of approved claims.' + ), + ] = 'https://adcontextprotocol.org/claims/rights/grant' + subject: Annotated[ + Subject10 | Subject | Subject19 | None, + Field( + description='Typed identity of the entity or object an attestation credential is about. Brand and agent subjects reuse canonical AdCP identities. Other resources use an open, URI-namespaced resource_type plus an identifier whose namespace is explicit. Evaluators MUST compare the resolved credential subject to this complete typed identity, not to id alone.', + discriminator='type', + examples=[ + { + 'type': 'brand', + 'brand': {'domain': 'nova-brands.example', 'brand_id': 'nova_motors'}, + }, + { + 'type': 'resource', + 'resource_type': 'https://adcontextprotocol.org/claims/subjects/signal', + 'namespace': 'https://signals.meridian.example/adcp', + 'id': 'signal_urban_commuters', + }, + ], + title='Attestation Subject', + ), + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.rights_attestation_evaluation import ( - ActionBinding, - Evaluation, - Issuer, - Issuer5, - Issuer6, - Reference, - RightsAttestationEvaluation, - Subject, - Subject10, - Subject19, -) - -# Explicit exports -__all__ = [ - "ActionBinding", - "Evaluation", - "Issuer", - "Issuer5", - "Issuer6", - "Reference", - "RightsAttestationEvaluation", - "Subject", - "Subject10", - "Subject19", -] +class RightsAttestationEvaluation(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + rights_id: Annotated[ + str, + Field( + description='Rights grant identifier. MUST equal reference.subject.id and evaluation.action_binding.action_id.', + min_length=1, + ), + ] + content_digest: Annotated[ + str, + Field( + description='Digest of the exact rights constraint evaluated. MUST equal reference.subject.content_digest and evaluation.action_binding.action_digest.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] + reference: Reference + evaluation: Evaluation + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/rights_constraint.py b/src/adcp/types/domains/core/rights_constraint.py index 59354a82a..d5838dd94 100644 --- a/src/adcp/types/domains/core/rights_constraint.py +++ b/src/adcp/types/domains/core/rights_constraint.py @@ -1,50 +1,318 @@ -"""Types declared by the AdCP ``core/rights_constraint`` schema. +# generated by datamodel-codegen: +# filename: core/rights_constraint.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.rights_constraint import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from adcp.types._scalar import ScalarStr +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictBool + +from ..enums import right_type as right_type_1 +from ..enums import right_use +from . import brand_ref +from . import ext as ext_1 +from .attestation_reference import AttestationReference + + +class RightsAgent(AdCPBaseModel): + url: Annotated[AnyUrl, Field(description='MCP endpoint URL of the rights agent')] + id: Annotated[str, Field(description='Agent identifier')] + + +class Country(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{2}$'} + + +class ExcludedCountry(Country): + pass + + +class ApprovalStatus(StrEnum): + pending = 'pending' + approved = 'approved' + rejected = 'rejected' + + +class GrantStatus(StrEnum): + active = 'active' + paused = 'paused' + revoked = 'revoked' + + +class Restriction(ScalarStr): + __slots__ = () + _constraints = {'min_length': 1} + + +class Disclosure(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + required: StrictBool + text: Annotated[str | None, Field(min_length=1)] = None + + +class Subject(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Literal['agent'] = 'agent' + resource_type: Literal['https://adcontextprotocol.org/claims/subjects/rights-grant'] = 'https://adcontextprotocol.org/claims/subjects/rights-grant' + agent_url: Annotated[ + AnyUrl, Field(description='Canonical HTTPS endpoint of the agent the claim concerns.') + ] + ext: ext_1.ExtensionObject | None = None + + +class Subject29(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Literal['resource'] = 'resource' + resource_type: Annotated[ + Literal['https://adcontextprotocol.org/claims/subjects/rights-grant'], + Field( + description='Open, absolute URI naming the subject vocabulary, such as https://adcontextprotocol.org/claims/subjects/signal. AdCP does not maintain an exhaustive enum.' + ), + ] = 'https://adcontextprotocol.org/claims/subjects/rights-grant' + namespace: Annotated[ + AnyUrl, + Field( + description='Absolute URI identifying the namespace in which id is unique. This may be an AdCP agent endpoint, a catalog origin, or a domain-specific namespace URI.' + ), + ] + id: Annotated[ + str, + Field( + description='Stable identifier for the subject within namespace. It MUST NOT be compared without resource_type and namespace.', + max_length=1024, + min_length=1, + ), + ] + content_digest: Annotated[ + str, + Field( + description='Optional SHA-256 pin for the exact content or immutable snapshot identified by this resource subject. This is part of the complete typed subject identity and is distinct from AttestationReference.content_digest, which pins credential bytes.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] + ext: ext_1.ExtensionObject | None = None + + +class Issuer(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Annotated[ + Literal['brand'], Field(description='The issuer is identified by an AdCP BrandRef.') + ] = 'brand' + brand: brand_ref.BrandReference + ext: ext_1.ExtensionObject | None = None + + +class Issuer8(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Annotated[ + Literal['agent'], + Field( + description='The issuer is an AdCP agent identified by its canonical HTTPS endpoint.' + ), + ] = 'agent' + brand: brand_ref.BrandReference + agent_url: Annotated[ + AnyUrl, + Field( + description='Canonical HTTPS endpoint of the issuing agent. Evaluators compare it using AdCP URL canonicalization rules.' + ), + ] + ext: ext_1.ExtensionObject | None = None + + +class Issuer9(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Annotated[ + Literal['origin'], + Field( + description='The issuer is identified by a canonical HTTPS origin because no AdCP brand or agent identity applies.' + ), + ] = 'origin' + brand: brand_ref.BrandReference + origin: Annotated[ + AnyUrl, + Field( + description='Canonical HTTPS origin with no path, query, fragment, or userinfo. This identifies the issuer; it does not authorize a fetch.' + ), + ] + ext: ext_1.ExtensionObject | None = None + + +class Subject20(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Literal['brand'] = 'brand' + resource_type: Literal['https://adcontextprotocol.org/claims/subjects/rights-grant'] = 'https://adcontextprotocol.org/claims/subjects/rights-grant' + brand: brand_ref.BrandReference + ext: ext_1.ExtensionObject | None = None + + +class AttestationRef(AttestationReference): + issuer: Annotated[ + Issuer | Issuer8 | Issuer9 | None, + Field( + description='Canonical identity of the party that issued an attestation credential. The discriminator selects an existing AdCP identity when one exists and falls back to an HTTPS origin for other attestors. This identity is a claim carried by the presentation; evaluators MUST match it against their configured trust policy and verify it from the resolved or embedded credential before relying on it.', + discriminator='type', + examples=[ + { + 'type': 'brand', + 'brand': {'domain': 'nova-brands.example', 'brand_id': 'nova_motors'}, + }, + {'type': 'agent', 'agent_url': 'https://attestor.example/adcp'}, + {'type': 'origin', 'origin': 'https://credentials.example'}, + ], + title='Attestation Issuer', + ), + ] = None + claim_type: Annotated[ + Literal['https://adcontextprotocol.org/claims/rights/grant'], + Field( + description='Open, absolute URI identifying the claim vocabulary. The URI is an identifier and need not be dereferenceable. AdCP does not maintain an enum of approved claims.' + ), + ] = 'https://adcontextprotocol.org/claims/rights/grant' + subject: Annotated[ + Subject20 | Subject | Subject29 | None, + Field( + description='Typed identity of the entity or object an attestation credential is about. Brand and agent subjects reuse canonical AdCP identities. Other resources use an open, URI-namespaced resource_type plus an identifier whose namespace is explicit. Evaluators MUST compare the resolved credential subject to this complete typed identity, not to id alone.', + discriminator='type', + examples=[ + { + 'type': 'brand', + 'brand': {'domain': 'nova-brands.example', 'brand_id': 'nova_motors'}, + }, + { + 'type': 'resource', + 'resource_type': 'https://adcontextprotocol.org/claims/subjects/signal', + 'namespace': 'https://signals.meridian.example/adcp', + 'id': 'signal_urban_commuters', + }, + ], + title='Attestation Subject', + ), + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.rights_constraint import ( - ApprovalStatus, - AttestationRef, - Country, - Disclosure, - ExcludedCountry, - GrantStatus, - Issuer, - Issuer8, - Issuer9, - Restriction, - RightsAgent, - RightsConstraint, - Subject, - Subject20, - Subject29, -) - -# Explicit exports -__all__ = [ - "ApprovalStatus", - "AttestationRef", - "Country", - "Disclosure", - "ExcludedCountry", - "GrantStatus", - "Issuer", - "Issuer8", - "Issuer9", - "Restriction", - "RightsAgent", - "RightsConstraint", - "Subject", - "Subject20", - "Subject29", -] +class RightsConstraint(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + rights_id: Annotated[ + str, Field(description='Rights grant identifier from the acquire_rights response') + ] + rights_agent: Annotated[RightsAgent, Field(description='The agent that granted these rights')] + rights_holder: Annotated[ + brand_ref.BrandReference | None, + Field( + description='Canonical BrandRef for the rights holder that issued the grant through an authorized rights agent. Required whenever attestation_refs is present. Evaluators first match this exact BrandRef against local issuer policy, then resolve the authoritative brand.json and bind the credential verification key to exactly one matching agents[] entry with type rights. Sibling-brand agents are never authority. Legacy unattested constraints may omit this field but remain machine-unverified.' + ), + ] = None + valid_from: Annotated[ + AwareDatetime | None, Field(description='Start of the rights validity period') + ] = None + valid_until: Annotated[ + AwareDatetime | None, + Field( + description='End of the rights validity period. Creative should not be served after this time.' + ), + ] = None + uses: Annotated[ + list[right_use.RightUse], + Field(description='Rights uses covered by this constraint', min_length=1), + ] + countries: Annotated[ + list[Country] | None, + Field( + description='Countries where this creative may be served under these rights (ISO 3166-1 alpha-2). If omitted, no country restriction. When both countries and excluded_countries are present, the effective set is countries minus excluded_countries.' + ), + ] = None + excluded_countries: Annotated[ + list[ExcludedCountry] | None, + Field( + description='Countries excluded from rights availability (ISO 3166-1 alpha-2). Use when the grant is worldwide except specific markets.' + ), + ] = None + impression_cap: Annotated[ + SchemaInt | None, + Field( + description='Maximum total impressions allowed for the full validity period (valid_from to valid_until). This is the absolute cap across all creatives using this rights grant, not a per-creative or per-period limit.', + ge=1, + ), + ] = None + right_type: Annotated[ + right_type_1.RightType | None, + Field( + description='Type of rights (talent, music, etc.). Helps identify constraints when a creative combines multiple rights types.' + ), + ] = None + approval_status: Annotated[ + ApprovalStatus | None, + Field( + description='Approval status from the rights holder at manifest creation time (snapshot, not a live value)' + ), + ] = None + grant_status: Annotated[ + GrantStatus | None, + Field( + description='Authoritative lifecycle state bound into the grant digest and credential status evidence. Only active can evaluate to verified. paused and revoked credentials are ineligible for serving; resumption issues a fresh active credential and reference.' + ), + ] = None + restrictions: Annotated[ + list[Restriction] | None, + Field( + description='Normalized enforceable content and usage restrictions issued with the grant. When present these values are part of content_digest and cannot be omitted or changed without invalidating the attestation.', + min_length=1, + ), + ] = None + disclosure: Annotated[ + Disclosure | None, + Field( + description='Disclosure obligation issued with the grant and bound by content_digest.' + ), + ] = None + creative_approval_required: Annotated[ + StrictBool | None, + Field( + description='Whether each creative produced under this grant requires holder approval before distribution. This enforceable term is bound by content_digest.' + ), + ] = None + verification_url: Annotated[ + AnyUrl | None, + Field( + deprecated=True, + description='DEPRECATED legacy informational locator. It is not proof of grant status, a revocation source, a credential locator, or permission to make a network request. Receivers MUST NOT fetch it during rights evaluation, and HTTP status codes have no AdCP authorization meaning. Rights authorization uses attestation_refs and a verifier-of-record evaluation under evaluator-owned policy.', + ), + ] = None + content_digest: Annotated[ + str | None, + Field( + description='SHA-256 of UTF8(JCS(rights_constraint minus content_digest, attestation_refs, and verification_url)), formatted as sha256:. This binds all serving-relevant grant fields, including future extension fields, while deliberately excluding the legacy non-authoritative URL.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + attestation_refs: Annotated[ + list[AttestationRef] | None, + Field( + description='Alternative portable presentations of a holder-issued rights-grant credential signed by its authorized rights agent. The buyer carries references only; the serving party remains verifier-of-record. At least one reference must evaluate to verified and match rights_holder, rights_id, rights_agent, content_digest, validity, revocation state, and local policy before this constraint can contribute to serving eligibility.', + max_length=4, + min_length=1, + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/seller_agent_ref.py b/src/adcp/types/domains/core/seller_agent_ref.py index c539bdf35..a7cf517a5 100644 --- a/src/adcp/types/domains/core/seller_agent_ref.py +++ b/src/adcp/types/domains/core/seller_agent_ref.py @@ -1,18 +1,30 @@ -"""Types declared by the AdCP ``core/seller_agent_ref`` schema. +# generated by datamodel-codegen: +# filename: core/seller_agent_ref.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.seller_agent_ref import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field -from adcp.types.generated_poc.core.seller_agent_ref import SellerAgentReference -# Explicit exports -__all__ = ["SellerAgentReference"] +class SellerAgentReference(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + agent_url: Annotated[ + AnyUrl, + Field( + description="The seller agent's API endpoint URL as declared in the property publisher's adagents.json `authorized_agents[].url`. MUST use the `https://` scheme. Receivers compare this URL against the `authorized_agents` list using the AdCP URL canonicalization rules — not byte-equality — and reject mismatches with `seller_not_authorized`. See docs/reference/url-canonicalization." + ), + ] + id: Annotated[ + str | None, + Field( + description='Reserved for a future registry-assigned stable seller identifier. Not used today — senders MUST NOT populate this field until a registry is defined. When a future release populates both `agent_url` and `id`, `agent_url` remains authoritative and `id` is advisory.', + min_length=1, + pattern='^[a-zA-Z0-9_-]+$', + ), + ] = None diff --git a/src/adcp/types/domains/core/signal_coverage_forecast.py b/src/adcp/types/domains/core/signal_coverage_forecast.py index a559d7335..41a01b7c3 100644 --- a/src/adcp/types/domains/core/signal_coverage_forecast.py +++ b/src/adcp/types/domains/core/signal_coverage_forecast.py @@ -1,38 +1,182 @@ -"""Types declared by the AdCP ``core/signal_coverage_forecast`` schema. +# generated by datamodel-codegen: +# filename: core/signal_coverage_forecast.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.signal_coverage_forecast import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Dict, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat + +from ..enums import forecast_method +from . import date_range as date_range_1 +from . import ext as ext_1 +from . import forecast_point, forecast_point_dimensions, forecast_range + + +class CoverageRate(forecast_point.CoverageRate): + low: Annotated[ + StrictFloat | None, + Field(description='Conservative (low-end) forecast value', ge=0.0, le=1.0), + ] = None + mid: Annotated[ + StrictFloat | None, + Field(description='Expected (most likely) forecast value', ge=0.0, le=1.0), + ] = None + high: Annotated[ + StrictFloat | None, + Field(description='Optimistic (high-end) forecast value', ge=0.0, le=1.0), + ] = None + + +class Metrics(forecast_point.Metrics): + __pydantic_extra__: Dict[str, forecast_range.ForecastRange] + model_config = ConfigDict( + extra='allow', + ) + audience_size: forecast_range.ForecastRange | None = None + reach: forecast_range.ForecastRange | None = None + frequency: forecast_range.ForecastRange | None = None + impressions: forecast_range.ForecastRange | None = None + clicks: forecast_range.ForecastRange | None = None + spend: forecast_range.ForecastRange | None = None + views: forecast_range.ForecastRange | None = None + completed_views: forecast_range.ForecastRange | None = None + grps: forecast_range.ForecastRange | None = None + engagements: forecast_range.ForecastRange | None = None + follows: forecast_range.ForecastRange | None = None + saves: forecast_range.ForecastRange | None = None + profile_visits: forecast_range.ForecastRange | None = None + measured_impressions: forecast_range.ForecastRange | None = None + downloads: forecast_range.ForecastRange | None = None + plays: forecast_range.ForecastRange | None = None + coverage_rate: Annotated[ + CoverageRate, + Field( + description="Share of the declared forecast scope represented by this point. For signal coverage forecasts, this is the point's count divided by the coverage_forecast.scope denominator. Range 0.0 to 1.0." + ), + ] + + + + +class Kind(StrEnum): + inventory = 'inventory' + product = 'product' + account = 'account' + custom = 'custom' + + +class Country(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{2}$'} + + +class Scope(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + kind: Annotated[Kind, Field(description='Denominator family for the coverage forecast.')] + label: Annotated[ + str, + Field( + description="Human-readable denominator label, such as 'network price-priority inventory'." + ), + ] + product_id: Annotated[ + str | None, Field(description="Product denominator when kind is 'product'.") + ] = None + countries: Annotated[ + list[Country] | None, + Field( + description='Countries included in the denominator, as ISO 3166-1 alpha-2 codes.', + min_length=1, + ), + ] = None + line_item_types: Annotated[ + list[str] | None, + Field( + description='Seller or ad-server line item types included in the denominator.', + min_length=1, + ), + ] = None + date_range: Annotated[ + date_range_1.DateRange | None, + Field(description='Historical or planned date window used to compute the denominator.'), + ] = None + + +class BucketSemantics(StrEnum): + exclusive = 'exclusive' + overlapping = 'overlapping' + + +class BucketCompleteness(StrEnum): + complete = 'complete' + partial = 'partial' + + +class Point(forecast_point.ForecastPoint): + dimensions: Annotated[ + forecast_point_dimensions.ForecastPointDimensions, + Field( + description='Dimension constraints represented by this forecast point, such as country, region, placement, device type, platform, audience, signal value, time window, or intersections such as placement x country or product x signal. Each item declares one dimension family; when multiple items are present, the point represents their intersection. Sellers MUST NOT emit more than one item for each `kind` on a point; consumers MUST NOT treat repeated kinds as OR semantics. Use multiple points with dimensions to expose country/placement/signal availability within one product, proposal, or signal coverage forecast without creating separate products solely for each dimension. Dimensions describe the forecast row and are independent of pricing_options.' + ), + ] + metrics: Annotated[ + Metrics, + Field( + description='Forecasted metric values. Keys are forecastable-metric enum values for delivery/engagement or event-type enum values for outcomes. Values are ForecastRange objects (low/mid/high). Use { "mid": value } for point estimates. When budget is present, these are the expected metrics at that spend level. When budget is omitted, these represent total available inventory — use spend to express the estimated cost. Additional keys beyond the documented properties are allowed for event-type values (purchase, lead, app_install, etc.).' + ), + ] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.signal_coverage_forecast import ( - BucketCompleteness, - BucketSemantics, - Country, - CoverageRate, - Kind, - Metrics, - Point, - Scope, - SignalCoverageForecast, -) - -# Explicit exports -__all__ = [ - "BucketCompleteness", - "BucketSemantics", - "Country", - "CoverageRate", - "Kind", - "Metrics", - "Point", - "Scope", - "SignalCoverageForecast", -] +class SignalCoverageForecast(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + points: Annotated[ + list[Point], + Field( + description='Coverage or availability points. Each point reuses the standard ForecastPoint shape, MUST include a signal dimension, and MUST include metrics.coverage_rate. Use metrics.impressions for count denominators and metrics.coverage_rate for the fraction of the declared scope represented by the point.', + min_length=1, + ), + ] + forecast_range_unit: Annotated[ + Literal['availability'], + Field( + description="How to interpret the points array. Signal coverage forecasts always use 'availability' because the points describe available inventory or population coverage, not spend curves or temporal pacing." + ), + ] = 'availability' + method: Annotated[ + forecast_method.ForecastMethod, + Field(description='Method used to produce this coverage forecast.'), + ] + scope: Annotated[ + Scope, + Field( + description='Explicit denominator for the coverage forecast. This identifies the inventory, product, account, or custom universe that coverage_rate values are relative to. Additional seller-specific qualifiers are allowed for scopes such as line item type, ad server, inventory class, country, or flight window.' + ), + ] + bucket_semantics: Annotated[ + BucketSemantics, + Field( + description="'exclusive' means the returned signal-value buckets do not overlap with each other. 'overlapping' means one impression or user can appear in multiple returned buckets, so coverage_rate values may sum above 1.0. This field describes overlap among returned buckets; bucket_completeness declares whether the returned buckets cover the full denominator." + ), + ] + bucket_completeness: Annotated[ + BucketCompleteness, + Field( + description="'complete' means the returned buckets cover the declared denominator. For complete + exclusive forecasts, count metrics and coverage_rate values can be treated as a full partition, subject to metric additivity rules. 'partial' means omitted denominator share represents undisclosed, other, or unsupported buckets; buyers MUST NOT infer totals by summing returned points." + ), + ] + generated_at: Annotated[ + AwareDatetime | None, Field(description='When this coverage forecast was computed.') + ] = None + valid_until: Annotated[ + AwareDatetime | None, Field(description='When this coverage forecast expires.') + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/signal_definition.py b/src/adcp/types/domains/core/signal_definition.py index e2c4aa227..1aedabd51 100644 --- a/src/adcp/types/domains/core/signal_definition.py +++ b/src/adcp/types/domains/core/signal_definition.py @@ -1,72 +1,545 @@ -"""Types declared by the AdCP ``core/signal_definition`` schema. +# generated by datamodel-codegen: +# filename: core/signal_definition.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.signal_definition import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from adcp.types._scalar import ScalarStr +from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, StrictBool, StrictFloat + +from ..enums import audience_resolution_method, audience_subject_type +from ..enums import consent_basis as consent_basis_1 +from ..enums import restricted_attribute, signal_value_type +from . import demographic_predicate as demographic_predicate_1 +from . import signal_modeling_disclosure + + +class Tag(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[a-z0-9_-]+$'} + + +class Range(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + min: Annotated[StrictFloat, Field(description='Minimum value')] + max: Annotated[StrictFloat, Field(description='Maximum value')] + unit: Annotated[ + str | None, Field(description="Unit of measurement (e.g., 'score', 'dollars', 'years')") + ] = None + + +class Value(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + id: Annotated[str, Field(description='Taxonomy node identifier.', min_length=1)] + path: Annotated[ + str | None, + Field( + description='Optional human-readable or taxonomy-native path for display and review.' + ), + ] = None + modifiers: Annotated[ + list[str] | None, + Field(description='Optional taxonomy-specific modifiers that qualify the node.'), + ] = None + + +class ValueMapping(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + value: Annotated[str, Field(description='Categorical value from allowed_values[].')] + taxonomy_value_id: Annotated[ + str, Field(description='Taxonomy node identifier corresponding to this categorical value.') + ] + path: Annotated[ + str | None, + Field( + description='Optional human-readable or taxonomy-native path for display and review.' + ), + ] = None + modifiers: Annotated[ + list[str] | None, + Field(description='Optional taxonomy-specific modifiers that qualify the mapped value.'), + ] = None + + +class ParentMatchBehavior(StrEnum): + exact_only = 'exact_only' + descendants_supported = 'descendants_supported' + unknown = 'unknown' + + +class Taxonomy(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + ref: Annotated[ + AnyUrl, Field(description='URI identifying the taxonomy or taxonomy documentation.') + ] + version: Annotated[ + str | None, + Field( + description='Version identifier for the taxonomy when the taxonomy has versioned definitions.' + ), + ] = None + segtax: Annotated[ + SchemaInt | None, + Field( + description='OpenRTB segtax code when the taxonomy maps to an OpenRTB segment taxonomy.', + ge=1, + ), + ] = None + etag: Annotated[ + str | None, + Field( + description='Optional validator for custom taxonomy definitions so consumers can detect drift between signal publication and taxonomy resolution.' + ), + ] = None + values: Annotated[ + list[Value], + Field( + description='Taxonomy node values that describe this signal. These are meaning/discovery metadata for the signal definition, not the values a buyer submits in package targeting expressions.', + min_length=1, + ), + ] + value_mappings: Annotated[ + list[ValueMapping] | None, + Field( + description="For categorical signals, maps package-targeting allowed_values[] strings to stable taxonomy node identifiers. The allowed_values[] strings remain the wire values buyers submit; this mapping explains how those strings resolve into the published taxonomy. Each value_mappings[].value SHOULD match one of the signal's allowed_values[] entries; JSON Schema draft-07 cannot enforce this cross-array constraint.", + min_length=1, + ), + ] = None + parent_match_behavior: Annotated[ + ParentMatchBehavior | None, + Field( + description="Whether this signal definition supports treating a parent taxonomy node as matching descendant nodes for discovery/filtering or seller-side expansion. 'exact_only' means only explicitly listed node ids match. 'descendants_supported' means the seller can expand known, version-pinned parent nodes to descendants internally, typically by ORing the children in its execution system. 'unknown' means the provider has not declared parent-node behavior. This field is metadata about discovery/translation behavior, not a package targeting operator." + ), + ] = None + + +class DataSource(StrEnum): + app_behavior = 'app_behavior' + app_usage = 'app_usage' + web_usage = 'web_usage' + geo_location = 'geo_location' + email = 'email' + tv_ott_or_stb_device = 'tv_ott_or_stb_device' + panel = 'panel' + online_ecommerce = 'online_ecommerce' + credit_data = 'credit_data' + loyalty_card = 'loyalty_card' + transaction = 'transaction' + online_survey = 'online_survey' + offline_survey = 'offline_survey' + public_record_census = 'public_record_census' + public_record_voter_file = 'public_record_voter_file' + public_record_other = 'public_record_other' + offline_transaction = 'offline_transaction' + + +class Methodology(StrEnum): + observed = 'observed' + declared = 'declared' + derived = 'derived' + inferred = 'inferred' + modeled = 'modeled' + + +class RefreshCadence(StrEnum): + intra_day = 'intra_day' + daily = 'daily' + weekly = 'weekly' + monthly = 'monthly' + bi_monthly = 'bi_monthly' + quarterly = 'quarterly' + bi_annually = 'bi_annually' + annually = 'annually' + + +class MatchKey(StrEnum): + name = 'name' + address = 'address' + email = 'email' + postal = 'postal' + lat_long = 'lat_long' + mobile_id = 'mobile_id' + cookie_id = 'cookie_id' + ip = 'ip' + customer_id = 'customer_id' + phone = 'phone' + + +class PreOnboardingPrecisionLevel(StrEnum): + individual = 'individual' + household = 'household' + business = 'business' + geography = 'geography' + + +class Onboarder(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + match_keys: Annotated[list[MatchKey], Field(min_length=1)] + pre_onboarding_audience_expansion: StrictBool | None = None + pre_onboarding_device_expansion: StrictBool | None = None + pre_onboarding_precision_level: PreOnboardingPrecisionLevel | None = None + + +class IdType(StrEnum): + cookie = 'cookie' + mobile_id = 'mobile_id' + platform_id = 'platform_id' + user_enabled_id = 'user_enabled_id' + + +class AudienceScope(StrEnum): + single_domain = 'single_domain' + cross_domain_owned = 'cross_domain_owned' + cross_domain_unowned = 'cross_domain_unowned' + offline = 'offline' + + +class Country(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{2}$'} + + +class Art9Basis(StrEnum): + explicit_consent = 'explicit_consent' + manifestly_made_public = 'manifestly_made_public' + substantial_public_interest = 'substantial_public_interest' + vital_interests = 'vital_interests' + + +class Method(StrEnum): + lookalike = 'lookalike' + supervised = 'supervised' + embedding = 'embedding' + rules = 'rules' + + +class Type(StrEnum): + first_party_crm = 'first_party_crm' + panel = 'panel' + declared_survey = 'declared_survey' + transactional = 'transactional' + behavioral = 'behavioral' + + +class SeedSource(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Type + provider_signed: Annotated[ + StrictBool, + Field( + description="Whether the seed source carries a signed attestation under one of the provider's published signing keys. This is a forward-looking claim until the consumer can resolve and verify the provider's applicable signing keys through the AdCP signing profile; consumers MUST NOT treat this boolean alone as cryptographic proof." + ), + ] + + +class TrainingDataJurisdiction(Country): + pass + + +class AiActRiskClass(StrEnum): + minimal = 'minimal' + limited = 'limited' + high_risk = 'high_risk' + + +class Right(StrEnum): + access = 'access' + rectification = 'rectification' + erasure = 'erasure' + portability = 'portability' + objection = 'objection' + + +class Channel(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + rights: Annotated[ + list[Right], Field(description='Rights supported by this channel.', min_length=1) + ] + url: Annotated[ + AnyUrl | None, Field(description='HTTPS URL for submitting the rights request.') + ] = None + email: Annotated[ + EmailStr | None, Field(description='Email address for submitting the rights request.') + ] = None + languages: Annotated[ + list[str] | None, Field(description='BCP 47 language tags supported by this channel.') + ] = None + countries: Annotated[ + list[Country] | None, + Field( + description='ISO 3166-1 alpha-2 countries this channel serves, when the provider routes rights by country.' + ), + ] = None + + +class DataSubjectRights(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + upstream_source_domain: Annotated[ + str | None, + Field( + description='Domain of the upstream data source whose rights process these channels reach, when different from the publishing domain.', + max_length=253, + pattern='^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]{0,61}[A-Za-z0-9])$', + ), + ] = None + channels: Annotated[ + list[Channel], + Field( + description='Rights request channels and the rights each channel supports. At least one declared channel MUST support one or more of access, erasure, or objection; schema validators enforce this with draft-07 contains, and consumers whose SDK drops contains need an equivalent runtime check.', + min_length=1, + ), + ] + response_sla_days: Annotated[ + SchemaInt | None, + Field( + description="Maximum response time in days for rights requests handled through the declared rights channels. For provider-published signals, providers SHOULD avoid duplicating this field across every signal unless the value varies by signal or upstream source; consumers MAY also consult the provider's public privacy policy or registry disclosures when present.", + ge=1, + le=90, + ), + ] = None + ccpa_opt_out_url: Annotated[ + AnyUrl | None, + Field(description="US-specific 'Do Not Sell or Share' opt-out URL where required."), + ] = None + + +class Modeling(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + method: Method + seed_source: SeedSource + training_data_jurisdictions: Annotated[ + list[TrainingDataJurisdiction], + Field( + description="ISO 3166-1 alpha-2 country codes where the model's training data was collected.", + min_length=1, + ), + ] + ai_act_risk_class: Annotated[ + AiActRiskClass, + Field( + description='EU AI Act risk classification self-declared by the provider. Prohibited-risk modeled signals must not be published as AdCP signal definitions.' + ), + ] + disclosure: Annotated[ + signal_modeling_disclosure.SignalModelingDisclosure | None, + Field( + description='Signal/modeling-specific disclosure requirements and jurisdictional notes. This is not creative provenance render guidance.' + ), + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.signal_definition import ( - AiActRiskClass, - Art9Basis, - AudienceScope, - Channel, - Country, - DataSource, - DataSubjectRights, - IdType, - MatchKey, - Method, - Methodology, - Modeling, - Onboarder, - ParentMatchBehavior, - PreOnboardingPrecisionLevel, - Range, - RefreshCadence, - Right, - SeedSource, - SignalDefinition, - Tag, - Taxonomy, - TrainingDataJurisdiction, - Type, - Value, - ValueMapping, -) - -# Explicit exports -__all__ = [ - "AiActRiskClass", - "Art9Basis", - "AudienceScope", - "Channel", - "Country", - "DataSource", - "DataSubjectRights", - "IdType", - "MatchKey", - "Method", - "Methodology", - "Modeling", - "Onboarder", - "ParentMatchBehavior", - "PreOnboardingPrecisionLevel", - "Range", - "RefreshCadence", - "Right", - "SeedSource", - "SignalDefinition", - "Tag", - "Taxonomy", - "TrainingDataJurisdiction", - "Type", - "Value", - "ValueMapping", -] +class SignalDefinition(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + id: Annotated[ + str, + Field( + description="Signal identifier within the publishing domain's adagents.json signals[]", + pattern='^[a-zA-Z0-9_-]+$', + ), + ] + name: Annotated[ + str, Field(description='Human-readable signal name', max_length=255, min_length=1) + ] + description: Annotated[ + str | None, + Field( + description="Detailed description of what this signal represents and how it's derived", + max_length=2000, + ), + ] = None + value_type: Annotated[ + signal_value_type.SignalValueType, + Field(description="The data type of this signal's values"), + ] + tags: Annotated[ + list[Tag] | None, + Field( + description="Tags for grouping and filtering this domain's published signal definitions" + ), + ] = None + allowed_values: Annotated[ + list[str] | None, + Field( + description='For categorical signals, the valid values users can be assigned', + min_length=1, + ), + ] = None + restricted_attributes: Annotated[ + list[restricted_attribute.RestrictedAttribute] | None, + Field( + description="Restricted attribute categories this signal touches. Data providers SHOULD declare these so governance agents can structurally match signals against a plan's restricted_attributes without relying on semantic inference from the signal name or description.", + min_length=1, + ), + ] = None + demographic_predicate: Annotated[ + demographic_predicate_1.DemographicPredicate | None, + Field( + description='Authoritative machine-readable demographic meaning for this signal. Signal names and taxonomy labels alone never establish exact demographic equivalence. Signals carrying this field MUST also declare restricted_attributes including age.' + ), + ] = None + policy_categories: Annotated[ + list[str] | None, + Field( + description="Policy categories this signal is sensitive for (e.g., a children's interest signal declares ['children_directed']). Governance agents match these against a plan's policy_categories to flag sensitive data usage.", + min_length=1, + ), + ] = None + range: Annotated[ + Range | None, Field(description='For numeric signals, the valid value range') + ] = None + taxonomy: Annotated[ + Taxonomy | None, + Field( + description='Optional taxonomy metadata describing what this signal means in an external audience, content, retail-media, or provider-owned taxonomy. Taxonomy metadata does not create a new value_type and does not change package targeting grammar: buyers still target the named signal according to value_type. When a taxonomy value is a parent node, parent/descendant expansion is seller behavior and must be declared through parent_match_behavior rather than assumed.' + ), + ] = None + segmentation_criteria: Annotated[ + str | None, + Field( + description='Rules governing inclusion of identifiers in the segment. Aligns with IAB Data Transparency Standard audience criteria disclosure.', + max_length=500, + ), + ] = None + criteria_url: Annotated[ + AnyUrl | None, + Field( + description='Optional URL to a longer-form methodology or criteria document. This is a disclosure pointer; buyers should not branch programmatically on the linked content.' + ), + ] = None + data_sources: Annotated[ + list[DataSource] | None, + Field( + description="Origin categories of the raw data used to compile the signal, aligned with IAB Data Transparency Standard source disclosure. Use 'panel' for respondent-panel or JIC-style audience sources. Offline and public-record sources require onboarder disclosure. Co-viewing projection and reconciliation of seller claims against a JIC or measurement vendor belong in measurement reporting/vendor metrics rather than package signal targeting.", + min_length=1, + ), + ] = None + methodology: Annotated[ + Methodology | None, + Field( + description="How the signal's audience membership or attribute was determined. 'modeled' requires the modeling block." + ), + ] = None + audience_expansion: Annotated[ + StrictBool | None, + Field( + description='Whether look-alike or similar-audience expansion was used to include additional identifiers. When true, modeling is required.' + ), + ] = None + device_expansion: Annotated[ + StrictBool | None, + Field( + description="Whether the signal was expanded deterministically across devices of the same user, household, or business. Probabilistic cross-device expansion is modeling and should use methodology 'modeled' or the modeling block." + ), + ] = None + refresh_cadence: Annotated[ + RefreshCadence | None, + Field( + description="Cadence at which the signal definition's underlying segment membership is refreshed." + ), + ] = None + lookback_window: Annotated[ + RefreshCadence | None, + Field(description='Time window in which a qualifying event can occur for inclusion.'), + ] = None + onboarder: Annotated[ + Onboarder | None, + Field( + description='Onboarder disclosure. Required when data_sources includes an offline_* or public_record_* source.' + ), + ] = None + subject_type: Annotated[ + audience_subject_type.AudienceSubjectType | None, + Field(description='What kind of subject this signal characterizes.'), + ] = None + resolution_method: Annotated[ + audience_resolution_method.AudienceResolutionMethod | None, + Field(description='How the subject is resolved at decision time.'), + ] = None + id_types: Annotated[ + list[IdType] | None, + Field( + description='Identifier currencies analyzed to determine audience membership or attributes.', + min_length=1, + ), + ] = None + audience_scope: Annotated[ + AudienceScope | None, + Field( + description="Context within which the audience attribute was determined. 'single_domain' requires originating_domain." + ), + ] = None + originating_domain: Annotated[ + str | None, + Field( + description="Domain of the digital property where the audience originates. Required when audience_scope is 'single_domain'.", + pattern='^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]{0,61}[A-Za-z0-9])$', + ), + ] = None + countries: Annotated[ + list[Country] | None, + Field( + description="ISO 3166-1 alpha-2 country codes where the signal is applicable. Sellers must not expose a signal for media buys in countries outside this list. Federating agents that surface a peer's signal MUST treat the peer-published list as an upper bound, re-check it against the buyer's intended deployment countries, and may apply only narrower local policy.", + min_length=1, + ), + ] = None + consent_basis: Annotated[ + list[consent_basis_1.ConsentBasis] | None, + Field( + description="Declared GDPR Article 6 lawful basis or consent basis under which this signal's data is processed. For non-GDPR regimes, use countries, policy_categories, and disclosure fields to describe jurisdiction-specific obligations unless a future enum value applies.", + min_length=1, + ), + ] = None + art9_basis: Annotated[ + Art9Basis | None, + Field( + description='GDPR Article 9 basis when restricted_attributes is non-empty and the signal is used in jurisdictions where Article 9 applies. Required by policy for applicable use cases rather than universally required at schema level because sensitivity and lawful basis are jurisdiction-relative.' + ), + ] = None + modeling: Annotated[ + Modeling | None, + Field( + description="Modeling disclosure for modeled data signals. Required when methodology is 'modeled' or audience_expansion is true. This describes data modeling and intentionally does not reuse creative provenance, which is content/render oriented." + ), + ] = None + data_subject_rights: Annotated[ + DataSubjectRights | None, + Field( + description='Per-signal data-subject-rights routing. Inline on the signal because upstream source, rights routing, and response commitments can differ by segment or may be unavailable from a public provider document for custom/private signals. This is a contact/routing reference, not a machine-callable AdCP API.' + ), + ] = None + last_updated: Annotated[ + AwareDatetime | None, + Field( + description='When this definition record was last updated. This indicates freshness of the definition record, not an attestation that the underlying data or model was refreshed at that time.' + ), + ] = None + dts_compliant_version: Annotated[ + str | None, + Field( + description='IAB Data Transparency Standard version this signal definition self-attests as satisfying, when applicable.' + ), + ] = None diff --git a/src/adcp/types/domains/core/signal_definition_enrichment.py b/src/adcp/types/domains/core/signal_definition_enrichment.py index 66b9e99e3..2151a3a24 100644 --- a/src/adcp/types/domains/core/signal_definition_enrichment.py +++ b/src/adcp/types/domains/core/signal_definition_enrichment.py @@ -1,64 +1,283 @@ -"""Types declared by the AdCP ``core/signal_definition_enrichment`` schema. +# generated by datamodel-codegen: +# filename: core/signal_definition_enrichment.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.signal_definition_enrichment import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from adcp.types._scalar import ScalarStr +from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, StrictBool + +from ..enums import consent_basis as consent_basis_1 +from ..enums import restricted_attribute +from . import demographic_predicate as demographic_predicate_1 +from . import signal_modeling_disclosure + + +class Value(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + id: Annotated[str, Field(min_length=1)] + path: str | None = None + modifiers: list[str] | None = None + + +class ValueMapping(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + value: str + taxonomy_value_id: str + path: str | None = None + modifiers: list[str] | None = None + + +class ParentMatchBehavior(StrEnum): + exact_only = 'exact_only' + descendants_supported = 'descendants_supported' + unknown = 'unknown' + + +class Taxonomy(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + ref: AnyUrl + version: str | None = None + segtax: Annotated[SchemaInt | None, Field(ge=1)] = None + etag: str | None = None + values: Annotated[list[Value], Field(min_length=1)] + value_mappings: Annotated[list[ValueMapping] | None, Field(min_length=1)] = None + parent_match_behavior: ParentMatchBehavior | None = None + + +class DataSource(StrEnum): + app_behavior = 'app_behavior' + app_usage = 'app_usage' + web_usage = 'web_usage' + geo_location = 'geo_location' + email = 'email' + tv_ott_or_stb_device = 'tv_ott_or_stb_device' + panel = 'panel' + online_ecommerce = 'online_ecommerce' + credit_data = 'credit_data' + loyalty_card = 'loyalty_card' + transaction = 'transaction' + online_survey = 'online_survey' + offline_survey = 'offline_survey' + public_record_census = 'public_record_census' + public_record_voter_file = 'public_record_voter_file' + public_record_other = 'public_record_other' + offline_transaction = 'offline_transaction' + + +class Methodology(StrEnum): + observed = 'observed' + declared = 'declared' + derived = 'derived' + inferred = 'inferred' + modeled = 'modeled' + + +class RefreshCadence(StrEnum): + intra_day = 'intra_day' + daily = 'daily' + weekly = 'weekly' + monthly = 'monthly' + bi_monthly = 'bi_monthly' + quarterly = 'quarterly' + bi_annually = 'bi_annually' + annually = 'annually' + + +class MatchKey(StrEnum): + name = 'name' + address = 'address' + email = 'email' + postal = 'postal' + lat_long = 'lat_long' + mobile_id = 'mobile_id' + cookie_id = 'cookie_id' + ip = 'ip' + customer_id = 'customer_id' + phone = 'phone' + + +class PreOnboardingPrecisionLevel(StrEnum): + individual = 'individual' + household = 'household' + business = 'business' + geography = 'geography' + + +class Onboarder(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + match_keys: Annotated[list[MatchKey], Field(min_length=1)] + pre_onboarding_audience_expansion: StrictBool | None = None + pre_onboarding_device_expansion: StrictBool | None = None + pre_onboarding_precision_level: PreOnboardingPrecisionLevel | None = None + + +class Country(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{2}$'} + + +class Art9Basis(StrEnum): + explicit_consent = 'explicit_consent' + manifestly_made_public = 'manifestly_made_public' + substantial_public_interest = 'substantial_public_interest' + vital_interests = 'vital_interests' + + +class Method(StrEnum): + lookalike = 'lookalike' + supervised = 'supervised' + embedding = 'embedding' + rules = 'rules' + + +class Type(StrEnum): + first_party_crm = 'first_party_crm' + panel = 'panel' + declared_survey = 'declared_survey' + transactional = 'transactional' + behavioral = 'behavioral' + + +class SeedSource(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Type + provider_signed: Annotated[ + StrictBool, + Field( + description='Provider assertion that the seed source carries a signed attestation. Consumers MUST NOT treat this boolean alone as cryptographic proof.' + ), + ] + + +class TrainingDataJurisdiction(Country): + pass + + +class AiActRiskClass(StrEnum): + minimal = 'minimal' + limited = 'limited' + high_risk = 'high_risk' + + +class Right(StrEnum): + access = 'access' + rectification = 'rectification' + erasure = 'erasure' + portability = 'portability' + objection = 'objection' + + +class Channel(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + rights: Annotated[list[Right], Field(min_length=1)] + url: AnyUrl | None = None + email: EmailStr | None = None + languages: list[str] | None = None + countries: list[Country] | None = None + + +class DataSubjectRights(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + upstream_source_domain: Annotated[ + str | None, + Field( + max_length=253, + pattern='^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]{0,61}[A-Za-z0-9])$', + ), + ] = None + channels: Annotated[list[Channel], Field(min_length=1)] + response_sla_days: Annotated[SchemaInt | None, Field(ge=1, le=90)] = None + ccpa_opt_out_url: AnyUrl | None = None + + +class Modeling(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + method: Method + seed_source: SeedSource + training_data_jurisdictions: Annotated[list[TrainingDataJurisdiction], Field(min_length=1)] + ai_act_risk_class: AiActRiskClass + disclosure: signal_modeling_disclosure.SignalModelingDisclosure | None = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.signal_definition_enrichment import ( - AiActRiskClass, - Art9Basis, - Channel, - Country, - DataSource, - DataSubjectRights, - MatchKey, - Method, - Methodology, - Modeling, - Onboarder, - ParentMatchBehavior, - PreOnboardingPrecisionLevel, - RefreshCadence, - Right, - SeedSource, - SignalDefinitionEnrichment, - Taxonomy, - TrainingDataJurisdiction, - Type, - Value, - ValueMapping, -) - -# Explicit exports -__all__ = [ - "AiActRiskClass", - "Art9Basis", - "Channel", - "Country", - "DataSource", - "DataSubjectRights", - "MatchKey", - "Method", - "Methodology", - "Modeling", - "Onboarder", - "ParentMatchBehavior", - "PreOnboardingPrecisionLevel", - "RefreshCadence", - "Right", - "SeedSource", - "SignalDefinitionEnrichment", - "Taxonomy", - "TrainingDataJurisdiction", - "Type", - "Value", - "ValueMapping", -] +class SignalDefinitionEnrichment(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + restricted_attributes: Annotated[ + list[restricted_attribute.RestrictedAttribute] | None, + Field(description='Restricted attribute categories this signal touches.', min_length=1), + ] = None + demographic_predicate: Annotated[ + demographic_predicate_1.DemographicPredicate | None, + Field( + description="Projected authoritative demographic meaning for the signal. When projected from another provider, this MUST match the provider's definition exactly. Signal names alone never establish demographic semantics." + ), + ] = None + policy_categories: Annotated[ + list[str] | None, + Field(description='Policy categories this signal is sensitive for.', min_length=1), + ] = None + taxonomy: Annotated[ + Taxonomy | None, + Field( + description='Optional taxonomy metadata describing what this signal means in an external audience, content, retail-media, or provider-owned taxonomy.' + ), + ] = None + segmentation_criteria: Annotated[str | None, Field(max_length=500)] = None + criteria_url: AnyUrl | None = None + data_sources: Annotated[list[DataSource] | None, Field(min_length=1)] = None + methodology: Methodology | None = None + audience_expansion: StrictBool | None = None + device_expansion: StrictBool | None = None + refresh_cadence: RefreshCadence | None = None + lookback_window: RefreshCadence | None = None + onboarder: Onboarder | None = None + countries: Annotated[list[Country] | None, Field(min_length=1)] = None + consent_basis: Annotated[ + list[consent_basis_1.ConsentBasis] | None, + Field( + description="Data provider's declared GDPR Article 6 lawful basis or consent basis for the underlying signal definition, projected into this get_signals response row when requested. Sellers and federating agents that pass through another provider's signal MUST NOT substitute their own processing basis for the provider-declared basis.", + min_length=1, + ), + ] = None + art9_basis: Annotated[ + Art9Basis | None, + Field( + description="Data provider's declared GDPR Article 9 basis for the underlying signal definition when special-category data is involved and Article 9 applies, projected into this get_signals response row when requested. Sellers and federating agents that pass through another provider's signal MUST NOT substitute their own Article 9 basis for the provider-declared basis." + ), + ] = None + modeling: Modeling | None = None + data_subject_rights: Annotated[ + DataSubjectRights | None, + Field( + description='Per-signal data-subject-rights routing. This is a contact/routing reference, not a machine-callable AdCP API.' + ), + ] = None + last_updated: Annotated[ + AwareDatetime | None, + Field( + description='When this definition record was last updated. This indicates freshness of the definition record, not an attestation that the underlying data or model was refreshed at that time.' + ), + ] = None + dts_compliant_version: str | None = None diff --git a/src/adcp/types/domains/core/signal_filters.py b/src/adcp/types/domains/core/signal_filters.py index 7faefcbc5..1e07697cd 100644 --- a/src/adcp/types/domains/core/signal_filters.py +++ b/src/adcp/types/domains/core/signal_filters.py @@ -1,18 +1,47 @@ -"""Types declared by the AdCP ``core/signal_filters`` schema. +# generated by datamodel-codegen: +# filename: core/signal_filters.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.signal_filters import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import signal_catalog_type +from . import ext as ext_1 -from adcp.types.generated_poc.core.signal_filters import SignalFilters -# Explicit exports -__all__ = ["SignalFilters"] +class SignalFilters(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + catalog_types: Annotated[ + list[signal_catalog_type.SignalAvailabilityType] | None, + Field(description='Filter by catalog type', min_length=1), + ] = None + data_providers: Annotated[ + list[str] | None, Field(description='Filter by specific data providers', min_length=1) + ] = None + max_cpm: Annotated[ + StrictFloat | None, + Field(description="Maximum CPM filter. Applies only to signals with model='cpm'.", ge=0.0), + ] = None + max_percent: Annotated[ + StrictFloat | None, + Field( + description='Maximum percent-of-media rate filter. Signals where all percent_of_media pricing options exceed this value are excluded. Does not account for max_cpm caps.', + ge=0.0, + le=100.0, + ), + ] = None + min_coverage_percentage: Annotated[ + StrictFloat | None, Field(description='Minimum coverage requirement', ge=0.0, le=100.0) + ] = None + ext: Annotated[ + ext_1.ExtensionObject | None, + Field( + description='Vendor-namespaced extension parameters for seller- or platform-specific signal filter criteria not covered by standard fields. Keys MUST be namespaced under a vendor or platform key (e.g., ext.gam, ext.platform_x). Sellers MUST treat all values as untrusted buyer input; avoid unbounded logging or labels, and do not interpolate values into caller-visible error strings, LLM prompts, SQL queries, or system commands without sanitization. Persistent use of an extension key across multiple buyers is a signal to propose standardization.' + ), + ] = None diff --git a/src/adcp/types/domains/core/signal_id.py b/src/adcp/types/domains/core/signal_id.py index e8e219e79..85041b060 100644 --- a/src/adcp/types/domains/core/signal_id.py +++ b/src/adcp/types/domains/core/signal_id.py @@ -1,18 +1,71 @@ -"""Types declared by the AdCP ``core/signal_id`` schema. +# generated by datamodel-codegen: +# filename: core/signal_id.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.signal_id import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.signal_id import SignalId, SignalId8, SignalId9 +class SignalId8(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + source: Annotated[ + Literal['catalog'], + Field( + description="Discriminator indicating this signal is from a data provider's published adagents.json signals[]" + ), + ] = 'catalog' + data_provider_domain: Annotated[ + str, + Field( + description="Domain of the data provider that owns this signal (e.g., 'pinnacle-data.example'). The signal definition is published at this domain's /.well-known/adagents.json", + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] + id: Annotated[ + str, + Field( + description="Signal identifier within the data provider's catalog (e.g., 'likely_ev_buyers', 'income_100k_plus')", + pattern='^[a-zA-Z0-9_-]+$', + ), + ] + + +class SignalId9(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + source: Annotated[ + Literal['agent'], + Field( + description="Discriminator indicating this signal is native to the signal source identified by agent_url, not from a data provider's published signal definitions." + ), + ] = 'agent' + agent_url: Annotated[ + AnyUrl, + Field( + description="URL of the signal source that provides this signal (e.g., 'https://signals.example/.well-known/adcp/signals')" + ), + ] + id: Annotated[ + str, + Field( + description="Signal identifier within the agent's signal set (e.g., 'custom_auto_intenders')", + pattern='^[a-zA-Z0-9_-]+$', + ), + ] + -# Explicit exports -__all__ = ["SignalId", "SignalId8", "SignalId9"] +SignalId = Annotated[ + SignalId8 | SignalId9, + Field( + description="DEPRECATED. Use SignalRef for new discovery, activation, and media-buy signal identity surfaces. Legacy universal signal identifier used by older Signals Protocol clients. Uses 'source' as discriminator: 'catalog' for signals from a data provider's published catalog (verifiable), or 'agent' for signals native to a signal source identified by agent_url.", + discriminator='source', + title='Signal ID', + ), +] diff --git a/src/adcp/types/domains/core/signal_listing.py b/src/adcp/types/domains/core/signal_listing.py index f1735fab3..30ca559d8 100644 --- a/src/adcp/types/domains/core/signal_listing.py +++ b/src/adcp/types/domains/core/signal_listing.py @@ -1,18 +1,109 @@ -"""Types declared by the AdCP ``core/signal_listing`` schema. +# generated by datamodel-codegen: +# filename: core/signal_listing.json +# timestamp: 2026-10-04T01:14:11+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.signal_listing import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictFloat, model_validator + +from ..enums import restricted_attribute, signal_value_type +from . import demographic_predicate as demographic_predicate_1 +from . import signal_id as signal_id_1 +from . import signal_ref as signal_ref_1 + + +class Range(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + min: Annotated[StrictFloat, Field(description='Minimum value, inclusive.')] + max: Annotated[StrictFloat, Field(description='Maximum value, inclusive.')] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.signal_listing import Range, SignalListing +class SignalListing(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + signal_ref: Annotated[ + signal_ref_1.SignalRef | None, + Field( + description="Canonical signal reference. Use scope 'product' for a product-local signal defined by this listing; use scope 'data_provider' with data_provider_domain for a signal defined in a data provider's published adagents.json signals[]; use scope 'signal_source' with signal_source_url for a source-native signal." + ), + ] = None + signal_id: Annotated[ + signal_id_1.SignalId | None, + Field( + deprecated=True, + description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older Signals Protocol clients.', + ), + ] = None + name: Annotated[ + str | None, + Field( + description="Human-readable signal name. Required when signal_ref.scope is 'product'. For data_provider and signal_source refs, this is optional contextual display text; the referenced definition or source remains authoritative." + ), + ] = None + description: Annotated[ + str | None, + Field( + description='Detailed signal description. For data_provider and signal_source refs, this is optional contextual display text and MUST NOT replace the referenced definition.' + ), + ] = None + methodology_url: Annotated[ + AnyUrl | None, + Field( + description='Optional link to published methodology, media-kit, or data documentation. For data_provider and signal_source refs, this SHOULD match or supplement the referenced definition.' + ), + ] = None + last_updated: Annotated[ + AwareDatetime | None, + Field( + description='When this listing record was last updated. This indicates freshness of the listing record, not an attestation that the underlying data or model was refreshed at that time.' + ), + ] = None + value_type: Annotated[ + signal_value_type.SignalValueType | None, + Field( + description="The data type of this signal's values. Required when signal_ref.scope is 'product'." + ), + ] = None + categories: Annotated[ + list[str] | None, + Field( + description="Valid values for categorical signals. Present when value_type is 'categorical'.", + min_length=1, + ), + ] = None + range: Annotated[ + Range | None, + Field(description="Valid range for numeric signals. Present when value_type is 'numeric'."), + ] = None + restricted_attributes: Annotated[ + list[restricted_attribute.RestrictedAttribute] | None, + Field( + description='Restricted attribute categories this listing touches. Required with demographic_predicate and must include age. For referenced provider/source signals, any projected values must match the authoritative definition.', + min_length=1, + ), + ] = None + demographic_predicate: Annotated[ + demographic_predicate_1.DemographicPredicate | None, + Field( + description='Machine-readable demographic meaning. For product-local signals this listing is authoritative; for data-provider and signal-source refs, any projected value MUST match the referenced authoritative definition. Signal names alone never establish demographic semantics.' + ), + ] = None -# Explicit exports -__all__ = ["Range", "SignalListing"] + @model_validator(mode='after') + def _require_schema_required_group(self) -> SignalListing: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('signal_ref',), ('signal_id',),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'SignalListing requires at least one of these field groups: signal_ref | signal_id' + ) diff --git a/src/adcp/types/domains/core/signal_modeling_disclosure.py b/src/adcp/types/domains/core/signal_modeling_disclosure.py index e0e52b363..31be34976 100644 --- a/src/adcp/types/domains/core/signal_modeling_disclosure.py +++ b/src/adcp/types/domains/core/signal_modeling_disclosure.py @@ -1,22 +1,77 @@ -"""Types declared by the AdCP ``core/signal_modeling_disclosure`` schema. +# generated by datamodel-codegen: +# filename: core/signal_modeling_disclosure.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.signal_modeling_disclosure import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.signal_modeling_disclosure import ( - Audience, - Jurisdiction, - SignalModelingDisclosure, -) +class Audience(StrEnum): + buyer = 'buyer' + data_subject = 'data_subject' + regulator = 'regulator' + public = 'public' + + +class Jurisdiction(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + country: Annotated[ + str, Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + ] + region: Annotated[ + str | None, + Field( + description='Provider-defined sub-national region code or name when the obligation is regional. No global canonical format is implied.' + ), + ] = None + regulation: Annotated[ + str, + Field(description='Provider-supplied regulation identifier for the disclosure obligation.'), + ] + disclosure_text: Annotated[ + str | None, + Field( + description='Human-readable disclosure text or summary the provider expects buyers or reviewers to see.' + ), + ] = None + disclosure_url: Annotated[ + AnyUrl | None, + Field( + description="Optional URL to the provider's canonical disclosure or methodology page for this jurisdiction." + ), + ] = None + audience: Annotated[ + Audience | None, Field(description='Primary audience for this disclosure entry.') + ] = None + -# Explicit exports -__all__ = ["Audience", "Jurisdiction", "SignalModelingDisclosure"] +class SignalModelingDisclosure(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + required: Annotated[ + StrictBool, + Field( + description="The provider's claim that a modeling or AI-use disclosure is required for this signal in at least one applicable jurisdiction. This is a declared compliance signal, not a protocol-level legal determination." + ), + ] + jurisdictions: Annotated[ + list[Jurisdiction] | None, + Field( + description='Jurisdictions where a modeling or AI-use disclosure applies.', min_length=1 + ), + ] = None + notes: Annotated[ + str | None, + Field( + description='Optional provider notes on how the disclosure should be interpreted. Informational only; buyers should not branch programmatically on this text.', + max_length=2000, + ), + ] = None diff --git a/src/adcp/types/domains/core/signal_pricing.py b/src/adcp/types/domains/core/signal_pricing.py index ddeb950ad..2c35b6cc7 100644 --- a/src/adcp/types/domains/core/signal_pricing.py +++ b/src/adcp/types/domains/core/signal_pricing.py @@ -1,36 +1,130 @@ -"""Types declared by the AdCP ``core/signal_pricing`` schema. +# generated by datamodel-codegen: +# filename: core/signal_pricing.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.signal_pricing import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictFloat + +from . import ext as ext_1 + + +class VendorPricing1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + model: Literal['cpm'] = 'cpm' + cpm: Annotated[StrictFloat, Field(description='Cost per thousand impressions', ge=0.0)] + currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] + ext: ext_1.ExtensionObject | None = None + + +class VendorPricing2(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + model: Literal['percent_of_media'] = 'percent_of_media' + percent: Annotated[ + StrictFloat, Field(description='Percentage of media spend, e.g. 15 = 15%', ge=0.0, le=100.0) + ] + max_cpm: Annotated[ + StrictFloat | None, + Field( + description='Optional CPM cap. When set, the effective charge is min(percent × media_spend_per_mille, max_cpm).', + ge=0.0, + ), + ] = None + currency: Annotated[ + str, + Field(description='ISO 4217 currency code for the resulting charge', pattern='^[A-Z]{3}$'), + ] + ext: ext_1.ExtensionObject | None = None + + +class Period(StrEnum): + monthly = 'monthly' + quarterly = 'quarterly' + annual = 'annual' + campaign = 'campaign' + + +class VendorPricing3(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + model: Literal['flat_fee'] = 'flat_fee' + amount: Annotated[StrictFloat, Field(description='Fixed charge for the billing period', ge=0.0)] + period: Annotated[Period, Field(description='Billing period for the flat fee.')] + currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] + ext: ext_1.ExtensionObject | None = None + + +class VendorPricing4(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + model: Literal['per_unit'] = 'per_unit' + unit: Annotated[ + str, + Field( + description="What is counted — e.g. 'format', 'image', 'token', 'variant', 'render', 'evaluation'." + ), + ] + unit_price: Annotated[StrictFloat, Field(description='Cost per one unit', ge=0.0)] + currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] + ext: ext_1.ExtensionObject | None = None + + +class Metadata(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + summary_for_operator: Annotated[ + str | None, + Field( + description="One or two sentences describing the pricing construct in plain language, displayed to the buyer's operator when requesting approval. Should not repeat the top-level `description` verbatim — summarize the charge mechanic instead (e.g., 'Base $12 CPM plus $0.50 per qualifying post-view conversion, capped at $45 CPM').", + min_length=1, + ), + ] = None + + +class VendorPricing5(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + model: Literal['custom'] = 'custom' + description: Annotated[ + str, + Field( + description='Human-readable description of the custom pricing model. Buyers display this to the operator when requesting approval.', + min_length=1, + ), + ] + metadata: Annotated[ + Metadata, + Field( + description="Structured parameters for the custom model. Keys follow lowercase_snake_case. Values may be primitives, arrays, or nested objects. Must be sufficient for a human to understand the pricing basis and for a downstream system to reconstruct the charge. Vendors SHOULD include a `summary_for_operator` string (one or two sentences, suitable for display in a buyer's operator-review UI) so reviewers across vendors see a consistent prompt. Required operator-review fields (approver role, dollar threshold for automatic approval, escalation contact) MAY be surfaced via additional keys the buyer's review surface recognizes." + ), + ] + currency: Annotated[ + str | None, + Field( + description='ISO 4217 currency code. Present when the pricing resolves to a monetary charge in a specific currency.', + pattern='^[A-Z]{3}$', + ), + ] = None + ext: ext_1.ExtensionObject | None = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.signal_pricing import ( - Metadata, - Period, - VendorPricing, - VendorPricing1, - VendorPricing2, - VendorPricing3, - VendorPricing4, - VendorPricing5, -) - -# Explicit exports -__all__ = [ - "Metadata", - "Period", - "VendorPricing", - "VendorPricing1", - "VendorPricing2", - "VendorPricing3", - "VendorPricing4", - "VendorPricing5", +VendorPricing = Annotated[ + VendorPricing1 | VendorPricing2 | VendorPricing3 | VendorPricing4 | VendorPricing5, + Field( + description="Pricing model for a vendor service. Discriminated by model: 'cpm' (fixed CPM), 'percent_of_media' (percentage of spend with optional CPM cap), 'flat_fee' (fixed charge per reporting period), 'per_unit' (fixed price per unit of work), or 'custom' (escape hatch for models not covered by the enumerated forms — requires a description and structured metadata).", + discriminator='model', + title='Vendor Pricing', + ), ] diff --git a/src/adcp/types/domains/core/signal_pricing_option.py b/src/adcp/types/domains/core/signal_pricing_option.py index 437035cb4..d42f10c56 100644 --- a/src/adcp/types/domains/core/signal_pricing_option.py +++ b/src/adcp/types/domains/core/signal_pricing_option.py @@ -1,18 +1,21 @@ -"""Types declared by the AdCP ``core/signal_pricing_option`` schema. +# generated by datamodel-codegen: +# filename: core/signal_pricing_option.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.signal_pricing_option import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import Field, RootModel -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import vendor_pricing_option -from adcp.types.generated_poc.core.signal_pricing_option import SignalPricingOption -# Explicit exports -__all__ = ["SignalPricingOption"] +class SignalPricingOption(RootModel[vendor_pricing_option.VendorPricingOption]): + root: Annotated[ + vendor_pricing_option.VendorPricingOption, + Field( + description='Deprecated — use vendor-pricing-option.json for new implementations. This alias is retained for backward compatibility.', + title='Signal Pricing Option', + ), + ] diff --git a/src/adcp/types/domains/core/signal_ref.py b/src/adcp/types/domains/core/signal_ref.py index 2ed49e91d..888ba70ba 100644 --- a/src/adcp/types/domains/core/signal_ref.py +++ b/src/adcp/types/domains/core/signal_ref.py @@ -1,18 +1,87 @@ -"""Types declared by the AdCP ``core/signal_ref`` schema. +# generated by datamodel-codegen: +# filename: core/signal_ref.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.signal_ref import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.signal_ref import SignalRef, SignalRef1, SignalRef2, SignalRef3 +class SignalRef1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + scope: Annotated[ + Literal['product'], + Field( + description="Discriminator indicating the signal resolves through the selected product's included_signals or signal_targeting_options." + ), + ] = 'product' + signal_id: Annotated[ + str, + Field( + description='Product-local signal identifier. For local signals exposed on both get_signals and get_products, this MUST match get_signals.signals[].signal_ref.signal_id for the same signal.', + pattern='^[a-zA-Z0-9_-]+$', + ), + ] + + +class SignalRef2(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + scope: Annotated[ + Literal['data_provider'], + Field( + description="Discriminator indicating the signal resolves through a data provider's published adagents.json signals[]." + ), + ] = 'data_provider' + data_provider_domain: Annotated[ + str, + Field( + description='Domain that publishes the signal definition in its adagents.json signals[].', + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] + signal_id: Annotated[ + str, + Field( + description="Signal identifier within the data provider's published adagents.json signals[].", + pattern='^[a-zA-Z0-9_-]+$', + ), + ] + + +class SignalRef3(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + scope: Annotated[ + Literal['signal_source'], + Field( + description='Discriminator indicating the signal resolves through the issuing signal source.' + ), + ] = 'signal_source' + signal_source_url: Annotated[ + AnyUrl, Field(description='URL of the signal source that issues this source-native signal.') + ] + signal_id: Annotated[ + str, + Field( + description="Signal identifier within the issuing signal source's signal set.", + pattern='^[a-zA-Z0-9_-]+$', + ), + ] + -# Explicit exports -__all__ = ["SignalRef", "SignalRef1", "SignalRef2", "SignalRef3"] +SignalRef = Annotated[ + SignalRef1 | SignalRef2 | SignalRef3, + Field( + description="Reference to a named signal definition. Uses scope as discriminator: 'data_provider' for a signal resolved through published adagents.json signals[], 'signal_source' for a source-native signal resolved through the issuing signal source, or 'product' for a product-local signal option. Scope is the resolution path, not provenance; authoritative enrichment lives on the seller, signal source, or data-provider signal definition, not on this reference.", + discriminator='scope', + title='Signal Ref', + ), +] diff --git a/src/adcp/types/domains/core/signal_selection_group_rule.py b/src/adcp/types/domains/core/signal_selection_group_rule.py index eefd20251..830ae4f84 100644 --- a/src/adcp/types/domains/core/signal_selection_group_rule.py +++ b/src/adcp/types/domains/core/signal_selection_group_rule.py @@ -1,22 +1,57 @@ -"""Types declared by the AdCP ``core/signal_selection_group_rule`` schema. +# generated by datamodel-codegen: +# filename: core/signal_selection_group_rule.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.signal_selection_group_rule import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.signal_selection_group_rule import ( - SelectionMode, - SignalSelectionGroupRule, - TargetingMode, -) +class TargetingMode(StrEnum): + include = 'include' + exclude = 'exclude' + + +class SelectionMode(StrEnum): + optional = 'optional' + required = 'required' + fixed = 'fixed' + -# Explicit exports -__all__ = ["SelectionMode", "SignalSelectionGroupRule", "TargetingMode"] +class SignalSelectionGroupRule(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + selection_group: Annotated[ + str, + Field( + description='ProductSignalTargetingOption.selection_group value this rule applies to.' + ), + ] + targeting_mode: Annotated[ + TargetingMode | None, + Field( + description="How options in this selection_group are intended to be used in signal_targeting_groups. 'include' maps to child groups with operator 'any'. 'exclude' maps to child groups with operator 'none'. Omit when options in the group may be used according to each option's allowed_targeting_modes." + ), + ] = None + selection_mode: Annotated[ + SelectionMode | None, + Field( + description="Selection behavior for this selection_group. 'required' means at least min_selected_signals, or 1 when omitted. 'fixed' means default_selected options in this group are seller-applied and read-only." + ), + ] = None + min_selected_signals: Annotated[ + SchemaInt | None, + Field( + description="Minimum selected options from this selection_group. If selection_mode is 'required' and omitted, sellers MUST treat the minimum as 1.", + ge=0, + ), + ] = None + max_selected_signals: Annotated[ + SchemaInt | None, + Field(description='Maximum selected options from this selection_group.', ge=1), + ] = None diff --git a/src/adcp/types/domains/core/signal_targeting.py b/src/adcp/types/domains/core/signal_targeting.py index 1f33267f5..d9de5babe 100644 --- a/src/adcp/types/domains/core/signal_targeting.py +++ b/src/adcp/types/domains/core/signal_targeting.py @@ -1,23 +1,106 @@ -"""Types declared by the AdCP ``core/signal_targeting`` schema. +# generated by datamodel-codegen: +# filename: core/signal_targeting.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.signal_targeting import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictBool, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import signal_id as signal_id_1 +from . import signal_ref as signal_ref_1 + + +class SignalTargeting1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + signal_ref: Annotated[ + signal_ref_1.SignalRef | None, + Field(description='The signal to target. New targeting constraints SHOULD use signal_ref.'), + ] = None + signal_id: Annotated[ + signal_id_1.SignalId | None, + Field( + deprecated=True, + description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', + ), + ] = None + value_type: Annotated[Literal['binary'], Field(description='Discriminator for binary signals')] = 'binary' + value: Annotated[ + StrictBool, + Field( + description='Whether to include (true) or exclude (false) users matching this signal' + ), + ] + + +class SignalTargeting2(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + signal_ref: Annotated[ + signal_ref_1.SignalRef | None, + Field(description='The signal to target. New targeting constraints SHOULD use signal_ref.'), + ] = None + signal_id: Annotated[ + signal_id_1.SignalId | None, + Field( + deprecated=True, + description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', + ), + ] = None + value_type: Annotated[ + Literal['categorical'], Field(description='Discriminator for categorical signals') + ] = 'categorical' + values: Annotated[ + list[str], + Field( + description='Values to target. Users with any of these values will be included.', + min_length=1, + ), + ] + + +class SignalTargeting3(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + signal_ref: Annotated[ + signal_ref_1.SignalRef | None, + Field(description='The signal to target. New targeting constraints SHOULD use signal_ref.'), + ] = None + signal_id: Annotated[ + signal_id_1.SignalId | None, + Field( + deprecated=True, + description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', + ), + ] = None + value_type: Annotated[ + Literal['numeric'], Field(description='Discriminator for numeric signals') + ] = 'numeric' + min_value: Annotated[ + StrictFloat | None, + Field( + description="Minimum value (inclusive). Omit for no minimum. Must be <= max_value when both are provided. Should be >= signal's range.min if defined." + ), + ] = None + max_value: Annotated[ + StrictFloat | None, + Field( + description="Maximum value (inclusive). Omit for no maximum. Must be >= min_value when both are provided. Should be <= signal's range.max if defined." + ), + ] = None -from adcp.types.generated_poc.core.signal_targeting import ( - SignalTargeting, - SignalTargeting1, - SignalTargeting2, - SignalTargeting3, -) -# Explicit exports -__all__ = ["SignalTargeting", "SignalTargeting1", "SignalTargeting2", "SignalTargeting3"] +SignalTargeting = Annotated[ + SignalTargeting1 | SignalTargeting2 | SignalTargeting3, + Field( + description='Targeting constraint for a specific signal. Uses value_type as discriminator to determine the targeting expression format.', + discriminator='value_type', + title='Signal Targeting', + ), +] diff --git a/src/adcp/types/domains/core/signal_targeting_expression.py b/src/adcp/types/domains/core/signal_targeting_expression.py index 7f21650ba..93a9e8048 100644 --- a/src/adcp/types/domains/core/signal_targeting_expression.py +++ b/src/adcp/types/domains/core/signal_targeting_expression.py @@ -1,28 +1,75 @@ -"""Types declared by the AdCP ``core/signal_targeting_expression`` schema. +# generated by datamodel-codegen: +# filename: core/signal_targeting_expression.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.signal_targeting_expression import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictFloat + +from . import signal_ref as signal_ref_1 + + +class SignalTargetingExpression1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + signal_ref: Annotated[signal_ref_1.SignalRef, Field(description='Named signal being targeted.')] + value_type: Annotated[Literal['binary'], Field(description='Discriminator for binary signals.')] = 'binary' + value: Annotated[ + Literal[True], + Field( + description='Binary package signal entries match users for whom the signal is true. Use the parent group operator for include/exclude.' + ), + ] + + +class SignalTargetingExpression2(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + signal_ref: Annotated[signal_ref_1.SignalRef, Field(description='Named signal being targeted.')] + value_type: Annotated[ + Literal['categorical'], Field(description='Discriminator for categorical signals.') + ] = 'categorical' + values: Annotated[ + list[str], + Field( + description='Values to target. Users with any of these values match the expression.', + min_length=1, + ), + ] + + +class SignalTargetingExpression3(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + signal_ref: Annotated[signal_ref_1.SignalRef, Field(description='Named signal being targeted.')] + value_type: Annotated[ + Literal['numeric'], Field(description='Discriminator for numeric signals.') + ] = 'numeric' + min_value: Annotated[ + StrictFloat | None, + Field( + description="Minimum value, inclusive. Omit for no minimum. Should be within the signal definition's range when declared." + ), + ] = None + max_value: Annotated[ + StrictFloat | None, + Field( + description="Maximum value, inclusive. Omit for no maximum. Should be within the signal definition's range when declared." + ), + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.signal_targeting_expression import ( - SignalTargetingExpression, - SignalTargetingExpression1, - SignalTargetingExpression2, - SignalTargetingExpression3, -) - -# Explicit exports -__all__ = [ - "SignalTargetingExpression", - "SignalTargetingExpression1", - "SignalTargetingExpression2", - "SignalTargetingExpression3", +SignalTargetingExpression = Annotated[ + SignalTargetingExpression1 | SignalTargetingExpression2 | SignalTargetingExpression3, + Field( + description='Predicate over a named signal definition. Signals are typed dimensions, similar to feature values: binary signals match true, categorical signals match one of a set of values, and numeric signals match a range. In package signal targeting groups, include/exclude semantics are controlled by the parent group operator, not by negating the expression.', + discriminator='value_type', + title='Signal Targeting Expression', + ), ] diff --git a/src/adcp/types/domains/core/signal_targeting_rules.py b/src/adcp/types/domains/core/signal_targeting_rules.py index 9d263abb3..4d9d6d7be 100644 --- a/src/adcp/types/domains/core/signal_targeting_rules.py +++ b/src/adcp/types/domains/core/signal_targeting_rules.py @@ -1,22 +1,84 @@ -"""Types declared by the AdCP ``core/signal_targeting_rules`` schema. +# generated by datamodel-codegen: +# filename: core/signal_targeting_rules.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.signal_targeting_rules import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import signal_selection_group_rule + + +class ResolutionModel(StrEnum): + direct_targeting = 'direct_targeting' + seller_planned = 'seller_planned' + + +class SelectionMode(StrEnum): + optional = 'optional' + required = 'required' + fixed = 'fixed' -from adcp.types.generated_poc.core.signal_targeting_rules import ( - ResolutionModel, - SelectionMode, - SignalTargetingRules, -) -# Explicit exports -__all__ = ["ResolutionModel", "SelectionMode", "SignalTargetingRules"] +class SignalTargetingRules(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + resolution_model: Annotated[ + ResolutionModel | None, + Field( + description="How selected signal_targeting_options are resolved against the product's inventory. 'direct_targeting' means selected signals are applied as targeting predicates to the package inventory. 'seller_planned' means selected signals are planning inputs that the seller resolves against product-specific inventory, timing, availability, reach, or pacing constraints; buyers SHOULD NOT attempt to decompose the signal selection into lower-level inventory or schedule decisions. Use 'seller_planned' for products such as linear broadcast schedules where the audience definition may be portable but the audience-to-avails plan is seller-resolved." + ), + ] = ResolutionModel.direct_targeting + selection_mode: Annotated[ + SelectionMode | None, + Field( + description="Default selection behavior for selectable signals on this product. 'optional' means the buyer may select zero or more signals. 'required' means the buyer must select at least min_selected_signals, or 1 when min_selected_signals is omitted. 'fixed' means the seller applies the default_selected signals and the buyer cannot add or remove them; buyers SHOULD render those entries as read-only and sellers MUST echo them in package targeting_overlay.signal_targeting_groups. Use selection_group_rules for product-scoped products that need different behavior for different groups, such as fixed suppressions plus a required include tier." + ), + ] = SelectionMode.optional + min_selected_signals: Annotated[ + SchemaInt | None, + Field( + description="Minimum number of signals the buyer must select when selection_mode is 'required'. If selection_mode is 'required' and this field is omitted, sellers MUST treat the minimum as 1. Defaults to 0 for optional selection.", + ge=0, + ), + ] = None + max_selected_signals: Annotated[ + SchemaInt | None, + Field( + description='Maximum number of signals the buyer may select for a package. Omit when there is no declared limit beyond the available options.', + ge=1, + ), + ] = None + max_selected_per_group: Annotated[ + SchemaInt | None, + Field( + description='Maximum number of signal_targeting_options the buyer may select from the same ProductSignalTargetingOption.selection_group. Use 1 for mutually exclusive alternatives within each option group. This limit applies to product option grouping, not to the number of child groups in packages[].targeting_overlay.signal_targeting_groups.', + ge=1, + ), + ] = None + max_signal_targeting_groups: Annotated[ + SchemaInt | None, + Field( + description='Maximum number of child groups allowed in packages[].targeting_overlay.signal_targeting_groups.groups. Omit when the seller has no declared limit beyond product terms.', + ge=1, + ), + ] = None + max_signals_per_targeting_group: Annotated[ + SchemaInt | None, + Field( + description='Maximum number of signals allowed in each packages[].targeting_overlay.signal_targeting_groups.groups[].signals array. Omit when the seller has no declared limit beyond product terms.', + ge=1, + ), + ] = None + selection_group_rules: Annotated[ + list[signal_selection_group_rule.SignalSelectionGroupRule] | None, + Field( + description='Optional product-scoped overrides for specific ProductSignalTargetingOption.selection_group values. Use this when one product has mixed behavior, such as fixed seller-applied suppressions, a required pick-one include tier, optional buyer-selected exclusions, or heterogeneous targeting planes that must be represented as separate ANDed clauses. Rules apply only to options whose selection_group matches. When selection_group_rules are present, each packages[].targeting_overlay.signal_targeting_groups child group MUST contain signals from exactly one selection_group and one targeting_mode, and buyers MUST send at most one child group for each (selection_group, targeting_mode) pair. Sellers MUST reject duplicate, mixed, or collapsed groups that combine distinct selection_group_rules into the same child group.', + min_length=1, + ), + ] = None diff --git a/src/adcp/types/domains/core/sla_window.py b/src/adcp/types/domains/core/sla_window.py index 3f1ffd943..f6545809d 100644 --- a/src/adcp/types/domains/core/sla_window.py +++ b/src/adcp/types/domains/core/sla_window.py @@ -1,18 +1,33 @@ -"""Types declared by the AdCP ``core/sla_window`` schema. +# generated by datamodel-codegen: +# filename: core/sla_window.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.sla_window import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -from adcp.types.generated_poc.core.sla_window import SlaWindow -# Explicit exports -__all__ = ["SlaWindow"] +class SlaWindow(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + regex_engine="python-re", + ) + response_max: Annotated[ + str | None, + Field( + description='Maximum elapsed time from when the buyer issues the action to when the seller acknowledges receipt (mode-appropriate: synchronous response for self_serve, tolerance decision for conditional_self_serve, or queue acknowledgement for seller_managed and legacy requires_approval). Sellers include weekends and non-working periods in the maximum. ISO 8601 duration.', + examples=['PT5M', 'PT4H', 'P1D'], + pattern='^P(?!$)(\\d+Y)?(\\d+M)?(\\d+D)?(T(\\d+H)?(\\d+M)?(\\d+S)?)?$', + ), + ] = None + completion_max: Annotated[ + str | None, + Field( + description='Maximum elapsed time from buyer issuing the action to the seller completing it (mutation applied, proposal finalized, or seller-managed decision resolved). Sellers include weekends and non-working periods in the maximum. ISO 8601 duration.', + examples=['PT1H', 'PT24H', 'P2D'], + pattern='^P(?!$)(\\d+Y)?(\\d+M)?(\\d+D)?(T(\\d+H)?(\\d+M)?(\\d+S)?)?$', + ), + ] = None diff --git a/src/adcp/types/domains/core/special.py b/src/adcp/types/domains/core/special.py index 7fdce855b..c2b96e54d 100644 --- a/src/adcp/types/domains/core/special.py +++ b/src/adcp/types/domains/core/special.py @@ -1,18 +1,31 @@ -"""Types declared by the AdCP ``core/special`` schema. +# generated by datamodel-codegen: +# filename: core/special.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.special import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import special_category -from adcp.types.generated_poc.core.special import Special -# Explicit exports -__all__ = ["Special"] +class Special(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + name: Annotated[ + str, Field(description="Name of the event (e.g., 'Olympics 2028', 'Super Bowl LXI')") + ] + category: Annotated[ + special_category.SpecialCategory | None, Field(description='Category of the event') + ] = None + starts: Annotated[ + AwareDatetime | None, Field(description='When the event starts (ISO 8601)') + ] = None + ends: Annotated[ + AwareDatetime | None, + Field(description='When the event ends (ISO 8601). Omit for single-day events.'), + ] = None diff --git a/src/adcp/types/domains/core/spot_reporting_capability.py b/src/adcp/types/domains/core/spot_reporting_capability.py index e21383562..597e24863 100644 --- a/src/adcp/types/domains/core/spot_reporting_capability.py +++ b/src/adcp/types/domains/core/spot_reporting_capability.py @@ -1,18 +1,24 @@ -"""Types declared by the AdCP ``core/spot_reporting_capability`` schema. +# generated by datamodel-codegen: +# filename: core/spot_reporting_capability.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.spot_reporting_capability import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import available_metric -from adcp.types.generated_poc.core.spot_reporting_capability import SpotReportingCapability -# Explicit exports -__all__ = ["SpotReportingCapability"] +class SpotReportingCapability(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + available_metrics: Annotated[ + list[available_metric.AvailableMetric], + Field( + description='Metrics available at spot grain. A declared metric may be absent from a provisional measurement window and appear when a later window supersedes it. A metric omitted from this declaration is not promised at spot grain even when it is available at package grain.' + ), + ] diff --git a/src/adcp/types/domains/core/start_timing.py b/src/adcp/types/domains/core/start_timing.py index f837c8150..3992e2387 100644 --- a/src/adcp/types/domains/core/start_timing.py +++ b/src/adcp/types/domains/core/start_timing.py @@ -1,18 +1,17 @@ -"""Types declared by the AdCP ``core/start_timing`` schema. +# generated by datamodel-codegen: +# filename: core/start_timing.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.start_timing import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import AwareDatetime, Field -from adcp.types.generated_poc.core.start_timing import StartTiming -# Explicit exports -__all__ = ["StartTiming"] +StartTiming = Annotated[ + Literal['asap'] | AwareDatetime, + Field( + description="Campaign start timing: 'asap' or ISO 8601 date-time", title='Start Timing' + ), +] diff --git a/src/adcp/types/domains/core/store_item.py b/src/adcp/types/domains/core/store_item.py index e0601f390..a93a4eb49 100644 --- a/src/adcp/types/domains/core/store_item.py +++ b/src/adcp/types/domains/core/store_item.py @@ -1,18 +1,98 @@ -"""Types declared by the AdCP ``core/store_item`` schema. +# generated by datamodel-codegen: +# filename: core/store_item.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.store_item import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import catchment +from . import ext as ext_1 + + +class Location(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + lat: Annotated[ + StrictFloat, Field(description='Latitude in decimal degrees (WGS 84).', ge=-90.0, le=90.0) + ] + lng: Annotated[ + StrictFloat, + Field(description='Longitude in decimal degrees (WGS 84).', ge=-180.0, le=180.0), + ] + + +class Address(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + street: Annotated[str | None, Field(description="Street address (e.g., '123 Main St').")] = None + city: Annotated[str | None, Field(description='City name.')] = None + region: Annotated[ + str | None, + Field( + description="State, province, or region. ISO 3166-2 subdivision code preferred (e.g., 'NL-NH', 'US-CA')." + ), + ] = None + postal_code: Annotated[str | None, Field(description='Postal or ZIP code.')] = None + country: Annotated[ + str | None, Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') + ] = None -from adcp.types.generated_poc.core.store_item import Address, Location, StoreItem -# Explicit exports -__all__ = ["Address", "Location", "StoreItem"] +class StoreItem(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + store_id: Annotated[ + str, + Field( + description='Unique identifier for this store. Used to reference specific stores in targeting, inventory feeds, and creative templates.' + ), + ] + name: Annotated[ + str, + Field( + description="Human-readable store name (e.g., 'Amsterdam Flagship', 'Brooklyn Heights')." + ), + ] + location: Annotated[Location, Field(description='Geographic coordinates of the store.')] + address: Annotated[ + Address | None, Field(description='Structured address for display and geocoding fallback.') + ] = None + catchments: Annotated[ + list[catchment.Catchment] | None, + Field( + description='Catchment areas for this store. Each defines a reachable area using travel time (isochrone), simple radius, or pre-computed GeoJSON. Multiple catchments allow different modes — e.g., 15-minute drive AND 10-minute walk.', + min_length=1, + ), + ] = None + phone: Annotated[ + str | None, Field(description="Store phone number in E.164 format (e.g., '+31201234567').") + ] = None + url: Annotated[ + AnyUrl | None, + Field(description='Store-specific page URL (e.g., store locator detail page).'), + ] = None + hours: Annotated[ + dict[ + Literal['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'], + str, + ] + | None, + Field( + description='Operating hours. Keys are ISO day names (monday–sunday), values are time ranges.' + ), + ] = None + tags: Annotated[ + list[str] | None, + Field( + description="Tags for filtering stores in targeting and creative selection (e.g., 'flagship', 'pickup', 'pharmacy').", + min_length=1, + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/talent.py b/src/adcp/types/domains/core/talent.py index 5f270a4d4..5607dfb08 100644 --- a/src/adcp/types/domains/core/talent.py +++ b/src/adcp/types/domains/core/talent.py @@ -1,18 +1,29 @@ -"""Types declared by the AdCP ``core/talent`` schema. +# generated by datamodel-codegen: +# filename: core/talent.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.talent import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import talent_role -from adcp.types.generated_poc.core.talent import Talent -# Explicit exports -__all__ = ["Talent"] +class Talent(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + role: Annotated[ + talent_role.TalentRole, + Field(description='Role of this person on the collection or installment'), + ] + name: Annotated[str, Field(description="Person's name as credited on the collection")] + brand_url: Annotated[ + AnyUrl | None, + Field( + description="URL to this person's brand.json entry. Enables buyer agents to evaluate the talent's brand identity and associations." + ), + ] = None diff --git a/src/adcp/types/domains/core/targeting.py b/src/adcp/types/domains/core/targeting.py index ba7fd6b03..17707ed15 100644 --- a/src/adcp/types/domains/core/targeting.py +++ b/src/adcp/types/domains/core/targeting.py @@ -1,48 +1,476 @@ -"""Types declared by the AdCP ``core/targeting`` schema. +# generated by datamodel-codegen: +# filename: core/targeting.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.targeting import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-04 01:19:07 UTC -""" +from adcp.types._str_enum import StrEnum +from collections.abc import Sequence +from typing import Annotated, Any -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel, SchemaInt +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field, StrictBool, StrictFloat -from adcp.types.generated_poc.core.targeting import ( - AgeRestriction, - GeoCountriesExcludeItem, - GeoCountry, - GeoMetrosExcludeItem, - GeoProximityItem, - GeoRegion, - GeoRegionsExcludeItem, - Geometry, - KeywordTarget, - Radius, - StoreCatchment, - TargetingOverlay, - TravelTime, - Type, +from ..enums import age_verification_method, browser_family +from ..enums import device_platform as device_platform_1 +from ..enums import device_type as device_type_1 +from ..enums import distance_unit +from ..enums import match_type as match_type_1 +from ..enums import metro_system +from ..enums import transport_mode as transport_mode_1 +from ..enums import travel_time_unit +from . import collection_list_ref +from . import collection_selection as collection_selection_1 +from . import daypart_target, demographic_targeting_intent +from . import ext as ext_1 +from . import frequency_cap as frequency_cap_1 +from . import ( + geo_metro, + geo_place_area, + locale_tag, + negative_keyword, + package_signal_targeting_groups, ) +from . import placement_selection as placement_selection_1 +from . import postal_area, property_list_ref +from . import signal_targeting as signal_targeting_1 + + +class GeoCountry(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{2}$'} + + +class GeoCountriesExcludeItem(GeoCountry): + pass + + +class GeoRegion(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{2}-[A-Z0-9]{1,3}$'} + + +class GeoRegionsExcludeItem(GeoRegion): + pass + + +class GeoMetrosExcludeItem(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + system: Annotated[ + metro_system.MetroAreaSystem, + Field(description="Metro area classification system (e.g., 'nielsen_dma', 'uk_itl2')"), + ] + values: Annotated[ + list[str], + Field( + description="Metro codes to exclude within the system (e.g., ['501', '602'] for Nielsen DMAs)", + min_length=1, + ), + ] + + +class AgeRestriction(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + min: Annotated[SchemaInt, Field(description='Minimum age required', ge=13, le=99)] + verification_required: Annotated[ + StrictBool | None, + Field(description='Whether verified age (not inferred) is required for compliance'), + ] = False + accepted_methods: Annotated[ + list[age_verification_method.AgeVerificationMethod] | None, + Field( + description='Accepted verification methods. If omitted, any method the platform supports is acceptable.', + min_length=1, + ), + ] = None + + +class StoreCatchment(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + catalog_id: Annotated[ + str, Field(description='Synced store-type catalog ID from sync_catalogs.') + ] + store_ids: Annotated[ + list[str] | None, + Field( + description='Filter to specific stores within the catalog. Omit to target all stores.', + min_length=1, + ), + ] = None + catchment_ids: Annotated[ + list[str] | None, + Field( + description="Catchment zone IDs to target (e.g., 'walk', 'drive'). Omit to target all catchment zones.", + min_length=1, + ), + ] = None + + +class Radius(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + value: Annotated[StrictFloat, Field(description='Radius distance.', gt=0.0)] + unit: Annotated[distance_unit.DistanceUnit, Field(description='Distance unit.')] + + +class Type(StrEnum): + Polygon = 'Polygon' + MultiPolygon = 'MultiPolygon' + + +class Geometry(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Annotated[Type, Field(description='GeoJSON geometry type.')] + coordinates: Annotated[ + list[Any], + Field( + description='GeoJSON coordinates array. For Polygon: array of linear rings. For MultiPolygon: array of polygons.' + ), + ] + + +class KeywordTarget(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + keyword: Annotated[str, Field(description='The keyword to target', min_length=1)] + match_type: match_type_1.MatchType + bid_price: Annotated[ + StrictFloat | None, + Field( + description="Per-keyword bid price, denominated in the same currency as the package's pricing option. Overrides the package-level bid_price for this keyword. Inherits the max_bid interpretation from the pricing option: when max_bid is true, this is the keyword's bid ceiling; when false, this is the exact bid. If omitted, the package bid_price applies.", + ge=0.0, + ), + ] = None + + +class TravelTime(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + value: Annotated[StrictFloat, Field(description='Travel time limit.', ge=1.0)] + unit: travel_time_unit.TravelTimeUnit + + +class GeoProximityItem(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + lat: Annotated[ + StrictFloat | None, + Field( + description='Latitude in decimal degrees (WGS 84). Required for travel_time and radius methods.', + ge=-90.0, + le=90.0, + ), + ] = None + lng: Annotated[ + StrictFloat | None, + Field( + description='Longitude in decimal degrees (WGS 84). Required for travel_time and radius methods.', + ge=-180.0, + le=180.0, + ), + ] = None + label: Annotated[ + str | None, + Field( + description="Human-readable label for this entry (e.g., 'Düsseldorf', 'Heathrow Airport', 'Primary trade area')." + ), + ] = None + travel_time: Annotated[ + TravelTime | None, + Field( + description='Travel time limit for isochrone calculation. The platform resolves this to a geographic boundary based on actual transportation networks.' + ), + ] = None + transport_mode: Annotated[ + transport_mode_1.TransportMode | None, + Field( + description='Transportation mode for isochrone calculation. Required when travel_time is provided.' + ), + ] = None + radius: Annotated[ + Radius | None, + Field( + description='Simple radius from the point. The platform draws a circle of this distance around the coordinates.' + ), + ] = None + geometry: Annotated[ + Geometry | None, + Field( + description='Pre-computed GeoJSON geometry defining the proximity boundary. Use when the buyer has already calculated isochrones (via TravelTime, Mapbox, etc.) or has custom boundaries. When geometry is provided, lat/lng are not required.' + ), + ] = None + ext: ext_1.ExtensionObject | None = None + -# Explicit exports -__all__ = [ - "AgeRestriction", - "GeoCountriesExcludeItem", - "GeoCountry", - "GeoMetrosExcludeItem", - "GeoProximityItem", - "GeoRegion", - "GeoRegionsExcludeItem", - "Geometry", - "KeywordTarget", - "Radius", - "StoreCatchment", - "TargetingOverlay", - "TravelTime", - "Type", -] +class TargetingOverlay(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + geo_countries: Annotated[ + list[GeoCountry] | None, + Field( + description="Restrict delivery to specific countries. ISO 3166-1 alpha-2 codes (e.g., 'US', 'GB', 'DE').", + min_length=1, + ), + ] = None + geo_countries_exclude: Annotated[ + Sequence[GeoCountriesExcludeItem] | None, + Field( + description="Exclude specific countries from delivery. ISO 3166-1 alpha-2 codes (e.g., 'US', 'GB', 'DE').", + min_length=1, + ), + ] = None + geo_regions: Annotated[ + list[GeoRegion] | None, + Field( + description='Restrict delivery to exact canonical ISO 3166-2 subdivisions (states, provinces, regions, departments, or other subdivision categories). Unknown identifiers are invalid. At create or update, sellers MUST reject unsupported identifiers and MUST NOT silently widen, drop, or partially apply the list. During get_products, a seller may instead return a sparse, buyer-reviewable targeting_resolution modification for a valid but unsupported requested outcome. Exact internal translation preserves accepted identifiers in package readback.', + min_length=1, + ), + ] = None + geo_regions_exclude: Annotated[ + Sequence[GeoRegionsExcludeItem] | None, + Field( + description='Exclude exact canonical ISO 3166-2 subdivisions. Support is independent from geo_regions inclusion support. Unknown identifiers and values also present in geo_regions are invalid. At create or update, sellers MUST reject unsupported identifiers and partial application; during get_products, a seller may instead return a sparse, buyer-reviewable targeting_resolution modification for a valid but unsupported requested outcome.', + min_length=1, + ), + ] = None + geo_metros: Annotated[ + list[geo_metro.GeoMetro] | None, + Field( + description='Restrict delivery to specific metro areas. Each entry specifies the classification system and target values. Seller must declare supported systems in get_adcp_capabilities.', + min_length=1, + title='Targeting Geo Metros', + ), + ] = None + geo_metros_exclude: Annotated[ + Sequence[GeoMetrosExcludeItem] | None, + Field( + description='Exclude specific metro areas from delivery. Each entry specifies the classification system and excluded values. Seller must declare supported systems in get_adcp_capabilities.', + min_length=1, + ), + ] = None + geo_postal_areas: Annotated[ + list[postal_area.PostalArea] | None, + Field( + description='Restrict delivery to specific postal areas. Prefer the native country + postal system form. The deprecated legacy country-fused postal-system tokens remain accepted for compatibility. Seller must declare supported systems in get_adcp_capabilities.', + min_length=1, + ), + ] = None + geo_postal_areas_exclude: Annotated[ + Sequence[postal_area.PostalArea] | None, + Field( + description='Exclude specific postal areas from delivery. Prefer the native country + postal system form. The deprecated legacy country-fused postal-system tokens remain accepted for compatibility. Seller must declare supported systems in get_adcp_capabilities.', + min_length=1, + ), + ] = None + geo_places: Annotated[ + list[geo_place_area.GeographicPlaceArea] | None, + Field( + description='Restrict delivery to catalog-backed named places. Values MUST be stable identifiers in the declared system, not display names. Sellers must declare supported systems, countries, and place types in get_adcp_capabilities and reject unsupported entries rather than silently dropping them.', + min_length=1, + ), + ] = None + geo_places_exclude: Annotated[ + list[geo_place_area.GeographicPlaceArea] | None, + Field( + description='Exclude catalog-backed named places. Uses the same identifier-based shape as geo_places. Sellers MUST reject overlap with geo_places for the same country, system, place_type, and value.', + min_length=1, + ), + ] = None + daypart_targets: Annotated[ + list[daypart_target.DaypartTarget] | None, + Field( + description='Restrict delivery to specific time windows. Each entry specifies days of week, an hour range, and an optional timezone that defaults to inventory_local. A concrete IANA zone uses one shared civil-time clock, while inventory_local evaluates each inventory unit in its seller-assigned local timezone. Entries are independent and MAY use different clocks.', + min_length=1, + ), + ] = None + axe_include_segment: Annotated[ + str | None, + Field( + deprecated=True, + description='Deprecated: Use TMP provider fields instead. AXE segment ID to include for targeting.', + ), + ] = None + axe_exclude_segment: Annotated[ + str | None, + Field( + deprecated=True, + description='Deprecated: Use TMP provider fields instead. AXE segment ID to exclude from targeting.', + ), + ] = None + audience_include: Annotated[ + list[str] | None, + Field( + description='Restrict delivery to members of these first-party CRM audiences. Only users present in the uploaded lists are eligible. References audience_id values from sync_audiences on the same seller account — audience IDs are not portable across sellers. Not for lookalike expansion — express that intent in the campaign brief. Seller must declare support in get_adcp_capabilities.', + min_length=1, + ), + ] = None + audience_exclude: Annotated[ + list[str] | None, + Field( + description='Suppress delivery to members of these first-party CRM audiences. Matched users are excluded regardless of other targeting. References audience_id values from sync_audiences on the same seller account — audience IDs are not portable across sellers. Seller must declare support in get_adcp_capabilities.', + min_length=1, + ), + ] = None + signal_targeting_groups: Annotated[ + package_signal_targeting_groups.PackageSignalTargetingGroups | None, + Field( + description="Basic Boolean grouping for seller-offered signals. v1 supports a required top-level operator 'all' and child groups with operator 'any' for include groups or 'none' for exclusion groups. Example semantics: group 1 any(A, B) plus group 2 none(C, D) means (A OR B) AND NOT (C OR D). Signal entries reference named signal definitions with signal_ref scope 'product' for product-local signal options or scope 'data_provider' for external signals published in adagents.json signals[]. For simple include-only targeting, send one child group with operator 'any'. Sellers SHOULD reject entries that are not available for the product through inline signal_targeting_options or get_signals, are not active for the account, or exceed the product's signal_targeting_allowed/signal_targeting_rules/product terms. Signal targeting limits are product-scoped, not declared in get_adcp_capabilities, because products may be backed by different ad servers. Sellers MUST echo applied signal_targeting_groups on the resulting package state, including fixed/default selections. Sellers MAY return REQUOTE_REQUIRED when a targeting mutation changes commercial terms.", + title='Targeting Signal Groups', + ), + ] = None + signal_targeting: Annotated[ + list[signal_targeting_1.SignalTargeting] | None, + Field( + deprecated=True, + description='DEPRECATED. Use signal_targeting_groups for package-level signal targeting. Legacy flat signal_targeting remains accepted during the SignalRef migration window but cannot express grouped include/exclude composition or product-scoped pricing.', + min_length=1, + ), + ] = None + demographics: Annotated[ + demographic_targeting_intent.DemographicTargetingIntent | None, + Field( + description='Canonical demographic audience targeting intent with optional constraints on how age may be determined. This is distinct from age_restriction: demographics selects an audience, while age_restriction expresses a legal eligibility or verification floor. Fresh create/update targeting MUST compile exactly or be rejected. During get_products, a seller may offer a different configured predicate only through sparse targeting_resolution modifications on a distinguishable product_id; selecting that product accepts the alternative. Sellers never silently broaden, narrow, default, drop, or substitute the basis.' + ), + ] = None + frequency_cap: Annotated[ + frequency_cap_1.FrequencyCap | None, Field(title='Targeting Frequency Cap') + ] = None + property_list: Annotated[ + property_list_ref.PropertyListReference | None, + Field( + description="Reference to a property list for targeting specific properties within this product. The package runs on the intersection of the product's publisher_properties and this list. Sellers SHOULD return a validation error if the product has property_targeting_allowed: false.", + title='Targeting Property List', + ), + ] = None + property_list_exclude: Annotated[ + property_list_ref.PropertyListReference | None, + Field( + description="Reference to a property list whose properties must not carry the buyer's ads. Matched properties are removed from delivery. Use for brand-safety do-not-run lists (apps, sites). Exclude wins on overlap with property_list, and applies regardless of the product's property_targeting_allowed flag. Seller must declare support in get_adcp_capabilities." + ), + ] = None + collection_list: Annotated[ + collection_list_ref.CollectionListReference | None, + Field( + description='Reference to a collection list for including specific collections (programs, publications, channels) within this product. The package runs on the intersection of matched collections and this list. Use for inclusion-based collection targeting. Seller must declare support in get_adcp_capabilities.', + title='Targeting Collection List', + ), + ] = None + collection_list_exclude: Annotated[ + collection_list_ref.CollectionListReference | None, + Field( + description="Reference to a collection list for excluding specific collections (programs, publications, channels) from this product. Matched collections must not carry the buyer's ads. Use for brand safety do-not-air lists. Seller must declare support in get_adcp_capabilities." + ), + ] = None + placement_selection: Annotated[ + placement_selection_1.PlacementSelection | None, + Field( + description='Purchased placement selection within the product. This constrains package inventory; it is distinct from creative_assignments[].placement_refs, which only route individual creatives within the purchased set. On create, mode selected supplies the complete selected set and mode default uses the product default. In request-side Targeting Input, a non-null value replaces this dimension, omission preserves or inherits it, and null clears it when the product permits that broader inventory set.' + ), + ] = None + collection_selection: Annotated[ + collection_selection_1.CollectionSelection | None, + Field( + description="Purchased collection selection within the product. On create, mode selected supplies the complete selected set and mode default uses the product's full bundle. On package readback this is the committed selection sellers MUST echo as concrete selectors, materializing any collection_list composition; collection_list fields remain the buyer-managed list mechanism. In request-side Targeting Input, a non-null value replaces this dimension, omission preserves or inherits it, and null clears it when the product permits that broader inventory set.", + title='Targeting Collection Selection', + ), + ] = None + age_restriction: Annotated[ + AgeRestriction | None, + Field( + description='Age restriction for compliance. Use for legal requirements (alcohol, gambling), not audience targeting.' + ), + ] = None + device_platform: Annotated[ + list[device_platform_1.DevicePlatform] | None, + Field( + description='Restrict to specific platforms. Use for technical compatibility (app only works on iOS). Values from Sec-CH-UA-Platform standard, extended for CTV.', + min_length=1, + ), + ] = None + device_platform_exclude: Annotated[ + list[device_platform_1.DevicePlatform] | None, + Field( + description='Exclude specific operating-system platforms from delivery. When a platform appears in both device_platform and device_platform_exclude, exclusion wins. Sellers MUST reject a request they cannot enforce rather than silently dropping the exclusion.', + min_length=1, + ), + ] = None + device_type: Annotated[ + list[device_type_1.DeviceType] | None, + Field( + description='Restrict to specific device form factors. Use for campaigns targeting hardware categories rather than operating systems (e.g., mobile-only promotions, CTV campaigns).', + min_length=1, + ), + ] = None + device_type_exclude: Annotated[ + list[device_type_1.DeviceType] | None, + Field( + description='Exclude specific device form factors from delivery (e.g., exclude CTV for app-install campaigns).', + min_length=1, + ), + ] = None + browser: Annotated[ + list[browser_family.BrowserFamily] | None, + Field( + description='Restrict delivery to specific canonical browser families in the impression delivery and rendering environment, not the post-click landing-page browser. Values MUST NOT be inferred solely from operating system, device, web/mobile-web inventory, or placement. Values in this array use OR semantics. When browser is supplied, families not listed are ineligible: other includes a seller-recognized family that is not explicitly enumerated, while unknown includes a browser the seller cannot classify into a recognized family. When the same family appears in browser and browser_exclude, exclusion wins. Browser and device constraints intersect; a seller that cannot enforce the exact combination MUST exclude or explicitly reconfigure the product during discovery and MUST reject it at create or update rather than silently widening delivery. Browser versions and seller-native IDs are intentionally unsupported.', + min_length=1, + ), + ] = None + browser_exclude: Annotated[ + list[browser_family.BrowserFamily] | None, + Field( + description='Exclude specific canonical browser families from delivery. other excludes seller-recognized families that are not explicitly enumerated; unknown excludes browsers the seller cannot classify into a recognized family. When the same family appears in browser and browser_exclude, exclusion wins. Sellers MUST reject a request they cannot enforce rather than silently dropping the exclusion.', + min_length=1, + ), + ] = None + store_catchments: Annotated[ + list[StoreCatchment] | None, + Field( + description='Target users within store catchment areas from a synced store catalog. Each entry references a store-type catalog and optionally narrows to specific stores or catchment zones.', + min_length=1, + ), + ] = None + geo_proximity: Annotated[ + list[GeoProximityItem] | None, + Field( + description='Target users within travel time, distance, or a custom boundary around arbitrary geographic points. Multiple entries use OR semantics — a user within range of any listed point is eligible. For campaigns targeting 10+ locations, consider using store_catchments with a location catalog instead. Seller must declare support in get_adcp_capabilities.', + min_length=1, + ), + ] = None + language: Annotated[ + list[locale_tag.LanguageTag] | None, + Field( + description="Restrict to users with specific language preferences using canonical BCP 47 language ranges. Each buyer range is evaluated against a user's language-preference tag with RFC 4647 section 3.3.1 Basic Filtering: 'fr' matches 'fr', 'fr-CA', and 'fr-FR', while 'fr-CA' matches 'fr-CA' and more-specific descendants but not 'fr' or 'fr-FR'. Values use OR logic.", + min_length=1, + title='Targeting Languages', + ), + ] = None + keyword_targets: Annotated[ + list[KeywordTarget] | None, + Field( + description='Keyword targeting for search and retail media platforms. Restricts delivery to queries matching the specified keywords. Each keyword is identified by the tuple (keyword, match_type) — the same keyword string with different match types are distinct targets. Sellers SHOULD reject duplicate (keyword, match_type) pairs within a single request. Seller must declare support in get_adcp_capabilities.', + min_length=1, + title='Targeting Keywords', + ), + ] = None + negative_keywords: Annotated[ + list[negative_keyword.NegativeKeyword] | None, + Field( + description='Keywords to exclude from delivery. Queries matching these keywords will not trigger the ad. Each negative keyword is identified by the tuple (keyword, match_type). Seller must declare support in get_adcp_capabilities.', + min_length=1, + title='Targeting Negative Keywords', + ), + ] = None diff --git a/src/adcp/types/domains/core/targeting_input.py b/src/adcp/types/domains/core/targeting_input.py index 7091e3eda..a86a60833 100644 --- a/src/adcp/types/domains/core/targeting_input.py +++ b/src/adcp/types/domains/core/targeting_input.py @@ -1,46 +1,409 @@ -"""Types declared by the AdCP ``core/targeting_input`` schema. +# generated by datamodel-codegen: +# filename: core/targeting_input.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.targeting_input import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-04 01:19:07 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field, StrictFloat -from adcp.types.generated_poc.core.targeting_input import ( - GeoCountriesExcludeItem, - GeoCountry, - GeoMetrosExcludeItem, - GeoProximityItem, - GeoRegion, - GeoRegionsExcludeItem, - Geometry, - KeywordTarget, - Radius, - StoreCatchment, - TargetingOverlayInput, - TravelTime, - Type, +from ..enums import browser_family +from ..enums import device_platform as device_platform_1 +from ..enums import device_type as device_type_1 +from ..enums import distance_unit +from ..enums import match_type as match_type_1 +from ..enums import metro_system +from ..enums import transport_mode as transport_mode_1 +from ..enums import travel_time_unit +from . import collection_list_ref +from . import collection_selection as collection_selection_1 +from . import daypart_target, demographic_targeting_intent +from . import ext as ext_1 +from . import frequency_cap as frequency_cap_1 +from . import ( + geo_metro, + geo_place_area, + locale_tag, + negative_keyword, + package_signal_targeting_groups, ) +from . import placement_selection as placement_selection_1 +from . import postal_area, property_list_ref +from . import signal_targeting as signal_targeting_1 +from . import targeting + + +class GeoCountry(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{2}$'} + + +class GeoCountriesExcludeItem(GeoCountry): + pass + + +class GeoRegion(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{2}-[A-Z0-9]{1,3}$'} + + +class GeoRegionsExcludeItem(GeoRegion): + pass + + +class GeoMetrosExcludeItem(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + system: Annotated[ + metro_system.MetroAreaSystem, + Field(description="Metro area classification system (e.g., 'nielsen_dma', 'uk_itl2')"), + ] + values: Annotated[ + list[str], + Field( + description="Metro codes to exclude within the system (e.g., ['501', '602'] for Nielsen DMAs)", + min_length=1, + ), + ] + + +class StoreCatchment(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + catalog_id: Annotated[ + str, Field(description='Synced store-type catalog ID from sync_catalogs.') + ] + store_ids: Annotated[ + list[str] | None, + Field( + description='Filter to specific stores within the catalog. Omit to target all stores.', + min_length=1, + ), + ] = None + catchment_ids: Annotated[ + list[str] | None, + Field( + description="Catchment zone IDs to target (e.g., 'walk', 'drive'). Omit to target all catchment zones.", + min_length=1, + ), + ] = None + + +class Radius(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + value: Annotated[StrictFloat, Field(description='Radius distance.', gt=0.0)] + unit: Annotated[distance_unit.DistanceUnit, Field(description='Distance unit.')] + + +class Type(StrEnum): + Polygon = 'Polygon' + MultiPolygon = 'MultiPolygon' + + +class Geometry(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Annotated[Type, Field(description='GeoJSON geometry type.')] + coordinates: Annotated[ + list[Any], + Field( + description='GeoJSON coordinates array. For Polygon: array of linear rings. For MultiPolygon: array of polygons.' + ), + ] + + +class KeywordTarget(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + keyword: Annotated[str, Field(description='The keyword to target', min_length=1)] + match_type: match_type_1.MatchType + bid_price: Annotated[ + StrictFloat | None, + Field( + description="Per-keyword bid price, denominated in the same currency as the package's pricing option. Overrides the package-level bid_price for this keyword. Inherits the max_bid interpretation from the pricing option: when max_bid is true, this is the keyword's bid ceiling; when false, this is the exact bid. If omitted, the package bid_price applies.", + ge=0.0, + ), + ] = None + + +class TravelTime(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + value: Annotated[StrictFloat, Field(description='Travel time limit.', ge=1.0)] + unit: travel_time_unit.TravelTimeUnit + + +class GeoProximityItem(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + lat: Annotated[ + StrictFloat | None, + Field( + description='Latitude in decimal degrees (WGS 84). Required for travel_time and radius methods.', + ge=-90.0, + le=90.0, + ), + ] = None + lng: Annotated[ + StrictFloat | None, + Field( + description='Longitude in decimal degrees (WGS 84). Required for travel_time and radius methods.', + ge=-180.0, + le=180.0, + ), + ] = None + label: Annotated[ + str | None, + Field( + description="Human-readable label for this entry (e.g., 'Düsseldorf', 'Heathrow Airport', 'Primary trade area')." + ), + ] = None + travel_time: Annotated[ + TravelTime | None, + Field( + description='Travel time limit for isochrone calculation. The platform resolves this to a geographic boundary based on actual transportation networks.' + ), + ] = None + transport_mode: Annotated[ + transport_mode_1.TransportMode | None, + Field( + description='Transportation mode for isochrone calculation. Required when travel_time is provided.' + ), + ] = None + radius: Annotated[ + Radius | None, + Field( + description='Simple radius from the point. The platform draws a circle of this distance around the coordinates.' + ), + ] = None + geometry: Annotated[ + Geometry | None, + Field( + description='Pre-computed GeoJSON geometry defining the proximity boundary. Use when the buyer has already calculated isochrones (via TravelTime, Mapbox, etc.) or has custom boundaries. When geometry is provided, lat/lng are not required.' + ), + ] = None + ext: ext_1.ExtensionObject | None = None + -# Explicit exports -__all__ = [ - "GeoCountriesExcludeItem", - "GeoCountry", - "GeoMetrosExcludeItem", - "GeoProximityItem", - "GeoRegion", - "GeoRegionsExcludeItem", - "Geometry", - "KeywordTarget", - "Radius", - "StoreCatchment", - "TargetingOverlayInput", - "TravelTime", - "Type", -] +class TargetingOverlayInput(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + geo_countries: Annotated[ + list[GeoCountry] | None, + Field( + description="Restrict delivery to specific countries. ISO 3166-1 alpha-2 codes (e.g., 'US', 'GB', 'DE').", + min_length=1, + ), + ] = None + geo_countries_exclude: Annotated[ + list[GeoCountriesExcludeItem] | None, + Field( + description="Exclude specific countries from delivery. ISO 3166-1 alpha-2 codes (e.g., 'US', 'GB', 'DE').", + min_length=1, + ), + ] = None + geo_regions: Annotated[ + list[GeoRegion] | None, + Field( + description='Restrict delivery to exact canonical ISO 3166-2 subdivisions (states, provinces, regions, departments, or other subdivision categories). Unknown identifiers are invalid. At create or update, sellers MUST reject unsupported identifiers and MUST NOT silently widen, drop, or partially apply the list. During get_products, a seller may instead return a sparse, buyer-reviewable targeting_resolution modification for a valid but unsupported requested outcome. Exact internal translation preserves accepted identifiers in package readback.', + min_length=1, + ), + ] = None + geo_regions_exclude: Annotated[ + list[GeoRegionsExcludeItem] | None, + Field( + description='Exclude exact canonical ISO 3166-2 subdivisions. Support is independent from geo_regions inclusion support. Unknown identifiers and values also present in geo_regions are invalid. At create or update, sellers MUST reject unsupported identifiers and partial application; during get_products, a seller may instead return a sparse, buyer-reviewable targeting_resolution modification for a valid but unsupported requested outcome.', + min_length=1, + ), + ] = None + geo_metros: Annotated[ + list[geo_metro.GeoMetro] | None, + Field( + description='Restrict delivery to specific metro areas. Each entry specifies the classification system and target values. Seller must declare supported systems in get_adcp_capabilities.', + min_length=1, + title='Targeting Geo Metros', + ), + ] = None + geo_metros_exclude: Annotated[ + list[GeoMetrosExcludeItem] | None, + Field( + description='Exclude specific metro areas from delivery. Each entry specifies the classification system and excluded values. Seller must declare supported systems in get_adcp_capabilities.', + min_length=1, + ), + ] = None + geo_postal_areas: Annotated[ + list[postal_area.PostalArea] | None, + Field( + description='Restrict delivery to specific postal areas. Prefer the native country + postal system form. The deprecated legacy country-fused postal-system tokens remain accepted for compatibility. Seller must declare supported systems in get_adcp_capabilities.', + min_length=1, + ), + ] = None + geo_postal_areas_exclude: Annotated[ + list[postal_area.PostalArea] | None, + Field( + description='Exclude specific postal areas from delivery. Prefer the native country + postal system form. The deprecated legacy country-fused postal-system tokens remain accepted for compatibility. Seller must declare supported systems in get_adcp_capabilities.', + min_length=1, + ), + ] = None + geo_places: Annotated[ + list[geo_place_area.GeographicPlaceArea] | None, + Field( + description='Restrict delivery to catalog-backed named places. Values MUST be stable identifiers in the declared system, not display names. Sellers must declare supported systems, countries, and place types in get_adcp_capabilities and reject unsupported entries rather than silently dropping them.', + min_length=1, + ), + ] = None + geo_places_exclude: Annotated[ + list[geo_place_area.GeographicPlaceArea] | None, + Field( + description='Exclude catalog-backed named places. Uses the same identifier-based shape as geo_places. Sellers MUST reject overlap with geo_places for the same country, system, place_type, and value.', + min_length=1, + ), + ] = None + daypart_targets: Annotated[ + list[daypart_target.DaypartTarget] | None, + Field( + description='Restrict delivery to specific time windows. Each entry specifies days of week, an hour range, and an optional timezone that defaults to inventory_local. A concrete IANA zone uses one shared civil-time clock, while inventory_local evaluates each inventory unit in its seller-assigned local timezone. Entries are independent and MAY use different clocks.', + min_length=1, + ), + ] = None + axe_include_segment: Annotated[ + str | None, + Field( + deprecated=True, + description='Deprecated: Use TMP provider fields instead. AXE segment ID to include for targeting.', + ), + ] = None + axe_exclude_segment: Annotated[ + str | None, + Field( + deprecated=True, + description='Deprecated: Use TMP provider fields instead. AXE segment ID to exclude from targeting.', + ), + ] = None + audience_include: Annotated[ + list[str] | None, + Field( + description='Restrict delivery to members of these first-party CRM audiences. Only users present in the uploaded lists are eligible. References audience_id values from sync_audiences on the same seller account — audience IDs are not portable across sellers. Not for lookalike expansion — express that intent in the campaign brief. Seller must declare support in get_adcp_capabilities.', + min_length=1, + ), + ] = None + audience_exclude: Annotated[ + list[str] | None, + Field( + description='Suppress delivery to members of these first-party CRM audiences. Matched users are excluded regardless of other targeting. References audience_id values from sync_audiences on the same seller account — audience IDs are not portable across sellers. Seller must declare support in get_adcp_capabilities.', + min_length=1, + ), + ] = None + signal_targeting_groups: package_signal_targeting_groups.PackageSignalTargetingGroups | None = ( + None + ) + signal_targeting: Annotated[ + list[signal_targeting_1.SignalTargeting] | None, + Field( + deprecated=True, + description='DEPRECATED. Use signal_targeting_groups for package-level signal targeting. Legacy flat signal_targeting remains accepted during the SignalRef migration window but cannot express grouped include/exclude composition or product-scoped pricing.', + min_length=1, + ), + ] = None + demographics: demographic_targeting_intent.DemographicTargetingIntent | None = None + frequency_cap: frequency_cap_1.FrequencyCap | None = None + property_list: property_list_ref.PropertyListReference | None = None + property_list_exclude: property_list_ref.PropertyListReference | None = None + collection_list: collection_list_ref.CollectionListReference | None = None + collection_list_exclude: collection_list_ref.CollectionListReference | None = None + placement_selection: placement_selection_1.PlacementSelection | None = None + collection_selection: collection_selection_1.CollectionSelection | None = None + age_restriction: targeting.AgeRestriction | None = None + device_platform: Annotated[ + list[device_platform_1.DevicePlatform] | None, + Field( + description='Restrict to specific platforms. Use for technical compatibility (app only works on iOS). Values from Sec-CH-UA-Platform standard, extended for CTV.', + min_length=1, + ), + ] = None + device_platform_exclude: Annotated[ + list[device_platform_1.DevicePlatform] | None, + Field( + description='Exclude specific operating-system platforms from delivery. When a platform appears in both device_platform and device_platform_exclude, exclusion wins. Sellers MUST reject a request they cannot enforce rather than silently dropping the exclusion.', + min_length=1, + ), + ] = None + device_type: Annotated[ + list[device_type_1.DeviceType] | None, + Field( + description='Restrict to specific device form factors. Use for campaigns targeting hardware categories rather than operating systems (e.g., mobile-only promotions, CTV campaigns).', + min_length=1, + ), + ] = None + device_type_exclude: Annotated[ + list[device_type_1.DeviceType] | None, + Field( + description='Exclude specific device form factors from delivery (e.g., exclude CTV for app-install campaigns).', + min_length=1, + ), + ] = None + browser: Annotated[ + list[browser_family.BrowserFamily] | None, + Field( + description='Restrict delivery to specific canonical browser families in the impression delivery and rendering environment, not the post-click landing-page browser. Values MUST NOT be inferred solely from operating system, device, web/mobile-web inventory, or placement. Values in this array use OR semantics. When browser is supplied, families not listed are ineligible: other includes a seller-recognized family that is not explicitly enumerated, while unknown includes a browser the seller cannot classify into a recognized family. When the same family appears in browser and browser_exclude, exclusion wins. Browser and device constraints intersect; a seller that cannot enforce the exact combination MUST exclude or explicitly reconfigure the product during discovery and MUST reject it at create or update rather than silently widening delivery. Browser versions and seller-native IDs are intentionally unsupported.', + min_length=1, + ), + ] = None + browser_exclude: Annotated[ + list[browser_family.BrowserFamily] | None, + Field( + description='Exclude specific canonical browser families from delivery. other excludes seller-recognized families that are not explicitly enumerated; unknown excludes browsers the seller cannot classify into a recognized family. When the same family appears in browser and browser_exclude, exclusion wins. Sellers MUST reject a request they cannot enforce rather than silently dropping the exclusion.', + min_length=1, + ), + ] = None + store_catchments: Annotated[ + list[StoreCatchment] | None, + Field( + description='Target users within store catchment areas from a synced store catalog. Each entry references a store-type catalog and optionally narrows to specific stores or catchment zones.', + min_length=1, + ), + ] = None + geo_proximity: Annotated[ + list[GeoProximityItem] | None, + Field( + description='Target users within travel time, distance, or a custom boundary around arbitrary geographic points. Multiple entries use OR semantics — a user within range of any listed point is eligible. For campaigns targeting 10+ locations, consider using store_catchments with a location catalog instead. Seller must declare support in get_adcp_capabilities.', + min_length=1, + ), + ] = None + language: Annotated[ + list[locale_tag.LanguageTag] | None, + Field( + description="Restrict to users with specific language preferences using canonical BCP 47 language ranges. Each buyer range is evaluated against a user's language-preference tag with RFC 4647 section 3.3.1 Basic Filtering: 'fr' matches 'fr', 'fr-CA', and 'fr-FR', while 'fr-CA' matches 'fr-CA' and more-specific descendants but not 'fr' or 'fr-FR'. Values use OR logic.", + min_length=1, + title='Targeting Languages', + ), + ] = None + keyword_targets: Annotated[ + list[KeywordTarget] | None, + Field( + description='Keyword targeting for search and retail media platforms. Restricts delivery to queries matching the specified keywords. Each keyword is identified by the tuple (keyword, match_type) — the same keyword string with different match types are distinct targets. Sellers SHOULD reject duplicate (keyword, match_type) pairs within a single request. Seller must declare support in get_adcp_capabilities.', + min_length=1, + title='Targeting Keywords', + ), + ] = None + negative_keywords: Annotated[ + list[negative_keyword.NegativeKeyword] | None, + Field( + description='Keywords to exclude from delivery. Queries matching these keywords will not trigger the ad. Each negative keyword is identified by the tuple (keyword, match_type). Seller must declare support in get_adcp_capabilities.', + min_length=1, + title='Targeting Negative Keywords', + ), + ] = None diff --git a/src/adcp/types/domains/core/targeting_modification.py b/src/adcp/types/domains/core/targeting_modification.py index b1716fd37..086fbe0e2 100644 --- a/src/adcp/types/domains/core/targeting_modification.py +++ b/src/adcp/types/domains/core/targeting_modification.py @@ -1,30 +1,99 @@ -"""Types declared by the AdCP ``core/targeting_modification`` schema. +# generated by datamodel-codegen: +# filename: core/targeting_modification.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.targeting_modification import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field + +from . import ext as ext_1 + + +class TargetingModification1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + operation: Literal['replace'] = 'replace' + path: Annotated[ + str, + Field( + description='RFC 6901 JSON Pointer to one complete targeting value relative to targeting_overlay, for example /demographics/age. Array indexes are forbidden by protocol semantics even if syntactically valid JSON Pointer; target a stable whole dimension instead.', + pattern='^(?:/(?:[^~/]|~[01])*)+$', + ), + ] + applied: Annotated[ + Any, + Field( + description='Complete replacement value. It MUST validate against targeting.json at path; the seller then validates the complete effective overlay. This operation may narrow or broaden only as an explicit buyer-visible proposal.' + ), + ] + reason: Annotated[str, Field(min_length=1)] + ext: ext_1.ExtensionObject | None = None + + +class Path(StrEnum): + field_geo_countries = '/geo_countries' + field_geo_countries_exclude = '/geo_countries_exclude' + field_geo_regions = '/geo_regions' + field_geo_regions_exclude = '/geo_regions_exclude' + field_geo_postal_areas = '/geo_postal_areas' + field_geo_postal_areas_exclude = '/geo_postal_areas_exclude' + field_audience_include = '/audience_include' + field_audience_exclude = '/audience_exclude' + field_device_platform = '/device_platform' + field_device_platform_exclude = '/device_platform_exclude' + field_device_type = '/device_type' + field_device_type_exclude = '/device_type_exclude' + field_browser = '/browser' + field_browser_exclude = '/browser_exclude' + field_language = '/language' + + +class Selector(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + country: Annotated[str, Field(pattern='^[A-Z]{2}$')] + system: Annotated[str, Field(min_length=1)] + + +class TargetingModification2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + operation: Literal['remove_values'] = 'remove_values' + path: Annotated[ + Path, + Field( + description='Supported targeting field with string-set semantics. Postal-area paths identify their group with selector; direct top-level sets omit selector.' + ), + ] + selector: Annotated[ + Selector | None, + Field( + description='Stable identity of exactly one postal-area group in the requested array. Required only for postal-area paths. Zero or multiple matches make the modification invalid.' + ), + ] = None + values: Annotated[ + list[str], + Field( + description='Values removed from the requested set. Values absent from that set are invalid rather than no-ops.', + min_length=1, + ), + ] + reason: Annotated[str, Field(min_length=1)] + ext: ext_1.ExtensionObject | None = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.targeting_modification import ( - Path, - Selector, - TargetingModification, - TargetingModification1, - TargetingModification2, -) - -# Explicit exports -__all__ = [ - "Path", - "Selector", - "TargetingModification", - "TargetingModification1", - "TargetingModification2", +TargetingModification = Annotated[ + TargetingModification1 | TargetingModification2, + Field( + description='Sparse, buyer-reviewable change from targeting requested in get_products to targeting bound to a returned configured product. Entries are applied in array order to the original targeting_overlay; after every entry, the complete result MUST validate against targeting.json. A seller MUST NOT emit two replace operations for the same path or combine replace and remove_values on the same path. Selecting the configured product_id accepts the ordered result.', + discriminator='operation', + title='Targeting Modification', + ), ] diff --git a/src/adcp/types/domains/core/targeting_overlay_requirements.py b/src/adcp/types/domains/core/targeting_overlay_requirements.py index 84fbe06ed..9ae9b0230 100644 --- a/src/adcp/types/domains/core/targeting_overlay_requirements.py +++ b/src/adcp/types/domains/core/targeting_overlay_requirements.py @@ -1,44 +1,168 @@ -"""Types declared by the AdCP ``core/targeting_overlay_requirements`` schema. +# generated by datamodel-codegen: +# filename: core/targeting_overlay_requirements.json +# timestamp: 2026-10-03T00:35:15+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.targeting_overlay_requirements import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Any, Annotated, Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, RootModel -from adcp.types.generated_poc.core.targeting_overlay_requirements import ( - BrowserRequirement, - BrowserRequirement1, - DaypartRequirement, - DaypartRequirement1, - Demographics, - GeoProximity, - KeywordRequirement, - KeywordRequirement1, - MetroRequirement, - MetroRequirement1, - Required, - TargetingOverlayRequirements, +from ..enums import browser_family, daypart_timezone_mode, match_type, metro_system, transport_mode +from . import ext as ext_1 +from . import ( + frequency_cap_requirements, + geo_place_requirement, + geo_region_requirement, + iana_timezone, + positive_postal_area_support, ) -# Explicit exports -__all__ = [ - "BrowserRequirement", - "BrowserRequirement1", - "DaypartRequirement", - "DaypartRequirement1", - "Demographics", - "GeoProximity", - "KeywordRequirement", - "KeywordRequirement1", - "MetroRequirement", - "MetroRequirement1", - "Required", - "TargetingOverlayRequirements", -] + +class Required(RootModel[Literal[True]]): + root: Literal[True] + + +class MetroRequirement1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + systems: Annotated[list[metro_system.MetroAreaSystem], Field(min_length=1)] + + +class MetroRequirement(RootModel[Required | MetroRequirement1]): + root: Required | MetroRequirement1 + def __getattr__(self, name: str) -> Any: + """Proxy attribute access to the wrapped type.""" + if name.startswith('_'): + raise AttributeError(name) + return getattr(self.root, name) + + + +class KeywordRequirement1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + supported_match_types: Annotated[list[match_type.MatchType], Field(min_length=1)] + + +class KeywordRequirement(RootModel[Required | KeywordRequirement1]): + root: Required | KeywordRequirement1 + def __getattr__(self, name: str) -> Any: + """Proxy attribute access to the wrapped type.""" + if name.startswith('_'): + raise AttributeError(name) + return getattr(self.root, name) + + + +class BrowserRequirement1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + families: Annotated[list[browser_family.BrowserFamily], Field(min_length=1)] + + +class BrowserRequirement(RootModel[Required | BrowserRequirement1]): + root: Required | BrowserRequirement1 + def __getattr__(self, name: str) -> Any: + """Proxy attribute access to the wrapped type.""" + if name.startswith('_'): + raise AttributeError(name) + return getattr(self.root, name) + + + +class DaypartRequirement1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + timezone_modes: Annotated[list[daypart_timezone_mode.DaypartTimezoneMode], Field(min_length=1)] + iana_timezones: Annotated[ + list[iana_timezone.IanaTimezoneIdentifier] | None, + Field( + description='Exact concrete IANA timezone identifiers that must remain selectable after discovery. Valid only when timezone_modes includes iana.', + min_length=1, + ), + ] = None + + +class DaypartRequirement(RootModel[Required | DaypartRequirement1]): + root: Required | DaypartRequirement1 + def __getattr__(self, name: str) -> Any: + """Proxy attribute access to the wrapped type.""" + if name.startswith('_'): + raise AttributeError(name) + return getattr(self.root, name) + + + +class GeoProximity(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + radius: Required | None = None + travel_time: Required | None = None + geometry: Required | None = None + transport_modes: Annotated[list[transport_mode.TransportMode] | None, Field(min_length=1)] = ( + None + ) + + +class Demographics(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + age: Required + + +class TargetingOverlayRequirements(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + geo_countries: Required | None = None + geo_countries_exclude: Required | None = None + geo_regions: Required | geo_region_requirement.GeographicRegionRequirement | None = None + geo_regions_exclude: Required | geo_region_requirement.GeographicRegionRequirement | None = None + geo_metros: MetroRequirement | None = None + geo_metros_exclude: MetroRequirement | None = None + geo_places: geo_place_requirement.GeographicPlaceRequirement | None = None + geo_places_exclude: geo_place_requirement.GeographicPlaceRequirement | None = None + geo_postal_areas: Required | positive_postal_area_support.PositivePostalAreaSupport | None = ( + None + ) + geo_postal_areas_exclude: ( + Required | positive_postal_area_support.PositivePostalAreaSupport | None + ) = None + geo_proximity: Required | GeoProximity | None = None + daypart_targets: DaypartRequirement | None = None + audience_include: Required | None = None + audience_exclude: Required | None = None + signal_targeting_groups: Required | None = None + demographics: Required | Demographics | None = None + frequency_cap: Required | None = None + frequency_cap_support: Annotated[ + frequency_cap_requirements.FrequencyCapRequirements | None, + Field( + description='Minimum structured package-cap support. Matches a broad legacy frequency_cap: true or a containing frequency_cap_support object. Mutually exclusive with a frequency_cap: true requirement, which would exclude every constrained product.' + ), + ] = None + property_list: Required | None = None + property_list_exclude: Required | None = None + collection_list: Required | None = None + collection_list_exclude: Required | None = None + placement_selection: Required | None = None + age_restriction: Required | None = None + device_platform: Required | None = None + device_platform_exclude: Required | None = None + device_type: Required | None = None + device_type_exclude: Required | None = None + browser: BrowserRequirement | None = None + browser_exclude: BrowserRequirement | None = None + store_catchments: Required | None = None + language: Required | None = None + keyword_targets: KeywordRequirement | None = None + negative_keywords: KeywordRequirement | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/targeting_overlay_support.py b/src/adcp/types/domains/core/targeting_overlay_support.py index 43f2ba87e..045645c96 100644 --- a/src/adcp/types/domains/core/targeting_overlay_support.py +++ b/src/adcp/types/domains/core/targeting_overlay_support.py @@ -1,58 +1,278 @@ -"""Types declared by the AdCP ``core/targeting_overlay_support`` schema. +# generated by datamodel-codegen: +# filename: core/targeting_overlay_support.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.targeting_overlay_support import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Any, Annotated, Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel, SchemaInt +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field, RootModel, StringConstraints -from adcp.types.generated_poc.core.targeting_overlay_support import ( - BrowserSupport, - BrowserSupport1, - CountrySupport, - CountrySupport1, - DaypartSupport, - DaypartSupport1, - Demographics, - GeoProximity, - IanaTimezones, - KeywordSupport, - KeywordSupport1, - MetroSupport, - MetroSupport1, - PlaceCatalogSupport, - PlaceSupport, - PlacementSelection, - Supported, - SystemVersion, - TargetingOverlaySupport, +from ..enums import browser_family, daypart_timezone_mode, match_type, metro_system, transport_mode +from . import ext as ext_1 +from . import ( + frequency_cap_constraints, + geo_place_system, + geo_place_type, + geo_region_support, + iana_timezone, + positive_postal_area_support, ) -# Explicit exports -__all__ = [ - "BrowserSupport", - "BrowserSupport1", - "CountrySupport", - "CountrySupport1", - "DaypartSupport", - "DaypartSupport1", - "Demographics", - "GeoProximity", - "IanaTimezones", - "KeywordSupport", - "KeywordSupport1", - "MetroSupport", - "MetroSupport1", - "PlaceCatalogSupport", - "PlaceSupport", - "PlacementSelection", - "Supported", - "SystemVersion", - "TargetingOverlaySupport", -] + +class PlacementSelection(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + max_values_per_package: Annotated[SchemaInt | None, Field(ge=1)] = None + max_packages: Annotated[SchemaInt | None, Field(ge=1)] = None + ext: ext_1.ExtensionObject | None = None + + +class Supported(RootModel[Literal[True]]): + root: Literal[True] + + +class CountrySupport1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + max_values_per_package: Annotated[ + SchemaInt, + Field( + description='Maximum number of country values accepted in this targeting field on one package.', + ge=1, + ), + ] + ext: ext_1.ExtensionObject | None = None + + +class CountrySupport(RootModel[Supported | CountrySupport1]): + root: Supported | CountrySupport1 + def __getattr__(self, name: str) -> Any: + """Proxy attribute access to the wrapped type.""" + if name.startswith('_'): + raise AttributeError(name) + return getattr(self.root, name) + + + +class MetroSupport1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + systems: Annotated[list[metro_system.MetroAreaSystem], Field(min_length=1)] + max_values_per_package: Annotated[SchemaInt | None, Field(ge=1)] = None + max_packages: Annotated[ + SchemaInt | None, + Field( + description='Optional maximum number of independently targeted packages the seller will create from this configured product.', + ge=1, + ), + ] = None + ext: ext_1.ExtensionObject | None = None + + +class MetroSupport(RootModel[Supported | MetroSupport1]): + root: Supported | MetroSupport1 + def __getattr__(self, name: str) -> Any: + """Proxy attribute access to the wrapped type.""" + if name.startswith('_'): + raise AttributeError(name) + return getattr(self.root, name) + + + +class SystemVersion(ScalarStr): + __slots__ = () + _constraints = {'min_length': 1} + + +class PlaceCatalogSupport(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + countries: Annotated[ + dict[Annotated[str, StringConstraints(pattern=r'^[A-Z]{2}$')], list[geo_place_type.GeographicPlaceType]], + Field(min_length=1), + ] + current_version: Annotated[ + str, + Field( + description='Version applied when a later package overlay omits system_version. Must be present in system_versions.', + min_length=1, + ), + ] + system_versions: Annotated[ + list[SystemVersion], + Field( + description='Exact catalog versions selectable on later package overlays.', min_length=1 + ), + ] + ext: ext_1.ExtensionObject | None = None + + +class PlaceSupport(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + systems: Annotated[ + dict[geo_place_system.GeographicPlaceIdentifierSystem, PlaceCatalogSupport], + Field(min_length=1), + ] + max_values_per_package: Annotated[SchemaInt | None, Field(ge=1)] = None + max_packages: Annotated[ + SchemaInt | None, + Field( + description='Optional maximum number of independently place-targeted packages the seller will create from this configured product.', + ge=1, + ), + ] = None + ext: ext_1.ExtensionObject | None = None + + +class KeywordSupport1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + supported_match_types: Annotated[list[match_type.MatchType], Field(min_length=1)] + ext: ext_1.ExtensionObject | None = None + + +class KeywordSupport(RootModel[Supported | KeywordSupport1]): + root: Supported | KeywordSupport1 + def __getattr__(self, name: str) -> Any: + """Proxy attribute access to the wrapped type.""" + if name.startswith('_'): + raise AttributeError(name) + return getattr(self.root, name) + + + +class BrowserSupport1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + families: Annotated[list[browser_family.BrowserFamily], Field(min_length=1)] + ext: ext_1.ExtensionObject | None = None + + +class BrowserSupport(RootModel[Supported | BrowserSupport1]): + root: Supported | BrowserSupport1 + def __getattr__(self, name: str) -> Any: + """Proxy attribute access to the wrapped type.""" + if name.startswith('_'): + raise AttributeError(name) + return getattr(self.root, name) + + + +class IanaTimezones(RootModel[list[iana_timezone.IanaTimezoneIdentifier]]): + root: Annotated[ + list[iana_timezone.IanaTimezoneIdentifier], + Field( + description="Concrete IANA timezone identifiers accepted by this product. true means every identifier valid in the seller's supported IANA TZDB; an array is the exact supported subset. Required when timezone_modes includes iana and forbidden otherwise.", + min_length=1, + ), + ] + + +class DaypartSupport1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + timezone_modes: Annotated[list[daypart_timezone_mode.DaypartTimezoneMode], Field(min_length=1)] + iana_timezones: Annotated[ + Literal[True] | IanaTimezones | None, + Field( + description="Concrete IANA timezone identifiers accepted by this product. true means every identifier valid in the seller's supported IANA TZDB; an array is the exact supported subset. Required when timezone_modes includes iana and forbidden otherwise." + ), + ] = None + ext: ext_1.ExtensionObject | None = None + + +class DaypartSupport(RootModel[Literal[True] | DaypartSupport1]): + root: Literal[True] | DaypartSupport1 + def __getattr__(self, name: str) -> Any: + """Proxy attribute access to the wrapped type.""" + if name.startswith('_'): + raise AttributeError(name) + return getattr(self.root, name) + + + +class GeoProximity(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + radius: Supported | None = None + travel_time: Supported | None = None + geometry: Supported | None = None + transport_modes: Annotated[list[transport_mode.TransportMode] | None, Field(min_length=1)] = ( + None + ) + max_values_per_package: Annotated[ + SchemaInt | None, + Field(description='Maximum number of proximity entries accepted on one package.', ge=1), + ] = None + ext: ext_1.ExtensionObject | None = None + + +class Demographics(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + age: Supported + ext: ext_1.ExtensionObject | None = None + + +class TargetingOverlaySupport(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + geo_countries: CountrySupport | None = None + geo_countries_exclude: CountrySupport | None = None + geo_regions: Supported | geo_region_support.GeographicRegionSupport | None = None + geo_regions_exclude: Supported | geo_region_support.GeographicRegionSupport | None = None + geo_metros: MetroSupport | None = None + geo_metros_exclude: MetroSupport | None = None + geo_places: PlaceSupport | None = None + geo_places_exclude: PlaceSupport | None = None + geo_postal_areas: Supported | positive_postal_area_support.PositivePostalAreaSupport | None = ( + None + ) + geo_postal_areas_exclude: ( + Supported | positive_postal_area_support.PositivePostalAreaSupport | None + ) = None + geo_proximity: Supported | GeoProximity | None = None + daypart_targets: DaypartSupport | None = None + audience_include: Supported | None = None + audience_exclude: Supported | None = None + signal_targeting_groups: Supported | None = None + demographics: Supported | Demographics | None = None + frequency_cap: Supported | None = None + frequency_cap_support: Annotated[ + frequency_cap_constraints.FrequencyCapConstraints | None, + Field( + description='Independently positive constrained package-cap support. Mutually exclusive with legacy frequency_cap: a product declares one form or the other.' + ), + ] = None + property_list: Supported | None = None + property_list_exclude: Supported | None = None + collection_list: Supported | None = None + collection_list_exclude: Supported | None = None + placement_selection: Supported | PlacementSelection | None = None + age_restriction: Supported | None = None + device_platform: Supported | None = None + device_platform_exclude: Supported | None = None + device_type: Supported | None = None + device_type_exclude: Supported | None = None + browser: BrowserSupport | None = None + browser_exclude: BrowserSupport | None = None + store_catchments: Supported | None = None + language: Supported | None = None + keyword_targets: KeywordSupport | None = None + negative_keywords: KeywordSupport | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/targeting_unknown_age_eligibility_constraint.py b/src/adcp/types/domains/core/targeting_unknown_age_eligibility_constraint.py index ff2c79b21..b28b93d9e 100644 --- a/src/adcp/types/domains/core/targeting_unknown_age_eligibility_constraint.py +++ b/src/adcp/types/domains/core/targeting_unknown_age_eligibility_constraint.py @@ -1,20 +1,19 @@ -"""Types declared by the AdCP ``core/targeting_unknown_age_eligibility_constraint`` schema. +# generated by datamodel-codegen: +# filename: core/targeting_unknown_age_eligibility_constraint.json +# timestamp: 2026-09-14T14:16:02+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.targeting_unknown_age_eligibility_constraint import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Any -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import Field, RootModel -from adcp.types.generated_poc.core.targeting_unknown_age_eligibility_constraint import ( - TargetingUnknownAgeEligibilityConstraint, -) -# Explicit exports -__all__ = ["TargetingUnknownAgeEligibilityConstraint"] +class TargetingUnknownAgeEligibilityConstraint(RootModel[Any]): + root: Annotated[ + Any, + Field( + description='Unknown-age delivery cannot satisfy a minimum-age eligibility policy. When demographic audience targeting and age_restriction are both present, include_unknown must be false.', + title='Targeting Unknown Age Eligibility Constraint', + ), + ] diff --git a/src/adcp/types/domains/core/targeting_verified_age_basis_constraint.py b/src/adcp/types/domains/core/targeting_verified_age_basis_constraint.py index 09187d475..61a615b76 100644 --- a/src/adcp/types/domains/core/targeting_verified_age_basis_constraint.py +++ b/src/adcp/types/domains/core/targeting_verified_age_basis_constraint.py @@ -1,20 +1,19 @@ -"""Types declared by the AdCP ``core/targeting_verified_age_basis_constraint`` schema. +# generated by datamodel-codegen: +# filename: core/targeting_verified_age_basis_constraint.json +# timestamp: 2026-09-14T14:16:02+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.targeting_verified_age_basis_constraint import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Any -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import Field, RootModel -from adcp.types.generated_poc.core.targeting_verified_age_basis_constraint import ( - TargetingVerifiedAgeBasisConstraint, -) -# Explicit exports -__all__ = ["TargetingVerifiedAgeBasisConstraint"] +class TargetingVerifiedAgeBasisConstraint(RootModel[Any]): + root: Annotated[ + Any, + Field( + description='A legal verification requirement always narrows demographic targeting. When the buyer supplies accepted_bases and age_restriction requires verification, verified must be accepted; otherwise the constraints have an empty intersection and the request is invalid.', + title='Targeting Verified Age Basis Constraint', + ), + ] diff --git a/src/adcp/types/domains/core/tasks_get_request.py b/src/adcp/types/domains/core/tasks_get_request.py index 6f933e90d..59654efce 100644 --- a/src/adcp/types/domains/core/tasks_get_request.py +++ b/src/adcp/types/domains/core/tasks_get_request.py @@ -1,18 +1,41 @@ -"""Types declared by the AdCP ``core/tasks_get_request`` schema. +# generated by datamodel-codegen: +# filename: core/tasks_get_request.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.tasks_get_request import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import account_ref +from . import context as context_1 +from . import ext as ext_1 +from .version_envelope import AdcpVersionEnvelope -from adcp.types.generated_poc.core.tasks_get_request import TasksGetRequest -# Explicit exports -__all__ = ["TasksGetRequest"] +class TasksGetRequest(AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + task_id: Annotated[str, Field(description='Unique identifier of the task to retrieve')] + account: Annotated[ + account_ref.AccountReference | None, + Field( + description='Account scope for the task lookup. Sellers MUST return REFERENCE_NOT_FOUND for a task_id that exists only under a different account or principal. When omitted, the seller MAY use the credential-bound singleton account, but multi-account credentials SHOULD require an explicit account.' + ), + ] = None + include_history: Annotated[ + StrictBool | None, + Field( + description='Include full conversation history for this task (may increase response size)' + ), + ] = False + include_result: Annotated[ + StrictBool | None, + Field( + description="Include the task's canonical terminal result payload when one exists. Defaults to false for lightweight status-only polls. When true, sellers MUST include result for completed, failed, or rejected terminal tasks when that task produced a terminal artifact; canceled tasks may have no result. The legacy singular error field remains a convenience for failed tasks but does not replace the canonical terminal result." + ), + ] = False + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/tasks_get_response.py b/src/adcp/types/domains/core/tasks_get_response.py index 02810269e..99e8c8f85 100644 --- a/src/adcp/types/domains/core/tasks_get_response.py +++ b/src/adcp/types/domains/core/tasks_get_response.py @@ -1,25 +1,122 @@ -"""Types declared by the AdCP ``core/tasks_get_response`` schema. +# generated by datamodel-codegen: +# filename: core/tasks_get_response.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.tasks_get_response import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat + +from ..enums import adcp_protocol, task_status +from ..enums import task_type as task_type_1 +from . import context as context_1 +from . import ext as ext_1 +from .protocol_envelope import ProtocolEnvelope +from .version_envelope import AdcpVersionEnvelope + + +class Progress(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + percentage: Annotated[ + StrictFloat | None, Field(description='Completion percentage (0-100)', ge=0.0, le=100.0) + ] = None + current_step: Annotated[ + str | None, Field(description='Current step or phase of the operation') + ] = None + total_steps: Annotated[ + SchemaInt | None, Field(description='Total number of steps in the operation', ge=1) + ] = None + step_number: Annotated[SchemaInt | None, Field(description='Current step number', ge=1)] = None + + +class Details(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + protocol: Annotated[ + adcp_protocol.AdcpProtocol | None, Field(description='AdCP protocol where error occurred') + ] = None + operation: Annotated[str | None, Field(description='Specific operation that failed')] = None + specific_context: Annotated[ + dict[str, Any] | None, Field(description='Domain-specific error context') + ] = None + + +class Error(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + code: Annotated[str, Field(description='Error code for programmatic handling')] + message: Annotated[str, Field(description='Detailed error message')] + details: Annotated[Details | None, Field(description='Additional error context')] = None + + +class Type(StrEnum): + request = 'request' + response = 'response' + + +class HistoryItem(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + timestamp: Annotated[AwareDatetime, Field(description='When this exchange occurred (ISO 8601)')] + type: Annotated[ + Type, Field(description='Whether this was a request from client or response from server') + ] + data: Annotated[dict[str, Any], Field(description='The full request or response payload')] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.tasks_get_response import ( - Details, - Error, - HistoryItem, - Progress, - TasksGetResponse, - Type, -) - -# Explicit exports -__all__ = ["Details", "Error", "HistoryItem", "Progress", "TasksGetResponse", "Type"] +class TasksGetResponse(AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + task_id: Annotated[str, Field(description='Unique identifier for this task')] + task_type: Annotated[task_type_1.TaskType, Field(description='Type of AdCP operation')] + protocol: Annotated[ + adcp_protocol.AdcpProtocol, Field(description='AdCP protocol this task belongs to') + ] + status: Annotated[task_status.TaskStatus, Field(description='Current task status')] + created_at: Annotated[ + AwareDatetime, Field(description='When the task was initially created (ISO 8601)') + ] + updated_at: Annotated[ + AwareDatetime, Field(description='When the task was last updated (ISO 8601)') + ] + completed_at: Annotated[ + AwareDatetime | None, + Field( + description='When the task completed (ISO 8601, only for completed/failed/canceled tasks)' + ), + ] = None + has_webhook: Annotated[ + StrictBool | None, Field(description='Whether this task has webhook configuration') + ] = None + progress: Annotated[ + Progress | None, Field(description='Progress information for long-running tasks') + ] = None + error: Annotated[ + Error | None, + Field( + description='Convenience summary for failed tasks. When include_result was true and the canonical terminal result is also present, this error MUST agree with the canonical fatal error in result. A legacy poll carrying only this singular summary proves failure status but not equivalence to a richer terminal webhook artifact.' + ), + ] = None + history: Annotated[ + list[HistoryItem] | None, + Field( + description='Complete conversation history for this task (only included if include_history was true in request)' + ), + ] = None + result: Annotated[ + dict[str, Any] | None, + Field( + description='Canonical task-specific terminal payload. Present when include_result was true and a completed, failed, or rejected task produced a terminal artifact; canceled tasks may omit it. For failed tasks, the singular error field is a convenience summary and MUST agree with the canonical fatal error represented here. Consumers and sellers MUST resolve and validate the exact schema through manifest.task_result_resolution: use terminal_schema_overrides[task_type] when present, otherwise tools[task_type].response_schema. The polling envelope keeps this field generic so tasks/get does not embed every task response schema.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/tasks_list_request.py b/src/adcp/types/domains/core/tasks_list_request.py index 71e6ff754..2cb1b573d 100644 --- a/src/adcp/types/domains/core/tasks_list_request.py +++ b/src/adcp/types/domains/core/tasks_list_request.py @@ -1,18 +1,117 @@ -"""Types declared by the AdCP ``core/tasks_list_request`` schema. +# generated by datamodel-codegen: +# filename: core/tasks_list_request.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.tasks_list_request import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import adcp_protocol, sort_direction, task_status +from ..enums import task_type as task_type_1 +from . import account_ref +from . import context as context_1 +from . import ext as ext_1 +from . import pagination_request +from .version_envelope import AdcpVersionEnvelope + + +class Filters(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + protocol: Annotated[ + adcp_protocol.AdcpProtocol | None, Field(description='Filter by single AdCP protocol') + ] = None + protocols: Annotated[ + list[adcp_protocol.AdcpProtocol] | None, + Field(description='Filter by multiple AdCP protocols', min_length=1), + ] = None + status: Annotated[ + task_status.TaskStatus | None, Field(description='Filter by single task status') + ] = None + statuses: Annotated[ + list[task_status.TaskStatus] | None, + Field(description='Filter by multiple task statuses', min_length=1), + ] = None + task_type: Annotated[ + task_type_1.TaskType | None, Field(description='Filter by single task type') + ] = None + task_types: Annotated[ + list[task_type_1.TaskType] | None, + Field(description='Filter by multiple task types', min_length=1), + ] = None + created_after: Annotated[ + AwareDatetime | None, Field(description='Filter tasks created after this date (ISO 8601)') + ] = None + created_before: Annotated[ + AwareDatetime | None, Field(description='Filter tasks created before this date (ISO 8601)') + ] = None + updated_after: Annotated[ + AwareDatetime | None, + Field(description='Filter tasks last updated after this date (ISO 8601)'), + ] = None + updated_before: Annotated[ + AwareDatetime | None, + Field(description='Filter tasks last updated before this date (ISO 8601)'), + ] = None + task_ids: Annotated[ + list[str] | None, + Field(description='Filter by specific task IDs', max_length=100, min_length=1), + ] = None + context_contains: Annotated[ + str | None, + Field( + description='Filter tasks where context contains this text (searches media_buy_id, signal_id, etc.)' + ), + ] = None + has_webhook: Annotated[ + StrictBool | None, + Field(description='Filter tasks that have webhook configuration when true'), + ] = None + + +class Field1(StrEnum): + created_at = 'created_at' + updated_at = 'updated_at' + status = 'status' + task_type = 'task_type' + protocol = 'protocol' + + +class Sort(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + field: Annotated[Field1 | None, Field(description='Field to sort by')] = Field1.created_at + direction: Annotated[ + sort_direction.SortDirection | None, Field(description='Sort direction') + ] = sort_direction.SortDirection.desc -from adcp.types.generated_poc.core.tasks_list_request import Field1, Filters, Sort, TasksListRequest -# Explicit exports -__all__ = ["Field1", "Filters", "Sort", "TasksListRequest"] +class TasksListRequest(AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + account: Annotated[ + account_ref.AccountReference | None, + Field( + description="Account scope for task reconciliation. Sellers MUST only return tasks created for the caller's authenticated account + principal pair. When omitted, the seller MAY use the credential-bound singleton account, but multi-account credentials SHOULD require an explicit account." + ), + ] = None + filters: Annotated[Filters | None, Field(description='Filter criteria for querying tasks')] = ( + None + ) + sort: Annotated[Sort | None, Field(description='Sorting parameters')] = None + pagination: pagination_request.PaginationRequest | None = None + include_history: Annotated[ + StrictBool | None, + Field( + description='Include full conversation history for each task (may significantly increase response size)' + ), + ] = False + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/tasks_list_response.py b/src/adcp/types/domains/core/tasks_list_response.py index a9a8f8bb0..0f5b7e550 100644 --- a/src/adcp/types/domains/core/tasks_list_response.py +++ b/src/adcp/types/domains/core/tasks_list_response.py @@ -1,34 +1,117 @@ -"""Types declared by the AdCP ``core/tasks_list_response`` schema. +# generated by datamodel-codegen: +# filename: core/tasks_list_response.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.tasks_list_response import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AwareDatetime, ConfigDict, Field, StrictBool + +from ..enums import task_status +from ..enums import task_type as task_type_1 +from . import context as context_1 +from . import ext as ext_1 +from . import pagination_response +from .protocol_envelope import ProtocolEnvelope +from .version_envelope import AdcpVersionEnvelope + + +class DomainBreakdown(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + media_buy: Annotated[ + SchemaInt | None, + Field(alias='media-buy', description='Number of media-buy tasks in results', ge=0), + ] = None + signals: Annotated[ + SchemaInt | None, Field(description='Number of signals tasks in results', ge=0) + ] = None + creative: Annotated[ + SchemaInt | None, Field(description='Number of creative tasks in results', ge=0) + ] = None + + +class Direction(StrEnum): + asc = 'asc' + desc = 'desc' + + +class SortApplied(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + field: str + direction: Direction + + +class QuerySummary(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + total_matching: Annotated[ + SchemaInt | None, + Field(description='Total number of tasks matching filters (across all pages)', ge=0), + ] = None + returned: Annotated[ + SchemaInt | None, Field(description='Number of tasks returned in this response', ge=0) + ] = None + domain_breakdown: Annotated[ + DomainBreakdown | None, Field(description='Count of tasks by domain') + ] = None + status_breakdown: Annotated[ + dict[str, SchemaInt] | None, Field(description='Count of tasks by status') + ] = None + filters_applied: Annotated[ + list[str] | None, Field(description='List of filters that were applied to the query') + ] = None + sort_applied: Annotated[ + SortApplied | None, Field(description='Sort order that was applied') + ] = None + + +class Domain(StrEnum): + media_buy = 'media-buy' + signals = 'signals' + creative = 'creative' + + +class Task(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + task_id: Annotated[str, Field(description='Unique identifier for this task')] + task_type: Annotated[task_type_1.TaskType, Field(description='Type of AdCP operation')] + domain: Annotated[Domain, Field(description='AdCP domain this task belongs to')] + status: Annotated[task_status.TaskStatus, Field(description='Current task status')] + created_at: Annotated[ + AwareDatetime, Field(description='When the task was initially created (ISO 8601)') + ] + updated_at: Annotated[ + AwareDatetime, Field(description='When the task was last updated (ISO 8601)') + ] + completed_at: Annotated[ + AwareDatetime | None, + Field( + description='When the task completed (ISO 8601, only for completed/failed/canceled tasks)' + ), + ] = None + has_webhook: Annotated[ + StrictBool | None, Field(description='Whether this task has webhook configuration') + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.tasks_list_response import ( - Direction, - Domain, - DomainBreakdown, - QuerySummary, - SortApplied, - Task, - TasksListResponse, -) - -# Explicit exports -__all__ = [ - "Direction", - "Domain", - "DomainBreakdown", - "QuerySummary", - "SortApplied", - "Task", - "TasksListResponse", -] +class TasksListResponse(AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + query_summary: Annotated[ + QuerySummary, Field(description='Summary of the query that was executed') + ] + tasks: Annotated[list[Task], Field(description='Array of tasks matching the query criteria')] + pagination: pagination_response.PaginationResponse + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/tracker_execution_contract.py b/src/adcp/types/domains/core/tracker_execution_contract.py index 141c9cf9f..d779cdaaf 100644 --- a/src/adcp/types/domains/core/tracker_execution_contract.py +++ b/src/adcp/types/domains/core/tracker_execution_contract.py @@ -1,18 +1,30 @@ -"""Types declared by the AdCP ``core/tracker_execution_contract`` schema. +# generated by datamodel-codegen: +# filename: core/tracker_execution_contract.json +# timestamp: 2026-10-03T15:09:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.tracker_execution_contract import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import tracker_execution_selector -from adcp.types.generated_poc.core.tracker_execution_contract import TrackerExecutionContract -# Explicit exports -__all__ = ["TrackerExecutionContract"] +class TrackerExecutionContract(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + complete: Annotated[ + StrictBool, + Field( + description='Whether honored is the complete set. complete:true with an empty honored array explicitly supports no buyer-supplied trackers.' + ), + ] + honored: Annotated[ + list[tracker_execution_selector.TrackerExecutionSelector], + Field( + description='Exact tracker/event selectors the seller accepts and initiates once per manifest instance for each logical event occurrence. selector_id and structural selector identity are each unique within this array.' + ), + ] diff --git a/src/adcp/types/domains/core/tracker_execution_selector.py b/src/adcp/types/domains/core/tracker_execution_selector.py index fa2e223a4..c3b58ceed 100644 --- a/src/adcp/types/domains/core/tracker_execution_selector.py +++ b/src/adcp/types/domains/core/tracker_execution_selector.py @@ -1,28 +1,109 @@ -"""Types declared by the AdCP ``core/tracker_execution_selector`` schema. +# generated by datamodel-codegen: +# filename: core/tracker_execution_selector.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.tracker_execution_selector import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field + +from ..enums import pixel_tracking_event, tracker_execution_actor, tracker_firing_path +from . import daast_tracker_constraints, vast_tracker_constraints +from .daast_tracker_constraints import DaastTrackerConstraints +from .vast_tracker_constraints import VastTrackerConstraints + + +class TrackerExecutionSelector1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + selector_id: Annotated[ + str, + Field( + description='Stable identifier unique within the materialized execution contract.', + min_length=1, + ), + ] + asset_type: Literal['pixel_tracker'] = 'pixel_tracker' + event: pixel_tracking_event.PixelTrackingEvent + method: Annotated[ + Literal['img'], + Field( + description='AdCP 3.2 execution contracts cover image-pixel initiation only; JavaScript response evaluation is outside this contract version.' + ), + ] = 'img' + custom_event_name: Annotated[ + str | None, Field(description='Required only when event is custom.', min_length=1) + ] = None + execution_actor: tracker_execution_actor.TrackerExecutionActor + firing_paths: Annotated[ + list[tracker_firing_path.TrackerFiringPath], + Field( + description='Complete set of permitted initiation environments. Exactly one path is selected for each logical event occurrence.', + min_length=1, + ), + ] + + +class TrackerExecutionSelector3(DaastTrackerConstraints): + model_config = ConfigDict( + extra='forbid', + ) + selector_id: Annotated[ + str, + Field( + description='Stable identifier unique within the materialized execution contract.', + min_length=1, + ), + ] + asset_type: Literal['daast_tracker'] = 'daast_tracker' + daast_versions: daast_tracker_constraints.DaastVersions + daast_event: daast_tracker_constraints.DaastEvent + target: daast_tracker_constraints.DaastTarget + offset: daast_tracker_constraints.DaastOffset | None = None + execution_actor: tracker_execution_actor.TrackerExecutionActor + firing_paths: Annotated[ + list[tracker_firing_path.TrackerFiringPath], + Field( + description='Complete set of permitted initiation environments. Exactly one path is selected for each logical event occurrence.', + min_length=1, + ), + ] + + +class TrackerExecutionSelector2(VastTrackerConstraints): + model_config = ConfigDict( + extra='forbid', + ) + selector_id: Annotated[ + str, + Field( + description='Stable identifier unique within the materialized execution contract.', + min_length=1, + ), + ] + asset_type: Literal['vast_tracker'] = 'vast_tracker' + vast_versions: vast_tracker_constraints.VastVersions + vast_event: vast_tracker_constraints.VastEvent + target: vast_tracker_constraints.VastTarget + offset: vast_tracker_constraints.VastOffset | None = None + execution_actor: tracker_execution_actor.TrackerExecutionActor + firing_paths: Annotated[ + list[tracker_firing_path.TrackerFiringPath], + Field( + description='Complete set of permitted initiation environments. Exactly one path is selected for each logical event occurrence.', + min_length=1, + ), + ] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.tracker_execution_selector import ( - TrackerExecutionSelector, - TrackerExecutionSelector1, - TrackerExecutionSelector2, - TrackerExecutionSelector3, -) - -# Explicit exports -__all__ = [ - "TrackerExecutionSelector", - "TrackerExecutionSelector1", - "TrackerExecutionSelector2", - "TrackerExecutionSelector3", +TrackerExecutionSelector = Annotated[ + TrackerExecutionSelector1 | TrackerExecutionSelector2 | TrackerExecutionSelector3, + Field( + description='One exact first-class manifest tracker selector in a seller production execution contract. AdCP 3.2 supports pixel_tracker, vast_tracker, and daast_tracker only; URL-slot selectors are not part of this union.', + discriminator='asset_type', + title='Tracker Execution Selector', + ), ] diff --git a/src/adcp/types/domains/core/transformer.py b/src/adcp/types/domains/core/transformer.py index 9d92c982f..f19273a11 100644 --- a/src/adcp/types/domains/core/transformer.py +++ b/src/adcp/types/domains/core/transformer.py @@ -1,102 +1,1874 @@ -"""Types declared by the AdCP ``core/transformer`` schema. +# generated by datamodel-codegen: +# filename: core/transformer.json +# timestamp: 2026-10-04T01:14:11+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.transformer import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from adcp.types._scalar import ScalarStr +from pydantic import AnyUrl, ConfigDict, Field, StrictBool, model_validator -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import channels +from ..formats.canonical import ( + agent_placement, + audio_daast, + audio_hosted, + audio_vast, + coordinated_placements, + display_tag, + html5, + image, + image_carousel, + native_in_feed, + responsive_creative, + seller_rendered_stateful_display, + sponsored_placement, + video_hosted, + video_vast, +) +from . import creative_locale_policy, format_id, macro_resolution_capability, platform_extension_ref +from . import tracker_execution_contract as tracker_execution_contract_1 +from . import transformer_param, vendor_pricing_option + + +class BrandAgent(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + url: Annotated[AnyUrl, Field(description='MCP endpoint URL of the brand agent.')] + id: Annotated[str, Field(description='Agent identifier.')] + + +class VoiceSynthesisRefItem(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + brand_agent: Annotated[ + BrandAgent, + Field(description='Brand agent that exposed the referenced voice_synthesis entry.'), + ] + voice_id: Annotated[str, Field(description='voice_synthesis.voice_id from the brand agent.')] + rights_id: Annotated[ + str | None, + Field( + description='Optional rights offering or buyer-specific grant identifier associated with this provisioned voice. Brand-side voice_synthesis uses rights_offering_id for the configuration-time offering anchor. This transformer field may carry that offering ID or a grant ID after provisioning; it remains provenance metadata only, not a build_creative rights token.' + ), + ] = None + + +class SellerPreference(StrEnum): + preferred = 'preferred' + accepted = 'accepted' + discouraged = 'discouraged' + + +class InputFormat17(AdCPBaseModel): + pass + + +class OutputCapabilityId(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[a-zA-Z0-9_-]+$'} + + +class VariantDimension(StrEnum): + voice = 'voice' + theme = 'theme' + best_of_n = 'best_of_n' + transformer_config = 'transformer_config' + custom = 'custom' + + +class Multiplicity(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + supports_catalog_fanout: Annotated[ + StrictBool | None, Field(description='Whether this transformer accepts max_creatives.') + ] = False + max_creatives_limit: Annotated[ + SchemaInt | None, + Field(description='Per-transformer ceiling on max_creatives (≤ the agent ceiling).', ge=1), + ] = None + supports_variants: Annotated[ + StrictBool | None, + Field(description='Whether this transformer accepts max_variants > 1 / variant_axis.'), + ] = False + max_variants_limit: Annotated[ + SchemaInt | None, + Field(description='Per-transformer ceiling on max_variants (≤ the agent ceiling).', ge=1), + ] = None + variant_dimensions: Annotated[ + list[VariantDimension] | None, + Field(description="Variant axis dimensions this transformer supports (⊆ the agent's)."), + ] = None + + +class InputFormat1(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['image'] = 'image' + params: image.CanonicalFormatImage + + +class InputFormat2(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['html5'] = 'html5' + params: html5.CanonicalFormatHtml5Banner + + +class InputFormat3(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['display_tag'] = 'display_tag' + params: display_tag.CanonicalFormatDisplayTag + + +class InputFormat4(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['image_carousel'] = 'image_carousel' + params: image_carousel.CanonicalFormatImageCarousel + + +class InputFormat5(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['video_hosted'] = 'video_hosted' + params: video_hosted.CanonicalFormatHostedVideo + + +class InputFormat6(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['video_vast'] = 'video_vast' + params: video_vast.CanonicalFormatVastVideo + + +class InputFormat7(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['audio_hosted'] = 'audio_hosted' + params: audio_hosted.CanonicalFormatHostedAudio + + +class InputFormat8(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['audio_vast'] = 'audio_vast' + params: audio_vast.CanonicalFormatVastAudio + + +class InputFormat9(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['audio_daast'] = 'audio_daast' + params: audio_daast.CanonicalFormatDaastAudio + + +class InputFormat10(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['sponsored_placement'] = 'sponsored_placement' + params: sponsored_placement.CanonicalFormatSponsoredPlacementRetailMediaCatalogDriven + + +class InputFormat11(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['native_in_feed'] = 'native_in_feed' + params: native_in_feed.CanonicalFormatNativeInFeed + + +class InputFormat12(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['responsive_creative'] = 'responsive_creative' + params: responsive_creative.CanonicalFormatResponsiveCreative + + +class InputFormat13(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['agent_placement'] = 'agent_placement' + params: agent_placement.CanonicalFormatAgentPlacementAiSurfaceSponsoredPlacement -from adcp.types.generated_poc.core.transformer import ( - BrandAgent, - InputFormat, - InputFormat1, - InputFormat10, - InputFormat11, - InputFormat12, - InputFormat13, - InputFormat14, - InputFormat15, - InputFormat16, - InputFormat17, - InputFormat18, - InputFormat19, - InputFormat2, - InputFormat20, - InputFormat21, - InputFormat22, - InputFormat23, - InputFormat24, - InputFormat25, - InputFormat26, - InputFormat27, - InputFormat28, - InputFormat29, - InputFormat3, - InputFormat30, - InputFormat31, - InputFormat32, - InputFormat33, - InputFormat4, - InputFormat5, - InputFormat6, - InputFormat7, - InputFormat8, - InputFormat9, - Multiplicity, - OutputCapabilityId, - SellerPreference, - Transformer, - VariantDimension, - VoiceSynthesisRefItem, + +class InputFormat14(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['seller_rendered_stateful_display'] = 'seller_rendered_stateful_display' + params: seller_rendered_stateful_display.CanonicalFormatSellerRenderedStatefulDisplay + + +class InputFormat15(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['coordinated_placements'] = 'coordinated_placements' + params: coordinated_placements.CanonicalFormatCoordinatedPlacements + + +class InputFormat16(AdCPBaseModel): + format_option_id: Annotated[ + str | None, + Field( + description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 + tracker_execution_contract: Annotated[ + tracker_execution_contract_1.TrackerExecutionContract | None, + Field( + description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', + min_length=1, + ), + ] = None + technical_requirements_complete: Annotated[ + StrictBool | None, + Field( + description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' + ), + ] = 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 + sample_render_url: Annotated[ + AnyUrl | None, + Field( + description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' + ), + ] = 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 + locale_policy: Annotated[ + creative_locale_policy.CreativeLocalePolicy | None, + Field( + description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' + ), + ] = None + canonical_formats_only: Annotated[ + StrictBool | 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[ + StrictBool | None, + Field( + description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." + ), + ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' + ), + ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' + ), + ] = None + format_kind: Literal['custom'] = 'custom' + params: Annotated[ + dict[str, Any], + Field( + description="Custom shape's params. Validated against the schema fetched from `format_schema.uri` at the cached `format_schema.digest`." + ), + ] + + +class InputFormat18(InputFormat1, InputFormat17): + pass + + +class InputFormat19(InputFormat2, InputFormat17): + pass + + +class InputFormat20(InputFormat3, InputFormat17): + pass + + +class InputFormat21(InputFormat4, InputFormat17): + pass + + +class InputFormat22(InputFormat5, InputFormat17): + pass + + +class InputFormat23(InputFormat6, InputFormat17): + pass + + +class InputFormat24(InputFormat7, InputFormat17): + pass + + +class InputFormat25(InputFormat8, InputFormat17): + pass + + +class InputFormat26(InputFormat9, InputFormat17): + pass + + +class InputFormat27(InputFormat10, InputFormat17): + pass + + +class InputFormat28(InputFormat11, InputFormat17): + pass + + +class InputFormat29(InputFormat12, InputFormat17): + pass + + +class InputFormat30(InputFormat13, InputFormat17): + pass + + +class InputFormat31(InputFormat14, InputFormat17): + pass + + +class InputFormat32(InputFormat15, InputFormat17): + pass + + +class InputFormat33(InputFormat16, InputFormat17): + pass + + +InputFormat = ( + InputFormat18 + | InputFormat19 + | InputFormat20 + | InputFormat21 + | InputFormat22 + | InputFormat23 + | InputFormat24 + | InputFormat25 + | InputFormat26 + | InputFormat27 + | InputFormat28 + | InputFormat29 + | InputFormat30 + | InputFormat31 + | InputFormat32 + | InputFormat33 ) -# Explicit exports -__all__ = [ - "BrandAgent", - "InputFormat", - "InputFormat1", - "InputFormat10", - "InputFormat11", - "InputFormat12", - "InputFormat13", - "InputFormat14", - "InputFormat15", - "InputFormat16", - "InputFormat17", - "InputFormat18", - "InputFormat19", - "InputFormat2", - "InputFormat20", - "InputFormat21", - "InputFormat22", - "InputFormat23", - "InputFormat24", - "InputFormat25", - "InputFormat26", - "InputFormat27", - "InputFormat28", - "InputFormat29", - "InputFormat3", - "InputFormat30", - "InputFormat31", - "InputFormat32", - "InputFormat33", - "InputFormat4", - "InputFormat5", - "InputFormat6", - "InputFormat7", - "InputFormat8", - "InputFormat9", - "Multiplicity", - "OutputCapabilityId", - "SellerPreference", - "Transformer", - "VariantDimension", - "VoiceSynthesisRefItem", -] + +class Transformer(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + transformer_id: Annotated[ + str, + Field( + description='Stable identifier for this transformer within the agent. Pass to build_creative `transformer_id` to select it.' + ), + ] + name: Annotated[ + str, + Field( + description="Human-readable transformer name (e.g. 'Voiceover — Isaac', 'Veo 3 text-to-video')." + ), + ] + description: Annotated[ + str | None, + Field(description='Plain-text explanation of what this transformer produces and how.'), + ] = None + metadata: Annotated[ + dict[str, Any] | None, + Field( + description='Transformer-specific attributes a buyer can filter or display (e.g. provider, modality, language).' + ), + ] = None + voice_synthesis_ref: Annotated[ + list[VoiceSynthesisRefItem] | None, + Field( + description='Optional discovery/audit anchors for voice transformers provisioned from brand-agent voice_synthesis entries. Informational only: these references help buyers match a discovered transformer to brand/rights-agent provenance, but they do not assert build-time authorization or require the creative agent to perform rights-token validation.', + min_length=1, + ), + ] = None + input_format_ids: Annotated[ + list[format_id.FormatReferenceStructuredObject] | None, + Field( + deprecated=True, + description='**DEPRECATED in 3.2.** Legacy named formats this transformer accepts as input. Use input_formats with canonical declarations.', + ), + ] = None + output_format_ids: Annotated[ + list[format_id.FormatReferenceStructuredObject] | None, + Field( + deprecated=True, + description='**DEPRECATED in 3.2.** Legacy named formats this transformer can produce. Use output_capability_ids.', + min_length=1, + ), + ] = None + input_formats: Annotated[ + list[InputFormat] | None, + Field( + description='Canonical format declarations this transformer accepts as input. Omitted means it builds from a brief or raw assets rather than transforming an existing creative. Compatibility uses canonical constraint satisfaction, not identifier equality. Transformer self-description has no seller production authority, so tracker_execution_contract and tracker_execution_contract_digest are forbidden.', + min_length=1, + ), + ] = None + output_capability_ids: Annotated[ + list[OutputCapabilityId] | None, + Field( + description="Canonical output capabilities this transformer can produce. Every value MUST match this agent's get_adcp_capabilities `creative.supported_formats[].capability_id`. A build_creative request's target_capability_id(s) MUST be a subset of this array.", + min_length=1, + ), + ] = None + params: Annotated[ + list[transformer_param.TransformerParam] | None, + Field( + description="Configuration knobs this transformer exposes. The buyer supplies values in build_creative `config`, keyed by each param's `field`. Enumerable param values (e.g. account-specific voices) are returned only when requested via list_transformers `expand_params`." + ), + ] = None + pricing_options: Annotated[ + list[vendor_pricing_option.VendorPricingOption] | None, + Field( + description='Per-account rate-card options for using this transformer. Present when the list_transformers request set include_pricing=true with an account. The applied option is echoed back per-leaf on the build_creative response and reconciled via report_usage.', + min_length=1, + ), + ] = None + multiplicity: Annotated[ + Multiplicity | None, + Field( + description="Optional per-transformer fan-out limits that NARROW the agent-level get_adcp_capabilities `creative.multiplicity`. Same shape as the agent-level object. When present, this transformer's authoritative; its ceilings (max_creatives_limit / max_variants_limit) MUST NOT exceed the agent ceilings, and its variant_dimensions MUST be a subset of the agent's. Omit to inherit the agent-level capability unchanged." + ), + ] = None + + @model_validator(mode='after') + def _require_output_format_declaration(self) -> Transformer: + """At least one output declaration is required by the schema.""" + # Read Pydantic's stored values directly so validation itself does not + # emit a deprecation warning for the still-supported legacy field. + if ( + self.__dict__.get('output_capability_ids') is None + and self.__dict__.get('output_format_ids') is None + ): + raise ValueError( + 'one of output_capability_ids or deprecated output_format_ids is required' + ) + return self + + @model_validator(mode='after') + def _require_schema_required_group(self) -> Transformer: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('output_capability_ids',), ('output_format_ids',),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'Transformer requires at least one of these field groups: output_capability_ids | output_format_ids' + ) diff --git a/src/adcp/types/domains/core/transformer_param.py b/src/adcp/types/domains/core/transformer_param.py index 4f8ea68b8..2651bd37c 100644 --- a/src/adcp/types/domains/core/transformer_param.py +++ b/src/adcp/types/domains/core/transformer_param.py @@ -1,23 +1,111 @@ -"""Types declared by the AdCP ``core/transformer_param`` schema. +# generated by datamodel-codegen: +# filename: core/transformer_param.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.transformer_param import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictBool, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.transformer_param import ( - Option, - TransformerParam, - Type, - ValueSource, -) +class Type(StrEnum): + string = 'string' + number = 'number' + integer = 'integer' + boolean = 'boolean' + + +class ValueSource(StrEnum): + inline = 'inline' + range = 'range' + enumerable = 'enumerable' + free_text = 'free_text' + + +class Option(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + value: Annotated[ + Any, Field(description='The value the buyer passes in `config` for this field.') + ] + label: Annotated[str | None, Field(description='Human-readable label for this option.')] = None + metadata: Annotated[ + dict[str, Any] | None, + Field( + description='Option-specific attributes the buyer can filter or display (e.g. for a voice: language, gender, provider, custom).' + ), + ] = None + -# Explicit exports -__all__ = ["Option", "TransformerParam", "Type", "ValueSource"] +class TransformerParam(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + field: Annotated[ + str, + Field( + description='The config key. Buyers set the value under this exact key in build_creative `config`.' + ), + ] + type: Annotated[ + Type, Field(description='JSON type of the value the buyer supplies for this field.') + ] + value_source: Annotated[ + ValueSource, + Field( + description='Where the legal values come from. `inline` — a small closed set listed in `allowed_values` (e.g. mastering_preset). `range` — a numeric interval bounded by `minimum`/`maximum` (e.g. speaking_rate). `enumerable` — an account-scoped, dynamic set (e.g. voices, including custom/cloned ones) resolved per-credential; values appear in `options[]` only when expanded. `free_text` — an open buyer-authored string with no closed/enumerable set (e.g. a negative_prompt or style note for a generative agent); `type` MUST be `string` and `allowed_values`/`minimum`/`maximum`/`options`/`options_cursor` MUST be absent. NOTE: a transformer-param MUST NOT be a generation-count knob (sample_count/n/num_images/count) — output count is owned by `max_variants`/`max_creatives`, never config.' + ), + ] + max_length: Annotated[ + SchemaInt | None, + Field( + description='Optional maximum character length for a `free_text` param. Omit for no declared limit.', + ge=1, + ), + ] = None + allowed_values: Annotated[ + list[Any] | None, + Field( + description='The closed set of legal values. Present when `value_source` is `inline`.', + min_length=1, + ), + ] = None + minimum: Annotated[ + StrictFloat | None, + Field(description='Inclusive lower bound. Present when `value_source` is `range`.'), + ] = None + maximum: Annotated[ + StrictFloat | None, + Field(description='Inclusive upper bound. Present when `value_source` is `range`.'), + ] = None + options: Annotated[ + list[Option] | None, + Field( + description="Account-scoped legal values for an `enumerable` param. Populated ONLY when this param's `field` was named in the list_transformers `expand_params` request — otherwise omitted (the buyer enumerates on demand). Brief-filtered and paginated; use `options_cursor` for the next page." + ), + ] = None + options_cursor: Annotated[ + str | None, + Field( + description="Opaque pagination cursor for this param's `options[]`. Present when more option values are available than were returned. Pass back to list_transformers (scoped to this transformer + field) to fetch the next page." + ), + ] = None + default: Annotated[ + Any | None, + Field( + description='The value applied when the buyer omits this field from `config`. Type matches `type`.' + ), + ] = None + required: Annotated[ + StrictBool | None, + Field( + description='Whether the buyer MUST supply this field in `config`. When false and no `default` is declared, the agent chooses.' + ), + ] = False + description: Annotated[ + str | None, Field(description='Human-readable explanation of what this knob does.') + ] = None diff --git a/src/adcp/types/domains/core/truncation_sentinel.py b/src/adcp/types/domains/core/truncation_sentinel.py index 15b85156b..e83010fec 100644 --- a/src/adcp/types/domains/core/truncation_sentinel.py +++ b/src/adcp/types/domains/core/truncation_sentinel.py @@ -1,18 +1,48 @@ -"""Types declared by the AdCP ``core/truncation_sentinel`` schema. +# generated by datamodel-codegen: +# filename: core/truncation_sentinel.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.truncation_sentinel import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.truncation_sentinel import FieldTruncation, TruncationSentinel +class FieldTruncation(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + original_size_bytes: Annotated[ + SchemaInt, + Field( + description='Size of the untruncated original content in bytes, measured before any encoding overhead. Lets the receiver decide whether to fetch the full value via a payload-bearing surface (when one exists) or proceed with the preview.', + ge=0, + ), + ] + preview: Annotated[ + str | None, + Field( + description='Optional bounded human-readable excerpt of the original content. Sellers SHOULD keep previews under 8 KiB and SHOULD truncate on a UTF-8 codepoint boundary. Absent when the seller cannot safely surface even a preview (e.g., the original content is binary and not text-decodable, or seller policy forbids any payload surfacing). Receivers MUST NOT parse `preview` as a complete representation of the original value.' + ), + ] = None + preview_format: Annotated[ + str | None, + Field( + description='Hint for how to render `preview`. Common values: `text` (plain UTF-8), `json` (JSON fragment), `base64` (base64-encoded binary), `xml`, `html`. The list is open — adopters MAY use other MIME-derived shorthands. Receivers SHOULD treat unknown values as `text` and SHOULD NOT reject the sentinel when the value is unfamiliar.' + ), + ] = None + -# Explicit exports -__all__ = ["FieldTruncation", "TruncationSentinel"] +class TruncationSentinel(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + field_truncation: Annotated[ + FieldTruncation, + Field( + alias='_truncation', + description='Truncation envelope. The leading underscore is a deliberate signal that this property is a control marker and not a payload field — the natural value being surfaced will not have a `_truncation` key. `additionalProperties: true` so future revisions of this contract can add classification fields without a forward-compat break.', + ), + ] diff --git a/src/adcp/types/domains/core/user_match.py b/src/adcp/types/domains/core/user_match.py index 603084784..7fc0a30b7 100644 --- a/src/adcp/types/domains/core/user_match.py +++ b/src/adcp/types/domains/core/user_match.py @@ -1,18 +1,72 @@ -"""Types declared by the AdCP ``core/user_match`` schema. +# generated by datamodel-codegen: +# filename: core/user_match.json +# timestamp: 2026-10-04T01:14:11+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.user_match import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, model_validator + +from ..enums import uid_type +from . import ext as ext_1 + + +class Uid(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + type: Annotated[uid_type.UidType, Field(description='Universal ID type')] + value: Annotated[str, Field(description='Universal ID value')] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.user_match import Uid, UserMatch +class UserMatch(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + uids: Annotated[ + list[Uid] | None, Field(description='Universal ID values for user matching', min_length=1) + ] = None + hashed_email: Annotated[ + str | None, + Field( + description='SHA-256 hash of lowercase, trimmed email address. Buyer must normalize before hashing: lowercase, trim whitespace. Pseudonymous PII, not anonymous — the email namespace is small enough that an unsalted SHA-256 is recoverable via precomputed dictionaries. Treat as PII for retention, consent, and access-control purposes. See docs/reference/privacy-considerations#unsalted-hashed-identifiers-are-pseudonymous-not-anonymous.', + pattern='^[a-f0-9]{64}$', + ), + ] = None + hashed_phone: Annotated[ + str | None, + Field( + description='SHA-256 hash of E.164-formatted phone number (e.g. +12065551234). Buyer must normalize to E.164 before hashing. Pseudonymous PII, not anonymous — the E.164 namespace is small enough that an unsalted SHA-256 is recoverable via precomputed dictionaries. Treat as PII for retention, consent, and access-control purposes. See docs/reference/privacy-considerations#unsalted-hashed-identifiers-are-pseudonymous-not-anonymous.', + pattern='^[a-f0-9]{64}$', + ), + ] = None + click_id: Annotated[ + str | None, + Field(description='Platform click identifier (fbclid, gclid, ttclid, ScCid, etc.)'), + ] = None + click_id_type: Annotated[ + str | None, + Field(description='Type of click identifier (e.g. fbclid, gclid, ttclid, msclkid, ScCid)'), + ] = None + client_ip: Annotated[ + str | None, Field(description='Client IP address for probabilistic matching') + ] = None + client_user_agent: Annotated[ + str | None, Field(description='Client user agent string for probabilistic matching') + ] = None + ext: ext_1.ExtensionObject | None = None -# Explicit exports -__all__ = ["Uid", "UserMatch"] + @model_validator(mode='after') + def _require_schema_required_group(self) -> UserMatch: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('uids',), ('hashed_email',), ('hashed_phone',), ('click_id',), ('client_ip', 'client_user_agent'),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'UserMatch requires at least one of these field groups: uids | hashed_email | hashed_phone | click_id | client_ip+client_user_agent' + ) diff --git a/src/adcp/types/domains/core/vast_media_file_requirements.py b/src/adcp/types/domains/core/vast_media_file_requirements.py index 12b184715..25dd654bb 100644 --- a/src/adcp/types/domains/core/vast_media_file_requirements.py +++ b/src/adcp/types/domains/core/vast_media_file_requirements.py @@ -1,23 +1,97 @@ -"""Types declared by the AdCP ``core/vast_media_file_requirements`` schema. +# generated by datamodel-codegen: +# filename: core/vast_media_file_requirements.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.vast_media_file_requirements import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import vast_media_delivery_method + + +class MimeType(ScalarStr): + __slots__ = () + _constraints = { + 'pattern': '^[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]*/[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]*$', + } + + +class Container(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[a-z0-9][a-z0-9._+-]*$'} + + +class Codec(ScalarStr): + __slots__ = () + _constraints = {'min_length': 1} -from adcp.types.generated_poc.core.vast_media_file_requirements import ( - Codec, - Container, - MimeType, - VastMediafileRequirements, -) -# Explicit exports -__all__ = ["Codec", "Container", "MimeType", "VastMediafileRequirements"] +class VastMediafileRequirements(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + delivery_methods: Annotated[ + list[vast_media_delivery_method.VastMediaDeliveryMethod] | None, + Field( + description='Accepted values of the required `MediaFile@delivery` attribute. `progressive` identifies a directly downloadable media file; `streaming` identifies a streaming media resource.', + min_length=1, + ), + ] = None + mime_types: Annotated[ + list[MimeType] | None, + Field( + description='Accepted MIME types from `MediaFile@type`, compared case-insensitively after trimming optional whitespace. Parameters are not accepted in this field. Examples: `video/mp4`, `video/webm`, `audio/mpeg`, `audio/aac`.', + min_length=1, + ), + ] = None + containers: Annotated[ + list[Container] | None, + Field( + description='Accepted normalized container identifiers, such as `mp4`, `webm`, or `mpeg_ts`. A receiver MUST determine the container from authoritative metadata or safe byte inspection and MUST NOT guess it only from the media URI suffix.', + min_length=1, + ), + ] = None + codecs: Annotated[ + list[Codec] | None, + Field( + description='Accepted codec identifiers for `MediaFile@codec`, using the codec syntax referenced by the applicable VAST version. A missing codec attribute does not satisfy a declared codec constraint unless safe inspection establishes the codec.', + min_length=1, + ), + ] = None + min_width: Annotated[ + SchemaInt | None, Field(description='Minimum accepted `MediaFile@width` in pixels.', ge=1) + ] = None + max_width: Annotated[ + SchemaInt | None, Field(description='Maximum accepted `MediaFile@width` in pixels.', ge=1) + ] = None + min_height: Annotated[ + SchemaInt | None, Field(description='Minimum accepted `MediaFile@height` in pixels.', ge=1) + ] = None + max_height: Annotated[ + SchemaInt | None, Field(description='Maximum accepted `MediaFile@height` in pixels.', ge=1) + ] = None + min_bitrate_kbps: Annotated[ + SchemaInt | None, + Field( + description='Minimum accepted MediaFile bitrate in kilobits per second. A fixed `MediaFile@bitrate` must be at least this value. For adaptive/streaming media, the declared `MediaFile@minBitrate` must be at least this value. Safe byte inspection may establish the value when metadata is absent.', + ge=1, + ), + ] = None + max_bitrate_kbps: Annotated[ + SchemaInt | None, + Field( + description='Maximum accepted MediaFile bitrate in kilobits per second. A fixed `MediaFile@bitrate` must not exceed this value. For adaptive/streaming media, the declared `MediaFile@maxBitrate` must not exceed this value. Safe byte inspection may establish the value when metadata is absent.', + ge=1, + ), + ] = None + max_file_size_bytes: Annotated[ + SchemaInt | None, + Field( + description='Maximum accepted MediaFile size in exact bytes. `MediaFile@fileSize`, when present, is expressed in bytes; a receiver MAY verify it against safely fetched media bytes and MUST use the verified byte count if the values disagree.', + ge=1, + ), + ] = None diff --git a/src/adcp/types/domains/core/vast_tracker_constraints.py b/src/adcp/types/domains/core/vast_tracker_constraints.py index c02a22781..94f3664d0 100644 --- a/src/adcp/types/domains/core/vast_tracker_constraints.py +++ b/src/adcp/types/domains/core/vast_tracker_constraints.py @@ -1,24 +1,66 @@ -"""Types declared by the AdCP ``core/vast_tracker_constraints`` schema. +# generated by datamodel-codegen: +# filename: core/vast_tracker_constraints.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.vast_tracker_constraints import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import Field, RootModel -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import vast_version + + +class VastEvent(StrEnum): + creativeView = 'creativeView' + loaded = 'loaded' + start = 'start' + firstQuartile = 'firstQuartile' + midpoint = 'midpoint' + thirdQuartile = 'thirdQuartile' + complete = 'complete' + mute = 'mute' + unmute = 'unmute' + pause = 'pause' + resume = 'resume' + rewind = 'rewind' + skip = 'skip' + playerExpand = 'playerExpand' + playerCollapse = 'playerCollapse' + fullscreen = 'fullscreen' + exitFullscreen = 'exitFullscreen' + progress = 'progress' + acceptInvitation = 'acceptInvitation' + adExpand = 'adExpand' + adCollapse = 'adCollapse' + minimize = 'minimize' + overlayViewDuration = 'overlayViewDuration' + otherAdInteraction = 'otherAdInteraction' + interactiveStart = 'interactiveStart' + close = 'close' + closeLinear = 'closeLinear' + + +class VastTarget(StrEnum): + linear = 'linear' + non_linear = 'non_linear' + companion = 'companion' + + +class VastOffset(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$'} + + +class VastVersions(RootModel[list[vast_version.VastVersion]]): + root: Annotated[list[vast_version.VastVersion], Field(min_length=1)] -from adcp.types.generated_poc.core.vast_tracker_constraints import ( - VastEvent, - VastOffset, - VastTarget, - VastTrackerConstraints, - VastVersions, -) -# Explicit exports -__all__ = ["VastEvent", "VastOffset", "VastTarget", "VastTrackerConstraints", "VastVersions"] +class VastTrackerConstraints(AdCPBaseModel): + vast_event: VastEvent | None = None + target: VastTarget | None = VastTarget.linear + offset: VastOffset | None = None + vast_versions: VastVersions | None = None diff --git a/src/adcp/types/domains/core/vehicle_item.py b/src/adcp/types/domains/core/vehicle_item.py index c9d3264d5..b3c6b9bcf 100644 --- a/src/adcp/types/domains/core/vehicle_item.py +++ b/src/adcp/types/domains/core/vehicle_item.py @@ -1,36 +1,115 @@ -"""Types declared by the AdCP ``core/vehicle_item`` schema. +# generated by datamodel-codegen: +# filename: core/vehicle_item.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.vehicle_item import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AnyUrl, ConfigDict, Field, StrictFloat + +from . import ext as ext_1 +from . import offering_asset_group +from . import price as price_1 + + +class Condition(StrEnum): + new = 'new' + used = 'used' + certified_pre_owned = 'certified_pre_owned' + + +class Unit(StrEnum): + km = 'km' + mi = 'mi' + + +class Mileage(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + value: Annotated[StrictFloat, Field(description='Mileage value.', ge=0.0)] + unit: Annotated[Unit, Field(description='Distance unit.')] + + +class BodyStyle(StrEnum): + sedan = 'sedan' + suv = 'suv' + truck = 'truck' + coupe = 'coupe' + convertible = 'convertible' + wagon = 'wagon' + van = 'van' + hatchback = 'hatchback' + + +class Transmission(StrEnum): + automatic = 'automatic' + manual = 'manual' + cvt = 'cvt' + + +class FuelType(StrEnum): + gasoline = 'gasoline' + diesel = 'diesel' + electric = 'electric' + hybrid = 'hybrid' + plug_in_hybrid = 'plug_in_hybrid' + + +class Location(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + lat: Annotated[ + StrictFloat, Field(description='Latitude in decimal degrees (WGS 84).', ge=-90.0, le=90.0) + ] + lng: Annotated[ + StrictFloat, + Field(description='Longitude in decimal degrees (WGS 84).', ge=-180.0, le=180.0), + ] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.vehicle_item import ( - BodyStyle, - Condition, - FuelType, - Location, - Mileage, - Transmission, - Unit, - VehicleItem, -) - -# Explicit exports -__all__ = [ - "BodyStyle", - "Condition", - "FuelType", - "Location", - "Mileage", - "Transmission", - "Unit", - "VehicleItem", -] +class VehicleItem(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + vehicle_id: Annotated[str, Field(description='Unique identifier for this vehicle listing.')] + title: Annotated[str, Field(description="Listing title (e.g., '2024 Honda Civic EX Sedan').")] + make: Annotated[str, Field(description="Vehicle manufacturer (e.g., 'Honda', 'Ford', 'BMW').")] + model: Annotated[str, Field(description="Vehicle model (e.g., 'Civic', 'F-150', 'X5').")] + year: Annotated[SchemaInt, Field(description='Model year.', ge=1900)] + price: Annotated[price_1.Price | None, Field(description='Vehicle price.')] = None + condition: Annotated[Condition | None, Field(description='Vehicle condition.')] = None + vin: Annotated[ + str | None, Field(description='Vehicle Identification Number (17-character VIN).') + ] = None + trim: Annotated[ + str | None, Field(description="Trim level (e.g., 'EX', 'Limited', 'Sport').") + ] = None + mileage: Annotated[Mileage | None, Field(description='Odometer reading.')] = None + body_style: Annotated[BodyStyle | None, Field(description='Vehicle body style.')] = None + transmission: Annotated[Transmission | None, Field(description='Transmission type.')] = None + fuel_type: Annotated[FuelType | None, Field(description='Fuel or powertrain type.')] = None + exterior_color: Annotated[str | None, Field(description='Exterior color.')] = None + interior_color: Annotated[str | None, Field(description='Interior color.')] = None + location: Annotated[Location | None, Field(description='Dealer or vehicle location.')] = None + image_url: Annotated[AnyUrl | None, Field(description='Primary vehicle image URL.')] = None + url: Annotated[AnyUrl | None, Field(description='Vehicle listing page URL.')] = None + tags: Annotated[ + list[str] | None, + Field( + description="Tags for filtering (e.g., 'low-mileage', 'one-owner', 'dealer-certified').", + min_length=1, + ), + ] = None + assets: Annotated[ + list[offering_asset_group.OfferingAssetGroup] | None, + Field( + description="Typed creative asset pools for this vehicle. Uses the same OfferingAssetGroup structure as offering-type catalogs. Standard group IDs: 'images_landscape' (exterior hero), 'images_vertical' (9:16 for Stories), 'images_square' (1:1). Enables formats to declare typed image requirements that map unambiguously to the right asset regardless of platform.", + min_length=1, + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/vendor_metric_id.py b/src/adcp/types/domains/core/vendor_metric_id.py index 7cacf93ab..4f8897ff1 100644 --- a/src/adcp/types/domains/core/vendor_metric_id.py +++ b/src/adcp/types/domains/core/vendor_metric_id.py @@ -1,18 +1,17 @@ -"""Types declared by the AdCP ``core/vendor_metric_id`` schema. +# generated by datamodel-codegen: +# filename: core/vendor_metric_id.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.vendor_metric_id import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.vendor_metric_id import VendorMetricId +from adcp.types._scalar import ScalarStr + -# Explicit exports -__all__ = ["VendorMetricId"] +class VendorMetricId(ScalarStr): + __slots__ = () + _constraints = {'max_length': 64, 'min_length': 1, 'pattern': '^[a-z][a-z0-9_]*$'} + _json_schema_extra = { + 'description': "Identifier for a vendor-defined metric within the vendor's vocabulary. Stable lookup key; the vendor publishes the canonical list (with category, methodology, and standard alignment) in `brand.json` `agents[type='measurement']`. Lowercase with underscores so a future enum promotion into `available-metric.json` is a literal string lift. Identifier is namespaced by the vendor — the same `metric_id` may mean different things in different vendors' vocabularies.", + 'examples': ['attention_units', 'gco2e_per_impression', 'demographic_reach', 'co_view_index', 'incremental_lift_percent'], + 'title': 'Vendor Metric ID', + } diff --git a/src/adcp/types/domains/core/vendor_metric_optimization.py b/src/adcp/types/domains/core/vendor_metric_optimization.py index 1312ff9a9..2698a7fba 100644 --- a/src/adcp/types/domains/core/vendor_metric_optimization.py +++ b/src/adcp/types/domains/core/vendor_metric_optimization.py @@ -1,18 +1,24 @@ -"""Types declared by the AdCP ``core/vendor_metric_optimization`` schema. +# generated by datamodel-codegen: +# filename: core/vendor_metric_optimization.json +# timestamp: 2026-05-30T17:30:49+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.vendor_metric_optimization import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import vendor_metric_optimization_supported_metric -from adcp.types.generated_poc.core.vendor_metric_optimization import VendorMetricOptimization -# Explicit exports -__all__ = ["VendorMetricOptimization"] +class VendorMetricOptimization(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + supported_metrics: Annotated[ + list[vendor_metric_optimization_supported_metric.VendorMetricOptimizationSupportedMetric], + Field( + description="Vendor-defined metrics this product can steer delivery toward. Each entry pairs a vendor identity (BrandRef anchored on the vendor's `brand.json` `agents[type='measurement']`) with a `metric_id` from that vendor's published `measurement.metrics[]` catalog, plus the target kinds the seller supports for the pair. Semantic uniqueness key is `(vendor.domain, vendor.brand_id, metric_id)`; sellers MUST de-duplicate before publication. JSON Schema `uniqueItems` blocks exact-object duplicates; semantic deduplication on the BrandRef-domain key is a seller obligation." + ), + ] diff --git a/src/adcp/types/domains/core/vendor_metric_optimization_supported_metric.py b/src/adcp/types/domains/core/vendor_metric_optimization_supported_metric.py index f33b44163..613c76d8c 100644 --- a/src/adcp/types/domains/core/vendor_metric_optimization_supported_metric.py +++ b/src/adcp/types/domains/core/vendor_metric_optimization_supported_metric.py @@ -1,21 +1,42 @@ -"""Types declared by the AdCP ``core/vendor_metric_optimization_supported_metric`` schema. +# generated by datamodel-codegen: +# filename: core/vendor_metric_optimization_supported_metric.json +# timestamp: 2026-05-30T17:30:49+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.vendor_metric_optimization_supported_metric import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import brand_ref, vendor_metric_id + + +class SupportedTarget(StrEnum): + cost_per = 'cost_per' + threshold_rate = 'threshold_rate' -from adcp.types.generated_poc.core.vendor_metric_optimization_supported_metric import ( - SupportedTarget, - VendorMetricOptimizationSupportedMetric, -) -# Explicit exports -__all__ = ["SupportedTarget", "VendorMetricOptimizationSupportedMetric"] +class VendorMetricOptimizationSupportedMetric(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + vendor: Annotated[ + brand_ref.BrandReference, + Field( + description="Vendor that defines and computes this metric. The vendor's `brand.json` is the discovery anchor for the measurement agent (entry with `type: 'measurement'` in the `agents[]` array); the metric's definition, methodology, and unit live at that agent's `get_adcp_capabilities.measurement.metrics[]` and are not duplicated inline here. Same shape as the `vendor` field on `reporting_capabilities.vendor_metrics` for symmetry across optimization and reporting capability declarations." + ), + ] + metric_id: Annotated[ + vendor_metric_id.VendorMetricId, + Field( + description="Identifier for the metric within the vendor's vocabulary (e.g., `attention_score`, `attention_seconds`, `gco2e_per_impression`, `awareness_lift`). MUST be present in the vendor's published `measurement.metrics[]` catalog." + ), + ] + supported_targets: Annotated[ + list[SupportedTarget] | None, + Field( + description='Target kinds available for `vendor_metric` goals against this `(vendor, metric_id)` pair. Values match `target.kind` on the optimization goal. `cost_per` — target cost per metric unit (e.g., $0.05 per attention-second). `threshold_rate` — minimum per-impression value (e.g., attention_score ≥ 70). Only these target kinds are accepted — goals with unlisted target kinds will be rejected. A goal without a target implicitly maximizes the metric within budget — no declaration needed for that mode. When omitted, buyers can still set target-less vendor_metric goals.' + ), + ] = None diff --git a/src/adcp/types/domains/core/vendor_metric_value.py b/src/adcp/types/domains/core/vendor_metric_value.py index 6c2cc3690..8729dcfd3 100644 --- a/src/adcp/types/domains/core/vendor_metric_value.py +++ b/src/adcp/types/domains/core/vendor_metric_value.py @@ -1,18 +1,98 @@ -"""Types declared by the AdCP ``core/vendor_metric_value`` schema. +# generated by datamodel-codegen: +# filename: core/vendor_metric_value.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.vendor_metric_value import +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import attribution_methodology as attribution_methodology_1 +from ..enums import completion_source as completion_source_1 +from ..enums import lift_dimension as lift_dimension_1 +from ..enums import vendor_relationship as vendor_relationship_1 +from ..enums import viewability_standard as viewability_standard_1 +from . import brand_ref, duration, vendor_metric_id + + +class Qualifier(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + viewability_standard: viewability_standard_1.ViewabilityStandard | None = None + completion_source: completion_source_1.CompletionSource | None = None + attribution_methodology: attribution_methodology_1.AttributionMethodology | None = None + attribution_window: duration.Duration | None = None + lift_dimension: lift_dimension_1.LiftDimension | None = None -from adcp.types.generated_poc.core.vendor_metric_value import Qualifier, VendorMetricValue -# Explicit exports -__all__ = ["Qualifier", "VendorMetricValue"] +class VendorMetricValue(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + vendor: Annotated[ + brand_ref.BrandReference, + Field( + description='Vendor that produced this value. Matches a `vendor_metrics[].vendor` declaration on the product.' + ), + ] + metric_id: Annotated[ + vendor_metric_id.VendorMetricId, + Field( + description="Identifier for the metric within the vendor's vocabulary. Matches a `vendor_metrics[].metric_id` declaration on the product." + ), + ] + value: Annotated[ + StrictFloat, + Field( + description="The reported value. Unit semantics are vendor-defined — see `unit` field below and the vendor's `brand.json` measurement-agent documentation." + ), + ] + unit: Annotated[ + str | None, + Field( + description="Unit of the value. Free-form to accommodate the heterogeneity of vendor metrics (e.g., `score`, `seconds`, `persons`, `gCO2e`, `USD`, `lift_percent`). When the value is monetary, use the ISO 4217 code (e.g., `USD`, `EUR`); for non-monetary, use whatever the vendor publishes. Optional on every row — the canonical unit lives at the vendor's measurement-agent metric definition (`brand.json` `agents[type='measurement']`). When sellers populate it inline they SHOULD match the vendor's published unit; buyers MAY resolve from the vendor's measurement agent when this field is absent.", + examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], + ), + ] = None + measurable_impressions: Annotated[ + StrictFloat | None, + Field( + description='Number of impressions in this reporting period that the vendor was able to measure. Coverage denominator — buyers compute coverage rate as `measurable_impressions / impressions`. When absent, coverage is unspecified — buyers MUST NOT compute a coverage rate or assume full coverage. When the vendor measured zero impressions but is integrated, set to 0 explicitly. When the entry is omitted from `vendor_metric_values` entirely, the buyer infers no measurement happened (no integration). This pattern parallels `viewability.measurable_impressions` (`delivery-metrics.json#/properties/viewability`), which has handled vendor coverage in the IAS/DV/MRC ecosystem for over a decade — same convention: absence is unknown, not full. For channels where the atomic observation unit is a play or screen-second rather than an impression (DOOH, cinema, place-based), report `measurable_plays` or `measurable_play_seconds` instead — `impressions` there is itself a modelled figure (`plays × audience multiplier`), so `measurable_impressions / impressions` divides a measured count by a model output and has no interpretation.', + ge=0.0, + ), + ] = None + measurable_plays: Annotated[ + StrictFloat | None, + Field( + description='Number of plays (loop plays / spots aired) in this reporting period that the vendor was able to measure. Coverage denominator for channels where a play, not an impression, is the atomic observation unit — DOOH, cinema, place-based audio. Buyers compute coverage as `measurable_plays / plays` (top-level `plays` on `delivery-metrics.json`). Same absence semantics as `measurable_impressions`: absent means coverage is unspecified and buyers MUST NOT compute a rate; 0 means the vendor is integrated but measured nothing. A row SHOULD carry exactly one coverage denominator — the one matching the unit the vendor actually observes.', + ge=0.0, + ), + ] = None + measurable_play_seconds: Annotated[ + StrictFloat | None, + Field( + description="Play-seconds — seconds of creative playout summed across endpoints (screens, speakers, players) — in this reporting period that the vendor was able to measure. Medium-neutral on purpose: place-based audio has plays and duration but no screen. Coverage denominator when the vendor meters exposure duration rather than discrete plays. On screen networks buyers compute coverage as `measurable_play_seconds / dooh_metrics.screen_time_seconds`; on other place-based media, against the seller's reported playout seconds for the period. Same absence semantics as `measurable_impressions`.", + ge=0.0, + ), + ] = None + vendor_relationship: Annotated[ + vendor_relationship_1.VendorRelationship | None, + Field( + description="Optional echo of the product's `reporting_capabilities.vendor_metrics[].vendor_relationship` for this `(vendor, metric_id)`, so the delivery row is self-describing without joining back to the product (same reasoning as `viewability.vendor`). When present it MUST equal the declared value; buyers MAY resolve from the product declaration when absent. Absence on the row is *undeclared*, never `third_party`. A relationship disposition, not a trust ranking. Deliberately not a `qualifier` key: the relationship is constant across every row for a (seller, vendor) pair and does not partition rows, so it stays out of the `(vendor, metric_id, qualifier)` reconciliation join." + ), + ] = None + qualifier: Annotated[ + Qualifier | None, + Field( + description='Optional qualifier disambiguating this row from sibling rows for the same (vendor, metric_id) — e.g., the same vendor outcome metric reported under 7-day and 30-day attribution windows. Same closed key set as `committed-metric`. When the matching `committed_metrics` entry carries a qualifier, this row MUST carry the identical qualifier so reconciliation joins on `(vendor, metric_id, qualifier)`.' + ), + ] = None + breakdown: Annotated[ + dict[str, Any] | None, + Field( + description="Optional structured payload for vendor metrics that don't fit a single scalar — panel demographic breakouts, co-view audience composition, incremental reach + frequency + lift decompositions. Free-form; the keys and value semantics are defined by the vendor (see the vendor's `brand.json` measurement-agent docs). Buyers MUST treat this object as opaque without consulting the vendor's documentation. Vendors place any fields beyond the standard envelope (e.g., confidence intervals, panel sizes) inside this object rather than at the top level." + ), + ] = None diff --git a/src/adcp/types/domains/core/vendor_pricing_option.py b/src/adcp/types/domains/core/vendor_pricing_option.py index 394a75020..e4f5e35ac 100644 --- a/src/adcp/types/domains/core/vendor_pricing_option.py +++ b/src/adcp/types/domains/core/vendor_pricing_option.py @@ -1,50 +1,184 @@ -"""Types declared by the AdCP ``core/vendor_pricing_option`` schema. +# generated by datamodel-codegen: +# filename: core/vendor_pricing_option.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.vendor_pricing_option import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field, StrictFloat + +from . import ext as ext_1 +from . import format_id + + +class AppliesToOutputCapabilityId(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[a-zA-Z0-9_-]+$'} + + +class VendorPricingOption1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + model: Literal['cpm'] = 'cpm' + cpm: Annotated[StrictFloat, Field(description='Cost per thousand impressions', ge=0.0)] + currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] + ext: ext_1.ExtensionObject | None = None + + +class VendorPricingOption2(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + model: Literal['percent_of_media'] = 'percent_of_media' + percent: Annotated[ + StrictFloat, Field(description='Percentage of media spend, e.g. 15 = 15%', ge=0.0, le=100.0) + ] + max_cpm: Annotated[ + StrictFloat | None, + Field( + description='Optional CPM cap. When set, the effective charge is min(percent × media_spend_per_mille, max_cpm).', + ge=0.0, + ), + ] = None + currency: Annotated[ + str, + Field(description='ISO 4217 currency code for the resulting charge', pattern='^[A-Z]{3}$'), + ] + ext: ext_1.ExtensionObject | None = None + + +class Period(StrEnum): + monthly = 'monthly' + quarterly = 'quarterly' + annual = 'annual' + campaign = 'campaign' + + +class VendorPricingOption3(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + model: Literal['flat_fee'] = 'flat_fee' + amount: Annotated[StrictFloat, Field(description='Fixed charge for the billing period', ge=0.0)] + period: Annotated[Period, Field(description='Billing period for the flat fee.')] + currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] + ext: ext_1.ExtensionObject | None = None + + +class VendorPricingOption4(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + model: Literal['per_unit'] = 'per_unit' + unit: Annotated[ + str, + Field( + description="What is counted — e.g. 'format', 'image', 'token', 'variant', 'render', 'evaluation'." + ), + ] + unit_price: Annotated[StrictFloat, Field(description='Cost per one unit', ge=0.0)] + currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] + ext: ext_1.ExtensionObject | None = None + + +class Metadata(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + summary_for_operator: Annotated[ + str | None, + Field( + description="One or two sentences describing the pricing construct in plain language, displayed to the buyer's operator when requesting approval. Should not repeat the top-level `description` verbatim — summarize the charge mechanic instead (e.g., 'Base $12 CPM plus $0.50 per qualifying post-view conversion, capped at $45 CPM').", + min_length=1, + ), + ] = None + + +class VendorPricingOption5(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + model: Literal['custom'] = 'custom' + description: Annotated[ + str, + Field( + description='Human-readable description of the custom pricing model. Buyers display this to the operator when requesting approval.', + min_length=1, + ), + ] + metadata: Annotated[ + Metadata, + Field( + description="Structured parameters for the custom model. Keys follow lowercase_snake_case. Values may be primitives, arrays, or nested objects. Must be sufficient for a human to understand the pricing basis and for a downstream system to reconstruct the charge. Vendors SHOULD include a `summary_for_operator` string (one or two sentences, suitable for display in a buyer's operator-review UI) so reviewers across vendors see a consistent prompt. Required operator-review fields (approver role, dollar threshold for automatic approval, escalation contact) MAY be surfaced via additional keys the buyer's review surface recognizes." + ), + ] + currency: Annotated[ + str | None, + Field( + description='ISO 4217 currency code. Present when the pricing resolves to a monetary charge in a specific currency.', + pattern='^[A-Z]{3}$', + ), + ] = None + ext: ext_1.ExtensionObject | None = None + + +class VendorPricingOption6(AdCPBaseModel): + pricing_option_id: Annotated[ + str, + Field( + description='Opaque identifier for this pricing option, unique within the vendor agent. Pass this in report_usage to identify which pricing option was applied.' + ), + ] + applies_to_output_format_ids: Annotated[ + list[format_id.FormatReferenceStructuredObject] | None, + Field( + deprecated=True, + description='**DEPRECATED in 3.2.** Legacy named-format pricing scope. Use applies_to_output_capability_ids.', + min_length=1, + ), + ] = None + applies_to_output_capability_ids: Annotated[ + list[AppliesToOutputCapabilityId] | None, + Field( + description='Creative transformers only: scopes this pricing option to canonical output capabilities advertised in creative.supported_formats[].capability_id. When absent, the option is the default for any output. A build targeting an output that matches no scoped option and has no unscoped default is rejected with UNPRICEABLE_OUTPUT.', + min_length=1, + ), + ] = None + + +class VendorPricingOption7(VendorPricingOption1, VendorPricingOption6): + pass + + +class VendorPricingOption8(VendorPricingOption2, VendorPricingOption6): + pass + + +class VendorPricingOption9(VendorPricingOption3, VendorPricingOption6): + pass + + +class VendorPricingOption10(VendorPricingOption4, VendorPricingOption6): + pass + + +class VendorPricingOption11(VendorPricingOption5, VendorPricingOption6): + pass -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.vendor_pricing_option import ( - AppliesToOutputCapabilityId, - Metadata, - Period, - VendorPricingOption, - VendorPricingOption1, - VendorPricingOption10, - VendorPricingOption11, - VendorPricingOption2, - VendorPricingOption3, - VendorPricingOption4, - VendorPricingOption5, - VendorPricingOption6, - VendorPricingOption7, - VendorPricingOption8, - VendorPricingOption9, -) - -# Explicit exports -__all__ = [ - "AppliesToOutputCapabilityId", - "Metadata", - "Period", - "VendorPricingOption", - "VendorPricingOption1", - "VendorPricingOption10", - "VendorPricingOption11", - "VendorPricingOption2", - "VendorPricingOption3", - "VendorPricingOption4", - "VendorPricingOption5", - "VendorPricingOption6", - "VendorPricingOption7", - "VendorPricingOption8", - "VendorPricingOption9", +VendorPricingOption = Annotated[ + VendorPricingOption7 + | VendorPricingOption8 + | VendorPricingOption9 + | VendorPricingOption10 + | VendorPricingOption11, + Field( + description='A pricing option offered by a vendor agent (signals, creative, governance). Combines pricing_option_id with the pricing model fields. Pass pricing_option_id in report_usage for billing verification. All vendor discovery responses return pricing_options as an array — vendors may offer multiple options (volume tiers, context-specific rates, different models per product line).', + title='Vendor Pricing Option', + ), ] diff --git a/src/adcp/types/domains/core/verification_token_claims.py b/src/adcp/types/domains/core/verification_token_claims.py index 79d11fe56..c1f781f67 100644 --- a/src/adcp/types/domains/core/verification_token_claims.py +++ b/src/adcp/types/domains/core/verification_token_claims.py @@ -1,28 +1,60 @@ -"""Types declared by the AdCP ``core/verification_token_claims`` schema. +# generated by datamodel-codegen: +# filename: core/verification_token_claims.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.verification_token_claims import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field + + +class Role(StrEnum): + media_buy = 'media-buy' + creative = 'creative' + signals = 'signals' + governance = 'governance' + brand = 'brand' + sponsored_intelligence = 'sponsored-intelligence' + + +class VerificationTokenMode(StrEnum): + spec = 'spec' + live = 'live' + + +class VerificationTokenGradingProfile(StrEnum): + legacy = 'legacy' + spec = 'spec' -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.verification_token_claims import ( - AgenticadvertisingOrgVerificationTokenClaims, - Role, - VerificationTokenGradingProfile, - VerificationTokenMode, -) - -# Explicit exports -__all__ = [ - "AgenticadvertisingOrgVerificationTokenClaims", - "Role", - "VerificationTokenGradingProfile", - "VerificationTokenMode", -] +class AgenticadvertisingOrgVerificationTokenClaims(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + iss: Literal['https://aao.org'] = 'https://aao.org' + sub: AnyUrl + aud: Literal['aao-verification'] = 'aao-verification' + jti: Annotated[str, Field(min_length=1)] + iat: Annotated[SchemaInt, Field(ge=0)] + exp: Annotated[SchemaInt, Field(ge=0)] + agent_url: AnyUrl + role: Role + verified_specialisms: Annotated[list[str], Field(min_length=1)] + verification_modes: Annotated[list[VerificationTokenMode], Field(min_length=1)] + grading_profile: Annotated[ + VerificationTokenGradingProfile | None, + Field( + description='Grading policy that produced the badge. Absence on a historical token means Legacy; verification_modes remains an independent evidence axis.' + ), + ] = None + first_failing_spec_at: Annotated[ + AwareDatetime | None, + Field( + description='Start of the current Strict Spec failure episode. Omitted when no Strict Spec failure clock is active. The registry remains authoritative for real-time status.' + ), + ] = None + adcp_version: Annotated[str | None, Field(pattern='^[1-9][0-9]*\\.[0-9]+$')] = None + protocol_version: str | None = None diff --git a/src/adcp/types/domains/core/version_envelope.py b/src/adcp/types/domains/core/version_envelope.py index a33ec791d..d75760373 100644 --- a/src/adcp/types/domains/core/version_envelope.py +++ b/src/adcp/types/domains/core/version_envelope.py @@ -1,18 +1,30 @@ -"""Types declared by the AdCP ``core/version_envelope`` schema. +# generated by datamodel-codegen: +# filename: core/version_envelope.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.version_envelope import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-04 01:19:07 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import Field -from adcp.types.generated_poc.core.version_envelope import AdcpVersionEnvelope -# Explicit exports -__all__ = ["AdcpVersionEnvelope"] +class AdcpVersionEnvelope(AdCPBaseModel): + adcp_version: Annotated[ + str | None, + Field( + description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', + examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], + pattern='^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?)?$', + ), + ] = None + adcp_major_version: Annotated[ + SchemaInt | None, + Field( + deprecated=True, + description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", + ge=1, + le=99, + ), + ] = None diff --git a/src/adcp/types/domains/core/warning.py b/src/adcp/types/domains/core/warning.py index 1d91beee9..a233f4469 100644 --- a/src/adcp/types/domains/core/warning.py +++ b/src/adcp/types/domains/core/warning.py @@ -1,18 +1,42 @@ -"""Types declared by the AdCP ``core/warning`` schema. +# generated by datamodel-codegen: +# filename: core/warning.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.warning import +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import warning_code +from . import ext as ext_1 +from . import warning_resource -from adcp.types.generated_poc.core.warning import Warning -# Explicit exports -__all__ = ["Warning"] +class Warning(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + code: warning_code.WarningCode + message: Annotated[ + str, + Field( + description='Short human-readable explanation. Treat as untrusted seller text and do not require buyers to parse it for routing.', + max_length=2000, + min_length=1, + ), + ] + affected_resource: Annotated[ + warning_resource.WarningAffectedResource, + Field( + description='Resource or relationship affected by this warning. Required so multi-package and multi-creative responses remain machine-joinable.' + ), + ] + details: Annotated[ + dict[str, Any] | None, + Field( + description='Optional seller-specific structured diagnostics. AdCP 3.2 defines interoperability through code and affected_resource only; buyers MUST NOT require portable keys inside details. Seller extensions that are not direct diagnostics belong in ext.' + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/warning_resource.py b/src/adcp/types/domains/core/warning_resource.py index 97045cd60..11a11e41a 100644 --- a/src/adcp/types/domains/core/warning_resource.py +++ b/src/adcp/types/domains/core/warning_resource.py @@ -1,18 +1,27 @@ -"""Types declared by the AdCP ``core/warning_resource`` schema. +# generated by datamodel-codegen: +# filename: core/warning_resource.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.warning_resource import +from adcp.types._str_enum import StrEnum -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.warning_resource import ResourceType, WarningAffectedResource +class ResourceType(StrEnum): + media_buy = 'media_buy' + package = 'package' + creative = 'creative' + creative_assignment = 'creative_assignment' + -# Explicit exports -__all__ = ["ResourceType", "WarningAffectedResource"] +class WarningAffectedResource(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + resource_type: ResourceType + media_buy_id: str | None = None + package_id: str | None = None + creative_id: str | None = None diff --git a/src/adcp/types/domains/core/webhook_activity_record.py b/src/adcp/types/domains/core/webhook_activity_record.py index 07b61616f..9ac54ddb8 100644 --- a/src/adcp/types/domains/core/webhook_activity_record.py +++ b/src/adcp/types/domains/core/webhook_activity_record.py @@ -1,18 +1,128 @@ -"""Types declared by the AdCP ``core/webhook_activity_record`` schema. +# generated by datamodel-codegen: +# filename: core/webhook_activity_record.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.webhook_activity_record import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import notification_type as notification_type_1 +from . import ext as ext_1 + + +class Status(StrEnum): + success = 'success' + failed = 'failed' + timeout = 'timeout' + connection_error = 'connection_error' + pending = 'pending' -from adcp.types.generated_poc.core.webhook_activity_record import Status, WebhookActivityRecord -# Explicit exports -__all__ = ["Status", "WebhookActivityRecord"] +class WebhookActivityRecord(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + idempotency_key: Annotated[ + str, + Field( + description='Equals the `idempotency_key` carried in the webhook payload itself (see docs/building/by-layer/L3/webhooks.mdx § Dedup by `idempotency_key`). Stable across retry attempts of the same logical fire — retries with `attempt` > 1 reuse this key. Buyers correlate this surface with their own endpoint logs via this exact field; the spec deliberately reuses the payload key rather than minting a parallel `delivery_id` so callers do not need a join table. Format is sender-defined; callers MUST treat as opaque.' + ), + ] + notification_id: Annotated[ + str | None, + Field( + description='Optional event-layer identifier copied verbatim from the webhook payload. For fires whose payload carries `notification_id`, sellers SHOULD populate this field on newly recorded attempts and, when populated, MUST copy the payload value without transformation. The field remains optional in AdCP 3.2 so sellers can return retained activity records created before they persisted event identity. Point-in-time delivery-report fires do not define `notification_id` and omit this field. Re-emissions of the same logical event have different `idempotency_key` values but reuse `notification_id`, allowing buyers to distinguish a re-emission from a transport retry. Callers MUST treat the value as opaque.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] = None + subscriber_id: Annotated[ + str | None, + Field( + description='Identifies which registered webhook subscriber received this fire. **Required on records from account-anchored notification channels** (`notification_configs[]` registered via `sync_accounts`) — every subscriber has a `subscriber_id` at registration time, the seller MUST echo it on every fire and every activity record. **Optional on records from per-resource push channels** (`push_notification_config` on a media buy or task) — the calling principal is unambiguous in single-subscriber configurations and the field MAY be omitted; sellers MUST populate it once `reporting_webhook` adopts multi-subscriber (per #3009 in AdCP 4.0). Buyers MUST NOT use absence as a signal that no other subscribers exist; that information is not exposed by this surface.' + ), + ] = None + fired_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the seller initiated the HTTP request for this attempt.' + ), + ] + completed_at: Annotated[ + AwareDatetime | None, + Field( + description="ISO 8601 timestamp when the seller observed the response (or terminal timeout / connection error — for `timeout` and `connection_error` outcomes, `completed_at` is set to the moment the seller declared the attempt terminal). Explicitly `null` when the attempt is still in flight or queued for retry (status `pending`); MUST be set as `null` rather than omitted so callers can distinguish 'still in flight' from 'field missing'." + ), + ] = None + notification_type: Annotated[ + notification_type_1.NotificationType, + Field( + description='Notification type carried by this fire, verbatim from the webhook payload. Includes delivery-report types (`scheduled`, `final`, `delayed`, `adjusted`, `window_update`), health notifications (`impairment`), and account-anchored indicator or creative-assignment invalidations from the shared registry. All share the same persistent-channel webhook contract and the same buyer-side debug need.' + ), + ] + sequence_number: Annotated[ + SchemaInt | None, + Field( + description='Sequence number from the webhook payload. Surfaced here so the buyer can spot stale-sequence drops and gaps without correlating against their own endpoint log. Absent for notification types that do not carry a sequence number.', + ge=0, + ), + ] = None + attempt: Annotated[ + SchemaInt, + Field( + description='1-indexed retry counter for this logical fire. Initial fire is attempt=1; retries increment. Sellers MUST emit one record per attempt, so a successful first-attempt fire appears as a single record with `attempt: 1` and a 3-attempt retry trail appears as three records sharing `idempotency_key`.', + ge=1, + ), + ] + status: Annotated[ + Status, + Field( + description="Outcome of this attempt. `success` — response received with 2xx (`http_status_code` populated). `failed` — response received with non-2xx (`http_status_code` populated). `timeout` — no response within the seller's configured timeout (`http_status_code` null). `connection_error` — DNS / TLS / socket failure before any HTTP response (`http_status_code` null). `pending` — attempt is in flight or queued for retry (`completed_at` null, `http_status_code` null). The `timeout` / `connection_error` split is intentional and operationally distinct: `timeout` typically signals a slow / overloaded buyer endpoint, `connection_error` typically signals it is unreachable or misconfigured." + ), + ] + url: Annotated[ + AnyUrl, + Field( + description='Target URL for this fire. Query string and fragment MUST be stripped before surfacing — buyers commonly stash bearer tokens in the query string and sellers MUST NOT echo those back through this debug surface. Sellers SHOULD additionally redact path segments matching obvious secret patterns (e.g., a path segment that is high-entropy random material or matches a UUID / token format). Buyers matching this against their own configured URL should compare by origin + path; query strings will not match and that mismatch is expected.' + ), + ] + http_status_code: Annotated[ + SchemaInt | None, + Field( + description="HTTP status code returned by the buyer's endpoint. Explicitly `null` when no HTTP response was received (status `timeout`, `connection_error`, or `pending`); MUST be set as `null` rather than omitted.", + ge=100, + le=599, + ), + ] = None + response_time_ms: Annotated[ + SchemaInt | None, + Field( + description='Wall-clock latency between request send and response receipt, in milliseconds. Explicitly `null` when the attempt did not complete (`timeout`, `connection_error`, `pending`); MUST be set as `null` rather than omitted.', + ge=0, + ), + ] = None + payload_size_bytes: Annotated[ + SchemaInt | None, + Field( + description="Size of the request body the seller sent, in bytes. Useful for diagnosing oversized-payload rejections from the buyer's gateway.", + ge=0, + ), + ] = None + error_message: Annotated[ + str | None, + Field( + description='Short human-readable server-side classification of why this attempt did not succeed (e.g., `connection refused`, `TLS handshake timeout`, `HTTP 503 Service Unavailable`). Explicitly `null` for `success` (MUST be set as `null` rather than omitted). Sellers MUST NOT include request headers, request body content, or response body content in this field — payload surfacing is reserved for a future `include_webhook_payloads` extension and is subject to stricter access controls. Sellers SHOULD also avoid including buyer-endpoint internal hostnames, stack traces, or other implementation detail leaked by the response — keep it a stable classification string.', + max_length=500, + ), + ] = None + ext: Annotated[ + ext_1.ExtensionObject | None, + Field( + description='Resource-specific extension slot. Adopters MAY surface a resource-specific cross-reference (e.g., `creative_id` on a creative-lifecycle record, `media_buy_id` on a record nested inside an account-level read) under `ext` rather than adding top-level fields — the canonical record shape stays uniform across resources and the `ext` envelope absorbs per-resource needs. Top-level extensions are not permitted (`additionalProperties: false`).' + ), + ] = None diff --git a/src/adcp/types/domains/core/webhook_challenge.py b/src/adcp/types/domains/core/webhook_challenge.py index bf234ea18..5823f7f0e 100644 --- a/src/adcp/types/domains/core/webhook_challenge.py +++ b/src/adcp/types/domains/core/webhook_challenge.py @@ -1,18 +1,91 @@ -"""Types declared by the AdCP ``core/webhook_challenge`` schema. +# generated by datamodel-codegen: +# filename: core/webhook_challenge.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.webhook_challenge import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import notification_type + + +class Mode(StrEnum): + rfc9421 = 'rfc9421' + Bearer = 'Bearer' + HMAC_SHA256 = 'HMAC-SHA256' + + +class DeliveryAuth(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + mode: Annotated[ + Mode, + Field( + description='Future webhook delivery authentication mode. `rfc9421` is used when notification_configs[].authentication is absent; Bearer and HMAC-SHA256 are the deprecated legacy modes selected by notification_configs[].authentication.' + ), + ] + credential_fingerprint: Annotated[ + str | None, + Field( + description='SHA-256 hex fingerprint of the exact legacy credential string supplied in notification_configs[].authentication.credentials. Required when mode is Bearer or HMAC-SHA256; absent for rfc9421.', + pattern='^[a-f0-9]{64}$', + ), + ] = None -from adcp.types.generated_poc.core.webhook_challenge import DeliveryAuth, Mode, WebhookChallenge -# Explicit exports -__all__ = ["DeliveryAuth", "Mode", "WebhookChallenge"] +class WebhookChallenge(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Annotated[ + Literal['webhook.challenge'], + Field(description='Discriminator for endpoint proof-of-control challenges.'), + ] = 'webhook.challenge' + challenge: Annotated[ + str, + Field( + description='Opaque, cryptographically random value that the receiver must echo in the response body. Recommended encoding: base64url without padding.', + max_length=255, + min_length=32, + pattern='^[A-Za-z0-9_.:-]{32,255}$', + ), + ] + account_id: Annotated[ + str, + Field( + description='Seller account identifier for the account whose notification_configs[] entry is being challenged.' + ), + ] + subscriber_id: Annotated[ + str, + Field( + description='Buyer-supplied subscriber identifier from the notification_configs[] entry being challenged.', + max_length=64, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,64}$', + ), + ] + seller_agent_url: Annotated[ + AnyUrl, + Field( + description='Exact seller agent URL whose RFC 9421 webhook profile key signs this challenge and that will send subsequent webhooks.' + ), + ] + delivery_auth: Annotated[ + DeliveryAuth, + Field( + description='Authentication/signing mode the seller will use for subsequent webhooks delivered to this notification config.' + ), + ] + event_types: Annotated[ + list[notification_type.NotificationType], + Field( + description='Normalized notification types requested by the subscriber at the time of the challenge. Part of the endpoint proof scope; changing event_types[] requires a fresh challenge before the new set can become active.', + min_length=1, + ), + ] diff --git a/src/adcp/types/domains/core/webhook_challenge_response.py b/src/adcp/types/domains/core/webhook_challenge_response.py index 8e92ac5c8..00e1dfba7 100644 --- a/src/adcp/types/domains/core/webhook_challenge_response.py +++ b/src/adcp/types/domains/core/webhook_challenge_response.py @@ -1,18 +1,47 @@ -"""Types declared by the AdCP ``core/webhook_challenge_response`` schema. +# generated by datamodel-codegen: +# filename: core/webhook_challenge_response.json +# timestamp: 2026-10-04T01:14:11+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.webhook_challenge_response import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, model_validator -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.webhook_challenge_response import WebhookChallengeResponse +class WebhookChallengeResponse(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + challenge: Annotated[ + str | None, + Field( + description='Echo of the challenge value supplied by the seller.', + max_length=255, + min_length=32, + pattern='^[A-Za-z0-9_.:-]{32,255}$', + ), + ] = None + token: Annotated[ + str | None, + Field( + description='Backward-compatible alias for `challenge`. Receivers SHOULD prefer `challenge`; sellers MUST accept either field.', + max_length=255, + min_length=32, + pattern='^[A-Za-z0-9_.:-]{32,255}$', + ), + ] = None -# Explicit exports -__all__ = ["WebhookChallengeResponse"] + @model_validator(mode='after') + def _require_schema_required_group(self) -> WebhookChallengeResponse: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('challenge',), ('token',),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'WebhookChallengeResponse requires at least one of these field groups: challenge | token' + ) diff --git a/src/adcp/types/domains/core/wholesale_feed_event.py b/src/adcp/types/domains/core/wholesale_feed_event.py index ec699858e..2d23ce13f 100644 --- a/src/adcp/types/domains/core/wholesale_feed_event.py +++ b/src/adcp/types/domains/core/wholesale_feed_event.py @@ -1,76 +1,688 @@ -"""Types declared by the AdCP ``core/wholesale_feed_event`` schema. +# generated by datamodel-codegen: +# filename: core/wholesale_feed_event.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.wholesale_feed_event import +from adcp.types._str_enum import StrEnum +from collections.abc import Sequence +from typing import Annotated, Literal +from uuid import UUID -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-04 01:19:07 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat + +from ..enums import signal_catalog_type, signal_value_type +from . import canonical_pricing_option +from . import canonical_product as canonical_product_1 +from . import deployment, pricing_option +from . import product as product_1 +from . import signal_coverage_forecast +from . import signal_id as signal_id_1 +from . import signal_ref as signal_ref_1 +from . import vendor_pricing_option +from .signal_listing import SignalListing + + +class EventType(StrEnum): + product_created = 'product.created' + product_updated = 'product.updated' + product_priced = 'product.priced' + product_removed = 'product.removed' + signal_created = 'signal.created' + signal_updated = 'signal.updated' + signal_priced = 'signal.priced' + signal_removed = 'signal.removed' + wholesale_feed_bulk_change = 'wholesale_feed.bulk_change' + + +class EntityType(StrEnum): + product = 'product' + signal = 'signal' + feed = 'feed' + + +class AffectedEntityType(StrEnum): + product = 'product' + signal = 'signal' + + +class AppliesTo1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + scope: Literal['public'] = 'public' + + +class AppliesTo2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + scope: Literal['account'] = 'account' + account_ids: Annotated[ + list[str] | None, + Field( + description="Optional. When present, names the accounts whose overlays are affected. When omitted, subscribers infer that their own principal's overlay is affected (they received the event because the per-subscriber scope filter routed it to them).", + min_length=1, + ), + ] = None + + +AppliesTo = Annotated[ + AppliesTo1 | AppliesTo2, + Field( + description='Declares which cache layer this event invalidates. See specs/wholesale-feed-webhooks.md §Cache layering and event scoping.', + discriminator='scope', + title='Cache layer scope', + ), +] + + +class RemovalReason(StrEnum): + withdrawn = 'withdrawn' + cancellation = 'cancellation' + expired = 'expired' + depublication = 'depublication' + policy_takedown = 'policy_takedown' + + +class Range(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + min: StrictFloat + max: StrictFloat + + +class Payload(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + product_id: str + pricing_options: Annotated[ + list[pricing_option.PricingOption] | None, + Field( + description='Full post-change pricing_options array. NOT a delta — consumers replace the prior options with this list.', + min_length=1, + ), + ] = None + canonical_pricing_options: Annotated[ + list[canonical_pricing_option.CanonicalPricingOption] | None, + Field( + description='Full post-change canonical pricing array for list_products mirrors.', + min_length=1, + ), + ] = None + previous_pricing_option_ids: Annotated[ + list[str] | None, + Field(description='Optional. Lets consumers detect which pricing_option_ids were retired.'), + ] = None + effective_at: Annotated[ + AwareDatetime | None, + Field( + description='When the price change takes effect. A value in the future is a pre-announcement: consumers MAY warm caches but MUST NOT bind decisions until the effective time has passed. See specs/wholesale-feed-webhooks.md §`effective_at` and pre-announce.' + ), + ] = None + retracts_event_id: Annotated[ + UUID | None, + Field( + description="Optional. When this event retracts a prior pre-announced *.priced (per the §`effective_at` and pre-announce rules), set this to the event_id of the announcement being retracted. Lets consumers detect retraction deterministically by event_id lookup rather than maintaining per-entity 'latest-event' state. When present, the retraction MUST carry effective_at <= the retracted event's effective_at." + ), + ] = None + applies_to: AppliesTo + + +class WholesaleFeedEvent3(AdCPBaseModel): + event_id: Annotated[ + UUID, + Field( + description='Stable logical event identifier for this wholesale feed change. MUST equal the enclosing webhook notification_id. UUID v7 is RECOMMENDED so receivers can detect obvious out-of-order delivery; missed or distrusted pushes are repaired through list_products / get_signals.' + ), + ] + event_type: Annotated[ + Literal['product.priced'], + Field(description='Discriminator. Determines the shape of `payload`.'), + ] = 'product.priced' + entity_type: Annotated[ + Literal['product'], + Field( + description="Entity class. 'product' for product.* events, 'signal' for signal.* events, 'feed' for wholesale_feed.bulk_change." + ), + ] = 'product' + entity_id: Annotated[ + str, + Field( + description='Entity identifier. For product.* events, the product_id. For signal.* events, the signal_agent_segment_id. For wholesale_feed.bulk_change, an agent-defined operation id.' + ), + ] + created_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the agent emitted the event. Advisory — consumers MUST order by event_id (UUID v7), not by created_at, to avoid clock-skew artifacts.' + ), + ] + payload: Annotated[ + Payload, + Field( + description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' + ), + ] + + +class Payload20(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + product_id: str + removal_reason: RemovalReason | None = None + applies_to: AppliesTo + + +class WholesaleFeedEvent4(AdCPBaseModel): + event_id: Annotated[ + UUID, + Field( + description='Stable logical event identifier for this wholesale feed change. MUST equal the enclosing webhook notification_id. UUID v7 is RECOMMENDED so receivers can detect obvious out-of-order delivery; missed or distrusted pushes are repaired through list_products / get_signals.' + ), + ] + event_type: Annotated[ + Literal['product.removed'], + Field(description='Discriminator. Determines the shape of `payload`.'), + ] = 'product.removed' + entity_type: Annotated[ + Literal['product'], + Field( + description="Entity class. 'product' for product.* events, 'signal' for signal.* events, 'feed' for wholesale_feed.bulk_change." + ), + ] = 'product' + entity_id: Annotated[ + str, + Field( + description='Entity identifier. For product.* events, the product_id. For signal.* events, the signal_agent_segment_id. For wholesale_feed.bulk_change, an agent-defined operation id.' + ), + ] + created_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the agent emitted the event. Advisory — consumers MUST order by event_id (UUID v7), not by created_at, to avoid clock-skew artifacts.' + ), + ] + payload: Annotated[ + Payload20, + Field( + description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' + ), + ] + + +class Payload23(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + signal_agent_segment_id: str + signal_ref: signal_ref_1.SignalRef | None = None + pricing_options: Annotated[ + list[vendor_pricing_option.VendorPricingOption], + Field(description='Full post-change pricing_options array. NOT a delta.', min_length=1), + ] + previous_pricing_option_ids: list[str] | None = None + effective_at: Annotated[ + AwareDatetime | None, + Field( + description='When the price change takes effect. A value in the future is a pre-announcement: consumers MAY warm caches but MUST NOT bind decisions until the effective time has passed. See specs/wholesale-feed-webhooks.md §`effective_at` and pre-announce.' + ), + ] = None + retracts_event_id: Annotated[ + UUID | None, + Field( + description='Optional. When this event retracts a prior pre-announced *.priced, set this to the event_id of the announcement being retracted. See product.priced for the full retraction contract.' + ), + ] = None + applies_to: AppliesTo + + +class WholesaleFeedEvent7(AdCPBaseModel): + event_id: Annotated[ + UUID, + Field( + description='Stable logical event identifier for this wholesale feed change. MUST equal the enclosing webhook notification_id. UUID v7 is RECOMMENDED so receivers can detect obvious out-of-order delivery; missed or distrusted pushes are repaired through list_products / get_signals.' + ), + ] + event_type: Annotated[ + Literal['signal.priced'], + Field(description='Discriminator. Determines the shape of `payload`.'), + ] = 'signal.priced' + entity_type: Annotated[ + Literal['signal'], + Field( + description="Entity class. 'product' for product.* events, 'signal' for signal.* events, 'feed' for wholesale_feed.bulk_change." + ), + ] = 'signal' + entity_id: Annotated[ + str, + Field( + description='Entity identifier. For product.* events, the product_id. For signal.* events, the signal_agent_segment_id. For wholesale_feed.bulk_change, an agent-defined operation id.' + ), + ] + created_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the agent emitted the event. Advisory — consumers MUST order by event_id (UUID v7), not by created_at, to avoid clock-skew artifacts.' + ), + ] + payload: Annotated[ + Payload23, + Field( + description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' + ), + ] + + +class Payload24(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + signal_agent_segment_id: str + signal_ref: signal_ref_1.SignalRef | None = None + removal_reason: RemovalReason | None = None + applies_to: AppliesTo + + +class WholesaleFeedEvent8(AdCPBaseModel): + event_id: Annotated[ + UUID, + Field( + description='Stable logical event identifier for this wholesale feed change. MUST equal the enclosing webhook notification_id. UUID v7 is RECOMMENDED so receivers can detect obvious out-of-order delivery; missed or distrusted pushes are repaired through list_products / get_signals.' + ), + ] + event_type: Annotated[ + Literal['signal.removed'], + Field(description='Discriminator. Determines the shape of `payload`.'), + ] = 'signal.removed' + entity_type: Annotated[ + Literal['signal'], + Field( + description="Entity class. 'product' for product.* events, 'signal' for signal.* events, 'feed' for wholesale_feed.bulk_change." + ), + ] = 'signal' + entity_id: Annotated[ + str, + Field( + description='Entity identifier. For product.* events, the product_id. For signal.* events, the signal_agent_segment_id. For wholesale_feed.bulk_change, an agent-defined operation id.' + ), + ] + created_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the agent emitted the event. Advisory — consumers MUST order by event_id (UUID v7), not by created_at, to avoid clock-skew artifacts.' + ), + ] + payload: Annotated[ + Payload24, + Field( + description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' + ), + ] + + +class Payload25(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + summary: Annotated[ + str, + Field( + description="Human-readable description of the bulk operation (e.g., 'Q3 2026 rate card refresh')." + ), + ] + affected_count: Annotated[ + SchemaInt, Field(description='Approximate count of affected entities.', ge=1) + ] + recommendation: Annotated[ + Literal['wholesale_resync'] | None, + Field( + description='Advisory recommendation. Consumers SHOULD repair by re-reading the affected feed named by affected_entity_type; the recommendation is not a synchronized trigger. Modeled as a single-value enum (not a boolean) so future minor versions can extend the recommendation vocabulary without a breaking shape change. Consumers MUST be prepared to ignore unknown recommendation values.' + ), + ] = None + applies_to: AppliesTo + affected_entity_type: Annotated[ + AffectedEntityType, + Field( + description='Which wholesale feed this bulk operation touched. A bulk operation that changes both products and signals MUST emit one webhook per feed so each envelope carries the correct post-change wholesale_feed_version.' + ), + ] + + +class WholesaleFeedEvent9(AdCPBaseModel): + event_id: Annotated[ + UUID, + Field( + description='Stable logical event identifier for this wholesale feed change. MUST equal the enclosing webhook notification_id. UUID v7 is RECOMMENDED so receivers can detect obvious out-of-order delivery; missed or distrusted pushes are repaired through list_products / get_signals.' + ), + ] + event_type: Annotated[ + Literal['wholesale_feed.bulk_change'], + Field(description='Discriminator. Determines the shape of `payload`.'), + ] = 'wholesale_feed.bulk_change' + entity_type: Annotated[ + Literal['feed'], + Field( + description="Entity class. 'product' for product.* events, 'signal' for signal.* events, 'feed' for wholesale_feed.bulk_change." + ), + ] = 'feed' + entity_id: Annotated[ + str, + Field( + description='Entity identifier. For product.* events, the product_id. For signal.* events, the signal_agent_segment_id. For wholesale_feed.bulk_change, an agent-defined operation id.' + ), + ] + created_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the agent emitted the event. Advisory — consumers MUST order by event_id (UUID v7), not by created_at, to avoid clock-skew artifacts.' + ), + ] + payload: Annotated[ + Payload25, + Field( + description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' + ), + ] + + +class Signal(SignalListing): + model_config = ConfigDict( + extra='allow', + ) + signal_ref: Annotated[ + signal_ref_1.SignalRef | None, + Field( + description='Canonical signal reference for this wholesale signal. New events SHOULD use signal_ref.' + ), + ] = None + signal_id: Annotated[ + signal_id_1.SignalId | None, + Field( + deprecated=True, + description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', + ), + ] = None + signal_agent_segment_id: Annotated[ + str, + Field(description='Opaque activation handle returned by the signals agent.', min_length=1), + ] + name: Annotated[str, Field(description='Human-readable signal name', min_length=1)] + description: Annotated[str, Field(description='Detailed signal description', min_length=1)] + value_type: signal_value_type.SignalValueType | None = None + categories: Annotated[list[str] | None, Field(min_length=1)] = None + range: Range | None = None + signal_type: signal_catalog_type.SignalAvailabilityType + data_provider: Annotated[str | None, Field(min_length=1)] = None + coverage_percentage: Annotated[ + StrictFloat | None, + Field( + deprecated=True, + description='DEPRECATED for detailed planning. Optional legacy scalar percentage of audience coverage retained only as a fallback for clients that do not consume coverage_forecast. When coverage_forecast is present, coverage_forecast is authoritative for signal-level discovery and coverage_percentage is fallback-only.', + ge=0.0, + le=100.0, + ), + ] = None + coverage_forecast: Annotated[ + signal_coverage_forecast.SignalCoverageForecast | None, + Field( + description='Optional forecast-shaped signal availability guidance using the same wire shape as get_signals.signals[].coverage_forecast. When present, this is authoritative for signal-level discovery coverage.' + ), + ] = None + deployments: Annotated[Sequence[deployment.Deployment], Field(min_length=1)] + pricing_options: Annotated[ + list[vendor_pricing_option.VendorPricingOption] | None, Field(min_length=1) + ] = None + + +class Payload21(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + signal_agent_segment_id: str + signal_ref: signal_ref_1.SignalRef | None = None + applies_to: Annotated[ + AppliesTo, + Field( + description="REQUIRED. Agents MUST declare the cache layer explicitly on every *.created event. When introducing a signal that exists only in an account overlay (e.g., a custom segment for a single account), the agent MUST emit { scope: 'account', account_ids: [...] } to prevent leak into every consumer's public-layer cache. For public-layer additions, declare { scope: 'public' } explicitly rather than relying on a default — schema-required declaration prevents the quiet-failure path." + ), + ] + signal: Annotated[Signal, Field(description='Full post-change signal object.')] + + +class WholesaleFeedEvent5(AdCPBaseModel): + event_id: Annotated[ + UUID, + Field( + description='Stable logical event identifier for this wholesale feed change. MUST equal the enclosing webhook notification_id. UUID v7 is RECOMMENDED so receivers can detect obvious out-of-order delivery; missed or distrusted pushes are repaired through list_products / get_signals.' + ), + ] + event_type: Annotated[ + Literal['signal.created'], + Field(description='Discriminator. Determines the shape of `payload`.'), + ] = 'signal.created' + entity_type: Annotated[ + Literal['signal'], + Field( + description="Entity class. 'product' for product.* events, 'signal' for signal.* events, 'feed' for wholesale_feed.bulk_change." + ), + ] = 'signal' + entity_id: Annotated[ + str, + Field( + description='Entity identifier. For product.* events, the product_id. For signal.* events, the signal_agent_segment_id. For wholesale_feed.bulk_change, an agent-defined operation id.' + ), + ] + created_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the agent emitted the event. Advisory — consumers MUST order by event_id (UUID v7), not by created_at, to avoid clock-skew artifacts.' + ), + ] + payload: Annotated[ + Payload21, + Field( + description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' + ), + ] + + +class Payload22(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + signal_agent_segment_id: str + signal_ref: signal_ref_1.SignalRef | None = None + changed_fields: Annotated[ + list[str] | None, + Field( + description='Advisory list of changed top-level field names. Consumers MAY use for fine-grained re-render, but the signal object is the source of truth for this webhook payload.' + ), + ] = None + applies_to: AppliesTo + signal: Annotated[ + Signal, + Field( + description='Full post-change signal object. Consumers replace the prior signal mirror entry with this object.' + ), + ] + + +class WholesaleFeedEvent6(AdCPBaseModel): + event_id: Annotated[ + UUID, + Field( + description='Stable logical event identifier for this wholesale feed change. MUST equal the enclosing webhook notification_id. UUID v7 is RECOMMENDED so receivers can detect obvious out-of-order delivery; missed or distrusted pushes are repaired through list_products / get_signals.' + ), + ] + event_type: Annotated[ + Literal['signal.updated'], + Field(description='Discriminator. Determines the shape of `payload`.'), + ] = 'signal.updated' + entity_type: Annotated[ + Literal['signal'], + Field( + description="Entity class. 'product' for product.* events, 'signal' for signal.* events, 'feed' for wholesale_feed.bulk_change." + ), + ] = 'signal' + entity_id: Annotated[ + str, + Field( + description='Entity identifier. For product.* events, the product_id. For signal.* events, the signal_agent_segment_id. For wholesale_feed.bulk_change, an agent-defined operation id.' + ), + ] + created_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the agent emitted the event. Advisory — consumers MUST order by event_id (UUID v7), not by created_at, to avoid clock-skew artifacts.' + ), + ] + payload: Annotated[ + Payload22, + Field( + description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' + ), + ] + + +class Payload17(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + product_id: str + product: Annotated[ + product_1.Product | None, + Field( + description='Legacy 3.x post-change Product. Subscribers using get_products consume this form.' + ), + ] = None + canonical_product: Annotated[ + canonical_product_1.CanonicalProduct | None, + Field( + description='Canonical post-change Product for subscribers that negotiated lifecycle_tools.list_products. Consumers replace the matching canonical mirror entry directly.' + ), + ] = None + applies_to: Annotated[ + AppliesTo, + Field( + description="REQUIRED. Sellers MUST declare the cache layer explicitly on every *.created event. When introducing an entity that exists only in an account overlay (e.g., a custom product for a single account), the seller MUST emit { scope: 'account', account_ids: [...] } to prevent the entity from leaking into every consumer's public-layer cache. For public-layer additions, declare { scope: 'public' } explicitly rather than relying on a default — schema-required declaration prevents the quiet-failure path where a forgotten applies_to leaks an account-only entity to all consumers." + ), + ] + + +class WholesaleFeedEvent1(AdCPBaseModel): + event_id: Annotated[ + UUID, + Field( + description='Stable logical event identifier for this wholesale feed change. MUST equal the enclosing webhook notification_id. UUID v7 is RECOMMENDED so receivers can detect obvious out-of-order delivery; missed or distrusted pushes are repaired through list_products / get_signals.' + ), + ] + event_type: Annotated[ + Literal['product.created'], + Field(description='Discriminator. Determines the shape of `payload`.'), + ] = 'product.created' + entity_type: Annotated[ + Literal['product'], + Field( + description="Entity class. 'product' for product.* events, 'signal' for signal.* events, 'feed' for wholesale_feed.bulk_change." + ), + ] = 'product' + entity_id: Annotated[ + str, + Field( + description='Entity identifier. For product.* events, the product_id. For signal.* events, the signal_agent_segment_id. For wholesale_feed.bulk_change, an agent-defined operation id.' + ), + ] + created_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the agent emitted the event. Advisory — consumers MUST order by event_id (UUID v7), not by created_at, to avoid clock-skew artifacts.' + ), + ] + payload: Annotated[ + Payload17, + Field( + description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' + ), + ] + + +class Payload18(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + product_id: str + product: Annotated[ + product_1.Product | None, + Field(description='Legacy 3.x post-change Product for get_products mirrors.'), + ] = None + canonical_product: Annotated[ + canonical_product_1.CanonicalProduct | None, + Field(description='Canonical post-change Product for list_products mirrors.'), + ] = None + changed_fields: Annotated[ + list[str] | None, + Field( + description="Advisory list of changed top-level field names (e.g., ['format_ids', 'performance_standards']). Consumers MAY use for fine-grained re-render, but the product object is the source of truth for this webhook payload." + ), + ] = None + applies_to: AppliesTo + + +class WholesaleFeedEvent2(AdCPBaseModel): + event_id: Annotated[ + UUID, + Field( + description='Stable logical event identifier for this wholesale feed change. MUST equal the enclosing webhook notification_id. UUID v7 is RECOMMENDED so receivers can detect obvious out-of-order delivery; missed or distrusted pushes are repaired through list_products / get_signals.' + ), + ] + event_type: Annotated[ + Literal['product.updated'], + Field(description='Discriminator. Determines the shape of `payload`.'), + ] = 'product.updated' + entity_type: Annotated[ + Literal['product'], + Field( + description="Entity class. 'product' for product.* events, 'signal' for signal.* events, 'feed' for wholesale_feed.bulk_change." + ), + ] = 'product' + entity_id: Annotated[ + str, + Field( + description='Entity identifier. For product.* events, the product_id. For signal.* events, the signal_agent_segment_id. For wholesale_feed.bulk_change, an agent-defined operation id.' + ), + ] + created_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the agent emitted the event. Advisory — consumers MUST order by event_id (UUID v7), not by created_at, to avoid clock-skew artifacts.' + ), + ] + payload: Annotated[ + Payload18, + Field( + description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' + ), + ] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.core.wholesale_feed_event import ( - AffectedEntityType, - AppliesTo, - AppliesTo1, - AppliesTo2, - EntityType, - EventType, - Payload, - Payload17, - Payload18, - Payload20, - Payload21, - Payload22, - Payload23, - Payload24, - Payload25, - Range, - RemovalReason, - Signal, - WholesaleFeedEvent, - WholesaleFeedEvent1, - WholesaleFeedEvent2, - WholesaleFeedEvent3, - WholesaleFeedEvent4, - WholesaleFeedEvent5, - WholesaleFeedEvent6, - WholesaleFeedEvent7, - WholesaleFeedEvent8, - WholesaleFeedEvent9, -) - -# Explicit exports -__all__ = [ - "AffectedEntityType", - "AppliesTo", - "AppliesTo1", - "AppliesTo2", - "EntityType", - "EventType", - "Payload", - "Payload17", - "Payload18", - "Payload20", - "Payload21", - "Payload22", - "Payload23", - "Payload24", - "Payload25", - "Range", - "RemovalReason", - "Signal", - "WholesaleFeedEvent", - "WholesaleFeedEvent1", - "WholesaleFeedEvent2", - "WholesaleFeedEvent3", - "WholesaleFeedEvent4", - "WholesaleFeedEvent5", - "WholesaleFeedEvent6", - "WholesaleFeedEvent7", - "WholesaleFeedEvent8", - "WholesaleFeedEvent9", +WholesaleFeedEvent = Annotated[ + WholesaleFeedEvent1 + | WholesaleFeedEvent2 + | WholesaleFeedEvent3 + | WholesaleFeedEvent4 + | WholesaleFeedEvent5 + | WholesaleFeedEvent6 + | WholesaleFeedEvent7 + | WholesaleFeedEvent8 + | WholesaleFeedEvent9, + Field( + description="A single change event emitted by an AdCP agent's wholesale product feed or wholesale signals feed and delivered inside wholesale-feed-webhook payloads. Events are denormalized so consumers can update local state without polling list_products / get_signals. This is distinct from buyer-provided feeds managed by sync_catalogs. The discriminator is event_type; each branch defines the payload shape.", + discriminator='event_type', + title='Wholesale Feed Event', + ), ] diff --git a/src/adcp/types/domains/core/wholesale_feed_webhook.py b/src/adcp/types/domains/core/wholesale_feed_webhook.py index 90d921dc2..6a78e73dd 100644 --- a/src/adcp/types/domains/core/wholesale_feed_webhook.py +++ b/src/adcp/types/domains/core/wholesale_feed_webhook.py @@ -1,23 +1,116 @@ -"""Types declared by the AdCP ``core/wholesale_feed_webhook`` schema. +# generated by datamodel-codegen: +# filename: core/wholesale_feed_webhook.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.core.wholesale_feed_webhook import +from adcp.types._str_enum import StrEnum +from typing import Annotated +from uuid import UUID -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from . import ext as ext_1 +from . import wholesale_feed_event + + +class NotificationType(StrEnum): + product_created = 'product.created' + product_updated = 'product.updated' + product_priced = 'product.priced' + product_removed = 'product.removed' + signal_created = 'signal.created' + signal_updated = 'signal.updated' + signal_priced = 'signal.priced' + signal_removed = 'signal.removed' + wholesale_feed_bulk_change = 'wholesale_feed.bulk_change' + + +class ProductPayloadView(StrEnum): + canonical = 'canonical' + legacy = 'legacy' + + +class CacheScope(StrEnum): + public = 'public' + account = 'account' -from adcp.types.generated_poc.core.wholesale_feed_webhook import ( - CacheScope, - NotificationType, - ProductPayloadView, - WholesaleFeedWebhook, -) -# Explicit exports -__all__ = ["CacheScope", "NotificationType", "ProductPayloadView", "WholesaleFeedWebhook"] +class WholesaleFeedWebhook(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + idempotency_key: Annotated[ + str, + Field( + description='Sender-generated key stable across retries of the same webhook fire. Receivers MUST dedupe by this key, scoped to the authenticated sender identity.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + notification_id: Annotated[ + UUID, + Field( + description='Stable identifier for this logical wholesale feed event. MUST equal event.event_id. Re-emissions of the same logical event reuse this value under a new idempotency_key.' + ), + ] + notification_type: Annotated[ + NotificationType, + Field( + description='Wholesale feed notification type discriminator. MUST match event.event_type.' + ), + ] + fired_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the seller initiated this webhook fire. Distinct from event.created_at, which is when the seller observed or recorded the feed change.' + ), + ] + subscriber_id: Annotated[ + str, + Field( + description='Identifies which notification_configs[] entry is receiving this fire. Echoed from the registered subscriber_id.', + max_length=64, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,64}$', + ), + ] + account_id: Annotated[ + str, + Field( + description='Seller account identifier for the account scope that registered this webhook through sync_accounts.accounts[].notification_configs[]. Required because wholesale feed webhooks are account-anchored notifications.' + ), + ] + wholesale_feed_version: Annotated[ + str, + Field( + description='Opaque post-change version token for the affected wholesale feed. Store it only after applying the event. A stale mirror repairs with its last applied version; uncertain or bulk repair omits the conditional token.' + ), + ] + product_payload_view: Annotated[ + ProductPayloadView | None, + Field( + description='Product representation selected by the receiving notification config. Present on product.* fires; canonical uses canonical_product/canonical_pricing_options and legacy uses product/pricing_options.' + ), + ] = None + previous_wholesale_feed_version: Annotated[ + str | None, + Field( + description='Opaque version token for the affected wholesale feed before this change, when the seller can cheaply provide it. Receivers MAY use this to detect obvious gaps, but MUST NOT require it.' + ), + ] = None + cache_scope: Annotated[ + CacheScope, + Field( + description='Cache layer affected by this change. MUST equal event.payload.applies_to.scope. Mirrors the cache_scope returned by list_products / get_signals for the affected wholesale feed.' + ), + ] + event: Annotated[ + wholesale_feed_event.WholesaleFeedEvent, + Field( + description='The actual product, signal, or bulk-change event. Consumers MAY apply this payload to their local mirror. Before any binding action, or when ordering/gap checks fail, consumers MUST reconcile through list_products / get_signals.' + ), + ] + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/core/x_entity_types.py b/src/adcp/types/domains/core/x_entity_types.py index 547fd5433..d1865421d 100644 --- a/src/adcp/types/domains/core/x_entity_types.py +++ b/src/adcp/types/domains/core/x_entity_types.py @@ -1,18 +1,87 @@ -"""Types declared by the AdCP ``core/x_entity_types`` schema. +# generated by datamodel-codegen: +# filename: core/x_entity_types.json +# timestamp: 2026-09-24T08:59:48+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.core.x_entity_types import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.x_entity_types import XEntityTypes +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["XEntityTypes"] +class XEntityTypes(StrEnum): + advertiser_brand = 'advertiser_brand' + rights_holder_brand = 'rights_holder_brand' + rights_grant = 'rights_grant' + account = 'account' + operator = 'operator' + operator_unit = 'operator_unit' + media_buy = 'media_buy' + package = 'package' + product = 'product' + proposal = 'proposal' + opportunity = 'opportunity' + placement = 'placement' + product_pricing_option = 'product_pricing_option' + vendor_pricing_option = 'vendor_pricing_option' + creative = 'creative' + creative_revision = 'creative_revision' + creative_representation = 'creative_representation' + macro_declaration = 'macro_declaration' + tracker_execution_selector = 'tracker_execution_selector' + creative_locale_variant = 'creative_locale_variant' + creative_format = 'creative_format' + transformer = 'transformer' + evaluator = 'evaluator' + creative_evaluation = 'creative_evaluation' + build_variant = 'build_variant' + served_variant = 'served_variant' + audience = 'audience' + audience_evidence = 'audience_evidence' + audience_evidence_snapshot = 'audience_evidence_snapshot' + signal = 'signal' + signal_activation_id = 'signal_activation_id' + demographic_interval_id = 'demographic_interval_id' + spot_airing = 'spot_airing' + event_source = 'event_source' + impairment = 'impairment' + collection = 'collection' + installment = 'installment' + collection_list = 'collection_list' + property_list = 'property_list' + catalog = 'catalog' + catalog_generation = 'catalog_generation' + catalog_item = 'catalog_item' + property = 'property' + media_plan = 'media_plan' + governance_plan = 'governance_plan' + governance_registry_policy = 'governance_registry_policy' + governance_policy_category = 'governance_policy_category' + governance_policy_category_facet = 'governance_policy_category_facet' + acceptance_policy_profile = 'acceptance_policy_profile' + acceptance_policy_rule = 'acceptance_policy_rule' + media_buy_change_term = 'media_buy_change_term' + governance_inline_policy = 'governance_inline_policy' + governance_check = 'governance_check' + governance_delivery_statement = 'governance_delivery_statement' + governance_delivery_observation = 'governance_delivery_observation' + governance_outcome = 'governance_outcome' + governance_adjustment = 'governance_adjustment' + governance_adjustment_evidence = 'governance_adjustment_evidence' + seller_adjustment = 'seller_adjustment' + content_standards = 'content_standards' + task = 'task' + attestation_credential = 'attestation_credential' + si_session = 'si_session' + offering = 'offering' + vendor_metric = 'vendor_metric' + reporting_destination = 'reporting_destination' + reporting_offering = 'reporting_offering' + reporting_delivery_config = 'reporting_delivery_config' + reporting_definition = 'reporting_definition' + reporting_obligation = 'reporting_obligation' + reporting_revision = 'reporting_revision' + reporting_adjustment = 'reporting_adjustment' + reporting_materialization = 'reporting_materialization' + reporting_receipt = 'reporting_receipt' + reporting_consumer_status = 'reporting_consumer_status' + reporting_resource = 'reporting_resource' + identity_relying_party = 'identity_relying_party' diff --git a/src/adcp/types/domains/creative/__init__.py b/src/adcp/types/domains/creative/__init__.py index a46e62211..e250f48bc 100644 --- a/src/adcp/types/domains/creative/__init__.py +++ b/src/adcp/types/domains/creative/__init__.py @@ -9,71 +9,64 @@ it from its own schema's module, ``adcp.types.domains.creative.``. Nothing here is renamed. -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:33:59 UTC +Auto-generated from the generated domain tree. DO NOT EDIT MANUALLY. +Generation date: 2026-10-04 18:45:11 UTC """ # ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.creative.audit_observation import ( +from adcp.types.domains.creative.audit_observation import ( ClaimedValue, CreativeAuditObservation, Details, HumanOversight, ) -from adcp.types.generated_poc.creative.creative_assignment_changed_webhook import ( +from adcp.types.domains.creative.creative_assignment_changed_webhook import ( ChangeKind, CreativeAssignmentChangedWebhook, ) -from adcp.types.generated_poc.creative.creative_feature_result import CreativeFeatureResult -from adcp.types.generated_poc.creative.creative_purged_webhook import ( - CreativePurgedWebhook, - PurgeKind, -) -from adcp.types.generated_poc.creative.creative_status_changed_webhook import ( +from adcp.types.domains.creative.creative_feature_result import CreativeFeatureResult +from adcp.types.domains.creative.creative_purged_webhook import CreativePurgedWebhook, PurgeKind +from adcp.types.domains.creative.creative_status_changed_webhook import ( CreativeStatusChangedWebhook, From, Transition, ) -from adcp.types.generated_poc.creative.get_creative_delivery_request import ( - GetCreativeDeliveryRequest, -) -from adcp.types.generated_poc.creative.get_creative_delivery_response import ( +from adcp.types.domains.creative.get_creative_delivery_request import GetCreativeDeliveryRequest +from adcp.types.domains.creative.get_creative_delivery_response import ( GetCreativeDeliveryResponse, Pagination, ReportingPeriod, ) -from adcp.types.generated_poc.creative.get_creative_features_async_response_submitted import ( +from adcp.types.domains.creative.get_creative_features_async_response_submitted import ( GetCreativeFeaturesSubmitted, ) -from adcp.types.generated_poc.creative.get_creative_features_request import ( - GetCreativeFeaturesRequest, -) -from adcp.types.generated_poc.creative.get_creative_features_response import ( +from adcp.types.domains.creative.get_creative_features_request import GetCreativeFeaturesRequest +from adcp.types.domains.creative.get_creative_features_response import ( GetCreativeFeaturesResponse, GetCreativeFeaturesResponse1, GetCreativeFeaturesResponse2, GetCreativeFeaturesResponse3, ) -from adcp.types.generated_poc.creative.get_creative_features_terminal_success import ( +from adcp.types.domains.creative.get_creative_features_terminal_success import ( GetCreativeFeaturesSuccess, ) -from adcp.types.generated_poc.creative.list_creative_formats_request import ( +from adcp.types.domains.creative.list_creative_formats_request import ( ListCreativeFormatsRequestCreativeAgent, Type, ) -from adcp.types.generated_poc.creative.list_creative_formats_response import ( +from adcp.types.domains.creative.list_creative_formats_response import ( CreativeAgent, ListCreativeFormatsResponseCreativeAgent, ) -from adcp.types.generated_poc.creative.list_creatives_request import ( +from adcp.types.domains.creative.list_creatives_request import ( AssignmentProjection, Field1, ListCreativesRequest, Sort, ) -from adcp.types.generated_poc.creative.list_creatives_response import ( +from adcp.types.domains.creative.list_creatives_response import ( Assets, AssignedPackage, Assignments, @@ -89,21 +82,21 @@ SortApplied, StatusSummary, ) -from adcp.types.generated_poc.creative.list_transformers_request import ( +from adcp.types.domains.creative.list_transformers_request import ( ExpandPaginationItem, ListTransformersRequestCreativeAgent, OutputCapabilityId, ) -from adcp.types.generated_poc.creative.list_transformers_response import ( +from adcp.types.domains.creative.list_transformers_response import ( ListTransformersResponseCreativeAgent, ) -from adcp.types.generated_poc.creative.preview_creative_request import ( +from adcp.types.domains.creative.preview_creative_request import ( Input10, PreviewCreativeRequest, Request, RequestType, ) -from adcp.types.generated_poc.creative.preview_creative_response import ( +from adcp.types.domains.creative.preview_creative_response import ( Input2, Preview, Preview2, @@ -116,7 +109,7 @@ Response, Result, ) -from adcp.types.generated_poc.creative.preview_render import ( +from adcp.types.domains.creative.preview_render import ( Dimensions, Embedding, PreviewRender, @@ -124,17 +117,15 @@ PreviewRender2, PreviewRender3, ) -from adcp.types.generated_poc.creative.sync_creatives_async_response_input_required import ( +from adcp.types.domains.creative.sync_creatives_async_response_input_required import ( Reason, SyncCreativesInputRequired, ) -from adcp.types.generated_poc.creative.sync_creatives_async_response_submitted import ( +from adcp.types.domains.creative.sync_creatives_async_response_submitted import ( SyncCreativesSubmitted, ) -from adcp.types.generated_poc.creative.sync_creatives_async_response_working import ( - SyncCreativesWorking, -) -from adcp.types.generated_poc.creative.sync_creatives_request import ( +from adcp.types.domains.creative.sync_creatives_async_response_working import SyncCreativesWorking +from adcp.types.domains.creative.sync_creatives_request import ( Assignment, AssignmentOperations, AssignmentOperations1, @@ -143,13 +134,13 @@ PlacementId, SyncCreativesRequest, ) -from adcp.types.generated_poc.creative.sync_creatives_response import ( +from adcp.types.domains.creative.sync_creatives_response import ( SyncCreativesResponse, SyncCreativesResponse1, SyncCreativesResponse2, SyncCreativesResponse3, ) -from adcp.types.generated_poc.creative.validate_input_request import ( +from adcp.types.domains.creative.validate_input_request import ( Targets, Targets1, Targets2, @@ -157,8 +148,8 @@ Targets4, ValidateInputRequest, ) -from adcp.types.generated_poc.creative.validate_input_response import ValidateInputResponse -from adcp.types.generated_poc.creative.validate_input_result import ( +from adcp.types.domains.creative.validate_input_response import ValidateInputResponse +from adcp.types.domains.creative.validate_input_result import ( Kind, ResultKind, Target, @@ -166,7 +157,7 @@ Violation, Warning, ) -from adcp.types.generated_poc.creative.video_brief import Segment, VideoBrief +from adcp.types.domains.creative.video_brief import Segment, VideoBrief # Explicit exports __all__ = [ diff --git a/src/adcp/types/domains/creative/audit_observation.py b/src/adcp/types/domains/creative/audit_observation.py index 640aef801..ebe0e57b5 100644 --- a/src/adcp/types/domains/creative/audit_observation.py +++ b/src/adcp/types/domains/creative/audit_observation.py @@ -1,23 +1,107 @@ -"""Types declared by the AdCP ``creative/audit_observation`` schema. +# generated by datamodel-codegen: +# filename: creative/audit_observation.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.creative.audit_observation import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field, StrictBool, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import ext as ext_1 + + +class HumanOversight(StrEnum): + edited = 'edited' + directed = 'directed' + + +class ClaimedValue(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + human_oversight: Annotated[ + HumanOversight, + Field(description='Human oversight level declared by the creative provenance.'), + ] + disclosure_required: Annotated[ + Literal[False], + Field(description='Disclosure-required claim declared by the creative provenance.'), + ] + + +class Details(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + agent_url: Annotated[ + AnyUrl, Field(description='Governance agent URL that produced the observation.') + ] + feature_id: Annotated[ + str | None, Field(description='Feature or policy check that produced the observation.') + ] = None + claimed_value: Annotated[ + ClaimedValue, + Field( + description='Compact object of claimed provenance values that triggered the observation.' + ), + ] + observed_value: Annotated[ + StrictBool | StrictFloat | str | None, + Field(description='Verifier observation relevant to the claim, when applicable.'), + ] = None + confidence: Annotated[ + StrictFloat | None, + Field(description='Confidence score for the observation, when applicable.', ge=0.0, le=1.0), + ] = None + substituted_for: Annotated[ + AnyUrl | None, + Field( + description='Buyer-nominated verifier URL when the seller or orchestrator used a different on-list governance agent.' + ), + ] = None -from adcp.types.generated_poc.creative.audit_observation import ( - ClaimedValue, - CreativeAuditObservation, - Details, - HumanOversight, -) -# Explicit exports -__all__ = ["ClaimedValue", "CreativeAuditObservation", "Details", "HumanOversight"] +class CreativeAuditObservation(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + code: Annotated[ + Literal['OVERSIGHT_DISCLOSURE_CARVEOUT_CLAIMED'], + Field( + description='Machine-readable observation code. `OVERSIGHT_DISCLOSURE_CARVEOUT_CLAIMED` means provenance declares `human_oversight` as `edited` or `directed` while also declaring `disclosure.required: false`; the verifier is surfacing the carve-out claim for audit, not adjudicating it.' + ), + ] = 'OVERSIGHT_DISCLOSURE_CARVEOUT_CLAIMED' + severity: Annotated[ + Literal['audit-worthy'], + Field( + description='Routing severity. `audit-worthy` means the observation should be retained and may be routed to human or downstream audit review, but it is not a protocol rejection signal.' + ), + ] = 'audit-worthy' + recovery: Annotated[ + Literal['informational'], + Field( + description='Caller recovery category for audit observations, distinct from the canonical error-code recovery enum. `informational` means the creative can continue through the normal flow; the observation is audit context rather than a required correction.' + ), + ] = 'informational' + field: Annotated[ + str, + Field( + description='Resolved creative manifest path for the risky claim side of the observation, for example `creative_manifest.provenance.disclosure.required`. Some observations are triggered by a combination of fields; `field` anchors the primary claim, not necessarily every field in the trigger condition.' + ), + ] + message: Annotated[ + str, + Field( + description='Human-readable summary suitable for an audit queue. Do not include PII, cross-tenant data, or vendor-only report details.' + ), + ] + details: Annotated[ + Details, + Field( + description='Audit-safe structured details. Mirrors the safe allowlist keys used for `PROVENANCE_CLAIM_CONTRADICTED`; value shapes remain observation-specific. Top-level `ext` remains the standard protocol extension point, but details do not allow arbitrary verifier response fields.' + ), + ] + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/creative/creative_assignment_changed_webhook.py b/src/adcp/types/domains/creative/creative_assignment_changed_webhook.py index 0df103fc7..5f4cc9518 100644 --- a/src/adcp/types/domains/creative/creative_assignment_changed_webhook.py +++ b/src/adcp/types/domains/creative/creative_assignment_changed_webhook.py @@ -1,21 +1,49 @@ -"""Types declared by the AdCP ``creative/creative_assignment_changed_webhook`` schema. +# generated by datamodel-codegen: +# filename: creative/creative_assignment_changed_webhook.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.creative.creative_assignment_changed_webhook import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import ext as ext_1 + + +class ChangeKind(StrEnum): + assigned = 'assigned' + unassigned = 'unassigned' + approval_changed = 'approval_changed' -from adcp.types.generated_poc.creative.creative_assignment_changed_webhook import ( - ChangeKind, - CreativeAssignmentChangedWebhook, -) -# Explicit exports -__all__ = ["ChangeKind", "CreativeAssignmentChangedWebhook"] +class CreativeAssignmentChangedWebhook(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + idempotency_key: Annotated[ + str, Field(max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$') + ] + notification_id: Annotated[ + str, + Field( + description='Stable identity for this logical assignment change across re-emissions.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] + notification_type: Literal['creative.assignment_changed'] = 'creative.assignment_changed' + fired_at: AwareDatetime + subscriber_id: Annotated[ + str, Field(max_length=64, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,64}$') + ] + account_id: str + media_buy_id: str + package_id: str + creative_id: str + change_kind: ChangeKind + observed_at: AwareDatetime + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/creative/creative_feature_result.py b/src/adcp/types/domains/creative/creative_feature_result.py index 1e6c0dd79..51210c409 100644 --- a/src/adcp/types/domains/creative/creative_feature_result.py +++ b/src/adcp/types/domains/creative/creative_feature_result.py @@ -1,18 +1,61 @@ -"""Types declared by the AdCP ``creative/creative_feature_result`` schema. +# generated by datamodel-codegen: +# filename: creative/creative_feature_result.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.creative.creative_feature_result import +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import ext as ext_1 -from adcp.types.generated_poc.creative.creative_feature_result import CreativeFeatureResult -# Explicit exports -__all__ = ["CreativeFeatureResult"] +class CreativeFeatureResult(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + feature_id: Annotated[ + str, + Field( + description="The feature that was evaluated (e.g., 'auto_redirect', 'brand_consistency'). Features prefixed with 'registry:' reference standardized policies from the shared policy registry (e.g., 'registry:eu_ai_act_article_50'). Unprefixed feature IDs are agent-defined." + ), + ] + value: Annotated[ + StrictBool | StrictFloat | str, + Field( + description='The feature value. Type depends on feature definition: boolean for binary, number for quantitative, string for categorical.' + ), + ] + unit: Annotated[ + str | None, + Field( + description="Unit of measurement for quantitative values (e.g., 'percentage', 'score')" + ), + ] = None + confidence: Annotated[ + StrictFloat | None, + Field(description='Confidence score for this value (0-1)', ge=0.0, le=1.0), + ] = None + measured_at: Annotated[ + AwareDatetime | None, Field(description='When this feature was evaluated') + ] = None + expires_at: Annotated[ + AwareDatetime | None, + Field(description='When this evaluation expires and should be refreshed'), + ] = None + methodology_version: Annotated[ + str | None, Field(description='Version of the methodology used to evaluate this feature') + ] = None + details: Annotated[ + dict[str, Any] | None, + Field(description='Additional vendor-specific details about this evaluation'), + ] = None + policy_id: Annotated[ + str | None, + Field( + description='Optional attribution — when this feature was evaluated for the purpose of a specific policy, policy_id references the authorizing PolicyEntry. Creative agents and sellers populate when the measurement was motivated by a specific policy; do NOT populate when the feature is a generic measurement (carbon score, brand consistency) unrelated to any policy. See /docs/governance/policy-attribution.' + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/creative/creative_purged_webhook.py b/src/adcp/types/domains/creative/creative_purged_webhook.py index 146fef0fc..340bc8e66 100644 --- a/src/adcp/types/domains/creative/creative_purged_webhook.py +++ b/src/adcp/types/domains/creative/creative_purged_webhook.py @@ -1,22 +1,108 @@ -"""Types declared by the AdCP ``creative/creative_purged_webhook`` schema. +# generated by datamodel-codegen: +# filename: creative/creative_purged_webhook.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.creative.creative_purged_webhook import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import ext as ext_1 +from ..enums import creative_event_reason_code + + +class PurgeKind(StrEnum): + soft = 'soft' + hard = 'hard' + + +class Initiator(StrEnum): + seller = 'seller' + system = 'system' -from adcp.types.generated_poc.creative.creative_purged_webhook import ( - CreativePurgedWebhook, - Initiator, - PurgeKind, -) -# Explicit exports -__all__ = ["CreativePurgedWebhook", "Initiator", "PurgeKind"] +class CreativePurgedWebhook(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + idempotency_key: Annotated[ + str, + Field( + description='Sender-generated key stable across retries of the same fire.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + notification_id: Annotated[ + str, + Field( + description='Stable identifier for this purge event. Receivers correlate fires by this id; a single creative can only be purged once, so a new id implies a different creative or (rare) a re-emission of the same purge after retention-driven snapshot loss.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] + notification_type: Annotated[ + Literal['creative.purged'], Field(description='Fixed notification type discriminator.') + ] = 'creative.purged' + fired_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the seller initiated this fire. Sellers MUST fire `creative.purged` immediately after purge — coalescence is not permitted on this notification type.' + ), + ] + subscriber_id: Annotated[ + str, + Field( + description='Identifies which `notification_configs[]` entry on the recipient account is receiving this fire.', + max_length=64, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,64}$', + ), + ] + account_id: Annotated[ + str, + Field(description="Seller's identifier for the account that owned the purged creative."), + ] + creative_id: Annotated[ + str, + Field( + description="Seller's identifier for the purged creative. After a `soft` purge this id resolves on `list_creatives` (with `include_purged: true`) for the seller's tombstone retention window; after a `hard` purge no record remains." + ), + ] + purge_kind: Annotated[ + PurgeKind, + Field( + description="`soft` — tombstone retained on `list_creatives` (with `include_purged: true`) for the seller's retention window (default 30 days, MUST match the `webhook_activity[]` retention rule). `hard` — no tombstone; the seller MUST NOT retain or echo the asset content elsewhere. `hard` is reserved for legal erasure or equivalent compelled destruction; sellers SHOULD default to `soft`." + ), + ] + purged_at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the seller observed the destruction. Distinct from `fired_at`.' + ), + ] + reason_code: Annotated[ + creative_event_reason_code.CreativeEventReasonCode, + Field( + description='Categorical reason for the purge. Typical values: `retention_expired`, `legal_erasure`, `takedown_request`. Receivers MUST treat unknown reason codes as forward-compatible additions.' + ), + ] + reason_detail: Annotated[ + str | None, + Field( + description='Human-readable supplement. Sellers MUST NOT include third-party PII or the asset content in this field — the field is meant for operational context, not asset preservation.', + max_length=500, + ), + ] = None + initiator: Annotated[ + Initiator, + Field( + description='Who initiated the purge. `seller` — explicit decision (legal team, content policy, takedown handler). `system` — automated retention sweep, storage policy enforcement. `buyer` never appears on this event — buyer-initiated destruction (if and when supported) is acknowledged on the `sync_creatives` response path.' + ), + ] + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/creative/creative_status_changed_webhook.py b/src/adcp/types/domains/creative/creative_status_changed_webhook.py index b35c7faf4..21e563e53 100644 --- a/src/adcp/types/domains/creative/creative_status_changed_webhook.py +++ b/src/adcp/types/domains/creative/creative_status_changed_webhook.py @@ -1,23 +1,136 @@ -"""Types declared by the AdCP ``creative/creative_status_changed_webhook`` schema. +# generated by datamodel-codegen: +# filename: creative/creative_status_changed_webhook.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.creative.creative_status_changed_webhook import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import creative_revision_id +from ..core import ext as ext_1 +from ..enums import creative_event_reason_code, creative_status + + +class From(StrEnum): + processing = 'processing' + pending_review = 'pending_review' + approved = 'approved' + suspended = 'suspended' + + +class Transition(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + from_: Annotated[ + From, + Field( + alias='from', + description='Prior status — restricted to the states from which a seller/system-initiated transition can fire. For initial review outcomes the prior status is `pending_review`; for processing outcomes it is `processing`; for seller re-review, post-approval revocation, recoverable suspension, and seller-initiated archive it is `approved`; for recovery from a dependency/authorization outage or terminal escalation after a suspension it is `suspended`.', + ), + ] + to: Annotated[creative_status.CreativeStatus, Field(description='New status.')] + observed_at: Annotated[ + AwareDatetime, + Field( + description="ISO 8601 timestamp when the seller observed the transition. Distinct from `fired_at` — `fired_at` reflects when the webhook was emitted, which may lag `observed_at` by up to the seller's coalescence window. **Ordering with `media-buy.impairment`:** when a creative transition also causes a media-buy impairment (e.g., `approved → suspended` or `approved → rejected` while assignments exist), the `creative.status_changed` and `impairment` fires are not ordered — buyers MUST NOT assume one arrives before the other. Reconcile via the snapshot (`list_creatives` and `get_media_buys`) when the two fires reference the same `creative_id`." + ), + ] + + +class Initiator(StrEnum): + seller = 'seller' + system = 'system' -from adcp.types.generated_poc.creative.creative_status_changed_webhook import ( - CreativeStatusChangedWebhook, - From, - Initiator, - Transition, -) -# Explicit exports -__all__ = ["CreativeStatusChangedWebhook", "From", "Initiator", "Transition"] +class CreativeStatusChangedWebhook(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + idempotency_key: Annotated[ + str, + Field( + description='Sender-generated key stable across retries of the same fire. Sellers MUST generate a cryptographically random value (UUID v4 recommended) per distinct fire and reuse it on every retry of the same fire. Receivers MUST dedupe by this key, scoped to the authenticated sender identity. Distinct from `notification_id` — same `notification_id` under two different `idempotency_key` values is a re-emission signal (snapshot-and-log Rule 1).', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + notification_id: Annotated[ + str, + Field( + description="Stable identifier for this logical transition event, used by buyers to correlate fires to current snapshot state. Stable across re-emissions of the same transition (e.g., the seller re-fires after the buyer's endpoint was unreachable); a fresh transition on the same creative receives a new id. Charset matches `idempotency_key` so the value is safe to log, embed in dashboard URLs, and pass into LLM prompts without escaping.", + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] + notification_type: Annotated[ + Literal['creative.status_changed'], + Field( + description="Fixed notification type discriminator. Matches the value registered on the subscriber's `event_types`." + ), + ] = 'creative.status_changed' + fired_at: Annotated[ + AwareDatetime, + Field( + description="ISO 8601 timestamp when the seller initiated this fire. Distinct from when the transition was observed (`transition.observed_at`) — fires MAY be coalesced or delayed up to the seller's declared coalescence window for `creative.status_changed`." + ), + ] + subscriber_id: Annotated[ + str, + Field( + description="Identifies which `notification_configs[]` entry on the recipient account is receiving this fire. Echoed verbatim from the entry's `subscriber_id`. Required so multi-subscriber accounts can route by endpoint.", + max_length=64, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,64}$', + ), + ] + account_id: Annotated[ + str, + Field( + description="Seller's identifier for the account this creative belongs to. Echoed so multi-account buyers can route without re-resolving via `list_accounts`." + ), + ] + creative_id: Annotated[ + str, + Field( + description="Seller's identifier for the creative whose status changed. References the same id space as `list_creatives` / `sync_creatives` responses." + ), + ] + revision_id: Annotated[ + creative_revision_id.CreativeRevisionId | None, + Field( + description='Buyer-authored input revision to which this transition applies. Required when the reviewed state has revision identity and the seller advertises creative.supports_revisions; it need not be the current list revision when the fire is received. Omitted for unversioned current content. A stale review outcome MUST NOT emit a fire as though it applied to a newer current revision.' + ), + ] = None + transition: Annotated[ + Transition, + Field( + description='The status transition that triggered this fire. Valid `from` values are restricted to the prior states from which a seller/system-initiated transition can fire (`processing` for processing outcomes, `pending_review` for initial review outcomes, `approved` for re-review/revocation/seller-archive/recoverable suspension, `suspended` for seller-observed recovery or terminal escalation). The post-terminal states `rejected` and `archived` MUST NOT appear as `from` — those would require a buyer-initiated unblock (`sync_creatives` resubmit / unarchive), which does not fire this event.' + ), + ] + reason_code: Annotated[ + creative_event_reason_code.CreativeEventReasonCode, + Field( + description='Categorical reason for the transition. Per-transition valid subsets are constrained by impairment.coherence-style narrative rules — see docs/creative/creative-lifecycle-webhooks.mdx. Receivers MUST treat unknown reason codes as forward-compatible additions and not reject the fire.' + ), + ] + reason_detail: Annotated[ + str | None, + Field( + description='Human-readable supplement to `reason_code`. Free text from the seller. Sellers MUST NOT include third-party PII in this field.', + max_length=500, + ), + ] = None + initiator: Annotated[ + Initiator, + Field( + description='Who initiated the transition. `seller` — explicit decision by the seller (human reviewer, policy operator, takedown handler). `system` — automated seller-side process (processing pipeline, retention sweep, inactivity scan). `buyer` never appears on this event — buyer-initiated transitions are acknowledged on the `sync_creatives` response path and MUST NOT fire `creative.status_changed`.' + ), + ] + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/creative/get_creative_delivery_request.py b/src/adcp/types/domains/creative/get_creative_delivery_request.py index a88ddba6c..afe7a380d 100644 --- a/src/adcp/types/domains/creative/get_creative_delivery_request.py +++ b/src/adcp/types/domains/creative/get_creative_delivery_request.py @@ -1,20 +1,85 @@ -"""Types declared by the AdCP ``creative/get_creative_delivery_request`` schema. +# generated by datamodel-codegen: +# filename: creative/get_creative_delivery_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.creative.get_creative_delivery_request import +from adcp.types.base import AdcpRequest, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated + +from pydantic import ConfigDict, Field, model_validator + +from ..core import account_ref +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core import pagination_request +from ..core.version_envelope import AdcpVersionEnvelope -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.creative.get_creative_delivery_request import ( - GetCreativeDeliveryRequest, -) +class GetCreativeDeliveryRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + account: Annotated[ + account_ref.AccountReference | None, + Field( + description='Account for routing and scoping. Limits results to creatives within this account.' + ), + ] = None + media_buy_ids: Annotated[ + list[str] | None, + Field( + description='Filter to specific media buys by publisher ID. If omitted, returns creative delivery across all matching media buys.', + min_length=1, + ), + ] = None + creative_ids: Annotated[ + list[str] | None, + Field( + description='Filter to specific creatives by ID. If omitted, returns delivery for all creatives matching the other filters.', + min_length=1, + ), + ] = None + start_date: Annotated[ + str | None, + Field( + description="Start date for delivery period (YYYY-MM-DD). Interpreted in the platform's reporting timezone.", + pattern='^\\d{4}-\\d{2}-\\d{2}$', + ), + ] = None + end_date: Annotated[ + str | None, + Field( + description="End date for delivery period (YYYY-MM-DD). Interpreted in the platform's reporting timezone.", + pattern='^\\d{4}-\\d{2}-\\d{2}$', + ), + ] = None + max_variants: Annotated[ + SchemaInt | None, + Field( + description='Maximum number of variants to return per creative. When omitted, the agent returns all variants. Use this to limit response size for generative creatives that may produce large numbers of variants.', + ge=1, + ), + ] = None + pagination: Annotated[ + pagination_request.PaginationRequest | None, + Field( + description='Pagination parameters for the creatives array in the response. Uses cursor-based pagination consistent with other list operations.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None -# Explicit exports -__all__ = ["GetCreativeDeliveryRequest"] + @model_validator(mode='after') + def _require_schema_required_group(self) -> GetCreativeDeliveryRequest: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('media_buy_ids',), ('creative_ids',),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'GetCreativeDeliveryRequest requires at least one of these field groups: media_buy_ids | creative_ids' + ) diff --git a/src/adcp/types/domains/creative/get_creative_delivery_response.py b/src/adcp/types/domains/creative/get_creative_delivery_response.py index 9798d1ffc..e710949a3 100644 --- a/src/adcp/types/domains/creative/get_creative_delivery_response.py +++ b/src/adcp/types/domains/creative/get_creative_delivery_response.py @@ -1,23 +1,147 @@ -"""Types declared by the AdCP ``creative/get_creative_delivery_response`` schema. +# generated by datamodel-codegen: +# filename: creative/get_creative_delivery_response.json +# timestamp: 2026-10-05T13:49:50+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.creative.get_creative_delivery_response import +from adcp.types.base import AdcpResponse, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from collections.abc import Sequence +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field, StrictBool + +from ..core import context as context_1 +from ..core import creative_variant, delivery_metrics, error +from ..core import ext as ext_1 +from ..core import format_id as format_id_1 +from ..core import format_option_ref as format_option_ref_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope + + +class ReportingPeriod(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + start: Annotated[AwareDatetime, Field(description='ISO 8601 start timestamp')] + end: Annotated[AwareDatetime, Field(description='ISO 8601 end timestamp')] + timezone: Annotated[ + str | None, + Field( + description="IANA timezone identifier for the reporting period (e.g., 'America/New_York', 'UTC'). Platforms report in their native timezone." + ), + ] = None + + +class Pagination(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + limit: Annotated[SchemaInt, Field(description='Maximum number of creatives requested', ge=1)] + offset: Annotated[SchemaInt, Field(description='Number of creatives skipped', ge=0)] + has_more: Annotated[ + StrictBool, Field(description='Whether more creatives are available beyond this page') + ] + total_count: Annotated[ + SchemaInt | None, + Field( + description='Total number of creatives matching the request filters. Canonical field name (matches `PaginationResponse.total_count`). Sellers SHOULD populate this and the deprecated `total` field identically until 4.0; buyers SHOULD prefer this field.', + ge=0, + ), + ] = None + total: Annotated[ + SchemaInt | None, + Field( + deprecated=True, + description='**Deprecated** — use `total_count` instead. Retained as a legacy alias for 3.x backward compatibility; removed in AdCP 4.0. Sellers populating this field MUST also populate `total_count` with the same value.', + ge=0, + ), + ] = None + + +class Creative(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + creative_id: Annotated[str, Field(description='Creative identifier')] + media_buy_id: Annotated[ + str | None, + Field( + description="Publisher's media buy identifier for this creative. Present when the request spanned multiple media buys, so the buyer can correlate each creative to its media buy." + ), + ] = None + format_id: Annotated[ + format_id_1.FormatReferenceStructuredObject | None, + Field( + deprecated=True, + description='**DEPRECATED in 3.2.** Legacy named format of this creative. New responses use `format_kind` and optional `format_option_ref`.', + ), + ] = None + format_kind: Annotated[ + str | None, Field(description='Canonical format kind delivered for this creative.') + ] = None + format_option_ref: Annotated[ + format_option_ref_1.FormatOptionReference | None, + Field( + description='Portable reference to the product or publisher format option used for delivery, when disambiguation is required.' + ), + ] = None + totals: Annotated[ + delivery_metrics.DeliveryMetrics | None, + Field(description='Aggregate delivery metrics across all variants of this creative'), + ] = None + variant_count: Annotated[ + SchemaInt | None, + Field( + description='Total number of agent-unique variant_id rows for this creative. When max_variants was specified in the request, this may exceed the number of items in the variants array.', + ge=0, + ), + ] = None + variants: Annotated[ + list[creative_variant.CreativeVariant], + Field( + description='Variant-level delivery breakdown. Each agent-unique variant_id identifies one immutable served execution and each row includes metrics from exactly one source revision and, for localized delivery, exactly one locale variant. A distinct revision, locale, or rendered manifest receives a distinct variant_id; metrics MUST NOT cross those boundaries. For standard creatives, contains one row per source revision and locale represented in the reporting period. For asset group optimization, one per combination, source revision, and locale. For generative creative, one per generated execution, source revision, and locale. Empty when a creative has no variants yet.' + ), + ] -from adcp.types.generated_poc.creative.get_creative_delivery_response import ( - Creative, - GetCreativeDeliveryResponse, - Pagination, - ReportingPeriod, -) -# Explicit exports -__all__ = ["Creative", "GetCreativeDeliveryResponse", "Pagination", "ReportingPeriod"] +class GetCreativeDeliveryResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + account_id: Annotated[ + str | None, + Field( + description='Account identifier. Present when the response spans or is scoped to a specific account.' + ), + ] = None + media_buy_id: Annotated[ + str | None, + Field( + description="Publisher's media buy identifier. Present when the request was scoped to a single media buy." + ), + ] = None + currency: Annotated[ + str, + Field( + description="ISO 4217 currency code for monetary values in this response (e.g., 'USD', 'EUR')", + pattern='^[A-Z]{3}$', + ), + ] + reporting_period: Annotated[ReportingPeriod, Field(description='Date range for the report.')] + creatives: Annotated[ + Sequence[Creative], Field(description='Creative delivery data with variant breakdowns') + ] + pagination: Annotated[ + Pagination | None, + Field( + description='Pagination information. Present when the request included pagination parameters. **Note:** `get_creative_delivery` uses page-based pagination (`limit`/`offset`) for historical reasons, distinct from the cursor-based [`PaginationResponse`](/schemas/v3/core/pagination-response.json) used by `list_*` tools. Field naming aligned with `PaginationResponse.total_count` in 3.1; the legacy `total` field is retained as a deprecated alias until 4.0. Sellers MUST populate both fields identically; buyers SHOULD prefer `total_count` (the canonical name) and ignore `total` if both are present.' + ), + ] = None + errors: Annotated[ + list[error.Error] | None, Field(description='Task-specific errors and warnings') + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/creative/get_creative_features_async_response_submitted.py b/src/adcp/types/domains/creative/get_creative_features_async_response_submitted.py index af0f78426..e941d061b 100644 --- a/src/adcp/types/domains/creative/get_creative_features_async_response_submitted.py +++ b/src/adcp/types/domains/creative/get_creative_features_async_response_submitted.py @@ -1,20 +1,48 @@ -"""Types declared by the AdCP ``creative/get_creative_features_async_response_submitted`` schema. +# generated by datamodel-codegen: +# filename: creative/get_creative_features_async_response_submitted.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.creative.get_creative_features_async_response_submitted import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import context as context_1 +from ..core import ext as ext_1 -from adcp.types.generated_poc.creative.get_creative_features_async_response_submitted import ( - GetCreativeFeaturesSubmitted, -) -# Explicit exports -__all__ = ["GetCreativeFeaturesSubmitted"] +class GetCreativeFeaturesSubmitted(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + status: Annotated[ + Literal['submitted'], + Field( + description='Task-level status literal that discriminates this acknowledgement from terminal success and error responses.' + ), + ] = 'submitted' + task_id: Annotated[ + str, + Field( + description='AdCP task handle used to poll get_task_status or correlate terminal webhook delivery. Distinct from evaluation_id.', + min_length=1, + ), + ] + evaluation_id: Annotated[ + str | None, + Field( + description='Provider-generated identity allocated when the evaluation is accepted. Optional in AdCP 3.x and required in AdCP 4.0. Providers SHOULD emit it in 3.x; when present, exact replays and the terminal result MUST carry this same value. Distinct from task_id and idempotency_key.', + min_length=1, + ), + ] = None + message: Annotated[ + str | None, + Field( + description='Optional human-readable explanation of why the evaluation is submitted. Plain text only; callers treat it as untrusted provider input.', + max_length=2000, + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/creative/get_creative_features_request.py b/src/adcp/types/domains/creative/get_creative_features_request.py index 60f8352a2..c56997404 100644 --- a/src/adcp/types/domains/creative/get_creative_features_request.py +++ b/src/adcp/types/domains/creative/get_creative_features_request.py @@ -1,20 +1,60 @@ -"""Types declared by the AdCP ``creative/get_creative_features_request`` schema. +# generated by datamodel-codegen: +# filename: creative/get_creative_features_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.creative.get_creative_features_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field + +from ..core import account_ref +from ..core import context as context_1 +from ..core import creative_manifest as creative_manifest_1 +from ..core import ext as ext_1 +from ..core import push_notification_config as push_notification_config_1 +from ..core.version_envelope import AdcpVersionEnvelope -from adcp.types.generated_poc.creative.get_creative_features_request import ( - GetCreativeFeaturesRequest, -) -# Explicit exports -__all__ = ["GetCreativeFeaturesRequest"] +class GetCreativeFeaturesRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + idempotency_key: Annotated[ + str | None, + Field( + description='Optional in AdCP 3.x for wire compatibility; clients SHOULD send a unique key for every logical evaluation. When supplied to a provider that advertises adcp.idempotency.supported: true, exact retries with the same canonical payload MUST return the cached initial response with replayed: true and MUST NOT dispatch a second provider evaluation or record a second consumption or cost event. Reusing the key with a different creative_manifest, account, or feature_ids payload returns IDEMPOTENCY_CONFLICT. The provider MUST retain the replay record for at least 24 hours. Keys MUST be unique per (provider, request) pair to prevent cross-provider correlation. This field becomes required in AdCP 4.0; use a fresh UUID v4 for each logical evaluation.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] = None + creative_manifest: Annotated[ + creative_manifest_1.CreativeManifest, + Field( + description='The canonical creative manifest to evaluate. In 3.2 it contains `format_kind`, optional `format_option_ref`, and typed assets. The deprecated named `format_id` branch remains available only for older 3.x peers.' + ), + ] + feature_ids: Annotated[ + list[str] | None, + Field( + description='Optional filter to specific features. If omitted, returns all available features.', + min_length=1, + ), + ] = None + account: Annotated[ + account_ref.AccountReference | None, + Field( + description='Account for billing this evaluation. Required when the governance agent charges per evaluation.' + ), + ] = None + push_notification_config: Annotated[ + push_notification_config_1.PushNotificationConfig | None, + Field( + description='Optional webhook configuration for async terminal completion/failure notifications. A submitted acknowledgement remains pollable through get_task_status whether or not this field is present. If the provider accepts this field and returns status: submitted, it MUST deliver at least the terminal completion or failure notification to the configured URL. If it cannot honor the webhook channel, it MUST reject the request instead of silently downgrading delivery.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/creative/get_creative_features_response.py b/src/adcp/types/domains/creative/get_creative_features_response.py index c5d9d624e..d4ba8cf32 100644 --- a/src/adcp/types/domains/creative/get_creative_features_response.py +++ b/src/adcp/types/domains/creative/get_creative_features_response.py @@ -1,28 +1,51 @@ -"""Types declared by the AdCP ``creative/get_creative_features_response`` schema. +# generated by datamodel-codegen: +# filename: creative/get_creative_features_response.json +# timestamp: preserved-by-post-generate-fixes -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.creative.get_creative_features_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Literal, TypeAlias -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, StringConstraints + +from ..core.version_envelope import AdcpVersionEnvelope +from ..core import context as context_1 +from ..core import error as error_1 +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..enums import task_status as task_status_1 + + +class GetCreativeFeaturesResponse1(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + + +class GetCreativeFeaturesResponse2(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + evaluation_id: Annotated[str, StringConstraints(min_length=1)] | None = None + errors: list[error_1.Error] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class GetCreativeFeaturesResponse3(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow', validate_default=True) + status: Literal[task_status_1.TaskStatus.submitted] = task_status_1.TaskStatus.submitted + task_id: Annotated[str, StringConstraints(min_length=1)] + evaluation_id: Annotated[str, StringConstraints(min_length=1)] | None = None + message: Annotated[str, StringConstraints(max_length=2000)] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +GetCreativeFeaturesResponse: TypeAlias = GetCreativeFeaturesResponse1 | GetCreativeFeaturesResponse2 | GetCreativeFeaturesResponse3 -from adcp.types.generated_poc.creative.get_creative_features_response import ( - GetCreativeFeaturesResponse, - GetCreativeFeaturesResponse1, - GetCreativeFeaturesResponse2, - GetCreativeFeaturesResponse3, -) -# Explicit exports __all__ = [ - "GetCreativeFeaturesResponse", - "GetCreativeFeaturesResponse1", - "GetCreativeFeaturesResponse2", - "GetCreativeFeaturesResponse3", + 'GetCreativeFeaturesResponse', + 'GetCreativeFeaturesResponse1', + 'GetCreativeFeaturesResponse2', + 'GetCreativeFeaturesResponse3', ] diff --git a/src/adcp/types/domains/creative/get_creative_features_terminal_success.py b/src/adcp/types/domains/creative/get_creative_features_terminal_success.py index adcc6132f..a834279b9 100644 --- a/src/adcp/types/domains/creative/get_creative_features_terminal_success.py +++ b/src/adcp/types/domains/creative/get_creative_features_terminal_success.py @@ -1,20 +1,66 @@ -"""Types declared by the AdCP ``creative/get_creative_features_terminal_success`` schema. +# generated by datamodel-codegen: +# filename: creative/get_creative_features_terminal_success.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.creative.get_creative_features_terminal_success import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import context as context_1 +from ..core import creative_consumption +from ..core import ext as ext_1 +from . import audit_observation, creative_feature_result -from adcp.types.generated_poc.creative.get_creative_features_terminal_success import ( - GetCreativeFeaturesSuccess, -) -# Explicit exports -__all__ = ["GetCreativeFeaturesSuccess"] +class GetCreativeFeaturesSuccess(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + evaluation_id: Annotated[ + str | None, + Field( + description='Provider-generated identity for this evaluation. Optional in AdCP 3.x and required in AdCP 4.0. Providers SHOULD emit it in 3.x. When present, it MUST be stable across exact response replays and, for async work whose submitted acknowledgement included evaluation_id, MUST be identical to that value. Used for result provenance, consumption and provider-cost reconciliation, and support; it is not an idempotency key or task identity.', + min_length=1, + ), + ] = None + results: Annotated[ + list[creative_feature_result.CreativeFeatureResult], + Field(description='Feature values for the evaluated creative'), + ] + detail_url: Annotated[ + AnyUrl | None, + Field( + description="URL to the vendor's full assessment report. The vendor controls what information is disclosed and access control." + ), + ] = None + audit_observations: Annotated[ + list[audit_observation.CreativeAuditObservation] | None, + Field( + description='Non-blocking audit observations from the governance agent. Observations surface audit-worthy claims that are not verifier refutations and are not rejection grounds by themselves.' + ), + ] = None + pricing_option_id: Annotated[ + str | None, + Field( + description='Which rate card pricing option was applied for this evaluation. Present when the governance agent charges for evaluations and account was provided in the request.' + ), + ] = None + vendor_cost: Annotated[ + StrictFloat | None, + Field(description='Cost incurred for this evaluation, denominated in currency.', ge=0.0), + ] = None + currency: Annotated[ + str | None, + Field(description='ISO 4217 currency code for vendor_cost.', pattern='^[A-Z]{3}$'), + ] = None + consumption: Annotated[ + creative_consumption.CreativeConsumption | None, + Field( + description='Structured consumption details for this evaluation. Informational — lets the buyer verify that vendor_cost is consistent with the rate card. vendor_cost is the billing source of truth.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/creative/list_creative_formats_request.py b/src/adcp/types/domains/creative/list_creative_formats_request.py index 17b6b7fad..cc7d1aaac 100644 --- a/src/adcp/types/domains/creative/list_creative_formats_request.py +++ b/src/adcp/types/domains/creative/list_creative_formats_request.py @@ -1,21 +1,142 @@ -"""Types declared by the AdCP ``creative/list_creative_formats_request`` schema. +# generated by datamodel-codegen: +# filename: creative/list_creative_formats_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.creative.list_creative_formats_request import +from adcp.types.base import AdcpRequest, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field, StrictBool + +from ..core import account_ref +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core import format_id, pagination_request +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import asset_content_type +from ..enums import disclosure_persistence as disclosure_persistence_1 +from ..enums import disclosure_position +from ..enums import wcag_level as wcag_level_1 + + +class Type(StrEnum): + audio = 'audio' + video = 'video' + display = 'display' + dooh = 'dooh' -from adcp.types.generated_poc.creative.list_creative_formats_request import ( - ListCreativeFormatsRequestCreativeAgent, - Type, -) -# Explicit exports -__all__ = ["ListCreativeFormatsRequestCreativeAgent", "Type"] +class ListCreativeFormatsRequestCreativeAgent(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + format_ids: Annotated[ + list[format_id.FormatReferenceStructuredObject] | None, + Field( + deprecated=True, + description='Deprecated in AdCP 3.2; removed in AdCP 4.0. Return only these specific named-format IDs. Use canonical-format discovery in 4.0.', + min_length=1, + ), + ] = None + type: Annotated[ + Type | None, + Field( + description='Filter by format type (technical categories with distinct requirements)' + ), + ] = None + asset_types: Annotated[ + list[asset_content_type.AssetContentType] | None, + Field( + description="Filter to formats that include these asset types. For third-party tags, search for 'html' or 'javascript'. For published-post reference formats, search for 'published_post'. E.g., ['image', 'text'] returns formats with images and text, ['javascript'] returns formats accepting JavaScript tags.", + min_length=1, + ), + ] = None + max_width: Annotated[ + SchemaInt | None, + Field( + description='Maximum width in pixels (inclusive). Returns formats with width <= this value. Omit for responsive/fluid formats.' + ), + ] = None + max_height: Annotated[ + SchemaInt | None, + Field( + description='Maximum height in pixels (inclusive). Returns formats with height <= this value. Omit for responsive/fluid formats.' + ), + ] = None + min_width: Annotated[ + SchemaInt | None, + Field( + description='Minimum width in pixels (inclusive). Returns formats with width >= this value.' + ), + ] = None + min_height: Annotated[ + SchemaInt | None, + Field( + description='Minimum height in pixels (inclusive). Returns formats with height >= this value.' + ), + ] = None + is_responsive: Annotated[ + StrictBool | None, + Field( + description='Filter for responsive formats that adapt to container size. When true, returns formats without fixed dimensions.' + ), + ] = None + name_search: Annotated[ + str | None, Field(description='Search for formats by name (case-insensitive partial match)') + ] = None + wcag_level: Annotated[ + wcag_level_1.WcagLevel | None, + Field( + description='Filter to formats that meet at least this WCAG conformance level (A < AA < AAA)' + ), + ] = None + disclosure_positions: Annotated[ + list[disclosure_position.DisclosurePosition] | None, + Field( + description="Filter to formats that support all of these disclosure positions. When a format has disclosure_capabilities, match against those positions. Otherwise fall back to supported_disclosure_positions. Use to find formats compatible with a brief's compliance requirements.", + min_length=1, + ), + ] = None + disclosure_persistence: Annotated[ + list[disclosure_persistence_1.DisclosurePersistence] | None, + Field( + description='Filter to formats where each requested persistence mode is supported by at least one position in disclosure_capabilities. Different positions may satisfy different modes. Use to find formats compatible with jurisdiction-specific persistence requirements (e.g., continuous for EU AI Act).', + min_length=1, + ), + ] = None + output_format_ids: Annotated[ + list[format_id.FormatReferenceStructuredObject] | None, + Field( + deprecated=True, + description='**DEPRECATED in 3.1.** Discover build capability via `list_transformers` (filter its `output_format_ids`) instead — build capability is a property of transformers, not a relationship between formats. *Legacy:* filter to formats whose `output_format_ids` includes any of these format IDs.', + min_length=1, + ), + ] = None + input_format_ids: Annotated[ + list[format_id.FormatReferenceStructuredObject] | None, + Field( + deprecated=True, + description='**DEPRECATED in 3.1.** Discover build capability via `list_transformers` (filter its `input_format_ids`) instead. *Legacy:* filter to formats whose `input_format_ids` includes any of these format IDs.', + min_length=1, + ), + ] = None + include_pricing: Annotated[ + StrictBool | None, + Field( + deprecated=True, + description='**DEPRECATED in 3.1. Removed at 4.0.** Use `list_transformers` with `include_pricing` and `account` for transformation and generation pricing. *Legacy 3.x behavior:* include `pricing_options` on each returned format. Requires `account`; when false or omitted, pricing is not computed. Agents supporting the 3.1 format-pricing surface MUST continue to honor this field through 3.x.', + ), + ] = False + account: Annotated[ + account_ref.AccountReference | None, + Field( + deprecated=True, + description='**DEPRECATED for `list_creative_formats` in 3.1. Removed at 4.0.** Use the account-scoped `list_transformers` request instead. *Legacy 3.x behavior:* identifies the rate card used when `include_pricing` is true.', + ), + ] = None + pagination: pagination_request.PaginationRequest | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/creative/list_creative_formats_response.py b/src/adcp/types/domains/creative/list_creative_formats_response.py index a7003d2b2..87cf3f3f6 100644 --- a/src/adcp/types/domains/creative/list_creative_formats_response.py +++ b/src/adcp/types/domains/creative/list_creative_formats_response.py @@ -1,21 +1,62 @@ -"""Types declared by the AdCP ``creative/list_creative_formats_response`` schema. +# generated by datamodel-codegen: +# filename: creative/list_creative_formats_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.creative.list_creative_formats_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field + +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 +from ..core import format, pagination_response +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import creative_agent_capability + + +class CreativeAgent(AdCPBaseModel): + agent_url: Annotated[ + AnyUrl, + Field( + description="Base URL for the creative agent (e.g., 'https://reference.example.com', 'https://dco.example.com')." + ), + ] + agent_name: Annotated[ + str | None, Field(description='Human-readable name for the creative agent') + ] = None + capabilities: Annotated[ + list[creative_agent_capability.CreativeAgentCapability] | None, + Field(description='Capabilities this creative agent provides'), + ] = None -from adcp.types.generated_poc.creative.list_creative_formats_response import ( - CreativeAgent, - ListCreativeFormatsResponseCreativeAgent, -) -# Explicit exports -__all__ = ["CreativeAgent", "ListCreativeFormatsResponseCreativeAgent"] +class ListCreativeFormatsResponseCreativeAgent(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + formats: Annotated[ + list[format.Format], + Field( + deprecated=True, + description='Deprecated named-format definitions projected for older 3.x callers. Canonical creative capabilities are declared by get_adcp_capabilities creative.supported_formats[].', + ), + ] + creative_agents: Annotated[ + list[CreativeAgent] | None, + Field( + deprecated=True, + description='Deprecated recursive discovery projection retained only for historical 3.x responses. New buyers use registry reverse lookup and direct get_adcp_capabilities confirmation.', + ), + ] = None + errors: Annotated[ + list[error.Error] | None, Field(description='Task-specific errors and warnings') + ] = None + pagination: pagination_response.PaginationResponse | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/creative/list_creatives_request.py b/src/adcp/types/domains/creative/list_creatives_request.py index 4dee3bfd0..7d945c87d 100644 --- a/src/adcp/types/domains/creative/list_creatives_request.py +++ b/src/adcp/types/domains/creative/list_creatives_request.py @@ -1,23 +1,144 @@ -"""Types declared by the AdCP ``creative/list_creatives_request`` schema. +# generated by datamodel-codegen: +# filename: creative/list_creatives_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.creative.list_creatives_request import +from adcp.types.base import AdcpRequest, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictBool + +from ..core import account_ref +from ..core import context as context_1 +from ..core import creative_filters +from ..core import ext as ext_1 +from ..core import pagination_request +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import creative_sort_field, sort_direction + + +class AssignmentProjection(StrEnum): + all = 'all' + matching = 'matching' + + +class Field1(StrEnum): + creative_id = 'creative_id' + name = 'name' + format_id = 'format_id' + format_kind = 'format_kind' + format_option_ref = 'format_option_ref' + assets = 'assets' + status = 'status' + created_date = 'created_date' + updated_date = 'updated_date' + tags = 'tags' + rights = 'rights' + rights_attestation_evaluations = 'rights_attestation_evaluations' + localization = 'localization' + localization_unavailable = 'localization_unavailable' + assignments = 'assignments' + snapshot = 'snapshot' + items = 'items' + variables = 'variables' + concept = 'concept' + pricing_options = 'pricing_options' + + +class Sort(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + field: Annotated[ + creative_sort_field.CreativeSortField | None, Field(description='Field to sort by') + ] = creative_sort_field.CreativeSortField.created_date + direction: Annotated[ + sort_direction.SortDirection | None, Field(description='Sort direction') + ] = sort_direction.SortDirection.desc -from adcp.types.generated_poc.creative.list_creatives_request import ( - AssignmentProjection, - Field1, - ListCreativesRequest, - Sort, -) -# Explicit exports -__all__ = ["AssignmentProjection", "Field1", "ListCreativesRequest", "Sort"] +class ListCreativesRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + filters: creative_filters.CreativeFilters | None = None + sort: Annotated[Sort | None, Field(description='Sorting parameters')] = None + pagination: pagination_request.PaginationRequest | None = None + include_assignments: Annotated[ + StrictBool | None, Field(description='Include package assignment information in response') + ] = True + assignment_projection: Annotated[ + AssignmentProjection | None, + Field( + description='Controls nested assigned_packages projection when assignments are included. all returns up to assignment_limit active assignments per creative. matching returns only assignments matching filters.indicator_types and requires that filter. Use matching for compact indicator discovery; get_media_buys remains the complete authoritative repair path when assignments_truncated is true.' + ), + ] = AssignmentProjection.all + assignment_limit: Annotated[ + SchemaInt | None, + Field( + description='Maximum assigned_packages rows returned per creative. Sellers MUST set assignments.assignments_truncated when additional qualifying rows exist.', + ge=1, + le=200, + ), + ] = 50 + include_snapshot: Annotated[ + StrictBool | None, + Field( + description='Include a lightweight delivery snapshot per creative (lifetime impressions and last-served date). For detailed performance analytics, use get_creative_delivery.' + ), + ] = False + include_items: Annotated[ + StrictBool | None, + Field(description='Include items for multi-asset formats like carousels and native ads'), + ] = False + include_variables: Annotated[ + StrictBool | None, + Field( + description='Include dynamic content variable definitions (DCO slots) for each creative' + ), + ] = False + include_pricing: Annotated[ + StrictBool | None, + Field( + description='Include pricing_options on each creative. Requires account to be provided. When false or omitted, pricing is not computed.' + ), + ] = False + include_purged: Annotated[ + StrictBool | None, + Field( + description="Include soft-purged creative tombstones in the result set. When true, creatives destroyed via `creative.purged` with `purge_kind: soft` surface as tombstone records carrying `purged: true`, `purged_at`, and the purge reason — within the seller's webhook activity retention window (30 days from `purged_at`, MUST match `webhook-activity-record` retention). Hard-purged creatives MUST NOT appear regardless of this flag. When false or omitted, the result set excludes all purged creatives — same default as today." + ), + ] = False + include_webhook_activity: Annotated[ + StrictBool | None, + Field( + description='Include recent webhook activity per creative. When true, each returned creative carries a `webhook_activity[]` array of the most recent fires scoped to that creative — `creative.status_changed` and `creative.purged` deliveries. Adoption of the `webhook_activity[]` pattern per `snapshot-and-log.mdx § Webhook activity log pattern`. Retention is 30 days from `completed_at` (MUST). Three-state presence applies: omitted = seller does not surface; `[]` = persists but no recent fires; non-empty = actual records.' + ), + ] = False + webhook_activity_limit: Annotated[ + SchemaInt | None, + Field( + description="Maximum number of `webhook_activity[]` records to return per creative. Only meaningful when `include_webhook_activity: true`. Sellers MUST respect the cap; structural enforcement is provided by the response schema's `maxItems: 200` on the array.", + ge=1, + le=200, + ), + ] = 50 + account: Annotated[ + account_ref.AccountReference | None, + Field( + description="Account reference for pricing and access. When provided with include_pricing, the agent returns pricing_options from this account's rate card on each creative." + ), + ] = None + fields: Annotated[ + list[Field1] | None, + Field( + description="Specific fields to include in response (omit for all fields). The 'concept' value returns both concept_id and concept_name. `format_id` is a deprecated 3.x compatibility projection; new integrations request `format_kind` and `format_option_ref`. Selecting localization automatically includes creative_id, status, assets, the selected format identity, and localization_unavailable when applicable. Selecting rights_attestation_evaluations automatically includes rights so each seller-produced result can be reconciled with the exact retained constraint and reference.", + min_length=1, + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/creative/list_creatives_response.py b/src/adcp/types/domains/creative/list_creatives_response.py index 1ca2df73a..2eba42a0c 100644 --- a/src/adcp/types/domains/creative/list_creatives_response.py +++ b/src/adcp/types/domains/creative/list_creatives_response.py @@ -1,50 +1,482 @@ -"""Types declared by the AdCP ``creative/list_creatives_response`` schema. +# generated by datamodel-codegen: +# filename: creative/list_creatives_response.json +# timestamp: 2026-10-05T13:49:50+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.creative.list_creatives_response import +from adcp.types.base import AdcpResponse, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from collections.abc import Sequence +from typing import Annotated, Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field, RootModel, StrictBool, StringConstraints, model_validator -from adcp.types.generated_poc.creative.list_creatives_response import ( - Assets, - AssignedPackage, - Assignments, - Creative, - Creatives, - Creatives1, - Indicator, - IndicatorTypesEvaluatedEnum, - ListCreativesResponse, - LocalizationUnavailable, - Purge, - QuerySummary, - Snapshot, - SortApplied, - StatusSummary, +from ..core import account as account_1 +from ..core import context as context_1 +from ..core import ( + creative_approval_scope, + creative_assets, + creative_item, + creative_localization_readback, + creative_revision_id, + creative_variable, + error, ) +from ..core import ext as ext_1 +from ..core import format_id as format_id_1 +from ..core import format_option_ref as format_option_ref_1 +from ..core import indicator_scope, pagination_response +from ..core import representation_selection as representation_selection_1 +from ..core import ( + rights_attestation_evaluation, + rights_constraint, + vendor_pricing_option, + webhook_activity_record, +) +from ..core.assets import asset_union +from ..core.indicator_bearing import IndicatorBearingResourceState +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import creative_approval_status, creative_event_reason_code, creative_status +from ..enums import snapshot_unavailable_reason as snapshot_unavailable_reason_1 +from ..enums import sort_direction + + +class LocalizationUnavailable(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + errors: Annotated[list[error.Error], Field(min_length=1)] + retryable: Annotated[ + StrictBool, + Field( + description='Whether a later list_creatives call may recover without buyer mutation.' + ), + ] + + +class IndicatorTypesEvaluatedEnum(StrEnum): + creative_fatigue = 'creative_fatigue' + creative_quality_opportunity = 'creative_quality_opportunity' + creative_diversity_low = 'creative_diversity_low' + audience_saturation = 'audience_saturation' + inventory_shortfall_forecast = 'inventory_shortfall_forecast' + pacing_risk = 'pacing_risk' + budget_constrained = 'budget_constrained' + creative_fatigue_1 = 'creative_fatigue' + creative_quality_opportunity_1 = 'creative_quality_opportunity' + + +class Indicator(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + type: Annotated[ + IndicatorTypesEvaluatedEnum, + Field( + description="A seller-asserted material risk or optimization opportunity that currently warrants buyer attention on an AdCP resource relationship. AdCP standardizes the broad meaning of each value for the protocol release; it does not standardize or version the seller's detection methodology or prescribe a universal remedy.", + title='Indicator Type', + ), + ] + detected_at: Annotated[ + AwareDatetime | None, + Field( + description='When the seller first detected the current uninterrupted occurrence of this indicator. Keep this value stable while the condition remains present. If the condition clears and is later detected again, use the new detection time. Optional because some upstream platforms expose current assessments without an original detection timestamp.' + ), + ] = None + scope: Annotated[ + list[indicator_scope.IndicatorScope] | None, + Field( + description='Optional narrower publisher or placement scope within the enclosing media buy, package, or package–creative assignment. Omit only when the seller evaluated and asserts the indicator across the whole enclosing resource/relationship. When partial indicators_evaluated_scope is declared, every returned indicator MUST include scope and every entry MUST fall within that coverage. This is scope, not source: the responding seller remains the source.', + min_length=1, + ), + ] = None + ext: Annotated[ + ext_1.ExtensionObject | None, + Field( + description='Seller- or provider-specific detail such as scores, thresholds, evaluation windows, methodology identifiers, or upstream attribution. Core consumers must not need ext to understand the broad meaning of type.' + ), + ] = None + + +class Snapshot(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + as_of: Annotated[ + AwareDatetime, Field(description='When this snapshot was captured by the platform') + ] + staleness_seconds: Annotated[ + SchemaInt, + Field( + description='Maximum age of this data in seconds. For example, 3600 means the data may be up to 1 hour old.', + ge=0, + ), + ] + impressions: Annotated[ + SchemaInt, + Field( + description='Lifetime impressions across all assignments. Not scoped to any date range.', + ge=0, + ), + ] + last_served: Annotated[ + AwareDatetime | None, + Field( + description='Last time this creative served an impression. Absent when the creative has never served.' + ), + ] = None + + +class Purge(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Annotated[ + Literal['soft'], + Field(description='Always `soft` on tombstones — hard purges do not surface on this read.'), + ] = 'soft' + at: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 timestamp when the creative was destroyed. Matches the `purged_at` field on the corresponding `creative.purged` webhook fire.' + ), + ] + reason_code: Annotated[ + creative_event_reason_code.CreativeEventReasonCode, + Field( + description='Categorical reason for the purge. Matches the value the seller emitted on the corresponding `creative.purged` webhook fire.' + ), + ] + + +class StatusSummary(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + processing: Annotated[ + SchemaInt | None, Field(description='Number of creatives being processed', ge=0) + ] = None + approved: Annotated[ + SchemaInt | None, Field(description='Number of approved creatives', ge=0) + ] = None + pending_review: Annotated[ + SchemaInt | None, Field(description='Number of creatives pending review', ge=0) + ] = None + rejected: Annotated[ + SchemaInt | None, Field(description='Number of rejected creatives', ge=0) + ] = None + archived: Annotated[ + SchemaInt | None, Field(description='Number of archived creatives', ge=0) + ] = None + + +class SortApplied(AdCPBaseModel): + field: str | None = None + direction: sort_direction.SortDirection | None = None + + +class QuerySummary(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + total_matching: Annotated[ + SchemaInt, + Field(description='Total number of creatives matching filters (across all pages)', ge=0), + ] + returned: Annotated[ + SchemaInt, Field(description='Number of creatives returned in this response', ge=0) + ] + filters_applied: Annotated[ + list[str] | None, Field(description='List of filters that were applied to the query') + ] = None + sort_applied: Annotated[ + SortApplied | None, Field(description='Sort order that was applied') + ] = None + + +class AssignedPackage(IndicatorBearingResourceState): + model_config = ConfigDict( + extra='allow', + ) + indicator_types_evaluated: Annotated[ + list[IndicatorTypesEvaluatedEnum] | None, + Field( + description='Indicator types covered by this snapshot. Required whenever indicators is present. Types omitted from this list remain unknown even when indicators is empty. Every returned indicator.type MUST appear in this list.', + min_length=1, + ), + ] = None + indicators: Annotated[ + list[Indicator] | None, + Field( + description='Current seller assertions for the indicator types and publisher/placement coverage named by the sibling evaluation fields. Omitted means unknown or not evaluated. A present empty array means evaluated with no current assertion for indicator_types_evaluated in the evaluated scope.' + ), + ] = None + package_id: Annotated[str, Field(description='Package identifier')] + media_buy_id: Annotated[ + str | None, + Field( + description='Media buy containing this package. A seller advertising list_creatives in media_buy.relationship_notifications.projection_tasks MUST include this field on every assignment row, including rows where indicators is omitted as unknown, so buyers can key and reread the relationship unambiguously when package IDs are reused across media buys.' + ), + ] = None + assigned_date: Annotated[AwareDatetime, Field(description='When this assignment was created')] + approval_status: Annotated[ + creative_approval_status.CreativeApprovalStatus | None, + Field( + description='Aggregate approval state for this creative in this package assignment. This mirrors the same relationship in get_media_buys. Sellers advertising list_creatives as an indicator projection task MUST include it; partially_approved requires approval_scopes.' + ), + ] = None + rejection_reason: Annotated[ + str | None, + Field( + description='Human-readable explanation when approval_status is rejected. Mirrors get_media_buys for the same relationship.' + ), + ] = None + approval_scopes: Annotated[ + list[creative_approval_scope.ScopedCreativeApproval] | None, + Field( + description='Complete, disjoint publisher/placement approval partition when approval_status is partially_approved. A normalized scope appears once. For one publisher, use either one publisher-wide row or placement-specific rows, never both. Omit when one approval_status applies uniformly to the whole assignment. The same scoped outcomes are mirrored on get_media_buys.', + min_length=2, + ), + ] = None + indicators_as_of: Annotated[ + AwareDatetime | None, + Field( + description='When the seller last completed the evaluation represented by indicators for this relationship. Required whenever indicators is present, including an empty array.' + ), + ] = None + indicators_evaluated_scope: Annotated[ + list[indicator_scope.IndicatorScope] | None, + Field( + description='Optional publisher or placement scopes covered by this evaluation. Omit when indicators covers the whole package–creative assignment. When present, scopes not listed remain unknown; every returned indicator.scope entry MUST be contained by this set.', + min_length=1, + ), + ] = None + + +class Assignments(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + assignment_count: Annotated[ + SchemaInt, Field(description='Total number of active package assignments', ge=0) + ] + returned_assignment_count: Annotated[ + SchemaInt | None, + Field( + description='Number of rows present in assigned_packages for this response.', + ge=0, + le=200, + ), + ] = None + matching_assignment_count: Annotated[ + SchemaInt | None, + Field( + description='Total active assignments matching filters.indicator_types. MUST be present exactly when assignment_projection was matching. May exceed returned_assignment_count.', + ge=0, + ), + ] = None + assignments_truncated: Annotated[ + StrictBool | None, + Field( + description='True exactly when more qualifying assignments exist than were returned under assignment_limit. Qualifying means assignment_count for the all projection and matching_assignment_count for the matching projection. Buyers needing complete state repair through get_media_buys.' + ), + ] = None + assigned_packages: Annotated[ + list[AssignedPackage] | None, + Field( + description='Bounded package assignment projection. Under assignment_projection: matching, contains only assignments carrying a requested indicator type; otherwise contains active assignments up to assignment_limit. The response ceiling is enforced via verifier_constraints rather than maxItems, so payloads from 3.1 sellers remain schema-valid.' + ), + ] = None + + +class Assets(RootModel[list[asset_union.AssetVariant]]): + root: Annotated[list[asset_union.AssetVariant], Field(min_length=1)] + + +class Creative(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + creative_id: Annotated[str, Field(description='Unique identifier for the creative')] + revision_id: Annotated[ + creative_revision_id.CreativeRevisionId | None, + Field( + description='Current buyer-authored input revision for this creative. Present when the current effective content has revision identity and the seller advertises creative.supports_revisions; omitted after an accepted content-bearing legacy update without revision_id.' + ), + ] = None + representation_selection: Annotated[ + representation_selection_1.RepresentationSelection | None, + Field( + description='Exact source-set and selected-output lineage retained when the current stored creative was resolved from a CreativeRepresentationSet.' + ), + ] = None + account: Annotated[ + account_1.Account | None, Field(description='Account that owns this creative') + ] = None + name: Annotated[str, Field(description='Human-readable creative name')] + format_id: Annotated[ + format_id_1.FormatReferenceStructuredObject | None, + Field( + deprecated=True, + description='**DEPRECATED in 3.2.** Legacy named-format path. New listed creatives use `format_kind` and optional `format_option_ref`.', + ), + ] = None + format_kind: Annotated[ + str | None, + Field( + description='Canonical 3.2 path. The canonical format kind this creative targets. Mutually exclusive with deprecated `format_id`.' + ), + ] = None + format_option_ref: Annotated[ + format_option_ref_1.FormatOptionReference | None, + Field( + description='Optional reference to the concrete canonical format option this creative targets. Required when `format_kind` alone is ambiguous in the enclosing product context.' + ), + ] = None + status: Annotated[ + creative_status.CreativeStatus, Field(description='Current approval status of the creative') + ] + created_date: Annotated[AwareDatetime, Field(description='When the creative was created')] + updated_date: Annotated[AwareDatetime, Field(description='When the creative was last modified')] + assets: Annotated[ + dict[Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], asset_union.AssetVariant | Assets] | None, + Field( + description='Assets for this creative, keyed by asset_id. Each slot value is either a single asset object or an array of asset objects (for slots with `min`/`max > 1`). Each asset value carries an `asset_type` discriminator that selects the matching asset schema.' + ), + ] = None + component_assets: Annotated[ + dict[Annotated[str, StringConstraints(pattern=r'^[a-z][a-z0-9_]*$')], creative_assets.CreativeAssets] | None, + Field( + description='Preserved component-addressed asset maps for `coordinated_placements`, keyed by coordinated component ID.' + ), + ] = None + localization: Annotated[ + creative_localization_readback.CreativeLocalizationReadback | None, + Field( + description='Authoritative exact materialized locale-variant state. Present for localized creatives when complete. The enclosing creative status is the single review lifecycle for all variants.' + ), + ] = None + localization_unavailable: Annotated[ + LocalizationUnavailable | None, + Field( + description='Per-creative fail-closed state returned instead of localization when the seller knows the creative is localized but cannot construct complete exact readback. The creative remains in this page and counts toward query_summary.returned and pagination; buyers may continue using the base creative fields but MUST NOT infer locale eligibility.' + ), + ] = None + tags: Annotated[ + list[str] | None, Field(description='User-defined tags for organization and searchability') + ] = None + rights: Annotated[ + list[rights_constraint.RightsConstraint] | None, + Field( + description="Exact rights constraints retained from the buyer's creative submission. Presence is presentation readback, not proof that the seller accepted or verified the rights.", + min_length=1, + ), + ] = None + rights_attestation_evaluations: Annotated[ + list[rights_attestation_evaluation.RightsAttestationEvaluation] | None, + Field( + description="Complete seller-produced verifier-of-record results for retained rights references. Buyers MUST ignore any evaluation they originally supplied and rely only on this seller readback for this seller's eligibility decision. This array has no independent item ceiling because rights is not capped; under a required policy every applicable retained constraint needs a corresponding current verified result.", + min_length=1, + ), + ] = None + concept_id: Annotated[ + str | None, + Field( + description='Creative concept this creative belongs to. Concepts group related creatives across sizes and formats.' + ), + ] = None + concept_name: Annotated[str | None, Field(description='Human-readable concept name')] = None + variables: Annotated[ + list[creative_variable.CreativeVariable] | None, + Field( + description='Dynamic content variables (DCO slots) for this creative. Included when include_variables=true.' + ), + ] = None + assignments: Annotated[ + Assignments | None, + Field(description='Current package assignments (included when include_assignments=true)'), + ] = None + snapshot: Annotated[ + Snapshot | None, + Field( + description='Lightweight delivery snapshot (included when include_snapshot=true). For detailed performance analytics, use get_creative_delivery.' + ), + ] = None + snapshot_unavailable_reason: Annotated[ + snapshot_unavailable_reason_1.SnapshotUnavailableReason | None, + Field( + description='Machine-readable reason the snapshot is omitted. Present only when include_snapshot was true and snapshot data is unavailable for this creative.' + ), + ] = None + items: Annotated[ + list[creative_item.CreativeItem] | None, + Field( + description='Items for multi-asset formats like carousels and native ads (included when include_items=true)' + ), + ] = None + pricing_options: Annotated[ + list[vendor_pricing_option.VendorPricingOption] | None, + Field( + description='Pricing options for using this creative (serving, delivery). Used by ad servers and library agents. Transformation agents expose build pricing on canonical transformer.pricing_options entries from list_transformers instead. Present when include_pricing=true and account provided. The buyer passes the applied pricing_option_id in report_usage.', + min_length=1, + ), + ] = None + purge: Annotated[ + Purge | None, + Field( + description="Tombstone block — present only when this record is a soft-purged creative surfaced via `include_purged: true`. The record's `status` field reflects the last status before purge (frozen — buyers MUST treat the creative as gone; assignments, snapshot, and serving operations no longer apply). Tombstones surface for the seller's webhook activity retention window (30 days from `purge.at`). Hard purges (`purge_kind: hard` on the webhook) do not surface on this read — the [`creative.purged`](https://adcontextprotocol.org/schemas/v3/creative/creative-purged-webhook.json) webhook is the only signal." + ), + ] = None + webhook_activity: Annotated[ + list[webhook_activity_record.WebhookActivityRecord] | None, + Field( + description='Recent webhook fires scoped to this creative — creative.status_changed, creative.purged, creative.assignment_changed, and assignment-level indicators.changed deliveries. Present only when include_webhook_activity is true. Account-anchored records include subscriber_id; the parent creative_id disambiguates the record. Retention: 30 days from completed_at. See snapshot-and-log.mdx § Webhook activity log pattern.', + max_length=200, + ), + ] = None + + + @model_validator(mode='after') + def _validate_format_reference_xor(self) -> Creative: + if (self.format_id is None) == (self.format_kind is None): + raise ValueError('exactly one of format_id and format_kind is required') + return self + + +Creatives = Creative +Creatives1 = Creative + -# Explicit exports -__all__ = [ - "Assets", - "AssignedPackage", - "Assignments", - "Creative", - "Creatives", - "Creatives1", - "Indicator", - "IndicatorTypesEvaluatedEnum", - "ListCreativesResponse", - "LocalizationUnavailable", - "Purge", - "QuerySummary", - "Snapshot", - "SortApplied", - "StatusSummary", -] +class ListCreativesResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + query_summary: Annotated[ + QuerySummary, Field(description='Summary of the query that was executed') + ] + pagination: pagination_response.PaginationResponse + creatives: Annotated[ + Sequence[Creative], Field(description='Array of creative assets matching the query') + ] + format_summary: Annotated[ + dict[Annotated[str, StringConstraints(pattern=r'^[a-zA-Z0-9_-]+$')], SchemaInt] | None, + Field( + description='Breakdown of creatives by canonical format kind. Keys SHOULD be `format_kind` values; an implementation may append a stable option suffix when separate product or publisher options must be distinguished.' + ), + ] = None + status_summary: Annotated[ + StatusSummary | None, Field(description='Breakdown of creatives by status') + ] = None + errors: Annotated[ + list[error.Error] | None, + Field(description='Task-specific errors (e.g., invalid filters, account not found)'), + ] = None + sandbox: Annotated[ + StrictBool | None, + Field(description='When true, this response contains simulated data from sandbox mode.'), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/creative/list_transformers_request.py b/src/adcp/types/domains/creative/list_transformers_request.py index 759f72919..ad24fcc9e 100644 --- a/src/adcp/types/domains/creative/list_transformers_request.py +++ b/src/adcp/types/domains/creative/list_transformers_request.py @@ -1,22 +1,114 @@ -"""Types declared by the AdCP ``creative/list_transformers_request`` schema. +# generated by datamodel-codegen: +# filename: creative/list_transformers_request.json +# timestamp: 2026-10-05T13:49:50+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.creative.list_transformers_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field, StrictBool + +from ..core import account_ref +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core import format_id, pagination_request +from ..core.version_envelope import AdcpVersionEnvelope + + +class OutputCapabilityId(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[a-zA-Z0-9_-]+$'} + + +class ExpandPaginationItem(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + transformer_id: Annotated[ + str, Field(description='The transformer whose param options to page.') + ] + field: Annotated[str, Field(description='The param `field` to page.')] + options_cursor: Annotated[ + str, Field(description="Opaque cursor from that param's prior `params[].options_cursor`.") + ] -from adcp.types.generated_poc.creative.list_transformers_request import ( - ExpandPaginationItem, - ListTransformersRequestCreativeAgent, - OutputCapabilityId, -) -# Explicit exports -__all__ = ["ExpandPaginationItem", "ListTransformersRequestCreativeAgent", "OutputCapabilityId"] +class ListTransformersRequestCreativeAgent(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + transformer_ids: Annotated[ + list[str] | None, + Field(description='Return only these specific transformer IDs.', min_length=1), + ] = None + input_format_ids: Annotated[ + list[format_id.FormatReferenceStructuredObject] | None, + Field( + deprecated=True, + description='**DEPRECATED in 3.2.** Filter by legacy named input formats. Use input_format_kinds.', + min_length=1, + ), + ] = None + output_format_ids: Annotated[ + list[format_id.FormatReferenceStructuredObject] | None, + Field( + deprecated=True, + description='**DEPRECATED in 3.2.** Filter by legacy named output formats. Use output_capability_ids.', + min_length=1, + ), + ] = None + input_format_kinds: Annotated[ + list[str] | None, + Field( + description='Filter to transformers whose canonical input_formats include any of these canonical format kinds.', + min_length=1, + ), + ] = None + output_capability_ids: Annotated[ + list[OutputCapabilityId] | None, + Field( + description='Filter to transformers that can produce any of these canonical creative.supported_formats capability IDs.', + min_length=1, + ), + ] = None + name_search: Annotated[ + str | None, + Field(description='Search transformers by name (case-insensitive partial match).'), + ] = None + brief: Annotated[ + str | None, + Field( + description="Natural-language brief used to rank and filter transformers (and their enumerable option values when expanded) — e.g. 'warm female Spanish-language voiceover'. Curates to intent rather than returning the full set, the way get_products curates inventory." + ), + ] = None + expand_params: Annotated[ + list[str] | None, + Field( + description="Param `field` names for which to return the FIRST page of account-scoped option VALUES inline on each transformer's `params[].options[]`. Omit to return param descriptors without enumerated values (the lean default). When a param's options are truncated, its `params[].options_cursor` is set — fetch the next page via `expand_pagination` (below).", + min_length=1, + ), + ] = None + expand_pagination: Annotated[ + list[ExpandPaginationItem] | None, + Field( + description="Fetch the NEXT page of a specific param's account-scoped options, using the `options_cursor` a prior response returned for that `(transformer, param)`. Scoped per `(transformer_id, field)` so multiple params can be paged independently. Use this instead of `expand_params` once you hold a cursor.", + min_length=1, + ), + ] = None + include_pricing: Annotated[ + StrictBool | None, + Field(description='Include `pricing_options` on each transformer. Requires `account`.'), + ] = False + account: Annotated[ + account_ref.AccountReference | None, + Field( + description='Account reference. Transformers are account-scoped — the returned set, the enumerable option values, and (with include_pricing) the rate card are all resolved for this credential.' + ), + ] = None + pagination: pagination_request.PaginationRequest | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/creative/list_transformers_response.py b/src/adcp/types/domains/creative/list_transformers_response.py index 0b4aa5646..b96006e15 100644 --- a/src/adcp/types/domains/creative/list_transformers_response.py +++ b/src/adcp/types/domains/creative/list_transformers_response.py @@ -1,20 +1,34 @@ -"""Types declared by the AdCP ``creative/list_transformers_response`` schema. +# generated by datamodel-codegen: +# filename: creative/list_transformers_response.json +# timestamp: 2026-10-04T02:05:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.creative.list_transformers_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field + +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 +from ..core import pagination_response, transformer +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope -from adcp.types.generated_poc.creative.list_transformers_response import ( - ListTransformersResponseCreativeAgent, -) -# Explicit exports -__all__ = ["ListTransformersResponseCreativeAgent"] +class ListTransformersResponseCreativeAgent(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + transformers: Annotated[ + list[transformer.Transformer], + Field(description='Transformer descriptors matching the query.'), + ] + errors: Annotated[ + list[error.Error] | None, Field(description='Task-specific errors and warnings.') + ] = None + pagination: pagination_response.PaginationResponse | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/creative/preview_creative_request.py b/src/adcp/types/domains/creative/preview_creative_request.py index 59b4b98c3..8c1daca29 100644 --- a/src/adcp/types/domains/creative/preview_creative_request.py +++ b/src/adcp/types/domains/creative/preview_creative_request.py @@ -1,24 +1,212 @@ -"""Types declared by the AdCP ``creative/preview_creative_request`` schema. +# generated by datamodel-codegen: +# filename: creative/preview_creative_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.creative.preview_creative_request import +from adcp.types.base import AdcpRequest, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictBool + +from ..core import context as context_1 +from ..core import creative_manifest as creative_manifest_1 +from ..core import ext as ext_1 +from ..core import format_id as format_id_1 +from ..core import push_notification_config as push_notification_config_1 +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import creative_quality, preview_output_format + + +class RequestType(StrEnum): + single = 'single' + batch = 'batch' + variant = 'variant' + + +class Input(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + name: Annotated[ + str, + Field( + description="Human-readable name for this input set (e.g., 'Sunny morning on mobile', 'Evening podcast ad', 'Desktop dark mode')" + ), + ] + macros: Annotated[ + dict[str, str] | None, + Field( + description="Macro values to use for this preview. Supports all universal macros from the format's supported_macros list." + ), + ] = None + context_description: Annotated[ + str | None, + Field( + description="Natural language description of the context for AI-generated content (e.g., 'User just searched for running shoes', 'Podcast discussing weather patterns')" + ), + ] = None + + +class Input10(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + name: Annotated[str, Field(description='Human-readable name for this input set')] + macros: Annotated[ + dict[str, str] | None, Field(description='Macro values to use for this preview') + ] = None + context_description: Annotated[ + str | None, + Field(description='Natural language description of the context for AI-generated content'), + ] = None + + +class Request(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + target_capability_id: Annotated[ + str | None, + Field( + description='Canonical preview-operation selector for this batch item. Overrides the batch-level target_capability_id and MUST identify an advertised capability whose operations contains preview. If neither item nor batch supplies one, renderer inference is permitted only for a unique compatible preview capability.', + pattern='^[a-zA-Z0-9_-]+$', + ), + ] = None + format_id: Annotated[ + format_id_1.FormatReferenceStructuredObject | None, + Field( + deprecated=True, + description='**DEPRECATED in 3.2.** Legacy named-format preview route. Use target_capability_id plus the canonical identity in creative_manifest.', + ), + ] = None + creative_manifest: Annotated[ + creative_manifest_1.CreativeManifest | None, + Field(description='Complete creative manifest with all required assets.'), + ] = None + creative_id: Annotated[ + str | None, + Field( + description='Creative-library identifier. Use instead of creative_manifest to preview a stored canonical creative.' + ), + ] = None + inputs: Annotated[ + list[Input10] | None, + Field( + description='Array of input sets for generating multiple preview variants', min_length=1 + ), + ] = None + template_id: Annotated[ + str | None, Field(description='Specific template ID for custom format rendering') + ] = None + quality: Annotated[ + creative_quality.CreativeQuality | None, + Field(description='Render quality for this preview. Overrides batch-level default.'), + ] = None + output_format: Annotated[ + preview_output_format.PreviewOutputFormat | None, + Field(description='Output format for this preview. Overrides batch-level default.'), + ] = preview_output_format.PreviewOutputFormat.url + item_limit: Annotated[ + SchemaInt | None, + Field(description='Maximum number of catalog items to render in this preview.', ge=1), + ] = None -from adcp.types.generated_poc.creative.preview_creative_request import ( - Input, - Input10, - PreviewCreativeRequest, - Request, - RequestType, -) -# Explicit exports -__all__ = ["Input", "Input10", "PreviewCreativeRequest", "Request", "RequestType"] +class PreviewCreativeRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + request_type: Annotated[ + RequestType, + Field( + description="Preview mode. 'single' previews one creative manifest. 'batch' previews multiple creatives in one call. 'variant' replays a post-flight variant by ID." + ), + ] + creative_manifest: Annotated[ + creative_manifest_1.CreativeManifest | None, + Field( + description='Complete creative manifest with all required assets for the format. In single mode, provide exactly one of creative_manifest or creative_id. Also accepted per item in batch mode.' + ), + ] = None + target_capability_id: Annotated[ + str | None, + Field( + description="Canonical preview-operation selector. Identifies one get_adcp_capabilities creative.supported_formats[].capability_id entry whose operations contains preview. In single mode it selects the renderer for this request; in batch mode it is the default for items that omit their own target_capability_id. When omitted, the agent MAY resolve the renderer only if exactly one advertised preview capability satisfies the manifest's canonical declaration; zero matches or multiple matches MUST be rejected with FORMAT_NOT_SUPPORTED rather than choosing nondeterministically. Mutually exclusive with deprecated format_id.", + pattern='^[a-zA-Z0-9_-]+$', + ), + ] = None + format_id: Annotated[ + format_id_1.FormatReferenceStructuredObject | None, + Field( + deprecated=True, + description='**DEPRECATED in 3.2.** Legacy named-format preview route. New requests select an advertised preview renderer with target_capability_id and carry portable format identity in creative_manifest_1.format_kind plus optional creative_manifest_1.format_option_ref.', + ), + ] = None + inputs: Annotated[ + list[Input] | None, + Field( + description='Array of input sets for generating multiple preview variants. Each input set defines macros and context values for one preview rendering. Used in single mode.', + min_length=1, + ), + ] = None + template_id: Annotated[ + str | None, + Field(description='Specific template ID for custom format rendering. Used in single mode.'), + ] = None + quality: Annotated[ + creative_quality.CreativeQuality | None, + Field( + description="Render quality. 'draft' produces fast, lower-fidelity renderings. 'production' produces full-quality renderings. In batch mode, sets the default for all requests (individual items can override)." + ), + ] = None + output_format: Annotated[ + preview_output_format.PreviewOutputFormat | None, + Field( + description="Output format. 'url' returns preview_url (iframe-embeddable URL), 'html' returns preview_html (raw HTML). In batch mode, sets the default for all requests (individual items can override). Default: 'url'." + ), + ] = preview_output_format.PreviewOutputFormat.url + item_limit: Annotated[ + SchemaInt | None, + Field( + description='Maximum number of catalog items to render per preview variant. Used in single mode. Creative agents SHOULD default to a reasonable sample when omitted and the catalog is large.', + ge=1, + ), + ] = None + requests: Annotated[ + list[Request] | None, + Field( + description="Array of preview requests (1-50 items). Required when request_type is 'batch'. Each item follows the single request structure.", + max_length=50, + min_length=1, + ), + ] = None + variant_id: Annotated[ + str | None, + Field( + description="Agent-assigned AdCP served-execution identifier from get_creative_delivery. Required when request_type is 'variant'. It is agent-unique when the source agent advertises creative.supports_revisions; for legacy agents the published scope remains agent plus creative, and callers SHOULD also send creative_id to disambiguate reused values." + ), + ] = None + creative_id: Annotated[ + str | None, + Field( + description='Creative-library identifier. In single mode, previews the stored canonical creative without requiring the caller to reconstruct its manifest. Also available as context in variant mode.' + ), + ] = None + allow_async: Annotated[ + StrictBool | None, + Field( + description="Opt in to an asynchronous preview response. When true, the creative agent MAY return status 'submitted' with a task_id only when rendering has been handed to a queue or external renderer and will continue after the request connection is released. Active processing on an open connection uses working progress instead. The buyer polls get_task_status for completion. When false or absent, the agent MUST return a synchronous preview response or a terminal protocol error; it MUST NOT return the submitted shape. This field applies to preview_creative only; build_creative already defines its own async lifecycle." + ), + ] = False + push_notification_config: Annotated[ + push_notification_config_1.PushNotificationConfig | None, + Field( + description='Optional webhook configuration for terminal completion/failure notifications when allow_async is true and preview_creative returns a submitted task envelope. Submitted tasks remain pollable through get_task_status whether or not this field is present. If the agent accepts this configuration and returns submitted, it MUST deliver at least the terminal notification; if it cannot honor the webhook, it MUST return a structured error. Presence of this field alone MUST NOT cause asynchronous execution.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/creative/preview_creative_response.py b/src/adcp/types/domains/creative/preview_creative_response.py index 5d375424e..82d3d678d 100644 --- a/src/adcp/types/domains/creative/preview_creative_response.py +++ b/src/adcp/types/domains/creative/preview_creative_response.py @@ -1,44 +1,131 @@ -"""Types declared by the AdCP ``creative/preview_creative_response`` schema. +# generated by datamodel-codegen: +# filename: creative/preview_creative_response.json +# timestamp: preserved-by-post-generate-fixes -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.creative.preview_creative_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Literal, TypeAlias + +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StringConstraints + +from ..core.version_envelope import AdcpVersionEnvelope +from . import preview_render as preview_render_1 +from ..core import context as context_1 +from ..core import creative_manifest as creative_manifest_1 +from ..core import error as error_1 +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..enums import creative_quality as creative_quality_1 +from ..enums import task_status as task_status_1 + + +class Input(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + name: str + macros: dict[str, str] | None = None + context_description: str | None = None + + +class Preview(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + preview_id: str + renders: Annotated[list[preview_render_1.PreviewRender], Field(min_length=1)] + input: Input + + +class Input2(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + name: str + macros: dict[str, str] | None = None + context_description: str | None = None + + +class Preview2(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + preview_id: str + renders: Annotated[list[preview_render_1.PreviewRender], Field(min_length=1)] + input: Input2 + + +class Response(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + previews: Annotated[list[Preview2], Field(min_length=1)] + interactive_url: AnyUrl | None = None + expires_at: AwareDatetime | None = None + + +class Result(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + success: bool + creative_id: str + quality_used: creative_quality_1.CreativeQuality | None = None + response: Response | None = None + errors: Annotated[list[error_1.Error], Field(min_length=1)] | None = None + + +class Preview3(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + preview_id: str + renders: Annotated[list[preview_render_1.PreviewRender], Field(min_length=1)] + + +class PreviewCreativeResponse1(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + response_type: Literal['single'] = 'single' + previews: Annotated[list[Preview], Field(min_length=1)] + quality_used: creative_quality_1.CreativeQuality | None = None + interactive_url: AnyUrl | None = None + expires_at: AwareDatetime | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class PreviewCreativeResponse2(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + response_type: Literal['batch'] = 'batch' + results: Annotated[list[Result], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class PreviewCreativeResponse3(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + response_type: Literal['variant'] = 'variant' + variant_id: str + creative_id: str | None = None + previews: Annotated[list[Preview3], Field(min_length=1)] + manifest: creative_manifest_1.CreativeManifest | None = None + expires_at: AwareDatetime | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class PreviewCreativeResponse4(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow', validate_default=True) + response_type: Literal['submitted'] = 'submitted' + status: Literal[task_status_1.TaskStatus.submitted] = task_status_1.TaskStatus.submitted + task_id: str + message: Annotated[str, StringConstraints(max_length=2000)] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +PreviewCreativeResponse: TypeAlias = PreviewCreativeResponse1 | PreviewCreativeResponse2 | PreviewCreativeResponse3 | PreviewCreativeResponse4 -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.creative.preview_creative_response import ( - Input, - Input2, - Preview, - Preview2, - Preview3, - PreviewCreativeResponse, - PreviewCreativeResponse1, - PreviewCreativeResponse2, - PreviewCreativeResponse3, - PreviewCreativeResponse4, - Response, - Result, -) - -# Explicit exports __all__ = [ - "Input", - "Input2", - "Preview", - "Preview2", - "Preview3", - "PreviewCreativeResponse", - "PreviewCreativeResponse1", - "PreviewCreativeResponse2", - "PreviewCreativeResponse3", - "PreviewCreativeResponse4", - "Response", - "Result", + 'PreviewCreativeResponse', + 'PreviewCreativeResponse1', + 'PreviewCreativeResponse2', + 'PreviewCreativeResponse3', + 'PreviewCreativeResponse4', + 'Input', + 'Input2', + 'Preview', + 'Preview2', + 'Preview3', + 'Response', + 'Result', ] diff --git a/src/adcp/types/domains/creative/preview_render.py b/src/adcp/types/domains/creative/preview_render.py index 88b2dd77c..30d08fbbb 100644 --- a/src/adcp/types/domains/creative/preview_render.py +++ b/src/adcp/types/domains/creative/preview_render.py @@ -1,32 +1,165 @@ -"""Types declared by the AdCP ``creative/preview_render`` schema. +# generated by datamodel-codegen: +# filename: creative/preview_render.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.creative.preview_render import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field, StrictBool, StrictFloat + +from ..core import preview_renderer_metadata + + +class Dimensions(AdCPBaseModel): + width: Annotated[StrictFloat, Field(ge=0.0)] + height: Annotated[StrictFloat, Field(ge=0.0)] + + +class Embedding(AdCPBaseModel): + recommended_sandbox: Annotated[ + Literal[''], + Field( + description='Empty iframe sandbox token set. Provider metadata MUST NOT grant scripts, same-origin, navigation, popups, forms, downloads, or other capabilities.' + ), + ] = '' + requires_https: Annotated[ + StrictBool | None, + Field(description='Whether this output requires HTTPS for secure embedding'), + ] = None + supports_fullscreen: Annotated[ + StrictBool | None, Field(description='Whether this output supports fullscreen mode') + ] = None + csp_policy: Annotated[ + str | None, Field(description='Content Security Policy requirements for embedding') + ] = None + + +class PreviewRender1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + render_id: Annotated[ + str, Field(description='Unique identifier for this rendered piece within the variant') + ] + output_format: Annotated[ + Literal['url'], Field(description='Discriminator indicating preview_url is provided') + ] = 'url' + preview_url: Annotated[ + AnyUrl, + Field( + description='Untrusted URL to an HTML page that renders this piece. Consumers MUST load it only in a cross-origin iframe with an empty sandbox token set and a caller-enforced restrictive CSP. Provider embedding metadata is advisory and MUST NOT loosen that policy.' + ), + ] + role: Annotated[ + str, + Field( + description="Semantic role of this rendered piece. Use 'primary' for main content, 'companion' for associated banners, descriptive strings for device variants or custom roles." + ), + ] + dimensions: Annotated[ + Dimensions | None, Field(description='Dimensions for this rendered piece') + ] = None + embedding: Annotated[ + Embedding | None, + Field(description='Optional security and embedding metadata for safe iframe integration'), + ] = None + renderer: Annotated[ + preview_renderer_metadata.PreviewRendererMetadata | None, + Field( + description='Optional renderer implementation and safety metadata for audit and reproducibility. Authority is still resolved from capability discovery and placement delegation.' + ), + ] = None + + +class PreviewRender2(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + render_id: Annotated[ + str, Field(description='Unique identifier for this rendered piece within the variant') + ] + output_format: Annotated[ + Literal['html'], Field(description='Discriminator indicating preview_html is provided') + ] = 'html' + preview_html: Annotated[ + str, + Field( + description='Untrusted HTML. Consumers MUST NOT inject it into the host DOM. Render only as iframe srcdoc with an empty sandbox token set and a caller-enforced restrictive CSP; provider embedding metadata is advisory and MUST NOT loosen that policy.' + ), + ] + role: Annotated[ + str, + Field( + description="Semantic role of this rendered piece. Use 'primary' for main content, 'companion' for associated banners, descriptive strings for device variants or custom roles." + ), + ] + dimensions: Annotated[ + Dimensions | None, Field(description='Dimensions for this rendered piece') + ] = None + embedding: Annotated[ + Embedding | None, Field(description='Optional security and embedding metadata') + ] = None + renderer: Annotated[ + preview_renderer_metadata.PreviewRendererMetadata | None, + Field( + description='Optional renderer implementation and safety metadata for audit and reproducibility. Authority is still resolved from capability discovery and placement delegation.' + ), + ] = None + + +class PreviewRender3(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + render_id: Annotated[ + str, Field(description='Unique identifier for this rendered piece within the variant') + ] + output_format: Annotated[ + Literal['both'], + Field( + description='Discriminator indicating both preview_url and preview_html are provided' + ), + ] = 'both' + preview_url: Annotated[ + AnyUrl, + Field( + description='Untrusted URL to an HTML page that renders this piece. Consumers MUST load it only in a cross-origin iframe with an empty sandbox token set and a caller-enforced restrictive CSP. Provider embedding metadata is advisory and MUST NOT loosen that policy.' + ), + ] + preview_html: Annotated[ + str, + Field( + description='Untrusted HTML. Consumers MUST NOT inject it into the host DOM. Render only as iframe srcdoc with an empty sandbox token set and a caller-enforced restrictive CSP; provider embedding metadata is advisory and MUST NOT loosen that policy.' + ), + ] + role: Annotated[ + str, + Field( + description="Semantic role of this rendered piece. Use 'primary' for main content, 'companion' for associated banners, descriptive strings for device variants or custom roles." + ), + ] + dimensions: Annotated[ + Dimensions | None, Field(description='Dimensions for this rendered piece') + ] = None + embedding: Annotated[ + Embedding | None, + Field(description='Optional security and embedding metadata for safe iframe integration'), + ] = None + renderer: Annotated[ + preview_renderer_metadata.PreviewRendererMetadata | None, + Field( + description='Optional renderer implementation and safety metadata for audit and reproducibility. Authority is still resolved from capability discovery and placement delegation.' + ), + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.creative.preview_render import ( - Dimensions, - Embedding, - PreviewRender, - PreviewRender1, - PreviewRender2, - PreviewRender3, -) - -# Explicit exports -__all__ = [ - "Dimensions", - "Embedding", - "PreviewRender", - "PreviewRender1", - "PreviewRender2", - "PreviewRender3", +PreviewRender = Annotated[ + PreviewRender1 | PreviewRender2 | PreviewRender3, + Field( + description='A single rendered piece of a creative preview with discriminated output format', + discriminator='output_format', + title='Preview Render', + ), ] diff --git a/src/adcp/types/domains/creative/sync_creatives_async_response_input_required.py b/src/adcp/types/domains/creative/sync_creatives_async_response_input_required.py index 9366e4e89..64c70998f 100644 --- a/src/adcp/types/domains/creative/sync_creatives_async_response_input_required.py +++ b/src/adcp/types/domains/creative/sync_creatives_async_response_input_required.py @@ -1,21 +1,31 @@ -"""Types declared by the AdCP ``creative/sync_creatives_async_response_input_required`` schema. +# generated by datamodel-codegen: +# filename: creative/sync_creatives_async_response_input_required.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.creative.sync_creatives_async_response_input_required import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import context as context_1 +from ..core import ext as ext_1 + + +class Reason(StrEnum): + APPROVAL_REQUIRED = 'APPROVAL_REQUIRED' + ASSET_CONFIRMATION = 'ASSET_CONFIRMATION' + FORMAT_CLARIFICATION = 'FORMAT_CLARIFICATION' -from adcp.types.generated_poc.creative.sync_creatives_async_response_input_required import ( - Reason, - SyncCreativesInputRequired, -) -# Explicit exports -__all__ = ["Reason", "SyncCreativesInputRequired"] +class SyncCreativesInputRequired(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + reason: Annotated[ + Reason | None, Field(description='Reason code indicating why buyer input is needed') + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/creative/sync_creatives_async_response_submitted.py b/src/adcp/types/domains/creative/sync_creatives_async_response_submitted.py index ff3fd1f92..15a54bc6d 100644 --- a/src/adcp/types/domains/creative/sync_creatives_async_response_submitted.py +++ b/src/adcp/types/domains/creative/sync_creatives_async_response_submitted.py @@ -1,20 +1,47 @@ -"""Types declared by the AdCP ``creative/sync_creatives_async_response_submitted`` schema. +# generated by datamodel-codegen: +# filename: creative/sync_creatives_async_response_submitted.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.creative.sync_creatives_async_response_submitted import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 -from adcp.types.generated_poc.creative.sync_creatives_async_response_submitted import ( - SyncCreativesSubmitted, -) -# Explicit exports -__all__ = ["SyncCreativesSubmitted"] +class SyncCreativesSubmitted(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + status: Annotated[ + Literal['submitted'], + Field( + description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, whose creatives array carries per-item approval state via CreativeStatus. See task-status.json for the full task-status enum.' + ), + ] = 'submitted' + task_id: Annotated[ + str, + Field( + description='Task handle the buyer uses with get_task_status (or the legacy AdCP tasks/get alias), and that the seller references on push-notification callbacks. The creatives array is issued on the completion artifact, not here. This AdCP application-layer handle remains the snake_case task_id in every transport payload and is distinct from any transport-native A2A Task id.' + ), + ] + message: Annotated[ + str | None, + Field( + description="Optional human-readable explanation of why the task is submitted — e.g., 'Batch ingestion queued; typical turnaround 15-30 minutes.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", + max_length=2000, + ), + ] = None + errors: Annotated[ + list[error.Error] | None, + Field( + description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/creative/sync_creatives_async_response_working.py b/src/adcp/types/domains/creative/sync_creatives_async_response_working.py index fbb44bd84..24172dac3 100644 --- a/src/adcp/types/domains/creative/sync_creatives_async_response_working.py +++ b/src/adcp/types/domains/creative/sync_creatives_async_response_working.py @@ -1,20 +1,37 @@ -"""Types declared by the AdCP ``creative/sync_creatives_async_response_working`` schema. +# generated by datamodel-codegen: +# filename: creative/sync_creatives_async_response_working.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.creative.sync_creatives_async_response_working import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import context as context_1 +from ..core import ext as ext_1 -from adcp.types.generated_poc.creative.sync_creatives_async_response_working import ( - SyncCreativesWorking, -) -# Explicit exports -__all__ = ["SyncCreativesWorking"] +class SyncCreativesWorking(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + percentage: Annotated[ + StrictFloat | None, Field(description='Completion percentage (0-100)', ge=0.0, le=100.0) + ] = None + current_step: Annotated[ + str | None, Field(description='Current step or phase of the operation') + ] = None + total_steps: Annotated[ + SchemaInt | None, Field(description='Total number of steps in the operation', ge=1) + ] = None + step_number: Annotated[SchemaInt | None, Field(description='Current step number', ge=1)] = None + creatives_processed: Annotated[ + SchemaInt | None, Field(description='Number of creatives processed so far', ge=0) + ] = None + creatives_total: Annotated[ + SchemaInt | None, Field(description='Total number of creatives to process', ge=0) + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/creative/sync_creatives_request.py b/src/adcp/types/domains/creative/sync_creatives_request.py index fa46ca252..5c52e1bbb 100644 --- a/src/adcp/types/domains/creative/sync_creatives_request.py +++ b/src/adcp/types/domains/creative/sync_creatives_request.py @@ -1,36 +1,194 @@ -"""Types declared by the AdCP ``creative/sync_creatives_request`` schema. +# generated by datamodel-codegen: +# filename: creative/sync_creatives_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.creative.sync_creatives_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Literal + +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field, StrictBool, StrictFloat, model_validator + +from ..core import account_ref +from ..core import context as context_1 +from ..core import creative_localization, creative_revision_id +from ..core import ext as ext_1 +from ..core import push_notification_config as push_notification_config_1 +from ..core.creative_asset import CreativeAsset +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import validation_mode as validation_mode_1 + + +class Assignment(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + creative_id: Annotated[str, Field(description='ID of the creative to assign')] + package_id: Annotated[str, Field(description='ID of the package to assign the creative to')] + weight: Annotated[ + StrictFloat | None, + Field( + description='Relative delivery weight (0-100). When multiple creatives are assigned to the same package, weights determine impression distribution proportionally. When omitted, the creative receives equal rotation with other unweighted creatives. A weight of 0 means the creative is assigned but paused (receives no delivery).', + ge=0.0, + le=100.0, + ), + ] = None + placement_ids: Annotated[ + list[str] | None, + Field( + description='Restrict this creative to specific placements within the package. When omitted, the creative is eligible for all placements.', + min_length=1, + ), + ] = None + + +class PlacementId(ScalarStr): + __slots__ = () + _constraints = {'min_length': 1} + + +class AssignmentOperations1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + operation: Literal['assign'] = 'assign' + creative_id: Annotated[str, Field(min_length=1)] + package_id: Annotated[str, Field(min_length=1)] + weight: Annotated[StrictFloat | None, Field(ge=0.0, le=100.0)] = None + placement_ids: Annotated[list[PlacementId] | None, Field(min_length=1)] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.creative.sync_creatives_request import ( - Assignment, - AssignmentOperations, - AssignmentOperations1, - AssignmentOperations2, - AssignmentOperations3, - Creative, - PlacementId, - SyncCreativesRequest, -) - -# Explicit exports -__all__ = [ - "Assignment", - "AssignmentOperations", - "AssignmentOperations1", - "AssignmentOperations2", - "AssignmentOperations3", - "Creative", - "PlacementId", - "SyncCreativesRequest", +class AssignmentOperations2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + operation: Literal['unassign'] = 'unassign' + creative_id: Annotated[str, Field(min_length=1)] + package_id: Annotated[str, Field(min_length=1)] + + +class AssignmentOperations3(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + operation: Literal['replace'] = 'replace' + creative_id: Annotated[str, Field(description='Replacement creative ID.', min_length=1)] + replaces_creative_id: Annotated[ + str, Field(description='Currently assigned creative ID to remove atomically.', min_length=1) + ] + package_id: Annotated[str, Field(min_length=1)] + weight: Annotated[StrictFloat | None, Field(ge=0.0, le=100.0)] = None + placement_ids: Annotated[list[PlacementId] | None, Field(min_length=1)] = None + + +AssignmentOperations = Annotated[ + AssignmentOperations1 | AssignmentOperations2 | AssignmentOperations3, + Field(discriminator='operation'), ] + + +class Creative(CreativeAsset): + revision_id: Annotated[ + creative_revision_id.CreativeRevisionId | None, + Field( + description='Optional buyer-assigned identity for this exact input-content state. Buyers may send it to any 3.2 peer; creative.supports_revisions gates reliance on immutability, echo, readback, and delivery correlation, not whether the open request shape may carry the field. Within a supporting seller, a revision_id is immutable under one creative_id: reusing it with different canonical revision content MUST fail with CREATIVE_REVISION_CONTENT_MISMATCH. Metadata and assignment fields excluded from revision content may change without minting a new revision.' + ), + ] = None + localization: Annotated[ + creative_localization.CreativeLocalization | None, + Field( + description='Sync-only materialized-localization mutation. The top-level assets are the source variant; optional locale_fallbacks declare buyer-approved language-family substitutions, and default_locale_variant_id selects the final serving fallback. An object transactionally replaces the source assets and complete locale set, omission preserves existing localization only when the top-level source assets are unchanged, and null removes localization. This field never requests translation or generation. Receivers MUST advertise get_adcp_capabilities creative.localization before accepting it.' + ), + ] = None + + +class SyncCreativesRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + account: Annotated[ + account_ref.AccountReference, Field(description='Account that owns these creatives.') + ] + creatives: Annotated[ + list[Creative] | None, + Field( + description='Array of creative assets to sync (create or update)', + max_length=100, + min_length=1, + ), + ] = None + creative_ids: Annotated[ + list[str] | None, + Field( + description='Optional filter to limit sync scope to specific creative IDs. When provided, only these creatives will be created/updated. Other creatives in the library are unaffected. Useful for partial updates and error recovery.', + max_length=100, + min_length=1, + ), + ] = None + assignments: Annotated[ + list[Assignment] | None, + Field( + deprecated=True, + description='Deprecated additive assignment shorthand. Each entry upserts one creative-to-package assignment. Use assignment_operations for explicit assign, unassign, and replace semantics. Standalone creative agents that do not manage media buys ignore this field.', + min_length=1, + ), + ] = None + assignment_operations: Annotated[ + list[AssignmentOperations] | None, + Field( + description='Explicit, ordered assignment mutations. These operations may be sent without creatives to traffic existing creative IDs independently from MediaBuy commercial control. The entire request is atomic under idempotency_key and therefore requires strict validation; lenient partial processing is not permitted.', + max_length=500, + min_length=1, + ), + ] = None + idempotency_key: Annotated[ + str, + Field( + description='Client-generated idempotency key for safe retries. If a sync fails without a response, resending with the same idempotency_key guarantees at-most-once execution. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + delete_missing: Annotated[ + StrictBool | None, + Field( + description='When true, creatives not included in this sync will be archived. Use with caution for full library replacement. Invalid when creative_ids is provided — delete_missing applies to the entire library scope, not a filtered subset.' + ), + ] = False + dry_run: Annotated[ + StrictBool | None, + Field( + description="When true, rehearse this sync_creatives operation without applying it. Validates the actual trafficking request in the seller's current context, including library upsert semantics, creative IDs, assignments, account-scoped gates, and seller policies, then returns what would be created/updated/deleted. This is distinct from validate_input, which only validates manifest structure against canonical/product format targets." + ), + ] = False + validation_mode: Annotated[ + validation_mode_1.ValidationMode | None, + Field( + description="Validation strictness. 'strict' fails entire sync on any validation error. 'lenient' processes valid creatives and reports errors." + ), + ] = validation_mode_1.ValidationMode.strict + push_notification_config: Annotated[ + push_notification_config_1.PushNotificationConfig | None, + Field( + description='Optional webhook configuration for async sync notifications. The agent will send a webhook when sync completes if the operation takes longer than immediate response time (typically for large bulk operations or manual approval/HITL).' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + @model_validator(mode='after') + def _require_schema_required_group(self) -> SyncCreativesRequest: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('creatives',), ('assignments',), ('assignment_operations',),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'SyncCreativesRequest requires at least one of these field groups: creatives | assignments | assignment_operations' + ) diff --git a/src/adcp/types/domains/creative/sync_creatives_response.py b/src/adcp/types/domains/creative/sync_creatives_response.py index 51a55814b..5a11c8cce 100644 --- a/src/adcp/types/domains/creative/sync_creatives_response.py +++ b/src/adcp/types/domains/creative/sync_creatives_response.py @@ -1,30 +1,81 @@ -"""Types declared by the AdCP ``creative/sync_creatives_response`` schema. +# generated by datamodel-codegen: +# filename: creative/sync_creatives_response.json +# timestamp: preserved-by-post-generate-fixes -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.creative.sync_creatives_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Literal, TypeAlias -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StringConstraints + +from ..core.version_envelope import AdcpVersionEnvelope +from ..core import account as account_1 +from ..core import context as context_1 +from ..core import creative_localization_readback as creative_localization_readback_1 +from ..core import creative_revision_id as creative_revision_id_1 +from ..core import error as error_1 +from ..core import ext as ext_1 +from ..core import macro_resolution_result as macro_resolution_result_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..enums import creative_action as creative_action_1 +from ..enums import creative_status as creative_status_1 +from ..enums import task_status as task_status_1 + + +class Creative(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + creative_id: str + revision_id: creative_revision_id_1.CreativeRevisionId | None = None + account: account_1.Account | None = None + action: creative_action_1.CreativeAction + status: creative_status_1.CreativeStatus | None = None + platform_id: str | None = None + localization: creative_localization_readback_1.CreativeLocalizationReadback | None = None + changes: list[str] | None = None + errors: list[error_1.Error] | None = None + warnings: list[str] | None = None + macro_resolution_results: list[macro_resolution_result_1.MacroResolutionResult] | None = None + preview_url: AnyUrl | None = None + expires_at: AwareDatetime | None = None + assigned_to: list[str] | None = None + assignment_errors: dict[Annotated[str, StringConstraints(pattern='^[a-zA-Z0-9_-]+$')], str] | None = None + + +class SyncCreativesResponse1(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + dry_run: bool | None = None + creatives: list[Creative] + sandbox: bool | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class SyncCreativesResponse2(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + errors: Annotated[list[error_1.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class SyncCreativesResponse3(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow', validate_default=True) + status: Literal[task_status_1.TaskStatus.submitted] = task_status_1.TaskStatus.submitted + task_id: str + message: Annotated[str, StringConstraints(max_length=2000)] | None = None + errors: list[error_1.Error] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +SyncCreativesResponse: TypeAlias = SyncCreativesResponse1 | SyncCreativesResponse2 | SyncCreativesResponse3 -from adcp.types.generated_poc.creative.sync_creatives_response import ( - Creative, - SyncCreativesResponse, - SyncCreativesResponse1, - SyncCreativesResponse2, - SyncCreativesResponse3, -) -# Explicit exports __all__ = [ - "Creative", - "SyncCreativesResponse", - "SyncCreativesResponse1", - "SyncCreativesResponse2", - "SyncCreativesResponse3", + 'SyncCreativesResponse', + 'SyncCreativesResponse1', + 'SyncCreativesResponse2', + 'SyncCreativesResponse3', + 'Creative', ] diff --git a/src/adcp/types/domains/creative/validate_input_request.py b/src/adcp/types/domains/creative/validate_input_request.py index 253011712..a8bfc5f2a 100644 --- a/src/adcp/types/domains/creative/validate_input_request.py +++ b/src/adcp/types/domains/creative/validate_input_request.py @@ -1,25 +1,90 @@ -"""Types declared by the AdCP ``creative/validate_input_request`` schema. +# generated by datamodel-codegen: +# filename: creative/validate_input_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.creative.validate_input_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Literal + +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field + +from ..core import account_ref, brand_ref, creative_manifest + + +class Targets1(AdCPBaseModel): + kind: Literal['canonical'] = 'canonical' + id: Annotated[ + str, + Field( + description="Canonical format name from `canonical-format-kind.json` (e.g., `image`, `video_hosted`, `audio_daast`). Validators check the manifest against the canonical's parameter schema." + ), + ] + + +class Targets2(AdCPBaseModel): + kind: Literal['product'] = 'product' + id: Annotated[ + str, + Field( + description="Product ID. Validators check the manifest against the product's inline `ProductFormatDeclaration` narrowing of the canonical (parameter constraints, slot requirements, platform extensions)." + ), + ] + + +class Targets3(AdCPBaseModel): + kind: Literal['third_party_format'] = 'third_party_format' + id: Annotated[ + AnyUrl, + Field( + description='URI-form format identifier referencing a third-party format definition (e.g., `https://creativevendor.example/formats/image_300x250@sha256:...`). Validators fetch the definition (digest-pinned, cached) and validate the manifest against it.' + ), + ] + + +class Targets4(AdCPBaseModel): + kind: Literal['capability'] = 'capability' + id: Annotated[ + str, + Field( + description='Agent-local capability_id from get_adcp_capabilities creative.supported_formats. The selected entry MUST include validate in operations; unknown IDs or entries without validate are rejected with FORMAT_NOT_SUPPORTED.', + pattern='^[a-zA-Z0-9_-]+$', + ), + ] + + +Targets = Annotated[ + Targets1 | Targets2 | Targets3 | Targets4, + Field(discriminator='kind'), +] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.creative.validate_input_request import ( - Targets, - Targets1, - Targets2, - Targets3, - Targets4, - ValidateInputRequest, -) - -# Explicit exports -__all__ = ["Targets", "Targets1", "Targets2", "Targets3", "Targets4", "ValidateInputRequest"] +class ValidateInputRequest(AdcpRequest, AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + account: Annotated[ + account_ref.AccountReference | None, + Field( + description='Optional account scope for seller-specific product validation. Required by sellers that route product declarations by buyer account.' + ), + ] = None + brand: Annotated[ + brand_ref.BrandReference | None, + Field( + description='Optional brand scope when account is omitted or the seller keys sandbox validation by brand identity.' + ), + ] = None + manifest: Annotated[ + creative_manifest.CreativeManifest, Field(description='Creative manifest to validate.') + ] + targets: Annotated[ + list[Targets] | None, + Field( + description="Discriminated list of validation targets. Each entry mirrors the `target` shape on `validate-input-result.json` so the request/response wire shapes match exactly. Multi-target requests enable universal-creative scenarios where one manifest targets multiple sellers' format declarations in a single round-trip; the response carries one result per target in the same order.", + max_length=50, + min_length=1, + ), + ] = None diff --git a/src/adcp/types/domains/creative/validate_input_response.py b/src/adcp/types/domains/creative/validate_input_response.py index b1488f34b..4d1864281 100644 --- a/src/adcp/types/domains/creative/validate_input_response.py +++ b/src/adcp/types/domains/creative/validate_input_response.py @@ -1,18 +1,25 @@ -"""Types declared by the AdCP ``creative/validate_input_response`` schema. +# generated by datamodel-codegen: +# filename: creative/validate_input_response.json +# timestamp: 2026-10-04T02:05:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.creative.validate_input_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field + +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from . import validate_input_result -from adcp.types.generated_poc.creative.validate_input_response import ValidateInputResponse -# Explicit exports -__all__ = ["ValidateInputResponse"] +class ValidateInputResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + results: Annotated[ + list[validate_input_result.ValidateInputResult], + Field(description='Per-target validation results.'), + ] diff --git a/src/adcp/types/domains/creative/validate_input_result.py b/src/adcp/types/domains/creative/validate_input_result.py index db2cc235f..c7b4fbbf7 100644 --- a/src/adcp/types/domains/creative/validate_input_result.py +++ b/src/adcp/types/domains/creative/validate_input_result.py @@ -1,25 +1,110 @@ -"""Types declared by the AdCP ``creative/validate_input_result`` schema. +# generated by datamodel-codegen: +# filename: creative/validate_input_result.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.creative.validate_input_result import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field + +from ..core import macro_resolution_result + + +class Kind(StrEnum): + canonical = 'canonical' + product = 'product' + third_party_format = 'third_party_format' + capability = 'capability' + + +class Target(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + kind: Kind + id: Annotated[ + str, + Field( + description="Canonical format name (e.g., 'image'), product_id, URI-form third-party format identifier, or agent-local creative capability_id." + ), + ] + + +class ResultKind(StrEnum): + validated_pass = 'validated_pass' + validated_fail = 'validated_fail' + unvalidatable_nondeterministic = 'unvalidatable_nondeterministic' + + +class Violation(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + rule: Annotated[ + str, + Field( + description="Rule name (e.g., 'duration_ms_range', 'aspect_ratio', 'max_file_size_kb')." + ), + ] + expected: Annotated[ + Any | None, Field(description="Expected value or range (e.g., '28000-32000', '9:16', 200).") + ] = None + predicted: Annotated[ + Any | None, + Field( + description="Platform's pre-flight estimate for this field (NOT the actual output — there is no protocol state for orphaned out-of-spec artifacts). For TTS, this might be the predicted audio duration from text-length analysis. Helps the buyer fix the input before committing to a build." + ), + ] = None + field: Annotated[ + str, + Field(description="Path to the violating field (e.g., 'assets.video_main.duration_ms')."), + ] + retry_with: Annotated[ + dict[str, Any] | None, + Field( + description='Optional advisory adjustment hint. Platforms MAY suggest a corrected input shape; buyers MUST treat this as advisory, not authoritative.' + ), + ] = None + + +class Warning(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + rule: str + expected: Any | None = None + predicted: Any | None = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.creative.validate_input_result import ( - Kind, - ResultKind, - Target, - ValidateInputResult, - Violation, - Warning, -) - -# Explicit exports -__all__ = ["Kind", "ResultKind", "Target", "ValidateInputResult", "Violation", "Warning"] +class ValidateInputResult(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + target: Target + result_kind: Annotated[ + ResultKind, + Field( + description="Discriminator for the validation outcome. See schema description for the three states. Replaces the earlier boolean `ok` to distinguish 'failed validation' from 'platform is nondeterministic, can't pre-validate'." + ), + ] + violations: Annotated[ + list[Violation] | None, + Field( + description="When `result_kind` is `validated_fail`, the specific constraints the manifest fails to meet. MUST be absent (or empty) for `validated_pass` and `unvalidatable_nondeterministic` — neither has constraint violations to enumerate (`unvalidatable_nondeterministic` doesn't validate at all; `validated_pass` has nothing to fail)." + ), + ] = None + warnings: Annotated[ + list[Warning] | None, + Field( + description='Non-blocking observations (e.g. LEAN policy advisories such as hover-triggered expansion or non-user-initiated entry into overlay anchoring) that do not affect `result_kind`. MAY be present alongside `validated_pass`, `validated_fail`, or `unvalidatable_nondeterministic`. Same item shape as `violations`.' + ), + ] = None + macro_resolution_results: Annotated[ + list[macro_resolution_result.MacroResolutionResult] | None, + Field( + description='Per-token compatibility against the selected target. Required or unsafe `unsupported`/`ambiguous` results make the target `validated_fail`; deliberate preservation for the declared downstream resolver may still pass.' + ), + ] = None diff --git a/src/adcp/types/domains/creative/video_brief.py b/src/adcp/types/domains/creative/video_brief.py index dcbe154dc..bee1c4e05 100644 --- a/src/adcp/types/domains/creative/video_brief.py +++ b/src/adcp/types/domains/creative/video_brief.py @@ -1,18 +1,48 @@ -"""Types declared by the AdCP ``creative/video_brief`` schema. +# generated by datamodel-codegen: +# filename: creative/video_brief.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.creative.video_brief import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.creative.video_brief import Segment, VideoBrief +class Segment(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + order: Annotated[ + SchemaInt, + Field(description='1-indexed sequence position of this segment in the final video.', ge=1), + ] + duration_ms: Annotated[ + SchemaInt, Field(description='Duration of this segment in milliseconds.', ge=1) + ] + prompt: Annotated[ + str, + Field( + description='Text prompt fed to the synthesis pipeline for this segment (subject, action, setting, mood). Renamed from earlier `description` to make explicit that this is a generation prompt — not a description-of-finished-content.' + ), + ] + vo: Annotated[str | None, Field(description='Voiceover line for this segment (optional).')] = ( + None + ) + caption: Annotated[ + str | None, Field(description='On-screen caption text for this segment (optional).') + ] = None + -# Explicit exports -__all__ = ["Segment", "VideoBrief"] +class VideoBrief(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + segments: Annotated[ + list[Segment], + Field( + description="Ordered list of per-segment prompts that compose the generated video. The sum of `duration_ms` across segments should match the target video duration declared by the format declaration's `duration_ms_exact` or `duration_ms_range`.", + min_length=1, + ), + ] diff --git a/src/adcp/types/domains/enums/__init__.py b/src/adcp/types/domains/enums/__init__.py index 43d02ec05..b5693f91b 100644 --- a/src/adcp/types/domains/enums/__init__.py +++ b/src/adcp/types/domains/enums/__init__.py @@ -9,230 +9,225 @@ it from its own schema's module, ``adcp.types.domains.enums.``. Nothing here is renamed. -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:33:59 UTC +Auto-generated from the generated domain tree. DO NOT EDIT MANUALLY. +Generation date: 2026-10-04 18:45:11 UTC """ # ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.account_currency_mode import AccountCurrencyMode -from adcp.types.generated_poc.enums.account_scope import AccountScope -from adcp.types.generated_poc.enums.account_status import AccountStatus -from adcp.types.generated_poc.enums.action_not_allowed_reason import ActionNotAllowedReason -from adcp.types.generated_poc.enums.action_source import ActionSource -from adcp.types.generated_poc.enums.activation_method import CreativeActivationMethod -from adcp.types.generated_poc.enums.adcp_protocol import AdcpProtocol -from adcp.types.generated_poc.enums.adjustment_kind import PriceAdjustmentKind -from adcp.types.generated_poc.enums.advertiser_industry import AdvertiserIndustry -from adcp.types.generated_poc.enums.age_determination_basis import AgeDeterminationBasis -from adcp.types.generated_poc.enums.age_verification_method import AgeVerificationMethod -from adcp.types.generated_poc.enums.assessment_status import AssessmentStatus -from adcp.types.generated_poc.enums.asset_content_type import AssetContentType -from adcp.types.generated_poc.enums.attestation_claim import AttestationClaim -from adcp.types.generated_poc.enums.attribution_methodology import AttributionMethodology -from adcp.types.generated_poc.enums.attribution_model import AttributionModel -from adcp.types.generated_poc.enums.audience_evidence_methodology import AudienceEvidenceMethodology -from adcp.types.generated_poc.enums.audience_resolution_method import AudienceResolutionMethod -from adcp.types.generated_poc.enums.audience_source import AudienceSource -from adcp.types.generated_poc.enums.audience_status import AudienceStatus -from adcp.types.generated_poc.enums.audience_subject_type import AudienceSubjectType -from adcp.types.generated_poc.enums.audio_channel_layout import AudioChannelLayout -from adcp.types.generated_poc.enums.audio_distribution_type import AudioDistributionType -from adcp.types.generated_poc.enums.auth_scheme import AuthenticationScheme -from adcp.types.generated_poc.enums.availability_status import AvailabilityStatus -from adcp.types.generated_poc.enums.available_metric import AvailableMetric -from adcp.types.generated_poc.enums.billing_party import BillingParty -from adcp.types.generated_poc.enums.binary_verdict import BinaryVerdict -from adcp.types.generated_poc.enums.brand_agent_type import BrandAgentType -from adcp.types.generated_poc.enums.browser_family import BrowserFamily -from adcp.types.generated_poc.enums.c2pa_watermark_action import C2PaWatermarkAction -from adcp.types.generated_poc.enums.canceled_by import CanceledBy -from adcp.types.generated_poc.enums.canonical_media_buy_action import CanonicalMediaBuyActionName -from adcp.types.generated_poc.enums.canonical_media_buy_action_mode import ( - CanonicalMediaBuyActionMode, -) -from adcp.types.generated_poc.enums.catalog_action import CatalogAction -from adcp.types.generated_poc.enums.catalog_item_status import CatalogItemStatus -from adcp.types.generated_poc.enums.catalog_type import CatalogType -from adcp.types.generated_poc.enums.channels import MediaChannel -from adcp.types.generated_poc.enums.cloud_storage_protocol import CloudStorageProtocol -from adcp.types.generated_poc.enums.co_branding_requirement import CoBrandingRequirement -from adcp.types.generated_poc.enums.collection_cadence import CollectionCadence -from adcp.types.generated_poc.enums.collection_kind import CollectionKind -from adcp.types.generated_poc.enums.collection_relationship import CollectionRelationship -from adcp.types.generated_poc.enums.collection_status import CollectionStatus -from adcp.types.generated_poc.enums.completion_source import CompletionSource -from adcp.types.generated_poc.enums.consent_basis import ConsentBasis -from adcp.types.generated_poc.enums.content_id_type import ContentIdType -from adcp.types.generated_poc.enums.content_rating_system import ContentRatingSystem -from adcp.types.generated_poc.enums.creative_action import CreativeAction -from adcp.types.generated_poc.enums.creative_agent_capability import CreativeAgentCapability -from adcp.types.generated_poc.enums.creative_approval_status import CreativeApprovalStatus -from adcp.types.generated_poc.enums.creative_event_reason_code import CreativeEventReasonCode -from adcp.types.generated_poc.enums.creative_identifier_type import CreativeIdentifierType -from adcp.types.generated_poc.enums.creative_quality import CreativeQuality -from adcp.types.generated_poc.enums.creative_selection_strategy import CreativeSelectionStrategy -from adcp.types.generated_poc.enums.creative_sort_field import CreativeSortField -from adcp.types.generated_poc.enums.creative_status import CreativeStatus -from adcp.types.generated_poc.enums.ctv_ad_experience import CtvAdExperience -from adcp.types.generated_poc.enums.daast_tracking_event import DaastTrackingEvent -from adcp.types.generated_poc.enums.daast_version import DaastVersion -from adcp.types.generated_poc.enums.day_of_week import DayOfWeek -from adcp.types.generated_poc.enums.daypart_timezone_mode import DaypartTimezoneMode -from adcp.types.generated_poc.enums.delegation_authority import DelegationAuthority -from adcp.types.generated_poc.enums.delivery_recipient_cloud import DeliveryRecipientCloud -from adcp.types.generated_poc.enums.delivery_status import DeliveryStatus -from adcp.types.generated_poc.enums.delivery_type import DeliveryType -from adcp.types.generated_poc.enums.demographic_system import DemographicSystem -from adcp.types.generated_poc.enums.derivative_type import DerivativeType -from adcp.types.generated_poc.enums.device_platform import DevicePlatform -from adcp.types.generated_poc.enums.device_type import DeviceType -from adcp.types.generated_poc.enums.digital_source_type import DigitalSourceType -from adcp.types.generated_poc.enums.dimension_unit import DimensionUnit -from adcp.types.generated_poc.enums.disclosure_persistence import DisclosurePersistence -from adcp.types.generated_poc.enums.disclosure_position import DisclosurePosition -from adcp.types.generated_poc.enums.distance_unit import DistanceUnit -from adcp.types.generated_poc.enums.distribution_identifier_type import DistributionIdentifierType -from adcp.types.generated_poc.enums.dooh_motion_type import DoohMotionType -from adcp.types.generated_poc.enums.embedded_provenance_method import EmbeddedProvenanceMethod -from adcp.types.generated_poc.enums.error_code import ErrorCode -from adcp.types.generated_poc.enums.error_scope import ErrorScope -from adcp.types.generated_poc.enums.escalation_severity import EscalationSeverity -from adcp.types.generated_poc.enums.event_type import EventType -from adcp.types.generated_poc.enums.exclusivity import Exclusivity -from adcp.types.generated_poc.enums.feature_check_status import FeatureCheckStatus -from adcp.types.generated_poc.enums.feed_format import FeedFormat -from adcp.types.generated_poc.enums.feedback_source import FeedbackSource -from adcp.types.generated_poc.enums.forecast_method import ForecastMethod -from adcp.types.generated_poc.enums.forecast_range_unit import ForecastRangeUnit -from adcp.types.generated_poc.enums.forecastable_metric import ForecastableMetric -from adcp.types.generated_poc.enums.format_id_parameter import FormatIdParameter -from adcp.types.generated_poc.enums.frame_rate_type import FrameRateType -from adcp.types.generated_poc.enums.frequency_cap_control_mode import FrequencyCapControlMode -from adcp.types.generated_poc.enums.frequency_cap_mutable_field import FrequencyCapMutableField -from adcp.types.generated_poc.enums.frequency_cap_scope import FrequencyCapScope -from adcp.types.generated_poc.enums.genre_taxonomy import GenreTaxonomy -from adcp.types.generated_poc.enums.geo_level import GeographicTargetingLevel -from adcp.types.generated_poc.enums.gop_type import GopType -from adcp.types.generated_poc.enums.governance_decision import GovernanceDecision -from adcp.types.generated_poc.enums.governance_domain import GovernanceDomain -from adcp.types.generated_poc.enums.governance_mode import GovernanceMode -from adcp.types.generated_poc.enums.governance_phase import GovernancePhase -from adcp.types.generated_poc.enums.history_entry_type import HistoryEntryType -from adcp.types.generated_poc.enums.http_method import HttpMethod -from adcp.types.generated_poc.enums.identifier_types import PropertyIdentifierTypes -from adcp.types.generated_poc.enums.impairment_offline_state import ImpairmentOfflineState -from adcp.types.generated_poc.enums.impairment_reason_code import ImpairmentReasonCode -from adcp.types.generated_poc.enums.indicator_type import IndicatorType -from adcp.types.generated_poc.enums.installment_status import InstallmentStatus -from adcp.types.generated_poc.enums.javascript_module_type import JavascriptModuleType -from adcp.types.generated_poc.enums.landing_page_requirement import LandingPageRequirement -from adcp.types.generated_poc.enums.legacy_postal_system import CountryFusedPostalCodeSystem -from adcp.types.generated_poc.enums.lift_dimension import LiftDimension -from adcp.types.generated_poc.enums.logo_slot import LogoSlot -from adcp.types.generated_poc.enums.macro_dialect import MacroDialectFamily -from adcp.types.generated_poc.enums.macro_mapping_status import MacroMappingStatus -from adcp.types.generated_poc.enums.macro_processing_operation import MacroProcessingOperation -from adcp.types.generated_poc.enums.macro_resolution_reason import MacroResolutionReason -from adcp.types.generated_poc.enums.macro_resolver import MacroProcessingActor -from adcp.types.generated_poc.enums.macro_value_context import MacroValueContext -from adcp.types.generated_poc.enums.makegood_remedy import MakegoodRemedy -from adcp.types.generated_poc.enums.markdown_flavor import MarkdownFlavor -from adcp.types.generated_poc.enums.match_id_type import MatchIdType -from adcp.types.generated_poc.enums.match_type import MatchType -from adcp.types.generated_poc.enums.media_buy_action_mode import MediaBuyActionMode -from adcp.types.generated_poc.enums.media_buy_frequency_cap_control_mode import ( +from adcp.types.domains.enums.account_currency_mode import AccountCurrencyMode +from adcp.types.domains.enums.account_scope import AccountScope +from adcp.types.domains.enums.account_status import AccountStatus +from adcp.types.domains.enums.action_not_allowed_reason import ActionNotAllowedReason +from adcp.types.domains.enums.action_source import ActionSource +from adcp.types.domains.enums.activation_method import CreativeActivationMethod +from adcp.types.domains.enums.adcp_protocol import AdcpProtocol +from adcp.types.domains.enums.adjustment_kind import PriceAdjustmentKind +from adcp.types.domains.enums.advertiser_industry import AdvertiserIndustry +from adcp.types.domains.enums.age_determination_basis import AgeDeterminationBasis +from adcp.types.domains.enums.age_verification_method import AgeVerificationMethod +from adcp.types.domains.enums.assessment_status import AssessmentStatus +from adcp.types.domains.enums.asset_content_type import AssetContentType +from adcp.types.domains.enums.attestation_claim import AttestationClaim +from adcp.types.domains.enums.attribution_methodology import AttributionMethodology +from adcp.types.domains.enums.attribution_model import AttributionModel +from adcp.types.domains.enums.audience_evidence_methodology import AudienceEvidenceMethodology +from adcp.types.domains.enums.audience_resolution_method import AudienceResolutionMethod +from adcp.types.domains.enums.audience_source import AudienceSource +from adcp.types.domains.enums.audience_status import AudienceStatus +from adcp.types.domains.enums.audience_subject_type import AudienceSubjectType +from adcp.types.domains.enums.audio_channel_layout import AudioChannelLayout +from adcp.types.domains.enums.audio_distribution_type import AudioDistributionType +from adcp.types.domains.enums.auth_scheme import AuthenticationScheme +from adcp.types.domains.enums.availability_status import AvailabilityStatus +from adcp.types.domains.enums.available_metric import AvailableMetric +from adcp.types.domains.enums.billing_party import BillingParty +from adcp.types.domains.enums.binary_verdict import BinaryVerdict +from adcp.types.domains.enums.brand_agent_type import BrandAgentType +from adcp.types.domains.enums.browser_family import BrowserFamily +from adcp.types.domains.enums.c2pa_watermark_action import C2PaWatermarkAction +from adcp.types.domains.enums.canceled_by import CanceledBy +from adcp.types.domains.enums.canonical_media_buy_action import CanonicalMediaBuyActionName +from adcp.types.domains.enums.canonical_media_buy_action_mode import CanonicalMediaBuyActionMode +from adcp.types.domains.enums.catalog_action import CatalogAction +from adcp.types.domains.enums.catalog_item_status import CatalogItemStatus +from adcp.types.domains.enums.catalog_type import CatalogType +from adcp.types.domains.enums.channels import MediaChannel +from adcp.types.domains.enums.cloud_storage_protocol import CloudStorageProtocol +from adcp.types.domains.enums.co_branding_requirement import CoBrandingRequirement +from adcp.types.domains.enums.collection_cadence import CollectionCadence +from adcp.types.domains.enums.collection_kind import CollectionKind +from adcp.types.domains.enums.collection_relationship import CollectionRelationship +from adcp.types.domains.enums.collection_status import CollectionStatus +from adcp.types.domains.enums.completion_source import CompletionSource +from adcp.types.domains.enums.consent_basis import ConsentBasis +from adcp.types.domains.enums.content_id_type import ContentIdType +from adcp.types.domains.enums.content_rating_system import ContentRatingSystem +from adcp.types.domains.enums.creative_action import CreativeAction +from adcp.types.domains.enums.creative_agent_capability import CreativeAgentCapability +from adcp.types.domains.enums.creative_approval_status import CreativeApprovalStatus +from adcp.types.domains.enums.creative_event_reason_code import CreativeEventReasonCode +from adcp.types.domains.enums.creative_identifier_type import CreativeIdentifierType +from adcp.types.domains.enums.creative_quality import CreativeQuality +from adcp.types.domains.enums.creative_selection_strategy import CreativeSelectionStrategy +from adcp.types.domains.enums.creative_sort_field import CreativeSortField +from adcp.types.domains.enums.creative_status import CreativeStatus +from adcp.types.domains.enums.ctv_ad_experience import CtvAdExperience +from adcp.types.domains.enums.daast_tracking_event import DaastTrackingEvent +from adcp.types.domains.enums.daast_version import DaastVersion +from adcp.types.domains.enums.day_of_week import DayOfWeek +from adcp.types.domains.enums.daypart_timezone_mode import DaypartTimezoneMode +from adcp.types.domains.enums.delegation_authority import DelegationAuthority +from adcp.types.domains.enums.delivery_recipient_cloud import DeliveryRecipientCloud +from adcp.types.domains.enums.delivery_status import DeliveryStatus +from adcp.types.domains.enums.delivery_type import DeliveryType +from adcp.types.domains.enums.demographic_system import DemographicSystem +from adcp.types.domains.enums.derivative_type import DerivativeType +from adcp.types.domains.enums.device_platform import DevicePlatform +from adcp.types.domains.enums.device_type import DeviceType +from adcp.types.domains.enums.digital_source_type import DigitalSourceType +from adcp.types.domains.enums.dimension_unit import DimensionUnit +from adcp.types.domains.enums.disclosure_persistence import DisclosurePersistence +from adcp.types.domains.enums.disclosure_position import DisclosurePosition +from adcp.types.domains.enums.distance_unit import DistanceUnit +from adcp.types.domains.enums.distribution_identifier_type import DistributionIdentifierType +from adcp.types.domains.enums.dooh_motion_type import DoohMotionType +from adcp.types.domains.enums.embedded_provenance_method import EmbeddedProvenanceMethod +from adcp.types.domains.enums.error_code import ErrorCode +from adcp.types.domains.enums.error_scope import ErrorScope +from adcp.types.domains.enums.escalation_severity import EscalationSeverity +from adcp.types.domains.enums.event_type import EventType +from adcp.types.domains.enums.exclusivity import Exclusivity +from adcp.types.domains.enums.feature_check_status import FeatureCheckStatus +from adcp.types.domains.enums.feed_format import FeedFormat +from adcp.types.domains.enums.feedback_source import FeedbackSource +from adcp.types.domains.enums.forecast_method import ForecastMethod +from adcp.types.domains.enums.forecast_range_unit import ForecastRangeUnit +from adcp.types.domains.enums.forecastable_metric import ForecastableMetric +from adcp.types.domains.enums.format_id_parameter import FormatIdParameter +from adcp.types.domains.enums.frame_rate_type import FrameRateType +from adcp.types.domains.enums.frequency_cap_control_mode import FrequencyCapControlMode +from adcp.types.domains.enums.frequency_cap_mutable_field import FrequencyCapMutableField +from adcp.types.domains.enums.frequency_cap_scope import FrequencyCapScope +from adcp.types.domains.enums.genre_taxonomy import GenreTaxonomy +from adcp.types.domains.enums.geo_level import GeographicTargetingLevel +from adcp.types.domains.enums.gop_type import GopType +from adcp.types.domains.enums.governance_decision import GovernanceDecision +from adcp.types.domains.enums.governance_domain import GovernanceDomain +from adcp.types.domains.enums.governance_mode import GovernanceMode +from adcp.types.domains.enums.governance_phase import GovernancePhase +from adcp.types.domains.enums.history_entry_type import HistoryEntryType +from adcp.types.domains.enums.http_method import HttpMethod +from adcp.types.domains.enums.identifier_types import PropertyIdentifierTypes +from adcp.types.domains.enums.impairment_offline_state import ImpairmentOfflineState +from adcp.types.domains.enums.impairment_reason_code import ImpairmentReasonCode +from adcp.types.domains.enums.indicator_type import IndicatorType +from adcp.types.domains.enums.installment_status import InstallmentStatus +from adcp.types.domains.enums.javascript_module_type import JavascriptModuleType +from adcp.types.domains.enums.landing_page_requirement import LandingPageRequirement +from adcp.types.domains.enums.legacy_postal_system import CountryFusedPostalCodeSystem +from adcp.types.domains.enums.lift_dimension import LiftDimension +from adcp.types.domains.enums.logo_slot import LogoSlot +from adcp.types.domains.enums.macro_dialect import MacroDialectFamily +from adcp.types.domains.enums.macro_mapping_status import MacroMappingStatus +from adcp.types.domains.enums.macro_processing_operation import MacroProcessingOperation +from adcp.types.domains.enums.macro_resolution_reason import MacroResolutionReason +from adcp.types.domains.enums.macro_resolver import MacroProcessingActor +from adcp.types.domains.enums.macro_value_context import MacroValueContext +from adcp.types.domains.enums.makegood_remedy import MakegoodRemedy +from adcp.types.domains.enums.markdown_flavor import MarkdownFlavor +from adcp.types.domains.enums.match_id_type import MatchIdType +from adcp.types.domains.enums.match_type import MatchType +from adcp.types.domains.enums.media_buy_action_mode import MediaBuyActionMode +from adcp.types.domains.enums.media_buy_frequency_cap_control_mode import ( MediaBuyFrequencyCapControlMode, ) -from adcp.types.generated_poc.enums.media_buy_health import MediaBuyHealth -from adcp.types.generated_poc.enums.media_buy_status import MediaBuyStatus -from adcp.types.generated_poc.enums.media_buy_valid_action import MediaBuyValidAction -from adcp.types.generated_poc.enums.metric_scope import MetricScope -from adcp.types.generated_poc.enums.metric_type import MetricType -from adcp.types.generated_poc.enums.metro_system import MetroAreaSystem -from adcp.types.generated_poc.enums.moov_atom_position import MoovAtomPosition -from adcp.types.generated_poc.enums.motion_level import CreativeMotionLevel -from adcp.types.generated_poc.enums.notification_type import NotificationType -from adcp.types.generated_poc.enums.offering_availability_status import OfferingAvailabilityStatus -from adcp.types.generated_poc.enums.outcome_target_cost_strength import OutcomeTargetCostStrength -from adcp.types.generated_poc.enums.outcome_type import OutcomeType -from adcp.types.generated_poc.enums.pacing import Pacing -from adcp.types.generated_poc.enums.payment_terms import PaymentTerms -from adcp.types.generated_poc.enums.performance_baseline import PerformanceBaseline -from adcp.types.generated_poc.enums.performance_standard_metric import PerformanceStandardMetric -from adcp.types.generated_poc.enums.pixel_tracking_event import PixelTrackingEvent -from adcp.types.generated_poc.enums.policy_category import PolicyCategory -from adcp.types.generated_poc.enums.policy_enforcement import PolicyEnforcementLevel -from adcp.types.generated_poc.enums.postal_system import PostalCodeSystem -from adcp.types.generated_poc.enums.preview_output_format import PreviewOutputFormat -from adcp.types.generated_poc.enums.pricing_model import PricingModel -from adcp.types.generated_poc.enums.pricing_structure import PricingStructure -from adcp.types.generated_poc.enums.principal_kind import PrincipalKind -from adcp.types.generated_poc.enums.production_quality import ProductionQuality -from adcp.types.generated_poc.enums.property_type import PropertyType -from adcp.types.generated_poc.enums.proposal_decline_reason import ProposalDeclineReason -from adcp.types.generated_poc.enums.proposal_refinement_reason import ProposalRefinementReason -from adcp.types.generated_poc.enums.proposal_status import ProposalStatus -from adcp.types.generated_poc.enums.publisher_identifier_types import PublisherIdentifierTypes -from adcp.types.generated_poc.enums.purchase_type import PurchaseType -from adcp.types.generated_poc.enums.reach_aggregation import ReachAggregation -from adcp.types.generated_poc.enums.reach_unit import ReachUnit -from adcp.types.generated_poc.enums.reporting_destination_setup_state import ( +from adcp.types.domains.enums.media_buy_health import MediaBuyHealth +from adcp.types.domains.enums.media_buy_status import MediaBuyStatus +from adcp.types.domains.enums.media_buy_valid_action import MediaBuyValidAction +from adcp.types.domains.enums.metric_scope import MetricScope +from adcp.types.domains.enums.metric_type import MetricType +from adcp.types.domains.enums.metro_system import MetroAreaSystem +from adcp.types.domains.enums.moov_atom_position import MoovAtomPosition +from adcp.types.domains.enums.motion_level import CreativeMotionLevel +from adcp.types.domains.enums.notification_type import NotificationType +from adcp.types.domains.enums.offering_availability_status import OfferingAvailabilityStatus +from adcp.types.domains.enums.outcome_target_cost_strength import OutcomeTargetCostStrength +from adcp.types.domains.enums.outcome_type import OutcomeType +from adcp.types.domains.enums.pacing import Pacing +from adcp.types.domains.enums.payment_terms import PaymentTerms +from adcp.types.domains.enums.performance_baseline import PerformanceBaseline +from adcp.types.domains.enums.performance_standard_metric import PerformanceStandardMetric +from adcp.types.domains.enums.pixel_tracking_event import PixelTrackingEvent +from adcp.types.domains.enums.policy_category import PolicyCategory +from adcp.types.domains.enums.policy_enforcement import PolicyEnforcementLevel +from adcp.types.domains.enums.postal_system import PostalCodeSystem +from adcp.types.domains.enums.preview_output_format import PreviewOutputFormat +from adcp.types.domains.enums.pricing_model import PricingModel +from adcp.types.domains.enums.pricing_structure import PricingStructure +from adcp.types.domains.enums.principal_kind import PrincipalKind +from adcp.types.domains.enums.production_quality import ProductionQuality +from adcp.types.domains.enums.property_type import PropertyType +from adcp.types.domains.enums.proposal_decline_reason import ProposalDeclineReason +from adcp.types.domains.enums.proposal_refinement_reason import ProposalRefinementReason +from adcp.types.domains.enums.proposal_status import ProposalStatus +from adcp.types.domains.enums.publisher_identifier_types import PublisherIdentifierTypes +from adcp.types.domains.enums.purchase_type import PurchaseType +from adcp.types.domains.enums.reach_aggregation import ReachAggregation +from adcp.types.domains.enums.reach_unit import ReachUnit +from adcp.types.domains.enums.reporting_destination_setup_state import ( ReportingDestinationSetupState, ) -from adcp.types.generated_poc.enums.reporting_finality import ReportingFinality -from adcp.types.generated_poc.enums.reporting_frequency import ReportingFrequency -from adcp.types.generated_poc.enums.reporting_health import ReportingHealth -from adcp.types.generated_poc.enums.representation_selection_strategy import ( +from adcp.types.domains.enums.reporting_finality import ReportingFinality +from adcp.types.domains.enums.reporting_frequency import ReportingFrequency +from adcp.types.domains.enums.reporting_health import ReportingHealth +from adcp.types.domains.enums.representation_selection_strategy import ( RepresentationSelectionStrategy, ) -from adcp.types.generated_poc.enums.request_signing_error_code import RequestSigningErrorCode -from adcp.types.generated_poc.enums.response_type import TmpResponseType -from adcp.types.generated_poc.enums.restricted_attribute import RestrictedAttribute -from adcp.types.generated_poc.enums.right_type import RightType -from adcp.types.generated_poc.enums.right_use import RightUse -from adcp.types.generated_poc.enums.rights_billing_period import RightsBillingPeriod -from adcp.types.generated_poc.enums.scan_type import ScanType -from adcp.types.generated_poc.enums.seller_policy_decline_reason import SellerPolicyDeclineReason -from adcp.types.generated_poc.enums.si_session_status import SiSessionStatus -from adcp.types.generated_poc.enums.signal_catalog_type import ( - SignalAvailabilityType, - SignalCatalogType, -) -from adcp.types.generated_poc.enums.signal_source import SignalSource -from adcp.types.generated_poc.enums.signal_value_type import SignalValueType -from adcp.types.generated_poc.enums.snapshot_unavailable_reason import SnapshotUnavailableReason -from adcp.types.generated_poc.enums.social_placement_surface import SocialPlacementSurface -from adcp.types.generated_poc.enums.sort_direction import SortDirection -from adcp.types.generated_poc.enums.sort_metric import SortMetric -from adcp.types.generated_poc.enums.special_category import SpecialCategory -from adcp.types.generated_poc.enums.specialism import AdcpSpecialism -from adcp.types.generated_poc.enums.sponsored_placement_type import SponsoredPlacementType -from adcp.types.generated_poc.enums.talent_role import TalentRole -from adcp.types.generated_poc.enums.task_status import TaskStatus -from adcp.types.generated_poc.enums.task_type import TaskType -from adcp.types.generated_poc.enums.tracker_execution_actor import TrackerExecutionActor -from adcp.types.generated_poc.enums.tracker_firing_path import TrackerFiringPath -from adcp.types.generated_poc.enums.transport_mode import TransportMode -from adcp.types.generated_poc.enums.travel_time_unit import TravelTimeUnit -from adcp.types.generated_poc.enums.uid_type import UidType -from adcp.types.generated_poc.enums.universal_macro import UniversalMacro -from adcp.types.generated_poc.enums.update_frequency import UpdateFrequency -from adcp.types.generated_poc.enums.url_asset_type import UrlAssetType -from adcp.types.generated_poc.enums.validation_mode import ValidationMode -from adcp.types.generated_poc.enums.vast_media_delivery_method import VastMediaDeliveryMethod -from adcp.types.generated_poc.enums.vast_tracking_event import VastTrackingEvent -from adcp.types.generated_poc.enums.vast_version import VastVersion -from adcp.types.generated_poc.enums.vendor_relationship import VendorRelationship -from adcp.types.generated_poc.enums.video_placement_type import VideoPlacementType -from adcp.types.generated_poc.enums.view_threshold_basis import ViewThresholdBasis -from adcp.types.generated_poc.enums.viewability_standard import ViewabilityStandard -from adcp.types.generated_poc.enums.warning_code import WarningCode -from adcp.types.generated_poc.enums.watermark_media_type import WatermarkMediaType -from adcp.types.generated_poc.enums.wcag_level import WcagLevel -from adcp.types.generated_poc.enums.webhook_response_type import WebhookResponseType -from adcp.types.generated_poc.enums.webhook_security_method import WebhookSecurityMethod +from adcp.types.domains.enums.request_signing_error_code import RequestSigningErrorCode +from adcp.types.domains.enums.response_type import TmpResponseType +from adcp.types.domains.enums.restricted_attribute import RestrictedAttribute +from adcp.types.domains.enums.right_type import RightType +from adcp.types.domains.enums.right_use import RightUse +from adcp.types.domains.enums.rights_billing_period import RightsBillingPeriod +from adcp.types.domains.enums.scan_type import ScanType +from adcp.types.domains.enums.seller_policy_decline_reason import SellerPolicyDeclineReason +from adcp.types.domains.enums.si_session_status import SiSessionStatus +from adcp.types.domains.enums.signal_catalog_type import SignalAvailabilityType, SignalCatalogType +from adcp.types.domains.enums.signal_source import SignalSource +from adcp.types.domains.enums.signal_value_type import SignalValueType +from adcp.types.domains.enums.snapshot_unavailable_reason import SnapshotUnavailableReason +from adcp.types.domains.enums.social_placement_surface import SocialPlacementSurface +from adcp.types.domains.enums.sort_direction import SortDirection +from adcp.types.domains.enums.sort_metric import SortMetric +from adcp.types.domains.enums.special_category import SpecialCategory +from adcp.types.domains.enums.specialism import AdcpSpecialism +from adcp.types.domains.enums.sponsored_placement_type import SponsoredPlacementType +from adcp.types.domains.enums.talent_role import TalentRole +from adcp.types.domains.enums.task_status import TaskStatus +from adcp.types.domains.enums.task_type import TaskType +from adcp.types.domains.enums.tracker_execution_actor import TrackerExecutionActor +from adcp.types.domains.enums.tracker_firing_path import TrackerFiringPath +from adcp.types.domains.enums.transport_mode import TransportMode +from adcp.types.domains.enums.travel_time_unit import TravelTimeUnit +from adcp.types.domains.enums.uid_type import UidType +from adcp.types.domains.enums.universal_macro import UniversalMacro +from adcp.types.domains.enums.update_frequency import UpdateFrequency +from adcp.types.domains.enums.url_asset_type import UrlAssetType +from adcp.types.domains.enums.validation_mode import ValidationMode +from adcp.types.domains.enums.vast_media_delivery_method import VastMediaDeliveryMethod +from adcp.types.domains.enums.vast_tracking_event import VastTrackingEvent +from adcp.types.domains.enums.vast_version import VastVersion +from adcp.types.domains.enums.vendor_relationship import VendorRelationship +from adcp.types.domains.enums.video_placement_type import VideoPlacementType +from adcp.types.domains.enums.view_threshold_basis import ViewThresholdBasis +from adcp.types.domains.enums.viewability_standard import ViewabilityStandard +from adcp.types.domains.enums.warning_code import WarningCode +from adcp.types.domains.enums.watermark_media_type import WatermarkMediaType +from adcp.types.domains.enums.wcag_level import WcagLevel +from adcp.types.domains.enums.webhook_response_type import WebhookResponseType +from adcp.types.domains.enums.webhook_security_method import WebhookSecurityMethod # Explicit exports __all__ = [ diff --git a/src/adcp/types/domains/enums/account_currency_mode.py b/src/adcp/types/domains/enums/account_currency_mode.py index 7c353cae7..3898a54be 100644 --- a/src/adcp/types/domains/enums/account_currency_mode.py +++ b/src/adcp/types/domains/enums/account_currency_mode.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``enums/account_currency_mode`` schema. +# generated by datamodel-codegen: +# filename: enums/account_currency_mode.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.account_currency_mode import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.account_currency_mode import AccountCurrencyMode +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["AccountCurrencyMode"] +class AccountCurrencyMode(StrEnum): + fixed = 'fixed' + per_media_buy = 'per_media_buy' diff --git a/src/adcp/types/domains/enums/account_scope.py b/src/adcp/types/domains/enums/account_scope.py index e0fcaa578..b1afad2d1 100644 --- a/src/adcp/types/domains/enums/account_scope.py +++ b/src/adcp/types/domains/enums/account_scope.py @@ -1,18 +1,14 @@ -"""Types declared by the AdCP ``enums/account_scope`` schema. +# generated by datamodel-codegen: +# filename: enums/account_scope.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.account_scope import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.account_scope import AccountScope +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["AccountScope"] +class AccountScope(StrEnum): + operator = 'operator' + brand = 'brand' + operator_brand = 'operator_brand' + agent = 'agent' diff --git a/src/adcp/types/domains/enums/account_status.py b/src/adcp/types/domains/enums/account_status.py index 73e3b903a..0bfcb3958 100644 --- a/src/adcp/types/domains/enums/account_status.py +++ b/src/adcp/types/domains/enums/account_status.py @@ -1,18 +1,16 @@ -"""Types declared by the AdCP ``enums/account_status`` schema. +# generated by datamodel-codegen: +# filename: enums/account_status.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.account_status import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.account_status import AccountStatus +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["AccountStatus"] +class AccountStatus(StrEnum): + active = 'active' + pending_approval = 'pending_approval' + rejected = 'rejected' + payment_required = 'payment_required' + suspended = 'suspended' + closed = 'closed' diff --git a/src/adcp/types/domains/enums/action_not_allowed_reason.py b/src/adcp/types/domains/enums/action_not_allowed_reason.py index 08d857a10..5ed94529f 100644 --- a/src/adcp/types/domains/enums/action_not_allowed_reason.py +++ b/src/adcp/types/domains/enums/action_not_allowed_reason.py @@ -1,18 +1,15 @@ -"""Types declared by the AdCP ``enums/action_not_allowed_reason`` schema. +# generated by datamodel-codegen: +# filename: enums/action_not_allowed_reason.json +# timestamp: 2026-08-28T20:03:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.action_not_allowed_reason import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.action_not_allowed_reason import ActionNotAllowedReason +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["ActionNotAllowedReason"] +class ActionNotAllowedReason(StrEnum): + wrong_status = 'wrong_status' + not_supported_on_product = 'not_supported_on_product' + not_supported_on_buy = 'not_supported_on_buy' + mode_mismatch = 'mode_mismatch' + condition_unresolved = 'condition_unresolved' diff --git a/src/adcp/types/domains/enums/action_source.py b/src/adcp/types/domains/enums/action_source.py index ec302b44c..e5297e5ae 100644 --- a/src/adcp/types/domains/enums/action_source.py +++ b/src/adcp/types/domains/enums/action_source.py @@ -1,18 +1,19 @@ -"""Types declared by the AdCP ``enums/action_source`` schema. +# generated by datamodel-codegen: +# filename: enums/action_source.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.action_source import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.action_source import ActionSource +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["ActionSource"] +class ActionSource(StrEnum): + website = 'website' + app = 'app' + offline = 'offline' + phone_call = 'phone_call' + chat = 'chat' + email = 'email' + in_store = 'in_store' + system_generated = 'system_generated' + other = 'other' diff --git a/src/adcp/types/domains/enums/activation_method.py b/src/adcp/types/domains/enums/activation_method.py index 0c7833664..11c82d10c 100644 --- a/src/adcp/types/domains/enums/activation_method.py +++ b/src/adcp/types/domains/enums/activation_method.py @@ -1,18 +1,16 @@ -"""Types declared by the AdCP ``enums/activation_method`` schema. +# generated by datamodel-codegen: +# filename: enums/activation_method.json +# timestamp: 2026-08-24T04:35:32+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.activation_method import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.activation_method import CreativeActivationMethod +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["CreativeActivationMethod"] +class CreativeActivationMethod(StrEnum): + qr_code = 'qr_code' + deep_link = 'deep_link' + push_notification = 'push_notification' + email = 'email' + tune_in = 'tune_in' + text_message = 'text_message' diff --git a/src/adcp/types/domains/enums/adcp_protocol.py b/src/adcp/types/domains/enums/adcp_protocol.py index 2c5a2447e..fd0bb879b 100644 --- a/src/adcp/types/domains/enums/adcp_protocol.py +++ b/src/adcp/types/domains/enums/adcp_protocol.py @@ -1,18 +1,17 @@ -"""Types declared by the AdCP ``enums/adcp_protocol`` schema. +# generated by datamodel-codegen: +# filename: enums/adcp_protocol.json +# timestamp: 2026-05-22T13:15:12+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.adcp_protocol import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.adcp_protocol import AdcpProtocol +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["AdcpProtocol"] +class AdcpProtocol(StrEnum): + media_buy = 'media-buy' + signals = 'signals' + governance = 'governance' + creative = 'creative' + brand = 'brand' + sponsored_intelligence = 'sponsored-intelligence' + measurement = 'measurement' diff --git a/src/adcp/types/domains/enums/adjustment_kind.py b/src/adcp/types/domains/enums/adjustment_kind.py index 26351b7e9..16a766fe5 100644 --- a/src/adcp/types/domains/enums/adjustment_kind.py +++ b/src/adcp/types/domains/enums/adjustment_kind.py @@ -1,18 +1,14 @@ -"""Types declared by the AdCP ``enums/adjustment_kind`` schema. +# generated by datamodel-codegen: +# filename: enums/adjustment_kind.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.adjustment_kind import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.adjustment_kind import PriceAdjustmentKind +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["PriceAdjustmentKind"] +class PriceAdjustmentKind(StrEnum): + fee = 'fee' + discount = 'discount' + commission = 'commission' + settlement = 'settlement' diff --git a/src/adcp/types/domains/enums/advertiser_industry.py b/src/adcp/types/domains/enums/advertiser_industry.py index aa3629db7..3273ed0d3 100644 --- a/src/adcp/types/domains/enums/advertiser_industry.py +++ b/src/adcp/types/domains/enums/advertiser_industry.py @@ -1,18 +1,91 @@ -"""Types declared by the AdCP ``enums/advertiser_industry`` schema. +# generated by datamodel-codegen: +# filename: enums/advertiser_industry.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.advertiser_industry import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.advertiser_industry import AdvertiserIndustry +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["AdvertiserIndustry"] +class AdvertiserIndustry(StrEnum): + automotive = 'automotive' + automotive_electric_vehicles = 'automotive.electric_vehicles' + automotive_parts_accessories = 'automotive.parts_accessories' + automotive_luxury = 'automotive.luxury' + beauty_cosmetics = 'beauty_cosmetics' + beauty_cosmetics_skincare = 'beauty_cosmetics.skincare' + beauty_cosmetics_fragrance = 'beauty_cosmetics.fragrance' + beauty_cosmetics_haircare = 'beauty_cosmetics.haircare' + cannabis = 'cannabis' + cpg = 'cpg' + cpg_personal_care = 'cpg.personal_care' + cpg_household = 'cpg.household' + dating = 'dating' + education = 'education' + education_higher_education = 'education.higher_education' + education_online_learning = 'education.online_learning' + education_k12 = 'education.k12' + energy_utilities = 'energy_utilities' + energy_utilities_renewable = 'energy_utilities.renewable' + fashion_apparel = 'fashion_apparel' + fashion_apparel_luxury = 'fashion_apparel.luxury' + fashion_apparel_sportswear = 'fashion_apparel.sportswear' + finance = 'finance' + finance_banking = 'finance.banking' + finance_insurance = 'finance.insurance' + finance_investment = 'finance.investment' + finance_cryptocurrency = 'finance.cryptocurrency' + food_beverage = 'food_beverage' + food_beverage_alcohol = 'food_beverage.alcohol' + food_beverage_restaurants = 'food_beverage.restaurants' + food_beverage_packaged_goods = 'food_beverage.packaged_goods' + gambling_betting = 'gambling_betting' + gambling_betting_sports_betting = 'gambling_betting.sports_betting' + gambling_betting_casino = 'gambling_betting.casino' + gaming = 'gaming' + gaming_mobile = 'gaming.mobile' + gaming_console_pc = 'gaming.console_pc' + gaming_esports = 'gaming.esports' + government_nonprofit = 'government_nonprofit' + government_nonprofit_political = 'government_nonprofit.political' + government_nonprofit_charity = 'government_nonprofit.charity' + healthcare = 'healthcare' + healthcare_pharmaceutical = 'healthcare.pharmaceutical' + healthcare_medical_devices = 'healthcare.medical_devices' + healthcare_wellness = 'healthcare.wellness' + home_garden = 'home_garden' + home_garden_furniture = 'home_garden.furniture' + home_garden_home_improvement = 'home_garden.home_improvement' + media_entertainment = 'media_entertainment' + media_entertainment_podcasts = 'media_entertainment.podcasts' + media_entertainment_music = 'media_entertainment.music' + media_entertainment_film_tv = 'media_entertainment.film_tv' + media_entertainment_publishing = 'media_entertainment.publishing' + media_entertainment_live_events = 'media_entertainment.live_events' + pets = 'pets' + professional_services = 'professional_services' + professional_services_legal = 'professional_services.legal' + professional_services_consulting = 'professional_services.consulting' + real_estate = 'real_estate' + real_estate_residential = 'real_estate.residential' + real_estate_commercial = 'real_estate.commercial' + recruitment_hr = 'recruitment_hr' + retail = 'retail' + retail_ecommerce = 'retail.ecommerce' + retail_department_stores = 'retail.department_stores' + sports_fitness = 'sports_fitness' + sports_fitness_equipment = 'sports_fitness.equipment' + sports_fitness_teams_leagues = 'sports_fitness.teams_leagues' + technology = 'technology' + technology_software = 'technology.software' + technology_hardware = 'technology.hardware' + technology_ai_ml = 'technology.ai_ml' + telecom = 'telecom' + telecom_mobile_carriers = 'telecom.mobile_carriers' + telecom_internet_providers = 'telecom.internet_providers' + transportation_logistics = 'transportation_logistics' + travel_hospitality = 'travel_hospitality' + travel_hospitality_airlines = 'travel_hospitality.airlines' + travel_hospitality_hotels = 'travel_hospitality.hotels' + travel_hospitality_cruise = 'travel_hospitality.cruise' + travel_hospitality_tourism = 'travel_hospitality.tourism' diff --git a/src/adcp/types/domains/enums/age_determination_basis.py b/src/adcp/types/domains/enums/age_determination_basis.py index e20f70ea7..901598216 100644 --- a/src/adcp/types/domains/enums/age_determination_basis.py +++ b/src/adcp/types/domains/enums/age_determination_basis.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/age_determination_basis`` schema. +# generated by datamodel-codegen: +# filename: enums/age_determination_basis.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.age_determination_basis import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.age_determination_basis import AgeDeterminationBasis +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["AgeDeterminationBasis"] +class AgeDeterminationBasis(StrEnum): + verified = 'verified' + declared = 'declared' + inferred = 'inferred' diff --git a/src/adcp/types/domains/enums/age_verification_method.py b/src/adcp/types/domains/enums/age_verification_method.py index 3590ddd03..2f76bb152 100644 --- a/src/adcp/types/domains/enums/age_verification_method.py +++ b/src/adcp/types/domains/enums/age_verification_method.py @@ -1,18 +1,15 @@ -"""Types declared by the AdCP ``enums/age_verification_method`` schema. +# generated by datamodel-codegen: +# filename: enums/age_verification_method.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.age_verification_method import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.age_verification_method import AgeVerificationMethod +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["AgeVerificationMethod"] +class AgeVerificationMethod(StrEnum): + facial_age_estimation = 'facial_age_estimation' + id_document = 'id_document' + digital_id = 'digital_id' + credit_card = 'credit_card' + world_id = 'world_id' diff --git a/src/adcp/types/domains/enums/assessment_status.py b/src/adcp/types/domains/enums/assessment_status.py index e5d0aa14b..e7af61416 100644 --- a/src/adcp/types/domains/enums/assessment_status.py +++ b/src/adcp/types/domains/enums/assessment_status.py @@ -1,18 +1,14 @@ -"""Types declared by the AdCP ``enums/assessment_status`` schema. +# generated by datamodel-codegen: +# filename: enums/assessment_status.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.assessment_status import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.assessment_status import AssessmentStatus +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["AssessmentStatus"] +class AssessmentStatus(StrEnum): + insufficient = 'insufficient' + minimum = 'minimum' + good = 'good' + excellent = 'excellent' diff --git a/src/adcp/types/domains/enums/asset_content_type.py b/src/adcp/types/domains/enums/asset_content_type.py index daaf4d8b2..401a0706c 100644 --- a/src/adcp/types/domains/enums/asset_content_type.py +++ b/src/adcp/types/domains/enums/asset_content_type.py @@ -1,18 +1,26 @@ -"""Types declared by the AdCP ``enums/asset_content_type`` schema. +# generated by datamodel-codegen: +# filename: enums/asset_content_type.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.asset_content_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.asset_content_type import AssetContentType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["AssetContentType"] +class AssetContentType(StrEnum): + image = 'image' + video = 'video' + audio = 'audio' + text = 'text' + markdown = 'markdown' + html = 'html' + css = 'css' + javascript = 'javascript' + zip = 'zip' + vast = 'vast' + daast = 'daast' + url = 'url' + webhook = 'webhook' + brief = 'brief' + catalog = 'catalog' + published_post = 'published_post' diff --git a/src/adcp/types/domains/enums/attestation_claim.py b/src/adcp/types/domains/enums/attestation_claim.py index 67d2b88fb..bb8a183c9 100644 --- a/src/adcp/types/domains/enums/attestation_claim.py +++ b/src/adcp/types/domains/enums/attestation_claim.py @@ -1,18 +1,15 @@ -"""Types declared by the AdCP ``enums/attestation_claim`` schema. +# generated by datamodel-codegen: +# filename: enums/attestation_claim.json +# timestamp: 2026-06-12T11:05:47+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.attestation_claim import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.attestation_claim import AttestationClaim +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["AttestationClaim"] +class AttestationClaim(StrEnum): + unique_human = 'unique_human' + age_over_13 = 'age_over_13' + age_over_16 = 'age_over_16' + age_over_18 = 'age_over_18' + age_over_21 = 'age_over_21' diff --git a/src/adcp/types/domains/enums/attribution_methodology.py b/src/adcp/types/domains/enums/attribution_methodology.py index 47c76d897..345dac1fd 100644 --- a/src/adcp/types/domains/enums/attribution_methodology.py +++ b/src/adcp/types/domains/enums/attribution_methodology.py @@ -1,18 +1,14 @@ -"""Types declared by the AdCP ``enums/attribution_methodology`` schema. +# generated by datamodel-codegen: +# filename: enums/attribution_methodology.json +# timestamp: 2026-05-22T13:15:12+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.attribution_methodology import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.attribution_methodology import AttributionMethodology +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["AttributionMethodology"] +class AttributionMethodology(StrEnum): + deterministic_purchase = 'deterministic_purchase' + probabilistic = 'probabilistic' + panel_based = 'panel_based' + modeled = 'modeled' diff --git a/src/adcp/types/domains/enums/attribution_model.py b/src/adcp/types/domains/enums/attribution_model.py index 04a4462e3..02b0940a9 100644 --- a/src/adcp/types/domains/enums/attribution_model.py +++ b/src/adcp/types/domains/enums/attribution_model.py @@ -1,18 +1,15 @@ -"""Types declared by the AdCP ``enums/attribution_model`` schema. +# generated by datamodel-codegen: +# filename: enums/attribution_model.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.attribution_model import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.attribution_model import AttributionModel +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["AttributionModel"] +class AttributionModel(StrEnum): + last_touch = 'last_touch' + first_touch = 'first_touch' + linear = 'linear' + time_decay = 'time_decay' + data_driven = 'data_driven' diff --git a/src/adcp/types/domains/enums/audience_evidence_methodology.py b/src/adcp/types/domains/enums/audience_evidence_methodology.py index 030e11f4b..d266dd758 100644 --- a/src/adcp/types/domains/enums/audience_evidence_methodology.py +++ b/src/adcp/types/domains/enums/audience_evidence_methodology.py @@ -1,18 +1,16 @@ -"""Types declared by the AdCP ``enums/audience_evidence_methodology`` schema. +# generated by datamodel-codegen: +# filename: enums/audience_evidence_methodology.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.audience_evidence_methodology import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.audience_evidence_methodology import AudienceEvidenceMethodology +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["AudienceEvidenceMethodology"] +class AudienceEvidenceMethodology(StrEnum): + observed = 'observed' + declared = 'declared' + derived = 'derived' + inferred = 'inferred' + modeled = 'modeled' + projected = 'projected' diff --git a/src/adcp/types/domains/enums/audience_resolution_method.py b/src/adcp/types/domains/enums/audience_resolution_method.py index f8a7743fd..c56c31c19 100644 --- a/src/adcp/types/domains/enums/audience_resolution_method.py +++ b/src/adcp/types/domains/enums/audience_resolution_method.py @@ -1,18 +1,16 @@ -"""Types declared by the AdCP ``enums/audience_resolution_method`` schema. +# generated by datamodel-codegen: +# filename: enums/audience_resolution_method.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.audience_resolution_method import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.audience_resolution_method import AudienceResolutionMethod +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["AudienceResolutionMethod"] +class AudienceResolutionMethod(StrEnum): + deterministic_id = 'deterministic_id' + probabilistic_device = 'probabilistic_device' + browser = 'browser' + geographic = 'geographic' + content_signal = 'content_signal' + mixed = 'mixed' diff --git a/src/adcp/types/domains/enums/audience_source.py b/src/adcp/types/domains/enums/audience_source.py index ab3f809e6..b0b04f9ce 100644 --- a/src/adcp/types/domains/enums/audience_source.py +++ b/src/adcp/types/domains/enums/audience_source.py @@ -1,18 +1,16 @@ -"""Types declared by the AdCP ``enums/audience_source`` schema. +# generated by datamodel-codegen: +# filename: enums/audience_source.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.audience_source import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.audience_source import AudienceSource +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["AudienceSource"] +class AudienceSource(StrEnum): + synced = 'synced' + platform = 'platform' + third_party = 'third_party' + lookalike = 'lookalike' + retargeting = 'retargeting' + unknown = 'unknown' diff --git a/src/adcp/types/domains/enums/audience_status.py b/src/adcp/types/domains/enums/audience_status.py index bc6e021e6..3694385a6 100644 --- a/src/adcp/types/domains/enums/audience_status.py +++ b/src/adcp/types/domains/enums/audience_status.py @@ -1,18 +1,14 @@ -"""Types declared by the AdCP ``enums/audience_status`` schema. +# generated by datamodel-codegen: +# filename: enums/audience_status.json +# timestamp: 2026-05-22T13:15:12+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.audience_status import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.audience_status import AudienceStatus +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["AudienceStatus"] +class AudienceStatus(StrEnum): + processing = 'processing' + ready = 'ready' + too_small = 'too_small' + suspended = 'suspended' diff --git a/src/adcp/types/domains/enums/audience_subject_type.py b/src/adcp/types/domains/enums/audience_subject_type.py index a00ba185d..f8bc03e6e 100644 --- a/src/adcp/types/domains/enums/audience_subject_type.py +++ b/src/adcp/types/domains/enums/audience_subject_type.py @@ -1,18 +1,15 @@ -"""Types declared by the AdCP ``enums/audience_subject_type`` schema. +# generated by datamodel-codegen: +# filename: enums/audience_subject_type.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.audience_subject_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.audience_subject_type import AudienceSubjectType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["AudienceSubjectType"] +class AudienceSubjectType(StrEnum): + individual = 'individual' + household = 'household' + business = 'business' + contextual = 'contextual' + none = 'none' diff --git a/src/adcp/types/domains/enums/audio_channel_layout.py b/src/adcp/types/domains/enums/audio_channel_layout.py index 61c059bdc..39048830c 100644 --- a/src/adcp/types/domains/enums/audio_channel_layout.py +++ b/src/adcp/types/domains/enums/audio_channel_layout.py @@ -1,18 +1,14 @@ -"""Types declared by the AdCP ``enums/audio_channel_layout`` schema. +# generated by datamodel-codegen: +# filename: enums/audio_channel_layout.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.audio_channel_layout import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.audio_channel_layout import AudioChannelLayout +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["AudioChannelLayout"] +class AudioChannelLayout(StrEnum): + mono = 'mono' + stereo = 'stereo' + field_5_1 = '5.1' + field_7_1 = '7.1' diff --git a/src/adcp/types/domains/enums/audio_distribution_type.py b/src/adcp/types/domains/enums/audio_distribution_type.py index bd9155566..aa3a02490 100644 --- a/src/adcp/types/domains/enums/audio_distribution_type.py +++ b/src/adcp/types/domains/enums/audio_distribution_type.py @@ -1,18 +1,17 @@ -"""Types declared by the AdCP ``enums/audio_distribution_type`` schema. +# generated by datamodel-codegen: +# filename: enums/audio_distribution_type.json +# timestamp: 2026-06-12T11:05:47+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.audio_distribution_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.audio_distribution_type import AudioDistributionType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["AudioDistributionType"] +class AudioDistributionType(StrEnum): + music_streaming_service = 'music_streaming_service' + fm_am_broadcast = 'fm_am_broadcast' + podcast = 'podcast' + catch_up_radio = 'catch_up_radio' + web_radio = 'web_radio' + video_game = 'video_game' + text_to_speech = 'text_to_speech' diff --git a/src/adcp/types/domains/enums/auth_scheme.py b/src/adcp/types/domains/enums/auth_scheme.py index ac0beead5..fff14236c 100644 --- a/src/adcp/types/domains/enums/auth_scheme.py +++ b/src/adcp/types/domains/enums/auth_scheme.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``enums/auth_scheme`` schema. +# generated by datamodel-codegen: +# filename: enums/auth_scheme.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.auth_scheme import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.auth_scheme import AuthenticationScheme +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["AuthenticationScheme"] +class AuthenticationScheme(StrEnum): + Bearer = 'Bearer' + HMAC_SHA256 = 'HMAC-SHA256' diff --git a/src/adcp/types/domains/enums/availability_status.py b/src/adcp/types/domains/enums/availability_status.py index 430f7615a..8bcf698a6 100644 --- a/src/adcp/types/domains/enums/availability_status.py +++ b/src/adcp/types/domains/enums/availability_status.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``enums/availability_status`` schema. +# generated by datamodel-codegen: +# filename: enums/availability_status.json +# timestamp: 2026-08-21T04:35:18+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.availability_status import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.availability_status import AvailabilityStatus +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["AvailabilityStatus"] +class AvailabilityStatus(StrEnum): + available = 'available' + unavailable = 'unavailable' diff --git a/src/adcp/types/domains/enums/available_metric.py b/src/adcp/types/domains/enums/available_metric.py index d2a16a59a..ceba1cdd3 100644 --- a/src/adcp/types/domains/enums/available_metric.py +++ b/src/adcp/types/domains/enums/available_metric.py @@ -1,18 +1,59 @@ -"""Types declared by the AdCP ``enums/available_metric`` schema. +# generated by datamodel-codegen: +# filename: enums/available_metric.json +# timestamp: 2026-08-28T20:03:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.available_metric import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.available_metric import AvailableMetric +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["AvailableMetric"] +class AvailableMetric(StrEnum): + impressions = 'impressions' + spend = 'spend' + clicks = 'clicks' + ctr = 'ctr' + views = 'views' + completed_views = 'completed_views' + completion_rate = 'completion_rate' + conversions = 'conversions' + conversion_value = 'conversion_value' + commissionable_value = 'commissionable_value' + roas = 'roas' + cost_per_acquisition = 'cost_per_acquisition' + new_to_brand_rate = 'new_to_brand_rate' + leads = 'leads' + reach = 'reach' + frequency = 'frequency' + grps = 'grps' + engagements = 'engagements' + engagement_rate = 'engagement_rate' + follows = 'follows' + saves = 'saves' + profile_visits = 'profile_visits' + viewability = 'viewability' + viewable_rate = 'viewable_rate' + viewable_impressions = 'viewable_impressions' + measurable_impressions = 'measurable_impressions' + viewed_seconds = 'viewed_seconds' + viewed_seconds_percentiles = 'viewed_seconds_percentiles' + viewed_seconds_histogram = 'viewed_seconds_histogram' + quartile_data = 'quartile_data' + quartile_25 = 'quartile_25' + quartile_50 = 'quartile_50' + quartile_75 = 'quartile_75' + quartile_100 = 'quartile_100' + time_based_views = 'time_based_views' + dooh_metrics = 'dooh_metrics' + ooh_metrics = 'ooh_metrics' + cost_per_click = 'cost_per_click' + cost_per_completed_view = 'cost_per_completed_view' + cpm = 'cpm' + downloads = 'downloads' + units_sold = 'units_sold' + new_to_brand_units = 'new_to_brand_units' + plays = 'plays' + incremental_sales_lift = 'incremental_sales_lift' + brand_lift = 'brand_lift' + foot_traffic = 'foot_traffic' + conversion_lift = 'conversion_lift' + brand_search_lift = 'brand_search_lift' diff --git a/src/adcp/types/domains/enums/billing_party.py b/src/adcp/types/domains/enums/billing_party.py index 06ec2ebfc..9a7e4faca 100644 --- a/src/adcp/types/domains/enums/billing_party.py +++ b/src/adcp/types/domains/enums/billing_party.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/billing_party`` schema. +# generated by datamodel-codegen: +# filename: enums/billing_party.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.billing_party import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.billing_party import BillingParty +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["BillingParty"] +class BillingParty(StrEnum): + operator = 'operator' + agent = 'agent' + advertiser = 'advertiser' diff --git a/src/adcp/types/domains/enums/binary_verdict.py b/src/adcp/types/domains/enums/binary_verdict.py index 9759f3de7..29b19dfed 100644 --- a/src/adcp/types/domains/enums/binary_verdict.py +++ b/src/adcp/types/domains/enums/binary_verdict.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``enums/binary_verdict`` schema. +# generated by datamodel-codegen: +# filename: enums/binary_verdict.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.binary_verdict import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.binary_verdict import BinaryVerdict +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["BinaryVerdict"] +class BinaryVerdict(StrEnum): + pass_ = 'pass' + fail = 'fail' diff --git a/src/adcp/types/domains/enums/brand_agent_type.py b/src/adcp/types/domains/enums/brand_agent_type.py index a9a7782db..3a5194d47 100644 --- a/src/adcp/types/domains/enums/brand_agent_type.py +++ b/src/adcp/types/domains/enums/brand_agent_type.py @@ -1,18 +1,18 @@ -"""Types declared by the AdCP ``enums/brand_agent_type`` schema. +# generated by datamodel-codegen: +# filename: enums/brand_agent_type.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.brand_agent_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.brand_agent_type import BrandAgentType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["BrandAgentType"] +class BrandAgentType(StrEnum): + brand = 'brand' + rights = 'rights' + measurement = 'measurement' + governance = 'governance' + creative = 'creative' + sales = 'sales' + buying = 'buying' + signals = 'signals' diff --git a/src/adcp/types/domains/enums/browser_family.py b/src/adcp/types/domains/enums/browser_family.py index 23000a2ad..9ccf55b28 100644 --- a/src/adcp/types/domains/enums/browser_family.py +++ b/src/adcp/types/domains/enums/browser_family.py @@ -1,18 +1,19 @@ -"""Types declared by the AdCP ``enums/browser_family`` schema. +# generated by datamodel-codegen: +# filename: enums/browser_family.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.browser_family import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.browser_family import BrowserFamily +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["BrowserFamily"] +class BrowserFamily(StrEnum): + chrome = 'chrome' + safari = 'safari' + firefox = 'firefox' + edge = 'edge' + opera = 'opera' + samsung_internet = 'samsung_internet' + android_webview = 'android_webview' + other = 'other' + unknown = 'unknown' diff --git a/src/adcp/types/domains/enums/c2pa_watermark_action.py b/src/adcp/types/domains/enums/c2pa_watermark_action.py index 8c8c26bbf..604064394 100644 --- a/src/adcp/types/domains/enums/c2pa_watermark_action.py +++ b/src/adcp/types/domains/enums/c2pa_watermark_action.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``enums/c2pa_watermark_action`` schema. +# generated by datamodel-codegen: +# filename: enums/c2pa_watermark_action.json +# timestamp: 2026-05-22T13:15:12+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.c2pa_watermark_action import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.c2pa_watermark_action import C2PaWatermarkAction +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["C2PaWatermarkAction"] +class C2PaWatermarkAction(StrEnum): + c2pa_watermarked_bound = 'c2pa.watermarked.bound' + c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' diff --git a/src/adcp/types/domains/enums/canceled_by.py b/src/adcp/types/domains/enums/canceled_by.py index 8e3cb87e4..fa1d979c9 100644 --- a/src/adcp/types/domains/enums/canceled_by.py +++ b/src/adcp/types/domains/enums/canceled_by.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``enums/canceled_by`` schema. +# generated by datamodel-codegen: +# filename: enums/canceled_by.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.canceled_by import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.canceled_by import CanceledBy +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["CanceledBy"] +class CanceledBy(StrEnum): + buyer = 'buyer' + seller = 'seller' diff --git a/src/adcp/types/domains/enums/canonical_media_buy_action.py b/src/adcp/types/domains/enums/canonical_media_buy_action.py index 2c4fce881..a492b33b0 100644 --- a/src/adcp/types/domains/enums/canonical_media_buy_action.py +++ b/src/adcp/types/domains/enums/canonical_media_buy_action.py @@ -1,18 +1,36 @@ -"""Types declared by the AdCP ``enums/canonical_media_buy_action`` schema. +# generated by datamodel-codegen: +# filename: enums/canonical_media_buy_action.json +# timestamp: 2026-09-14T14:16:02+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.canonical_media_buy_action import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.canonical_media_buy_action import CanonicalMediaBuyActionName +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["CanonicalMediaBuyActionName"] +class CanonicalMediaBuyActionName(StrEnum): + pause = 'pause' + resume = 'resume' + cancel = 'cancel' + extend_flight = 'extend_flight' + shorten_flight = 'shorten_flight' + update_flight_dates = 'update_flight_dates' + increase_budget = 'increase_budget' + decrease_budget = 'decrease_budget' + reallocate_budget = 'reallocate_budget' + update_budget_allocation = 'update_budget_allocation' + update_targeting = 'update_targeting' + update_pacing = 'update_pacing' + update_bidding = 'update_bidding' + update_frequency_caps = 'update_frequency_caps' + update_media_buy_frequency_cap = 'update_media_buy_frequency_cap' + update_catalog_assignments = 'update_catalog_assignments' + update_keywords = 'update_keywords' + update_optimization_goals = 'update_optimization_goals' + update_impression_goal = 'update_impression_goal' + update_spend_target = 'update_spend_target' + update_reporting_webhook = 'update_reporting_webhook' + replace_creative = 'replace_creative' + update_creative_assignments = 'update_creative_assignments' + remove_creative = 'remove_creative' + add_packages = 'add_packages' + remove_packages = 'remove_packages' diff --git a/src/adcp/types/domains/enums/canonical_media_buy_action_mode.py b/src/adcp/types/domains/enums/canonical_media_buy_action_mode.py index 2806b1727..0a216203d 100644 --- a/src/adcp/types/domains/enums/canonical_media_buy_action_mode.py +++ b/src/adcp/types/domains/enums/canonical_media_buy_action_mode.py @@ -1,20 +1,14 @@ -"""Types declared by the AdCP ``enums/canonical_media_buy_action_mode`` schema. +# generated by datamodel-codegen: +# filename: enums/canonical_media_buy_action_mode.json +# timestamp: 2026-08-28T20:03:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.canonical_media_buy_action_mode import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.canonical_media_buy_action_mode import ( - CanonicalMediaBuyActionMode, -) +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["CanonicalMediaBuyActionMode"] +class CanonicalMediaBuyActionMode(StrEnum): + self_serve = 'self_serve' + conditional_self_serve = 'conditional_self_serve' + seller_managed = 'seller_managed' + requires_approval = 'requires_approval' diff --git a/src/adcp/types/domains/enums/catalog_action.py b/src/adcp/types/domains/enums/catalog_action.py index 112a6ea79..3114de50f 100644 --- a/src/adcp/types/domains/enums/catalog_action.py +++ b/src/adcp/types/domains/enums/catalog_action.py @@ -1,18 +1,15 @@ -"""Types declared by the AdCP ``enums/catalog_action`` schema. +# generated by datamodel-codegen: +# filename: enums/catalog_action.json +# timestamp: 2026-05-22T13:15:12+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.catalog_action import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.catalog_action import CatalogAction +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["CatalogAction"] +class CatalogAction(StrEnum): + created = 'created' + updated = 'updated' + unchanged = 'unchanged' + failed = 'failed' + deleted = 'deleted' diff --git a/src/adcp/types/domains/enums/catalog_item_status.py b/src/adcp/types/domains/enums/catalog_item_status.py index a704dff1a..ddfd17b96 100644 --- a/src/adcp/types/domains/enums/catalog_item_status.py +++ b/src/adcp/types/domains/enums/catalog_item_status.py @@ -1,18 +1,15 @@ -"""Types declared by the AdCP ``enums/catalog_item_status`` schema. +# generated by datamodel-codegen: +# filename: enums/catalog_item_status.json +# timestamp: 2026-05-22T13:15:12+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.catalog_item_status import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.catalog_item_status import CatalogItemStatus +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["CatalogItemStatus"] +class CatalogItemStatus(StrEnum): + approved = 'approved' + pending = 'pending' + rejected = 'rejected' + warning = 'warning' + withdrawn = 'withdrawn' diff --git a/src/adcp/types/domains/enums/catalog_type.py b/src/adcp/types/domains/enums/catalog_type.py index 51e9dc58e..6ffea75bb 100644 --- a/src/adcp/types/domains/enums/catalog_type.py +++ b/src/adcp/types/domains/enums/catalog_type.py @@ -1,18 +1,23 @@ -"""Types declared by the AdCP ``enums/catalog_type`` schema. +# generated by datamodel-codegen: +# filename: enums/catalog_type.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.catalog_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.catalog_type import CatalogType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["CatalogType"] +class CatalogType(StrEnum): + offering = 'offering' + product = 'product' + inventory = 'inventory' + store = 'store' + promotion = 'promotion' + hotel = 'hotel' + flight = 'flight' + job = 'job' + vehicle = 'vehicle' + real_estate = 'real_estate' + education = 'education' + destination = 'destination' + app = 'app' diff --git a/src/adcp/types/domains/enums/channels.py b/src/adcp/types/domains/enums/channels.py index ff1e469a7..16da34188 100644 --- a/src/adcp/types/domains/enums/channels.py +++ b/src/adcp/types/domains/enums/channels.py @@ -1,18 +1,30 @@ -"""Types declared by the AdCP ``enums/channels`` schema. +# generated by datamodel-codegen: +# filename: enums/channels.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.channels import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.channels import MediaChannel +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["MediaChannel"] +class MediaChannel(StrEnum): + display = 'display' + olv = 'olv' + social = 'social' + search = 'search' + ctv = 'ctv' + linear_tv = 'linear_tv' + radio = 'radio' + streaming_audio = 'streaming_audio' + podcast = 'podcast' + dooh = 'dooh' + ooh = 'ooh' + print = 'print' + cinema = 'cinema' + email = 'email' + gaming = 'gaming' + retail_media = 'retail_media' + influencer = 'influencer' + affiliate = 'affiliate' + product_placement = 'product_placement' + sponsored_intelligence = 'sponsored_intelligence' diff --git a/src/adcp/types/domains/enums/cloud_storage_protocol.py b/src/adcp/types/domains/enums/cloud_storage_protocol.py index 1fdf20b12..7d1041594 100644 --- a/src/adcp/types/domains/enums/cloud_storage_protocol.py +++ b/src/adcp/types/domains/enums/cloud_storage_protocol.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/cloud_storage_protocol`` schema. +# generated by datamodel-codegen: +# filename: enums/cloud_storage_protocol.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.cloud_storage_protocol import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.cloud_storage_protocol import CloudStorageProtocol +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["CloudStorageProtocol"] +class CloudStorageProtocol(StrEnum): + s3 = 's3' + gcs = 'gcs' + azure_blob = 'azure_blob' diff --git a/src/adcp/types/domains/enums/co_branding_requirement.py b/src/adcp/types/domains/enums/co_branding_requirement.py index 3492a1867..0f4353626 100644 --- a/src/adcp/types/domains/enums/co_branding_requirement.py +++ b/src/adcp/types/domains/enums/co_branding_requirement.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/co_branding_requirement`` schema. +# generated by datamodel-codegen: +# filename: enums/co_branding_requirement.json +# timestamp: 2026-06-05T16:54:33+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.co_branding_requirement import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.co_branding_requirement import CoBrandingRequirement +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["CoBrandingRequirement"] +class CoBrandingRequirement(StrEnum): + required = 'required' + optional = 'optional' + none = 'none' diff --git a/src/adcp/types/domains/enums/collection_cadence.py b/src/adcp/types/domains/enums/collection_cadence.py index f1a9f1a6e..73ee3a19c 100644 --- a/src/adcp/types/domains/enums/collection_cadence.py +++ b/src/adcp/types/domains/enums/collection_cadence.py @@ -1,18 +1,16 @@ -"""Types declared by the AdCP ``enums/collection_cadence`` schema. +# generated by datamodel-codegen: +# filename: enums/collection_cadence.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.collection_cadence import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.collection_cadence import CollectionCadence +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["CollectionCadence"] +class CollectionCadence(StrEnum): + daily = 'daily' + weekly = 'weekly' + monthly = 'monthly' + seasonal = 'seasonal' + event = 'event' + irregular = 'irregular' diff --git a/src/adcp/types/domains/enums/collection_kind.py b/src/adcp/types/domains/enums/collection_kind.py index 60db1bb21..08cb77701 100644 --- a/src/adcp/types/domains/enums/collection_kind.py +++ b/src/adcp/types/domains/enums/collection_kind.py @@ -1,18 +1,15 @@ -"""Types declared by the AdCP ``enums/collection_kind`` schema. +# generated by datamodel-codegen: +# filename: enums/collection_kind.json +# timestamp: 2026-09-01T19:37:00+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.collection_kind import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.collection_kind import CollectionKind +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["CollectionKind"] +class CollectionKind(StrEnum): + series = 'series' + publication = 'publication' + event_series = 'event_series' + rotation = 'rotation' + channel = 'channel' diff --git a/src/adcp/types/domains/enums/collection_relationship.py b/src/adcp/types/domains/enums/collection_relationship.py index e2a36ba9b..051942b52 100644 --- a/src/adcp/types/domains/enums/collection_relationship.py +++ b/src/adcp/types/domains/enums/collection_relationship.py @@ -1,18 +1,15 @@ -"""Types declared by the AdCP ``enums/collection_relationship`` schema. +# generated by datamodel-codegen: +# filename: enums/collection_relationship.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.collection_relationship import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.collection_relationship import CollectionRelationship +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["CollectionRelationship"] +class CollectionRelationship(StrEnum): + spinoff = 'spinoff' + companion = 'companion' + sequel = 'sequel' + prequel = 'prequel' + crossover = 'crossover' diff --git a/src/adcp/types/domains/enums/collection_status.py b/src/adcp/types/domains/enums/collection_status.py index 6728bbade..70e6faa09 100644 --- a/src/adcp/types/domains/enums/collection_status.py +++ b/src/adcp/types/domains/enums/collection_status.py @@ -1,18 +1,14 @@ -"""Types declared by the AdCP ``enums/collection_status`` schema. +# generated by datamodel-codegen: +# filename: enums/collection_status.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.collection_status import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.collection_status import CollectionStatus +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["CollectionStatus"] +class CollectionStatus(StrEnum): + active = 'active' + hiatus = 'hiatus' + ended = 'ended' + upcoming = 'upcoming' diff --git a/src/adcp/types/domains/enums/completion_source.py b/src/adcp/types/domains/enums/completion_source.py index 295711cdf..73a4e9342 100644 --- a/src/adcp/types/domains/enums/completion_source.py +++ b/src/adcp/types/domains/enums/completion_source.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``enums/completion_source`` schema. +# generated by datamodel-codegen: +# filename: enums/completion_source.json +# timestamp: 2026-05-22T13:15:12+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.completion_source import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.completion_source import CompletionSource +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["CompletionSource"] +class CompletionSource(StrEnum): + seller_attested = 'seller_attested' + vendor_attested = 'vendor_attested' diff --git a/src/adcp/types/domains/enums/consent_basis.py b/src/adcp/types/domains/enums/consent_basis.py index 96cdc145b..e8e746026 100644 --- a/src/adcp/types/domains/enums/consent_basis.py +++ b/src/adcp/types/domains/enums/consent_basis.py @@ -1,18 +1,14 @@ -"""Types declared by the AdCP ``enums/consent_basis`` schema. +# generated by datamodel-codegen: +# filename: enums/consent_basis.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.consent_basis import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.consent_basis import ConsentBasis +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["ConsentBasis"] +class ConsentBasis(StrEnum): + consent = 'consent' + legitimate_interest = 'legitimate_interest' + contract = 'contract' + legal_obligation = 'legal_obligation' diff --git a/src/adcp/types/domains/enums/content_id_type.py b/src/adcp/types/domains/enums/content_id_type.py index 3dabb5b48..b8f2b0806 100644 --- a/src/adcp/types/domains/enums/content_id_type.py +++ b/src/adcp/types/domains/enums/content_id_type.py @@ -1,18 +1,22 @@ -"""Types declared by the AdCP ``enums/content_id_type`` schema. +# generated by datamodel-codegen: +# filename: enums/content_id_type.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.content_id_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.content_id_type import ContentIdType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["ContentIdType"] +class ContentIdType(StrEnum): + sku = 'sku' + gtin = 'gtin' + offering_id = 'offering_id' + job_id = 'job_id' + hotel_id = 'hotel_id' + flight_id = 'flight_id' + vehicle_id = 'vehicle_id' + listing_id = 'listing_id' + store_id = 'store_id' + program_id = 'program_id' + destination_id = 'destination_id' + app_id = 'app_id' diff --git a/src/adcp/types/domains/enums/content_rating_system.py b/src/adcp/types/domains/enums/content_rating_system.py index 76cd5f5ac..88e0d1b7b 100644 --- a/src/adcp/types/domains/enums/content_rating_system.py +++ b/src/adcp/types/domains/enums/content_rating_system.py @@ -1,18 +1,21 @@ -"""Types declared by the AdCP ``enums/content_rating_system`` schema. +# generated by datamodel-codegen: +# filename: enums/content_rating_system.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.content_rating_system import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.content_rating_system import ContentRatingSystem +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["ContentRatingSystem"] +class ContentRatingSystem(StrEnum): + tv_parental = 'tv_parental' + mpaa = 'mpaa' + podcast = 'podcast' + esrb = 'esrb' + bbfc = 'bbfc' + fsk = 'fsk' + acb = 'acb' + chvrs = 'chvrs' + csa = 'csa' + pegi = 'pegi' + custom = 'custom' diff --git a/src/adcp/types/domains/enums/creative_action.py b/src/adcp/types/domains/enums/creative_action.py index 76870b997..d8f6a55e6 100644 --- a/src/adcp/types/domains/enums/creative_action.py +++ b/src/adcp/types/domains/enums/creative_action.py @@ -1,18 +1,15 @@ -"""Types declared by the AdCP ``enums/creative_action`` schema. +# generated by datamodel-codegen: +# filename: enums/creative_action.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.creative_action import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.creative_action import CreativeAction +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["CreativeAction"] +class CreativeAction(StrEnum): + created = 'created' + updated = 'updated' + unchanged = 'unchanged' + failed = 'failed' + deleted = 'deleted' diff --git a/src/adcp/types/domains/enums/creative_agent_capability.py b/src/adcp/types/domains/enums/creative_agent_capability.py index a6a5eb378..2eadea814 100644 --- a/src/adcp/types/domains/enums/creative_agent_capability.py +++ b/src/adcp/types/domains/enums/creative_agent_capability.py @@ -1,18 +1,15 @@ -"""Types declared by the AdCP ``enums/creative_agent_capability`` schema. +# generated by datamodel-codegen: +# filename: enums/creative_agent_capability.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.creative_agent_capability import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.creative_agent_capability import CreativeAgentCapability +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["CreativeAgentCapability"] +class CreativeAgentCapability(StrEnum): + validation = 'validation' + assembly = 'assembly' + generation = 'generation' + preview = 'preview' + delivery = 'delivery' diff --git a/src/adcp/types/domains/enums/creative_approval_status.py b/src/adcp/types/domains/enums/creative_approval_status.py index 0a4fbce87..a37c6ed81 100644 --- a/src/adcp/types/domains/enums/creative_approval_status.py +++ b/src/adcp/types/domains/enums/creative_approval_status.py @@ -1,18 +1,14 @@ -"""Types declared by the AdCP ``enums/creative_approval_status`` schema. +# generated by datamodel-codegen: +# filename: enums/creative_approval_status.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.creative_approval_status import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.creative_approval_status import CreativeApprovalStatus +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["CreativeApprovalStatus"] +class CreativeApprovalStatus(StrEnum): + pending_review = 'pending_review' + approved = 'approved' + partially_approved = 'partially_approved' + rejected = 'rejected' diff --git a/src/adcp/types/domains/enums/creative_event_reason_code.py b/src/adcp/types/domains/enums/creative_event_reason_code.py index 7ac410df3..53f4bb36f 100644 --- a/src/adcp/types/domains/enums/creative_event_reason_code.py +++ b/src/adcp/types/domains/enums/creative_event_reason_code.py @@ -1,18 +1,27 @@ -"""Types declared by the AdCP ``enums/creative_event_reason_code`` schema. +# generated by datamodel-codegen: +# filename: enums/creative_event_reason_code.json +# timestamp: 2026-06-04T19:44:00+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.creative_event_reason_code import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.creative_event_reason_code import CreativeEventReasonCode +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["CreativeEventReasonCode"] +class CreativeEventReasonCode(StrEnum): + review_passed = 'review_passed' + review_failure = 'review_failure' + processing_failure = 'processing_failure' + seller_rereview = 'seller_rereview' + policy_revocation = 'policy_revocation' + content_drift = 'content_drift' + identity_authorization_revoked = 'identity_authorization_revoked' + identity_authorization_expired = 'identity_authorization_expired' + source_private = 'source_private' + source_deleted = 'source_deleted' + takedown_request = 'takedown_request' + advertiser_request = 'advertiser_request' + seller_archive = 'seller_archive' + account_closed = 'account_closed' + account_suspended = 'account_suspended' + retention_expired = 'retention_expired' + legal_erasure = 'legal_erasure' diff --git a/src/adcp/types/domains/enums/creative_identifier_type.py b/src/adcp/types/domains/enums/creative_identifier_type.py index ae6fa427a..29e633a68 100644 --- a/src/adcp/types/domains/enums/creative_identifier_type.py +++ b/src/adcp/types/domains/enums/creative_identifier_type.py @@ -1,18 +1,14 @@ -"""Types declared by the AdCP ``enums/creative_identifier_type`` schema. +# generated by datamodel-codegen: +# filename: enums/creative_identifier_type.json +# timestamp: 2026-06-04T19:44:00+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.creative_identifier_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.creative_identifier_type import CreativeIdentifierType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["CreativeIdentifierType"] +class CreativeIdentifierType(StrEnum): + ad_id = 'ad_id' + isci = 'isci' + clearcast_clock = 'clearcast_clock' + idcrea = 'idcrea' diff --git a/src/adcp/types/domains/enums/creative_quality.py b/src/adcp/types/domains/enums/creative_quality.py index 1e403b6a8..38cdf5afe 100644 --- a/src/adcp/types/domains/enums/creative_quality.py +++ b/src/adcp/types/domains/enums/creative_quality.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``enums/creative_quality`` schema. +# generated by datamodel-codegen: +# filename: enums/creative_quality.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.creative_quality import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.creative_quality import CreativeQuality +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["CreativeQuality"] +class CreativeQuality(StrEnum): + draft = 'draft' + production = 'production' diff --git a/src/adcp/types/domains/enums/creative_selection_strategy.py b/src/adcp/types/domains/enums/creative_selection_strategy.py index 238027751..cb558038c 100644 --- a/src/adcp/types/domains/enums/creative_selection_strategy.py +++ b/src/adcp/types/domains/enums/creative_selection_strategy.py @@ -1,18 +1,16 @@ -"""Types declared by the AdCP ``enums/creative_selection_strategy`` schema. +# generated by datamodel-codegen: +# filename: enums/creative_selection_strategy.json +# timestamp: 2026-06-04T19:44:00+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.creative_selection_strategy import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.creative_selection_strategy import CreativeSelectionStrategy +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["CreativeSelectionStrategy"] +class CreativeSelectionStrategy(StrEnum): + audience_relevance = 'audience_relevance' + contextual_fit = 'contextual_fit' + performance = 'performance' + proximity = 'proximity' + inventory_priority = 'inventory_priority' + random = 'random' diff --git a/src/adcp/types/domains/enums/creative_sort_field.py b/src/adcp/types/domains/enums/creative_sort_field.py index 9cea677c8..c5baee2a2 100644 --- a/src/adcp/types/domains/enums/creative_sort_field.py +++ b/src/adcp/types/domains/enums/creative_sort_field.py @@ -1,18 +1,15 @@ -"""Types declared by the AdCP ``enums/creative_sort_field`` schema. +# generated by datamodel-codegen: +# filename: enums/creative_sort_field.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.creative_sort_field import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.creative_sort_field import CreativeSortField +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["CreativeSortField"] +class CreativeSortField(StrEnum): + created_date = 'created_date' + updated_date = 'updated_date' + name = 'name' + status = 'status' + assignment_count = 'assignment_count' diff --git a/src/adcp/types/domains/enums/creative_status.py b/src/adcp/types/domains/enums/creative_status.py index f33be8f5c..56b7bb52e 100644 --- a/src/adcp/types/domains/enums/creative_status.py +++ b/src/adcp/types/domains/enums/creative_status.py @@ -1,18 +1,16 @@ -"""Types declared by the AdCP ``enums/creative_status`` schema. +# generated by datamodel-codegen: +# filename: enums/creative_status.json +# timestamp: 2026-06-04T19:44:00+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.creative_status import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.creative_status import CreativeStatus +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["CreativeStatus"] +class CreativeStatus(StrEnum): + processing = 'processing' + pending_review = 'pending_review' + approved = 'approved' + suspended = 'suspended' + rejected = 'rejected' + archived = 'archived' diff --git a/src/adcp/types/domains/enums/ctv_ad_experience.py b/src/adcp/types/domains/enums/ctv_ad_experience.py index 46e77ae0a..18bc4f0e9 100644 --- a/src/adcp/types/domains/enums/ctv_ad_experience.py +++ b/src/adcp/types/domains/enums/ctv_ad_experience.py @@ -1,18 +1,16 @@ -"""Types declared by the AdCP ``enums/ctv_ad_experience`` schema. +# generated by datamodel-codegen: +# filename: enums/ctv_ad_experience.json +# timestamp: 2026-08-24T04:35:32+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.ctv_ad_experience import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.ctv_ad_experience import CtvAdExperience +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["CtvAdExperience"] +class CtvAdExperience(StrEnum): + menu = 'menu' + pause = 'pause' + screensaver = 'screensaver' + overlay = 'overlay' + squeezeback = 'squeezeback' + in_scene = 'in_scene' diff --git a/src/adcp/types/domains/enums/daast_tracking_event.py b/src/adcp/types/domains/enums/daast_tracking_event.py index ba4aafa9b..6ccaa07d9 100644 --- a/src/adcp/types/domains/enums/daast_tracking_event.py +++ b/src/adcp/types/domains/enums/daast_tracking_event.py @@ -1,18 +1,33 @@ -"""Types declared by the AdCP ``enums/daast_tracking_event`` schema. +# generated by datamodel-codegen: +# filename: enums/daast_tracking_event.json +# timestamp: 2026-05-22T13:15:12+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.daast_tracking_event import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.daast_tracking_event import DaastTrackingEvent +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["DaastTrackingEvent"] +class DaastTrackingEvent(StrEnum): + impression = 'impression' + creativeView = 'creativeView' + start = 'start' + firstQuartile = 'firstQuartile' + midpoint = 'midpoint' + thirdQuartile = 'thirdQuartile' + complete = 'complete' + mute = 'mute' + unmute = 'unmute' + pause = 'pause' + resume = 'resume' + rewind = 'rewind' + skip = 'skip' + progress = 'progress' + clickTracking = 'clickTracking' + customClick = 'customClick' + close = 'close' + error = 'error' + viewable = 'viewable' + notViewable = 'notViewable' + viewUndetermined = 'viewUndetermined' + measurableImpression = 'measurableImpression' + viewableImpression = 'viewableImpression' diff --git a/src/adcp/types/domains/enums/daast_version.py b/src/adcp/types/domains/enums/daast_version.py index 5d0d1d439..7a4cd61ce 100644 --- a/src/adcp/types/domains/enums/daast_version.py +++ b/src/adcp/types/domains/enums/daast_version.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``enums/daast_version`` schema. +# generated by datamodel-codegen: +# filename: enums/daast_version.json +# timestamp: 2026-05-19T21:40:22+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.daast_version import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.daast_version import DaastVersion +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["DaastVersion"] +class DaastVersion(StrEnum): + field_1_0 = '1.0' + field_1_1 = '1.1' diff --git a/src/adcp/types/domains/enums/day_of_week.py b/src/adcp/types/domains/enums/day_of_week.py index 8a72964f8..6cb8e69d1 100644 --- a/src/adcp/types/domains/enums/day_of_week.py +++ b/src/adcp/types/domains/enums/day_of_week.py @@ -1,18 +1,17 @@ -"""Types declared by the AdCP ``enums/day_of_week`` schema. +# generated by datamodel-codegen: +# filename: enums/day_of_week.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.day_of_week import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.day_of_week import DayOfWeek +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["DayOfWeek"] +class DayOfWeek(StrEnum): + monday = 'monday' + tuesday = 'tuesday' + wednesday = 'wednesday' + thursday = 'thursday' + friday = 'friday' + saturday = 'saturday' + sunday = 'sunday' diff --git a/src/adcp/types/domains/enums/daypart_timezone_mode.py b/src/adcp/types/domains/enums/daypart_timezone_mode.py index 8f153447f..f1ae4ccc4 100644 --- a/src/adcp/types/domains/enums/daypart_timezone_mode.py +++ b/src/adcp/types/domains/enums/daypart_timezone_mode.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``enums/daypart_timezone_mode`` schema. +# generated by datamodel-codegen: +# filename: enums/daypart_timezone_mode.json +# timestamp: 2026-09-03T18:44:01+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.daypart_timezone_mode import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.daypart_timezone_mode import DaypartTimezoneMode +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["DaypartTimezoneMode"] +class DaypartTimezoneMode(StrEnum): + inventory_local = 'inventory_local' + iana = 'iana' diff --git a/src/adcp/types/domains/enums/delegation_authority.py b/src/adcp/types/domains/enums/delegation_authority.py index d3e43846d..d75356d18 100644 --- a/src/adcp/types/domains/enums/delegation_authority.py +++ b/src/adcp/types/domains/enums/delegation_authority.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/delegation_authority`` schema. +# generated by datamodel-codegen: +# filename: enums/delegation_authority.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.delegation_authority import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.delegation_authority import DelegationAuthority +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["DelegationAuthority"] +class DelegationAuthority(StrEnum): + full = 'full' + execute_only = 'execute_only' + propose_only = 'propose_only' diff --git a/src/adcp/types/domains/enums/delivery_recipient_cloud.py b/src/adcp/types/domains/enums/delivery_recipient_cloud.py index 8b5694891..2890eed5b 100644 --- a/src/adcp/types/domains/enums/delivery_recipient_cloud.py +++ b/src/adcp/types/domains/enums/delivery_recipient_cloud.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/delivery_recipient_cloud`` schema. +# generated by datamodel-codegen: +# filename: enums/delivery_recipient_cloud.json +# timestamp: 2026-09-01T19:37:00+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.delivery_recipient_cloud import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.delivery_recipient_cloud import DeliveryRecipientCloud +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["DeliveryRecipientCloud"] +class DeliveryRecipientCloud(StrEnum): + aws = 'aws' + azure = 'azure' + gcp = 'gcp' diff --git a/src/adcp/types/domains/enums/delivery_status.py b/src/adcp/types/domains/enums/delivery_status.py index bc857a0cd..8c31d99f7 100644 --- a/src/adcp/types/domains/enums/delivery_status.py +++ b/src/adcp/types/domains/enums/delivery_status.py @@ -1,18 +1,16 @@ -"""Types declared by the AdCP ``enums/delivery_status`` schema. +# generated by datamodel-codegen: +# filename: enums/delivery_status.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.delivery_status import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.delivery_status import DeliveryStatus +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["DeliveryStatus"] +class DeliveryStatus(StrEnum): + delivering = 'delivering' + not_delivering = 'not_delivering' + completed = 'completed' + budget_exhausted = 'budget_exhausted' + flight_ended = 'flight_ended' + goal_met = 'goal_met' diff --git a/src/adcp/types/domains/enums/delivery_type.py b/src/adcp/types/domains/enums/delivery_type.py index 55ff75482..451f4a0b3 100644 --- a/src/adcp/types/domains/enums/delivery_type.py +++ b/src/adcp/types/domains/enums/delivery_type.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``enums/delivery_type`` schema. +# generated by datamodel-codegen: +# filename: enums/delivery_type.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.delivery_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.delivery_type import DeliveryType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["DeliveryType"] +class DeliveryType(StrEnum): + guaranteed = 'guaranteed' + non_guaranteed = 'non_guaranteed' diff --git a/src/adcp/types/domains/enums/demographic_system.py b/src/adcp/types/domains/enums/demographic_system.py index 63d569293..098be4165 100644 --- a/src/adcp/types/domains/enums/demographic_system.py +++ b/src/adcp/types/domains/enums/demographic_system.py @@ -1,18 +1,17 @@ -"""Types declared by the AdCP ``enums/demographic_system`` schema. +# generated by datamodel-codegen: +# filename: enums/demographic_system.json +# timestamp: 2026-08-28T20:03:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.demographic_system import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.demographic_system import DemographicSystem +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["DemographicSystem"] +class DemographicSystem(StrEnum): + nielsen = 'nielsen' + nielsen_audio = 'nielsen_audio' + barb = 'barb' + agf = 'agf' + oztam = 'oztam' + mediametrie = 'mediametrie' + custom = 'custom' diff --git a/src/adcp/types/domains/enums/derivative_type.py b/src/adcp/types/domains/enums/derivative_type.py index c4867e8b8..687eed621 100644 --- a/src/adcp/types/domains/enums/derivative_type.py +++ b/src/adcp/types/domains/enums/derivative_type.py @@ -1,18 +1,15 @@ -"""Types declared by the AdCP ``enums/derivative_type`` schema. +# generated by datamodel-codegen: +# filename: enums/derivative_type.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.derivative_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.derivative_type import DerivativeType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["DerivativeType"] +class DerivativeType(StrEnum): + clip = 'clip' + highlight = 'highlight' + recap = 'recap' + trailer = 'trailer' + bonus = 'bonus' diff --git a/src/adcp/types/domains/enums/device_platform.py b/src/adcp/types/domains/enums/device_platform.py index 848293e60..3c9df30bf 100644 --- a/src/adcp/types/domains/enums/device_platform.py +++ b/src/adcp/types/domains/enums/device_platform.py @@ -1,18 +1,22 @@ -"""Types declared by the AdCP ``enums/device_platform`` schema. +# generated by datamodel-codegen: +# filename: enums/device_platform.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.device_platform import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.device_platform import DevicePlatform +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["DevicePlatform"] +class DevicePlatform(StrEnum): + ios = 'ios' + android = 'android' + windows = 'windows' + macos = 'macos' + linux = 'linux' + chromeos = 'chromeos' + tvos = 'tvos' + tizen = 'tizen' + webos = 'webos' + fire_os = 'fire_os' + roku_os = 'roku_os' + unknown = 'unknown' diff --git a/src/adcp/types/domains/enums/device_type.py b/src/adcp/types/domains/enums/device_type.py index 2d6ce52b7..625d9c7f4 100644 --- a/src/adcp/types/domains/enums/device_type.py +++ b/src/adcp/types/domains/enums/device_type.py @@ -1,18 +1,16 @@ -"""Types declared by the AdCP ``enums/device_type`` schema. +# generated by datamodel-codegen: +# filename: enums/device_type.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.device_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.device_type import DeviceType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["DeviceType"] +class DeviceType(StrEnum): + desktop = 'desktop' + mobile = 'mobile' + tablet = 'tablet' + ctv = 'ctv' + dooh = 'dooh' + unknown = 'unknown' diff --git a/src/adcp/types/domains/enums/digital_source_type.py b/src/adcp/types/domains/enums/digital_source_type.py index 44bfb655b..5d0756beb 100644 --- a/src/adcp/types/domains/enums/digital_source_type.py +++ b/src/adcp/types/domains/enums/digital_source_type.py @@ -1,18 +1,19 @@ -"""Types declared by the AdCP ``enums/digital_source_type`` schema. +# generated by datamodel-codegen: +# filename: enums/digital_source_type.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.digital_source_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.digital_source_type import DigitalSourceType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["DigitalSourceType"] +class DigitalSourceType(StrEnum): + digital_capture = 'digital_capture' + digital_creation = 'digital_creation' + trained_algorithmic_media = 'trained_algorithmic_media' + composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' + algorithmic_media = 'algorithmic_media' + composite_capture = 'composite_capture' + composite_synthetic = 'composite_synthetic' + human_edits = 'human_edits' + data_driven_media = 'data_driven_media' diff --git a/src/adcp/types/domains/enums/dimension_unit.py b/src/adcp/types/domains/enums/dimension_unit.py index 6b96e7396..78dfa62f5 100644 --- a/src/adcp/types/domains/enums/dimension_unit.py +++ b/src/adcp/types/domains/enums/dimension_unit.py @@ -1,18 +1,16 @@ -"""Types declared by the AdCP ``enums/dimension_unit`` schema. +# generated by datamodel-codegen: +# filename: enums/dimension_unit.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.dimension_unit import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.dimension_unit import DimensionUnit +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["DimensionUnit"] +class DimensionUnit(StrEnum): + px = 'px' + dp = 'dp' + inches = 'inches' + cm = 'cm' + mm = 'mm' + pt = 'pt' diff --git a/src/adcp/types/domains/enums/disclosure_persistence.py b/src/adcp/types/domains/enums/disclosure_persistence.py index 457f86116..e8fdecfa4 100644 --- a/src/adcp/types/domains/enums/disclosure_persistence.py +++ b/src/adcp/types/domains/enums/disclosure_persistence.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/disclosure_persistence`` schema. +# generated by datamodel-codegen: +# filename: enums/disclosure_persistence.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.disclosure_persistence import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.disclosure_persistence import DisclosurePersistence +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["DisclosurePersistence"] +class DisclosurePersistence(StrEnum): + continuous = 'continuous' + initial = 'initial' + flexible = 'flexible' diff --git a/src/adcp/types/domains/enums/disclosure_position.py b/src/adcp/types/domains/enums/disclosure_position.py index 0e82a0443..1ddf30646 100644 --- a/src/adcp/types/domains/enums/disclosure_position.py +++ b/src/adcp/types/domains/enums/disclosure_position.py @@ -1,18 +1,18 @@ -"""Types declared by the AdCP ``enums/disclosure_position`` schema. +# generated by datamodel-codegen: +# filename: enums/disclosure_position.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.disclosure_position import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.disclosure_position import DisclosurePosition +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["DisclosurePosition"] +class DisclosurePosition(StrEnum): + prominent = 'prominent' + footer = 'footer' + audio = 'audio' + subtitle = 'subtitle' + overlay = 'overlay' + end_card = 'end_card' + pre_roll = 'pre_roll' + companion = 'companion' diff --git a/src/adcp/types/domains/enums/distance_unit.py b/src/adcp/types/domains/enums/distance_unit.py index 1bd713f6f..4539bfd49 100644 --- a/src/adcp/types/domains/enums/distance_unit.py +++ b/src/adcp/types/domains/enums/distance_unit.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/distance_unit`` schema. +# generated by datamodel-codegen: +# filename: enums/distance_unit.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.distance_unit import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.distance_unit import DistanceUnit +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["DistanceUnit"] +class DistanceUnit(StrEnum): + km = 'km' + mi = 'mi' + m = 'm' diff --git a/src/adcp/types/domains/enums/distribution_identifier_type.py b/src/adcp/types/domains/enums/distribution_identifier_type.py index 551d077a5..672e88bd5 100644 --- a/src/adcp/types/domains/enums/distribution_identifier_type.py +++ b/src/adcp/types/domains/enums/distribution_identifier_type.py @@ -1,18 +1,34 @@ -"""Types declared by the AdCP ``enums/distribution_identifier_type`` schema. +# generated by datamodel-codegen: +# filename: enums/distribution_identifier_type.json +# timestamp: 2026-09-01T19:37:00+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.distribution_identifier_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.distribution_identifier_type import DistributionIdentifierType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["DistributionIdentifierType"] +class DistributionIdentifierType(StrEnum): + apple_podcast_id = 'apple_podcast_id' + spotify_collection_id = 'spotify_collection_id' + rss_url = 'rss_url' + podcast_guid = 'podcast_guid' + amazon_music_id = 'amazon_music_id' + iheart_id = 'iheart_id' + podcast_index_id = 'podcast_index_id' + youtube_channel_id = 'youtube_channel_id' + youtube_channel_handle = 'youtube_channel_handle' + youtube_channel_url = 'youtube_channel_url' + youtube_playlist_id = 'youtube_playlist_id' + amazon_title_id = 'amazon_title_id' + platform_channel_id = 'platform_channel_id' + roku_channel_id = 'roku_channel_id' + pluto_channel_id = 'pluto_channel_id' + tubi_id = 'tubi_id' + peacock_id = 'peacock_id' + tiktok_id = 'tiktok_id' + twitch_channel = 'twitch_channel' + imdb_id = 'imdb_id' + gracenote_id = 'gracenote_id' + eidr_id = 'eidr_id' + domain = 'domain' + substack_id = 'substack_id' diff --git a/src/adcp/types/domains/enums/dooh_motion_type.py b/src/adcp/types/domains/enums/dooh_motion_type.py index fae8ab2a9..81b1cb59a 100644 --- a/src/adcp/types/domains/enums/dooh_motion_type.py +++ b/src/adcp/types/domains/enums/dooh_motion_type.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/dooh_motion_type`` schema. +# generated by datamodel-codegen: +# filename: enums/dooh_motion_type.json +# timestamp: 2026-09-01T19:37:00+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.dooh_motion_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.dooh_motion_type import DoohMotionType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["DoohMotionType"] +class DoohMotionType(StrEnum): + full_motion = 'full_motion' + partial_motion = 'partial_motion' + static = 'static' diff --git a/src/adcp/types/domains/enums/embedded_provenance_method.py b/src/adcp/types/domains/enums/embedded_provenance_method.py index 24588640b..89f8eeda5 100644 --- a/src/adcp/types/domains/enums/embedded_provenance_method.py +++ b/src/adcp/types/domains/enums/embedded_provenance_method.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``enums/embedded_provenance_method`` schema. +# generated by datamodel-codegen: +# filename: enums/embedded_provenance_method.json +# timestamp: 2026-05-22T13:15:12+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.embedded_provenance_method import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.embedded_provenance_method import EmbeddedProvenanceMethod +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["EmbeddedProvenanceMethod"] +class EmbeddedProvenanceMethod(StrEnum): + manifest_wrapper = 'manifest_wrapper' + provenance_markers = 'provenance_markers' diff --git a/src/adcp/types/domains/enums/error_code.py b/src/adcp/types/domains/enums/error_code.py index 7243fa985..463df271b 100644 --- a/src/adcp/types/domains/enums/error_code.py +++ b/src/adcp/types/domains/enums/error_code.py @@ -1,18 +1,130 @@ -"""Types declared by the AdCP ``enums/error_code`` schema. +# generated by datamodel-codegen: +# filename: enums/error_code.json +# timestamp: 2026-09-24T08:59:48+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.error_code import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.error_code import ErrorCode +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["ErrorCode"] +class ErrorCode(StrEnum): + INVALID_REQUEST = 'INVALID_REQUEST' + AUTH_REQUIRED = 'AUTH_REQUIRED' + AUTH_MISSING = 'AUTH_MISSING' + AUTH_INVALID = 'AUTH_INVALID' + AUTHORIZATION_REQUIRED = 'AUTHORIZATION_REQUIRED' + RATE_LIMITED = 'RATE_LIMITED' + SERVICE_UNAVAILABLE = 'SERVICE_UNAVAILABLE' + CONFIGURATION_ERROR = 'CONFIGURATION_ERROR' + POLICY_VIOLATION = 'POLICY_VIOLATION' + PRODUCT_NOT_FOUND = 'PRODUCT_NOT_FOUND' + PRODUCT_UNAVAILABLE = 'PRODUCT_UNAVAILABLE' + PROPOSAL_EXPIRED = 'PROPOSAL_EXPIRED' + BUDGET_TOO_LOW = 'BUDGET_TOO_LOW' + CREATIVE_REJECTED = 'CREATIVE_REJECTED' + CREATIVE_SIZE_MISMATCH = 'CREATIVE_SIZE_MISMATCH' + CREATIVE_MISSING_CLICK_URL = 'CREATIVE_MISSING_CLICK_URL' + CREATIVE_VALIDATION_FAILED_GENERIC = 'CREATIVE_VALIDATION_FAILED_GENERIC' + CREATIVE_LOCALE_NOT_ACCEPTED = 'CREATIVE_LOCALE_NOT_ACCEPTED' + CREATIVE_VALUE_NOT_ALLOWED = 'CREATIVE_VALUE_NOT_ALLOWED' + CREATIVE_REVISION_CONTENT_MISMATCH = 'CREATIVE_REVISION_CONTENT_MISMATCH' + UNSUPPORTED_FEATURE = 'UNSUPPORTED_FEATURE' + UNPRICEABLE_OUTPUT = 'UNPRICEABLE_OUTPUT' + UNSUPPORTED_GRANULARITY = 'UNSUPPORTED_GRANULARITY' + UNSUPPORTED_PROVISIONING = 'UNSUPPORTED_PROVISIONING' + AUDIENCE_TOO_SMALL = 'AUDIENCE_TOO_SMALL' + ACCOUNT_REQUIRED = 'ACCOUNT_REQUIRED' + ACCOUNT_NOT_FOUND = 'ACCOUNT_NOT_FOUND' + ACCOUNT_MOVED = 'ACCOUNT_MOVED' + ACCOUNT_IDENTITY_CONFLICT = 'ACCOUNT_IDENTITY_CONFLICT' + ACCOUNT_SETUP_REQUIRED = 'ACCOUNT_SETUP_REQUIRED' + ACCOUNT_AMBIGUOUS = 'ACCOUNT_AMBIGUOUS' + ACCOUNT_PAYMENT_REQUIRED = 'ACCOUNT_PAYMENT_REQUIRED' + ACCOUNT_SUSPENDED = 'ACCOUNT_SUSPENDED' + COMPLIANCE_UNSATISFIED = 'COMPLIANCE_UNSATISFIED' + GOVERNANCE_DENIED = 'GOVERNANCE_DENIED' + BUDGET_EXHAUSTED = 'BUDGET_EXHAUSTED' + BUDGET_EXCEEDED = 'BUDGET_EXCEEDED' + BUDGET_CAP_REACHED = 'BUDGET_CAP_REACHED' + CONFLICT = 'CONFLICT' + COMMITTED_RESOURCE_PURGED = 'COMMITTED_RESOURCE_PURGED' + IDEMPOTENCY_CONFLICT = 'IDEMPOTENCY_CONFLICT' + IDEMPOTENCY_EXPIRED = 'IDEMPOTENCY_EXPIRED' + IDEMPOTENCY_IN_FLIGHT = 'IDEMPOTENCY_IN_FLIGHT' + CURSOR_EXPIRED = 'CURSOR_EXPIRED' + CREATIVE_DEADLINE_EXCEEDED = 'CREATIVE_DEADLINE_EXCEEDED' + CREATIVE_INACCESSIBLE = 'CREATIVE_INACCESSIBLE' + INVALID_STATE = 'INVALID_STATE' + MEDIA_BUY_NOT_FOUND = 'MEDIA_BUY_NOT_FOUND' + NOT_CANCELLABLE = 'NOT_CANCELLABLE' + PACKAGE_NOT_FOUND = 'PACKAGE_NOT_FOUND' + PLACE_TARGET_UNAVAILABLE = 'PLACE_TARGET_UNAVAILABLE' + CREATIVE_NOT_FOUND = 'CREATIVE_NOT_FOUND' + SIGNAL_NOT_FOUND = 'SIGNAL_NOT_FOUND' + SIGNAL_TARGETING_INCOMPATIBLE = 'SIGNAL_TARGETING_INCOMPATIBLE' + SESSION_NOT_FOUND = 'SESSION_NOT_FOUND' + PLAN_NOT_FOUND = 'PLAN_NOT_FOUND' + REFERENCE_NOT_FOUND = 'REFERENCE_NOT_FOUND' + SESSION_TERMINATED = 'SESSION_TERMINATED' + VALIDATION_ERROR = 'VALIDATION_ERROR' + PRODUCT_EXPIRED = 'PRODUCT_EXPIRED' + PROPOSAL_NOT_COMMITTED = 'PROPOSAL_NOT_COMMITTED' + PROPOSAL_NOT_FOUND = 'PROPOSAL_NOT_FOUND' + MULTI_FINALIZE_UNSUPPORTED = 'MULTI_FINALIZE_UNSUPPORTED' + IO_REQUIRED = 'IO_REQUIRED' + TERMS_REJECTED = 'TERMS_REJECTED' + BIDDING_PLACEMENT_CONFLICT = 'BIDDING_PLACEMENT_CONFLICT' + AMBIGUOUS_BIDDING_POLICY = 'AMBIGUOUS_BIDDING_POLICY' + CONFLICTING_SELECTORS = 'CONFLICTING_SELECTORS' + REQUOTE_REQUIRED = 'REQUOTE_REQUIRED' + VERSION_UNSUPPORTED = 'VERSION_UNSUPPORTED' + CAMPAIGN_SUSPENDED = 'CAMPAIGN_SUSPENDED' + GOVERNANCE_UNAVAILABLE = 'GOVERNANCE_UNAVAILABLE' + GOVERNANCE_AGENT_NOT_ACCEPTED = 'GOVERNANCE_AGENT_NOT_ACCEPTED' + PERMISSION_DENIED = 'PERMISSION_DENIED' + SCOPE_INSUFFICIENT = 'SCOPE_INSUFFICIENT' + READ_ONLY_SCOPE = 'READ_ONLY_SCOPE' + FIELD_NOT_PERMITTED = 'FIELD_NOT_PERMITTED' + PROVENANCE_REQUIRED = 'PROVENANCE_REQUIRED' + PROVENANCE_DIGITAL_SOURCE_TYPE_MISSING = 'PROVENANCE_DIGITAL_SOURCE_TYPE_MISSING' + PROVENANCE_SYNTHETIC_DEPICTION_MISSING = 'PROVENANCE_SYNTHETIC_DEPICTION_MISSING' + PROVENANCE_DISCLOSURE_MISSING = 'PROVENANCE_DISCLOSURE_MISSING' + PROVENANCE_EMBEDDED_MISSING = 'PROVENANCE_EMBEDDED_MISSING' + PROVENANCE_VERIFIER_NOT_ACCEPTED = 'PROVENANCE_VERIFIER_NOT_ACCEPTED' + PROVENANCE_CLAIM_CONTRADICTED = 'PROVENANCE_CLAIM_CONTRADICTED' + EVALUATOR_AGENT_NOT_ACCEPTED = 'EVALUATOR_AGENT_NOT_ACCEPTED' + BILLING_NOT_SUPPORTED = 'BILLING_NOT_SUPPORTED' + BILLING_NOT_PERMITTED_FOR_AGENT = 'BILLING_NOT_PERMITTED_FOR_AGENT' + BILLING_OUT_OF_BAND = 'BILLING_OUT_OF_BAND' + PAYMENT_TERMS_NOT_SUPPORTED = 'PAYMENT_TERMS_NOT_SUPPORTED' + BRAND_REQUIRED = 'BRAND_REQUIRED' + AGENT_SUSPENDED = 'AGENT_SUSPENDED' + AGENT_BLOCKED = 'AGENT_BLOCKED' + CREDENTIAL_IN_ARGS = 'CREDENTIAL_IN_ARGS' + ACTION_NOT_ALLOWED = 'ACTION_NOT_ALLOWED' + PRIVATE_FIELD_IN_PUBLIC_PLACEMENT = 'PRIVATE_FIELD_IN_PUBLIC_PLACEMENT' + FORMAT_PROJECTION_FAILED = 'FORMAT_PROJECTION_FAILED' + FORMAT_DECLARATION_DIVERGENT = 'FORMAT_DECLARATION_DIVERGENT' + FORMAT_SHAPE_PROMOTED = 'FORMAT_SHAPE_PROMOTED' + FORMAT_DECLARATION_V1_AMBIGUOUS = 'FORMAT_DECLARATION_V1_AMBIGUOUS' + FORMAT_OPTION_UNRESOLVED = 'FORMAT_OPTION_UNRESOLVED' + FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE = 'FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE' + FORMAT_NOT_SUPPORTED = 'FORMAT_NOT_SUPPORTED' + PIXEL_TRACKER_LOSSY_DOWNGRADE = 'PIXEL_TRACKER_LOSSY_DOWNGRADE' + PIXEL_TRACKER_UPGRADE_INFERRED = 'PIXEL_TRACKER_UPGRADE_INFERRED' + STALE_RESPONSE = 'STALE_RESPONSE' + FEED_FETCH_FAILED = 'FEED_FETCH_FAILED' + SOURCE_ACCESS_FAILED = 'SOURCE_ACCESS_FAILED' + INVALID_FEED_FORMAT = 'INVALID_FEED_FORMAT' + ITEM_VALIDATION_FAILED = 'ITEM_VALIDATION_FAILED' + CATALOG_LIMIT_EXCEEDED = 'CATALOG_LIMIT_EXCEEDED' + INVALID_PRICING_OPTION = 'INVALID_PRICING_OPTION' + INVALID_USAGE_DATA = 'INVALID_USAGE_DATA' + SIGNED_RESPONSE_ENVELOPE_EXPIRED = 'SIGNED_RESPONSE_ENVELOPE_EXPIRED' + SIGNED_RESPONSE_REQUEST_HASH_MISMATCH = 'SIGNED_RESPONSE_REQUEST_HASH_MISMATCH' + SIGNED_RESPONSE_TENANT_MISMATCH = 'SIGNED_RESPONSE_TENANT_MISMATCH' + VAST_PARSE_FAILED = 'VAST_PARSE_FAILED' + VAST_VERSION_MISMATCH = 'VAST_VERSION_MISMATCH' + VAST_WRAPPER_DEPTH_EXCEEDED = 'VAST_WRAPPER_DEPTH_EXCEEDED' + CREATIVE_REPRESENTATION_UNRESOLVED = 'CREATIVE_REPRESENTATION_UNRESOLVED' + MACRO_RESOLUTION_FAILED = 'MACRO_RESOLUTION_FAILED' diff --git a/src/adcp/types/domains/enums/error_scope.py b/src/adcp/types/domains/enums/error_scope.py index 54d3381e1..c61b362ac 100644 --- a/src/adcp/types/domains/enums/error_scope.py +++ b/src/adcp/types/domains/enums/error_scope.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/error_scope`` schema. +# generated by datamodel-codegen: +# filename: enums/error_scope.json +# timestamp: 2026-05-22T13:15:12+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.error_scope import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.error_scope import ErrorScope +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["ErrorScope"] +class ErrorScope(StrEnum): + capability = 'capability' + account = 'account' + agent = 'agent' diff --git a/src/adcp/types/domains/enums/escalation_severity.py b/src/adcp/types/domains/enums/escalation_severity.py index c06ea350e..e93e4bde0 100644 --- a/src/adcp/types/domains/enums/escalation_severity.py +++ b/src/adcp/types/domains/enums/escalation_severity.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/escalation_severity`` schema. +# generated by datamodel-codegen: +# filename: enums/escalation_severity.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.escalation_severity import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.escalation_severity import EscalationSeverity +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["EscalationSeverity"] +class EscalationSeverity(StrEnum): + info = 'info' + warning = 'warning' + critical = 'critical' diff --git a/src/adcp/types/domains/enums/event_type.py b/src/adcp/types/domains/enums/event_type.py index 4d8e28670..8ad76c33c 100644 --- a/src/adcp/types/domains/enums/event_type.py +++ b/src/adcp/types/domains/enums/event_type.py @@ -1,18 +1,41 @@ -"""Types declared by the AdCP ``enums/event_type`` schema. +# generated by datamodel-codegen: +# filename: enums/event_type.json +# timestamp: 2026-06-01T00:32:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.event_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.event_type import EventType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["EventType"] +class EventType(StrEnum): + page_view = 'page_view' + view_content = 'view_content' + select_content = 'select_content' + select_item = 'select_item' + search = 'search' + share = 'share' + add_to_cart = 'add_to_cart' + remove_from_cart = 'remove_from_cart' + viewed_cart = 'viewed_cart' + add_to_wishlist = 'add_to_wishlist' + initiate_checkout = 'initiate_checkout' + add_payment_info = 'add_payment_info' + purchase = 'purchase' + refund = 'refund' + lead = 'lead' + qualify_lead = 'qualify_lead' + close_convert_lead = 'close_convert_lead' + disqualify_lead = 'disqualify_lead' + complete_registration = 'complete_registration' + subscribe = 'subscribe' + follow = 'follow' + content_view = 'content_view' + watch_milestone = 'watch_milestone' + start_trial = 'start_trial' + app_install = 'app_install' + app_launch = 'app_launch' + contact = 'contact' + schedule = 'schedule' + donate = 'donate' + submit_application = 'submit_application' + custom = 'custom' diff --git a/src/adcp/types/domains/enums/exclusivity.py b/src/adcp/types/domains/enums/exclusivity.py index dbda9b8f9..14a926862 100644 --- a/src/adcp/types/domains/enums/exclusivity.py +++ b/src/adcp/types/domains/enums/exclusivity.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/exclusivity`` schema. +# generated by datamodel-codegen: +# filename: enums/exclusivity.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.exclusivity import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.exclusivity import Exclusivity +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["Exclusivity"] +class Exclusivity(StrEnum): + none = 'none' + category = 'category' + exclusive = 'exclusive' diff --git a/src/adcp/types/domains/enums/feature_check_status.py b/src/adcp/types/domains/enums/feature_check_status.py index f5d5cc918..94b6c78a6 100644 --- a/src/adcp/types/domains/enums/feature_check_status.py +++ b/src/adcp/types/domains/enums/feature_check_status.py @@ -1,18 +1,14 @@ -"""Types declared by the AdCP ``enums/feature_check_status`` schema. +# generated by datamodel-codegen: +# filename: enums/feature_check_status.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.feature_check_status import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.feature_check_status import FeatureCheckStatus +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["FeatureCheckStatus"] +class FeatureCheckStatus(StrEnum): + passed = 'passed' + failed = 'failed' + warning = 'warning' + unevaluated = 'unevaluated' diff --git a/src/adcp/types/domains/enums/feed_format.py b/src/adcp/types/domains/enums/feed_format.py index 6ef63f934..2164a7cee 100644 --- a/src/adcp/types/domains/enums/feed_format.py +++ b/src/adcp/types/domains/enums/feed_format.py @@ -1,18 +1,18 @@ -"""Types declared by the AdCP ``enums/feed_format`` schema. +# generated by datamodel-codegen: +# filename: enums/feed_format.json +# timestamp: 2026-06-04T19:44:00+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.feed_format import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.feed_format import FeedFormat +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["FeedFormat"] +class FeedFormat(StrEnum): + google_merchant_center = 'google_merchant_center' + facebook_catalog = 'facebook_catalog' + shopify = 'shopify' + linkedin_jobs = 'linkedin_jobs' + tiktok_shop = 'tiktok_shop' + pinterest_catalog = 'pinterest_catalog' + openai_product_feed = 'openai_product_feed' + custom = 'custom' diff --git a/src/adcp/types/domains/enums/feedback_source.py b/src/adcp/types/domains/enums/feedback_source.py index 96b1fdd4a..7e163d1b2 100644 --- a/src/adcp/types/domains/enums/feedback_source.py +++ b/src/adcp/types/domains/enums/feedback_source.py @@ -1,18 +1,14 @@ -"""Types declared by the AdCP ``enums/feedback_source`` schema. +# generated by datamodel-codegen: +# filename: enums/feedback_source.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.feedback_source import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.feedback_source import FeedbackSource +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["FeedbackSource"] +class FeedbackSource(StrEnum): + buyer_attribution = 'buyer_attribution' + third_party_measurement = 'third_party_measurement' + platform_analytics = 'platform_analytics' + verification_partner = 'verification_partner' diff --git a/src/adcp/types/domains/enums/forecast_method.py b/src/adcp/types/domains/enums/forecast_method.py index fa90abeb2..0bd062137 100644 --- a/src/adcp/types/domains/enums/forecast_method.py +++ b/src/adcp/types/domains/enums/forecast_method.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/forecast_method`` schema. +# generated by datamodel-codegen: +# filename: enums/forecast_method.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.forecast_method import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.forecast_method import ForecastMethod +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["ForecastMethod"] +class ForecastMethod(StrEnum): + estimate = 'estimate' + modeled = 'modeled' + guaranteed = 'guaranteed' diff --git a/src/adcp/types/domains/enums/forecast_range_unit.py b/src/adcp/types/domains/enums/forecast_range_unit.py index 59edd4ceb..1df752c19 100644 --- a/src/adcp/types/domains/enums/forecast_range_unit.py +++ b/src/adcp/types/domains/enums/forecast_range_unit.py @@ -1,18 +1,18 @@ -"""Types declared by the AdCP ``enums/forecast_range_unit`` schema. +# generated by datamodel-codegen: +# filename: enums/forecast_range_unit.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.forecast_range_unit import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.forecast_range_unit import ForecastRangeUnit +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["ForecastRangeUnit"] +class ForecastRangeUnit(StrEnum): + spend = 'spend' + availability = 'availability' + reach_freq = 'reach_freq' + weekly = 'weekly' + daily = 'daily' + clicks = 'clicks' + conversions = 'conversions' + package = 'package' diff --git a/src/adcp/types/domains/enums/forecastable_metric.py b/src/adcp/types/domains/enums/forecastable_metric.py index c9cd3f80d..5285d3688 100644 --- a/src/adcp/types/domains/enums/forecastable_metric.py +++ b/src/adcp/types/domains/enums/forecastable_metric.py @@ -1,18 +1,27 @@ -"""Types declared by the AdCP ``enums/forecastable_metric`` schema. +# generated by datamodel-codegen: +# filename: enums/forecastable_metric.json +# timestamp: 2026-05-28T10:34:10+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.forecastable_metric import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.forecastable_metric import ForecastableMetric +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["ForecastableMetric"] +class ForecastableMetric(StrEnum): + audience_size = 'audience_size' + reach = 'reach' + frequency = 'frequency' + impressions = 'impressions' + clicks = 'clicks' + spend = 'spend' + views = 'views' + completed_views = 'completed_views' + grps = 'grps' + engagements = 'engagements' + follows = 'follows' + saves = 'saves' + profile_visits = 'profile_visits' + measured_impressions = 'measured_impressions' + downloads = 'downloads' + plays = 'plays' + coverage_rate = 'coverage_rate' diff --git a/src/adcp/types/generated_poc/enums/format_category.py b/src/adcp/types/domains/enums/format_category.py similarity index 76% rename from src/adcp/types/generated_poc/enums/format_category.py rename to src/adcp/types/domains/enums/format_category.py index 89c830e29..1a01b4616 100644 --- a/src/adcp/types/generated_poc/enums/format_category.py +++ b/src/adcp/types/domains/enums/format_category.py @@ -6,17 +6,17 @@ Importing this module raises :class:`ImportError` with a pointer to the migration guide — so downstream import sites like:: - from adcp.types.generated_poc.enums.format_category import FormatCategory + from adcp.types.domains.enums.format_category import FormatCategory get the same pointer as the top-level ``from adcp import FormatCategory``, instead of a bare ``ModuleNotFoundError``. This file is restored after every codegen run by -``scripts/post_generate_fixes.py`` (which wipes ``generated_poc/``). +``scripts/post_generate_fixes.py`` (which wipes the generated tree). """ raise ImportError( - "adcp.types.generated_poc.enums.format_category was removed in AdCP 3.0. " + "adcp.types.domains.enums.format_category was removed in AdCP 3.0. " "Use free-form format-id strings (e.g. 'goog:video_responsive_ad') via " "adcp.types.FormatId. See MIGRATION_v3_to_v4.md#creative-format-asset-slots-formataasset-aliases " "for details." diff --git a/src/adcp/types/domains/enums/format_id_parameter.py b/src/adcp/types/domains/enums/format_id_parameter.py index fc0b4b2da..29a761618 100644 --- a/src/adcp/types/domains/enums/format_id_parameter.py +++ b/src/adcp/types/domains/enums/format_id_parameter.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/format_id_parameter`` schema. +# generated by datamodel-codegen: +# filename: enums/format_id_parameter.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.format_id_parameter import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.format_id_parameter import FormatIdParameter +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["FormatIdParameter"] +class FormatIdParameter(StrEnum): + dimensions = 'dimensions' + duration = 'duration' + pixel_ratio = 'pixel_ratio' diff --git a/src/adcp/types/domains/enums/frame_rate_type.py b/src/adcp/types/domains/enums/frame_rate_type.py index 27294b0d5..cfaebdd75 100644 --- a/src/adcp/types/domains/enums/frame_rate_type.py +++ b/src/adcp/types/domains/enums/frame_rate_type.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``enums/frame_rate_type`` schema. +# generated by datamodel-codegen: +# filename: enums/frame_rate_type.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.frame_rate_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.frame_rate_type import FrameRateType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["FrameRateType"] +class FrameRateType(StrEnum): + constant = 'constant' + variable = 'variable' diff --git a/src/adcp/types/domains/enums/frequency_cap_control_mode.py b/src/adcp/types/domains/enums/frequency_cap_control_mode.py index e46de16db..79e5015d6 100644 --- a/src/adcp/types/domains/enums/frequency_cap_control_mode.py +++ b/src/adcp/types/domains/enums/frequency_cap_control_mode.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/frequency_cap_control_mode`` schema. +# generated by datamodel-codegen: +# filename: enums/frequency_cap_control_mode.json +# timestamp: 2026-09-14T14:16:02+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.frequency_cap_control_mode import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.frequency_cap_control_mode import FrequencyCapControlMode +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["FrequencyCapControlMode"] +class FrequencyCapControlMode(StrEnum): + max_impressions = 'max_impressions' + suppress = 'suppress' + max_impressions_and_suppress = 'max_impressions_and_suppress' diff --git a/src/adcp/types/domains/enums/frequency_cap_mutable_field.py b/src/adcp/types/domains/enums/frequency_cap_mutable_field.py index e5578f074..aab069855 100644 --- a/src/adcp/types/domains/enums/frequency_cap_mutable_field.py +++ b/src/adcp/types/domains/enums/frequency_cap_mutable_field.py @@ -1,18 +1,14 @@ -"""Types declared by the AdCP ``enums/frequency_cap_mutable_field`` schema. +# generated by datamodel-codegen: +# filename: enums/frequency_cap_mutable_field.json +# timestamp: 2026-09-14T14:16:02+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.frequency_cap_mutable_field import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.frequency_cap_mutable_field import FrequencyCapMutableField +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["FrequencyCapMutableField"] +class FrequencyCapMutableField(StrEnum): + max_impressions = 'max_impressions' + per = 'per' + window = 'window' + suppress = 'suppress' diff --git a/src/adcp/types/domains/enums/frequency_cap_scope.py b/src/adcp/types/domains/enums/frequency_cap_scope.py index 8f6a242e2..ff99c43fe 100644 --- a/src/adcp/types/domains/enums/frequency_cap_scope.py +++ b/src/adcp/types/domains/enums/frequency_cap_scope.py @@ -1,18 +1,16 @@ -"""Types declared by the AdCP ``enums/frequency_cap_scope`` schema. +# generated by datamodel-codegen: +# filename: enums/frequency_cap_scope.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.frequency_cap_scope import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import Field, RootModel -from adcp.types.generated_poc.enums.frequency_cap_scope import FrequencyCapScope -# Explicit exports -__all__ = ["FrequencyCapScope"] +class FrequencyCapScope(RootModel[Literal['package']]): + root: Annotated[ + Literal['package'], + Field(description='Scope for frequency cap application', title='Frequency Cap Scope'), + ] = 'package' diff --git a/src/adcp/types/domains/enums/genre_taxonomy.py b/src/adcp/types/domains/enums/genre_taxonomy.py index 768935bba..6b6e10411 100644 --- a/src/adcp/types/domains/enums/genre_taxonomy.py +++ b/src/adcp/types/domains/enums/genre_taxonomy.py @@ -1,18 +1,19 @@ -"""Types declared by the AdCP ``enums/genre_taxonomy`` schema. +# generated by datamodel-codegen: +# filename: enums/genre_taxonomy.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.genre_taxonomy import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.genre_taxonomy import GenreTaxonomy +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["GenreTaxonomy"] +class GenreTaxonomy(StrEnum): + iab_content_3_0 = 'iab_content_3.0' + iab_content_2_2 = 'iab_content_2.2' + gracenote = 'gracenote' + eidr = 'eidr' + apple_genres = 'apple_genres' + google_genres = 'google_genres' + roku = 'roku' + amazon_genres = 'amazon_genres' + custom = 'custom' diff --git a/src/adcp/types/domains/enums/geo_level.py b/src/adcp/types/domains/enums/geo_level.py index cbc45aa6b..59d181519 100644 --- a/src/adcp/types/domains/enums/geo_level.py +++ b/src/adcp/types/domains/enums/geo_level.py @@ -1,18 +1,14 @@ -"""Types declared by the AdCP ``enums/geo_level`` schema. +# generated by datamodel-codegen: +# filename: enums/geo_level.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.geo_level import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.geo_level import GeographicTargetingLevel +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["GeographicTargetingLevel"] +class GeographicTargetingLevel(StrEnum): + country = 'country' + region = 'region' + metro = 'metro' + postal_area = 'postal_area' diff --git a/src/adcp/types/domains/enums/gop_type.py b/src/adcp/types/domains/enums/gop_type.py index 1da4102aa..046f84f52 100644 --- a/src/adcp/types/domains/enums/gop_type.py +++ b/src/adcp/types/domains/enums/gop_type.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``enums/gop_type`` schema. +# generated by datamodel-codegen: +# filename: enums/gop_type.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.gop_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.gop_type import GopType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["GopType"] +class GopType(StrEnum): + closed = 'closed' + open = 'open' diff --git a/src/adcp/types/domains/enums/governance_decision.py b/src/adcp/types/domains/enums/governance_decision.py index 28092c8b3..e514adc93 100644 --- a/src/adcp/types/domains/enums/governance_decision.py +++ b/src/adcp/types/domains/enums/governance_decision.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/governance_decision`` schema. +# generated by datamodel-codegen: +# filename: enums/governance_decision.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.governance_decision import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.governance_decision import GovernanceDecision +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["GovernanceDecision"] +class GovernanceDecision(StrEnum): + approved = 'approved' + denied = 'denied' + conditions = 'conditions' diff --git a/src/adcp/types/domains/enums/governance_domain.py b/src/adcp/types/domains/enums/governance_domain.py index 4b9709910..6ff6fe057 100644 --- a/src/adcp/types/domains/enums/governance_domain.py +++ b/src/adcp/types/domains/enums/governance_domain.py @@ -1,18 +1,14 @@ -"""Types declared by the AdCP ``enums/governance_domain`` schema. +# generated by datamodel-codegen: +# filename: enums/governance_domain.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.governance_domain import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.governance_domain import GovernanceDomain +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["GovernanceDomain"] +class GovernanceDomain(StrEnum): + campaign = 'campaign' + property = 'property' + creative = 'creative' + content_standards = 'content_standards' diff --git a/src/adcp/types/domains/enums/governance_mode.py b/src/adcp/types/domains/enums/governance_mode.py index 74aced641..c10cc406b 100644 --- a/src/adcp/types/domains/enums/governance_mode.py +++ b/src/adcp/types/domains/enums/governance_mode.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/governance_mode`` schema. +# generated by datamodel-codegen: +# filename: enums/governance_mode.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.governance_mode import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.governance_mode import GovernanceMode +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["GovernanceMode"] +class GovernanceMode(StrEnum): + audit = 'audit' + advisory = 'advisory' + enforce = 'enforce' diff --git a/src/adcp/types/domains/enums/governance_phase.py b/src/adcp/types/domains/enums/governance_phase.py index d565944e6..cac3da2ff 100644 --- a/src/adcp/types/domains/enums/governance_phase.py +++ b/src/adcp/types/domains/enums/governance_phase.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/governance_phase`` schema. +# generated by datamodel-codegen: +# filename: enums/governance_phase.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.governance_phase import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.governance_phase import GovernancePhase +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["GovernancePhase"] +class GovernancePhase(StrEnum): + purchase = 'purchase' + modification = 'modification' + delivery = 'delivery' diff --git a/src/adcp/types/domains/enums/history_entry_type.py b/src/adcp/types/domains/enums/history_entry_type.py index 94157a122..54e41d952 100644 --- a/src/adcp/types/domains/enums/history_entry_type.py +++ b/src/adcp/types/domains/enums/history_entry_type.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``enums/history_entry_type`` schema. +# generated by datamodel-codegen: +# filename: enums/history_entry_type.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.history_entry_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.history_entry_type import HistoryEntryType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["HistoryEntryType"] +class HistoryEntryType(StrEnum): + request = 'request' + response = 'response' diff --git a/src/adcp/types/domains/enums/http_method.py b/src/adcp/types/domains/enums/http_method.py index 8712a0611..37703d3e3 100644 --- a/src/adcp/types/domains/enums/http_method.py +++ b/src/adcp/types/domains/enums/http_method.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``enums/http_method`` schema. +# generated by datamodel-codegen: +# filename: enums/http_method.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.http_method import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.http_method import HttpMethod +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["HttpMethod"] +class HttpMethod(StrEnum): + GET = 'GET' + POST = 'POST' diff --git a/src/adcp/types/domains/enums/identifier_types.py b/src/adcp/types/domains/enums/identifier_types.py index 7eac8bd3a..ed39b9bb3 100644 --- a/src/adcp/types/domains/enums/identifier_types.py +++ b/src/adcp/types/domains/enums/identifier_types.py @@ -1,18 +1,31 @@ -"""Types declared by the AdCP ``enums/identifier_types`` schema. +# generated by datamodel-codegen: +# filename: enums/identifier_types.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.identifier_types import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.identifier_types import PropertyIdentifierTypes +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["PropertyIdentifierTypes"] +class PropertyIdentifierTypes(StrEnum): + domain = 'domain' + subdomain = 'subdomain' + network_id = 'network_id' + ios_bundle = 'ios_bundle' + android_package = 'android_package' + apple_app_store_id = 'apple_app_store_id' + google_play_id = 'google_play_id' + roku_store_id = 'roku_store_id' + fire_tv_asin = 'fire_tv_asin' + samsung_app_id = 'samsung_app_id' + apple_tv_bundle = 'apple_tv_bundle' + bundle_id = 'bundle_id' + venue_id = 'venue_id' + screen_id = 'screen_id' + openooh_venue_type = 'openooh_venue_type' + rss_url = 'rss_url' + apple_podcast_id = 'apple_podcast_id' + spotify_collection_id = 'spotify_collection_id' + podcast_guid = 'podcast_guid' + station_id = 'station_id' + facility_id = 'facility_id' diff --git a/src/adcp/types/domains/enums/impairment_offline_state.py b/src/adcp/types/domains/enums/impairment_offline_state.py index 9bdab3cc0..68af7bd2b 100644 --- a/src/adcp/types/domains/enums/impairment_offline_state.py +++ b/src/adcp/types/domains/enums/impairment_offline_state.py @@ -1,18 +1,15 @@ -"""Types declared by the AdCP ``enums/impairment_offline_state`` schema. +# generated by datamodel-codegen: +# filename: enums/impairment_offline_state.json +# timestamp: 2026-05-22T13:15:12+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.impairment_offline_state import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.impairment_offline_state import ImpairmentOfflineState +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["ImpairmentOfflineState"] +class ImpairmentOfflineState(StrEnum): + suspended = 'suspended' + rejected = 'rejected' + withdrawn = 'withdrawn' + insufficient = 'insufficient' + depublished = 'depublished' diff --git a/src/adcp/types/domains/enums/impairment_reason_code.py b/src/adcp/types/domains/enums/impairment_reason_code.py index 07c516050..a0127595a 100644 --- a/src/adcp/types/domains/enums/impairment_reason_code.py +++ b/src/adcp/types/domains/enums/impairment_reason_code.py @@ -1,18 +1,21 @@ -"""Types declared by the AdCP ``enums/impairment_reason_code`` schema. +# generated by datamodel-codegen: +# filename: enums/impairment_reason_code.json +# timestamp: 2026-06-04T19:44:00+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.impairment_reason_code import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.impairment_reason_code import ImpairmentReasonCode +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["ImpairmentReasonCode"] +class ImpairmentReasonCode(StrEnum): + policy_violation = 'policy_violation' + consent_expired = 'consent_expired' + ttl_expired = 'ttl_expired' + pii_audit_failed = 'pii_audit_failed' + seller_removed = 'seller_removed' + content_rejected = 'content_rejected' + identity_authorization_revoked = 'identity_authorization_revoked' + identity_authorization_expired = 'identity_authorization_expired' + source_private = 'source_private' + source_offline = 'source_offline' + property_depublished = 'property_depublished' diff --git a/src/adcp/types/domains/enums/indicator_type.py b/src/adcp/types/domains/enums/indicator_type.py index 3ab658a57..3d224cd44 100644 --- a/src/adcp/types/domains/enums/indicator_type.py +++ b/src/adcp/types/domains/enums/indicator_type.py @@ -1,18 +1,17 @@ -"""Types declared by the AdCP ``enums/indicator_type`` schema. +# generated by datamodel-codegen: +# filename: enums/indicator_type.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.indicator_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.indicator_type import IndicatorType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["IndicatorType"] +class IndicatorType(StrEnum): + creative_fatigue = 'creative_fatigue' + creative_quality_opportunity = 'creative_quality_opportunity' + creative_diversity_low = 'creative_diversity_low' + audience_saturation = 'audience_saturation' + inventory_shortfall_forecast = 'inventory_shortfall_forecast' + pacing_risk = 'pacing_risk' + budget_constrained = 'budget_constrained' diff --git a/src/adcp/types/domains/enums/installment_status.py b/src/adcp/types/domains/enums/installment_status.py index a4bfc5835..86a59a009 100644 --- a/src/adcp/types/domains/enums/installment_status.py +++ b/src/adcp/types/domains/enums/installment_status.py @@ -1,18 +1,17 @@ -"""Types declared by the AdCP ``enums/installment_status`` schema. +# generated by datamodel-codegen: +# filename: enums/installment_status.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.installment_status import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.installment_status import InstallmentStatus +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["InstallmentStatus"] +class InstallmentStatus(StrEnum): + scheduled = 'scheduled' + tentative = 'tentative' + live = 'live' + postponed = 'postponed' + cancelled = 'cancelled' + aired = 'aired' + published = 'published' diff --git a/src/adcp/types/domains/enums/javascript_module_type.py b/src/adcp/types/domains/enums/javascript_module_type.py index f0de25e14..54cc6b94a 100644 --- a/src/adcp/types/domains/enums/javascript_module_type.py +++ b/src/adcp/types/domains/enums/javascript_module_type.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/javascript_module_type`` schema. +# generated by datamodel-codegen: +# filename: enums/javascript_module_type.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.javascript_module_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.javascript_module_type import JavascriptModuleType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["JavascriptModuleType"] +class JavascriptModuleType(StrEnum): + esm = 'esm' + commonjs = 'commonjs' + script = 'script' diff --git a/src/adcp/types/domains/enums/landing_page_requirement.py b/src/adcp/types/domains/enums/landing_page_requirement.py index d7d8705c8..f04267a0b 100644 --- a/src/adcp/types/domains/enums/landing_page_requirement.py +++ b/src/adcp/types/domains/enums/landing_page_requirement.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/landing_page_requirement`` schema. +# generated by datamodel-codegen: +# filename: enums/landing_page_requirement.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.landing_page_requirement import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.landing_page_requirement import LandingPageRequirement +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["LandingPageRequirement"] +class LandingPageRequirement(StrEnum): + any = 'any' + retailer_site_only = 'retailer_site_only' + must_include_retailer = 'must_include_retailer' diff --git a/src/adcp/types/domains/enums/legacy_postal_system.py b/src/adcp/types/domains/enums/legacy_postal_system.py index 52dda3d89..441f3cd49 100644 --- a/src/adcp/types/domains/enums/legacy_postal_system.py +++ b/src/adcp/types/domains/enums/legacy_postal_system.py @@ -1,18 +1,21 @@ -"""Types declared by the AdCP ``enums/legacy_postal_system`` schema. +# generated by datamodel-codegen: +# filename: enums/legacy_postal_system.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.legacy_postal_system import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.legacy_postal_system import CountryFusedPostalCodeSystem +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["CountryFusedPostalCodeSystem"] +class CountryFusedPostalCodeSystem(StrEnum): + us_zip = 'us_zip' + us_zip_plus_four = 'us_zip_plus_four' + gb_outward = 'gb_outward' + gb_full = 'gb_full' + ca_fsa = 'ca_fsa' + ca_full = 'ca_full' + de_plz = 'de_plz' + fr_code_postal = 'fr_code_postal' + au_postcode = 'au_postcode' + ch_plz = 'ch_plz' + at_plz = 'at_plz' diff --git a/src/adcp/types/domains/enums/lift_dimension.py b/src/adcp/types/domains/enums/lift_dimension.py index 3699a154c..dc261a6bc 100644 --- a/src/adcp/types/domains/enums/lift_dimension.py +++ b/src/adcp/types/domains/enums/lift_dimension.py @@ -1,18 +1,15 @@ -"""Types declared by the AdCP ``enums/lift_dimension`` schema. +# generated by datamodel-codegen: +# filename: enums/lift_dimension.json +# timestamp: 2026-05-22T13:15:12+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.lift_dimension import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.lift_dimension import LiftDimension +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["LiftDimension"] +class LiftDimension(StrEnum): + awareness = 'awareness' + consideration = 'consideration' + favorability = 'favorability' + purchase_intent = 'purchase_intent' + ad_recall = 'ad_recall' diff --git a/src/adcp/types/domains/enums/logo_slot.py b/src/adcp/types/domains/enums/logo_slot.py index cda9caacd..3a0eb2444 100644 --- a/src/adcp/types/domains/enums/logo_slot.py +++ b/src/adcp/types/domains/enums/logo_slot.py @@ -1,18 +1,23 @@ -"""Types declared by the AdCP ``enums/logo_slot`` schema. +# generated by datamodel-codegen: +# filename: enums/logo_slot.json +# timestamp: 2026-05-29T23:56:01+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.logo_slot import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.logo_slot import LogoSlot +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["LogoSlot"] +class LogoSlot(StrEnum): + logo_card_light = 'logo_card_light' + logo_card_dark = 'logo_card_dark' + profile_mark = 'profile_mark' + favicon = 'favicon' + app_icon = 'app_icon' + social_profile_mark = 'social_profile_mark' + nav_header = 'nav_header' + footer = 'footer' + email_header = 'email_header' + watermark = 'watermark' + ad_end_card = 'ad_end_card' + co_brand_lockup = 'co_brand_lockup' + marketplace_listing = 'marketplace_listing' diff --git a/src/adcp/types/domains/enums/macro_dialect.py b/src/adcp/types/domains/enums/macro_dialect.py index 84df51fa1..073f3a429 100644 --- a/src/adcp/types/domains/enums/macro_dialect.py +++ b/src/adcp/types/domains/enums/macro_dialect.py @@ -1,18 +1,15 @@ -"""Types declared by the AdCP ``enums/macro_dialect`` schema. +# generated by datamodel-codegen: +# filename: enums/macro_dialect.json +# timestamp: 2026-08-28T20:03:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.macro_dialect import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.macro_dialect import MacroDialectFamily +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["MacroDialectFamily"] +class MacroDialectFamily(StrEnum): + adcp = 'adcp' + iab_vast = 'iab_vast' + iab_daast = 'iab_daast' + vendor = 'vendor' + unknown = 'unknown' diff --git a/src/adcp/types/domains/enums/macro_mapping_status.py b/src/adcp/types/domains/enums/macro_mapping_status.py index 8d51ec5ba..e07a5f952 100644 --- a/src/adcp/types/domains/enums/macro_mapping_status.py +++ b/src/adcp/types/domains/enums/macro_mapping_status.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/macro_mapping_status`` schema. +# generated by datamodel-codegen: +# filename: enums/macro_mapping_status.json +# timestamp: 2026-08-28T20:03:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.macro_mapping_status import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.macro_mapping_status import MacroMappingStatus +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["MacroMappingStatus"] +class MacroMappingStatus(StrEnum): + verified_universal = 'verified_universal' + dialect_defined = 'dialect_defined' + unresolved = 'unresolved' diff --git a/src/adcp/types/domains/enums/macro_processing_operation.py b/src/adcp/types/domains/enums/macro_processing_operation.py index d9c7a3d3f..2751d9928 100644 --- a/src/adcp/types/domains/enums/macro_processing_operation.py +++ b/src/adcp/types/domains/enums/macro_processing_operation.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/macro_processing_operation`` schema. +# generated by datamodel-codegen: +# filename: enums/macro_processing_operation.json +# timestamp: 2026-08-28T20:03:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.macro_processing_operation import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.macro_processing_operation import MacroProcessingOperation +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["MacroProcessingOperation"] +class MacroProcessingOperation(StrEnum): + translate_to_native = 'translate_to_native' + resolve_value = 'resolve_value' + preserve = 'preserve' diff --git a/src/adcp/types/domains/enums/macro_resolution_reason.py b/src/adcp/types/domains/enums/macro_resolution_reason.py index 0460dc9cc..0c642fbac 100644 --- a/src/adcp/types/domains/enums/macro_resolution_reason.py +++ b/src/adcp/types/domains/enums/macro_resolution_reason.py @@ -1,18 +1,22 @@ -"""Types declared by the AdCP ``enums/macro_resolution_reason`` schema. +# generated by datamodel-codegen: +# filename: enums/macro_resolution_reason.json +# timestamp: 2026-08-28T20:03:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.macro_resolution_reason import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.macro_resolution_reason import MacroResolutionReason +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["MacroResolutionReason"] +class MacroResolutionReason(StrEnum): + capability_match = 'capability_match' + preserved_unknown = 'preserved_unknown' + preserved_for_downstream = 'preserved_for_downstream' + dialect_unsupported = 'dialect_unsupported' + namespace_mismatch = 'namespace_mismatch' + revision_mismatch = 'revision_mismatch' + semantic_unsupported = 'semantic_unsupported' + operation_unsupported = 'operation_unsupported' + resolver_mismatch = 'resolver_mismatch' + context_unsupported = 'context_unsupported' + encoding_unsupported = 'encoding_unsupported' + ambiguous_mapping = 'ambiguous_mapping' diff --git a/src/adcp/types/domains/enums/macro_resolver.py b/src/adcp/types/domains/enums/macro_resolver.py index b601fd709..eecdd661a 100644 --- a/src/adcp/types/domains/enums/macro_resolver.py +++ b/src/adcp/types/domains/enums/macro_resolver.py @@ -1,18 +1,15 @@ -"""Types declared by the AdCP ``enums/macro_resolver`` schema. +# generated by datamodel-codegen: +# filename: enums/macro_resolver.json +# timestamp: 2026-08-28T20:03:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.macro_resolver import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.macro_resolver import MacroProcessingActor +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["MacroProcessingActor"] +class MacroProcessingActor(StrEnum): + buyer = 'buyer' + creative_agent = 'creative_agent' + seller = 'seller' + request_executor = 'request_executor' + source_ad_server = 'source_ad_server' diff --git a/src/adcp/types/domains/enums/macro_value_context.py b/src/adcp/types/domains/enums/macro_value_context.py index 76b7011d5..ae277cad7 100644 --- a/src/adcp/types/domains/enums/macro_value_context.py +++ b/src/adcp/types/domains/enums/macro_value_context.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/macro_value_context`` schema. +# generated by datamodel-codegen: +# filename: enums/macro_value_context.json +# timestamp: 2026-08-28T20:03:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.macro_value_context import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.macro_value_context import MacroValueContext +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["MacroValueContext"] +class MacroValueContext(StrEnum): + url_query_value = 'url_query_value' + url_path_segment = 'url_path_segment' + opaque = 'opaque' diff --git a/src/adcp/types/domains/enums/makegood_remedy.py b/src/adcp/types/domains/enums/makegood_remedy.py index 7a988b59f..a001cf603 100644 --- a/src/adcp/types/domains/enums/makegood_remedy.py +++ b/src/adcp/types/domains/enums/makegood_remedy.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/makegood_remedy`` schema. +# generated by datamodel-codegen: +# filename: enums/makegood_remedy.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.makegood_remedy import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.makegood_remedy import MakegoodRemedy +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["MakegoodRemedy"] +class MakegoodRemedy(StrEnum): + additional_delivery = 'additional_delivery' + credit = 'credit' + invoice_adjustment = 'invoice_adjustment' diff --git a/src/adcp/types/domains/enums/markdown_flavor.py b/src/adcp/types/domains/enums/markdown_flavor.py index cb67046a2..be75bdad4 100644 --- a/src/adcp/types/domains/enums/markdown_flavor.py +++ b/src/adcp/types/domains/enums/markdown_flavor.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``enums/markdown_flavor`` schema. +# generated by datamodel-codegen: +# filename: enums/markdown_flavor.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.markdown_flavor import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.markdown_flavor import MarkdownFlavor +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["MarkdownFlavor"] +class MarkdownFlavor(StrEnum): + commonmark = 'commonmark' + gfm = 'gfm' diff --git a/src/adcp/types/domains/enums/match_id_type.py b/src/adcp/types/domains/enums/match_id_type.py index ed76269aa..d5afd91cd 100644 --- a/src/adcp/types/domains/enums/match_id_type.py +++ b/src/adcp/types/domains/enums/match_id_type.py @@ -1,18 +1,19 @@ -"""Types declared by the AdCP ``enums/match_id_type`` schema. +# generated by datamodel-codegen: +# filename: enums/match_id_type.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.match_id_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.match_id_type import MatchIdType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["MatchIdType"] +class MatchIdType(StrEnum): + hashed_email = 'hashed_email' + hashed_phone = 'hashed_phone' + rampid = 'rampid' + id5 = 'id5' + uid2 = 'uid2' + euid = 'euid' + pairid = 'pairid' + maid = 'maid' + other = 'other' diff --git a/src/adcp/types/domains/enums/match_type.py b/src/adcp/types/domains/enums/match_type.py index a9a7bcfbf..7ced75155 100644 --- a/src/adcp/types/domains/enums/match_type.py +++ b/src/adcp/types/domains/enums/match_type.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/match_type`` schema. +# generated by datamodel-codegen: +# filename: enums/match_type.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.match_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.match_type import MatchType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["MatchType"] +class MatchType(StrEnum): + broad = 'broad' + phrase = 'phrase' + exact = 'exact' diff --git a/src/adcp/types/domains/enums/media_buy_action_mode.py b/src/adcp/types/domains/enums/media_buy_action_mode.py index 69298b425..cf2916d44 100644 --- a/src/adcp/types/domains/enums/media_buy_action_mode.py +++ b/src/adcp/types/domains/enums/media_buy_action_mode.py @@ -1,18 +1,14 @@ -"""Types declared by the AdCP ``enums/media_buy_action_mode`` schema. +# generated by datamodel-codegen: +# filename: enums/media_buy_action_mode.json +# timestamp: 2026-08-28T20:03:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.media_buy_action_mode import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.media_buy_action_mode import MediaBuyActionMode +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["MediaBuyActionMode"] +class MediaBuyActionMode(StrEnum): + self_serve = 'self_serve' + conditional_self_serve = 'conditional_self_serve' + seller_managed = 'seller_managed' + requires_approval = 'requires_approval' diff --git a/src/adcp/types/domains/enums/media_buy_frequency_cap_control_mode.py b/src/adcp/types/domains/enums/media_buy_frequency_cap_control_mode.py index e3d85bbeb..3947843bb 100644 --- a/src/adcp/types/domains/enums/media_buy_frequency_cap_control_mode.py +++ b/src/adcp/types/domains/enums/media_buy_frequency_cap_control_mode.py @@ -1,20 +1,19 @@ -"""Types declared by the AdCP ``enums/media_buy_frequency_cap_control_mode`` schema. +# generated by datamodel-codegen: +# filename: enums/media_buy_frequency_cap_control_mode.json +# timestamp: 2026-09-14T14:16:02+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.media_buy_frequency_cap_control_mode import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import Field, RootModel -from adcp.types.generated_poc.enums.media_buy_frequency_cap_control_mode import ( - MediaBuyFrequencyCapControlMode, -) -# Explicit exports -__all__ = ["MediaBuyFrequencyCapControlMode"] +class MediaBuyFrequencyCapControlMode(RootModel[Literal['max_impressions']]): + root: Annotated[ + Literal['max_impressions'], + Field( + description='The aggregate MediaBuy frequency-cap control shape supported in 3.2.', + title='Media Buy Frequency Cap Control Mode', + ), + ] = 'max_impressions' diff --git a/src/adcp/types/domains/enums/media_buy_health.py b/src/adcp/types/domains/enums/media_buy_health.py index f250cc6c2..15777e585 100644 --- a/src/adcp/types/domains/enums/media_buy_health.py +++ b/src/adcp/types/domains/enums/media_buy_health.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``enums/media_buy_health`` schema. +# generated by datamodel-codegen: +# filename: enums/media_buy_health.json +# timestamp: 2026-05-22T13:15:12+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.media_buy_health import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.media_buy_health import MediaBuyHealth +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["MediaBuyHealth"] +class MediaBuyHealth(StrEnum): + ok = 'ok' + impaired = 'impaired' diff --git a/src/adcp/types/domains/enums/media_buy_status.py b/src/adcp/types/domains/enums/media_buy_status.py index 97eed9a32..d6acb6292 100644 --- a/src/adcp/types/domains/enums/media_buy_status.py +++ b/src/adcp/types/domains/enums/media_buy_status.py @@ -1,18 +1,17 @@ -"""Types declared by the AdCP ``enums/media_buy_status`` schema. +# generated by datamodel-codegen: +# filename: enums/media_buy_status.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.media_buy_status import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.media_buy_status import MediaBuyStatus +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["MediaBuyStatus"] +class MediaBuyStatus(StrEnum): + pending_creatives = 'pending_creatives' + pending_start = 'pending_start' + active = 'active' + paused = 'paused' + completed = 'completed' + rejected = 'rejected' + canceled = 'canceled' diff --git a/src/adcp/types/domains/enums/media_buy_valid_action.py b/src/adcp/types/domains/enums/media_buy_valid_action.py index d2d4ede4e..6de1c892b 100644 --- a/src/adcp/types/domains/enums/media_buy_valid_action.py +++ b/src/adcp/types/domains/enums/media_buy_valid_action.py @@ -1,18 +1,34 @@ -"""Types declared by the AdCP ``enums/media_buy_valid_action`` schema. +# generated by datamodel-codegen: +# filename: enums/media_buy_valid_action.json +# timestamp: 2026-08-21T04:35:18+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.media_buy_valid_action import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.media_buy_valid_action import MediaBuyValidAction +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["MediaBuyValidAction"] +class MediaBuyValidAction(StrEnum): + pause = 'pause' + resume = 'resume' + cancel = 'cancel' + update_name = 'update_name' + extend_flight = 'extend_flight' + shorten_flight = 'shorten_flight' + update_flight_dates = 'update_flight_dates' + increase_budget = 'increase_budget' + decrease_budget = 'decrease_budget' + reallocate_budget = 'reallocate_budget' + update_budget_allocation = 'update_budget_allocation' + update_targeting = 'update_targeting' + update_pacing = 'update_pacing' + update_bidding = 'update_bidding' + update_frequency_caps = 'update_frequency_caps' + replace_creative = 'replace_creative' + update_creative_assignments = 'update_creative_assignments' + remove_creative = 'remove_creative' + add_packages = 'add_packages' + remove_packages = 'remove_packages' + update_budget = 'update_budget' + update_dates = 'update_dates' + update_packages = 'update_packages' + sync_creatives = 'sync_creatives' diff --git a/src/adcp/types/domains/enums/metric_scope.py b/src/adcp/types/domains/enums/metric_scope.py index 72a3ef1ad..4e42b951e 100644 --- a/src/adcp/types/domains/enums/metric_scope.py +++ b/src/adcp/types/domains/enums/metric_scope.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``enums/metric_scope`` schema. +# generated by datamodel-codegen: +# filename: enums/metric_scope.json +# timestamp: 2026-05-22T13:15:12+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.metric_scope import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.metric_scope import MetricScope +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["MetricScope"] +class MetricScope(StrEnum): + standard = 'standard' + vendor = 'vendor' diff --git a/src/adcp/types/domains/enums/metric_type.py b/src/adcp/types/domains/enums/metric_type.py index 0b64915be..13b275fe5 100644 --- a/src/adcp/types/domains/enums/metric_type.py +++ b/src/adcp/types/domains/enums/metric_type.py @@ -1,18 +1,18 @@ -"""Types declared by the AdCP ``enums/metric_type`` schema. +# generated by datamodel-codegen: +# filename: enums/metric_type.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.metric_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.metric_type import MetricType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["MetricType"] +class MetricType(StrEnum): + overall_performance = 'overall_performance' + conversion_rate = 'conversion_rate' + brand_lift = 'brand_lift' + click_through_rate = 'click_through_rate' + completion_rate = 'completion_rate' + viewability = 'viewability' + brand_safety = 'brand_safety' + cost_efficiency = 'cost_efficiency' diff --git a/src/adcp/types/domains/enums/metro_system.py b/src/adcp/types/domains/enums/metro_system.py index 0ab65b3ec..3fbfb4889 100644 --- a/src/adcp/types/domains/enums/metro_system.py +++ b/src/adcp/types/domains/enums/metro_system.py @@ -1,18 +1,15 @@ -"""Types declared by the AdCP ``enums/metro_system`` schema. +# generated by datamodel-codegen: +# filename: enums/metro_system.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.metro_system import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.metro_system import MetroAreaSystem +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["MetroAreaSystem"] +class MetroAreaSystem(StrEnum): + nielsen_dma = 'nielsen_dma' + uk_itl1 = 'uk_itl1' + uk_itl2 = 'uk_itl2' + eurostat_nuts2 = 'eurostat_nuts2' + custom = 'custom' diff --git a/src/adcp/types/domains/enums/moov_atom_position.py b/src/adcp/types/domains/enums/moov_atom_position.py index 94fe41a70..aedc3e89b 100644 --- a/src/adcp/types/domains/enums/moov_atom_position.py +++ b/src/adcp/types/domains/enums/moov_atom_position.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``enums/moov_atom_position`` schema. +# generated by datamodel-codegen: +# filename: enums/moov_atom_position.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.moov_atom_position import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.moov_atom_position import MoovAtomPosition +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["MoovAtomPosition"] +class MoovAtomPosition(StrEnum): + start = 'start' + end = 'end' diff --git a/src/adcp/types/domains/enums/motion_level.py b/src/adcp/types/domains/enums/motion_level.py index e047f6be6..2e7595e3a 100644 --- a/src/adcp/types/domains/enums/motion_level.py +++ b/src/adcp/types/domains/enums/motion_level.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/motion_level`` schema. +# generated by datamodel-codegen: +# filename: enums/motion_level.json +# timestamp: 2026-08-24T04:35:32+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.motion_level import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.motion_level import CreativeMotionLevel +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["CreativeMotionLevel"] +class CreativeMotionLevel(StrEnum): + static = 'static' + limited_motion = 'limited_motion' + full_motion = 'full_motion' diff --git a/src/adcp/types/domains/enums/notification_type.py b/src/adcp/types/domains/enums/notification_type.py index a42c7915a..e3cd266b2 100644 --- a/src/adcp/types/domains/enums/notification_type.py +++ b/src/adcp/types/domains/enums/notification_type.py @@ -1,18 +1,36 @@ -"""Types declared by the AdCP ``enums/notification_type`` schema. +# generated by datamodel-codegen: +# filename: enums/notification_type.json +# timestamp: 2026-09-05T11:09:57+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.notification_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.notification_type import NotificationType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["NotificationType"] +class NotificationType(StrEnum): + scheduled = 'scheduled' + final = 'final' + delayed = 'delayed' + adjusted = 'adjusted' + window_update = 'window_update' + impairment = 'impairment' + creative_status_changed = 'creative.status_changed' + creative_assignment_changed = 'creative.assignment_changed' + indicators_changed = 'indicators.changed' + creative_purged = 'creative.purged' + account_status_changed = 'account.status_changed' + account_change_recorded = 'account.change_recorded' + product_created = 'product.created' + product_updated = 'product.updated' + product_priced = 'product.priced' + product_removed = 'product.removed' + signal_created = 'signal.created' + signal_updated = 'signal.updated' + signal_priced = 'signal.priced' + signal_removed = 'signal.removed' + wholesale_feed_bulk_change = 'wholesale_feed.bulk_change' + capabilities_changed = 'capabilities.changed' + reporting_delivery_ready = 'reporting.delivery_ready' + reporting_status_changed = 'reporting.status_changed' + reporting_ledger_changed = 'reporting.ledger_changed' + principal_changed = 'principal.changed' diff --git a/src/adcp/types/domains/enums/offering_availability_status.py b/src/adcp/types/domains/enums/offering_availability_status.py index accab9bcd..205d00dbc 100644 --- a/src/adcp/types/domains/enums/offering_availability_status.py +++ b/src/adcp/types/domains/enums/offering_availability_status.py @@ -1,18 +1,16 @@ -"""Types declared by the AdCP ``enums/offering_availability_status`` schema. +# generated by datamodel-codegen: +# filename: enums/offering_availability_status.json +# timestamp: 2026-06-04T19:44:00+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.offering_availability_status import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.offering_availability_status import OfferingAvailabilityStatus +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["OfferingAvailabilityStatus"] +class OfferingAvailabilityStatus(StrEnum): + available = 'available' + limited = 'limited' + sold_out = 'sold_out' + expired = 'expired' + region_restricted = 'region_restricted' + inactive = 'inactive' diff --git a/src/adcp/types/domains/enums/outcome_target_cost_strength.py b/src/adcp/types/domains/enums/outcome_target_cost_strength.py index 4c8218c2d..0af7a9c4a 100644 --- a/src/adcp/types/domains/enums/outcome_target_cost_strength.py +++ b/src/adcp/types/domains/enums/outcome_target_cost_strength.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``enums/outcome_target_cost_strength`` schema. +# generated by datamodel-codegen: +# filename: enums/outcome_target_cost_strength.json +# timestamp: 2026-10-01T07:57:26+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.outcome_target_cost_strength import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.outcome_target_cost_strength import OutcomeTargetCostStrength +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["OutcomeTargetCostStrength"] +class OutcomeTargetCostStrength(StrEnum): + cap = 'cap' + target = 'target' diff --git a/src/adcp/types/domains/enums/outcome_type.py b/src/adcp/types/domains/enums/outcome_type.py index d3707910f..d109bfc00 100644 --- a/src/adcp/types/domains/enums/outcome_type.py +++ b/src/adcp/types/domains/enums/outcome_type.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/outcome_type`` schema. +# generated by datamodel-codegen: +# filename: enums/outcome_type.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.outcome_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.outcome_type import OutcomeType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["OutcomeType"] +class OutcomeType(StrEnum): + completed = 'completed' + failed = 'failed' + delivery = 'delivery' diff --git a/src/adcp/types/domains/enums/pacing.py b/src/adcp/types/domains/enums/pacing.py index 37ffc7634..36aac63f6 100644 --- a/src/adcp/types/domains/enums/pacing.py +++ b/src/adcp/types/domains/enums/pacing.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/pacing`` schema. +# generated by datamodel-codegen: +# filename: enums/pacing.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.pacing import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.pacing import Pacing +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["Pacing"] +class Pacing(StrEnum): + even = 'even' + asap = 'asap' + front_loaded = 'front_loaded' diff --git a/src/adcp/types/domains/enums/payment_terms.py b/src/adcp/types/domains/enums/payment_terms.py index fbb4983e2..c5f80d72f 100644 --- a/src/adcp/types/domains/enums/payment_terms.py +++ b/src/adcp/types/domains/enums/payment_terms.py @@ -1,18 +1,16 @@ -"""Types declared by the AdCP ``enums/payment_terms`` schema. +# generated by datamodel-codegen: +# filename: enums/payment_terms.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.payment_terms import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.payment_terms import PaymentTerms +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["PaymentTerms"] +class PaymentTerms(StrEnum): + net_15 = 'net_15' + net_30 = 'net_30' + net_45 = 'net_45' + net_60 = 'net_60' + net_90 = 'net_90' + prepay = 'prepay' diff --git a/src/adcp/types/domains/enums/performance_baseline.py b/src/adcp/types/domains/enums/performance_baseline.py index 6522e1d6e..1c1ac0ed5 100644 --- a/src/adcp/types/domains/enums/performance_baseline.py +++ b/src/adcp/types/domains/enums/performance_baseline.py @@ -1,18 +1,16 @@ -"""Types declared by the AdCP ``enums/performance_baseline`` schema. +# generated by datamodel-codegen: +# filename: enums/performance_baseline.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.performance_baseline import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.performance_baseline import PerformanceBaseline +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["PerformanceBaseline"] +class PerformanceBaseline(StrEnum): + campaign_target = 'campaign_target' + control_group = 'control_group' + seller_history = 'seller_history' + buyer_portfolio = 'buyer_portfolio' + market_benchmark = 'market_benchmark' + other = 'other' diff --git a/src/adcp/types/domains/enums/performance_standard_metric.py b/src/adcp/types/domains/enums/performance_standard_metric.py index 90b1624dd..f111a9130 100644 --- a/src/adcp/types/domains/enums/performance_standard_metric.py +++ b/src/adcp/types/domains/enums/performance_standard_metric.py @@ -1,18 +1,15 @@ -"""Types declared by the AdCP ``enums/performance_standard_metric`` schema. +# generated by datamodel-codegen: +# filename: enums/performance_standard_metric.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.performance_standard_metric import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.performance_standard_metric import PerformanceStandardMetric +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["PerformanceStandardMetric"] +class PerformanceStandardMetric(StrEnum): + viewability = 'viewability' + ivt = 'ivt' + completion_rate = 'completion_rate' + brand_safety = 'brand_safety' + attention_score = 'attention_score' diff --git a/src/adcp/types/domains/enums/pixel_tracking_event.py b/src/adcp/types/domains/enums/pixel_tracking_event.py index 5644c03d6..75933b160 100644 --- a/src/adcp/types/domains/enums/pixel_tracking_event.py +++ b/src/adcp/types/domains/enums/pixel_tracking_event.py @@ -1,18 +1,17 @@ -"""Types declared by the AdCP ``enums/pixel_tracking_event`` schema. +# generated by datamodel-codegen: +# filename: enums/pixel_tracking_event.json +# timestamp: 2026-08-28T20:03:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.pixel_tracking_event import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.pixel_tracking_event import PixelTrackingEvent +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["PixelTrackingEvent"] +class PixelTrackingEvent(StrEnum): + impression = 'impression' + viewable_mrc_50 = 'viewable_mrc_50' + viewable_mrc_100 = 'viewable_mrc_100' + viewable_video_50 = 'viewable_video_50' + audible_video_complete = 'audible_video_complete' + click = 'click' + custom = 'custom' diff --git a/src/adcp/types/domains/enums/policy_category.py b/src/adcp/types/domains/enums/policy_category.py index d29565ea6..41c116f65 100644 --- a/src/adcp/types/domains/enums/policy_category.py +++ b/src/adcp/types/domains/enums/policy_category.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``enums/policy_category`` schema. +# generated by datamodel-codegen: +# filename: enums/policy_category.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.policy_category import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.policy_category import PolicyCategory +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["PolicyCategory"] +class PolicyCategory(StrEnum): + regulation = 'regulation' + standard = 'standard' diff --git a/src/adcp/types/domains/enums/policy_enforcement.py b/src/adcp/types/domains/enums/policy_enforcement.py index ba0f58d25..c1078b37d 100644 --- a/src/adcp/types/domains/enums/policy_enforcement.py +++ b/src/adcp/types/domains/enums/policy_enforcement.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/policy_enforcement`` schema. +# generated by datamodel-codegen: +# filename: enums/policy_enforcement.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.policy_enforcement import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.policy_enforcement import PolicyEnforcementLevel +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["PolicyEnforcementLevel"] +class PolicyEnforcementLevel(StrEnum): + must = 'must' + should = 'should' + may = 'may' diff --git a/src/adcp/types/domains/enums/postal_system.py b/src/adcp/types/domains/enums/postal_system.py index 99ce5c8bb..27cbd459f 100644 --- a/src/adcp/types/domains/enums/postal_system.py +++ b/src/adcp/types/domains/enums/postal_system.py @@ -1,18 +1,33 @@ -"""Types declared by the AdCP ``enums/postal_system`` schema. +# generated by datamodel-codegen: +# filename: enums/postal_system.json +# timestamp: 2026-06-07T22:46:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.postal_system import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.postal_system import PostalCodeSystem +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["PostalCodeSystem"] +class PostalCodeSystem(StrEnum): + postal_code = 'postal_code' + zip = 'zip' + zip_plus_four = 'zip_plus_four' + outward = 'outward' + full = 'full' + fsa = 'fsa' + plz = 'plz' + code_postal = 'code_postal' + postcode = 'postcode' + cep = 'cep' + pin = 'pin' + custom = 'custom' + us_zip = 'us_zip' + us_zip_plus_four = 'us_zip_plus_four' + gb_outward = 'gb_outward' + gb_full = 'gb_full' + ca_fsa = 'ca_fsa' + ca_full = 'ca_full' + de_plz = 'de_plz' + fr_code_postal = 'fr_code_postal' + au_postcode = 'au_postcode' + ch_plz = 'ch_plz' + at_plz = 'at_plz' diff --git a/src/adcp/types/domains/enums/preview_output_format.py b/src/adcp/types/domains/enums/preview_output_format.py index 0ffac980e..b1095e5e2 100644 --- a/src/adcp/types/domains/enums/preview_output_format.py +++ b/src/adcp/types/domains/enums/preview_output_format.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``enums/preview_output_format`` schema. +# generated by datamodel-codegen: +# filename: enums/preview_output_format.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.preview_output_format import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.preview_output_format import PreviewOutputFormat +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["PreviewOutputFormat"] +class PreviewOutputFormat(StrEnum): + url = 'url' + html = 'html' diff --git a/src/adcp/types/domains/enums/pricing_model.py b/src/adcp/types/domains/enums/pricing_model.py index a7cdf1759..62c33a56b 100644 --- a/src/adcp/types/domains/enums/pricing_model.py +++ b/src/adcp/types/domains/enums/pricing_model.py @@ -1,18 +1,20 @@ -"""Types declared by the AdCP ``enums/pricing_model`` schema. +# generated by datamodel-codegen: +# filename: enums/pricing_model.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.pricing_model import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.pricing_model import PricingModel +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["PricingModel"] +class PricingModel(StrEnum): + cpm = 'cpm' + vcpm = 'vcpm' + cpc = 'cpc' + cpcv = 'cpcv' + cpv = 'cpv' + cpp = 'cpp' + cpa = 'cpa' + revenue_share = 'revenue_share' + flat_rate = 'flat_rate' + time = 'time' diff --git a/src/adcp/types/domains/enums/pricing_structure.py b/src/adcp/types/domains/enums/pricing_structure.py index 6682330d1..a77aecf79 100644 --- a/src/adcp/types/domains/enums/pricing_structure.py +++ b/src/adcp/types/domains/enums/pricing_structure.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/pricing_structure`` schema. +# generated by datamodel-codegen: +# filename: enums/pricing_structure.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.pricing_structure import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.pricing_structure import PricingStructure +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["PricingStructure"] +class PricingStructure(StrEnum): + fixed = 'fixed' + auction = 'auction' + contingent = 'contingent' diff --git a/src/adcp/types/domains/enums/principal_kind.py b/src/adcp/types/domains/enums/principal_kind.py index 15067e831..571ebec33 100644 --- a/src/adcp/types/domains/enums/principal_kind.py +++ b/src/adcp/types/domains/enums/principal_kind.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``enums/principal_kind`` schema. +# generated by datamodel-codegen: +# filename: enums/principal_kind.json +# timestamp: 2026-09-01T19:37:00+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.principal_kind import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.principal_kind import PrincipalKind +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["PrincipalKind"] +class PrincipalKind(StrEnum): + buyer_agent = 'buyer_agent' + operator = 'operator' diff --git a/src/adcp/types/domains/enums/production_quality.py b/src/adcp/types/domains/enums/production_quality.py index b83bc34ea..31c74a261 100644 --- a/src/adcp/types/domains/enums/production_quality.py +++ b/src/adcp/types/domains/enums/production_quality.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/production_quality`` schema. +# generated by datamodel-codegen: +# filename: enums/production_quality.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.production_quality import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.production_quality import ProductionQuality +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["ProductionQuality"] +class ProductionQuality(StrEnum): + professional = 'professional' + prosumer = 'prosumer' + ugc = 'ugc' diff --git a/src/adcp/types/domains/enums/property_type.py b/src/adcp/types/domains/enums/property_type.py index cd0e320da..f15bd4d77 100644 --- a/src/adcp/types/domains/enums/property_type.py +++ b/src/adcp/types/domains/enums/property_type.py @@ -1,18 +1,20 @@ -"""Types declared by the AdCP ``enums/property_type`` schema. +# generated by datamodel-codegen: +# filename: enums/property_type.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.property_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.property_type import PropertyType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["PropertyType"] +class PropertyType(StrEnum): + website = 'website' + mobile_app = 'mobile_app' + ctv_app = 'ctv_app' + desktop_app = 'desktop_app' + dooh = 'dooh' + podcast = 'podcast' + radio = 'radio' + linear_tv = 'linear_tv' + streaming_audio = 'streaming_audio' + ai_assistant = 'ai_assistant' diff --git a/src/adcp/types/domains/enums/proposal_decline_reason.py b/src/adcp/types/domains/enums/proposal_decline_reason.py index 22a44f5ed..e14501351 100644 --- a/src/adcp/types/domains/enums/proposal_decline_reason.py +++ b/src/adcp/types/domains/enums/proposal_decline_reason.py @@ -1,18 +1,20 @@ -"""Types declared by the AdCP ``enums/proposal_decline_reason`` schema. +# generated by datamodel-codegen: +# filename: enums/proposal_decline_reason.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.proposal_decline_reason import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.proposal_decline_reason import ProposalDeclineReason +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["ProposalDeclineReason"] +class ProposalDeclineReason(StrEnum): + price = 'price' + inventory_fit = 'inventory_fit' + audience_fit = 'audience_fit' + creative_unsupported = 'creative_unsupported' + measurement_unsupported = 'measurement_unsupported' + policy = 'policy' + timing = 'timing' + budget_changed = 'budget_changed' + selected_alternative = 'selected_alternative' + other = 'other' diff --git a/src/adcp/types/domains/enums/proposal_refinement_reason.py b/src/adcp/types/domains/enums/proposal_refinement_reason.py index 5ca426b5c..e46f7e7b6 100644 --- a/src/adcp/types/domains/enums/proposal_refinement_reason.py +++ b/src/adcp/types/domains/enums/proposal_refinement_reason.py @@ -1,18 +1,18 @@ -"""Types declared by the AdCP ``enums/proposal_refinement_reason`` schema. +# generated by datamodel-codegen: +# filename: enums/proposal_refinement_reason.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.proposal_refinement_reason import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.proposal_refinement_reason import ProposalRefinementReason +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["ProposalRefinementReason"] +class ProposalRefinementReason(StrEnum): + commercially_declined = 'commercially_declined' + constraint_unsatisfiable = 'constraint_unsatisfiable' + unsupported_dimension = 'unsupported_dimension' + uninterpreted = 'uninterpreted' + alternatives_unavailable = 'alternatives_unavailable' + source_unavailable = 'source_unavailable' + hold_unavailable = 'hold_unavailable' + batch_aborted = 'batch_aborted' diff --git a/src/adcp/types/domains/enums/proposal_status.py b/src/adcp/types/domains/enums/proposal_status.py index 299f87004..753eb76b0 100644 --- a/src/adcp/types/domains/enums/proposal_status.py +++ b/src/adcp/types/domains/enums/proposal_status.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/proposal_status`` schema. +# generated by datamodel-codegen: +# filename: enums/proposal_status.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.proposal_status import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.proposal_status import ProposalStatus +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["ProposalStatus"] +class ProposalStatus(StrEnum): + draft = 'draft' + committed = 'committed' + accepted = 'accepted' diff --git a/src/adcp/types/domains/enums/publisher_identifier_types.py b/src/adcp/types/domains/enums/publisher_identifier_types.py index c94d01b2a..e06cabb10 100644 --- a/src/adcp/types/domains/enums/publisher_identifier_types.py +++ b/src/adcp/types/domains/enums/publisher_identifier_types.py @@ -1,18 +1,15 @@ -"""Types declared by the AdCP ``enums/publisher_identifier_types`` schema. +# generated by datamodel-codegen: +# filename: enums/publisher_identifier_types.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.publisher_identifier_types import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.publisher_identifier_types import PublisherIdentifierTypes +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["PublisherIdentifierTypes"] +class PublisherIdentifierTypes(StrEnum): + tag_id = 'tag_id' + duns = 'duns' + lei = 'lei' + seller_id = 'seller_id' + gln = 'gln' diff --git a/src/adcp/types/domains/enums/purchase_type.py b/src/adcp/types/domains/enums/purchase_type.py index 4edaeb1ad..4363c77f0 100644 --- a/src/adcp/types/domains/enums/purchase_type.py +++ b/src/adcp/types/domains/enums/purchase_type.py @@ -1,18 +1,14 @@ -"""Types declared by the AdCP ``enums/purchase_type`` schema. +# generated by datamodel-codegen: +# filename: enums/purchase_type.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.purchase_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.purchase_type import PurchaseType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["PurchaseType"] +class PurchaseType(StrEnum): + media_buy = 'media_buy' + rights_license = 'rights_license' + signal_activation = 'signal_activation' + creative_services = 'creative_services' diff --git a/src/adcp/types/domains/enums/reach_aggregation.py b/src/adcp/types/domains/enums/reach_aggregation.py index a3a80eea8..b9f262347 100644 --- a/src/adcp/types/domains/enums/reach_aggregation.py +++ b/src/adcp/types/domains/enums/reach_aggregation.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``enums/reach_aggregation`` schema. +# generated by datamodel-codegen: +# filename: enums/reach_aggregation.json +# timestamp: 2026-09-05T11:09:57+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.reach_aggregation import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.reach_aggregation import ReachAggregation +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["ReachAggregation"] +class ReachAggregation(StrEnum): + deduplicated = 'deduplicated' + sum_of_constituent_reach = 'sum_of_constituent_reach' diff --git a/src/adcp/types/domains/enums/reach_unit.py b/src/adcp/types/domains/enums/reach_unit.py index d4cf1e075..54eab6f36 100644 --- a/src/adcp/types/domains/enums/reach_unit.py +++ b/src/adcp/types/domains/enums/reach_unit.py @@ -1,18 +1,16 @@ -"""Types declared by the AdCP ``enums/reach_unit`` schema. +# generated by datamodel-codegen: +# filename: enums/reach_unit.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.reach_unit import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.reach_unit import ReachUnit +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["ReachUnit"] +class ReachUnit(StrEnum): + individuals = 'individuals' + households = 'households' + devices = 'devices' + accounts = 'accounts' + cookies = 'cookies' + custom = 'custom' diff --git a/src/adcp/types/domains/enums/reporting_destination_setup_state.py b/src/adcp/types/domains/enums/reporting_destination_setup_state.py index 30f7949b2..e872982b0 100644 --- a/src/adcp/types/domains/enums/reporting_destination_setup_state.py +++ b/src/adcp/types/domains/enums/reporting_destination_setup_state.py @@ -1,20 +1,15 @@ -"""Types declared by the AdCP ``enums/reporting_destination_setup_state`` schema. +# generated by datamodel-codegen: +# filename: enums/reporting_destination_setup_state.json +# timestamp: 2026-09-01T19:37:00+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.reporting_destination_setup_state import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.reporting_destination_setup_state import ( - ReportingDestinationSetupState, -) +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["ReportingDestinationSetupState"] +class ReportingDestinationSetupState(StrEnum): + validating = 'validating' + ready = 'ready' + action_required = 'action_required' + inactive = 'inactive' + rejected = 'rejected' diff --git a/src/adcp/types/domains/enums/reporting_finality.py b/src/adcp/types/domains/enums/reporting_finality.py index 635fdd64e..d441cb4eb 100644 --- a/src/adcp/types/domains/enums/reporting_finality.py +++ b/src/adcp/types/domains/enums/reporting_finality.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``enums/reporting_finality`` schema. +# generated by datamodel-codegen: +# filename: enums/reporting_finality.json +# timestamp: 2026-09-01T19:37:00+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.reporting_finality import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.reporting_finality import ReportingFinality +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["ReportingFinality"] +class ReportingFinality(StrEnum): + snapshot = 'snapshot' + official = 'official' diff --git a/src/adcp/types/domains/enums/reporting_frequency.py b/src/adcp/types/domains/enums/reporting_frequency.py index 6253510a6..3c3275ed3 100644 --- a/src/adcp/types/domains/enums/reporting_frequency.py +++ b/src/adcp/types/domains/enums/reporting_frequency.py @@ -1,18 +1,16 @@ -"""Types declared by the AdCP ``enums/reporting_frequency`` schema. +# generated by datamodel-codegen: +# filename: enums/reporting_frequency.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.reporting_frequency import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.reporting_frequency import ReportingFrequency +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["ReportingFrequency"] +class ReportingFrequency(StrEnum): + hourly = 'hourly' + daily = 'daily' + weekly = 'weekly' + monthly = 'monthly' + quarterly = 'quarterly' + post_campaign = 'post_campaign' diff --git a/src/adcp/types/domains/enums/reporting_health.py b/src/adcp/types/domains/enums/reporting_health.py index 4ac639274..969682e14 100644 --- a/src/adcp/types/domains/enums/reporting_health.py +++ b/src/adcp/types/domains/enums/reporting_health.py @@ -1,18 +1,15 @@ -"""Types declared by the AdCP ``enums/reporting_health`` schema. +# generated by datamodel-codegen: +# filename: enums/reporting_health.json +# timestamp: 2026-09-01T19:37:00+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.reporting_health import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.reporting_health import ReportingHealth +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["ReportingHealth"] +class ReportingHealth(StrEnum): + healthy = 'healthy' + waiting = 'waiting' + delayed = 'delayed' + action_required = 'action_required' + complete = 'complete' diff --git a/src/adcp/types/domains/enums/representation_selection_strategy.py b/src/adcp/types/domains/enums/representation_selection_strategy.py index 723d195bd..968e15026 100644 --- a/src/adcp/types/domains/enums/representation_selection_strategy.py +++ b/src/adcp/types/domains/enums/representation_selection_strategy.py @@ -1,20 +1,12 @@ -"""Types declared by the AdCP ``enums/representation_selection_strategy`` schema. +# generated by datamodel-codegen: +# filename: enums/representation_selection_strategy.json +# timestamp: 2026-08-28T20:03:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.representation_selection_strategy import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.representation_selection_strategy import ( - RepresentationSelectionStrategy, -) +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["RepresentationSelectionStrategy"] +class RepresentationSelectionStrategy(StrEnum): + representation_order = 'representation_order' + highest_compatible_vast = 'highest_compatible_vast' diff --git a/src/adcp/types/domains/enums/request_signing_error_code.py b/src/adcp/types/domains/enums/request_signing_error_code.py index ddaf877ea..c1d63a65b 100644 --- a/src/adcp/types/domains/enums/request_signing_error_code.py +++ b/src/adcp/types/domains/enums/request_signing_error_code.py @@ -1,18 +1,39 @@ -"""Types declared by the AdCP ``enums/request_signing_error_code`` schema. +# generated by datamodel-codegen: +# filename: enums/request_signing_error_code.json +# timestamp: 2026-09-24T08:59:48+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.request_signing_error_code import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.request_signing_error_code import RequestSigningErrorCode +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["RequestSigningErrorCode"] +class RequestSigningErrorCode(StrEnum): + request_signature_brand_json_url_missing = 'request_signature_brand_json_url_missing' + request_signature_capabilities_unreachable = 'request_signature_capabilities_unreachable' + request_signature_brand_json_unreachable = 'request_signature_brand_json_unreachable' + request_signature_brand_json_malformed = 'request_signature_brand_json_malformed' + request_signature_brand_origin_mismatch = 'request_signature_brand_origin_mismatch' + request_signature_agent_not_in_brand_json = 'request_signature_agent_not_in_brand_json' + request_signature_brand_json_ambiguous = 'request_signature_brand_json_ambiguous' + request_signature_key_origin_mismatch = 'request_signature_key_origin_mismatch' + request_signature_key_origin_missing = 'request_signature_key_origin_missing' + request_signature_required = 'request_signature_required' + request_target_uri_malformed = 'request_target_uri_malformed' + request_signature_header_malformed = 'request_signature_header_malformed' + request_signature_params_incomplete = 'request_signature_params_incomplete' + request_signature_tag_invalid = 'request_signature_tag_invalid' + request_signature_alg_not_allowed = 'request_signature_alg_not_allowed' + request_signature_window_invalid = 'request_signature_window_invalid' + request_signature_components_incomplete = 'request_signature_components_incomplete' + request_signature_components_unexpected = 'request_signature_components_unexpected' + request_signature_key_unknown = 'request_signature_key_unknown' + request_signature_key_purpose_invalid = 'request_signature_key_purpose_invalid' + request_signature_key_revoked = 'request_signature_key_revoked' + request_signature_revocation_stale = 'request_signature_revocation_stale' + request_signature_invalid = 'request_signature_invalid' + request_signature_digest_mismatch = 'request_signature_digest_mismatch' + request_body_malformed = 'request_body_malformed' + request_signature_replayed = 'request_signature_replayed' + request_signature_rate_abuse = 'request_signature_rate_abuse' + request_signature_jwks_unavailable = 'request_signature_jwks_unavailable' + request_signature_jwks_untrusted = 'request_signature_jwks_untrusted' diff --git a/src/adcp/types/domains/enums/response_type.py b/src/adcp/types/domains/enums/response_type.py index 4d78bfad6..3e2ebea84 100644 --- a/src/adcp/types/domains/enums/response_type.py +++ b/src/adcp/types/domains/enums/response_type.py @@ -1,18 +1,14 @@ -"""Types declared by the AdCP ``enums/response_type`` schema. +# generated by datamodel-codegen: +# filename: enums/response_type.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.response_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.response_type import TmpResponseType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["TmpResponseType"] +class TmpResponseType(StrEnum): + activation = 'activation' + catalog_items = 'catalog_items' + creative = 'creative' + deal = 'deal' diff --git a/src/adcp/types/domains/enums/restricted_attribute.py b/src/adcp/types/domains/enums/restricted_attribute.py index 34c56877a..4a48f5ccf 100644 --- a/src/adcp/types/domains/enums/restricted_attribute.py +++ b/src/adcp/types/domains/enums/restricted_attribute.py @@ -1,18 +1,20 @@ -"""Types declared by the AdCP ``enums/restricted_attribute`` schema. +# generated by datamodel-codegen: +# filename: enums/restricted_attribute.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.restricted_attribute import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.restricted_attribute import RestrictedAttribute +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["RestrictedAttribute"] +class RestrictedAttribute(StrEnum): + racial_ethnic_origin = 'racial_ethnic_origin' + political_opinions = 'political_opinions' + religious_beliefs = 'religious_beliefs' + trade_union_membership = 'trade_union_membership' + health_data = 'health_data' + sex_life_sexual_orientation = 'sex_life_sexual_orientation' + genetic_data = 'genetic_data' + biometric_data = 'biometric_data' + age = 'age' + familial_status = 'familial_status' diff --git a/src/adcp/types/domains/enums/right_type.py b/src/adcp/types/domains/enums/right_type.py index dd75cd607..30c4b50d6 100644 --- a/src/adcp/types/domains/enums/right_type.py +++ b/src/adcp/types/domains/enums/right_type.py @@ -1,18 +1,15 @@ -"""Types declared by the AdCP ``enums/right_type`` schema. +# generated by datamodel-codegen: +# filename: enums/right_type.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.right_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.right_type import RightType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["RightType"] +class RightType(StrEnum): + talent = 'talent' + character = 'character' + brand_ip = 'brand_ip' + music = 'music' + stock_media = 'stock_media' diff --git a/src/adcp/types/domains/enums/right_use.py b/src/adcp/types/domains/enums/right_use.py index 3a5f41e14..388eca848 100644 --- a/src/adcp/types/domains/enums/right_use.py +++ b/src/adcp/types/domains/enums/right_use.py @@ -1,18 +1,22 @@ -"""Types declared by the AdCP ``enums/right_use`` schema. +# generated by datamodel-codegen: +# filename: enums/right_use.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.right_use import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.right_use import RightUse +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["RightUse"] +class RightUse(StrEnum): + likeness = 'likeness' + voice = 'voice' + name = 'name' + endorsement = 'endorsement' + motion_capture = 'motion_capture' + signature = 'signature' + catchphrase = 'catchphrase' + sync = 'sync' + background_music = 'background_music' + editorial = 'editorial' + commercial = 'commercial' + ai_generated_image = 'ai_generated_image' diff --git a/src/adcp/types/domains/enums/rights_billing_period.py b/src/adcp/types/domains/enums/rights_billing_period.py index 867bc0a29..38f770b48 100644 --- a/src/adcp/types/domains/enums/rights_billing_period.py +++ b/src/adcp/types/domains/enums/rights_billing_period.py @@ -1,18 +1,16 @@ -"""Types declared by the AdCP ``enums/rights_billing_period`` schema. +# generated by datamodel-codegen: +# filename: enums/rights_billing_period.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.rights_billing_period import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.rights_billing_period import RightsBillingPeriod +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["RightsBillingPeriod"] +class RightsBillingPeriod(StrEnum): + daily = 'daily' + weekly = 'weekly' + monthly = 'monthly' + quarterly = 'quarterly' + annual = 'annual' + one_time = 'one_time' diff --git a/src/adcp/types/domains/enums/scan_type.py b/src/adcp/types/domains/enums/scan_type.py index 1ff0a8740..a3f30f723 100644 --- a/src/adcp/types/domains/enums/scan_type.py +++ b/src/adcp/types/domains/enums/scan_type.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``enums/scan_type`` schema. +# generated by datamodel-codegen: +# filename: enums/scan_type.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.scan_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.scan_type import ScanType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["ScanType"] +class ScanType(StrEnum): + progressive = 'progressive' + interlaced = 'interlaced' diff --git a/src/adcp/types/domains/enums/seller_policy_decline_reason.py b/src/adcp/types/domains/enums/seller_policy_decline_reason.py index 1265e51c6..143895b99 100644 --- a/src/adcp/types/domains/enums/seller_policy_decline_reason.py +++ b/src/adcp/types/domains/enums/seller_policy_decline_reason.py @@ -1,18 +1,17 @@ -"""Types declared by the AdCP ``enums/seller_policy_decline_reason`` schema. +# generated by datamodel-codegen: +# filename: enums/seller_policy_decline_reason.json +# timestamp: 2026-09-24T08:59:48+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.seller_policy_decline_reason import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.seller_policy_decline_reason import SellerPolicyDeclineReason +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["SellerPolicyDeclineReason"] +class SellerPolicyDeclineReason(StrEnum): + inventory = 'inventory' + share_of_voice = 'share_of_voice' + minimum_commitment = 'minimum_commitment' + notice_period = 'notice_period' + contract_terms = 'contract_terms' + frequency_cap = 'frequency_cap' + other = 'other' diff --git a/src/adcp/types/domains/enums/si_session_status.py b/src/adcp/types/domains/enums/si_session_status.py index 58846d577..e118ebaff 100644 --- a/src/adcp/types/domains/enums/si_session_status.py +++ b/src/adcp/types/domains/enums/si_session_status.py @@ -1,18 +1,14 @@ -"""Types declared by the AdCP ``enums/si_session_status`` schema. +# generated by datamodel-codegen: +# filename: enums/si_session_status.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.si_session_status import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.si_session_status import SiSessionStatus +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["SiSessionStatus"] +class SiSessionStatus(StrEnum): + active = 'active' + pending_handoff = 'pending_handoff' + complete = 'complete' + terminated = 'terminated' diff --git a/src/adcp/types/domains/enums/signal_catalog_type.py b/src/adcp/types/domains/enums/signal_catalog_type.py index bda6d37d1..b4dda7ca5 100644 --- a/src/adcp/types/domains/enums/signal_catalog_type.py +++ b/src/adcp/types/domains/enums/signal_catalog_type.py @@ -1,21 +1,16 @@ -"""Types declared by the AdCP ``enums/signal_catalog_type`` schema. +# generated by datamodel-codegen: +# filename: enums/signal_catalog_type.json +# timestamp: 2026-05-29T23:56:01+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.enums.signal_catalog_type import +from adcp.types._str_enum import StrEnum -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" -# ruff: noqa: E501, I001 -from __future__ import annotations +class SignalAvailabilityType(StrEnum): + marketplace = 'marketplace' + custom = 'custom' + owned = 'owned' -from adcp.types.generated_poc.enums.signal_catalog_type import ( - SignalAvailabilityType, - SignalCatalogType, -) -# Explicit exports -__all__ = ["SignalAvailabilityType", "SignalCatalogType"] +SignalCatalogType = SignalAvailabilityType diff --git a/src/adcp/types/domains/enums/signal_source.py b/src/adcp/types/domains/enums/signal_source.py index 96ec73dc8..a31ccc875 100644 --- a/src/adcp/types/domains/enums/signal_source.py +++ b/src/adcp/types/domains/enums/signal_source.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``enums/signal_source`` schema. +# generated by datamodel-codegen: +# filename: enums/signal_source.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.signal_source import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.signal_source import SignalSource +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["SignalSource"] +class SignalSource(StrEnum): + catalog = 'catalog' + agent = 'agent' diff --git a/src/adcp/types/domains/enums/signal_value_type.py b/src/adcp/types/domains/enums/signal_value_type.py index 4b0c8d1b2..2fa0d1f83 100644 --- a/src/adcp/types/domains/enums/signal_value_type.py +++ b/src/adcp/types/domains/enums/signal_value_type.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/signal_value_type`` schema. +# generated by datamodel-codegen: +# filename: enums/signal_value_type.json +# timestamp: 2026-09-05T19:58:21+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.signal_value_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.signal_value_type import SignalValueType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["SignalValueType"] +class SignalValueType(StrEnum): + binary = 'binary' + categorical = 'categorical' + numeric = 'numeric' diff --git a/src/adcp/types/domains/enums/snapshot_unavailable_reason.py b/src/adcp/types/domains/enums/snapshot_unavailable_reason.py index 8595b9255..1021f2317 100644 --- a/src/adcp/types/domains/enums/snapshot_unavailable_reason.py +++ b/src/adcp/types/domains/enums/snapshot_unavailable_reason.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/snapshot_unavailable_reason`` schema. +# generated by datamodel-codegen: +# filename: enums/snapshot_unavailable_reason.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.snapshot_unavailable_reason import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.snapshot_unavailable_reason import SnapshotUnavailableReason +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["SnapshotUnavailableReason"] +class SnapshotUnavailableReason(StrEnum): + SNAPSHOT_UNSUPPORTED = 'SNAPSHOT_UNSUPPORTED' + SNAPSHOT_TEMPORARILY_UNAVAILABLE = 'SNAPSHOT_TEMPORARILY_UNAVAILABLE' + SNAPSHOT_PERMISSION_DENIED = 'SNAPSHOT_PERMISSION_DENIED' diff --git a/src/adcp/types/domains/enums/social_placement_surface.py b/src/adcp/types/domains/enums/social_placement_surface.py index 86652313d..b54a8a2ee 100644 --- a/src/adcp/types/domains/enums/social_placement_surface.py +++ b/src/adcp/types/domains/enums/social_placement_surface.py @@ -1,18 +1,15 @@ -"""Types declared by the AdCP ``enums/social_placement_surface`` schema. +# generated by datamodel-codegen: +# filename: enums/social_placement_surface.json +# timestamp: 2026-06-04T19:44:00+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.social_placement_surface import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.social_placement_surface import SocialPlacementSurface +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["SocialPlacementSurface"] +class SocialPlacementSurface(StrEnum): + feed = 'feed' + stories = 'stories' + short_video = 'short_video' + explore = 'explore' + search = 'search' diff --git a/src/adcp/types/domains/enums/sort_direction.py b/src/adcp/types/domains/enums/sort_direction.py index b21abded5..a5a21dfe9 100644 --- a/src/adcp/types/domains/enums/sort_direction.py +++ b/src/adcp/types/domains/enums/sort_direction.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``enums/sort_direction`` schema. +# generated by datamodel-codegen: +# filename: enums/sort_direction.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.sort_direction import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.sort_direction import SortDirection +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["SortDirection"] +class SortDirection(StrEnum): + asc = 'asc' + desc = 'desc' diff --git a/src/adcp/types/domains/enums/sort_metric.py b/src/adcp/types/domains/enums/sort_metric.py index 301323818..3b2030364 100644 --- a/src/adcp/types/domains/enums/sort_metric.py +++ b/src/adcp/types/domains/enums/sort_metric.py @@ -1,18 +1,47 @@ -"""Types declared by the AdCP ``enums/sort_metric`` schema. +# generated by datamodel-codegen: +# filename: enums/sort_metric.json +# timestamp: 2026-08-24T04:35:32+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.sort_metric import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.sort_metric import SortMetric +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["SortMetric"] +class SortMetric(StrEnum): + impressions = 'impressions' + spend = 'spend' + clicks = 'clicks' + ctr = 'ctr' + views = 'views' + completed_views = 'completed_views' + completion_rate = 'completion_rate' + conversions = 'conversions' + conversion_value = 'conversion_value' + roas = 'roas' + cost_per_acquisition = 'cost_per_acquisition' + new_to_brand_rate = 'new_to_brand_rate' + leads = 'leads' + grps = 'grps' + reach = 'reach' + frequency = 'frequency' + engagements = 'engagements' + follows = 'follows' + saves = 'saves' + profile_visits = 'profile_visits' + engagement_rate = 'engagement_rate' + cost_per_click = 'cost_per_click' + commissionable_value = 'commissionable_value' + plays = 'plays' + cost_per_completed_view = 'cost_per_completed_view' + cpm = 'cpm' + downloads = 'downloads' + units_sold = 'units_sold' + new_to_brand_units = 'new_to_brand_units' + viewable_rate = 'viewable_rate' + viewable_impressions = 'viewable_impressions' + measurable_impressions = 'measurable_impressions' + viewed_seconds = 'viewed_seconds' + quartile_25 = 'quartile_25' + quartile_50 = 'quartile_50' + quartile_75 = 'quartile_75' + quartile_100 = 'quartile_100' diff --git a/src/adcp/types/domains/enums/special_category.py b/src/adcp/types/domains/enums/special_category.py index ec572172d..83ae1b443 100644 --- a/src/adcp/types/domains/enums/special_category.py +++ b/src/adcp/types/domains/enums/special_category.py @@ -1,18 +1,22 @@ -"""Types declared by the AdCP ``enums/special_category`` schema. +# generated by datamodel-codegen: +# filename: enums/special_category.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.special_category import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.special_category import SpecialCategory +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["SpecialCategory"] +class SpecialCategory(StrEnum): + awards = 'awards' + championship = 'championship' + concert = 'concert' + conference = 'conference' + election = 'election' + festival = 'festival' + gala = 'gala' + holiday = 'holiday' + premiere = 'premiere' + product_launch = 'product_launch' + reunion = 'reunion' + tribute = 'tribute' diff --git a/src/adcp/types/domains/enums/specialism.py b/src/adcp/types/domains/enums/specialism.py index ad33fcaeb..4a8192ee1 100644 --- a/src/adcp/types/domains/enums/specialism.py +++ b/src/adcp/types/domains/enums/specialism.py @@ -1,18 +1,42 @@ -"""Types declared by the AdCP ``enums/specialism`` schema. +# generated by datamodel-codegen: +# filename: enums/specialism.json +# timestamp: 2026-09-24T08:59:48+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.specialism import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.specialism import AdcpSpecialism +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["AdcpSpecialism"] +class AdcpSpecialism(StrEnum): + audience_sync = 'audience-sync' + brand_rights = 'brand-rights' + buyer_activation = 'buyer-activation' + buyer_discovery = 'buyer-discovery' + buyer_monitoring = 'buyer-monitoring' + buyer_negotiation = 'buyer-negotiation' + buyer_recovery = 'buyer-recovery' + collection_lists = 'collection-lists' + content_standards = 'content-standards' + creative_ad_server = 'creative-ad-server' + creative_generative = 'creative-generative' + creative_template = 'creative-template' + creative_transformers = 'creative-transformers' + governance_aware_seller = 'governance-aware-seller' + governance_delivery_monitor = 'governance-delivery-monitor' + governance_spend_authority = 'governance-spend-authority' + property_lists = 'property-lists' + sales_broadcast_tv = 'sales-broadcast-tv' + sales_catalog_driven = 'sales-catalog-driven' + sales_dooh = 'sales-dooh' + sales_exchange = 'sales-exchange' + sales_guaranteed = 'sales-guaranteed' + sales_non_guaranteed = 'sales-non-guaranteed' + sales_proposal_mode = 'sales-proposal-mode' + sales_retail_media = 'sales-retail-media' + sales_streaming_tv = 'sales-streaming-tv' + sales_social = 'sales-social' + signal_marketplace = 'signal-marketplace' + orchestrator_multi_agent = 'orchestrator-multi-agent' + signal_owned = 'signal-owned' + signed_requests = 'signed-requests' + sponsored_intelligence = 'sponsored-intelligence' diff --git a/src/adcp/types/domains/enums/sponsored_placement_type.py b/src/adcp/types/domains/enums/sponsored_placement_type.py index 68815d807..f74718ca3 100644 --- a/src/adcp/types/domains/enums/sponsored_placement_type.py +++ b/src/adcp/types/domains/enums/sponsored_placement_type.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/sponsored_placement_type`` schema. +# generated by datamodel-codegen: +# filename: enums/sponsored_placement_type.json +# timestamp: 2026-06-04T19:44:00+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.sponsored_placement_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.sponsored_placement_type import SponsoredPlacementType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["SponsoredPlacementType"] +class SponsoredPlacementType(StrEnum): + sponsored_search = 'sponsored_search' + sponsored_display = 'sponsored_display' + sponsored_native = 'sponsored_native' diff --git a/src/adcp/types/domains/enums/talent_role.py b/src/adcp/types/domains/enums/talent_role.py index 9f5fb54f0..3916bb8a9 100644 --- a/src/adcp/types/domains/enums/talent_role.py +++ b/src/adcp/types/domains/enums/talent_role.py @@ -1,18 +1,19 @@ -"""Types declared by the AdCP ``enums/talent_role`` schema. +# generated by datamodel-codegen: +# filename: enums/talent_role.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.talent_role import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.talent_role import TalentRole +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["TalentRole"] +class TalentRole(StrEnum): + host = 'host' + guest = 'guest' + creator = 'creator' + cast = 'cast' + narrator = 'narrator' + producer = 'producer' + correspondent = 'correspondent' + commentator = 'commentator' + analyst = 'analyst' diff --git a/src/adcp/types/domains/enums/task_status.py b/src/adcp/types/domains/enums/task_status.py index 47693aad1..e2481b00c 100644 --- a/src/adcp/types/domains/enums/task_status.py +++ b/src/adcp/types/domains/enums/task_status.py @@ -1,18 +1,19 @@ -"""Types declared by the AdCP ``enums/task_status`` schema. +# generated by datamodel-codegen: +# filename: enums/task_status.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.task_status import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.task_status import TaskStatus +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["TaskStatus"] +class TaskStatus(StrEnum): + submitted = 'submitted' + working = 'working' + input_required = 'input-required' + completed = 'completed' + canceled = 'canceled' + failed = 'failed' + rejected = 'rejected' + auth_required = 'auth-required' + unknown = 'unknown' diff --git a/src/adcp/types/domains/enums/task_type.py b/src/adcp/types/domains/enums/task_type.py index 966d15c31..db25bf474 100644 --- a/src/adcp/types/domains/enums/task_type.py +++ b/src/adcp/types/domains/enums/task_type.py @@ -1,18 +1,48 @@ -"""Types declared by the AdCP ``enums/task_type`` schema. +# generated by datamodel-codegen: +# filename: enums/task_type.json +# timestamp: 2026-09-24T08:59:48+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.task_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.task_type import TaskType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["TaskType"] +class TaskType(StrEnum): + create_media_buy = 'create_media_buy' + update_media_buy = 'update_media_buy' + buy_products = 'buy_products' + accept_proposal = 'accept_proposal' + control_media_buy = 'control_media_buy' + media_buy_delivery = 'media_buy_delivery' + sync_creatives = 'sync_creatives' + build_creative = 'build_creative' + preview_creative = 'preview_creative' + get_creative_features = 'get_creative_features' + activate_signal = 'activate_signal' + get_products = 'get_products' + request_proposals = 'request_proposals' + refine_proposals = 'refine_proposals' + decline_proposals = 'decline_proposals' + get_signals = 'get_signals' + create_property_list = 'create_property_list' + update_property_list = 'update_property_list' + get_property_list = 'get_property_list' + list_property_lists = 'list_property_lists' + delete_property_list = 'delete_property_list' + sync_accounts = 'sync_accounts' + get_account_financials = 'get_account_financials' + get_creative_delivery = 'get_creative_delivery' + sync_event_sources = 'sync_event_sources' + sync_audiences = 'sync_audiences' + sync_catalogs = 'sync_catalogs' + log_event = 'log_event' + get_brand_identity = 'get_brand_identity' + search_brands = 'search_brands' + get_rights = 'get_rights' + acquire_rights = 'acquire_rights' + update_rights = 'update_rights' + sync_agent_notification_configs = 'sync_agent_notification_configs' + sync_principal = 'sync_principal' + get_principal = 'get_principal' + sync_reporting_status = 'sync_reporting_status' + sync_reporting_receipts = 'sync_reporting_receipts' diff --git a/src/adcp/types/domains/enums/tracker_execution_actor.py b/src/adcp/types/domains/enums/tracker_execution_actor.py index 9f9214fd0..14fb4e47b 100644 --- a/src/adcp/types/domains/enums/tracker_execution_actor.py +++ b/src/adcp/types/domains/enums/tracker_execution_actor.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``enums/tracker_execution_actor`` schema. +# generated by datamodel-codegen: +# filename: enums/tracker_execution_actor.json +# timestamp: 2026-08-28T20:03:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.tracker_execution_actor import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.tracker_execution_actor import TrackerExecutionActor +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["TrackerExecutionActor"] +class TrackerExecutionActor(StrEnum): + seller = 'seller' + request_executor = 'request_executor' diff --git a/src/adcp/types/domains/enums/tracker_firing_path.py b/src/adcp/types/domains/enums/tracker_firing_path.py index ce513d60e..227d8b47d 100644 --- a/src/adcp/types/domains/enums/tracker_firing_path.py +++ b/src/adcp/types/domains/enums/tracker_firing_path.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``enums/tracker_firing_path`` schema. +# generated by datamodel-codegen: +# filename: enums/tracker_firing_path.json +# timestamp: 2026-08-28T20:03:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.tracker_firing_path import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.tracker_firing_path import TrackerFiringPath +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["TrackerFiringPath"] +class TrackerFiringPath(StrEnum): + client = 'client' + server = 'server' diff --git a/src/adcp/types/domains/enums/transport_mode.py b/src/adcp/types/domains/enums/transport_mode.py index a50b210fc..f547a4804 100644 --- a/src/adcp/types/domains/enums/transport_mode.py +++ b/src/adcp/types/domains/enums/transport_mode.py @@ -1,18 +1,14 @@ -"""Types declared by the AdCP ``enums/transport_mode`` schema. +# generated by datamodel-codegen: +# filename: enums/transport_mode.json +# timestamp: 2026-06-04T19:44:00+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.transport_mode import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.transport_mode import TransportMode +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["TransportMode"] +class TransportMode(StrEnum): + walking = 'walking' + cycling = 'cycling' + driving = 'driving' + public_transport = 'public_transport' diff --git a/src/adcp/types/domains/enums/travel_time_unit.py b/src/adcp/types/domains/enums/travel_time_unit.py index cca5537cb..440fd83e0 100644 --- a/src/adcp/types/domains/enums/travel_time_unit.py +++ b/src/adcp/types/domains/enums/travel_time_unit.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``enums/travel_time_unit`` schema. +# generated by datamodel-codegen: +# filename: enums/travel_time_unit.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.travel_time_unit import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.travel_time_unit import TravelTimeUnit +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["TravelTimeUnit"] +class TravelTimeUnit(StrEnum): + min = 'min' + hr = 'hr' diff --git a/src/adcp/types/domains/enums/uid_type.py b/src/adcp/types/domains/enums/uid_type.py index 196a9d2af..6a1f44a01 100644 --- a/src/adcp/types/domains/enums/uid_type.py +++ b/src/adcp/types/domains/enums/uid_type.py @@ -1,18 +1,21 @@ -"""Types declared by the AdCP ``enums/uid_type`` schema. +# generated by datamodel-codegen: +# filename: enums/uid_type.json +# timestamp: 2026-06-12T11:05:47+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.uid_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.uid_type import UidType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["UidType"] +class UidType(StrEnum): + rampid = 'rampid' + rampid_derived = 'rampid_derived' + id5 = 'id5' + uid2 = 'uid2' + euid = 'euid' + pairid = 'pairid' + maid = 'maid' + hashed_email = 'hashed_email' + publisher_first_party = 'publisher_first_party' + world_id_nullifier = 'world_id_nullifier' + other = 'other' diff --git a/src/adcp/types/domains/enums/universal_macro.py b/src/adcp/types/domains/enums/universal_macro.py index 073d9432e..f479de8f6 100644 --- a/src/adcp/types/domains/enums/universal_macro.py +++ b/src/adcp/types/domains/enums/universal_macro.py @@ -1,18 +1,85 @@ -"""Types declared by the AdCP ``enums/universal_macro`` schema. +# generated by datamodel-codegen: +# filename: enums/universal_macro.json +# timestamp: 2026-06-04T19:44:00+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.universal_macro import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.universal_macro import UniversalMacro +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["UniversalMacro"] +class UniversalMacro(StrEnum): + MEDIA_BUY_ID = 'MEDIA_BUY_ID' + PACKAGE_ID = 'PACKAGE_ID' + CREATIVE_ID = 'CREATIVE_ID' + CACHEBUSTER = 'CACHEBUSTER' + TIMESTAMP = 'TIMESTAMP' + CLICK_URL = 'CLICK_URL' + GDPR = 'GDPR' + GDPR_CONSENT = 'GDPR_CONSENT' + US_PRIVACY = 'US_PRIVACY' + GPP_STRING = 'GPP_STRING' + GPP_SID = 'GPP_SID' + IP_ADDRESS = 'IP_ADDRESS' + LIMIT_AD_TRACKING = 'LIMIT_AD_TRACKING' + DEVICE_TYPE = 'DEVICE_TYPE' + OS = 'OS' + OS_VERSION = 'OS_VERSION' + DEVICE_MAKE = 'DEVICE_MAKE' + DEVICE_MODEL = 'DEVICE_MODEL' + USER_AGENT = 'USER_AGENT' + APP_BUNDLE = 'APP_BUNDLE' + APP_NAME = 'APP_NAME' + COUNTRY = 'COUNTRY' + REGION = 'REGION' + CITY = 'CITY' + ZIP = 'ZIP' + DMA = 'DMA' + LAT = 'LAT' + LONG = 'LONG' + DEVICE_ID = 'DEVICE_ID' + DEVICE_ID_TYPE = 'DEVICE_ID_TYPE' + DOMAIN = 'DOMAIN' + PAGE_URL = 'PAGE_URL' + REFERRER = 'REFERRER' + KEYWORDS = 'KEYWORDS' + PLACEMENT_ID = 'PLACEMENT_ID' + FOLD_POSITION = 'FOLD_POSITION' + AD_WIDTH = 'AD_WIDTH' + AD_HEIGHT = 'AD_HEIGHT' + VIDEO_ID = 'VIDEO_ID' + VIDEO_TITLE = 'VIDEO_TITLE' + VIDEO_DURATION = 'VIDEO_DURATION' + VIDEO_CATEGORY = 'VIDEO_CATEGORY' + CONTENT_GENRE = 'CONTENT_GENRE' + CONTENT_RATING = 'CONTENT_RATING' + PLAYER_WIDTH = 'PLAYER_WIDTH' + PLAYER_HEIGHT = 'PLAYER_HEIGHT' + POD_POSITION = 'POD_POSITION' + POD_SIZE = 'POD_SIZE' + AD_BREAK_ID = 'AD_BREAK_ID' + STATION_ID = 'STATION_ID' + COLLECTION_NAME = 'COLLECTION_NAME' + INSTALLMENT_ID = 'INSTALLMENT_ID' + AUDIO_DURATION = 'AUDIO_DURATION' + TMPX = 'TMPX' + IMPRESSION_ID = 'IMPRESSION_ID' + AXEM = 'AXEM' + CATALOG_ID = 'CATALOG_ID' + SKU = 'SKU' + GTIN = 'GTIN' + OFFERING_ID = 'OFFERING_ID' + JOB_ID = 'JOB_ID' + HOTEL_ID = 'HOTEL_ID' + FLIGHT_ID = 'FLIGHT_ID' + VEHICLE_ID = 'VEHICLE_ID' + LISTING_ID = 'LISTING_ID' + STORE_ID = 'STORE_ID' + PROGRAM_ID = 'PROGRAM_ID' + DESTINATION_ID = 'DESTINATION_ID' + CREATIVE_VARIANT_ID = 'CREATIVE_VARIANT_ID' + APP_ITEM_ID = 'APP_ITEM_ID' + ITEM_NAME = 'ITEM_NAME' + ITEM_DESCRIPTION = 'ITEM_DESCRIPTION' + ITEM_TAGLINE = 'ITEM_TAGLINE' + ITEM_PRICE = 'ITEM_PRICE' + ITEM_PRICE_CURRENCY = 'ITEM_PRICE_CURRENCY' diff --git a/src/adcp/types/domains/enums/update_frequency.py b/src/adcp/types/domains/enums/update_frequency.py index 310adb6a6..59f2b3b9e 100644 --- a/src/adcp/types/domains/enums/update_frequency.py +++ b/src/adcp/types/domains/enums/update_frequency.py @@ -1,18 +1,14 @@ -"""Types declared by the AdCP ``enums/update_frequency`` schema. +# generated by datamodel-codegen: +# filename: enums/update_frequency.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.update_frequency import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.update_frequency import UpdateFrequency +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["UpdateFrequency"] +class UpdateFrequency(StrEnum): + realtime = 'realtime' + hourly = 'hourly' + daily = 'daily' + weekly = 'weekly' diff --git a/src/adcp/types/domains/enums/url_asset_type.py b/src/adcp/types/domains/enums/url_asset_type.py index 3da1eeac3..fdbda4f21 100644 --- a/src/adcp/types/domains/enums/url_asset_type.py +++ b/src/adcp/types/domains/enums/url_asset_type.py @@ -1,18 +1,14 @@ -"""Types declared by the AdCP ``enums/url_asset_type`` schema. +# generated by datamodel-codegen: +# filename: enums/url_asset_type.json +# timestamp: 2026-08-28T20:03:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.url_asset_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.url_asset_type import UrlAssetType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["UrlAssetType"] +class UrlAssetType(StrEnum): + clickthrough = 'clickthrough' + ad_request = 'ad_request' + tracker_pixel = 'tracker_pixel' + tracker_script = 'tracker_script' diff --git a/src/adcp/types/domains/enums/validation_mode.py b/src/adcp/types/domains/enums/validation_mode.py index ce03da17d..998f56cae 100644 --- a/src/adcp/types/domains/enums/validation_mode.py +++ b/src/adcp/types/domains/enums/validation_mode.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``enums/validation_mode`` schema. +# generated by datamodel-codegen: +# filename: enums/validation_mode.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.validation_mode import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.validation_mode import ValidationMode +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["ValidationMode"] +class ValidationMode(StrEnum): + strict = 'strict' + lenient = 'lenient' diff --git a/src/adcp/types/domains/enums/vast_media_delivery_method.py b/src/adcp/types/domains/enums/vast_media_delivery_method.py index a4e1e07eb..ea2885107 100644 --- a/src/adcp/types/domains/enums/vast_media_delivery_method.py +++ b/src/adcp/types/domains/enums/vast_media_delivery_method.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``enums/vast_media_delivery_method`` schema. +# generated by datamodel-codegen: +# filename: enums/vast_media_delivery_method.json +# timestamp: 2026-08-28T20:03:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.vast_media_delivery_method import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.vast_media_delivery_method import VastMediaDeliveryMethod +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["VastMediaDeliveryMethod"] +class VastMediaDeliveryMethod(StrEnum): + progressive = 'progressive' + streaming = 'streaming' diff --git a/src/adcp/types/domains/enums/vast_tracking_event.py b/src/adcp/types/domains/enums/vast_tracking_event.py index 52b3b979f..9757c99e4 100644 --- a/src/adcp/types/domains/enums/vast_tracking_event.py +++ b/src/adcp/types/domains/enums/vast_tracking_event.py @@ -1,18 +1,46 @@ -"""Types declared by the AdCP ``enums/vast_tracking_event`` schema. +# generated by datamodel-codegen: +# filename: enums/vast_tracking_event.json +# timestamp: 2026-05-22T13:15:12+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.vast_tracking_event import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.vast_tracking_event import VastTrackingEvent +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["VastTrackingEvent"] +class VastTrackingEvent(StrEnum): + impression = 'impression' + creativeView = 'creativeView' + loaded = 'loaded' + start = 'start' + firstQuartile = 'firstQuartile' + midpoint = 'midpoint' + thirdQuartile = 'thirdQuartile' + complete = 'complete' + mute = 'mute' + unmute = 'unmute' + pause = 'pause' + resume = 'resume' + rewind = 'rewind' + skip = 'skip' + playerExpand = 'playerExpand' + playerCollapse = 'playerCollapse' + fullscreen = 'fullscreen' + exitFullscreen = 'exitFullscreen' + progress = 'progress' + acceptInvitation = 'acceptInvitation' + adExpand = 'adExpand' + adCollapse = 'adCollapse' + minimize = 'minimize' + overlayViewDuration = 'overlayViewDuration' + otherAdInteraction = 'otherAdInteraction' + interactiveStart = 'interactiveStart' + clickTracking = 'clickTracking' + customClick = 'customClick' + close = 'close' + closeLinear = 'closeLinear' + error = 'error' + viewable = 'viewable' + notViewable = 'notViewable' + viewUndetermined = 'viewUndetermined' + measurableImpression = 'measurableImpression' + viewableImpression = 'viewableImpression' diff --git a/src/adcp/types/domains/enums/vast_version.py b/src/adcp/types/domains/enums/vast_version.py index bcb420213..d650021b7 100644 --- a/src/adcp/types/domains/enums/vast_version.py +++ b/src/adcp/types/domains/enums/vast_version.py @@ -1,18 +1,16 @@ -"""Types declared by the AdCP ``enums/vast_version`` schema. +# generated by datamodel-codegen: +# filename: enums/vast_version.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.vast_version import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.vast_version import VastVersion +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["VastVersion"] +class VastVersion(StrEnum): + field_2_0 = '2.0' + field_3_0 = '3.0' + field_4_0 = '4.0' + field_4_1 = '4.1' + field_4_2 = '4.2' + field_4_3 = '4.3' diff --git a/src/adcp/types/domains/enums/vendor_relationship.py b/src/adcp/types/domains/enums/vendor_relationship.py index a2f5ecd31..e73cbcf28 100644 --- a/src/adcp/types/domains/enums/vendor_relationship.py +++ b/src/adcp/types/domains/enums/vendor_relationship.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/vendor_relationship`` schema. +# generated by datamodel-codegen: +# filename: enums/vendor_relationship.json +# timestamp: 2026-09-05T11:09:57+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.vendor_relationship import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.vendor_relationship import VendorRelationship +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["VendorRelationship"] +class VendorRelationship(StrEnum): + first_party = 'first_party' + affiliated = 'affiliated' + third_party = 'third_party' diff --git a/src/adcp/types/domains/enums/video_placement_type.py b/src/adcp/types/domains/enums/video_placement_type.py index 4fb2f9faf..aeaef22b1 100644 --- a/src/adcp/types/domains/enums/video_placement_type.py +++ b/src/adcp/types/domains/enums/video_placement_type.py @@ -1,18 +1,14 @@ -"""Types declared by the AdCP ``enums/video_placement_type`` schema. +# generated by datamodel-codegen: +# filename: enums/video_placement_type.json +# timestamp: 2026-05-29T23:56:01+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.video_placement_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.video_placement_type import VideoPlacementType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["VideoPlacementType"] +class VideoPlacementType(StrEnum): + instream = 'instream' + accompanying_content = 'accompanying_content' + interstitial = 'interstitial' + standalone = 'standalone' diff --git a/src/adcp/types/domains/enums/view_threshold_basis.py b/src/adcp/types/domains/enums/view_threshold_basis.py index e4d6f036e..549d9a180 100644 --- a/src/adcp/types/domains/enums/view_threshold_basis.py +++ b/src/adcp/types/domains/enums/view_threshold_basis.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``enums/view_threshold_basis`` schema. +# generated by datamodel-codegen: +# filename: enums/view_threshold_basis.json +# timestamp: 2026-08-24T04:35:32+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.view_threshold_basis import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.view_threshold_basis import ViewThresholdBasis +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["ViewThresholdBasis"] +class ViewThresholdBasis(StrEnum): + play_time = 'play_time' + in_view = 'in_view' diff --git a/src/adcp/types/domains/enums/viewability_standard.py b/src/adcp/types/domains/enums/viewability_standard.py index 8d2e899c6..cc6f86eec 100644 --- a/src/adcp/types/domains/enums/viewability_standard.py +++ b/src/adcp/types/domains/enums/viewability_standard.py @@ -1,18 +1,12 @@ -"""Types declared by the AdCP ``enums/viewability_standard`` schema. +# generated by datamodel-codegen: +# filename: enums/viewability_standard.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.viewability_standard import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.viewability_standard import ViewabilityStandard +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["ViewabilityStandard"] +class ViewabilityStandard(StrEnum): + mrc = 'mrc' + groupm = 'groupm' diff --git a/src/adcp/types/domains/enums/warning_code.py b/src/adcp/types/domains/enums/warning_code.py index 03116c95e..81879868e 100644 --- a/src/adcp/types/domains/enums/warning_code.py +++ b/src/adcp/types/domains/enums/warning_code.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/warning_code`` schema. +# generated by datamodel-codegen: +# filename: enums/warning_code.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.warning_code import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.warning_code import WarningCode +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["WarningCode"] +class WarningCode(StrEnum): + inventory_shortfall_forecast = 'inventory_shortfall_forecast' + flight_change_creates_pacing_risk = 'flight_change_creates_pacing_risk' + fields_ignored_due_to_precedence = 'fields_ignored_due_to_precedence' diff --git a/src/adcp/types/domains/enums/watermark_media_type.py b/src/adcp/types/domains/enums/watermark_media_type.py index 18f550ba9..8db3747b7 100644 --- a/src/adcp/types/domains/enums/watermark_media_type.py +++ b/src/adcp/types/domains/enums/watermark_media_type.py @@ -1,18 +1,14 @@ -"""Types declared by the AdCP ``enums/watermark_media_type`` schema. +# generated by datamodel-codegen: +# filename: enums/watermark_media_type.json +# timestamp: 2026-06-04T19:44:00+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.watermark_media_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.watermark_media_type import WatermarkMediaType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["WatermarkMediaType"] +class WatermarkMediaType(StrEnum): + audio = 'audio' + image = 'image' + video = 'video' + text = 'text' diff --git a/src/adcp/types/domains/enums/wcag_level.py b/src/adcp/types/domains/enums/wcag_level.py index ed171f5e4..013eafdc4 100644 --- a/src/adcp/types/domains/enums/wcag_level.py +++ b/src/adcp/types/domains/enums/wcag_level.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/wcag_level`` schema. +# generated by datamodel-codegen: +# filename: enums/wcag_level.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.wcag_level import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.wcag_level import WcagLevel +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["WcagLevel"] +class WcagLevel(StrEnum): + A = 'A' + AA = 'AA' + AAA = 'AAA' diff --git a/src/adcp/types/domains/enums/webhook_response_type.py b/src/adcp/types/domains/enums/webhook_response_type.py index 987727e4c..11a82949d 100644 --- a/src/adcp/types/domains/enums/webhook_response_type.py +++ b/src/adcp/types/domains/enums/webhook_response_type.py @@ -1,18 +1,14 @@ -"""Types declared by the AdCP ``enums/webhook_response_type`` schema. +# generated by datamodel-codegen: +# filename: enums/webhook_response_type.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.webhook_response_type import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.webhook_response_type import WebhookResponseType +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["WebhookResponseType"] +class WebhookResponseType(StrEnum): + html = 'html' + json = 'json' + xml = 'xml' + javascript = 'javascript' diff --git a/src/adcp/types/domains/enums/webhook_security_method.py b/src/adcp/types/domains/enums/webhook_security_method.py index 9975d7155..d0ab2901b 100644 --- a/src/adcp/types/domains/enums/webhook_security_method.py +++ b/src/adcp/types/domains/enums/webhook_security_method.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``enums/webhook_security_method`` schema. +# generated by datamodel-codegen: +# filename: enums/webhook_security_method.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.enums.webhook_security_method import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.enums.webhook_security_method import WebhookSecurityMethod +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["WebhookSecurityMethod"] +class WebhookSecurityMethod(StrEnum): + hmac_sha256 = 'hmac_sha256' + api_key = 'api_key' + none = 'none' diff --git a/src/adcp/types/domains/error_details/__init__.py b/src/adcp/types/domains/error_details/__init__.py index e942003b7..d956e4d80 100644 --- a/src/adcp/types/domains/error_details/__init__.py +++ b/src/adcp/types/domains/error_details/__init__.py @@ -9,75 +9,67 @@ it from its own schema's module, ``adcp.types.domains.error_details.``. Nothing here is renamed. -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-04 01:19:01 UTC +Auto-generated from the generated domain tree. DO NOT EDIT MANUALLY. +Generation date: 2026-10-04 18:45:11 UTC """ # ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.error_details.accessibility_violation import ( +from adcp.types.domains.error_details.accessibility_violation import ( AccessibilityViolationDetails, FailureKind, FailureKind1, Violation, ) -from adcp.types.generated_poc.error_details.account_moved import AccountMovedDetails -from adcp.types.generated_poc.error_details.account_setup_required import ( - AccountSetupRequiredDetails, -) -from adcp.types.generated_poc.error_details.action_not_allowed import ActionNotAllowedDetails -from adcp.types.generated_poc.error_details.agent_permission_denied import ( - AgentPermissionDeniedDetails, -) -from adcp.types.generated_poc.error_details.audience_too_small import AudienceTooSmallDetails -from adcp.types.generated_poc.error_details.authorization_required import ( - AuthorizationRequiredDetails, -) -from adcp.types.generated_poc.error_details.billing_not_permitted_for_agent import ( +from adcp.types.domains.error_details.account_moved import AccountMovedDetails +from adcp.types.domains.error_details.account_setup_required import AccountSetupRequiredDetails +from adcp.types.domains.error_details.action_not_allowed import ActionNotAllowedDetails +from adcp.types.domains.error_details.agent_permission_denied import AgentPermissionDeniedDetails +from adcp.types.domains.error_details.audience_too_small import AudienceTooSmallDetails +from adcp.types.domains.error_details.authorization_required import AuthorizationRequiredDetails +from adcp.types.domains.error_details.billing_not_permitted_for_agent import ( BillingNotPermittedForAgentDetails, ) -from adcp.types.generated_poc.error_details.billing_not_supported import BillingNotSupportedDetails -from adcp.types.generated_poc.error_details.budget_too_low import BudgetTooLowDetails -from adcp.types.generated_poc.error_details.conflict import ConflictDetails -from adcp.types.generated_poc.error_details.creative_rejected import CreativeRejectedDetails -from adcp.types.generated_poc.error_details.creative_representation_unresolved import ( +from adcp.types.domains.error_details.billing_not_supported import BillingNotSupportedDetails +from adcp.types.domains.error_details.budget_too_low import BudgetTooLowDetails +from adcp.types.domains.error_details.conflict import ConflictDetails +from adcp.types.domains.error_details.creative_rejected import CreativeRejectedDetails +from adcp.types.domains.error_details.creative_representation_unresolved import ( CreativeRepresentationUnresolvedDetails, ) -from adcp.types.generated_poc.error_details.creative_revision_content_mismatch import ( +from adcp.types.domains.error_details.creative_revision_content_mismatch import ( CreativeRevisionContentMismatchDetails, ) -from adcp.types.generated_poc.error_details.execution_requirement_unmet import ( +from adcp.types.domains.error_details.execution_requirement_unmet import ( ExecutionRequirementUnmetDetails, Reason, UnmetRequirement, ) -from adcp.types.generated_poc.error_details.governance_agent_not_accepted import ( +from adcp.types.domains.error_details.governance_agent_not_accepted import ( GovernanceAgentNotAcceptedDetails, GovernanceAgentNotAcceptedDetails1, GovernanceAgentNotAcceptedDetails2, ) -from adcp.types.generated_poc.error_details.macro_resolution_failed import ( - MacroResolutionFailedDetails, -) -from adcp.types.generated_poc.error_details.policy_violation import Origin, PolicyViolationDetails -from adcp.types.generated_poc.error_details.rate_limited import RateLimitedDetails -from adcp.types.generated_poc.error_details.requote_required import ( +from adcp.types.domains.error_details.macro_resolution_failed import MacroResolutionFailedDetails +from adcp.types.domains.error_details.policy_violation import Origin, PolicyViolationDetails +from adcp.types.domains.error_details.rate_limited import RateLimitedDetails +from adcp.types.domains.error_details.requote_required import ( EnvelopeField, EnvelopeField1, EnvelopeField1Item, RequoteRequiredDetails, ) -from adcp.types.generated_poc.error_details.stale_response import ( +from adcp.types.domains.error_details.stale_response import ( OriginalError, StaleResponseDetails, Upstream, ) -from adcp.types.generated_poc.error_details.unsupported_refinement_dimension import ( +from adcp.types.domains.error_details.unsupported_refinement_dimension import ( SupportedDimension, UnsupportedRefinementDimensionDetails, ) -from adcp.types.generated_poc.error_details.vast_version_mismatch import ( +from adcp.types.domains.error_details.vast_version_mismatch import ( DocumentRole, MismatchReason, VastVersionMismatchDetails, @@ -85,13 +77,13 @@ VastVersionMismatchDetails2, VastVersionMismatchDetails3, ) -from adcp.types.generated_poc.error_details.vendor_error_codes import ( +from adcp.types.domains.error_details.vendor_error_codes import ( Codes, Recovery, VendorErrorCodeRegistry, Vendors, ) -from adcp.types.generated_poc.error_details.version_unsupported import ( +from adcp.types.domains.error_details.version_unsupported import ( SupportedMajor, SupportedVersion, VersionUnsupportedDetails, diff --git a/src/adcp/types/domains/error_details/accessibility_violation.py b/src/adcp/types/domains/error_details/accessibility_violation.py index fbb1304c7..dd15f47d1 100644 --- a/src/adcp/types/domains/error_details/accessibility_violation.py +++ b/src/adcp/types/domains/error_details/accessibility_violation.py @@ -1,23 +1,106 @@ -"""Types declared by the AdCP ``error_details/accessibility_violation`` schema. +# generated by datamodel-codegen: +# filename: error_details/accessibility_violation.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.error_details.accessibility_violation import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.error_details.accessibility_violation import ( - AccessibilityViolationDetails, - FailureKind, - FailureKind1, - Violation, -) +class FailureKind(StrEnum): + alt_text_missing = 'alt_text_missing' + contrast_ratio_insufficient = 'contrast_ratio_insufficient' + captions_missing = 'captions_missing' + transcript_missing = 'transcript_missing' + keyboard_navigation_failure = 'keyboard_navigation_failure' + focus_order_invalid = 'focus_order_invalid' + aria_label_missing = 'aria_label_missing' + other = 'other' + + +class FailureKind1(ScalarStr): + __slots__ = () + _constraints = {'max_length': 64, 'pattern': '^x_[a-z0-9]+(?:_[a-z0-9]+)*$'} + _json_schema_extra = { + 'description': 'Diagnostic failure category. Canonical values cover common portable cases. Vendor-local extensions use an x_ prefix. Portable recovery dispatches on criterion_source plus criterion after validating this details schema; failure_kind may refine diagnostics but does not select the recovery class.', + } + + +class Violation(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + pointer: Annotated[ + str, + Field( + description='RFC 6901 JSON Pointer rooted at the complete task request payload, not at the nested creative manifest. For example, inline build_creative uses /creative_manifest/assets/hero_image and a sync_creatives batch uses /creatives/0/assets/hero_image. If build_creative resolves a stored manifest from creative_id, use /creative_id because the failing asset is not present in the request.', + max_length=256, + pattern='^(?:/(?:[^~/\\u0000-\\u001F\\u007F]|~0|~1)*)*$', + ), + ] + criterion: Annotated[ + str, + Field( + description='Stable criterion identifier within criterion_source, for example 1.4.3. Open string so external standards can evolve without an AdCP enum update.', + max_length=32, + min_length=1, + pattern='^[^\\u0000-\\u001F\\u007F]+$', + ), + ] + criterion_source: Annotated[ + str, + Field( + description='Standard or policy namespace defining the criterion, for example WCAG21, WCAG22, or EN301549.', + max_length=64, + min_length=1, + pattern='^[A-Za-z][A-Za-z0-9._:-]{0,63}$', + ), + ] + required_level: Annotated[ + str, + Field( + description="Conformance level or policy threshold that applied, for example AA. Kept open because not every accessibility standard uses WCAG's A/AA/AAA vocabulary.", + max_length=64, + min_length=1, + pattern='^[^\\u0000-\\u001F\\u007F]+$', + ), + ] + failure_kind: Annotated[ + FailureKind | FailureKind1, + Field( + description='Diagnostic failure category. Canonical values cover common portable cases. Vendor-local extensions use an x_ prefix. Portable recovery dispatches on criterion_source plus criterion after validating this details schema; failure_kind may refine diagnostics but does not select the recovery class.' + ), + ] + remediation: Annotated[ + str | None, + Field( + description='Optional human-readable correction guidance. Plain text only; consumers treat it as untrusted agent input.', + max_length=256, + pattern='^[^\\u0000-\\u001F\\u007F]*$', + ), + ] = None + -# Explicit exports -__all__ = ["AccessibilityViolationDetails", "FailureKind", "FailureKind1", "Violation"] +class AccessibilityViolationDetails(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + violations: Annotated[ + list[Violation], + Field( + description='Accessibility requirements the creative or its assets failed. This is distinct from error.issues, which carries JSON Schema validator failures.', + max_length=4, + min_length=1, + ), + ] + truncated: Annotated[ + StrictBool | None, + Field( + description='True when the producer omitted additional violations or remediation text to keep the complete serialized error within the 4096-byte transport safety limit. Omission means false.' + ), + ] = None diff --git a/src/adcp/types/domains/error_details/account_moved.py b/src/adcp/types/domains/error_details/account_moved.py index 3a9107458..49548a938 100644 --- a/src/adcp/types/domains/error_details/account_moved.py +++ b/src/adcp/types/domains/error_details/account_moved.py @@ -1,18 +1,30 @@ -"""Types declared by the AdCP ``error_details/account_moved`` schema. +# generated by datamodel-codegen: +# filename: error_details/account_moved.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.error_details.account_moved import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import canonical_account_ref -from adcp.types.generated_poc.error_details.account_moved import AccountMovedDetails -# Explicit exports -__all__ = ["AccountMovedDetails"] +class AccountMovedDetails(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + current_account: Annotated[ + canonical_account_ref.CanonicalAccountReference, + Field( + description='Current canonical reference. Buyer-declared account sellers return the complete current natural key; account-id namespaces may return account_id.' + ), + ] + revision: Annotated[ + SchemaInt | None, + Field( + description='Current account revision when the seller exposes account revisions.', ge=1 + ), + ] = None diff --git a/src/adcp/types/domains/error_details/account_setup_required.py b/src/adcp/types/domains/error_details/account_setup_required.py index b77f99162..4b7b5875b 100644 --- a/src/adcp/types/domains/error_details/account_setup_required.py +++ b/src/adcp/types/domains/error_details/account_setup_required.py @@ -1,20 +1,22 @@ -"""Types declared by the AdCP ``error_details/account_setup_required`` schema. +# generated by datamodel-codegen: +# filename: error_details/account_setup_required.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.error_details.account_setup_required import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field -from adcp.types.generated_poc.error_details.account_setup_required import ( - AccountSetupRequiredDetails, -) -# Explicit exports -__all__ = ["AccountSetupRequiredDetails"] +class AccountSetupRequiredDetails(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + setup_url: Annotated[ + AnyUrl | None, Field(description='URL where account setup can be completed') + ] = None + setup_steps: Annotated[ + list[str] | None, Field(description='Steps remaining before the account is ready') + ] = None diff --git a/src/adcp/types/domains/error_details/action_not_allowed.py b/src/adcp/types/domains/error_details/action_not_allowed.py index 5da016b39..fad18a7d1 100644 --- a/src/adcp/types/domains/error_details/action_not_allowed.py +++ b/src/adcp/types/domains/error_details/action_not_allowed.py @@ -1,18 +1,43 @@ -"""Types declared by the AdCP ``error_details/action_not_allowed`` schema. +# generated by datamodel-codegen: +# filename: error_details/action_not_allowed.json +# timestamp: 2026-09-24T08:59:48+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.error_details.action_not_allowed import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import media_buy_available_action, media_buy_available_action_id +from ..enums import action_not_allowed_reason, seller_policy_decline_reason -from adcp.types.generated_poc.error_details.action_not_allowed import ActionNotAllowedDetails -# Explicit exports -__all__ = ["ActionNotAllowedDetails"] +class ActionNotAllowedDetails(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + attempted_action: Annotated[ + media_buy_available_action_id.MediaBuyAvailableActionId, + Field( + description='The action the request mapped to, per the normative action-to-field mapping in update_media_buy task-reference.' + ), + ] + reason: Annotated[ + action_not_allowed_reason.ActionNotAllowedReason, + Field( + description='Why the action was rejected. Buyer SDKs branch on this to choose a recovery path.' + ), + ] + currently_available_actions: Annotated[ + list[media_buy_available_action.MediaBuyAvailableAction] | None, + Field( + description="Echo of the buy's resolved `available_actions[]` at rejection time. Buyer SDKs render this to the caller as the recovery option set." + ), + ] = None + decline_reason: Annotated[ + seller_policy_decline_reason.SellerPolicyDeclineReason | None, + Field( + description='Coarse seller-policy dimension behind a buy-specific denial. Sellers SHOULD populate this for `reason: not_supported_on_buy` when they have a structured reason and disclosure does not expose private thresholds or enforcement controls. It SHOULD be omitted for structural reasons such as `wrong_status`, `not_supported_on_product`, `mode_mismatch`, and `condition_unresolved`. When present, it MUST be consistent with the enclosing error.recovery classification.' + ), + ] = None diff --git a/src/adcp/types/domains/error_details/agent_permission_denied.py b/src/adcp/types/domains/error_details/agent_permission_denied.py index ae874d41b..12b2351ee 100644 --- a/src/adcp/types/domains/error_details/agent_permission_denied.py +++ b/src/adcp/types/domains/error_details/agent_permission_denied.py @@ -1,20 +1,28 @@ -"""Types declared by the AdCP ``error_details/agent_permission_denied`` schema. +# generated by datamodel-codegen: +# filename: error_details/agent_permission_denied.json +# timestamp: 2026-05-22T13:15:12+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.error_details.agent_permission_denied import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -from adcp.types.generated_poc.error_details.agent_permission_denied import ( - AgentPermissionDeniedDetails, -) -# Explicit exports -__all__ = ["AgentPermissionDeniedDetails"] +class AgentPermissionDeniedDetails(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + scope: Annotated[ + Literal['agent'], + Field( + description='Discriminator that this `PERMISSION_DENIED` was triggered by a per-buyer-agent provisioning gate (as opposed to a generic credential-shaped failure). Registered subset of `enums/error-scope.json` — sellers MUST set this field exactly to `"agent"` for this shape, and MUST omit `scope` entirely when responding on the unauthenticated/unestablished-identity path required by the cross-tenant oracle clamp. The `AGENT_SUSPENDED` / `AGENT_BLOCKED` codes (per-agent commercial status) do NOT carry an explicit `scope` field — the code itself is the discriminator on those paths, mirroring `BILLING_NOT_PERMITTED_FOR_AGENT`.' + ), + ] = 'agent' + reason: Annotated[ + Literal['sandbox_only'], + Field( + description='Registered per-agent provisioning gate that fired. `"sandbox_only"` — the agent is provisioned for sandbox traffic only and the request was against a non-sandbox account. New `reason` values MUST be added here (the enum is closed by `additionalProperties: false`) so cross-language SDKs can dispatch without parsing prose.' + ), + ] = 'sandbox_only' diff --git a/src/adcp/types/domains/error_details/audience_too_small.py b/src/adcp/types/domains/error_details/audience_too_small.py index 38409203e..0ea9a883e 100644 --- a/src/adcp/types/domains/error_details/audience_too_small.py +++ b/src/adcp/types/domains/error_details/audience_too_small.py @@ -1,18 +1,20 @@ -"""Types declared by the AdCP ``error_details/audience_too_small`` schema. +# generated by datamodel-codegen: +# filename: error_details/audience_too_small.json +# timestamp: 2026-10-03T15:09:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.error_details.audience_too_small import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictFloat -from adcp.types.generated_poc.error_details.audience_too_small import AudienceTooSmallDetails -# Explicit exports -__all__ = ["AudienceTooSmallDetails"] +class AudienceTooSmallDetails(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + minimum_size: Annotated[ + StrictFloat | None, Field(description='Minimum audience size required') + ] = None + current_size: Annotated[StrictFloat | None, Field(description='Current audience size')] = None diff --git a/src/adcp/types/domains/error_details/authorization_required.py b/src/adcp/types/domains/error_details/authorization_required.py index 41245c707..b49400dab 100644 --- a/src/adcp/types/domains/error_details/authorization_required.py +++ b/src/adcp/types/domains/error_details/authorization_required.py @@ -1,20 +1,49 @@ -"""Types declared by the AdCP ``error_details/authorization_required`` schema. +# generated by datamodel-codegen: +# filename: error_details/authorization_required.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.error_details.authorization_required import +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import downstream_connection_requirement -from adcp.types.generated_poc.error_details.authorization_required import ( - AuthorizationRequiredDetails, -) -# Explicit exports -__all__ = ["AuthorizationRequiredDetails"] +class AuthorizationRequiredDetails(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + required_connections: Annotated[ + list[downstream_connection_requirement.DownstreamConnectionRequirement] | None, + Field( + description='Complete set of downstream connections known to be required for the relevant product, format, or request.' + ), + ] = None + missing_connections: Annotated[ + list[downstream_connection_requirement.DownstreamConnectionRequirement] | None, + Field( + description='Subset of downstream connections that blocked the current request. Sellers SHOULD populate this array when the caller needs to route a human through a connections flow. Entries with `status` of `missing`, `pending`, `expired`, or `revoked` MUST include either `provider` or `authorization_url` so the buyer can route the remediation unambiguously.' + ), + ] = None + authorization_url: Annotated[ + AnyUrl | None, + Field( + description='General recovery URL when there is a single obvious authorization step or when the seller has its own connection-management page.' + ), + ] = None + authorization_instructions: Annotated[ + str | None, + Field( + description='Human-readable recovery instructions. Use `missing_connections[].authorization_instructions` when instructions differ per downstream connection.' + ), + ] = None + reference_authorization: Annotated[ + dict[str, Any] | None, + Field( + deprecated=True, + description='Legacy or provider-specific authorization hint for the referenced object. Prefer `missing_connections[]` for new implementations.', + ), + ] = None diff --git a/src/adcp/types/domains/error_details/billing_not_permitted_for_agent.py b/src/adcp/types/domains/error_details/billing_not_permitted_for_agent.py index 71fc15483..113dae0a8 100644 --- a/src/adcp/types/domains/error_details/billing_not_permitted_for_agent.py +++ b/src/adcp/types/domains/error_details/billing_not_permitted_for_agent.py @@ -1,20 +1,30 @@ -"""Types declared by the AdCP ``error_details/billing_not_permitted_for_agent`` schema. +# generated by datamodel-codegen: +# filename: error_details/billing_not_permitted_for_agent.json +# timestamp: 2026-05-22T13:15:12+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.error_details.billing_not_permitted_for_agent import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import billing_party -from adcp.types.generated_poc.error_details.billing_not_permitted_for_agent import ( - BillingNotPermittedForAgentDetails, -) -# Explicit exports -__all__ = ["BillingNotPermittedForAgentDetails"] +class BillingNotPermittedForAgentDetails(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + rejected_billing: Annotated[ + billing_party.BillingParty, + Field( + description='The `billing` value the seller rejected — echoed verbatim from the request for caller-side reconciliation.' + ), + ] + suggested_billing: Annotated[ + billing_party.BillingParty | None, + Field( + description="A single billing value the calling buyer agent MAY retry with autonomously. Typically `operator` for passthrough-only agents. Sellers MAY omit this field when no retryable value exists for this agent's commercial relationship — in that case the rejection is terminal-pending-onboarding and the buyer MUST complete offline payments-relationship onboarding before any value other than the original request will succeed. Sellers MUST NOT populate this field with the full subset of values the agent may use; the field surfaces at most one canonical retry." + ), + ] = None diff --git a/src/adcp/types/domains/error_details/billing_not_supported.py b/src/adcp/types/domains/error_details/billing_not_supported.py index 796ac7fe3..ca1042ad1 100644 --- a/src/adcp/types/domains/error_details/billing_not_supported.py +++ b/src/adcp/types/domains/error_details/billing_not_supported.py @@ -1,21 +1,37 @@ -"""Types declared by the AdCP ``error_details/billing_not_supported`` schema. +# generated by datamodel-codegen: +# filename: error_details/billing_not_supported.json +# timestamp: 2026-05-22T13:15:12+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.error_details.billing_not_supported import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import billing_party + + +class Scope(StrEnum): + capability = 'capability' + account = 'account' -from adcp.types.generated_poc.error_details.billing_not_supported import ( - BillingNotSupportedDetails, - Scope, -) -# Explicit exports -__all__ = ["BillingNotSupportedDetails", "Scope"] +class BillingNotSupportedDetails(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + scope: Annotated[ + Scope | None, + Field( + description='Which gate fired. `"capability"` — the seller\'s `supported_billing` capability does not include the requested value (advice: choose from `supported_billing` and retry). `"account"` — the seller\'s capability accepts the value generally but not for the specific operator on this account (advice: try the next-most-permissive value the capability allows). Registered subset of the shared discriminator vocabulary in `enums/error-scope.json` — the per-agent scope is reserved for `BILLING_NOT_PERMITTED_FOR_AGENT` (which carries no explicit `scope` field) and for `PERMISSION_DENIED` with `error-details/agent-permission-denied.json`. Sellers MUST omit this field when the response is being returned to a caller whose agent identity has not been established (signed-request derivation or credential-to-agent mapping); see the uniform-response requirement in error-handling.mdx Billing and Account Setup.' + ), + ] = None + supported_billing: Annotated[ + list[billing_party.BillingParty] | None, + Field( + description='Echoed copy of the seller\'s `supported_billing` capability for the caller\'s convenience — saves a `get_adcp_capabilities` round-trip when `scope` is `"capability"`. Sellers MAY omit this field; callers MUST treat absence as "call `get_adcp_capabilities`" rather than "any value is permitted."', + min_length=1, + ), + ] = None diff --git a/src/adcp/types/domains/error_details/budget_too_low.py b/src/adcp/types/domains/error_details/budget_too_low.py index 83ffd3d59..bc2f8d718 100644 --- a/src/adcp/types/domains/error_details/budget_too_low.py +++ b/src/adcp/types/domains/error_details/budget_too_low.py @@ -1,18 +1,20 @@ -"""Types declared by the AdCP ``error_details/budget_too_low`` schema. +# generated by datamodel-codegen: +# filename: error_details/budget_too_low.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.error_details.budget_too_low import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictFloat -from adcp.types.generated_poc.error_details.budget_too_low import BudgetTooLowDetails -# Explicit exports -__all__ = ["BudgetTooLowDetails"] +class BudgetTooLowDetails(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + minimum_budget: Annotated[ + StrictFloat | None, Field(description="Seller's minimum budget for this product") + ] = None + currency: Annotated[str | None, Field(description='ISO 4217 currency code')] = None diff --git a/src/adcp/types/domains/error_details/conflict.py b/src/adcp/types/domains/error_details/conflict.py index 40986466c..4095d020c 100644 --- a/src/adcp/types/domains/error_details/conflict.py +++ b/src/adcp/types/domains/error_details/conflict.py @@ -1,18 +1,26 @@ -"""Types declared by the AdCP ``error_details/conflict`` schema. +# generated by datamodel-codegen: +# filename: error_details/conflict.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.error_details.conflict import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictFloat -from adcp.types.generated_poc.error_details.conflict import ConflictDetails -# Explicit exports -__all__ = ["ConflictDetails"] +class ConflictDetails(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + resource_id: Annotated[ + str | None, Field(description='Identifier of the conflicting resource') + ] = None + expected_version: Annotated[ + StrictFloat | str | None, + Field(description='Version or ETag the client was operating against'), + ] = None + current_version: Annotated[ + StrictFloat | str | None, Field(description='Current version or ETag on the server') + ] = None diff --git a/src/adcp/types/domains/error_details/creative_rejected.py b/src/adcp/types/domains/error_details/creative_rejected.py index 47bd6f42c..458eb442c 100644 --- a/src/adcp/types/domains/error_details/creative_rejected.py +++ b/src/adcp/types/domains/error_details/creative_rejected.py @@ -1,18 +1,23 @@ -"""Types declared by the AdCP ``error_details/creative_rejected`` schema. +# generated by datamodel-codegen: +# filename: error_details/creative_rejected.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.error_details.creative_rejected import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field -from adcp.types.generated_poc.error_details.creative_rejected import CreativeRejectedDetails -# Explicit exports -__all__ = ["CreativeRejectedDetails"] +class CreativeRejectedDetails(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + policy_id: Annotated[str | None, Field(description='Identifier for the violated policy')] = None + policy_url: Annotated[ + AnyUrl | None, Field(description='URL where the full policy can be reviewed') + ] = None + reasons: Annotated[ + list[str] | None, Field(description='Specific reasons the creative was rejected') + ] = None diff --git a/src/adcp/types/domains/error_details/creative_representation_unresolved.py b/src/adcp/types/domains/error_details/creative_representation_unresolved.py index 88b1e5fe7..176022483 100644 --- a/src/adcp/types/domains/error_details/creative_representation_unresolved.py +++ b/src/adcp/types/domains/error_details/creative_representation_unresolved.py @@ -1,20 +1,21 @@ -"""Types declared by the AdCP ``error_details/creative_representation_unresolved`` schema. +# generated by datamodel-codegen: +# filename: error_details/creative_representation_unresolved.json +# timestamp: 2026-08-28T20:03:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.error_details.creative_representation_unresolved import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import representation_rejection -from adcp.types.generated_poc.error_details.creative_representation_unresolved import ( - CreativeRepresentationUnresolvedDetails, -) -# Explicit exports -__all__ = ["CreativeRepresentationUnresolvedDetails"] +class CreativeRepresentationUnresolvedDetails(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + representation_rejections: Annotated[ + list[representation_rejection.RepresentationRejection], Field(min_length=1) + ] diff --git a/src/adcp/types/domains/error_details/creative_revision_content_mismatch.py b/src/adcp/types/domains/error_details/creative_revision_content_mismatch.py index 60e36f89a..c1ddbba96 100644 --- a/src/adcp/types/domains/error_details/creative_revision_content_mismatch.py +++ b/src/adcp/types/domains/error_details/creative_revision_content_mismatch.py @@ -1,20 +1,20 @@ -"""Types declared by the AdCP ``error_details/creative_revision_content_mismatch`` schema. +# generated by datamodel-codegen: +# filename: error_details/creative_revision_content_mismatch.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.error_details.creative_revision_content_mismatch import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import creative_revision_id -from adcp.types.generated_poc.error_details.creative_revision_content_mismatch import ( - CreativeRevisionContentMismatchDetails, -) -# Explicit exports -__all__ = ["CreativeRevisionContentMismatchDetails"] +class CreativeRevisionContentMismatchDetails(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + creative_id: Annotated[str, Field(min_length=1)] + revision_id: creative_revision_id.CreativeRevisionId diff --git a/src/adcp/types/domains/error_details/execution_requirement_unmet.py b/src/adcp/types/domains/error_details/execution_requirement_unmet.py index d4080a625..decc9f70e 100644 --- a/src/adcp/types/domains/error_details/execution_requirement_unmet.py +++ b/src/adcp/types/domains/error_details/execution_requirement_unmet.py @@ -1,22 +1,54 @@ -"""Types declared by the AdCP ``error_details/execution_requirement_unmet`` schema. +# generated by datamodel-codegen: +# filename: error_details/execution_requirement_unmet.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.error_details.execution_requirement_unmet import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import product_execution_requirement + + +class Reason(StrEnum): + not_bound = 'not_bound' + not_found = 'not_found' + ineligible = 'ineligible' + + +class UnmetRequirement(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + product_id: Annotated[ + str, Field(description='Product whose requirement is unmet.', min_length=1) + ] + field: Annotated[ + str, + Field( + description='Request path where the resource binds, in the same JSONPath-lite form as `error.field`: `packages[i].optimization_goals` or `packages[i].catalogs` on `create_media_buy`; `new_packages[i].…` or `packages[i].…` on `update_media_buy`. For `not_found` and `ineligible`, sellers SHOULD point at the offending entry (for example `packages[0].optimization_goals[1].event_sources[0]` or `packages[0].catalogs[0]`).', + min_length=1, + ), + ] + reason: Annotated[ + Reason, + Field( + description='`not_bound`: the package does not reference a resource of this kind. `not_found`: the referenced resource is not available to the account (for example an `event_source_id` that was never registered through `sync_event_sources`); register or sync it, then retry. `ineligible`: the referenced resource exists but does not satisfy the requirement (for example an event type or catalog type the requirement does not list).' + ), + ] + requirement: Annotated[ + product_execution_requirement.ProductExecutionRequirement, + Field( + description="The product's declared requirement, echoed so the buyer can fix the request without another `get_products` call." + ), + ] -from adcp.types.generated_poc.error_details.execution_requirement_unmet import ( - ExecutionRequirementUnmetDetails, - Reason, - UnmetRequirement, -) -# Explicit exports -__all__ = ["ExecutionRequirementUnmetDetails", "Reason", "UnmetRequirement"] +class ExecutionRequirementUnmetDetails(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + unmet_requirements: Annotated[list[UnmetRequirement], Field(min_length=1)] diff --git a/src/adcp/types/domains/error_details/governance_agent_not_accepted.py b/src/adcp/types/domains/error_details/governance_agent_not_accepted.py index b202b0fbb..5b6d9c9fd 100644 --- a/src/adcp/types/domains/error_details/governance_agent_not_accepted.py +++ b/src/adcp/types/domains/error_details/governance_agent_not_accepted.py @@ -1,26 +1,51 @@ -"""Types declared by the AdCP ``error_details/governance_agent_not_accepted`` schema. +# generated by datamodel-codegen: +# filename: error_details/governance_agent_not_accepted.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.error_details.governance_agent_not_accepted import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field + +from ..governance import accepted_governance_agents as accepted_governance_agents_1 + + +class GovernanceAgentNotAcceptedDetails1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + disclosure: Literal['disclosed'] = 'disclosed' + attempted_agent_origin: Annotated[ + AnyUrl, + Field( + description='Parsed HTTPS origin only. The seller MUST strip userinfo, path, query, and fragment and MUST NOT echo the raw candidate URL.' + ), + ] + accepted_governance_agents: accepted_governance_agents_1.AcceptedGovernanceAgents + + +class GovernanceAgentNotAcceptedDetails2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + disclosure: Literal['opaque'] = 'opaque' + rejection_ref: Annotated[ + str | None, + Field( + description='Optional seller-local correlation reference. It does not identify or reveal an acceptance rule.', + max_length=128, + pattern='^[A-Za-z0-9_.:-]+$', + ), + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.error_details.governance_agent_not_accepted import ( - GovernanceAgentNotAcceptedDetails, - GovernanceAgentNotAcceptedDetails1, - GovernanceAgentNotAcceptedDetails2, -) - -# Explicit exports -__all__ = [ - "GovernanceAgentNotAcceptedDetails", - "GovernanceAgentNotAcceptedDetails1", - "GovernanceAgentNotAcceptedDetails2", +GovernanceAgentNotAcceptedDetails = Annotated[ + GovernanceAgentNotAcceptedDetails1 | GovernanceAgentNotAcceptedDetails2, + Field( + description='Per-account rejection details for a governance-agent binding. Sellers choose either the disclosed or opaque arm; callers must not infer undisclosed criteria.', + discriminator='disclosure', + title='Governance Agent Not Accepted Details', + ), ] diff --git a/src/adcp/types/domains/error_details/macro_resolution_failed.py b/src/adcp/types/domains/error_details/macro_resolution_failed.py index 9ef4945af..8f038bdd5 100644 --- a/src/adcp/types/domains/error_details/macro_resolution_failed.py +++ b/src/adcp/types/domains/error_details/macro_resolution_failed.py @@ -1,20 +1,21 @@ -"""Types declared by the AdCP ``error_details/macro_resolution_failed`` schema. +# generated by datamodel-codegen: +# filename: error_details/macro_resolution_failed.json +# timestamp: 2026-08-28T20:03:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.error_details.macro_resolution_failed import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import macro_resolution_result -from adcp.types.generated_poc.error_details.macro_resolution_failed import ( - MacroResolutionFailedDetails, -) -# Explicit exports -__all__ = ["MacroResolutionFailedDetails"] +class MacroResolutionFailedDetails(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + macro_resolution_results: Annotated[ + list[macro_resolution_result.MacroResolutionResult], Field(min_length=1) + ] diff --git a/src/adcp/types/domains/error_details/policy_violation.py b/src/adcp/types/domains/error_details/policy_violation.py index 14be85b28..4e1dd159f 100644 --- a/src/adcp/types/domains/error_details/policy_violation.py +++ b/src/adcp/types/domains/error_details/policy_violation.py @@ -1,18 +1,55 @@ -"""Types declared by the AdCP ``error_details/policy_violation`` schema. +# generated by datamodel-codegen: +# filename: error_details/policy_violation.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.error_details.policy_violation import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.error_details.policy_violation import Origin, PolicyViolationDetails +class Origin(StrEnum): + buyer_plan = 'buyer_plan' + registry = 'registry' + seller = 'seller' + -# Explicit exports -__all__ = ["Origin", "PolicyViolationDetails"] +class PolicyViolationDetails(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + origin: Annotated[ + Origin | None, + Field( + description='Whose policy produced the rejection. Optional so undisclosed seller policies can return only the typed POLICY_VIOLATION code if even origin would reveal sensitive information.' + ), + ] = None + policy_id: Annotated[ + str | None, + Field( + description='Shared policy-registry identifier for the violated policy. Use seller_policy_ref instead when the seller is not disclosing a registry policy.' + ), + ] = None + seller_policy_ref: Annotated[ + str | None, + Field( + description='Opaque seller-scoped reference that lets support and audit records correlate an undisclosed policy without publishing its rules. This is not a shared policy-registry ID.', + min_length=1, + ), + ] = None + policy_url: Annotated[ + AnyUrl | None, Field(description='URL where the full policy can be reviewed') + ] = None + violated_rules: Annotated[ + list[str] | None, Field(description='Specific rules that were violated') + ] = None + category: Annotated[ + str | None, + Field( + description="Optional coarse policy category disclosed at the seller's discretion.", + pattern='^[a-z][a-z0-9_]*$', + ), + ] = None diff --git a/src/adcp/types/domains/error_details/rate_limited.py b/src/adcp/types/domains/error_details/rate_limited.py index a73e05065..16ef25ce8 100644 --- a/src/adcp/types/domains/error_details/rate_limited.py +++ b/src/adcp/types/domains/error_details/rate_limited.py @@ -1,18 +1,33 @@ -"""Types declared by the AdCP ``error_details/rate_limited`` schema. +# generated by datamodel-codegen: +# filename: error_details/rate_limited.json +# timestamp: 2026-10-03T15:09:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.error_details.rate_limited import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.error_details.rate_limited import RateLimitedDetails, Scope +class Scope(StrEnum): + account = 'account' + tool = 'tool' + global_ = 'global' + -# Explicit exports -__all__ = ["RateLimitedDetails", "Scope"] +class RateLimitedDetails(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + limit: Annotated[ + StrictFloat | None, Field(description='Maximum requests allowed in the window') + ] = None + remaining: Annotated[ + StrictFloat | None, Field(description='Requests remaining in the current window') + ] = None + window_seconds: Annotated[ + StrictFloat | None, Field(description='Duration of the rate-limit window in seconds') + ] = None + scope: Annotated[Scope | None, Field(description='What the limit applies to')] = None diff --git a/src/adcp/types/domains/error_details/requote_required.py b/src/adcp/types/domains/error_details/requote_required.py index ad96d045d..3eb8c2608 100644 --- a/src/adcp/types/domains/error_details/requote_required.py +++ b/src/adcp/types/domains/error_details/requote_required.py @@ -1,23 +1,61 @@ -"""Types declared by the AdCP ``error_details/requote_required`` schema. +# generated by datamodel-codegen: +# filename: error_details/requote_required.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.error_details.requote_required import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field, RootModel -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import media_buy_change_term_id +from ..enums import seller_policy_decline_reason + + +class EnvelopeField(ScalarStr): + __slots__ = () + _constraints = {'min_length': 1} + _json_schema_extra = { + 'description': 'Buyer-visible request field path or paths that breached the accepted commercial envelope. Values MUST NOT name seller-internal objects or identifiers. New emitters SHOULD use the array form even for one path; the scalar form remains valid for compatibility with existing 3.x sellers.', + } + + +class EnvelopeField1Item(ScalarStr): + __slots__ = () + _constraints = {'min_length': 1} + + +class EnvelopeField1(RootModel[list[EnvelopeField1Item]]): + root: Annotated[ + list[EnvelopeField1Item], + Field( + description='Buyer-visible request field path or paths that breached the accepted commercial envelope. Values MUST NOT name seller-internal objects or identifiers. New emitters SHOULD use the array form even for one path; the scalar form remains valid for compatibility with existing 3.x sellers.', + min_length=1, + ), + ] -from adcp.types.generated_poc.error_details.requote_required import ( - EnvelopeField, - EnvelopeField1, - EnvelopeField1Item, - RequoteRequiredDetails, -) -# Explicit exports -__all__ = ["EnvelopeField", "EnvelopeField1", "EnvelopeField1Item", "RequoteRequiredDetails"] +class RequoteRequiredDetails(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + envelope_field: Annotated[ + EnvelopeField | EnvelopeField1 | None, + Field( + description='Buyer-visible request field path or paths that breached the accepted commercial envelope. Values MUST NOT name seller-internal objects or identifiers. New emitters SHOULD use the array form even for one path; the scalar form remains valid for compatibility with existing 3.x sellers.' + ), + ] = None + change_term_id: Annotated[ + media_buy_change_term_id.MediaBuyChangeTermId | None, + Field( + description='Accepted proposal change term whose typed constraint the request exceeded.' + ), + ] = None + decline_reason: Annotated[ + seller_policy_decline_reason.SellerPolicyDeclineReason | None, + Field( + description='Coarse seller-policy dimension that declined the requested shape. Sellers SHOULD populate this when they have a structured reason and disclosure does not expose private thresholds or enforcement controls. When present, it MUST be consistent with the enclosing error.recovery classification.' + ), + ] = None diff --git a/src/adcp/types/domains/error_details/stale_response.py b/src/adcp/types/domains/error_details/stale_response.py index a928b7c64..77b47bdd8 100644 --- a/src/adcp/types/domains/error_details/stale_response.py +++ b/src/adcp/types/domains/error_details/stale_response.py @@ -1,22 +1,84 @@ -"""Types declared by the AdCP ``error_details/stale_response`` schema. +# generated by datamodel-codegen: +# filename: error_details/stale_response.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.error_details.stale_response import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AnyUrl, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.error_details.stale_response import ( - OriginalError, - StaleResponseDetails, - Upstream, -) +class Upstream(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + url: Annotated[ + AnyUrl | None, + Field( + description='URL of the unreachable upstream, when the upstream is HTTP-addressable. Sellers MUST omit when the upstream is an internal (non-URL) dependency.' + ), + ] = None + name: Annotated[ + str | None, + Field( + description="Human-readable identifier for the upstream, e.g., 'creative-agent-foo' or 'inventory-service'. Used when the upstream is internal or when the URL is not informative on its own." + ), + ] = None + + +class OriginalError(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + code: Annotated[ + str | None, + Field( + description="Error code from the underlying failure, e.g., SERVICE_UNAVAILABLE, or a transport-level identifier like 'TIMEOUT' / 'CONNECTION_REFUSED' when the underlying call did not produce an AdCP error code." + ), + ] = None + message: Annotated[ + str | None, + Field( + description="Short human-readable description of the underlying failure (e.g., 'Connection timeout after 20s'). MUST NOT include credentials, internal hostnames, or stack traces." + ), + ] = None + -# Explicit exports -__all__ = ["OriginalError", "StaleResponseDetails", "Upstream"] +class StaleResponseDetails(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + served_from_cache: Annotated[ + Literal[True], + Field( + description='Always true for STALE_RESPONSE. Acts as a positive shape sentinel so consumers can validate the details payload matches the code.' + ), + ] + cache_age_seconds: Annotated[ + SchemaInt, + Field( + description='Age of the cached payload in seconds at the time of the response. Informational — buyer agents MAY use this to decide whether to immediately retry for fresh data or accept the cached value.', + ge=0, + ), + ] + freshness_target_seconds: Annotated[ + SchemaInt | None, + Field( + description="The seller's freshness target for this surface, in seconds. `cache_age_seconds - freshness_target_seconds` is how far beyond target the cached entry is. Optional — sellers MAY omit when no public freshness contract is declared.", + ge=0, + ), + ] = None + upstream: Annotated[ + Upstream | None, + Field( + description='Identifies the upstream or sub-agent whose fetch failed and triggered cache fallback. When N upstreams are stale, the seller emits N separate STALE_RESPONSE entries (one per upstream) rather than aggregating into a single entry — mirrors the per-asset advisory precedent set by PIXEL_TRACKER_LOSSY_DOWNGRADE.' + ), + ] = None + original_error: Annotated[ + OriginalError | None, + Field( + description='Minimal subset of the underlying failure that triggered cache fallback. Sellers MUST NOT include internal stack traces, credentials, or connection strings.' + ), + ] = None diff --git a/src/adcp/types/domains/error_details/unsupported_refinement_dimension.py b/src/adcp/types/domains/error_details/unsupported_refinement_dimension.py index 2a0f29255..313c96ee9 100644 --- a/src/adcp/types/domains/error_details/unsupported_refinement_dimension.py +++ b/src/adcp/types/domains/error_details/unsupported_refinement_dimension.py @@ -1,21 +1,35 @@ -"""Types declared by the AdCP ``error_details/unsupported_refinement_dimension`` schema. +# generated by datamodel-codegen: +# filename: error_details/unsupported_refinement_dimension.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.error_details.unsupported_refinement_dimension import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.error_details.unsupported_refinement_dimension import ( - SupportedDimension, - UnsupportedRefinementDimensionDetails, -) +class SupportedDimension(ScalarStr): + __slots__ = () + _constraints = {'min_length': 1} + -# Explicit exports -__all__ = ["SupportedDimension", "UnsupportedRefinementDimensionDetails"] +class UnsupportedRefinementDimensionDetails(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + unsupported_dimension: Annotated[ + str, + Field( + description='The first typed revision dimension in the request that the seller does not support, named with the supported_dimensions vocabulary.', + min_length=1, + ), + ] + supported_dimensions: Annotated[ + list[SupportedDimension], + Field( + description="The seller's complete supported_dimensions declaration, echoed so the buyer can reconstruct a valid request. An empty array means ask-only refinement." + ), + ] diff --git a/src/adcp/types/domains/error_details/vast_version_mismatch.py b/src/adcp/types/domains/error_details/vast_version_mismatch.py index 6e1b30493..1d19e8d17 100644 --- a/src/adcp/types/domains/error_details/vast_version_mismatch.py +++ b/src/adcp/types/domains/error_details/vast_version_mismatch.py @@ -1,32 +1,161 @@ -"""Types declared by the AdCP ``error_details/vast_version_mismatch`` schema. +# generated by datamodel-codegen: +# filename: error_details/vast_version_mismatch.json +# timestamp: 2026-10-04T00:56:30+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.error_details.vast_version_mismatch import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-04 01:19:07 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field + +from ..core import format_option_ref as format_option_ref_1 +from ..enums import vast_version + + +class MismatchReason(StrEnum): + asset_outside_acceptance = 'asset_outside_acceptance' + document_version_mismatch = 'document_version_mismatch' + + +class DocumentRole(StrEnum): + submitted = 'submitted' + wrapper = 'wrapper' + terminal_inline = 'terminal_inline' + + +class VastVersionMismatchDetails1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + mismatch_reason: Annotated[ + Literal['asset_outside_acceptance'], + Field(description='Machine-readable modern mismatch branch.'), + ] = 'asset_outside_acceptance' + asset_vast_version: vast_version.VastVersion + observed_document_vast_version: Annotated[ + str | None, + Field( + description='Raw version observed on the inspected VAST document whose root is being validated. `null` means the required version attribute was missing. This field is intentionally not limited to the governed supported-version enum so diagnostics can report future or invalid document values.', + min_length=1, + ), + ] = None + document_role: Annotated[ + DocumentRole | None, + Field( + description='Role of the inspected document. Only the submitted document is required to equal the asset declaration; wrapper and terminal documents are checked against the applicable acceptance sets.' + ), + ] = None + product_vast_versions: Annotated[list[vast_version.VastVersion], Field(min_length=1)] + seller_vast_versions: Annotated[list[vast_version.VastVersion], Field(min_length=1)] + format_option_ref: Annotated[ + format_option_ref_1.FormatOptionReference | None, + Field( + description='Selected option reference when the product option is addressable. Omitted for a unique id-less product option.' + ), + ] = None + supported_versions: Annotated[ + list[vast_version.VastVersion] | None, + Field( + deprecated=True, + description='Deprecated 3.x compatibility field. For an acceptance mismatch, this is the effective product/seller intersection when known. Modern emitters use the typed sets and mismatch_reason.', + min_length=1, + ), + ] = None + + +class VastVersionMismatchDetails2(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + mismatch_reason: Annotated[ + Literal['document_version_mismatch'], + Field(description='Machine-readable modern mismatch branch.'), + ] = 'document_version_mismatch' + asset_vast_version: vast_version.VastVersion + observed_document_vast_version: Annotated[ + str | None, + Field( + description='Raw version observed on the inspected VAST document whose root is being validated. `null` means the required version attribute was missing. This field is intentionally not limited to the governed supported-version enum so diagnostics can report future or invalid document values.', + min_length=1, + ), + ] + document_role: Annotated[ + DocumentRole, + Field( + description='Role of the inspected document. Only the submitted document is required to equal the asset declaration; wrapper and terminal documents are checked against the applicable acceptance sets.' + ), + ] + product_vast_versions: Annotated[list[vast_version.VastVersion] | None, Field(min_length=1)] = ( + None + ) + seller_vast_versions: Annotated[list[vast_version.VastVersion] | None, Field(min_length=1)] = ( + None + ) + format_option_ref: Annotated[ + format_option_ref_1.FormatOptionReference | None, + Field( + description='Selected option reference when the product option is addressable. Omitted for a unique id-less product option.' + ), + ] = None + supported_versions: Annotated[ + list[vast_version.VastVersion] | None, + Field( + deprecated=True, + description='Deprecated 3.x compatibility field. For an acceptance mismatch, this is the effective product/seller intersection when known. Modern emitters use the typed sets and mismatch_reason.', + min_length=1, + ), + ] = None + + +class VastVersionMismatchDetails3(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + mismatch_reason: Annotated[ + MismatchReason | None, Field(description='Machine-readable modern mismatch branch.') + ] = None + asset_vast_version: vast_version.VastVersion | None = None + observed_document_vast_version: Annotated[ + str | None, + Field( + description='Raw version observed on the inspected VAST document whose root is being validated. `null` means the required version attribute was missing. This field is intentionally not limited to the governed supported-version enum so diagnostics can report future or invalid document values.', + min_length=1, + ), + ] = None + document_role: Annotated[ + DocumentRole | None, + Field( + description='Role of the inspected document. Only the submitted document is required to equal the asset declaration; wrapper and terminal documents are checked against the applicable acceptance sets.' + ), + ] = None + product_vast_versions: Annotated[list[vast_version.VastVersion] | None, Field(min_length=1)] = ( + None + ) + seller_vast_versions: Annotated[list[vast_version.VastVersion] | None, Field(min_length=1)] = ( + None + ) + format_option_ref: Annotated[ + format_option_ref_1.FormatOptionReference | None, + Field( + description='Selected option reference when the product option is addressable. Omitted for a unique id-less product option.' + ), + ] = None + supported_versions: Annotated[ + list[vast_version.VastVersion], + Field( + deprecated=True, + description='Deprecated 3.x compatibility field. For an acceptance mismatch, this is the effective product/seller intersection when known. Modern emitters use the typed sets and mismatch_reason.', + min_length=1, + ), + ] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.error_details.vast_version_mismatch import ( - DocumentRole, - MismatchReason, - VastVersionMismatchDetails, - VastVersionMismatchDetails1, - VastVersionMismatchDetails2, - VastVersionMismatchDetails3, -) - -# Explicit exports -__all__ = [ - "DocumentRole", - "MismatchReason", - "VastVersionMismatchDetails", - "VastVersionMismatchDetails1", - "VastVersionMismatchDetails2", - "VastVersionMismatchDetails3", +VastVersionMismatchDetails = Annotated[ + VastVersionMismatchDetails1 | VastVersionMismatchDetails2 | VastVersionMismatchDetails3, + Field( + description='Required compatibility details for VAST_VERSION_MISMATCH. Modern emitters discriminate an asset-versus-acceptance mismatch from an inspected-document mismatch. The legacy supported_versions-only arm remains accepted for older 3.x peers.', + title='VAST Version Mismatch Details', + ), ] diff --git a/src/adcp/types/domains/error_details/vendor_error_codes.py b/src/adcp/types/domains/error_details/vendor_error_codes.py index f5d82081b..284bcee04 100644 --- a/src/adcp/types/domains/error_details/vendor_error_codes.py +++ b/src/adcp/types/domains/error_details/vendor_error_codes.py @@ -1,23 +1,38 @@ -"""Types declared by the AdCP ``error_details/vendor_error_codes`` schema. +# generated by datamodel-codegen: +# filename: error_details/vendor_error_codes.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.error_details.vendor_error_codes import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, Field -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.error_details.vendor_error_codes import ( - Codes, - Recovery, - VendorErrorCodeRegistry, - Vendors, -) +class Recovery(StrEnum): + transient = 'transient' + correctable = 'correctable' + terminal = 'terminal' + + +class Codes(AdCPBaseModel): + description: str + recovery: Recovery + + +class Vendors(AdCPBaseModel): + name: Annotated[str, Field(description='Full vendor name')] + url: Annotated[AnyUrl | None, Field(description='Vendor website or documentation URL')] = None + codes: Annotated[ + dict[str, Codes], + Field(description='Map of code suffix to description and recovery classification'), + ] + -# Explicit exports -__all__ = ["Codes", "Recovery", "VendorErrorCodeRegistry", "Vendors"] +class VendorErrorCodeRegistry(AdCPBaseModel): + vendors: Annotated[ + dict[str, Vendors] | None, + Field(description='Map of vendor prefix to vendor metadata and codes'), + ] = None diff --git a/src/adcp/types/domains/error_details/version_unsupported.py b/src/adcp/types/domains/error_details/version_unsupported.py index 53d034645..dbfa92cfa 100644 --- a/src/adcp/types/domains/error_details/version_unsupported.py +++ b/src/adcp/types/domains/error_details/version_unsupported.py @@ -1,22 +1,59 @@ -"""Types declared by the AdCP ``error_details/version_unsupported`` schema. +# generated by datamodel-codegen: +# filename: error_details/version_unsupported.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.error_details.version_unsupported import +from adcp.types._scalar import ScalarInt, ScalarStr -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field + +from ..core.version_envelope import AdcpVersionEnvelope + + +class SupportedVersion(ScalarStr): + __slots__ = () + _constraints = { + 'pattern': '^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?)?$', + } + + +class SupportedMajor(ScalarInt): + __slots__ = () + _constraints = {'ge': 1} -from adcp.types.generated_poc.error_details.version_unsupported import ( - SupportedMajor, - SupportedVersion, - VersionUnsupportedDetails, -) -# Explicit exports -__all__ = ["SupportedMajor", "SupportedVersion", "VersionUnsupportedDetails"] +class VersionUnsupportedDetails(AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + supported_versions: Annotated[ + list[SupportedVersion], + Field( + description='Release-precision versions the seller speaks. Authoritative — buyers SHOULD select a value from this list and retry.', + examples=[['3.0', '3.1']], + min_length=1, + ), + ] + supported_majors: Annotated[ + list[SupportedMajor] | None, + Field( + deprecated=True, + description='DEPRECATED in favor of `supported_versions`. Major versions the seller supports. Servers SHOULD emit both through 3.x; removed in 4.0.', + ), + ] = None + build_version: Annotated[ + str | None, + Field( + description="Optional advisory: full semver build identifier of the seller's deployment (MAJOR.MINOR.PATCH plus optional pre-release and build-metadata segments per semver §9–§10), for incident triage. Buyers MUST NOT use this field for negotiation.", + examples=[ + '3.1.2', + '3.1.0-beta.3', + '3.1.2+scope3.deploy.4821', + '3.1.0-beta.3+sha.a1b2c3d', + ], + pattern='^\\d+\\.\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?(\\+[a-zA-Z0-9.-]+)?$', + ), + ] = None diff --git a/src/adcp/types/domains/extensions/__init__.py b/src/adcp/types/domains/extensions/__init__.py index c4dfcd90b..9e2e13967 100644 --- a/src/adcp/types/domains/extensions/__init__.py +++ b/src/adcp/types/domains/extensions/__init__.py @@ -9,14 +9,14 @@ it from its own schema's module, ``adcp.types.domains.extensions.``. Nothing here is renamed. -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:33:59 UTC +Auto-generated from the generated domain tree. DO NOT EDIT MANUALLY. +Generation date: 2026-10-04 18:45:11 UTC """ # ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.extensions.extension_meta import AdcpExtensionFileSchema +from adcp.types.domains.extensions.extension_meta import AdcpExtensionFileSchema # Explicit exports __all__ = ["AdcpExtensionFileSchema"] diff --git a/src/adcp/types/domains/extensions/extension_meta.py b/src/adcp/types/domains/extensions/extension_meta.py index d5d166dc1..6aa395838 100644 --- a/src/adcp/types/domains/extensions/extension_meta.py +++ b/src/adcp/types/domains/extensions/extension_meta.py @@ -1,18 +1,50 @@ -"""Types declared by the AdCP ``extensions/extension_meta`` schema. +# generated by datamodel-codegen: +# filename: extensions/extension_meta.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.extensions.extension_meta import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Any, Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, Field -from adcp.types.generated_poc.extensions.extension_meta import AdcpExtensionFileSchema -# Explicit exports -__all__ = ["AdcpExtensionFileSchema"] +class AdcpExtensionFileSchema(AdCPBaseModel): + field_schema: Annotated[ + Literal['http://json-schema.org/draft-07/schema#'], Field(alias='$schema') + ] = 'http://json-schema.org/draft-07/schema#' + title: Annotated[str, Field(description='Human-readable title for the extension')] + description: Annotated[str, Field(description='Description of what this extension provides')] + valid_from: Annotated[ + str, + Field( + description="Minimum AdCP version this extension is compatible with (e.g., '2.5'). Extension will be included in all versioned schema builds >= this version.", + pattern='^\\d+\\.\\d+$', + ), + ] + valid_until: Annotated[ + str | None, + Field( + description="Last AdCP version this extension is compatible with (e.g., '3.0'). Omit if extension is still valid for current and future versions.", + pattern='^\\d+\\.\\d+$', + ), + ] = None + docs_url: Annotated[ + AnyUrl | None, Field(description='URL to documentation for implementors of this extension') + ] = None + type: Annotated[ + Literal['object'], + Field(description='Extensions must be objects (data within ext.{namespace})'), + ] = 'object' + properties: Annotated[ + dict[str, Any], + Field(description='Schema properties defining the structure of ext.{namespace} data'), + ] + required: Annotated[ + list[str] | None, Field(description='Required properties within the extension data') + ] = None + additionalProperties: Annotated[ + Any | None, + Field(description='Whether additional properties are allowed in the extension data'), + ] = None diff --git a/src/adcp/types/domains/formats/__init__.py b/src/adcp/types/domains/formats/__init__.py index 6477f6f92..596c05ef5 100644 --- a/src/adcp/types/domains/formats/__init__.py +++ b/src/adcp/types/domains/formats/__init__.py @@ -9,27 +9,27 @@ it from its own schema's module, ``adcp.types.domains.formats.``. Nothing here is renamed. -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:33:59 UTC +Auto-generated from the generated domain tree. DO NOT EDIT MANUALLY. +Generation date: 2026-10-04 18:45:11 UTC """ # ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.formats.canonical._base import ( +from adcp.types.domains.formats.canonical._base import ( CanonicalFormatBase, CompositionModel, ReferenceMutability, RequiredPixelRatio, Slot, ) -from adcp.types.generated_poc.formats.canonical.agent_placement import ( +from adcp.types.domains.formats.canonical.agent_placement import ( CanonicalFormatAgentPlacementAiSurfaceSponsoredPlacement, ) -from adcp.types.generated_poc.formats.canonical.audio_daast import CanonicalFormatDaastAudio -from adcp.types.generated_poc.formats.canonical.audio_hosted import CanonicalFormatHostedAudio -from adcp.types.generated_poc.formats.canonical.audio_vast import CanonicalFormatVastAudio -from adcp.types.generated_poc.formats.canonical.coordinated_placements import ( +from adcp.types.domains.formats.canonical.audio_daast import CanonicalFormatDaastAudio +from adcp.types.domains.formats.canonical.audio_hosted import CanonicalFormatHostedAudio +from adcp.types.domains.formats.canonical.audio_vast import CanonicalFormatVastAudio +from adcp.types.domains.formats.canonical.coordinated_placements import ( AssetSource5, AudioCodec2, CanonicalFormatCoordinatedPlacements, @@ -70,15 +70,15 @@ Transitions4, Transitions5, ) -from adcp.types.generated_poc.formats.canonical.display_tag import CanonicalFormatDisplayTag -from adcp.types.generated_poc.formats.canonical.html5 import CanonicalFormatHtml5Banner -from adcp.types.generated_poc.formats.canonical.image import CanonicalFormatImage -from adcp.types.generated_poc.formats.canonical.image_carousel import CanonicalFormatImageCarousel -from adcp.types.generated_poc.formats.canonical.native_in_feed import CanonicalFormatNativeInFeed -from adcp.types.generated_poc.formats.canonical.responsive_creative import ( +from adcp.types.domains.formats.canonical.display_tag import CanonicalFormatDisplayTag +from adcp.types.domains.formats.canonical.html5 import CanonicalFormatHtml5Banner +from adcp.types.domains.formats.canonical.image import CanonicalFormatImage +from adcp.types.domains.formats.canonical.image_carousel import CanonicalFormatImageCarousel +from adcp.types.domains.formats.canonical.native_in_feed import CanonicalFormatNativeInFeed +from adcp.types.domains.formats.canonical.responsive_creative import ( CanonicalFormatResponsiveCreative, ) -from adcp.types.generated_poc.formats.canonical.seller_rendered_stateful_display import ( +from adcp.types.domains.formats.canonical.seller_rendered_stateful_display import ( CanonicalFormatSellerRenderedStatefulDisplay, TransitionMode9, Transitions10, @@ -87,11 +87,11 @@ Transitions8, Transitions9, ) -from adcp.types.generated_poc.formats.canonical.sponsored_placement import ( +from adcp.types.domains.formats.canonical.sponsored_placement import ( CanonicalFormatSponsoredPlacementRetailMediaCatalogDriven, ) -from adcp.types.generated_poc.formats.canonical.video_hosted import CanonicalFormatHostedVideo -from adcp.types.generated_poc.formats.canonical.video_vast import CanonicalFormatVastVideo +from adcp.types.domains.formats.canonical.video_hosted import CanonicalFormatHostedVideo +from adcp.types.domains.formats.canonical.video_vast import CanonicalFormatVastVideo # Explicit exports __all__ = [ diff --git a/src/adcp/types/domains/formats/canonical/__init__.py b/src/adcp/types/domains/formats/canonical/__init__.py index c0988a5a9..8930ceaf3 100644 --- a/src/adcp/types/domains/formats/canonical/__init__.py +++ b/src/adcp/types/domains/formats/canonical/__init__.py @@ -1,7 +1,3 @@ -"""Public mirror of the AdCP ``formats/canonical`` schema directory. - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:34:03 UTC -""" - -__all__: list[str] = [] +# generated by datamodel-codegen: +# filename: .schema_temp +# timestamp: 2026-05-22T13:15:12+00:00 diff --git a/src/adcp/types/domains/formats/canonical/_base.py b/src/adcp/types/domains/formats/canonical/_base.py index 0200d2433..11adf76c7 100644 --- a/src/adcp/types/domains/formats/canonical/_base.py +++ b/src/adcp/types/domains/formats/canonical/_base.py @@ -1,34 +1,224 @@ -"""Types declared by the AdCP ``formats/canonical/_base`` schema. +# generated by datamodel-codegen: +# filename: formats/canonical/_base.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.formats.canonical._base import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from adcp.types._scalar import ScalarFloat +from pydantic import ConfigDict, Field, StrictBool + +from ...core import downstream_connection_requirement, platform_extension_ref +from ...enums import logo_slot + + +class CompositionModel(StrEnum): + deterministic = 'deterministic' + algorithmic = 'algorithmic' + + +class AssetType(StrEnum): + image = 'image' + video = 'video' + audio = 'audio' + text = 'text' + markdown = 'markdown' + url = 'url' + html = 'html' + css = 'css' + javascript = 'javascript' + vast = 'vast' + daast = 'daast' + display_tag = 'display_tag' + webhook = 'webhook' + brief = 'brief' + catalog = 'catalog' + published_post = 'published_post' + zip = 'zip' + card = 'card' + object = 'object' + pixel_tracker = 'pixel_tracker' + vast_tracker = 'vast_tracker' + daast_tracker = 'daast_tracker' + + +class PixelRatio(ScalarFloat): + __slots__ = () + _constraints = {'gt': 0.0} + + +class RequiredPixelRatio(PixelRatio): + pass + + +class ReferenceMutability(StrEnum): + immutable_snapshot = 'immutable_snapshot' + mutable_requires_reapproval = 'mutable_requires_reapproval' + mutable_auto_recheck = 'mutable_auto_recheck' + + +class Slot(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_group_id: Annotated[ + str, + Field( + description='Canonical asset_group_id from /schemas/core/asset-group-vocabulary.json. Non-canonical IDs are valid but trigger soft warnings.' + ), + ] + asset_type: Annotated[ + AssetType, + Field( + description='Discriminator selecting the asset schema this slot accepts. SDK codegen uses this to type the slot value. `display_tag` is the atomic third-party display representation (URL, inline markup, or paired redirect). `published_post` is an existing-post reference asset. `pixel_tracker` / `vast_tracker` / `daast_tracker` are renderer-fired tracker primitives. `object` is a last-resort fallback.' + ), + ] + required: Annotated[ + StrictBool | None, Field(description='Whether this slot is required for a valid manifest.') + ] = False + min: Annotated[ + SchemaInt | None, Field(description='Minimum count for repeatable / pool slots.', ge=0) + ] = None + max: Annotated[ + SchemaInt | None, Field(description='Maximum count for repeatable / pool slots.', ge=1) + ] = None + max_chars: Annotated[ + SchemaInt | None, + Field( + description="Per-slot character limit. Valid only when `asset_type` is `text`, `markdown`, or `brief`. Mutually exclusive with `max_size_kb` (which applies to binary asset types). Schema enforces via if/then so a producer can't set both on the same slot.", + ge=1, + ), + ] = None + max_size_kb: Annotated[ + SchemaInt | None, + Field( + description="Per-slot file size limit in exact kilobytes, where 1 KB = 1,000 bytes. Valid only when `asset_type` is `image`, `video`, `audio`, or `zip`. Mutually exclusive with `max_chars` (which applies to text asset types). Schema enforces via if/then so a producer can't set both on the same slot.", + ge=1, + ), + ] = None + pixel_ratios: Annotated[ + list[PixelRatio] | None, + Field( + description="Accepted intrinsic-pixel densities for this image-bearing slot. Valid when `asset_type` is `image`, and on a `card` slot where it constrains each card's image media (video media is unaffected). This makes density available to every canonical carrying image assets (native, carousel, responsive, companion images, and image itself), not only `format_kind: image`. When the image canonical also declares top-level `params.pixel_ratios`, the effective set is the intersection; an empty intersection is invalid. One matching asset satisfies the slot unless `required_pixel_ratios` requires rendition coverage.", + min_length=1, + ), + ] = None + required_pixel_ratios: Annotated[ + list[RequiredPixelRatio] | None, + Field( + description='Required density coverage for an image rendition set. Valid only when `asset_type` is `image` and `pixel_ratios` is also declared. Every value MUST appear in the effective accepted set after intersecting any top-level image `params.pixel_ratios`, and the manifest slot value MUST be an array containing exactly one matching image rendition for each required ratio. Other accepted ratios remain optional. For example, `pixel_ratios: [1, 1.5, 2]` with `required_pixel_ratios: [1, 2]` requires the 1x and 2x renditions while making 1.5x optional. SDKs enforce intersection, subset, coverage, and duplicate-ratio rules because JSON Schema draft-07 cannot express them generically.', + min_length=1, + ), + ] = None + logo_slots: Annotated[ + list[logo_slot.LogoSlot] | None, + Field( + description='When `asset_group_id` is `logo`, renderer-facing brand.json logo slots acceptable for this format slot. Producers selecting from brand.json SHOULD prefer `logos[]` entries whose `slots[]` intersects this list, then apply `visual_guidelines.logo_usage_rules[]`.' + ), + ] = None + required_logo_slots: Annotated[ + list[logo_slot.LogoSlot] | None, + Field( + description='Subset of `logo_slots` for which this format expects explicit logo coverage. A manifest or brand-derived logo pool SHOULD include at least one usable logo for each required slot; if coverage is missing, builders SHOULD surface a validation warning or approval mapping instead of guessing from prose.' + ), + ] = None + description: Annotated[ + str | None, + Field(description='Human-readable description of what the slot expects from the buyer.'), + ] = None + consumed_for_production: Annotated[ + StrictBool | None, + Field( + description="Dispatch hint for `build_creative` and v1↔v2 wire translators: when `true`, the slot's value is consumed as INPUT to a production step (host-read script, brief copy fed to generative synthesis, catalog feed driving per-SKU rendering) and is not rendered verbatim. When `false` (default), the slot's value is rendered verbatim on the placement (image bytes, video file, display tag).\n\nMotivates the v1↔v2 dispatch table: pre-v2 buyers shipped production-consumed inputs separately in a `inputs` map on the build_creative request; v2 collapses inputs and rendered assets into a single `assets` map keyed by `asset_group_id`. SDK translators between v1 and v2 use this flag per canonical to know which assets in the v2 manifest map back to v1 `inputs` vs v1 `assets`. Without the per-slot flag the dispatch table lives in adopter code and every SDK gets it slightly different.\n\nProducers SHOULD set this explicitly on slots whose consumption pattern isn't obvious (host-read scripts on `audio_hosted`, briefs on generative `video_hosted`, catalog feeds on `sponsored_placement`). For canonicals where every slot is render-verbatim (`image`, `display_tag`, `video_vast`, `audio_vast`), the default `false` is sufficient and the flag MAY be omitted." + ), + ] = False -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.formats.canonical._base import ( - AssetType, - CanonicalFormatBase, - CompositionModel, - PixelRatio, - ReferenceMutability, - RequiredPixelRatio, - Slot, -) - -# Explicit exports -__all__ = [ - "AssetType", - "CanonicalFormatBase", - "CompositionModel", - "PixelRatio", - "ReferenceMutability", - "RequiredPixelRatio", - "Slot", -] +class CanonicalFormatBase(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + experimental: Annotated[ + StrictBool | None, + Field( + description='When true, this canonical or seller narrowing may not work as declared. Adopters SHOULD preflight it with validate_input or in a sandbox and SHOULD NOT route production budget without testing; experimental status never makes the deprecated v1 path preferable. Drivers include unsettled spec shape, an adopter runtime gap, and custom shapes awaiting promotion. This replaces the earlier status plus runtime_status axes. Sellers SHOULD set experimental whenever a canonical or declaration is not production-ready.' + ), + ] = False + deprecated: Annotated[ + StrictBool | None, + Field( + description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." + ), + ] = False + v1_translatable: Annotated[ + StrictBool | None, + Field( + description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The six inherently-v2 canonicals in 3.2 are `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`, `seller_rendered_stateful_display`, and `coordinated_placements`." + ), + ] = True + since_version: Annotated[ + str | None, + Field( + description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", + pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', + ), + ] = None + migration_target_version: Annotated[ + str | None, + Field( + description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", + pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', + ), + ] = None + composition_model: Annotated[ + CompositionModel | None, + Field( + description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' + ), + ] = None + provenance_required: Annotated[ + StrictBool | None, + Field( + description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' + ), + ] = None + platform_extensions: Annotated[ + list[platform_extension_ref.PlatformExtensionReference] | None, + Field( + description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' + ), + ] = None + synthesis_nondeterministic: Annotated[ + StrictBool | None, + Field( + description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." + ), + ] = False + slots: Annotated[ + list[Slot] | None, + Field( + description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." + ), + ] = None + required_connections: Annotated[ + list[downstream_connection_requirement.DownstreamConnectionRequirement] | None, + Field( + description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' + ), + ] = None + reference_mutability: Annotated[ + ReferenceMutability | None, + Field( + description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' + ), + ] = None + production_window_business_days: Annotated[ + SchemaInt | None, + Field( + description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', + ge=0, + ), + ] = None diff --git a/src/adcp/types/domains/formats/canonical/agent_placement.py b/src/adcp/types/domains/formats/canonical/agent_placement.py index fb52bbbb3..3caf1718c 100644 --- a/src/adcp/types/domains/formats/canonical/agent_placement.py +++ b/src/adcp/types/domains/formats/canonical/agent_placement.py @@ -1,21 +1,90 @@ -"""Types declared by the AdCP ``formats/canonical/agent_placement`` schema. +# generated by datamodel-codegen: +# filename: formats/canonical/agent_placement.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.formats.canonical.agent_placement import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import SchemaInt +from pydantic import ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations +from ._base import CanonicalFormatBase + + +class OutputModality(StrEnum): + text = 'text' + audio = 'audio' + card = 'card' -from adcp.types.generated_poc.formats.canonical.agent_placement import ( - CanonicalFormatAgentPlacementAiSurfaceSponsoredPlacement, - OutputModality, -) -# Explicit exports -__all__ = ["CanonicalFormatAgentPlacementAiSurfaceSponsoredPlacement", "OutputModality"] +class CanonicalFormatAgentPlacementAiSurfaceSponsoredPlacement(CanonicalFormatBase): + model_config = ConfigDict( + extra='allow', + ) + experimental: Annotated[ + Any | None, + Field( + description="Marked experimental at 3.1 GA: the canonical's tracking model (mention-level impression + attribution, postback shape, cross-surface dedup) is intentionally underspecified for 3.1. Adopters claiming `agent_placement` ship private tracking integrations; buyer agents MUST treat attribution as adapter-defined until the 3.2 tracking-macro spec lands. Promotion to non-experimental gated on the 3.2 tracking-contract spec." + ), + ] = True + v1_translatable: Annotated[ + Any | None, + Field( + description="Inherently new in v2 — AI-surface sponsored mentions weren't expressible as v1 named formats. SDKs MUST NOT emit `FORMAT_PROJECTION_FAILED` for products using this canonical; the v1-unreachability is structural." + ), + ] = False + slots: Annotated[ + Any | None, + Field( + description="agent_placement has minimal buyer-shipped slots — the surface composes the rendered output from brand context (resolved via the manifest's top-level `brand` BrandRef) plus optional offering_ref and landing_page_url assets. None of these assets are rendered verbatim by the buyer; the agent chooses how to use them." + ), + ] = [ + {'asset_group_id': 'offering_ref', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, + ] + output_modality: Annotated[ + OutputModality | None, + Field( + description='How the surface presents the mention. `text` = inline text (chat, search snippet). `audio` = TTS-synthesized voice. `card` = structured card with optional image + text.' + ), + ] = None + max_mention_length_chars: Annotated[ + SchemaInt | None, + Field( + description='For text output: maximum length of the surface-composed mention text.', + ge=1, + ), + ] = None + max_mention_duration_ms: Annotated[ + SchemaInt | None, + Field( + description='For audio output: maximum duration of the spoken mention in milliseconds.', + ge=1, + ), + ] = None + supports_offering_reference: Annotated[ + StrictBool | None, + Field( + description='Whether the product accepts an offering reference (specific product/service to promote within the mention) in addition to brand context.' + ), + ] = None + supports_landing_page_url: Annotated[ + StrictBool | None, + Field( + description='Whether the surface attaches a landing page URL to the mention (citation, learn-more link).' + ), + ] = None + tone_constraints: Annotated[ + list[str] | None, + Field( + description="**Advisory only.** Buyer-declared brand-voice preferences the surface SHOULD honor (e.g., ['formal', 'no_superlatives']). LLM/agentic surfaces have no protocol-level mechanism to verify enforcement — adopters that need hard guarantees should rely on brand.json voice declarations and post-mention review rather than this field. Future revisions may tie this to a structured tone vocabulary; for now treat as free-text guidance." + ), + ] = None + disclosure_required: Annotated[ + StrictBool | None, + Field( + description='Whether the surface must include an explicit sponsorship disclosure label.' + ), + ] = None diff --git a/src/adcp/types/domains/formats/canonical/audio_daast.py b/src/adcp/types/domains/formats/canonical/audio_daast.py index 75556fab5..992d7d5e3 100644 --- a/src/adcp/types/domains/formats/canonical/audio_daast.py +++ b/src/adcp/types/domains/formats/canonical/audio_daast.py @@ -1,21 +1,68 @@ -"""Types declared by the AdCP ``formats/canonical/audio_daast`` schema. +# generated by datamodel-codegen: +# filename: formats/canonical/audio_daast.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.formats.canonical.audio_daast import +from adcp.types._scalar import ScalarInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Any -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import SchemaInt +from pydantic import ConfigDict, Field, StrictBool + +from ...core import daast_tracker_constraints +from ...enums import daast_version as daast_version_1 +from ._base import CanonicalFormatBase + + +class DurationMsRangeItem(ScalarInt): + __slots__ = () + _constraints = {'ge': 0} -from adcp.types.generated_poc.formats.canonical.audio_daast import ( - CanonicalFormatDaastAudio, - DurationMsRangeItem, -) -# Explicit exports -__all__ = ["CanonicalFormatDaastAudio", "DurationMsRangeItem"] +class CanonicalFormatDaastAudio(CanonicalFormatBase): + model_config = ConfigDict( + extra='allow', + ) + slots: Annotated[ + Any | None, + Field( + description="Default slots for audio_daast canonical. Buyer ships a DAAST tag (URL or inline XML, 1.0 or 1.1) plus an optional clickthrough URL. Tracking events are inherent to DAAST and don't require explicit slots." + ), + ] = [ + {'asset_group_id': 'daast_tag', 'asset_type': 'daast', 'required': True}, + {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, + ] + daast_version: Annotated[ + daast_version_1.DaastVersion | None, + Field( + deprecated=True, + description='Deprecated one-element alias for `daast_versions`. Producers use either the singular legacy alias or the plural 3.2 field, never both.', + ), + ] = None + daast_versions: Annotated[ + daast_tracker_constraints.DaastVersions | None, + Field( + description="Accepted DAAST versions for this format option. A tracker execution selector's daast_versions must be a nonempty subset of this set." + ), + ] = None + duration_ms_range: Annotated[ + list[DurationMsRangeItem] | None, + Field( + description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', + max_length=2, + min_length=2, + ), + ] = None + duration_ms_exact: Annotated[ + SchemaInt | None, + Field( + description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', + ge=1, + ), + ] = None + linear_required: StrictBool | None = None + max_wrapper_depth: Annotated[SchemaInt | None, Field(ge=0)] = None + ssl_required: StrictBool | None = None + companion_image_required: StrictBool | None = None diff --git a/src/adcp/types/domains/formats/canonical/audio_hosted.py b/src/adcp/types/domains/formats/canonical/audio_hosted.py index fc6e13dab..bc9bc1480 100644 --- a/src/adcp/types/domains/formats/canonical/audio_hosted.py +++ b/src/adcp/types/domains/formats/canonical/audio_hosted.py @@ -1,34 +1,125 @@ -"""Types declared by the AdCP ``formats/canonical/audio_hosted`` schema. +# generated by datamodel-codegen: +# filename: formats/canonical/audio_hosted.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.formats.canonical.audio_hosted import +from adcp.types._scalar import ScalarInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any + +from adcp.types.base import SchemaInt +from pydantic import ConfigDict, Field, StrictBool, StrictFloat + +from ._base import CanonicalFormatBase + + +class DurationMsRange(ScalarInt): + __slots__ = () + _constraints = {'ge': 0} + + +class AudioCodec(StrEnum): + mp3 = 'mp3' + aac = 'aac' + wav = 'wav' + opus = 'opus' + flac = 'flac' + + +class AudioSampleRate(ScalarInt): + __slots__ = () + _constraints = {'ge': 1} + + +class AudioChannel(StrEnum): + mono = 'mono' + stereo = 'stereo' + + +class AssetSource(StrEnum): + buyer_uploaded = 'buyer_uploaded' + publisher_host_recorded = 'publisher_host_recorded' + seller_pre_rendered_from_brief = 'seller_pre_rendered_from_brief' + seller_human_designed = 'seller_human_designed' + agent_synthesized = 'agent_synthesized' + publisher_owned_reference = 'publisher_owned_reference' + + +class BuyerAssetAcceptance(StrEnum): + accepted = 'accepted' + rejected = 'rejected' -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.formats.canonical.audio_hosted import ( - AssetSource, - AudioChannel, - AudioCodec, - AudioSampleRate, - BuyerAssetAcceptance, - CanonicalFormatHostedAudio, - DurationMsRange, -) - -# Explicit exports -__all__ = [ - "AssetSource", - "AudioChannel", - "AudioCodec", - "AudioSampleRate", - "BuyerAssetAcceptance", - "CanonicalFormatHostedAudio", - "DurationMsRange", -] +class CanonicalFormatHostedAudio(CanonicalFormatBase): + model_config = ConfigDict( + extra='allow', + ) + slots: Annotated[ + Any | None, + Field( + description="Default slots for buyer-uploaded audio. Host-read products override with a `script` (asset_type: text) or `creative_brief` (asset_type: brief) slot in place of `audio_main`, plus `asset_source: 'publisher_host_recorded'` and `buyer_asset_acceptance: 'rejected'`. TTS-from-script products override similarly with `asset_source: 'seller_pre_rendered_from_brief'`." + ), + ] = [ + {'asset_group_id': 'audio_main', 'asset_type': 'audio', 'required': True}, + {'asset_group_id': 'companion_image', 'asset_type': 'image', 'required': False}, + {'asset_group_id': 'brand_name', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, + ] + duration_ms_range: Annotated[ + list[DurationMsRange | None] | None, + Field( + description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', + max_length=2, + min_length=2, + ), + ] = None + duration_ms_exact: Annotated[ + SchemaInt | None, + Field( + description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', + ge=1, + ), + ] = None + audio_codecs: list[AudioCodec] | None = None + audio_sample_rates: list[AudioSampleRate] | None = None + audio_channels: list[AudioChannel] | None = None + min_bitrate_kbps: Annotated[SchemaInt | None, Field(ge=1)] = None + max_bitrate_kbps: Annotated[SchemaInt | None, Field(ge=1)] = None + max_file_size_mb: Annotated[ + StrictFloat | None, + Field( + description='Maximum hosted audio file size in decimal megabytes. Agents that proxy or cache media SHOULD advertise their effective transport ceiling here.', + gt=0.0, + ), + ] = None + loudness_lufs: Annotated[ + StrictFloat | None, + Field( + description='Required integrated loudness in LUFS (typical: -16 for streaming/podcast, -23 for broadcast). Negative values.' + ), + ] = None + loudness_tolerance_db: Annotated[ + StrictFloat | None, + Field(description='Permitted deviation from loudness_lufs in dB.', ge=0.0), + ] = None + true_peak_dbfs: Annotated[ + StrictFloat | None, Field(description='Maximum true-peak level in dBFS (typical: -2).') + ] = None + asset_source: Annotated[ + AssetSource | None, + Field( + description="Where the rendered audio bytes come from. Single shared enum across canonicals (see `image.json#asset_source` for the full semantics). `publisher_host_recorded`: the publisher's host records the audio (podcast host-read pattern); buyer must use the publisher's build_creative capability. `publisher_owned_reference` is valid only when the product accepts a reference asset whose publisher-owned source resolves to playable audio. `publisher_host_recorded` remains the normal audio-specific host-read value." + ), + ] = AssetSource.buyer_uploaded + buyer_asset_acceptance: Annotated[ + BuyerAssetAcceptance | None, + Field( + description="Whether the product accepts buyer-uploaded audio. When `rejected`, the buyer cannot ship an audio asset directly — they must use build_creative (or sync_creatives with brief inputs) so the seller produces the audio. Combined with `asset_source`, lets a product declare 'I produce audio from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`)." + ), + ] = BuyerAssetAcceptance.accepted + companion_image_required: StrictBool | None = None + companion_image_aspect_ratio: str | None = None + companion_image_max_file_size_kb: Annotated[SchemaInt | None, Field(ge=1)] = None + brand_name_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None diff --git a/src/adcp/types/domains/formats/canonical/audio_vast.py b/src/adcp/types/domains/formats/canonical/audio_vast.py index 48098647d..841bbe664 100644 --- a/src/adcp/types/domains/formats/canonical/audio_vast.py +++ b/src/adcp/types/domains/formats/canonical/audio_vast.py @@ -1,21 +1,83 @@ -"""Types declared by the AdCP ``formats/canonical/audio_vast`` schema. +# generated by datamodel-codegen: +# filename: formats/canonical/audio_vast.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.formats.canonical.audio_vast import +from adcp.types._scalar import ScalarInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Any -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import SchemaInt +from pydantic import ConfigDict, Field, StrictBool + +from ...core import vast_media_file_requirements +from ...enums import vast_version as vast_version_1 +from ._base import CanonicalFormatBase + + +class DurationMsRangeItem(ScalarInt): + __slots__ = () + _constraints = {'ge': 0} -from adcp.types.generated_poc.formats.canonical.audio_vast import ( - CanonicalFormatVastAudio, - DurationMsRangeItem, -) -# Explicit exports -__all__ = ["CanonicalFormatVastAudio", "DurationMsRangeItem"] +class CanonicalFormatVastAudio(CanonicalFormatBase): + model_config = ConfigDict( + extra='allow', + ) + slots: Annotated[ + Any | None, + Field( + description='Default slots for the audio_vast canonical. The buyer supplies a VAST tag (URL or inline XML) plus an optional clickthrough URL, which falls back to the VAST ClickThrough when omitted. Tracking events and companion creatives carried by the VAST document do not require separate slots.' + ), + ] = [ + {'asset_group_id': 'vast_tag', 'asset_type': 'vast', 'required': True}, + {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, + ] + vast_versions: Annotated[ + list[vast_version_1.VastVersion] | None, + Field( + description='VAST audio versions accepted by this product format option. Standardized audio support begins at VAST 4.1; listing 2.0, 3.0, or 4.0 explicitly declares seller-supported legacy audio interoperability rather than standards-conformant VAST audio. The asset declares exactly one vast_version; compatibility requires membership in this set and the seller-wide execution set.', + min_length=1, + ), + ] = None + vast_version: Annotated[ + vast_version_1.VastVersion | None, + Field( + deprecated=True, + description='Deprecated one-element alias for vast_versions. Producers use either the singular legacy alias or the plural 3.2 field, never both. VAST 4.1+ is the standards-conformant audio profile; older values declare legacy audio interoperability.', + ), + ] = None + media_file_requirements: Annotated[ + vast_media_file_requirements.VastMediafileRequirements | None, + Field( + description='Technical acceptance constraints for alternative audio MediaFile renditions in each resolved InLine linear creative. Declared MIME types must be audio/*; visual-dimension constraints are invalid because VAST represents audio MediaFiles with width and height 0.' + ), + ] = None + duration_ms_range: Annotated[ + list[DurationMsRangeItem] | None, + Field( + description='[min, max] duration in milliseconds. duration_ms_exact takes precedence when both fields are present; SDKs SHOULD warn when both are supplied.', + max_length=2, + min_length=2, + ), + ] = None + duration_ms_exact: Annotated[ + SchemaInt | None, + Field( + description='When set, the resolved audio creative duration must equal this value. Takes precedence over duration_ms_range.', + ge=1, + ), + ] = None + skippable_after_ms: Annotated[ + SchemaInt | None, + Field(description='When skippable, the buyer-side skip threshold in milliseconds.', ge=0), + ] = None + max_wrapper_depth: Annotated[ + SchemaInt | None, Field(description='Maximum VAST wrapper redirect depth permitted.', ge=0) + ] = None + ssl_required: StrictBool | None = None + companion_image_required: Annotated[ + StrictBool | None, + Field(description='Whether the resolved VAST audio ad must include a companion creative.'), + ] = None diff --git a/src/adcp/types/domains/formats/canonical/coordinated_placements.py b/src/adcp/types/domains/formats/canonical/coordinated_placements.py index c75dcf306..1cf202bf3 100644 --- a/src/adcp/types/domains/formats/canonical/coordinated_placements.py +++ b/src/adcp/types/domains/formats/canonical/coordinated_placements.py @@ -1,204 +1,3101 @@ -"""Types declared by the AdCP ``formats/canonical/coordinated_placements`` schema. +# generated by datamodel-codegen: +# filename: formats/canonical/coordinated_placements.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.formats.canonical.coordinated_placements import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from adcp.types._scalar import ScalarFloat, ScalarInt, ScalarStr +from pydantic import ConfigDict, Field, StrictBool, StrictFloat + +from ...core import canvas_constraint, daast_tracker_constraints +from ...core import format_option_ref as format_option_ref_1 +from ...core import placement_ref as placement_ref_1 +from ...core import vast_media_file_requirements +from ...enums import activation_method, catalog_type +from ...enums import ctv_ad_experience as ctv_ad_experience_1 +from ...enums import daast_version as daast_version_1 +from ...enums import motion_level as motion_level_1 +from ...enums import vast_version as vast_version_1 +from ._base import CanonicalFormatBase + + +class ServingPolicy(StrEnum): + seller_served_only = 'seller_served_only' + third_party_allowed = 'third_party_allowed' + + +class MotionLevel(StrEnum): + static = 'static' + limited_motion = 'limited_motion' + + +class Size(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + width: Annotated[SchemaInt, Field(ge=1)] + height: Annotated[SchemaInt, Field(ge=1)] + + +class PixelRatio(ScalarFloat): + __slots__ = () + _constraints = {'gt': 0.0} + + +class ImageFormat(StrEnum): + jpg = 'jpg' + jpeg = 'jpeg' + png = 'png' + gif = 'gif' + webp = 'webp' + svg = 'svg' + + +class AssetSource(StrEnum): + buyer_uploaded = 'buyer_uploaded' + publisher_host_recorded = 'publisher_host_recorded' + seller_pre_rendered_from_brief = 'seller_pre_rendered_from_brief' + seller_human_designed = 'seller_human_designed' + agent_synthesized = 'agent_synthesized' + publisher_owned_reference = 'publisher_owned_reference' + + +class BuyerAssetAcceptance(StrEnum): + accepted = 'accepted' + rejected = 'rejected' + + +class MraidVersion(StrEnum): + field_2_0 = '2.0' + field_3_0 = '3.0' + + +class ClicktagMacro(StrEnum): + clickTag = 'clickTag' + clickTAG = 'clickTAG' + + +class SupportedTagType(StrEnum): + iframe = 'iframe' + javascript = 'javascript' + field_1x1_redirect = '1x1_redirect' + + +class SupportedDeliveryType(StrEnum): + tag_url = 'tag_url' + inline_markup = 'inline_markup' + paired_redirect = 'paired_redirect' + + +class AllowedCardMediaAssetType(StrEnum): + image = 'image' + video = 'video' + + +class Orientation(StrEnum): + vertical = 'vertical' + horizontal = 'horizontal' + square = 'square' + + +class DurationMsRange(ScalarInt): + __slots__ = () + _constraints = {'ge': 0} + + +class VideoCodec(StrEnum): + h264 = 'h264' + h265 = 'h265' + vp8 = 'vp8' + vp9 = 'vp9' + av1 = 'av1' + prores = 'prores' + + +class AudioCodec(StrEnum): + aac = 'aac' + mp3 = 'mp3' + opus = 'opus' + pcm = 'pcm' + + +class Container(StrEnum): + mp4 = 'mp4' + webm = 'webm' + mov = 'mov' + + +class Captions(StrEnum): + required = 'required' + recommended = 'recommended' + not_required = 'not_required' + + +class CompanionBannerWidth(ScalarInt): + __slots__ = () + _constraints = {'ge': 1} + + +class CompanionBannerHeight(CompanionBannerWidth): + pass + + +class VpaidVersion(StrEnum): + field_1_0 = '1.0' + field_2_0 = '2.0' + + +class DurationMsRangeItem(DurationMsRange): + pass + + +class CreativeType(StrEnum): + linear = 'linear' + nonlinear = 'nonlinear' + either = 'either' + + +class AudioCodec2(StrEnum): + mp3 = 'mp3' + aac = 'aac' + wav = 'wav' + opus = 'opus' + flac = 'flac' + + +class AudioSampleRate(CompanionBannerWidth): + pass + + +class AudioChannel(StrEnum): + mono = 'mono' + stereo = 'stereo' + + +class FanoutMode(StrEnum): + per_item = 'per_item' + multi_item_in_creative = 'multi_item_in_creative' + single_item = 'single_item' + + +class SupportedIdType(StrEnum): + asin = 'asin' + sku = 'sku' + gtin = 'gtin' + offering_id = 'offering_id' + store_id = 'store_id' + hotel_id = 'hotel_id' + flight_id = 'flight_id' + vehicle_id = 'vehicle_id' + listing_id = 'listing_id' + program_id = 'program_id' + destination_id = 'destination_id' + app_id = 'app_id' + job_id = 'job_id' + + +class ItemProductionModel(StrEnum): + buyer_uploaded = 'buyer_uploaded' + seller_pre_rendered_from_brief = 'seller_pre_rendered_from_brief' + seller_human_designed = 'seller_human_designed' + agent_synthesized = 'agent_synthesized' + + +class MenuPlacement(StrEnum): + tile = 'tile' + headline_banner = 'headline_banner' + + +class FocusBehavior(StrEnum): + none = 'none' + autoplay_muted = 'autoplay_muted' + autoplay_sound = 'autoplay_sound' + + +class MainImageSize(Size): + pass + + +class IconSize(Size): + pass + + +class ImageFormat1(StrEnum): + jpg = 'jpg' + jpeg = 'jpeg' + png = 'png' + gif = 'gif' + webp = 'webp' + + +class AssetSource5(StrEnum): + buyer_uploaded = 'buyer_uploaded' + seller_pre_rendered_from_brief = 'seller_pre_rendered_from_brief' + seller_human_designed = 'seller_human_designed' + agent_synthesized = 'agent_synthesized' + publisher_owned_reference = 'publisher_owned_reference' + + +class OutputModality(StrEnum): + text = 'text' + audio = 'audio' + card = 'card' + + +class SupplyMode(StrEnum): + components = 'components' + rendered_canvases = 'rendered_canvases' + layered_source = 'layered_source' + + +class Anchoring(StrEnum): + inline = 'inline' + sticky_top = 'sticky_top' + sticky_bottom = 'sticky_bottom' + overlay = 'overlay' + fullscreen_overlay = 'fullscreen_overlay' + underlay = 'underlay' + + +class SlotBinding(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[a-z0-9_]+$'} + + +class Motion(StrEnum): + static = 'static' + animated = 'animated' + + +class WidthRangeItem(CompanionBannerWidth): + pass + + +class WidthMode(StrEnum): + full_bleed = 'full_bleed' + gutter_residual = 'gutter_residual' + + +class HeightRangeItem(CompanionBannerWidth): + pass + + +class Breakpoint(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + breakpoint_id: Annotated[ + str, + Field( + description='Stable identifier used by `state_canvases[].breakpoint_id`.', + pattern='^[a-z][a-z0-9_]*$', + ), + ] + width: Annotated[SchemaInt | None, Field(ge=1)] = None + width_range: Annotated[ + list[WidthRangeItem] | None, + Field( + description='Inclusive [minimum, maximum] accepted canvas width. The first value MUST be no greater than the second; semantic validators enforce ordering.', + max_length=2, + min_length=2, + ), + ] = None + width_mode: Annotated[ + WidthMode | None, + Field( + description='Fluid width classes. `full_bleed`: edge-to-edge viewport width (IAB adhesion, topscroll). `gutter_residual`: the margin remaining beside the page content column (desktop skins). Fluid breakpoints cannot be targeted by `rendered_canvases` supply — semantic validation rejects that pairing.' + ), + ] = None + height: Annotated[SchemaInt | None, Field(ge=1)] = None + height_range: Annotated[ + list[HeightRangeItem] | None, + Field( + description='Inclusive [minimum, maximum] accepted canvas height. The first value MUST be no greater than the second; semantic validators enforce ordering.', + max_length=2, + min_length=2, + ), + ] = None + viewport_height_percent: Annotated[ + StrictFloat | None, + Field( + description='Height as a percentage of the viewport (topscroll 80%, mobile skin panel 15%). Fluid; cannot be targeted by `rendered_canvases` supply.', + gt=0.0, + le=100.0, + ), + ] = None + canvas_aspect_ratio: Annotated[ + str | None, + Field( + description='Aspect ratio the rendered canvas maintains across the size range (IAB flexible units, ratio-bucket responsive). When both fixed `width` and `height` are declared, the ratio MUST be consistent with them.', + pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', + ), + ] = None + + +class State(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + state_id: Annotated[ + str, + Field( + description='Stable identifier used by `state_canvases[].state_id` and `state_click_urls[].state_id`.', + pattern='^[a-z][a-z0-9_]*$', + ), + ] + anchoring: Annotated[ + Anchoring, + Field( + description='`underlay` renders the canvas beneath page content, which scrolls over it (IAB New Ad Portfolio underlay class: skins, reveal units). Transitions into `overlay`/`fullscreen_overlay` states SHOULD be user-initiated; non-user-action entries emit LEAN policy warnings.' + ), + ] + slot_bindings: Annotated[ + list[SlotBinding] | None, + Field( + description='Asset group IDs rendered in this state (components mode). Makes the template executable: given components and bindings, assembly is deterministic. Every value MUST resolve to a declared slot. Omitted means all supplied component slots may render.' + ), + ] = None + motion: Annotated[ + Motion | None, + Field( + description='Whether the seller-rendered layout animates within this state (attract loops, load animations). Intra-state animation is seller-rendered; buyer canvases stay static images.' + ), + ] = Motion.static + max_animation_s: Annotated[ + StrictFloat | None, + Field( + description='Upper bound on intra-state animation duration. Required when `motion` is `animated`.', + gt=0.0, + ), + ] = None + breakpoints: Annotated[list[Breakpoint], Field(min_length=1)] + close_affordance: Annotated[ + StrictBool, + Field( + description='Whether this state visibly renders a seller-controlled close affordance. When true, rendering MUST follow IAB New Ad Portfolio close-button guidance (top-right, minimum 50×50 dp, available from state entry).' + ), + ] + + +class Reveal(StrEnum): + none = 'none' + clip_window = 'clip_window' + scroll_parallax = 'scroll_parallax' + + +class Trigger(StrEnum): + timer = 'timer' + in_view_timer = 'in_view_timer' + scroll_threshold = 'scroll_threshold' + scroll_progress = 'scroll_progress' + user_action = 'user_action' + media_event = 'media_event' + + +class Input(StrEnum): + tap = 'tap' + hover = 'hover' + swipe_up = 'swipe_up' + swipe_down = 'swipe_down' + swipe_left = 'swipe_left' + swipe_right = 'swipe_right' + scroll = 'scroll' + expand_control = 'expand_control' + collapse_control = 'collapse_control' + + +class MediaEvent(StrEnum): + video_start = 'video_start' + video_complete = 'video_complete' + + +class Direction(StrEnum): + down = 'down' + up = 'up' + + +class TransitionMode(StrEnum): + instant = 'instant' + animated = 'animated' + scroll_linked = 'scroll_linked' + instant_1 = 'instant' + animated_1 = 'animated' + + +class ScrollReference(StrEnum): + document_progress = 'document_progress' + containing_scroller_progress = 'containing_scroller_progress' + + +class Transitions(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + transition_id: Annotated[ + str, + Field( + description='Stable transition identifier for preview and reporting.', + pattern='^[a-z][a-z0-9_]*$', + ), + ] + from_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] + to_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] + trigger: Annotated[ + Literal['timer'], + Field( + description='Cause of the transition. `timer` counts from state entry; `in_view_timer` counts viewable time in the current state (industry auto-collapse is N seconds in view). `media_event` fires on `video_main` playback milestones. The outcome is expressed separately by `to_state_id`.' + ), + ] = 'timer' + input: Annotated[ + Input | None, + Field( + description='Input driving a user or scroll transition. `hover` expansion is disallowed by IAB NAP guidance and emits a LEAN policy warning visible to buyers.' + ), + ] = None + media_event: Annotated[ + MediaEvent | None, + Field( + description='Playback milestone of `video_main` driving a `media_event` transition (endframes, collapse-on-complete).' + ), + ] = None + direction: Annotated[ + Direction | None, + Field( + description='Scroll direction that arms a `scroll_threshold` transition; enables direction-aware expand/collapse cycles. Re-crossing in the opposite direction does not re-fire this transition.' + ), + ] = Direction.down + transition_mode: Annotated[ + TransitionMode, + Field( + description='`scroll_linked` continuously interpolates the seller-owned layout between the two declared endpoint states; it is valid only with trigger `scroll_progress` and does not permit buyer scripting.' + ), + ] + delay_ms: Annotated[ + SchemaInt, + Field( + description='For `timer`: delay after `from_state_id` activates (re-entry restarts it). For `in_view_timer`: accumulated viewable milliseconds in `from_state_id`. Timer-class transitions in a state cycle MUST declare at least 1000 (anti-strobe floor).', + ge=0, + ), + ] + duration_ms: Annotated[ + SchemaInt | None, + Field(description='Duration of a seller-rendered animated transition.', ge=0), + ] = None + scroll_threshold_percent: Annotated[ + StrictFloat | None, + Field( + description='Viewport/page scroll threshold that starts a `scroll_threshold` transition.', + ge=0.0, + le=100.0, + ), + ] = None + scroll_reference: Annotated[ + ScrollReference | None, + Field( + description='Reference frame for scroll percentages. Progress is `scroll_offset / max(scroll_extent - viewport_extent, 1) * 100`, measured on either the page document or the nearest seller-declared containing scroller.' + ), + ] = None + scroll_start_percent: Annotated[ + StrictFloat | None, + Field( + description='Start of the bounded scroll interval for a `scroll_progress` transition.', + ge=0.0, + le=100.0, + ), + ] = None + scroll_end_percent: Annotated[ + StrictFloat | None, + Field( + description='End of the bounded scroll interval for a `scroll_progress` transition. MUST be greater than `scroll_start_percent`.', + ge=0.0, + le=100.0, + ), + ] = None + preserve_playback: Annotated[ + StrictBool | None, + Field( + description='Whether `video_main` continues without restart while the seller changes state.' + ), + ] = False + + +class Transitions1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + transition_id: Annotated[ + str, + Field( + description='Stable transition identifier for preview and reporting.', + pattern='^[a-z][a-z0-9_]*$', + ), + ] + from_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] + to_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] + trigger: Annotated[ + Literal['in_view_timer'], + Field( + description='Cause of the transition. `timer` counts from state entry; `in_view_timer` counts viewable time in the current state (industry auto-collapse is N seconds in view). `media_event` fires on `video_main` playback milestones. The outcome is expressed separately by `to_state_id`.' + ), + ] = 'in_view_timer' + input: Annotated[ + Input | None, + Field( + description='Input driving a user or scroll transition. `hover` expansion is disallowed by IAB NAP guidance and emits a LEAN policy warning visible to buyers.' + ), + ] = None + media_event: Annotated[ + MediaEvent | None, + Field( + description='Playback milestone of `video_main` driving a `media_event` transition (endframes, collapse-on-complete).' + ), + ] = None + direction: Annotated[ + Direction | None, + Field( + description='Scroll direction that arms a `scroll_threshold` transition; enables direction-aware expand/collapse cycles. Re-crossing in the opposite direction does not re-fire this transition.' + ), + ] = Direction.down + transition_mode: Annotated[ + TransitionMode, + Field( + description='`scroll_linked` continuously interpolates the seller-owned layout between the two declared endpoint states; it is valid only with trigger `scroll_progress` and does not permit buyer scripting.' + ), + ] + delay_ms: Annotated[ + SchemaInt, + Field( + description='For `timer`: delay after `from_state_id` activates (re-entry restarts it). For `in_view_timer`: accumulated viewable milliseconds in `from_state_id`. Timer-class transitions in a state cycle MUST declare at least 1000 (anti-strobe floor).', + ge=0, + ), + ] + duration_ms: Annotated[ + SchemaInt | None, + Field(description='Duration of a seller-rendered animated transition.', ge=0), + ] = None + scroll_threshold_percent: Annotated[ + StrictFloat | None, + Field( + description='Viewport/page scroll threshold that starts a `scroll_threshold` transition.', + ge=0.0, + le=100.0, + ), + ] = None + scroll_reference: Annotated[ + ScrollReference | None, + Field( + description='Reference frame for scroll percentages. Progress is `scroll_offset / max(scroll_extent - viewport_extent, 1) * 100`, measured on either the page document or the nearest seller-declared containing scroller.' + ), + ] = None + scroll_start_percent: Annotated[ + StrictFloat | None, + Field( + description='Start of the bounded scroll interval for a `scroll_progress` transition.', + ge=0.0, + le=100.0, + ), + ] = None + scroll_end_percent: Annotated[ + StrictFloat | None, + Field( + description='End of the bounded scroll interval for a `scroll_progress` transition. MUST be greater than `scroll_start_percent`.', + ge=0.0, + le=100.0, + ), + ] = None + preserve_playback: Annotated[ + StrictBool | None, + Field( + description='Whether `video_main` continues without restart while the seller changes state.' + ), + ] = False + + +class Transitions2(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + transition_id: Annotated[ + str, + Field( + description='Stable transition identifier for preview and reporting.', + pattern='^[a-z][a-z0-9_]*$', + ), + ] + from_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] + to_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] + trigger: Annotated[ + Literal['scroll_threshold'], + Field( + description='Cause of the transition. `timer` counts from state entry; `in_view_timer` counts viewable time in the current state (industry auto-collapse is N seconds in view). `media_event` fires on `video_main` playback milestones. The outcome is expressed separately by `to_state_id`.' + ), + ] = 'scroll_threshold' + input: Annotated[ + Literal['scroll'], + Field( + description='Input driving a user or scroll transition. `hover` expansion is disallowed by IAB NAP guidance and emits a LEAN policy warning visible to buyers.' + ), + ] = 'scroll' + media_event: Annotated[ + MediaEvent | None, + Field( + description='Playback milestone of `video_main` driving a `media_event` transition (endframes, collapse-on-complete).' + ), + ] = None + direction: Annotated[ + Direction | None, + Field( + description='Scroll direction that arms a `scroll_threshold` transition; enables direction-aware expand/collapse cycles. Re-crossing in the opposite direction does not re-fire this transition.' + ), + ] = Direction.down + transition_mode: Annotated[ + TransitionMode, + Field( + description='`scroll_linked` continuously interpolates the seller-owned layout between the two declared endpoint states; it is valid only with trigger `scroll_progress` and does not permit buyer scripting.' + ), + ] + delay_ms: Annotated[ + SchemaInt | None, + Field( + description='For `timer`: delay after `from_state_id` activates (re-entry restarts it). For `in_view_timer`: accumulated viewable milliseconds in `from_state_id`. Timer-class transitions in a state cycle MUST declare at least 1000 (anti-strobe floor).', + ge=0, + ), + ] = None + duration_ms: Annotated[ + SchemaInt | None, + Field(description='Duration of a seller-rendered animated transition.', ge=0), + ] = None + scroll_threshold_percent: Annotated[ + StrictFloat, + Field( + description='Viewport/page scroll threshold that starts a `scroll_threshold` transition.', + ge=0.0, + le=100.0, + ), + ] + scroll_reference: Annotated[ + ScrollReference, + Field( + description='Reference frame for scroll percentages. Progress is `scroll_offset / max(scroll_extent - viewport_extent, 1) * 100`, measured on either the page document or the nearest seller-declared containing scroller.' + ), + ] + scroll_start_percent: Annotated[ + StrictFloat | None, + Field( + description='Start of the bounded scroll interval for a `scroll_progress` transition.', + ge=0.0, + le=100.0, + ), + ] = None + scroll_end_percent: Annotated[ + StrictFloat | None, + Field( + description='End of the bounded scroll interval for a `scroll_progress` transition. MUST be greater than `scroll_start_percent`.', + ge=0.0, + le=100.0, + ), + ] = None + preserve_playback: Annotated[ + StrictBool | None, + Field( + description='Whether `video_main` continues without restart while the seller changes state.' + ), + ] = False + + +class TransitionMode3(StrEnum): + instant = 'instant' + animated = 'animated' + scroll_linked = 'scroll_linked' + + +class Transitions3(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + transition_id: Annotated[ + str, + Field( + description='Stable transition identifier for preview and reporting.', + pattern='^[a-z][a-z0-9_]*$', + ), + ] + from_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] + to_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] + trigger: Annotated[ + Literal['scroll_progress'], + Field( + description='Cause of the transition. `timer` counts from state entry; `in_view_timer` counts viewable time in the current state (industry auto-collapse is N seconds in view). `media_event` fires on `video_main` playback milestones. The outcome is expressed separately by `to_state_id`.' + ), + ] = 'scroll_progress' + input: Annotated[ + Literal['scroll'], + Field( + description='Input driving a user or scroll transition. `hover` expansion is disallowed by IAB NAP guidance and emits a LEAN policy warning visible to buyers.' + ), + ] = 'scroll' + media_event: Annotated[ + MediaEvent | None, + Field( + description='Playback milestone of `video_main` driving a `media_event` transition (endframes, collapse-on-complete).' + ), + ] = None + direction: Annotated[ + Direction | None, + Field( + description='Scroll direction that arms a `scroll_threshold` transition; enables direction-aware expand/collapse cycles. Re-crossing in the opposite direction does not re-fire this transition.' + ), + ] = Direction.down + transition_mode: Annotated[ + Literal['scroll_linked'], + Field( + description='`scroll_linked` continuously interpolates the seller-owned layout between the two declared endpoint states; it is valid only with trigger `scroll_progress` and does not permit buyer scripting.' + ), + ] = 'scroll_linked' + delay_ms: Annotated[ + SchemaInt | None, + Field( + description='For `timer`: delay after `from_state_id` activates (re-entry restarts it). For `in_view_timer`: accumulated viewable milliseconds in `from_state_id`. Timer-class transitions in a state cycle MUST declare at least 1000 (anti-strobe floor).', + ge=0, + ), + ] = None + duration_ms: Annotated[ + SchemaInt | None, + Field(description='Duration of a seller-rendered animated transition.', ge=0), + ] = None + scroll_threshold_percent: Annotated[ + StrictFloat | None, + Field( + description='Viewport/page scroll threshold that starts a `scroll_threshold` transition.', + ge=0.0, + le=100.0, + ), + ] = None + scroll_reference: Annotated[ + ScrollReference, + Field( + description='Reference frame for scroll percentages. Progress is `scroll_offset / max(scroll_extent - viewport_extent, 1) * 100`, measured on either the page document or the nearest seller-declared containing scroller.' + ), + ] + scroll_start_percent: Annotated[ + StrictFloat, + Field( + description='Start of the bounded scroll interval for a `scroll_progress` transition.', + ge=0.0, + le=100.0, + ), + ] + scroll_end_percent: Annotated[ + StrictFloat, + Field( + description='End of the bounded scroll interval for a `scroll_progress` transition. MUST be greater than `scroll_start_percent`.', + ge=0.0, + le=100.0, + ), + ] + preserve_playback: Annotated[ + StrictBool | None, + Field( + description='Whether `video_main` continues without restart while the seller changes state.' + ), + ] = False + + +class Transitions4(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + transition_id: Annotated[ + str, + Field( + description='Stable transition identifier for preview and reporting.', + pattern='^[a-z][a-z0-9_]*$', + ), + ] + from_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] + to_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] + trigger: Annotated[ + Literal['user_action'], + Field( + description='Cause of the transition. `timer` counts from state entry; `in_view_timer` counts viewable time in the current state (industry auto-collapse is N seconds in view). `media_event` fires on `video_main` playback milestones. The outcome is expressed separately by `to_state_id`.' + ), + ] = 'user_action' + input: Annotated[ + Input, + Field( + description='Input driving a user or scroll transition. `hover` expansion is disallowed by IAB NAP guidance and emits a LEAN policy warning visible to buyers.' + ), + ] + media_event: Annotated[ + MediaEvent | None, + Field( + description='Playback milestone of `video_main` driving a `media_event` transition (endframes, collapse-on-complete).' + ), + ] = None + direction: Annotated[ + Direction | None, + Field( + description='Scroll direction that arms a `scroll_threshold` transition; enables direction-aware expand/collapse cycles. Re-crossing in the opposite direction does not re-fire this transition.' + ), + ] = Direction.down + transition_mode: Annotated[ + TransitionMode, + Field( + description='`scroll_linked` continuously interpolates the seller-owned layout between the two declared endpoint states; it is valid only with trigger `scroll_progress` and does not permit buyer scripting.' + ), + ] + delay_ms: Annotated[ + SchemaInt | None, + Field( + description='For `timer`: delay after `from_state_id` activates (re-entry restarts it). For `in_view_timer`: accumulated viewable milliseconds in `from_state_id`. Timer-class transitions in a state cycle MUST declare at least 1000 (anti-strobe floor).', + ge=0, + ), + ] = None + duration_ms: Annotated[ + SchemaInt | None, + Field(description='Duration of a seller-rendered animated transition.', ge=0), + ] = None + scroll_threshold_percent: Annotated[ + StrictFloat | None, + Field( + description='Viewport/page scroll threshold that starts a `scroll_threshold` transition.', + ge=0.0, + le=100.0, + ), + ] = None + scroll_reference: Annotated[ + ScrollReference | None, + Field( + description='Reference frame for scroll percentages. Progress is `scroll_offset / max(scroll_extent - viewport_extent, 1) * 100`, measured on either the page document or the nearest seller-declared containing scroller.' + ), + ] = None + scroll_start_percent: Annotated[ + StrictFloat | None, + Field( + description='Start of the bounded scroll interval for a `scroll_progress` transition.', + ge=0.0, + le=100.0, + ), + ] = None + scroll_end_percent: Annotated[ + StrictFloat | None, + Field( + description='End of the bounded scroll interval for a `scroll_progress` transition. MUST be greater than `scroll_start_percent`.', + ge=0.0, + le=100.0, + ), + ] = None + preserve_playback: Annotated[ + StrictBool | None, + Field( + description='Whether `video_main` continues without restart while the seller changes state.' + ), + ] = False + + +class Transitions5(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + transition_id: Annotated[ + str, + Field( + description='Stable transition identifier for preview and reporting.', + pattern='^[a-z][a-z0-9_]*$', + ), + ] + from_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] + to_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] + trigger: Annotated[ + Literal['media_event'], + Field( + description='Cause of the transition. `timer` counts from state entry; `in_view_timer` counts viewable time in the current state (industry auto-collapse is N seconds in view). `media_event` fires on `video_main` playback milestones. The outcome is expressed separately by `to_state_id`.' + ), + ] = 'media_event' + input: Annotated[ + Input | None, + Field( + description='Input driving a user or scroll transition. `hover` expansion is disallowed by IAB NAP guidance and emits a LEAN policy warning visible to buyers.' + ), + ] = None + media_event: Annotated[ + MediaEvent, + Field( + description='Playback milestone of `video_main` driving a `media_event` transition (endframes, collapse-on-complete).' + ), + ] + direction: Annotated[ + Direction | None, + Field( + description='Scroll direction that arms a `scroll_threshold` transition; enables direction-aware expand/collapse cycles. Re-crossing in the opposite direction does not re-fire this transition.' + ), + ] = Direction.down + transition_mode: Annotated[ + TransitionMode, + Field( + description='`scroll_linked` continuously interpolates the seller-owned layout between the two declared endpoint states; it is valid only with trigger `scroll_progress` and does not permit buyer scripting.' + ), + ] + delay_ms: Annotated[ + SchemaInt | None, + Field( + description='For `timer`: delay after `from_state_id` activates (re-entry restarts it). For `in_view_timer`: accumulated viewable milliseconds in `from_state_id`. Timer-class transitions in a state cycle MUST declare at least 1000 (anti-strobe floor).', + ge=0, + ), + ] = None + duration_ms: Annotated[ + SchemaInt | None, + Field(description='Duration of a seller-rendered animated transition.', ge=0), + ] = None + scroll_threshold_percent: Annotated[ + StrictFloat | None, + Field( + description='Viewport/page scroll threshold that starts a `scroll_threshold` transition.', + ge=0.0, + le=100.0, + ), + ] = None + scroll_reference: Annotated[ + ScrollReference | None, + Field( + description='Reference frame for scroll percentages. Progress is `scroll_offset / max(scroll_extent - viewport_extent, 1) * 100`, measured on either the page document or the nearest seller-declared containing scroller.' + ), + ] = None + scroll_start_percent: Annotated[ + StrictFloat | None, + Field( + description='Start of the bounded scroll interval for a `scroll_progress` transition.', + ge=0.0, + le=100.0, + ), + ] = None + scroll_end_percent: Annotated[ + StrictFloat | None, + Field( + description='End of the bounded scroll interval for a `scroll_progress` transition. MUST be greater than `scroll_start_percent`.', + ge=0.0, + le=100.0, + ), + ] = None + preserve_playback: Annotated[ + StrictBool | None, + Field( + description='Whether `video_main` continues without restart while the seller changes state.' + ), + ] = False + + +class Clickthrough(StrEnum): + required = 'required' + optional = 'optional' + none = 'none' + + +class UserControls(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + dismissible: StrictBool + user_collapsible: StrictBool + + +class VideoPlayback(StrEnum): + none = 'none' + auto_muted = 'auto_muted' + user_initiated = 'user_initiated' + + +class AssetType(StrEnum): + image = 'image' + video = 'video' + audio = 'audio' + text = 'text' + markdown = 'markdown' + url = 'url' + html = 'html' + css = 'css' + javascript = 'javascript' + vast = 'vast' + daast = 'daast' + webhook = 'webhook' + brief = 'brief' + catalog = 'catalog' + published_post = 'published_post' + zip = 'zip' + card = 'card' + object = 'object' + pixel_tracker = 'pixel_tracker' + vast_tracker = 'vast_tracker' + daast_tracker = 'daast_tracker' + + +class SharedSlot(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_group_id: Annotated[str, Field(pattern='^[a-z0-9_]+$')] + asset_type: AssetType + required: StrictBool | None = False + min: Annotated[SchemaInt | None, Field(ge=0)] = None + max: Annotated[SchemaInt | None, Field(ge=1)] = None + consumed_by: Annotated[ + list[str], + Field( + description='Component IDs that consume this shared asset. Every value MUST resolve to `components[].component_id`.', + min_length=1, + ), + ] + + +class Components(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + component_id: Annotated[ + str, + Field( + description='Stable coordination-local component identifier. Values MUST be unique within `components[]`.', + pattern='^[a-z][a-z0-9_]*$', + ), + ] + placement_ref: Annotated[ + placement_ref_1.PlacementReference, + Field( + description="Public product placement this component supplies. MUST resolve against the containing product's `placements[]`." + ), + ] + required: StrictBool + sequence: Annotated[ + SchemaInt | None, + Field( + description='Declared presentation order for sequential-messaging coordinated buys. Components sharing a sequence value present simultaneously; absent means unordered/simultaneous (default). Sequence declares seller-rendered ordering, not buyer-controlled timing.', + ge=1, + ), + ] = None + serving_policy: Annotated[ + ServingPolicy | None, + Field( + description='Per-component serving/tracking policy. Some sellers restrict specific components (e.g., page skins) to first-party serving while allowing third-party tags on sibling components. Defaults to the product-level policy when absent.' + ), + ] = None + canvas_constraints: Annotated[ + list[canvas_constraint.CanvasConstraint] | None, + Field( + description='Artwork constraints applied to this component, including safe areas and seller-reserved regions.' + ), + ] = None + format_option_ref: Annotated[ + format_option_ref_1.FormatOptionReference, + Field( + description='Reference to a sibling `format_options[]` entry on the same product. It MUST NOT resolve to `custom` or `coordinated_placements`.' + ), + ] + format_kind: str | None = None + params: dict[str, Any] | None = None + + +class Params(CanonicalFormatBase): + model_config = ConfigDict( + extra='allow', + ) + motion_level: MotionLevel | None = None + slots: Annotated[ + Any | None, + Field( + description="Default slots for image canonical. Buyer ships an image asset (file or hosted URL) plus optional headline, body text, primary text (long-form caption), CTA (typically constrained to an enum via `cta_values`), and clickthrough URL. Products MAY override the default — make `headline` required, narrow `cta` to a value enum, or remove slots the surface doesn't consume." + ), + ] = [ + {'asset_group_id': 'image_main', 'asset_type': 'image', 'required': True}, + {'asset_group_id': 'headline', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'body_text', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'primary_text', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'cta', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, + ] + width: Annotated[ + SchemaInt | None, + Field( + description="Logical render width in pixels — use for fixed-size slots (e.g., a 300×250 IAB MREC). When `pixel_ratios` is absent, the required image asset width is the same value (1x). When `pixel_ratios` is present, an accepted asset's intrinsic width is `width × pixel_ratio`. For multi-size flexible slots, use `sizes[]`; for responsive slots, use the min/max fields. The three size modes are mutually exclusive.", + ge=1, + ), + ] = None + height: Annotated[ + SchemaInt | None, + Field( + description='Logical render height in pixels. Intrinsic asset height is `height × pixel_ratio`, where the ratio defaults to 1 when `pixel_ratios` is absent. See `width` for size-mode mutual exclusion.', + ge=1, + ), + ] = None + sizes: Annotated[ + list[Size] | None, + Field( + description='List of accepted logical (width, height) render pairs for a multi-size flexible slot. The buyer ships an asset matching one logical size multiplied by one accepted `pixel_ratios` entry (or by 1 when `pixel_ratios` is absent). SDKs MUST treat size and density as separate axes: a 600×500 intrinsic asset at 2x satisfies a logical 300×250 size; it does not create a logical 600×500 placement. Mirrors OpenRTB `banner.format[]` semantics. Mutually exclusive with `(width, height)` and with responsive ranges.', + min_length=1, + ), + ] = None + pixel_ratios: Annotated[ + list[PixelRatio] | None, + Field( + description='Accepted intrinsic-pixel densities for image assets, expressed as intrinsic pixels per logical render pixel (for example `[1, 2]` accepts both standard and Retina renditions). Absence means `[1]` for backward compatibility. This is an acceptance set, not a requirement to submit every rendition: one `image_main` asset satisfying any listed ratio is sufficient unless the effective `image_main` slot declares `required_pixel_ratios`. SDKs determine the effective ratio from `asset.pixel_ratio` when supplied, otherwise infer it from intrinsic asset dimensions divided by the matched logical size. Width and height ratios MUST agree.', + min_length=1, + ), + ] = None + min_width: Annotated[ + SchemaInt | None, + Field( + description="Minimum accepted width in pixels for responsive slots that adapt within a range (e.g., 'any width from 300 to 970'). Use with `max_width` (and optionally `min_height`/`max_height`). Mutually exclusive with `(width, height)` and `sizes[]`.", + ge=1, + ), + ] = None + max_width: Annotated[ + SchemaInt | None, + Field( + description='Maximum accepted width in pixels for responsive slots. Pair with `min_width`. See `min_width` for size-mode mutual exclusion.', + ge=1, + ), + ] = None + min_height: Annotated[ + SchemaInt | None, + Field( + description='Minimum accepted height in pixels for responsive slots. Pair with `max_height`.', + ge=1, + ), + ] = None + max_height: Annotated[ + SchemaInt | None, + Field( + description='Maximum accepted height in pixels for responsive slots. Pair with `min_height`.', + ge=1, + ), + ] = None + aspect_ratio: Annotated[ + str | None, + Field( + description="Optional aspect ratio constraint (e.g., '1.91:1', '1:1'). When provided alongside `width`/`height`, must agree. When used with `sizes[]` or responsive ranges, narrows accepted entries to those matching the aspect ratio.", + pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', + ), + ] = None + max_file_size_kb: Annotated[ + SchemaInt | None, Field(description='Maximum file size in kilobytes.', ge=1) + ] = None + image_formats: Annotated[ + list[ImageFormat] | None, Field(description='Permitted image file formats.') + ] = None + ssl_required: Annotated[ + StrictBool | None, + Field(description='Whether the image and its trackers must be served over HTTPS.'), + ] = None + headline_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None + body_text_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None + cta_values: Annotated[ + list[str] | None, + Field( + description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW'])." + ), + ] = None + asset_source: Annotated[ + AssetSource | None, + Field( + description="Where the rendered asset bytes come from. Single shared enum across all canonicals (`image`, `video_hosted`, `audio_hosted` — replaces the earlier per-canonical `image_source` / `video_source` / `audio_source` fields). `buyer_uploaded` (default): buyer ships a pre-rendered asset. `publisher_host_recorded`: publisher's host records the asset (audio-specific; podcast host-read pattern). `seller_pre_rendered_from_brief`: buyer ships a brief plus structured copy; seller renders ONE asset at sync_creatives or build_creative time (generative-DSP pattern). `seller_human_designed`: seller's design team renders manually from a brief. `agent_synthesized`: AI synthesis pipeline; pair with `synthesis_nondeterministic: true` when the platform cannot guarantee in-spec output (Veo/Sora/Imagen-class). `publisher_owned_reference`: buyer references an existing post or publisher-owned object via a `published_post` slot; the seller resolves and serves the referenced content after authorization/review rather than receiving uploaded bytes.\n\nNot every value is meaningful on every canonical — `publisher_host_recorded` is audio-specific; on `image` or `video_hosted` it has no defined behavior. `publisher_owned_reference` is meaningful only when the product's `slots` declaration accepts a reference asset such as `published_post`. Adopters MUST select a value appropriate to the canonical's asset type. The `slots` declaration is the binding contract for what the buyer ships; `asset_source` is informational and lets buyers understand the production model when picking products." + ), + ] = AssetSource.buyer_uploaded + buyer_asset_acceptance: Annotated[ + BuyerAssetAcceptance | None, + Field( + description="Whether the product accepts buyer-uploaded assets. When `rejected`, the buyer cannot ship pre-rendered bytes directly — they must use build_creative (or sync_creatives with brief inputs or reference assets) so the seller produces or resolves the asset. Combined with `asset_source`, lets a product declare 'I produce assets from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`) or 'I accept existing post references, not uploaded bytes' (asset_source=`publisher_owned_reference`, buyer_asset_acceptance=`rejected`)." + ), + ] = BuyerAssetAcceptance.accepted + ctv_ad_experience: Annotated[ + ctv_ad_experience_1.CtvAdExperience | None, + Field( + description='CTV experience this option serves. On `image` only `pause` and `screensaver` are valid — the image-plus-copy contract the major pause-ad sellers ingest (seller composites the frame; typical canvas 1920×1080 or a transparent-region overlay). Other experiences route per the matrix in docs/creative/ctv-experiences.mdx.' + ), + ] = None + activation_methods: Annotated[ + list[activation_method.CreativeActivationMethod] | None, + Field( + description='Viewer activation mechanisms this option offers (e.g. `qr_code` on a pause frame). Activations are engagement events, not impressions.' + ), + ] = None + + +class Components1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + component_id: Annotated[ + str, + Field( + description='Stable coordination-local component identifier. Values MUST be unique within `components[]`.', + pattern='^[a-z][a-z0-9_]*$', + ), + ] + placement_ref: Annotated[ + placement_ref_1.PlacementReference, + Field( + description="Public product placement this component supplies. MUST resolve against the containing product's `placements[]`." + ), + ] + required: StrictBool + sequence: Annotated[ + SchemaInt | None, + Field( + description='Declared presentation order for sequential-messaging coordinated buys. Components sharing a sequence value present simultaneously; absent means unordered/simultaneous (default). Sequence declares seller-rendered ordering, not buyer-controlled timing.', + ge=1, + ), + ] = None + serving_policy: Annotated[ + ServingPolicy | None, + Field( + description='Per-component serving/tracking policy. Some sellers restrict specific components (e.g., page skins) to first-party serving while allowing third-party tags on sibling components. Defaults to the product-level policy when absent.' + ), + ] = None + canvas_constraints: Annotated[ + list[canvas_constraint.CanvasConstraint] | None, + Field( + description='Artwork constraints applied to this component, including safe areas and seller-reserved regions.' + ), + ] = None + format_option_ref: Annotated[ + format_option_ref_1.FormatOptionReference | None, + Field( + description='Reference to a sibling `format_options[]` entry on the same product. It MUST NOT resolve to `custom` or `coordinated_placements`.' + ), + ] = None + format_kind: Literal['image'] = 'image' + params: Annotated[ + Params, + Field( + description='Static image creative format. Slots: `image_main` (image asset, file or hosted URL), optional `headline` (text), `body_text` (text), `cta` (text/enum), `landing_page_url` (url). Tracking model: impression pixel + click URL via universal_macros, with optional viewability pixel. Distinct from `html5` (interactive bundles) and `display_tag` (third-party served). AR/dimensions narrow to specific sizes via product parameters — covers IAB display sizes (300x250, 728x90, 970x250, etc.) without a separate iab_size enum.', + title='Canonical Format: Image', + ), + ] + + +class Params2(CanonicalFormatBase): + model_config = ConfigDict( + extra='allow', + ) + slots: Annotated[ + Any | None, + Field( + description="Default slots for html5 canonical. Buyer ships a zip bundle plus optional backup image (required when `backup_image_required: true`) and clickthrough URL. The zip's entry point is typically `index.html`; click handling uses the `clickTag` (or `clickTAG`) macro substituted by the seller at serve time." + ), + ] = [ + {'asset_group_id': 'html5_bundle', 'asset_type': 'zip', 'required': True}, + {'asset_group_id': 'backup_image', 'asset_type': 'image', 'required': False}, + {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, + ] + width: Annotated[ + SchemaInt | None, + Field( + description='Required banner width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', + ge=1, + ), + ] = None + height: Annotated[ + SchemaInt | None, + Field( + description='Required banner height in pixels. See `width` for size-mode mutual exclusion.', + ge=1, + ), + ] = None + sizes: Annotated[ + list[Size] | None, + Field( + description='List of accepted (width, height) pairs for a multi-size flexible slot (publisher banner that accepts 300×250 OR 728×90 OR 970×250). Mirrors OpenRTB `banner.format[]`. Mutually exclusive with `(width, height)` and with responsive ranges.', + min_length=1, + ), + ] = None + min_width: Annotated[ + SchemaInt | None, + Field( + description='Minimum accepted width for responsive HTML5 banners that adapt within a range. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', + ge=1, + ), + ] = None + max_width: Annotated[ + SchemaInt | None, + Field( + description='Maximum accepted width for responsive HTML5 banners. Pair with `min_width`.', + ge=1, + ), + ] = None + min_height: Annotated[ + SchemaInt | None, + Field( + description='Minimum accepted height for responsive HTML5 banners. Pair with `max_height`.', + ge=1, + ), + ] = None + max_height: Annotated[ + SchemaInt | None, + Field( + description='Maximum accepted height for responsive HTML5 banners. Pair with `min_height`.', + ge=1, + ), + ] = None + max_initial_load_kb: Annotated[ + SchemaInt | None, + Field( + description='Maximum initial-load file size (zip + above-the-fold assets) in kilobytes. IAB display standards: 200 KB for fixed sizes, 100 KB for mobile.', + ge=1, + ), + ] = None + max_polite_load_kb: Annotated[ + SchemaInt | None, + Field( + description='Maximum polite-load file size after host-initiated subload, in kilobytes. IAB display standards: 500 KB for fixed sizes.', + ge=1, + ), + ] = None + host_initiated_subload: Annotated[ + StrictBool | None, + Field( + description='Whether the host page must initiate the polite-load phase. IAB-compliant banners require true.' + ), + ] = None + max_animation_duration_ms: Annotated[ + SchemaInt | None, + Field( + description='Maximum total animation duration in milliseconds. IAB standard: 30000 (30 seconds).', + ge=0, + ), + ] = None + max_cpu_load_percent: Annotated[ + SchemaInt | None, + Field(description='Maximum CPU load percentage during render.', ge=1, le=100), + ] = None + mraid_required: Annotated[ + StrictBool | None, + Field(description='Whether MRAID compatibility is required (mobile in-app).'), + ] = None + mraid_version: Annotated[ + MraidVersion | None, + Field(description='Required MRAID version when mraid_required is true.'), + ] = None + om_sdk_required: Annotated[ + StrictBool | None, + Field(description='Whether IAB Open Measurement SDK integration is required.'), + ] = None + clicktag_macro: Annotated[ + ClicktagMacro | None, Field(description='Name of the click-tag macro the bundle must use.') + ] = None + backup_image_required: Annotated[ + StrictBool | None, + Field( + description='Whether a backup image must accompany the zip for non-HTML5 environments.' + ), + ] = None + backup_image_max_size_kb: Annotated[ + SchemaInt | None, Field(description='Maximum backup image file size in kilobytes.', ge=1) + ] = None + ssl_required: StrictBool | None = None + + +class Components2(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + component_id: Annotated[ + str, + Field( + description='Stable coordination-local component identifier. Values MUST be unique within `components[]`.', + pattern='^[a-z][a-z0-9_]*$', + ), + ] + placement_ref: Annotated[ + placement_ref_1.PlacementReference, + Field( + description="Public product placement this component supplies. MUST resolve against the containing product's `placements[]`." + ), + ] + required: StrictBool + sequence: Annotated[ + SchemaInt | None, + Field( + description='Declared presentation order for sequential-messaging coordinated buys. Components sharing a sequence value present simultaneously; absent means unordered/simultaneous (default). Sequence declares seller-rendered ordering, not buyer-controlled timing.', + ge=1, + ), + ] = None + serving_policy: Annotated[ + ServingPolicy | None, + Field( + description='Per-component serving/tracking policy. Some sellers restrict specific components (e.g., page skins) to first-party serving while allowing third-party tags on sibling components. Defaults to the product-level policy when absent.' + ), + ] = None + canvas_constraints: Annotated[ + list[canvas_constraint.CanvasConstraint] | None, + Field( + description='Artwork constraints applied to this component, including safe areas and seller-reserved regions.' + ), + ] = None + format_option_ref: Annotated[ + format_option_ref_1.FormatOptionReference | None, + Field( + description='Reference to a sibling `format_options[]` entry on the same product. It MUST NOT resolve to `custom` or `coordinated_placements`.' + ), + ] = None + format_kind: Literal['html5'] = 'html5' + params: Annotated[ + Params2, + Field( + description="Interactive HTML5 banner delivered as a zip archive. Slot: `html5_bundle` (zip asset). Tracking model: MRAID + IAB Open Measurement (OM-SDK) + click-tag macro substitution + backup image fallback. Receivers unpack the zip, validate internal structure, and serve from CDN. Distinct from `image` (static, non-interactive) and `display_tag` (third-party served). The zip's entry point is typically `index.html`; click handling uses `clickTag` (or `clickTAG`) macro substitution.", + title='Canonical Format: HTML5 Banner', + ), + ] + + +class Params3(CanonicalFormatBase): + model_config = ConfigDict( + extra='allow', + ) + slots: Annotated[ + Any | None, + Field( + description='Backward-compatible URL-delivery slots. `tag_url` MUST use `url_type: ad_request`. A format option accepting `inline_markup` or `paired_redirect` overrides this list with a required slot whose `asset_type` is `display_tag`; the display-tag asset keeps paired redirect URLs atomic.' + ), + ] = [ + {'asset_group_id': 'tag_url', 'asset_type': 'url', 'required': True}, + {'asset_group_id': 'backup_image', 'asset_type': 'image', 'required': False}, + ] + width: Annotated[ + SchemaInt | None, + Field( + description='Required tag rendering width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', + ge=1, + ), + ] = None + height: Annotated[ + SchemaInt | None, + Field( + description='Required tag rendering height in pixels. See `width` for size-mode mutual exclusion.', + ge=1, + ), + ] = None + sizes: Annotated[ + list[Size] | None, + Field( + description="List of accepted (width, height) pairs for a multi-size flexible slot. The buyer's third-party tag must render at one of the listed sizes; the seller picks which size to request at impression time. Mutually exclusive with `(width, height)` and with responsive ranges.", + min_length=1, + ), + ] = None + min_width: Annotated[ + SchemaInt | None, + Field( + description='Minimum accepted width for responsive third-party tags. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', + ge=1, + ), + ] = None + max_width: Annotated[ + SchemaInt | None, + Field( + description='Maximum accepted width for responsive third-party tags. Pair with `min_width`.', + ge=1, + ), + ] = None + min_height: Annotated[ + SchemaInt | None, + Field( + description='Minimum accepted height for responsive third-party tags. Pair with `max_height`.', + ge=1, + ), + ] = None + max_height: Annotated[ + SchemaInt | None, + Field( + description='Maximum accepted height for responsive third-party tags. Pair with `min_height`.', + ge=1, + ), + ] = None + supported_tag_types: Annotated[ + list[SupportedTagType] | None, + Field( + deprecated=True, + description='Deprecated ambiguous mechanism list. Use `supported_delivery_types`; markup subtype lives on the `display_tag` asset.', + ), + ] = None + supported_delivery_types: Annotated[ + list[SupportedDeliveryType] | None, + Field( + description='Closed set of delivery types this format option can traffic. `paired_redirect` means one atomic ad-request/click-through pair (Internal Redirect semantics), never independently matchable URL slots.', + min_length=1, + ), + ] = None + ssl_required: Annotated[ + StrictBool | None, Field(description='Whether the tag URL must be HTTPS.') + ] = None + max_redirect_depth: Annotated[ + SchemaInt | None, Field(description='Maximum redirect chain depth permitted.', ge=0) + ] = None + max_response_time_ms: Annotated[ + SchemaInt | None, + Field(description='Maximum tag-server response time in milliseconds.', ge=1), + ] = None + backup_image_required: Annotated[ + StrictBool | None, + Field( + description='Whether a backup image must accompany the tag for environments that cannot render the third-party tag.' + ), + ] = None + backup_image_max_size_kb: Annotated[SchemaInt | None, Field(ge=1)] = None + om_sdk_required: Annotated[ + StrictBool | None, + Field( + description="Whether the buyer's tag must integrate IAB Open Measurement SDK for viewability." + ), + ] = None + + +class Components3(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + component_id: Annotated[ + str, + Field( + description='Stable coordination-local component identifier. Values MUST be unique within `components[]`.', + pattern='^[a-z][a-z0-9_]*$', + ), + ] + placement_ref: Annotated[ + placement_ref_1.PlacementReference, + Field( + description="Public product placement this component supplies. MUST resolve against the containing product's `placements[]`." + ), + ] + required: StrictBool + sequence: Annotated[ + SchemaInt | None, + Field( + description='Declared presentation order for sequential-messaging coordinated buys. Components sharing a sequence value present simultaneously; absent means unordered/simultaneous (default). Sequence declares seller-rendered ordering, not buyer-controlled timing.', + ge=1, + ), + ] = None + serving_policy: Annotated[ + ServingPolicy | None, + Field( + description='Per-component serving/tracking policy. Some sellers restrict specific components (e.g., page skins) to first-party serving while allowing third-party tags on sibling components. Defaults to the product-level policy when absent.' + ), + ] = None + canvas_constraints: Annotated[ + list[canvas_constraint.CanvasConstraint] | None, + Field( + description='Artwork constraints applied to this component, including safe areas and seller-reserved regions.' + ), + ] = None + format_option_ref: Annotated[ + format_option_ref_1.FormatOptionReference | None, + Field( + description='Reference to a sibling `format_options[]` entry on the same product. It MUST NOT resolve to `custom` or `coordinated_placements`.' + ), + ] = None + format_kind: Literal['display_tag'] = 'display_tag' + params: Annotated[ + Params3, + Field( + description='Third-party-served display creative delivered as one of three explicit representations: a single ad-request URL, byte-preserved inline markup, or an atomic paired redirect (`ad_request_url` plus `clickthrough_url`). The backward-compatible default slot remains `tag_url` (`url_type: ad_request`). Format options accepting inline or paired delivery override `slots` with a `display_tag` asset and declare `supported_delivery_types`. Tracking is opaque to the seller except for declared macro resolution. Distinct from `image` and seller-hosted `html5` zip bundles.', + title='Canonical Format: Display Tag', + ), + ] + + +class Params4(CanonicalFormatBase): + model_config = ConfigDict( + extra='allow', + ) + v1_translatable: Annotated[ + Any | None, + Field( + description="Inherently new in v2 — multi-card carousels (Meta carousel, Pinterest pin collections, Snap collection ads) weren't expressible as v1 named formats. SDKs MUST NOT emit `FORMAT_PROJECTION_FAILED` for products using this canonical; the v1-unreachability is structural." + ), + ] = False + slots: Annotated[ + Any | None, + Field( + description="Default slots for image_carousel. The `cards` slot's value in the manifest is an array of [card-asset](/schemas/core/assets/card-asset.json) objects; `min` / `max` constrain card count." + ), + ] = [ + {'asset_group_id': 'cards', 'asset_type': 'card', 'required': True, 'min': 2, 'max': 10}, + {'asset_group_id': 'primary_text', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, + ] + card_aspect_ratio: Annotated[ + str | None, + Field( + description="Aspect ratio shared across all cards (e.g., '1:1', '1.91:1', '4:5').", + pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', + ), + ] = None + min_cards: Annotated[ + SchemaInt | None, Field(description='Minimum card count (typical: 2 or 3).', ge=2) + ] = None + max_cards: Annotated[ + SchemaInt | None, + Field(description='Maximum card count (typical: 6, 10, or 35 depending on platform).'), + ] = None + allowed_card_media_asset_types: Annotated[ + list[AllowedCardMediaAssetType] | None, + Field( + description='Asset types each card\'s `media` field may carry. Default: [\'image\']. Polymorphic carousels (Meta) allow [\'image\', \'video\']. Renamed from `allowed_card_asset_types` to disambiguate that this constrains the card\'s media payload, not the card-asset itself (which is always asset_type: "card").' + ), + ] = None + allowed_card_asset_types: Annotated[ + list[AllowedCardMediaAssetType] | None, + Field( + deprecated=True, + description='DEPRECATED — alias for `allowed_card_media_asset_types`. Kept for back-compat; prefer the new field name. Removed in 5.0.', + ), + ] = None + card_image_max_file_size_kb: Annotated[SchemaInt | None, Field(ge=1)] = None + card_video_max_file_size_kb: Annotated[SchemaInt | None, Field(ge=1)] = None + card_video_max_duration_ms: Annotated[SchemaInt | None, Field(ge=1)] = None + primary_text_max_chars: Annotated[ + SchemaInt | None, + Field(description='Maximum length of the carousel-level primary text.', ge=1), + ] = None + card_headline_max_chars: Annotated[ + SchemaInt | None, + Field( + description='Per-card headline character limit. Governs the `headline` field on each card-asset in the `cards` slot.', + ge=1, + ), + ] = None + card_description_max_chars: Annotated[ + SchemaInt | None, + Field( + description='Per-card description character limit. Governs the `description` field on each card-asset in the `cards` slot. Distinct from `card_headline_max_chars`: description is longer body copy (typically 100-500 chars); headline is the short label (typically 25-40 chars).', + ge=1, + ), + ] = None + ssl_required: StrictBool | None = None + + +class Components4(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + component_id: Annotated[ + str, + Field( + description='Stable coordination-local component identifier. Values MUST be unique within `components[]`.', + pattern='^[a-z][a-z0-9_]*$', + ), + ] + placement_ref: Annotated[ + placement_ref_1.PlacementReference, + Field( + description="Public product placement this component supplies. MUST resolve against the containing product's `placements[]`." + ), + ] + required: StrictBool + sequence: Annotated[ + SchemaInt | None, + Field( + description='Declared presentation order for sequential-messaging coordinated buys. Components sharing a sequence value present simultaneously; absent means unordered/simultaneous (default). Sequence declares seller-rendered ordering, not buyer-controlled timing.', + ge=1, + ), + ] = None + serving_policy: Annotated[ + ServingPolicy | None, + Field( + description='Per-component serving/tracking policy. Some sellers restrict specific components (e.g., page skins) to first-party serving while allowing third-party tags on sibling components. Defaults to the product-level policy when absent.' + ), + ] = None + canvas_constraints: Annotated[ + list[canvas_constraint.CanvasConstraint] | None, + Field( + description='Artwork constraints applied to this component, including safe areas and seller-reserved regions.' + ), + ] = None + format_option_ref: Annotated[ + format_option_ref_1.FormatOptionReference | None, + Field( + description='Reference to a sibling `format_options[]` entry on the same product. It MUST NOT resolve to `custom` or `coordinated_placements`.' + ), + ] = None + format_kind: Literal['image_carousel'] = 'image_carousel' + params: Annotated[ + Params4, + Field( + description='Multi-card swipeable carousel. The buyer ships a `cards` slot whose value is an **array** of [card-asset](/schemas/core/assets/card-asset.json) objects (a single key with an array value — NOT one key per card, NOT dotted/bracketed paths). Each card-asset carries: `asset_type: "card"`, `media` (an image or video asset), optional `headline` (text), optional `landing_page_url` (url asset). Per-card structure is the same across all cards; mixed orientations not allowed within a single carousel. Tracking model: per-card impression and engagement pixels + carousel-level engagement (swipe, view-time). Allowed asset types for a card\'s `media` field: `image` and `video` (Meta-style mixed-media); platforms can narrow to image-only or video-only via `allowed_card_media_asset_types`.\n\nThe manifest\'s `assets.cards` value is an array of card-asset objects. Example: `"cards": [{"asset_type": "card", "media": {"asset_type": "image", "url": "..."}, "headline": "Buy now", "landing_page_url": {"asset_type": "url", "url_type": "clickthrough", "url": "..."}}, ...]`. Each card-asset validates against the card schema; per-card platform extensions attach via the card\'s `platform_extensions` field, never via inline non-canonical keys.', + title='Canonical Format: Image Carousel', + ), + ] + + +class Params5(CanonicalFormatBase): + model_config = ConfigDict( + extra='allow', + ) + slots: Annotated[ + Any | None, + Field( + description='Default slots for video_hosted canonical. Buyer ships a video asset (file or hosted URL); optional headline, primary text (long-form caption), CTA (typically constrained via `cta_values`), brand_name (typical for vertical short-form), companion_banner (typical for horizontal instream), and clickthrough URL. Products MAY override or extend the default — e.g., remove `companion_banner` for short-form vertical, narrow `cta` to a value enum, mark `landing_page_url` as required.' + ), + ] = [ + {'asset_group_id': 'video_main', 'asset_type': 'video', 'required': True}, + {'asset_group_id': 'headline', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'primary_text', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'cta', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'brand_name', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'companion_banner', 'asset_type': 'image', 'required': False}, + {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, + ] + orientation: Annotated[ + Orientation | None, + Field( + description='Video orientation. Vertical = 9:16 (Reels, Stories, Shorts). Horizontal = 16:9 (instream, CTV). Square = 1:1 (in-feed).' + ), + ] = None + aspect_ratio: Annotated[ + str | None, + Field( + description='Aspect ratio. Inferred from orientation if omitted.', + pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', + ), + ] = None + min_width: Annotated[SchemaInt | None, Field(ge=1)] = None + min_height: Annotated[SchemaInt | None, Field(ge=1)] = None + max_width: Annotated[SchemaInt | None, Field(ge=1)] = None + max_height: Annotated[SchemaInt | None, Field(ge=1)] = None + duration_ms_range: Annotated[ + list[DurationMsRange | None] | None, + Field( + description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', + max_length=2, + min_length=2, + ), + ] = None + duration_ms_exact: Annotated[ + SchemaInt | None, + Field( + description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship (see `duration_ms_range` description).', + ge=1, + ), + ] = None + video_codecs: list[VideoCodec] | None = None + audio_codecs: list[AudioCodec] | None = None + containers: list[Container] | None = None + min_bitrate_kbps: Annotated[SchemaInt | None, Field(ge=1)] = None + max_bitrate_kbps: Annotated[SchemaInt | None, Field(ge=1)] = None + max_file_size_mb: Annotated[ + SchemaInt | None, + Field(description='Maximum file size, where 1 MB is exactly 1,000,000 bytes.', ge=1), + ] = None + frame_rates: list[StrictFloat] | None = None + captions: Captions | None = None + om_sdk_required: StrictBool | None = None + headline_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None + primary_text_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None + brand_name_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None + cta_values: list[str] | None = None + companion_banner_widths: Annotated[ + list[CompanionBannerWidth] | None, + Field(description='Permitted companion banner widths (instream video).'), + ] = None + companion_banner_heights: list[CompanionBannerHeight] | None = None + asset_source: Annotated[ + AssetSource | None, + Field( + description='Where the rendered asset bytes come from. Single shared enum across canonicals. See `image.json#asset_source` for the full semantics. `publisher_host_recorded` is audio-specific and has no defined behavior on video. `publisher_owned_reference` is valid when the product accepts an existing post reference via a `published_post` slot instead of uploaded video bytes. Adopters MUST select a value appropriate to the canonical.' + ), + ] = AssetSource.buyer_uploaded + buyer_asset_acceptance: Annotated[ + BuyerAssetAcceptance | None, + Field( + description='Whether the product accepts buyer-uploaded video. When `rejected`, the buyer cannot ship a video asset directly — they must use build_creative, sync_creatives with brief inputs, or sync_creatives with an accepted reference asset so the seller produces or resolves the video.' + ), + ] = BuyerAssetAcceptance.accepted + ctv_ad_experience: Annotated[ + ctv_ad_experience_1.CtvAdExperience | None, + Field( + description='CTV experience this option serves. On `video_hosted` only `screensaver` is valid (ambient looping video the platform plays on idle). Other experiences route per the matrix in docs/creative/ctv-experiences.mdx; linear CTV video declares no experience.' + ), + ] = None + + +class Components5(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + component_id: Annotated[ + str, + Field( + description='Stable coordination-local component identifier. Values MUST be unique within `components[]`.', + pattern='^[a-z][a-z0-9_]*$', + ), + ] + placement_ref: Annotated[ + placement_ref_1.PlacementReference, + Field( + description="Public product placement this component supplies. MUST resolve against the containing product's `placements[]`." + ), + ] + required: StrictBool + sequence: Annotated[ + SchemaInt | None, + Field( + description='Declared presentation order for sequential-messaging coordinated buys. Components sharing a sequence value present simultaneously; absent means unordered/simultaneous (default). Sequence declares seller-rendered ordering, not buyer-controlled timing.', + ge=1, + ), + ] = None + serving_policy: Annotated[ + ServingPolicy | None, + Field( + description='Per-component serving/tracking policy. Some sellers restrict specific components (e.g., page skins) to first-party serving while allowing third-party tags on sibling components. Defaults to the product-level policy when absent.' + ), + ] = None + canvas_constraints: Annotated[ + list[canvas_constraint.CanvasConstraint] | None, + Field( + description='Artwork constraints applied to this component, including safe areas and seller-reserved regions.' + ), + ] = None + format_option_ref: Annotated[ + format_option_ref_1.FormatOptionReference | None, + Field( + description='Reference to a sibling `format_options[]` entry on the same product. It MUST NOT resolve to `custom` or `coordinated_placements`.' + ), + ] = None + format_kind: Literal['video_hosted'] = 'video_hosted' + params: Annotated[ + Params5, + Field( + description='Direct video file (mp4/webm/mov) hosted by the buyer. Slot: `video_main` (video asset, file or hosted URL), optional `headline`, `brand_name`, `cta`, `companion_banner`, `landing_page_url`. Tracking model: IAB Open Measurement SDK + external impression/click/quartile pixels via universal_macros. Orientation is a parameter (vertical 9:16 / horizontal 16:9 / square 1:1); slot shape includes optional `brand_name` (typical for vertical short-form) and optional `companion_banner` (typical for horizontal instream). Distinct from `video_vast` (VAST tag, inherent VAST event tracking) — receivers fire impression and click pixels at delivery time.', + title='Canonical Format: Hosted Video', + ), + ] + + +class Params6(CanonicalFormatBase): + model_config = ConfigDict( + extra='allow', + ) + slots: Annotated[ + Any | None, + Field( + description="Default slots for video_vast canonical. Buyer ships a VAST tag (URL or inline XML, VAST 2.x-4.x) plus an optional clickthrough URL (which falls back to the VAST `ClickThrough` element when omitted). Tracking events are inherent to VAST and don't require explicit slots." + ), + ] = [ + {'asset_group_id': 'vast_tag', 'asset_type': 'vast', 'required': True}, + {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, + ] + orientation: Orientation | None = None + aspect_ratio: Annotated[ + str | None, Field(pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$') + ] = None + vast_versions: Annotated[ + list[vast_version_1.VastVersion] | None, + Field( + description='VAST versions accepted by this product format option. The asset still declares exactly one `vast_version`; compatibility requires membership in this set and the seller-wide execution set.', + min_length=1, + ), + ] = None + vast_version: Annotated[ + vast_version_1.VastVersion | None, + Field( + deprecated=True, + description='Deprecated one-element alias for `vast_versions`. Producers use either the singular legacy alias or the plural 3.2 field, never both.', + ), + ] = None + media_file_requirements: Annotated[ + vast_media_file_requirements.VastMediafileRequirements | None, + Field( + description='Technical acceptance constraints for alternative VAST MediaFile renditions. Each applicable resolved InLine linear creative needs at least one MediaFile satisfying all declared constraints.' + ), + ] = None + vpaid_enabled: Annotated[ + StrictBool | None, + Field( + description='Whether VPAID interactivity is supported. When true, the VAST tag may carry VPAID JS/Flash payloads.' + ), + ] = None + vpaid_version: VpaidVersion | None = None + simid_supported: Annotated[ + StrictBool | None, + Field( + description='Whether the seller accepts IAB SIMID through `` on a Linear VAST creative. SIMID is not a generic VAST extension and cannot be serialized under NonLinearAds; every `ctv_ad_experience` profile therefore forbids `true`.' + ), + ] = None + duration_ms_range: Annotated[ + list[DurationMsRangeItem] | None, + Field( + description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', + max_length=2, + min_length=2, + ), + ] = None + duration_ms_exact: Annotated[ + SchemaInt | None, + Field( + description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', + ge=1, + ), + ] = None + min_width: Annotated[ + SchemaInt | None, + Field( + description='Minimum placement/player width in pixels. MediaFile rendition dimensions are declared in `media_file_requirements`.', + ge=1, + ), + ] = None + max_width: Annotated[ + SchemaInt | None, + Field( + description='Maximum placement/player width in pixels. MediaFile rendition dimensions are declared in `media_file_requirements`.', + ge=1, + ), + ] = None + min_height: Annotated[ + SchemaInt | None, + Field( + description='Minimum placement/player height in pixels. MediaFile rendition dimensions are declared in `media_file_requirements`.', + ge=1, + ), + ] = None + max_height: Annotated[ + SchemaInt | None, + Field( + description='Maximum placement/player height in pixels. MediaFile rendition dimensions are declared in `media_file_requirements`.', + ge=1, + ), + ] = None + creative_type: Annotated[ + CreativeType | None, + Field( + description='Required VAST creative class: `linear` (in-stream Linear), `nonlinear` (NonLinearAds overlay-class), or `either`. Supersedes `linear_required`; when both are present `creative_type` wins, and validators treat `linear_required: true` with no `creative_type` as `linear`.' + ), + ] = None + ctv_ad_experience: Annotated[ + ctv_ad_experience_1.CtvAdExperience | None, + Field( + description='CTV experience this option is eligible to serve. On video_vast only `pause`, `screensaver`, `overlay`, `squeezeback`, and `in_scene` are valid (`menu` routes to native_in_feed or sponsored_placement), and `creative_type` MUST be `nonlinear`. Because VAST places `` only under Linear ``, `simid_supported` MUST NOT be true on any of these NonLinear profiles. Per-experience floors: `overlay` and `squeezeback` require a 10s minimum duration; `in_scene` requires a 3s minimum brand-exposure duration and forbids interactivity (`vpaid_enabled` MUST NOT be true); `pause` has no duration floor and ends on viewer or device action.' + ), + ] = None + motion_level: Annotated[ + motion_level_1.CreativeMotionLevel | None, + Field(description='Accepted motion class for the rendered creative (AdCOM attrs 21-23).'), + ] = None + activation_methods: Annotated[ + list[activation_method.CreativeActivationMethod] | None, + Field( + description='Viewer activation mechanisms this option offers. Activations are engagement events, not impressions.' + ), + ] = None + linear_required: Annotated[ + StrictBool | None, + Field( + description='Whether the VAST creative must be linear (non-skippable in-stream). Superseded by `creative_type`; retained for pre-3.2 declarations.' + ), + ] = None + skippable_after_ms: Annotated[ + SchemaInt | None, + Field( + description='When skippable, the buyer-side skip threshold in milliseconds (e.g., 5000 for 5-second skippable pre-roll).', + ge=0, + ), + ] = None + max_wrapper_depth: Annotated[ + SchemaInt | None, Field(description='Maximum VAST wrapper redirect depth permitted.', ge=0) + ] = None + ssl_required: StrictBool | None = None + + +class Components6(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + component_id: Annotated[ + str, + Field( + description='Stable coordination-local component identifier. Values MUST be unique within `components[]`.', + pattern='^[a-z][a-z0-9_]*$', + ), + ] + placement_ref: Annotated[ + placement_ref_1.PlacementReference, + Field( + description="Public product placement this component supplies. MUST resolve against the containing product's `placements[]`." + ), + ] + required: StrictBool + sequence: Annotated[ + SchemaInt | None, + Field( + description='Declared presentation order for sequential-messaging coordinated buys. Components sharing a sequence value present simultaneously; absent means unordered/simultaneous (default). Sequence declares seller-rendered ordering, not buyer-controlled timing.', + ge=1, + ), + ] = None + serving_policy: Annotated[ + ServingPolicy | None, + Field( + description='Per-component serving/tracking policy. Some sellers restrict specific components (e.g., page skins) to first-party serving while allowing third-party tags on sibling components. Defaults to the product-level policy when absent.' + ), + ] = None + canvas_constraints: Annotated[ + list[canvas_constraint.CanvasConstraint] | None, + Field( + description='Artwork constraints applied to this component, including safe areas and seller-reserved regions.' + ), + ] = None + format_option_ref: Annotated[ + format_option_ref_1.FormatOptionReference | None, + Field( + description='Reference to a sibling `format_options[]` entry on the same product. It MUST NOT resolve to `custom` or `coordinated_placements`.' + ), + ] = None + format_kind: Literal['video_vast'] = 'video_vast' + params: Annotated[ + Params6, + Field( + description='VAST-tag-delivered video creative. Slot: `vast_tag` (vast asset, URL or inline XML, VAST 2.x-4.x). Tracking model: VAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `expand`, `collapse`, `fullscreen`, `creativeView`, `clickTracking`, `error`. VPAID interactivity via `vpaid_enabled: true` flag. SIMID is carried by the first-class VAST 4.1+ `` element on Linear creatives. Orientation is a parameter (vertical / horizontal / square). Distinct from `video_hosted` (direct file with external tracking).', + title='Canonical Format: VAST Video', + ), + ] + + +class Params7(CanonicalFormatBase): + model_config = ConfigDict( + extra='allow', + ) + slots: Annotated[ + Any | None, + Field( + description="Default slots for buyer-uploaded audio. Host-read products override with a `script` (asset_type: text) or `creative_brief` (asset_type: brief) slot in place of `audio_main`, plus `asset_source: 'publisher_host_recorded'` and `buyer_asset_acceptance: 'rejected'`. TTS-from-script products override similarly with `asset_source: 'seller_pre_rendered_from_brief'`." + ), + ] = [ + {'asset_group_id': 'audio_main', 'asset_type': 'audio', 'required': True}, + {'asset_group_id': 'companion_image', 'asset_type': 'image', 'required': False}, + {'asset_group_id': 'brand_name', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, + ] + duration_ms_range: Annotated[ + list[DurationMsRange | None] | None, + Field( + description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', + max_length=2, + min_length=2, + ), + ] = None + duration_ms_exact: Annotated[ + SchemaInt | None, + Field( + description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', + ge=1, + ), + ] = None + audio_codecs: list[AudioCodec2] | None = None + audio_sample_rates: list[AudioSampleRate] | None = None + audio_channels: list[AudioChannel] | None = None + min_bitrate_kbps: Annotated[SchemaInt | None, Field(ge=1)] = None + max_bitrate_kbps: Annotated[SchemaInt | None, Field(ge=1)] = None + max_file_size_mb: Annotated[ + StrictFloat | None, + Field( + description='Maximum hosted audio file size in decimal megabytes. Agents that proxy or cache media SHOULD advertise their effective transport ceiling here.', + gt=0.0, + ), + ] = None + loudness_lufs: Annotated[ + StrictFloat | None, + Field( + description='Required integrated loudness in LUFS (typical: -16 for streaming/podcast, -23 for broadcast). Negative values.' + ), + ] = None + loudness_tolerance_db: Annotated[ + StrictFloat | None, + Field(description='Permitted deviation from loudness_lufs in dB.', ge=0.0), + ] = None + true_peak_dbfs: Annotated[ + StrictFloat | None, Field(description='Maximum true-peak level in dBFS (typical: -2).') + ] = None + asset_source: Annotated[ + AssetSource | None, + Field( + description="Where the rendered audio bytes come from. Single shared enum across canonicals (see `image.json#asset_source` for the full semantics). `publisher_host_recorded`: the publisher's host records the audio (podcast host-read pattern); buyer must use the publisher's build_creative capability. `publisher_owned_reference` is valid only when the product accepts a reference asset whose publisher-owned source resolves to playable audio. `publisher_host_recorded` remains the normal audio-specific host-read value." + ), + ] = AssetSource.buyer_uploaded + buyer_asset_acceptance: Annotated[ + BuyerAssetAcceptance | None, + Field( + description="Whether the product accepts buyer-uploaded audio. When `rejected`, the buyer cannot ship an audio asset directly — they must use build_creative (or sync_creatives with brief inputs) so the seller produces the audio. Combined with `asset_source`, lets a product declare 'I produce audio from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`)." + ), + ] = BuyerAssetAcceptance.accepted + companion_image_required: StrictBool | None = None + companion_image_aspect_ratio: str | None = None + companion_image_max_file_size_kb: Annotated[SchemaInt | None, Field(ge=1)] = None + brand_name_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None + + +class Components7(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + component_id: Annotated[ + str, + Field( + description='Stable coordination-local component identifier. Values MUST be unique within `components[]`.', + pattern='^[a-z][a-z0-9_]*$', + ), + ] + placement_ref: Annotated[ + placement_ref_1.PlacementReference, + Field( + description="Public product placement this component supplies. MUST resolve against the containing product's `placements[]`." + ), + ] + required: StrictBool + sequence: Annotated[ + SchemaInt | None, + Field( + description='Declared presentation order for sequential-messaging coordinated buys. Components sharing a sequence value present simultaneously; absent means unordered/simultaneous (default). Sequence declares seller-rendered ordering, not buyer-controlled timing.', + ge=1, + ), + ] = None + serving_policy: Annotated[ + ServingPolicy | None, + Field( + description='Per-component serving/tracking policy. Some sellers restrict specific components (e.g., page skins) to first-party serving while allowing third-party tags on sibling components. Defaults to the product-level policy when absent.' + ), + ] = None + canvas_constraints: Annotated[ + list[canvas_constraint.CanvasConstraint] | None, + Field( + description='Artwork constraints applied to this component, including safe areas and seller-reserved regions.' + ), + ] = None + format_option_ref: Annotated[ + format_option_ref_1.FormatOptionReference | None, + Field( + description='Reference to a sibling `format_options[]` entry on the same product. It MUST NOT resolve to `custom` or `coordinated_placements`.' + ), + ] = None + format_kind: Literal['audio_hosted'] = 'audio_hosted' + params: Annotated[ + Params7, + Field( + description="Direct audio creative — buyer ships an `audio` asset (mp3/aac/wav) for asset-driven products, or ships a `script` / `creative_brief` text asset for products where the seller produces audio internally (podcast host-reads, TTS synthesis). Optional companion slots: `companion_image`, `brand_name`, `landing_page_url`. Tracking model: standard impression + completion + companion-image-click pixels via universal_macros. Distinct from `audio_daast` (DAAST tag, inherent DAAST event tracking). For host-reads and synthesized audio, the format declares `asset_source: 'publisher_host_recorded'` or `'agent_synthesized'` plus `buyer_asset_acceptance: 'rejected'`; the format's `slots` declaration enumerates which assets the buyer ships (e.g., `script` text asset for host-reads). The seller decides how to consume each asset (render verbatim vs produce audio from text) — there is no separate manifest 'inputs' map; everything the buyer ships goes in `assets`.", + title='Canonical Format: Hosted Audio', + ), + ] + + +class Params9(CanonicalFormatBase): + model_config = ConfigDict( + extra='allow', + ) + experimental: Annotated[ + Any | None, + Field( + description='Marked experimental at 3.1 GA: the canonical covers 4 meaningfully different retail-media adapter contracts (Amazon SP, Criteo SP / CitrusAd SP, Pinterest Collection, generative-per-SKU). Adopter contracts vary; buyers MUST validate per-adapter behavior before routing budget. Promotion to non-experimental gated on the #4592 adapter-contract docs work.' + ), + ] = True + v1_translatable: Annotated[ + Any | None, + Field( + description="Inherently new in v2 — retail-media catalog placements weren't expressible as v1 named formats. SDKs MUST NOT emit `FORMAT_PROJECTION_FAILED` for products using this canonical; the v1-unreachability is structural, not a registry-coverage gap." + ), + ] = False + slots: Any | None = [ + {'asset_group_id': 'source_catalog', 'required': True, 'asset_type': 'catalog'}, + {'asset_group_id': 'hero_asset', 'required': False, 'asset_type': 'image'}, + {'asset_group_id': 'landing_page_url', 'required': False, 'asset_type': 'url'}, + ] + supported_catalog_types: Annotated[ + list[catalog_type.CatalogType] | None, + Field(description='Catalog types this product accepts.'), + ] = None + min_items: Annotated[ + SchemaInt | None, Field(description='Minimum catalog item count buyer must supply.', ge=1) + ] = None + max_items: Annotated[ + SchemaInt | None, Field(description='Maximum items considered for placement.') + ] = None + fanout_mode: Annotated[ + FanoutMode | None, + Field( + description='How items map to delivery: per_item = one ad per catalog item; multi_item_in_creative = composed multi-item ad (Pinterest Collection, Snap Collection); single_item = one ad showing one item.' + ), + ] = None + required_catalog_fields: Annotated[ + list[str] | None, + Field( + description="Catalog item fields the seller requires (e.g., ['title', 'image_url', 'price'])." + ), + ] = None + supported_id_types: Annotated[ + list[SupportedIdType] | None, + Field(description='Catalog identifier types the placement renders against.'), + ] = None + hero_asset_supported: Annotated[ + StrictBool | None, + Field( + description='Whether the buyer can supply a hero/banner asset alongside the catalog (Pinterest Collection pattern).' + ), + ] = None + item_production_model: Annotated[ + ItemProductionModel | None, + Field( + description='How each per-item creative is produced. Covers the same production-source axis as `asset_source` on `image` / `video_hosted` / `audio_hosted` but with a 4-value subset — drops `publisher_host_recorded` because it\'s audio-specific and doesn\'t apply to retail-media catalog placements. SDK codegen MAY share a base enum and narrow per-canonical, or emit two distinct enums; either way the wire values overlap exactly for the 4 retained values. `buyer_uploaded` (default, current Amazon/Criteo/CitrusAd pattern): the buyer\'s catalog already contains rendered assets per item; the seller composes the placement using those assets. ("Uploaded" reads slightly off for catalog-keyed items where the buyer didn\'t actively upload bytes — the catalog ingestion already supplied them — but the semantic is the same: rendered bytes are buyer-supplied, not seller-produced.) `seller_pre_rendered_from_brief`: the buyer ships a brief plus the catalog reference; the seller renders one creative per catalog item from the brief at sync_creatives time. `seller_human_designed`: seller\'s design team produces per-item renders manually. `agent_synthesized`: AI synthesis pipeline produces per-item renders; pair with `synthesis_nondeterministic: true` for Veo/Sora-class generative video applied per item. Captures the multi-output generative pattern (1 brief × N catalog items → N rendered creatives) under the existing canonical without requiring a separate canonical. Distinct from `fanout_mode`, which describes how items map to delivery slots after rendering.' + ), + ] = ItemProductionModel.buyer_uploaded + ctv_ad_experience: Annotated[ + ctv_ad_experience_1.CtvAdExperience | None, + Field( + description='CTV experience this option serves. On `sponsored_placement`: `menu` (sponsored app/content tiles and rows whose assets derive from a catalog listing — Fire-TV-tile pattern), `squeezeback`, and `in_scene` (seller-composited brand integrations produced from the catalog/brief rather than a buyer wire creative). Asset-bundle menu heroes route to `native_in_feed`.' + ), + ] = None + + +class Components9(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + component_id: Annotated[ + str, + Field( + description='Stable coordination-local component identifier. Values MUST be unique within `components[]`.', + pattern='^[a-z][a-z0-9_]*$', + ), + ] + placement_ref: Annotated[ + placement_ref_1.PlacementReference, + Field( + description="Public product placement this component supplies. MUST resolve against the containing product's `placements[]`." + ), + ] + required: StrictBool + sequence: Annotated[ + SchemaInt | None, + Field( + description='Declared presentation order for sequential-messaging coordinated buys. Components sharing a sequence value present simultaneously; absent means unordered/simultaneous (default). Sequence declares seller-rendered ordering, not buyer-controlled timing.', + ge=1, + ), + ] = None + serving_policy: Annotated[ + ServingPolicy | None, + Field( + description='Per-component serving/tracking policy. Some sellers restrict specific components (e.g., page skins) to first-party serving while allowing third-party tags on sibling components. Defaults to the product-level policy when absent.' + ), + ] = None + canvas_constraints: Annotated[ + list[canvas_constraint.CanvasConstraint] | None, + Field( + description='Artwork constraints applied to this component, including safe areas and seller-reserved regions.' + ), + ] = None + format_option_ref: Annotated[ + format_option_ref_1.FormatOptionReference | None, + Field( + description='Reference to a sibling `format_options[]` entry on the same product. It MUST NOT resolve to `custom` or `coordinated_placements`.' + ), + ] = None + format_kind: Literal['sponsored_placement'] = 'sponsored_placement' + params: Annotated[ + Params9, + Field( + description="Catalog-driven retail-media format. Slot: `source_catalog` (catalog asset — product/SKU/ASIN/GTIN catalog reference, REQUIRED), optional `hero_asset`, optional `landing_page_url`. Buyer supplies the catalog reference; surface composes per-item or multi-item rendering using its native placement template. **Composition is deterministic** — buyer can predict per-slot rendering from the catalog item structure. Tracking model: per-item impression + click + conversion (catalog-keyed via offering_id/sku/gtin macros). Covers Amazon Sponsored Products, Criteo Sponsored Products, CitrusAd Sponsored Products, Walmart Connect Sponsored Products, Pinterest Collection (catalog-driven mode).\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for catalog-driven retail-media placements ONLY. The defining feature is the `source_catalog` slot — products under this canonical compose their creative *per catalog item* using the buyer-supplied catalog feed. Without a catalog feed there is nothing to render against. Buyer agents reading `format_kind: sponsored_placement` MUST attach a catalog reference; sellers MUST require `source_catalog` in the manifest.\n\n**Not this canonical (route elsewhere):**\n- IAB in-feed native ads, content-recommendation widgets (Taboola, Outbrain, Yahoo Native, AdMob Native, in-feed sponsored cards) — use `native_in_feed` (asset-bundle composition; no catalog).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Single-image or single-video creative — use `image` or `video_hosted`.\n\nThe earlier broader framing ('any sponsored placement') was too loose for buyer-agent routing — a buyer reading `sponsored_placement` couldn't disambiguate a catalog-driven Amazon SP from an in-feed Taboola widget. As of 3.1, the canonical is narrowed to catalog-keyed retail-media; native moves to `native_in_feed`. Distinct from `responsive_creative` (algorithmic combinator from buyer pool) and `agent_placement` (text/audio AI-surface composition).", + title='Canonical Format: Sponsored Placement (retail-media catalog-driven)', + ), + ] + + +class Params10(CanonicalFormatBase): + model_config = ConfigDict( + extra='allow', + ) + experimental: Annotated[ + Any | None, + Field( + description='Stable at 3.1 GA. Shape mirrors IAB OpenRTB Native 1.2 — the renderer contract is well-established across in-feed native and content-recommendation adopters.' + ), + ] = False + v1_translatable: Annotated[ + Any | None, + Field( + description='Translates to v1 named native formats (e.g., `native_standard`, `native_content`) via the projection registry. Sellers with existing v1 named native formats SHOULD point `v1_format_ref[]` at them.' + ), + ] = True + slots: Annotated[ + Any | None, + Field( + description="Default slot shape for native_in_feed. Mirrors IAB OpenRTB Native 1.2 asset types, including the Native video asset: `video` carries a VAST document (the Native 1.2 `vasttag` field) for video-bearing native units such as CTV menu heroes with focus-triggered playback. Products MAY override (`slots_override` on the projection ref) to narrow per-slot limits (`max_chars` on title/body) or remove unused slots (a content-recommendation slot that doesn't display an icon)." + ), + ] = [ + {'asset_group_id': 'title', 'asset_type': 'text', 'required': True}, + {'asset_group_id': 'body_text', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'main_image', 'asset_type': 'image', 'required': False}, + {'asset_group_id': 'icon', 'asset_type': 'image', 'required': False}, + {'asset_group_id': 'cta', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'advertiser_name', 'asset_type': 'text', 'required': True}, + {'asset_group_id': 'sponsored_label', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': True}, + {'asset_group_id': 'display_url', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'rating', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'price', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'video', 'asset_type': 'vast', 'required': False}, + {'asset_group_id': 'impression_tracker', 'asset_type': 'pixel_tracker', 'required': False}, + {'asset_group_id': 'viewability_tracker', 'asset_type': 'pixel_tracker', 'required': False}, + {'asset_group_id': 'click_tracker', 'asset_type': 'pixel_tracker', 'required': False}, + ] + ctv_ad_experience: Annotated[ + ctv_ad_experience_1.CtvAdExperience | None, + Field( + description='CTV experience this option serves. On native_in_feed `menu` and `overlay` are valid. `menu`: smart-TV home/menu surfaces where the platform assembles buyer assets (background/main image, logo/icon, copy, optional focus-triggered video); `menu_placement` selects the tile vs headline-banner variant, and catalog-derived sponsored tiles route to `sponsored_placement` instead. `overlay`: seller-composited in-stream overlays supplied as an asset bundle (video, logo, imagery, copy, activation copy) — the contract overlay sellers that do not ingest VAST tags use; VAST-ingesting sellers publish a `video_vast` sibling option instead. The wire name stays `native_in_feed` for 3.x even though neither surface is literally in-feed.' + ), + ] = None + menu_placement: Annotated[ + MenuPlacement | None, + Field( + description='Menu surface variant, mapping to OpenRTB Native `plcmttype` 1 (tile/feed) and 3 (headline banner). Valid only with `ctv_ad_experience: "menu"`.' + ), + ] = None + focus_behavior: Annotated[ + FocusBehavior | None, + Field( + description='What happens when the viewer\'s remote focus lands on the unit. `autoplay_*` requires a `video` asset; playback method maps to AdCOM playbackmethod on OpenRTB bridges. Valid only with `ctv_ad_experience: "menu"`.' + ), + ] = None + motion_level: Annotated[ + motion_level_1.CreativeMotionLevel | None, + Field(description='Accepted motion class for the rendered unit (AdCOM attrs 21-23).'), + ] = None + activation_methods: Annotated[ + list[activation_method.CreativeActivationMethod] | None, + Field( + description='Viewer activation mechanisms this option offers (QR, deep link, send-to-device). Activations are engagement events, not impressions.' + ), + ] = None + title_max_chars: Annotated[ + SchemaInt | None, + Field( + description='Maximum character length for the title slot. IAB native typical: 25 (short) to 90 (long). Buyer agents SHOULD validate ship-time title length against this.', + ge=1, + ), + ] = None + body_text_max_chars: Annotated[ + SchemaInt | None, + Field( + description='Maximum character length for the body_text slot. IAB native typical: 90 (mainline) to 140 (extended).', + ge=1, + ), + ] = None + cta_max_chars: Annotated[ + SchemaInt | None, + Field(description='Maximum character length for the cta slot. Typical: 15–25.', ge=1), + ] = None + cta_values: Annotated[ + list[str] | None, + Field( + description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW', 'SIGN_UP', 'DOWNLOAD']). When set, narrows the cta slot to a closed enum." + ), + ] = None + main_image_sizes: Annotated[ + list[MainImageSize] | None, + Field( + description='Accepted logical (width, height) pairs for the main_image slot. Common IAB native sizes: 1200×627 (1.91:1), 1080×1080 (1:1), 1080×1350 (4:5). When the effective main_image slot declares `pixel_ratios`, intrinsic asset dimensions are the matched logical pair multiplied by the selected ratio; absence remains 1x.', + min_length=1, + ), + ] = None + icon_size: Annotated[ + IconSize | None, + Field( + description='Required logical (width, height) for the icon slot when present (typical: 80×80 or 100×100). When the effective icon slot declares `pixel_ratios`, intrinsic dimensions are multiplied by the selected ratio; absence remains 1x.' + ), + ] = None + max_image_file_size_kb: Annotated[ + SchemaInt | None, + Field(description='Maximum file size in kilobytes for main_image and icon.', ge=1), + ] = None + image_formats: Annotated[ + list[ImageFormat1] | None, Field(description='Permitted image file formats.') + ] = None + ssl_required: Annotated[ + StrictBool | None, + Field( + description='Whether trackers, landing pages, and image URLs must be served over HTTPS.' + ), + ] = None + asset_source: Annotated[ + AssetSource5 | None, + Field( + description="Where the rendered native assets come from. `publisher_host_recorded` is omitted (audio-specific and not meaningful for native). Other values mirror the shared production-source axis used on `image` / `video_hosted`. `buyer_uploaded` (default): buyer ships pre-rendered title/image/body. `seller_pre_rendered_from_brief`: buyer ships a brief, seller renders the native bundle. `agent_synthesized`: AI synthesis pipeline produces title + image + body from a brief; pair with `synthesis_nondeterministic: true` for generative pipelines that can't guarantee in-spec output. `publisher_owned_reference`: buyer ships an existing published post reference; the seller resolves the post into the native presentation after authorization/review." + ), + ] = AssetSource5.buyer_uploaded + buyer_asset_acceptance: Annotated[ + BuyerAssetAcceptance | None, + Field( + description='Whether the product accepts buyer-uploaded native assets. When `rejected`, the buyer cannot ship pre-rendered title/image/body — they must use `build_creative`, `sync_creatives` with brief inputs, or an accepted `published_post` reference so the seller produces or resolves the native bundle.' + ), + ] = BuyerAssetAcceptance.accepted + + +class Components10(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + component_id: Annotated[ + str, + Field( + description='Stable coordination-local component identifier. Values MUST be unique within `components[]`.', + pattern='^[a-z][a-z0-9_]*$', + ), + ] + placement_ref: Annotated[ + placement_ref_1.PlacementReference, + Field( + description="Public product placement this component supplies. MUST resolve against the containing product's `placements[]`." + ), + ] + required: StrictBool + sequence: Annotated[ + SchemaInt | None, + Field( + description='Declared presentation order for sequential-messaging coordinated buys. Components sharing a sequence value present simultaneously; absent means unordered/simultaneous (default). Sequence declares seller-rendered ordering, not buyer-controlled timing.', + ge=1, + ), + ] = None + serving_policy: Annotated[ + ServingPolicy | None, + Field( + description='Per-component serving/tracking policy. Some sellers restrict specific components (e.g., page skins) to first-party serving while allowing third-party tags on sibling components. Defaults to the product-level policy when absent.' + ), + ] = None + canvas_constraints: Annotated[ + list[canvas_constraint.CanvasConstraint] | None, + Field( + description='Artwork constraints applied to this component, including safe areas and seller-reserved regions.' + ), + ] = None + format_option_ref: Annotated[ + format_option_ref_1.FormatOptionReference | None, + Field( + description='Reference to a sibling `format_options[]` entry on the same product. It MUST NOT resolve to `custom` or `coordinated_placements`.' + ), + ] = None + format_kind: Literal['native_in_feed'] = 'native_in_feed' + params: Annotated[ + Params10, + Field( + description='IAB-shaped native creative for in-feed and content-recommendation surfaces. Default slots cover the primary IAB OpenRTB Native 1.2 asset types — `title` (Title Asset), `body_text` (Data Asset type 2), `main_image` (Image Asset main), `icon` (Image Asset icon), `cta` (Data Asset type 12), `advertiser_name` (Data Asset type 1), `sponsored_label` (Title-adjacent), `landing_page_url` (Link Asset), `display_url` (Data Asset type 11 — visible URL/domain, distinct from clickthrough), `rating` (Data Asset type 3 — app/product rating), `price` (Data Asset type 6 — product price), plus renderer-fired `impression_tracker` / `viewability_tracker` / `click_tracker` (`pixel_tracker`). Products MAY use `slots_override` to add other IAB Native data asset types (likes — type 4, downloads — type 5, saleprice — type 7, phone_number — type 8, address — type 9, desc2 — type 10, etc.) or to remove slots the surface doesn\'t render. The publisher\'s renderer assembles these into its own look-and-feel — feed card, content-recommendation slot, in-stream native unit. Buyer ships a single asset bundle; the surface chooses presentation.\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for:\n- IAB OpenRTB Native 1.2 in-feed native ads (publisher feeds, app feeds)\n- Content-recommendation widgets (Taboola, Outbrain, Yahoo Recommendations)\n- AdMob Native / Yahoo Native publisher slots\n- In-feed sponsored placements without catalog dependency\n\n**Not this canonical:**\n- Catalog-driven retail-media (Amazon SP, Criteo SP, CitrusAd SP) — use `sponsored_placement` (requires `source_catalog`).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Multi-card carousel — use `image_carousel`.\n- Video-first native units where the asset is a hosted video file — use `video_hosted` with `applies_to_channels: ["native"]`. (Distinct from the CTV menu profile: a menu hero remains this canonical because the platform assembles the full asset bundle and the video rides the Native 1.2 `vasttag` video asset, playing on focus rather than being the unit itself.)\n\nDistinct from `sponsored_placement` along the catalog axis: native_in_feed is asset-bundle composition; sponsored_placement is catalog-row composition. A buyer agent reading `format_kind: native_in_feed` knows to assemble title + image + body + CTA; reading `format_kind: sponsored_placement` knows to attach a catalog feed.', + title='Canonical Format: Native In-Feed', + ), + ] + + +class Params11(CanonicalFormatBase): + model_config = ConfigDict( + extra='allow', + ) + experimental: Annotated[ + Any | None, + Field( + description="Marked experimental at 3.1 GA: composition is algorithmic (the surface picks combinations and reports per-asset breakdowns), and there's no clean v1-translatable equivalent. Buyers ship asset pools rather than rendered creatives; the surface's per-impression composition cannot be predicted by `validate_input`. Adopters SHOULD validate behavior per surface (Google PMax vs Meta Advantage+ creative differ meaningfully)." + ), + ] = True + v1_translatable: Annotated[ + Any | None, + Field( + description="Inherently new in v2 — algorithmic asset-pool composition (Google PMax / Meta Advantage+ creative) wasn't expressible as v1 named formats. SDKs MUST NOT emit `FORMAT_PROJECTION_FAILED` for products using this canonical; the v1-unreachability is structural." + ), + ] = False + slots: Any | None = [ + { + 'asset_group_id': 'headlines', + 'asset_type': 'text', + 'required': True, + 'min': 3, + 'max': 15, + }, + { + 'asset_group_id': 'long_headlines', + 'asset_type': 'text', + 'required': False, + 'min': 1, + 'max': 5, + }, + { + 'asset_group_id': 'descriptions', + 'asset_type': 'text', + 'required': True, + 'min': 2, + 'max': 5, + }, + { + 'asset_group_id': 'images_landscape', + 'asset_type': 'image', + 'required': False, + 'min': 1, + 'max': 20, + }, + { + 'asset_group_id': 'images_square', + 'asset_type': 'image', + 'required': False, + 'min': 1, + 'max': 20, + }, + { + 'asset_group_id': 'images_vertical', + 'asset_type': 'image', + 'required': False, + 'min': 1, + 'max': 20, + }, + {'asset_group_id': 'video', 'asset_type': 'video', 'required': False, 'min': 0, 'max': 5}, + { + 'asset_group_id': 'logo', + 'asset_type': 'image', + 'required': True, + 'min': 1, + 'max': 5, + 'logo_slots': [ + 'logo_card_light', + 'logo_card_dark', + 'marketplace_listing', + 'ad_end_card', + ], + 'required_logo_slots': ['logo_card_light', 'logo_card_dark'], + }, + { + 'asset_group_id': 'landing_page_url', + 'asset_type': 'url', + 'required': True, + 'min': 1, + 'max': 1, + }, + ] + headlines_min: Annotated[SchemaInt | None, Field(ge=0)] = None + headlines_max: Annotated[SchemaInt | None, Field(ge=0)] = None + headline_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None + long_headlines_min: Annotated[SchemaInt | None, Field(ge=0)] = None + long_headlines_max: Annotated[SchemaInt | None, Field(ge=0)] = None + long_headline_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None + descriptions_min: Annotated[SchemaInt | None, Field(ge=0)] = None + descriptions_max: Annotated[SchemaInt | None, Field(ge=0)] = None + description_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None + images_landscape_min: Annotated[SchemaInt | None, Field(ge=0)] = None + images_landscape_max: Annotated[SchemaInt | None, Field(ge=0)] = None + images_landscape_aspect_ratio: str | None = None + images_square_min: Annotated[SchemaInt | None, Field(ge=0)] = None + images_square_max: Annotated[SchemaInt | None, Field(ge=0)] = None + images_vertical_min: Annotated[SchemaInt | None, Field(ge=0)] = None + images_vertical_max: Annotated[SchemaInt | None, Field(ge=0)] = None + videos_min: Annotated[SchemaInt | None, Field(ge=0)] = None + videos_max: Annotated[SchemaInt | None, Field(ge=0)] = None + video_min_duration_ms: Annotated[SchemaInt | None, Field(ge=1)] = None + video_max_duration_ms: Annotated[SchemaInt | None, Field(ge=1)] = None + logo_min: Annotated[SchemaInt | None, Field(ge=0)] = None + logo_max: Annotated[SchemaInt | None, Field(ge=0)] = None + logo_aspect_ratios: list[str] | None = None + business_name_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None + asset_image_max_file_size_kb: Annotated[SchemaInt | None, Field(ge=1)] = None + supports_catalog_input: Annotated[ + StrictBool | None, + Field( + description='Whether the product can additionally consume a catalog reference (e.g., PMax with product feed).' + ), + ] = None + + +class Components11(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + component_id: Annotated[ + str, + Field( + description='Stable coordination-local component identifier. Values MUST be unique within `components[]`.', + pattern='^[a-z][a-z0-9_]*$', + ), + ] + placement_ref: Annotated[ + placement_ref_1.PlacementReference, + Field( + description="Public product placement this component supplies. MUST resolve against the containing product's `placements[]`." + ), + ] + required: StrictBool + sequence: Annotated[ + SchemaInt | None, + Field( + description='Declared presentation order for sequential-messaging coordinated buys. Components sharing a sequence value present simultaneously; absent means unordered/simultaneous (default). Sequence declares seller-rendered ordering, not buyer-controlled timing.', + ge=1, + ), + ] = None + serving_policy: Annotated[ + ServingPolicy | None, + Field( + description='Per-component serving/tracking policy. Some sellers restrict specific components (e.g., page skins) to first-party serving while allowing third-party tags on sibling components. Defaults to the product-level policy when absent.' + ), + ] = None + canvas_constraints: Annotated[ + list[canvas_constraint.CanvasConstraint] | None, + Field( + description='Artwork constraints applied to this component, including safe areas and seller-reserved regions.' + ), + ] = None + format_option_ref: Annotated[ + format_option_ref_1.FormatOptionReference | None, + Field( + description='Reference to a sibling `format_options[]` entry on the same product. It MUST NOT resolve to `custom` or `coordinated_placements`.' + ), + ] = None + format_kind: Literal['responsive_creative'] = 'responsive_creative' + params: Annotated[ + Params11, + Field( + description='Buyer supplies a pool of typed assets (multiple headlines, descriptions, images, videos, logos); the surface algorithmically composes combinations per placement. **Composition is algorithmic** — surface picks combinations and reports per-asset performance breakdowns. Covers Google Responsive Display Ads (RDA), Responsive Search Ads (RSA), Performance Max (PMax), Demand Gen, and Meta Advantage+ creative. Industry term: "Responsive" (Google) / "Advantage+ creative" (Meta) / "Dynamic Creative" (older Meta term). Distinct from `sponsored_placement` (catalog-driven, deterministic) and `agent_placement` (AI-surface composition). The structured `slots` field below enumerates expected canonical asset_group_id slots; per-slot count/length narrowing lives in flat parameters (`headlines_min`, `headline_max_chars`, etc.).', + title='Canonical Format: Responsive Creative', + ), + ] + + +class Params12(CanonicalFormatBase): + model_config = ConfigDict( + extra='allow', + ) + experimental: Annotated[ + Any | None, + Field( + description="Marked experimental at 3.1 GA: the canonical's tracking model (mention-level impression + attribution, postback shape, cross-surface dedup) is intentionally underspecified for 3.1. Adopters claiming `agent_placement` ship private tracking integrations; buyer agents MUST treat attribution as adapter-defined until the 3.2 tracking-macro spec lands. Promotion to non-experimental gated on the 3.2 tracking-contract spec." + ), + ] = True + v1_translatable: Annotated[ + Any | None, + Field( + description="Inherently new in v2 — AI-surface sponsored mentions weren't expressible as v1 named formats. SDKs MUST NOT emit `FORMAT_PROJECTION_FAILED` for products using this canonical; the v1-unreachability is structural." + ), + ] = False + slots: Annotated[ + Any | None, + Field( + description="agent_placement has minimal buyer-shipped slots — the surface composes the rendered output from brand context (resolved via the manifest's top-level `brand` BrandRef) plus optional offering_ref and landing_page_url assets. None of these assets are rendered verbatim by the buyer; the agent chooses how to use them." + ), + ] = [ + {'asset_group_id': 'offering_ref', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, + ] + output_modality: Annotated[ + OutputModality | None, + Field( + description='How the surface presents the mention. `text` = inline text (chat, search snippet). `audio` = TTS-synthesized voice. `card` = structured card with optional image + text.' + ), + ] = None + max_mention_length_chars: Annotated[ + SchemaInt | None, + Field( + description='For text output: maximum length of the surface-composed mention text.', + ge=1, + ), + ] = None + max_mention_duration_ms: Annotated[ + SchemaInt | None, + Field( + description='For audio output: maximum duration of the spoken mention in milliseconds.', + ge=1, + ), + ] = None + supports_offering_reference: Annotated[ + StrictBool | None, + Field( + description='Whether the product accepts an offering reference (specific product/service to promote within the mention) in addition to brand context.' + ), + ] = None + supports_landing_page_url: Annotated[ + StrictBool | None, + Field( + description='Whether the surface attaches a landing page URL to the mention (citation, learn-more link).' + ), + ] = None + tone_constraints: Annotated[ + list[str] | None, + Field( + description="**Advisory only.** Buyer-declared brand-voice preferences the surface SHOULD honor (e.g., ['formal', 'no_superlatives']). LLM/agentic surfaces have no protocol-level mechanism to verify enforcement — adopters that need hard guarantees should rely on brand.json voice declarations and post-mention review rather than this field. Future revisions may tie this to a structured tone vocabulary; for now treat as free-text guidance." + ), + ] = None + disclosure_required: Annotated[ + StrictBool | None, + Field( + description='Whether the surface must include an explicit sponsorship disclosure label.' + ), + ] = None + + +class Components12(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + component_id: Annotated[ + str, + Field( + description='Stable coordination-local component identifier. Values MUST be unique within `components[]`.', + pattern='^[a-z][a-z0-9_]*$', + ), + ] + placement_ref: Annotated[ + placement_ref_1.PlacementReference, + Field( + description="Public product placement this component supplies. MUST resolve against the containing product's `placements[]`." + ), + ] + required: StrictBool + sequence: Annotated[ + SchemaInt | None, + Field( + description='Declared presentation order for sequential-messaging coordinated buys. Components sharing a sequence value present simultaneously; absent means unordered/simultaneous (default). Sequence declares seller-rendered ordering, not buyer-controlled timing.', + ge=1, + ), + ] = None + serving_policy: Annotated[ + ServingPolicy | None, + Field( + description='Per-component serving/tracking policy. Some sellers restrict specific components (e.g., page skins) to first-party serving while allowing third-party tags on sibling components. Defaults to the product-level policy when absent.' + ), + ] = None + canvas_constraints: Annotated[ + list[canvas_constraint.CanvasConstraint] | None, + Field( + description='Artwork constraints applied to this component, including safe areas and seller-reserved regions.' + ), + ] = None + format_option_ref: Annotated[ + format_option_ref_1.FormatOptionReference | None, + Field( + description='Reference to a sibling `format_options[]` entry on the same product. It MUST NOT resolve to `custom` or `coordinated_placements`.' + ), + ] = None + format_kind: Literal['agent_placement'] = 'agent_placement' + params: Annotated[ + Params12, + Field( + description="**3.2-track canonical.** The structural shape (algorithmic composition + brand-context input + optional offering/landing_page) is captured here so adopters can declare against it in 3.1 catalogs, but the **mention-level tracking contract is intentionally underspecified for 3.1**: no normative macro vocabulary, no postback shape, no cross-surface dedup model. Adopters claiming `agent_placement` in 3.1 ship private tracking integrations and SHOULD leave `experimental: true` on the product declaration that references this canonical; buyer agents MUST treat agent_placement attribution as adapter-defined until the 3.2 tracking-macro spec lands. The canonical promotes to a normatively-buyer-callable surface in 3.2 (or later) once the tracking contract is specified.\n\nSponsored placement integrated into an AI-surface's response to a user. Buyer supplies a `BrandRef` (resolving brand.json for context), an optional `offering_ref` to focus the mention on a specific offering, and an optional `landing_page_url` the surface MAY attach as a citation. The surface (LLM, voice assistant, sponsored-search ranker) composes a natural-language mention, sponsored card, or audio snippet within its response to a user query. **Composition is algorithmic** — the agent chooses phrasing and presentation. Output asset_type varies by surface: `text` for chat UIs and sponsored search snippets; `audio` (synthesized) for voice assistants; `card` for structured AI-surface result cards. Tracking model: mention-level impression + attribution events; per-mention id keys back to brand and offering — but see the 3.2-track note above; the wire shape of these events is not yet specified. Distinct from `si_chat` (which is the user-converses-with-brand's-agent pattern — brand owns the conversational surface) and from `sponsored_placement` (retail-media catalog-driven). Parallels `sponsored_placement` structurally: both are surface-composed placements; agent_placement is for AI/agentic surfaces, sponsored_placement is for retail media.", + title='Canonical Format: Agent Placement (AI-surface sponsored placement)', + ), + ] + + +class Params13(CanonicalFormatBase): + model_config = ConfigDict( + extra='allow', + ) + experimental: Annotated[ + Any | None, + Field( + description='Experimental in AdCP 3.2 while the creative working group gathers implementation evidence across premium web and mobile/app sellers.' + ), + ] = True + v1_translatable: Annotated[ + Any | None, + Field( + description='No v1 named-format equivalent can express multiple seller-rendered states and their breakpoint bindings.' + ), + ] = False + since_version: Any | None = '3.2' + composition_model: Any | None = 'deterministic' + supply_mode: Annotated[ + SupplyMode | None, + Field( + description='Which end of the template contract the buyer feeds. `components`: buyer supplies component slots; seller renders states (no `state_canvases`/`layered_source` assets allowed). `rendered_canvases`: buyer supplies exactly one `state_canvases` image per declared state × breakpoint pair. `layered_source`: buyer ships design source (+ optional `font_files`); seller production derives states (`production_window_business_days` applies) — transitional for sellers without executable templates.' + ), + ] = SupplyMode.components + slots: Annotated[ + Any | None, + Field( + description='Default manifest slots; which are consumed depends on `supply_mode`. `state_canvases` images MUST carry `state_id` and `breakpoint_id`, and `state_click_urls` entries MUST carry `state_id` (semantic validators resolve the bindings). Component images SHOULD carry `focal_point` for deterministic seller cropping. `landing_page_url` is the default destination (see `clickthrough`). `font_files` MUST contain only buyer-licensed fonts; publisher-proprietary fonts never travel in manifests. Only image, video, text, url, zip, and pixel_tracker slot asset types are accepted — executable types (javascript, html, css, webhook) are rejected even via `slots` overrides.' + ), + ] = [ + {'asset_group_id': 'state_canvases', 'asset_type': 'image', 'required': False, 'min': 1}, + {'asset_group_id': 'logo', 'asset_type': 'image', 'required': False}, + {'asset_group_id': 'imagery', 'asset_type': 'image', 'required': False, 'min': 0}, + {'asset_group_id': 'headline', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'messaging', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'cta', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'legal_text', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'video_main', 'asset_type': 'video', 'required': False}, + {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': True}, + {'asset_group_id': 'state_click_urls', 'asset_type': 'url', 'required': False, 'min': 0}, + {'asset_group_id': 'layered_source', 'asset_type': 'zip', 'required': False}, + {'asset_group_id': 'font_files', 'asset_type': 'zip', 'required': False}, + ] + states: Annotated[ + list[State], + Field( + description='Finite visual states known at buy time; state and breakpoint IDs form the canvas-key matrix. Runtime causes live in `transitions[]`. A single-state unit (topscroll, interscroller, skin) declares one state, no transitions, and typically a `reveal` mechanic.', + min_length=1, + ), + ] + initial_state_id: Annotated[ + str, + Field( + description='State rendered when the unit first becomes visible. MUST resolve to `states[].state_id`; for a single-state unit it MUST equal the sole state.', + pattern='^[a-z][a-z0-9_]*$', + ), + ] + reveal: Annotated[ + Reveal | None, + Field( + description='How the unit enters view, distinct from state changes. `clip_window`: canvas fixed and progressively exposed through a scrolling window (interscroller, topscroll). `scroll_parallax`: canvas moves at a different rate than content. Reveal is presentation of one canvas, not a transition; do not fabricate a second state to express it.' + ), + ] = Reveal.none + transitions: Annotated[ + list[Transitions | Transitions1 | Transitions2 | Transitions3 | Transitions4 | Transitions5] + | None, + Field( + description='Bounded seller-rendered transitions between declared visual states. Required when `states` has more than one entry; MUST be omitted for single-state units. Every non-initial state MUST be reachable from `initial_state_id`. Dismissal is terminal unit behavior declared by `user_controls.dismissible`, not a hidden visual state.', + min_length=1, + ), + ] = None + clickthrough: Annotated[ + Clickthrough | None, + Field( + description='Destination policy. `required` (default): manifest MUST supply `landing_page_url`. `optional`: click-optional units (in-feed brand units) may omit it. `none`: unit is non-clickable; manifests MUST NOT supply `landing_page_url` or `state_click_urls`. Per-state overrides via `state_click_urls` entries carrying `state_id`; `landing_page_url` is the fallback for unlisted states.' + ), + ] = Clickthrough.required + user_controls: Annotated[ + UserControls, + Field( + description="When any state anchors as `overlay` or `fullscreen_overlay`, either `dismissible` MUST be true or that state's `close_affordance` MUST be true (dismissibility floor; semantic validators enforce)." + ), + ] + canvas_constraints: Annotated[ + list[canvas_constraint.CanvasConstraint] | None, + Field( + description='Rectangular areas constraining buyer artwork. Omitted state/breakpoint selectors apply the constraint to every canvas. For fluid or range-sized breakpoints, use percent-unit regions.' + ), + ] = None + duration_ms_range: Annotated[ + list[DurationMsRange | None] | None, + Field( + description='Accepted embedded-video duration [min, max]. `duration_ms_exact` takes precedence when both are present.', + max_length=2, + min_length=2, + ), + ] = None + duration_ms_exact: Annotated[SchemaInt | None, Field(ge=1)] = None + aspect_ratio: Annotated[ + str | None, + Field( + description='Embedded-video aspect ratio.', + pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', + ), + ] = None + containers: list[Container] | None = None + video_playback: VideoPlayback | None = None + max_initial_load_kb: Annotated[SchemaInt | None, Field(ge=1)] = None + max_subload_kb: Annotated[ + SchemaInt | None, + Field( + description='Ceiling on assets loaded after the window load event (IAB LEAN subload). Pairs with `max_initial_load_kb` to mirror the New Ad Portfolio initial/subload weight pair.', + ge=1, + ), + ] = None + polite_load: Annotated[ + StrictBool | None, + Field( + description="When true, non-initial assets load only after the host page's window load event (IAB LEAN subload boundary)." + ), + ] = None + + +class Components13(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + component_id: Annotated[ + str, + Field( + description='Stable coordination-local component identifier. Values MUST be unique within `components[]`.', + pattern='^[a-z][a-z0-9_]*$', + ), + ] + placement_ref: Annotated[ + placement_ref_1.PlacementReference, + Field( + description="Public product placement this component supplies. MUST resolve against the containing product's `placements[]`." + ), + ] + required: StrictBool + sequence: Annotated[ + SchemaInt | None, + Field( + description='Declared presentation order for sequential-messaging coordinated buys. Components sharing a sequence value present simultaneously; absent means unordered/simultaneous (default). Sequence declares seller-rendered ordering, not buyer-controlled timing.', + ge=1, + ), + ] = None + serving_policy: Annotated[ + ServingPolicy | None, + Field( + description='Per-component serving/tracking policy. Some sellers restrict specific components (e.g., page skins) to first-party serving while allowing third-party tags on sibling components. Defaults to the product-level policy when absent.' + ), + ] = None + canvas_constraints: Annotated[ + list[canvas_constraint.CanvasConstraint] | None, + Field( + description='Artwork constraints applied to this component, including safe areas and seller-reserved regions.' + ), + ] = None + format_option_ref: Annotated[ + format_option_ref_1.FormatOptionReference | None, + Field( + description='Reference to a sibling `format_options[]` entry on the same product. It MUST NOT resolve to `custom` or `coordinated_placements`.' + ), + ] = None + format_kind: Literal['seller_rendered_stateful_display'] = 'seller_rendered_stateful_display' + params: Annotated[ + Params13, + Field( + description='Seller-rendered display unit whose declaration is an executable template contract: buyer-known visual states, explicit transitions, breakpoint canvases, and per-state slot bindings. The seller owns the runtime; `supply_mode` declares which end the buyer feeds. For machine-rendered `components` and `rendered_canvases` supply, sellers MUST support `preview_creative` returning every state × breakpoint from a candidate manifest. `layered_source` instead follows the asynchronous seller-production preview path after the declared production window. `composition_model: deterministic` describes serving the finished states, not instant derivation from layered source. Buyer-executable HTML/MRAID is `html5`, a buyer-delivered tag is `display_tag`, arbitrary games/hotspots/scripts remain `custom`, and per-impression algorithmic assembly is `responsive_creative`.', + title='Canonical Format: Seller-Rendered Stateful Display', + ), + ] + + +class Params8(CanonicalFormatBase): + model_config = ConfigDict( + extra='allow', + ) + slots: Annotated[ + Any | None, + Field( + description="Default slots for audio_daast canonical. Buyer ships a DAAST tag (URL or inline XML, 1.0 or 1.1) plus an optional clickthrough URL. Tracking events are inherent to DAAST and don't require explicit slots." + ), + ] = [ + {'asset_group_id': 'daast_tag', 'asset_type': 'daast', 'required': True}, + {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, + ] + daast_version: Annotated[ + daast_version_1.DaastVersion | None, + Field( + deprecated=True, + description='Deprecated one-element alias for `daast_versions`. Producers use either the singular legacy alias or the plural 3.2 field, never both.', + ), + ] = None + daast_versions: Annotated[ + daast_tracker_constraints.DaastVersions | None, + Field( + description="Accepted DAAST versions for this format option. A tracker execution selector's daast_versions must be a nonempty subset of this set." + ), + ] = None + duration_ms_range: Annotated[ + list[DurationMsRangeItem] | None, + Field( + description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', + max_length=2, + min_length=2, + ), + ] = None + duration_ms_exact: Annotated[ + SchemaInt | None, + Field( + description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', + ge=1, + ), + ] = None + linear_required: StrictBool | None = None + max_wrapper_depth: Annotated[SchemaInt | None, Field(ge=0)] = None + ssl_required: StrictBool | None = None + companion_image_required: StrictBool | None = None + + +class Components8(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + component_id: Annotated[ + str, + Field( + description='Stable coordination-local component identifier. Values MUST be unique within `components[]`.', + pattern='^[a-z][a-z0-9_]*$', + ), + ] + placement_ref: Annotated[ + placement_ref_1.PlacementReference, + Field( + description="Public product placement this component supplies. MUST resolve against the containing product's `placements[]`." + ), + ] + required: StrictBool + sequence: Annotated[ + SchemaInt | None, + Field( + description='Declared presentation order for sequential-messaging coordinated buys. Components sharing a sequence value present simultaneously; absent means unordered/simultaneous (default). Sequence declares seller-rendered ordering, not buyer-controlled timing.', + ge=1, + ), + ] = None + serving_policy: Annotated[ + ServingPolicy | None, + Field( + description='Per-component serving/tracking policy. Some sellers restrict specific components (e.g., page skins) to first-party serving while allowing third-party tags on sibling components. Defaults to the product-level policy when absent.' + ), + ] = None + canvas_constraints: Annotated[ + list[canvas_constraint.CanvasConstraint] | None, + Field( + description='Artwork constraints applied to this component, including safe areas and seller-reserved regions.' + ), + ] = None + format_option_ref: Annotated[ + format_option_ref_1.FormatOptionReference | None, + Field( + description='Reference to a sibling `format_options[]` entry on the same product. It MUST NOT resolve to `custom` or `coordinated_placements`.' + ), + ] = None + format_kind: Literal['audio_daast'] = 'audio_daast' + params: Annotated[ + Params8, + Field( + description='DAAST-tag-delivered audio creative (audio analog of VAST). Slot: `daast_tag` (daast asset, URL or inline XML). Tracking model: DAAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `clickTracking`, `error`. Distinct from `audio_hosted` (direct file with external tracking).', + title='Canonical Format: DAAST Audio', + ), + ] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.formats.canonical.coordinated_placements import ( - AllowedCardMediaAssetType, - Anchoring, - AssetSource, - AssetSource5, - AssetType, - AudioChannel, - AudioCodec, - AudioCodec2, - AudioSampleRate, - Breakpoint, - BuyerAssetAcceptance, - CanonicalFormatCoordinatedPlacements, - Captions, - ClicktagMacro, - Clickthrough, - CompanionBannerHeight, - CompanionBannerWidth, - Components, - Components1, - Components10, - Components11, - Components12, - Components13, - Components2, - Components3, - Components4, - Components5, - Components6, - Components7, - Components8, - Components9, - Container, - CreativeType, - Direction, - DurationMsRange, - DurationMsRangeItem, - FanoutMode, - FocusBehavior, - HeightRangeItem, - IconSize, - ImageFormat, - ImageFormat1, - Input, - ItemProductionModel, - MainImageSize, - MediaEvent, - MenuPlacement, - Motion, - MotionLevel, - MraidVersion, - Orientation, - OutputModality, - Params, - Params10, - Params11, - Params12, - Params13, - Params2, - Params3, - Params4, - Params5, - Params6, - Params7, - Params8, - Params9, - PixelRatio, - Reveal, - ScrollReference, - ServingPolicy, - SharedSlot, - Size, - SlotBinding, - State, - SupplyMode, - SupportedDeliveryType, - SupportedIdType, - SupportedTagType, - TransitionMode, - TransitionMode3, - Transitions, - Transitions1, - Transitions2, - Transitions3, - Transitions4, - Transitions5, - Trigger, - UserControls, - VideoCodec, - VideoPlayback, - VpaidVersion, - WidthMode, - WidthRangeItem, -) - -# Explicit exports -__all__ = [ - "AllowedCardMediaAssetType", - "Anchoring", - "AssetSource", - "AssetSource5", - "AssetType", - "AudioChannel", - "AudioCodec", - "AudioCodec2", - "AudioSampleRate", - "Breakpoint", - "BuyerAssetAcceptance", - "CanonicalFormatCoordinatedPlacements", - "Captions", - "ClicktagMacro", - "Clickthrough", - "CompanionBannerHeight", - "CompanionBannerWidth", - "Components", - "Components1", - "Components10", - "Components11", - "Components12", - "Components13", - "Components2", - "Components3", - "Components4", - "Components5", - "Components6", - "Components7", - "Components8", - "Components9", - "Container", - "CreativeType", - "Direction", - "DurationMsRange", - "DurationMsRangeItem", - "FanoutMode", - "FocusBehavior", - "HeightRangeItem", - "IconSize", - "ImageFormat", - "ImageFormat1", - "Input", - "ItemProductionModel", - "MainImageSize", - "MediaEvent", - "MenuPlacement", - "Motion", - "MotionLevel", - "MraidVersion", - "Orientation", - "OutputModality", - "Params", - "Params10", - "Params11", - "Params12", - "Params13", - "Params2", - "Params3", - "Params4", - "Params5", - "Params6", - "Params7", - "Params8", - "Params9", - "PixelRatio", - "Reveal", - "ScrollReference", - "ServingPolicy", - "SharedSlot", - "Size", - "SlotBinding", - "State", - "SupplyMode", - "SupportedDeliveryType", - "SupportedIdType", - "SupportedTagType", - "TransitionMode", - "TransitionMode3", - "Transitions", - "Transitions1", - "Transitions2", - "Transitions3", - "Transitions4", - "Transitions5", - "Trigger", - "UserControls", - "VideoCodec", - "VideoPlayback", - "VpaidVersion", - "WidthMode", - "WidthRangeItem", -] +class CanonicalFormatCoordinatedPlacements(CanonicalFormatBase): + model_config = ConfigDict( + extra='allow', + ) + experimental: Annotated[ + Any | None, + Field( + description='Experimental in AdCP 3.2 while the creative working group gathers implementation evidence for atomic cross-placement composition.' + ), + ] = True + v1_translatable: Annotated[ + Any | None, + Field( + description='No v1 named-format equivalent can express a coordinated multi-placement buy.' + ), + ] = False + since_version: Any | None = '3.2' + composition_model: Any | None = 'deterministic' + components: Annotated[ + list[ + Components + | Components1 + | Components2 + | Components3 + | Components4 + | Components5 + | Components6 + | Components7 + | Components8 + | Components9 + | Components10 + | Components11 + | Components12 + | Components13 + ], + Field(min_length=2), + ] + shared_slots: Annotated[ + list[SharedSlot] | None, + Field( + description='Manifest slots supplied once and consumed by one or more coordinated components.' + ), + ] = None diff --git a/src/adcp/types/domains/formats/canonical/display_tag.py b/src/adcp/types/domains/formats/canonical/display_tag.py index d15c2906f..cd4079799 100644 --- a/src/adcp/types/domains/formats/canonical/display_tag.py +++ b/src/adcp/types/domains/formats/canonical/display_tag.py @@ -1,23 +1,134 @@ -"""Types declared by the AdCP ``formats/canonical/display_tag`` schema. +# generated by datamodel-codegen: +# filename: formats/canonical/display_tag.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.formats.canonical.display_tag import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations +from ._base import CanonicalFormatBase + + +class Size(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + width: Annotated[SchemaInt, Field(ge=1)] + height: Annotated[SchemaInt, Field(ge=1)] + + +class SupportedTagType(StrEnum): + iframe = 'iframe' + javascript = 'javascript' + field_1x1_redirect = '1x1_redirect' + + +class SupportedDeliveryType(StrEnum): + tag_url = 'tag_url' + inline_markup = 'inline_markup' + paired_redirect = 'paired_redirect' -from adcp.types.generated_poc.formats.canonical.display_tag import ( - CanonicalFormatDisplayTag, - Size, - SupportedDeliveryType, - SupportedTagType, -) -# Explicit exports -__all__ = ["CanonicalFormatDisplayTag", "Size", "SupportedDeliveryType", "SupportedTagType"] +class CanonicalFormatDisplayTag(CanonicalFormatBase): + model_config = ConfigDict( + extra='allow', + ) + slots: Annotated[ + Any | None, + Field( + description='Backward-compatible URL-delivery slots. `tag_url` MUST use `url_type: ad_request`. A format option accepting `inline_markup` or `paired_redirect` overrides this list with a required slot whose `asset_type` is `display_tag`; the display-tag asset keeps paired redirect URLs atomic.' + ), + ] = [ + {'asset_group_id': 'tag_url', 'asset_type': 'url', 'required': True}, + {'asset_group_id': 'backup_image', 'asset_type': 'image', 'required': False}, + ] + width: Annotated[ + SchemaInt | None, + Field( + description='Required tag rendering width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', + ge=1, + ), + ] = None + height: Annotated[ + SchemaInt | None, + Field( + description='Required tag rendering height in pixels. See `width` for size-mode mutual exclusion.', + ge=1, + ), + ] = None + sizes: Annotated[ + list[Size] | None, + Field( + description="List of accepted (width, height) pairs for a multi-size flexible slot. The buyer's third-party tag must render at one of the listed sizes; the seller picks which size to request at impression time. Mutually exclusive with `(width, height)` and with responsive ranges.", + min_length=1, + ), + ] = None + min_width: Annotated[ + SchemaInt | None, + Field( + description='Minimum accepted width for responsive third-party tags. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', + ge=1, + ), + ] = None + max_width: Annotated[ + SchemaInt | None, + Field( + description='Maximum accepted width for responsive third-party tags. Pair with `min_width`.', + ge=1, + ), + ] = None + min_height: Annotated[ + SchemaInt | None, + Field( + description='Minimum accepted height for responsive third-party tags. Pair with `max_height`.', + ge=1, + ), + ] = None + max_height: Annotated[ + SchemaInt | None, + Field( + description='Maximum accepted height for responsive third-party tags. Pair with `min_height`.', + ge=1, + ), + ] = None + supported_tag_types: Annotated[ + list[SupportedTagType] | None, + Field( + deprecated=True, + description='Deprecated ambiguous mechanism list. Use `supported_delivery_types`; markup subtype lives on the `display_tag` asset.', + ), + ] = None + supported_delivery_types: Annotated[ + list[SupportedDeliveryType] | None, + Field( + description='Closed set of delivery types this format option can traffic. `paired_redirect` means one atomic ad-request/click-through pair (Internal Redirect semantics), never independently matchable URL slots.', + min_length=1, + ), + ] = None + ssl_required: Annotated[ + StrictBool | None, Field(description='Whether the tag URL must be HTTPS.') + ] = None + max_redirect_depth: Annotated[ + SchemaInt | None, Field(description='Maximum redirect chain depth permitted.', ge=0) + ] = None + max_response_time_ms: Annotated[ + SchemaInt | None, + Field(description='Maximum tag-server response time in milliseconds.', ge=1), + ] = None + backup_image_required: Annotated[ + StrictBool | None, + Field( + description='Whether a backup image must accompany the tag for environments that cannot render the third-party tag.' + ), + ] = None + backup_image_max_size_kb: Annotated[SchemaInt | None, Field(ge=1)] = None + om_sdk_required: Annotated[ + StrictBool | None, + Field( + description="Whether the buyer's tag must integrate IAB Open Measurement SDK for viewability." + ), + ] = None diff --git a/src/adcp/types/domains/formats/canonical/html5.py b/src/adcp/types/domains/formats/canonical/html5.py index e8240f26b..055fb170c 100644 --- a/src/adcp/types/domains/formats/canonical/html5.py +++ b/src/adcp/types/domains/formats/canonical/html5.py @@ -1,23 +1,152 @@ -"""Types declared by the AdCP ``formats/canonical/html5`` schema. +# generated by datamodel-codegen: +# filename: formats/canonical/html5.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.formats.canonical.html5 import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations +from ._base import CanonicalFormatBase + + +class Size(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + width: Annotated[SchemaInt, Field(ge=1)] + height: Annotated[SchemaInt, Field(ge=1)] + + +class MraidVersion(StrEnum): + field_2_0 = '2.0' + field_3_0 = '3.0' + + +class ClicktagMacro(StrEnum): + clickTag = 'clickTag' + clickTAG = 'clickTAG' -from adcp.types.generated_poc.formats.canonical.html5 import ( - CanonicalFormatHtml5Banner, - ClicktagMacro, - MraidVersion, - Size, -) -# Explicit exports -__all__ = ["CanonicalFormatHtml5Banner", "ClicktagMacro", "MraidVersion", "Size"] +class CanonicalFormatHtml5Banner(CanonicalFormatBase): + model_config = ConfigDict( + extra='allow', + ) + slots: Annotated[ + Any | None, + Field( + description="Default slots for html5 canonical. Buyer ships a zip bundle plus optional backup image (required when `backup_image_required: true`) and clickthrough URL. The zip's entry point is typically `index.html`; click handling uses the `clickTag` (or `clickTAG`) macro substituted by the seller at serve time." + ), + ] = [ + {'asset_group_id': 'html5_bundle', 'asset_type': 'zip', 'required': True}, + {'asset_group_id': 'backup_image', 'asset_type': 'image', 'required': False}, + {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, + ] + width: Annotated[ + SchemaInt | None, + Field( + description='Required banner width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', + ge=1, + ), + ] = None + height: Annotated[ + SchemaInt | None, + Field( + description='Required banner height in pixels. See `width` for size-mode mutual exclusion.', + ge=1, + ), + ] = None + sizes: Annotated[ + list[Size] | None, + Field( + description='List of accepted (width, height) pairs for a multi-size flexible slot (publisher banner that accepts 300×250 OR 728×90 OR 970×250). Mirrors OpenRTB `banner.format[]`. Mutually exclusive with `(width, height)` and with responsive ranges.', + min_length=1, + ), + ] = None + min_width: Annotated[ + SchemaInt | None, + Field( + description='Minimum accepted width for responsive HTML5 banners that adapt within a range. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', + ge=1, + ), + ] = None + max_width: Annotated[ + SchemaInt | None, + Field( + description='Maximum accepted width for responsive HTML5 banners. Pair with `min_width`.', + ge=1, + ), + ] = None + min_height: Annotated[ + SchemaInt | None, + Field( + description='Minimum accepted height for responsive HTML5 banners. Pair with `max_height`.', + ge=1, + ), + ] = None + max_height: Annotated[ + SchemaInt | None, + Field( + description='Maximum accepted height for responsive HTML5 banners. Pair with `min_height`.', + ge=1, + ), + ] = None + max_initial_load_kb: Annotated[ + SchemaInt | None, + Field( + description='Maximum initial-load file size (zip + above-the-fold assets) in kilobytes. IAB display standards: 200 KB for fixed sizes, 100 KB for mobile.', + ge=1, + ), + ] = None + max_polite_load_kb: Annotated[ + SchemaInt | None, + Field( + description='Maximum polite-load file size after host-initiated subload, in kilobytes. IAB display standards: 500 KB for fixed sizes.', + ge=1, + ), + ] = None + host_initiated_subload: Annotated[ + StrictBool | None, + Field( + description='Whether the host page must initiate the polite-load phase. IAB-compliant banners require true.' + ), + ] = None + max_animation_duration_ms: Annotated[ + SchemaInt | None, + Field( + description='Maximum total animation duration in milliseconds. IAB standard: 30000 (30 seconds).', + ge=0, + ), + ] = None + max_cpu_load_percent: Annotated[ + SchemaInt | None, + Field(description='Maximum CPU load percentage during render.', ge=1, le=100), + ] = None + mraid_required: Annotated[ + StrictBool | None, + Field(description='Whether MRAID compatibility is required (mobile in-app).'), + ] = None + mraid_version: Annotated[ + MraidVersion | None, + Field(description='Required MRAID version when mraid_required is true.'), + ] = None + om_sdk_required: Annotated[ + StrictBool | None, + Field(description='Whether IAB Open Measurement SDK integration is required.'), + ] = None + clicktag_macro: Annotated[ + ClicktagMacro | None, Field(description='Name of the click-tag macro the bundle must use.') + ] = None + backup_image_required: Annotated[ + StrictBool | None, + Field( + description='Whether a backup image must accompany the zip for non-HTML5 environments.' + ), + ] = None + backup_image_max_size_kb: Annotated[ + SchemaInt | None, Field(description='Maximum backup image file size in kilobytes.', ge=1) + ] = None + ssl_required: StrictBool | None = None diff --git a/src/adcp/types/domains/formats/canonical/image.py b/src/adcp/types/domains/formats/canonical/image.py index 0db66bfef..dc9cf22f0 100644 --- a/src/adcp/types/domains/formats/canonical/image.py +++ b/src/adcp/types/domains/formats/canonical/image.py @@ -1,34 +1,182 @@ -"""Types declared by the AdCP ``formats/canonical/image`` schema. +# generated by datamodel-codegen: +# filename: formats/canonical/image.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.formats.canonical.image import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from adcp.types._scalar import ScalarFloat +from pydantic import ConfigDict, Field, StrictBool + +from ...enums import activation_method +from ...enums import ctv_ad_experience as ctv_ad_experience_1 +from ._base import CanonicalFormatBase + + +class MotionLevel(StrEnum): + static = 'static' + limited_motion = 'limited_motion' + + +class Size(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + width: Annotated[SchemaInt, Field(ge=1)] + height: Annotated[SchemaInt, Field(ge=1)] + + +class PixelRatio(ScalarFloat): + __slots__ = () + _constraints = {'gt': 0.0} + + +class ImageFormat(StrEnum): + jpg = 'jpg' + jpeg = 'jpeg' + png = 'png' + gif = 'gif' + webp = 'webp' + svg = 'svg' + + +class AssetSource(StrEnum): + buyer_uploaded = 'buyer_uploaded' + publisher_host_recorded = 'publisher_host_recorded' + seller_pre_rendered_from_brief = 'seller_pre_rendered_from_brief' + seller_human_designed = 'seller_human_designed' + agent_synthesized = 'agent_synthesized' + publisher_owned_reference = 'publisher_owned_reference' + + +class BuyerAssetAcceptance(StrEnum): + accepted = 'accepted' + rejected = 'rejected' -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.formats.canonical.image import ( - AssetSource, - BuyerAssetAcceptance, - CanonicalFormatImage, - ImageFormat, - MotionLevel, - PixelRatio, - Size, -) - -# Explicit exports -__all__ = [ - "AssetSource", - "BuyerAssetAcceptance", - "CanonicalFormatImage", - "ImageFormat", - "MotionLevel", - "PixelRatio", - "Size", -] +class CanonicalFormatImage(CanonicalFormatBase): + model_config = ConfigDict( + extra='allow', + ) + motion_level: MotionLevel | None = None + slots: Annotated[ + Any | None, + Field( + description="Default slots for image canonical. Buyer ships an image asset (file or hosted URL) plus optional headline, body text, primary text (long-form caption), CTA (typically constrained to an enum via `cta_values`), and clickthrough URL. Products MAY override the default — make `headline` required, narrow `cta` to a value enum, or remove slots the surface doesn't consume." + ), + ] = [ + {'asset_group_id': 'image_main', 'asset_type': 'image', 'required': True}, + {'asset_group_id': 'headline', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'body_text', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'primary_text', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'cta', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, + ] + width: Annotated[ + SchemaInt | None, + Field( + description="Logical render width in pixels — use for fixed-size slots (e.g., a 300×250 IAB MREC). When `pixel_ratios` is absent, the required image asset width is the same value (1x). When `pixel_ratios` is present, an accepted asset's intrinsic width is `width × pixel_ratio`. For multi-size flexible slots, use `sizes[]`; for responsive slots, use the min/max fields. The three size modes are mutually exclusive.", + ge=1, + ), + ] = None + height: Annotated[ + SchemaInt | None, + Field( + description='Logical render height in pixels. Intrinsic asset height is `height × pixel_ratio`, where the ratio defaults to 1 when `pixel_ratios` is absent. See `width` for size-mode mutual exclusion.', + ge=1, + ), + ] = None + sizes: Annotated[ + list[Size] | None, + Field( + description='List of accepted logical (width, height) render pairs for a multi-size flexible slot. The buyer ships an asset matching one logical size multiplied by one accepted `pixel_ratios` entry (or by 1 when `pixel_ratios` is absent). SDKs MUST treat size and density as separate axes: a 600×500 intrinsic asset at 2x satisfies a logical 300×250 size; it does not create a logical 600×500 placement. Mirrors OpenRTB `banner.format[]` semantics. Mutually exclusive with `(width, height)` and with responsive ranges.', + min_length=1, + ), + ] = None + pixel_ratios: Annotated[ + list[PixelRatio] | None, + Field( + description='Accepted intrinsic-pixel densities for image assets, expressed as intrinsic pixels per logical render pixel (for example `[1, 2]` accepts both standard and Retina renditions). Absence means `[1]` for backward compatibility. This is an acceptance set, not a requirement to submit every rendition: one `image_main` asset satisfying any listed ratio is sufficient unless the effective `image_main` slot declares `required_pixel_ratios`. SDKs determine the effective ratio from `asset.pixel_ratio` when supplied, otherwise infer it from intrinsic asset dimensions divided by the matched logical size. Width and height ratios MUST agree.', + min_length=1, + ), + ] = None + min_width: Annotated[ + SchemaInt | None, + Field( + description="Minimum accepted width in pixels for responsive slots that adapt within a range (e.g., 'any width from 300 to 970'). Use with `max_width` (and optionally `min_height`/`max_height`). Mutually exclusive with `(width, height)` and `sizes[]`.", + ge=1, + ), + ] = None + max_width: Annotated[ + SchemaInt | None, + Field( + description='Maximum accepted width in pixels for responsive slots. Pair with `min_width`. See `min_width` for size-mode mutual exclusion.', + ge=1, + ), + ] = None + min_height: Annotated[ + SchemaInt | None, + Field( + description='Minimum accepted height in pixels for responsive slots. Pair with `max_height`.', + ge=1, + ), + ] = None + max_height: Annotated[ + SchemaInt | None, + Field( + description='Maximum accepted height in pixels for responsive slots. Pair with `min_height`.', + ge=1, + ), + ] = None + aspect_ratio: Annotated[ + str | None, + Field( + description="Optional aspect ratio constraint (e.g., '1.91:1', '1:1'). When provided alongside `width`/`height`, must agree. When used with `sizes[]` or responsive ranges, narrows accepted entries to those matching the aspect ratio.", + pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', + ), + ] = None + max_file_size_kb: Annotated[ + SchemaInt | None, Field(description='Maximum file size in kilobytes.', ge=1) + ] = None + image_formats: Annotated[ + list[ImageFormat] | None, Field(description='Permitted image file formats.') + ] = None + ssl_required: Annotated[ + StrictBool | None, + Field(description='Whether the image and its trackers must be served over HTTPS.'), + ] = None + headline_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None + body_text_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None + cta_values: Annotated[ + list[str] | None, + Field( + description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW'])." + ), + ] = None + asset_source: Annotated[ + AssetSource | None, + Field( + description="Where the rendered asset bytes come from. Single shared enum across all canonicals (`image`, `video_hosted`, `audio_hosted` — replaces the earlier per-canonical `image_source` / `video_source` / `audio_source` fields). `buyer_uploaded` (default): buyer ships a pre-rendered asset. `publisher_host_recorded`: publisher's host records the asset (audio-specific; podcast host-read pattern). `seller_pre_rendered_from_brief`: buyer ships a brief plus structured copy; seller renders ONE asset at sync_creatives or build_creative time (generative-DSP pattern). `seller_human_designed`: seller's design team renders manually from a brief. `agent_synthesized`: AI synthesis pipeline; pair with `synthesis_nondeterministic: true` when the platform cannot guarantee in-spec output (Veo/Sora/Imagen-class). `publisher_owned_reference`: buyer references an existing post or publisher-owned object via a `published_post` slot; the seller resolves and serves the referenced content after authorization/review rather than receiving uploaded bytes.\n\nNot every value is meaningful on every canonical — `publisher_host_recorded` is audio-specific; on `image` or `video_hosted` it has no defined behavior. `publisher_owned_reference` is meaningful only when the product's `slots` declaration accepts a reference asset such as `published_post`. Adopters MUST select a value appropriate to the canonical's asset type. The `slots` declaration is the binding contract for what the buyer ships; `asset_source` is informational and lets buyers understand the production model when picking products." + ), + ] = AssetSource.buyer_uploaded + buyer_asset_acceptance: Annotated[ + BuyerAssetAcceptance | None, + Field( + description="Whether the product accepts buyer-uploaded assets. When `rejected`, the buyer cannot ship pre-rendered bytes directly — they must use build_creative (or sync_creatives with brief inputs or reference assets) so the seller produces or resolves the asset. Combined with `asset_source`, lets a product declare 'I produce assets from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`) or 'I accept existing post references, not uploaded bytes' (asset_source=`publisher_owned_reference`, buyer_asset_acceptance=`rejected`)." + ), + ] = BuyerAssetAcceptance.accepted + ctv_ad_experience: Annotated[ + ctv_ad_experience_1.CtvAdExperience | None, + Field( + description='CTV experience this option serves. On `image` only `pause` and `screensaver` are valid — the image-plus-copy contract the major pause-ad sellers ingest (seller composites the frame; typical canvas 1920×1080 or a transparent-region overlay). Other experiences route per the matrix in docs/creative/ctv-experiences.mdx.' + ), + ] = None + activation_methods: Annotated[ + list[activation_method.CreativeActivationMethod] | None, + Field( + description='Viewer activation mechanisms this option offers (e.g. `qr_code` on a pause frame). Activations are engagement events, not impressions.' + ), + ] = None diff --git a/src/adcp/types/domains/formats/canonical/image_carousel.py b/src/adcp/types/domains/formats/canonical/image_carousel.py index 27fa94e4e..ed5b1bade 100644 --- a/src/adcp/types/domains/formats/canonical/image_carousel.py +++ b/src/adcp/types/domains/formats/canonical/image_carousel.py @@ -1,21 +1,89 @@ -"""Types declared by the AdCP ``formats/canonical/image_carousel`` schema. +# generated by datamodel-codegen: +# filename: formats/canonical/image_carousel.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.formats.canonical.image_carousel import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import SchemaInt +from pydantic import ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations +from ._base import CanonicalFormatBase + + +class AllowedCardMediaAssetType(StrEnum): + image = 'image' + video = 'video' -from adcp.types.generated_poc.formats.canonical.image_carousel import ( - AllowedCardMediaAssetType, - CanonicalFormatImageCarousel, -) -# Explicit exports -__all__ = ["AllowedCardMediaAssetType", "CanonicalFormatImageCarousel"] +class CanonicalFormatImageCarousel(CanonicalFormatBase): + model_config = ConfigDict( + extra='allow', + ) + v1_translatable: Annotated[ + Any | None, + Field( + description="Inherently new in v2 — multi-card carousels (Meta carousel, Pinterest pin collections, Snap collection ads) weren't expressible as v1 named formats. SDKs MUST NOT emit `FORMAT_PROJECTION_FAILED` for products using this canonical; the v1-unreachability is structural." + ), + ] = False + slots: Annotated[ + Any | None, + Field( + description="Default slots for image_carousel. The `cards` slot's value in the manifest is an array of [card-asset](/schemas/core/assets/card-asset.json) objects; `min` / `max` constrain card count." + ), + ] = [ + {'asset_group_id': 'cards', 'asset_type': 'card', 'required': True, 'min': 2, 'max': 10}, + {'asset_group_id': 'primary_text', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, + ] + card_aspect_ratio: Annotated[ + str | None, + Field( + description="Aspect ratio shared across all cards (e.g., '1:1', '1.91:1', '4:5').", + pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', + ), + ] = None + min_cards: Annotated[ + SchemaInt | None, Field(description='Minimum card count (typical: 2 or 3).', ge=2) + ] = None + max_cards: Annotated[ + SchemaInt | None, + Field(description='Maximum card count (typical: 6, 10, or 35 depending on platform).'), + ] = None + allowed_card_media_asset_types: Annotated[ + list[AllowedCardMediaAssetType] | None, + Field( + description='Asset types each card\'s `media` field may carry. Default: [\'image\']. Polymorphic carousels (Meta) allow [\'image\', \'video\']. Renamed from `allowed_card_asset_types` to disambiguate that this constrains the card\'s media payload, not the card-asset itself (which is always asset_type: "card").' + ), + ] = None + allowed_card_asset_types: Annotated[ + list[AllowedCardMediaAssetType] | None, + Field( + deprecated=True, + description='DEPRECATED — alias for `allowed_card_media_asset_types`. Kept for back-compat; prefer the new field name. Removed in 5.0.', + ), + ] = None + card_image_max_file_size_kb: Annotated[SchemaInt | None, Field(ge=1)] = None + card_video_max_file_size_kb: Annotated[SchemaInt | None, Field(ge=1)] = None + card_video_max_duration_ms: Annotated[SchemaInt | None, Field(ge=1)] = None + primary_text_max_chars: Annotated[ + SchemaInt | None, + Field(description='Maximum length of the carousel-level primary text.', ge=1), + ] = None + card_headline_max_chars: Annotated[ + SchemaInt | None, + Field( + description='Per-card headline character limit. Governs the `headline` field on each card-asset in the `cards` slot.', + ge=1, + ), + ] = None + card_description_max_chars: Annotated[ + SchemaInt | None, + Field( + description='Per-card description character limit. Governs the `description` field on each card-asset in the `cards` slot. Distinct from `card_headline_max_chars`: description is longer body copy (typically 100-500 chars); headline is the short label (typically 25-40 chars).', + ge=1, + ), + ] = None + ssl_required: StrictBool | None = None diff --git a/src/adcp/types/domains/formats/canonical/native_in_feed.py b/src/adcp/types/domains/formats/canonical/native_in_feed.py index b90a64264..c23c5ad89 100644 --- a/src/adcp/types/domains/formats/canonical/native_in_feed.py +++ b/src/adcp/types/domains/formats/canonical/native_in_feed.py @@ -1,36 +1,190 @@ -"""Types declared by the AdCP ``formats/canonical/native_in_feed`` schema. +# generated by datamodel-codegen: +# filename: formats/canonical/native_in_feed.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.formats.canonical.native_in_feed import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictBool + +from ...enums import activation_method +from ...enums import ctv_ad_experience as ctv_ad_experience_1 +from ...enums import motion_level as motion_level_1 +from ._base import CanonicalFormatBase + + +class MenuPlacement(StrEnum): + tile = 'tile' + headline_banner = 'headline_banner' + + +class FocusBehavior(StrEnum): + none = 'none' + autoplay_muted = 'autoplay_muted' + autoplay_sound = 'autoplay_sound' + + +class MainImageSize(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + width: Annotated[SchemaInt, Field(ge=1)] + height: Annotated[SchemaInt, Field(ge=1)] + + +class IconSize(MainImageSize): + pass + + +class ImageFormat(StrEnum): + jpg = 'jpg' + jpeg = 'jpeg' + png = 'png' + gif = 'gif' + webp = 'webp' + + +class AssetSource(StrEnum): + buyer_uploaded = 'buyer_uploaded' + seller_pre_rendered_from_brief = 'seller_pre_rendered_from_brief' + seller_human_designed = 'seller_human_designed' + agent_synthesized = 'agent_synthesized' + publisher_owned_reference = 'publisher_owned_reference' + + +class BuyerAssetAcceptance(StrEnum): + accepted = 'accepted' + rejected = 'rejected' -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.formats.canonical.native_in_feed import ( - AssetSource, - BuyerAssetAcceptance, - CanonicalFormatNativeInFeed, - FocusBehavior, - IconSize, - ImageFormat, - MainImageSize, - MenuPlacement, -) - -# Explicit exports -__all__ = [ - "AssetSource", - "BuyerAssetAcceptance", - "CanonicalFormatNativeInFeed", - "FocusBehavior", - "IconSize", - "ImageFormat", - "MainImageSize", - "MenuPlacement", -] +class CanonicalFormatNativeInFeed(CanonicalFormatBase): + model_config = ConfigDict( + extra='allow', + ) + experimental: Annotated[ + Any | None, + Field( + description='Stable at 3.1 GA. Shape mirrors IAB OpenRTB Native 1.2 — the renderer contract is well-established across in-feed native and content-recommendation adopters.' + ), + ] = False + v1_translatable: Annotated[ + Any | None, + Field( + description='Translates to v1 named native formats (e.g., `native_standard`, `native_content`) via the projection registry. Sellers with existing v1 named native formats SHOULD point `v1_format_ref[]` at them.' + ), + ] = True + slots: Annotated[ + Any | None, + Field( + description="Default slot shape for native_in_feed. Mirrors IAB OpenRTB Native 1.2 asset types, including the Native video asset: `video` carries a VAST document (the Native 1.2 `vasttag` field) for video-bearing native units such as CTV menu heroes with focus-triggered playback. Products MAY override (`slots_override` on the projection ref) to narrow per-slot limits (`max_chars` on title/body) or remove unused slots (a content-recommendation slot that doesn't display an icon)." + ), + ] = [ + {'asset_group_id': 'title', 'asset_type': 'text', 'required': True}, + {'asset_group_id': 'body_text', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'main_image', 'asset_type': 'image', 'required': False}, + {'asset_group_id': 'icon', 'asset_type': 'image', 'required': False}, + {'asset_group_id': 'cta', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'advertiser_name', 'asset_type': 'text', 'required': True}, + {'asset_group_id': 'sponsored_label', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': True}, + {'asset_group_id': 'display_url', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'rating', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'price', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'video', 'asset_type': 'vast', 'required': False}, + {'asset_group_id': 'impression_tracker', 'asset_type': 'pixel_tracker', 'required': False}, + {'asset_group_id': 'viewability_tracker', 'asset_type': 'pixel_tracker', 'required': False}, + {'asset_group_id': 'click_tracker', 'asset_type': 'pixel_tracker', 'required': False}, + ] + ctv_ad_experience: Annotated[ + ctv_ad_experience_1.CtvAdExperience | None, + Field( + description='CTV experience this option serves. On native_in_feed `menu` and `overlay` are valid. `menu`: smart-TV home/menu surfaces where the platform assembles buyer assets (background/main image, logo/icon, copy, optional focus-triggered video); `menu_placement` selects the tile vs headline-banner variant, and catalog-derived sponsored tiles route to `sponsored_placement` instead. `overlay`: seller-composited in-stream overlays supplied as an asset bundle (video, logo, imagery, copy, activation copy) — the contract overlay sellers that do not ingest VAST tags use; VAST-ingesting sellers publish a `video_vast` sibling option instead. The wire name stays `native_in_feed` for 3.x even though neither surface is literally in-feed.' + ), + ] = None + menu_placement: Annotated[ + MenuPlacement | None, + Field( + description='Menu surface variant, mapping to OpenRTB Native `plcmttype` 1 (tile/feed) and 3 (headline banner). Valid only with `ctv_ad_experience: "menu"`.' + ), + ] = None + focus_behavior: Annotated[ + FocusBehavior | None, + Field( + description='What happens when the viewer\'s remote focus lands on the unit. `autoplay_*` requires a `video` asset; playback method maps to AdCOM playbackmethod on OpenRTB bridges. Valid only with `ctv_ad_experience: "menu"`.' + ), + ] = None + motion_level: Annotated[ + motion_level_1.CreativeMotionLevel | None, + Field(description='Accepted motion class for the rendered unit (AdCOM attrs 21-23).'), + ] = None + activation_methods: Annotated[ + list[activation_method.CreativeActivationMethod] | None, + Field( + description='Viewer activation mechanisms this option offers (QR, deep link, send-to-device). Activations are engagement events, not impressions.' + ), + ] = None + title_max_chars: Annotated[ + SchemaInt | None, + Field( + description='Maximum character length for the title slot. IAB native typical: 25 (short) to 90 (long). Buyer agents SHOULD validate ship-time title length against this.', + ge=1, + ), + ] = None + body_text_max_chars: Annotated[ + SchemaInt | None, + Field( + description='Maximum character length for the body_text slot. IAB native typical: 90 (mainline) to 140 (extended).', + ge=1, + ), + ] = None + cta_max_chars: Annotated[ + SchemaInt | None, + Field(description='Maximum character length for the cta slot. Typical: 15–25.', ge=1), + ] = None + cta_values: Annotated[ + list[str] | None, + Field( + description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW', 'SIGN_UP', 'DOWNLOAD']). When set, narrows the cta slot to a closed enum." + ), + ] = None + main_image_sizes: Annotated[ + list[MainImageSize] | None, + Field( + description='Accepted logical (width, height) pairs for the main_image slot. Common IAB native sizes: 1200×627 (1.91:1), 1080×1080 (1:1), 1080×1350 (4:5). When the effective main_image slot declares `pixel_ratios`, intrinsic asset dimensions are the matched logical pair multiplied by the selected ratio; absence remains 1x.', + min_length=1, + ), + ] = None + icon_size: Annotated[ + IconSize | None, + Field( + description='Required logical (width, height) for the icon slot when present (typical: 80×80 or 100×100). When the effective icon slot declares `pixel_ratios`, intrinsic dimensions are multiplied by the selected ratio; absence remains 1x.' + ), + ] = None + max_image_file_size_kb: Annotated[ + SchemaInt | None, + Field(description='Maximum file size in kilobytes for main_image and icon.', ge=1), + ] = None + image_formats: Annotated[ + list[ImageFormat] | None, Field(description='Permitted image file formats.') + ] = None + ssl_required: Annotated[ + StrictBool | None, + Field( + description='Whether trackers, landing pages, and image URLs must be served over HTTPS.' + ), + ] = None + asset_source: Annotated[ + AssetSource | None, + Field( + description="Where the rendered native assets come from. `publisher_host_recorded` is omitted (audio-specific and not meaningful for native). Other values mirror the shared production-source axis used on `image` / `video_hosted`. `buyer_uploaded` (default): buyer ships pre-rendered title/image/body. `seller_pre_rendered_from_brief`: buyer ships a brief, seller renders the native bundle. `agent_synthesized`: AI synthesis pipeline produces title + image + body from a brief; pair with `synthesis_nondeterministic: true` for generative pipelines that can't guarantee in-spec output. `publisher_owned_reference`: buyer ships an existing published post reference; the seller resolves the post into the native presentation after authorization/review." + ), + ] = AssetSource.buyer_uploaded + buyer_asset_acceptance: Annotated[ + BuyerAssetAcceptance | None, + Field( + description='Whether the product accepts buyer-uploaded native assets. When `rejected`, the buyer cannot ship pre-rendered title/image/body — they must use `build_creative`, `sync_creatives` with brief inputs, or an accepted `published_post` reference so the seller produces or resolves the native bundle.' + ), + ] = BuyerAssetAcceptance.accepted diff --git a/src/adcp/types/domains/formats/canonical/responsive_creative.py b/src/adcp/types/domains/formats/canonical/responsive_creative.py index 3a996cb29..bc698e42e 100644 --- a/src/adcp/types/domains/formats/canonical/responsive_creative.py +++ b/src/adcp/types/domains/formats/canonical/responsive_creative.py @@ -1,20 +1,127 @@ -"""Types declared by the AdCP ``formats/canonical/responsive_creative`` schema. +# generated by datamodel-codegen: +# filename: formats/canonical/responsive_creative.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.formats.canonical.responsive_creative import +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import SchemaInt +from pydantic import ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations +from ._base import CanonicalFormatBase -from adcp.types.generated_poc.formats.canonical.responsive_creative import ( - CanonicalFormatResponsiveCreative, -) -# Explicit exports -__all__ = ["CanonicalFormatResponsiveCreative"] +class CanonicalFormatResponsiveCreative(CanonicalFormatBase): + model_config = ConfigDict( + extra='allow', + ) + experimental: Annotated[ + Any | None, + Field( + description="Marked experimental at 3.1 GA: composition is algorithmic (the surface picks combinations and reports per-asset breakdowns), and there's no clean v1-translatable equivalent. Buyers ship asset pools rather than rendered creatives; the surface's per-impression composition cannot be predicted by `validate_input`. Adopters SHOULD validate behavior per surface (Google PMax vs Meta Advantage+ creative differ meaningfully)." + ), + ] = True + v1_translatable: Annotated[ + Any | None, + Field( + description="Inherently new in v2 — algorithmic asset-pool composition (Google PMax / Meta Advantage+ creative) wasn't expressible as v1 named formats. SDKs MUST NOT emit `FORMAT_PROJECTION_FAILED` for products using this canonical; the v1-unreachability is structural." + ), + ] = False + slots: Any | None = [ + { + 'asset_group_id': 'headlines', + 'asset_type': 'text', + 'required': True, + 'min': 3, + 'max': 15, + }, + { + 'asset_group_id': 'long_headlines', + 'asset_type': 'text', + 'required': False, + 'min': 1, + 'max': 5, + }, + { + 'asset_group_id': 'descriptions', + 'asset_type': 'text', + 'required': True, + 'min': 2, + 'max': 5, + }, + { + 'asset_group_id': 'images_landscape', + 'asset_type': 'image', + 'required': False, + 'min': 1, + 'max': 20, + }, + { + 'asset_group_id': 'images_square', + 'asset_type': 'image', + 'required': False, + 'min': 1, + 'max': 20, + }, + { + 'asset_group_id': 'images_vertical', + 'asset_type': 'image', + 'required': False, + 'min': 1, + 'max': 20, + }, + {'asset_group_id': 'video', 'asset_type': 'video', 'required': False, 'min': 0, 'max': 5}, + { + 'asset_group_id': 'logo', + 'asset_type': 'image', + 'required': True, + 'min': 1, + 'max': 5, + 'logo_slots': [ + 'logo_card_light', + 'logo_card_dark', + 'marketplace_listing', + 'ad_end_card', + ], + 'required_logo_slots': ['logo_card_light', 'logo_card_dark'], + }, + { + 'asset_group_id': 'landing_page_url', + 'asset_type': 'url', + 'required': True, + 'min': 1, + 'max': 1, + }, + ] + headlines_min: Annotated[SchemaInt | None, Field(ge=0)] = None + headlines_max: Annotated[SchemaInt | None, Field(ge=0)] = None + headline_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None + long_headlines_min: Annotated[SchemaInt | None, Field(ge=0)] = None + long_headlines_max: Annotated[SchemaInt | None, Field(ge=0)] = None + long_headline_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None + descriptions_min: Annotated[SchemaInt | None, Field(ge=0)] = None + descriptions_max: Annotated[SchemaInt | None, Field(ge=0)] = None + description_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None + images_landscape_min: Annotated[SchemaInt | None, Field(ge=0)] = None + images_landscape_max: Annotated[SchemaInt | None, Field(ge=0)] = None + images_landscape_aspect_ratio: str | None = None + images_square_min: Annotated[SchemaInt | None, Field(ge=0)] = None + images_square_max: Annotated[SchemaInt | None, Field(ge=0)] = None + images_vertical_min: Annotated[SchemaInt | None, Field(ge=0)] = None + images_vertical_max: Annotated[SchemaInt | None, Field(ge=0)] = None + videos_min: Annotated[SchemaInt | None, Field(ge=0)] = None + videos_max: Annotated[SchemaInt | None, Field(ge=0)] = None + video_min_duration_ms: Annotated[SchemaInt | None, Field(ge=1)] = None + video_max_duration_ms: Annotated[SchemaInt | None, Field(ge=1)] = None + logo_min: Annotated[SchemaInt | None, Field(ge=0)] = None + logo_max: Annotated[SchemaInt | None, Field(ge=0)] = None + logo_aspect_ratios: list[str] | None = None + business_name_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None + asset_image_max_file_size_kb: Annotated[SchemaInt | None, Field(ge=1)] = None + supports_catalog_input: Annotated[ + StrictBool | None, + Field( + description='Whether the product can additionally consume a catalog reference (e.g., PMax with product feed).' + ), + ] = None diff --git a/src/adcp/types/domains/formats/canonical/seller_rendered_stateful_display.py b/src/adcp/types/domains/formats/canonical/seller_rendered_stateful_display.py index a9e0b0874..cb7c76dfa 100644 --- a/src/adcp/types/domains/formats/canonical/seller_rendered_stateful_display.py +++ b/src/adcp/types/domains/formats/canonical/seller_rendered_stateful_display.py @@ -1,78 +1,915 @@ -"""Types declared by the AdCP ``formats/canonical/seller_rendered_stateful_display`` schema. +# generated by datamodel-codegen: +# filename: formats/canonical/seller_rendered_stateful_display.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.formats.canonical.seller_rendered_stateful_display import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from adcp.types._scalar import ScalarInt, ScalarStr +from pydantic import ConfigDict, Field, StrictBool, StrictFloat + +from ...core import canvas_constraint +from ._base import CanonicalFormatBase + + +class SupplyMode(StrEnum): + components = 'components' + rendered_canvases = 'rendered_canvases' + layered_source = 'layered_source' + + +class Anchoring(StrEnum): + inline = 'inline' + sticky_top = 'sticky_top' + sticky_bottom = 'sticky_bottom' + overlay = 'overlay' + fullscreen_overlay = 'fullscreen_overlay' + underlay = 'underlay' + + +class SlotBinding(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[a-z0-9_]+$'} + + +class Motion(StrEnum): + static = 'static' + animated = 'animated' + + +class WidthRangeItem(ScalarInt): + __slots__ = () + _constraints = {'ge': 1} + + +class WidthMode(StrEnum): + full_bleed = 'full_bleed' + gutter_residual = 'gutter_residual' + + +class HeightRangeItem(WidthRangeItem): + pass + + +class Breakpoint(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + breakpoint_id: Annotated[ + str, + Field( + description='Stable identifier used by `state_canvases[].breakpoint_id`.', + pattern='^[a-z][a-z0-9_]*$', + ), + ] + width: Annotated[SchemaInt | None, Field(ge=1)] = None + width_range: Annotated[ + list[WidthRangeItem] | None, + Field( + description='Inclusive [minimum, maximum] accepted canvas width. The first value MUST be no greater than the second; semantic validators enforce ordering.', + max_length=2, + min_length=2, + ), + ] = None + width_mode: Annotated[ + WidthMode | None, + Field( + description='Fluid width classes. `full_bleed`: edge-to-edge viewport width (IAB adhesion, topscroll). `gutter_residual`: the margin remaining beside the page content column (desktop skins). Fluid breakpoints cannot be targeted by `rendered_canvases` supply — semantic validation rejects that pairing.' + ), + ] = None + height: Annotated[SchemaInt | None, Field(ge=1)] = None + height_range: Annotated[ + list[HeightRangeItem] | None, + Field( + description='Inclusive [minimum, maximum] accepted canvas height. The first value MUST be no greater than the second; semantic validators enforce ordering.', + max_length=2, + min_length=2, + ), + ] = None + viewport_height_percent: Annotated[ + StrictFloat | None, + Field( + description='Height as a percentage of the viewport (topscroll 80%, mobile skin panel 15%). Fluid; cannot be targeted by `rendered_canvases` supply.', + gt=0.0, + le=100.0, + ), + ] = None + canvas_aspect_ratio: Annotated[ + str | None, + Field( + description='Aspect ratio the rendered canvas maintains across the size range (IAB flexible units, ratio-bucket responsive). When both fixed `width` and `height` are declared, the ratio MUST be consistent with them.', + pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', + ), + ] = None + + +class State(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + state_id: Annotated[ + str, + Field( + description='Stable identifier used by `state_canvases[].state_id` and `state_click_urls[].state_id`.', + pattern='^[a-z][a-z0-9_]*$', + ), + ] + anchoring: Annotated[ + Anchoring, + Field( + description='`underlay` renders the canvas beneath page content, which scrolls over it (IAB New Ad Portfolio underlay class: skins, reveal units). Transitions into `overlay`/`fullscreen_overlay` states SHOULD be user-initiated; non-user-action entries emit LEAN policy warnings.' + ), + ] + slot_bindings: Annotated[ + list[SlotBinding] | None, + Field( + description='Asset group IDs rendered in this state (components mode). Makes the template executable: given components and bindings, assembly is deterministic. Every value MUST resolve to a declared slot. Omitted means all supplied component slots may render.' + ), + ] = None + motion: Annotated[ + Motion | None, + Field( + description='Whether the seller-rendered layout animates within this state (attract loops, load animations). Intra-state animation is seller-rendered; buyer canvases stay static images.' + ), + ] = Motion.static + max_animation_s: Annotated[ + StrictFloat | None, + Field( + description='Upper bound on intra-state animation duration. Required when `motion` is `animated`.', + gt=0.0, + ), + ] = None + breakpoints: Annotated[list[Breakpoint], Field(min_length=1)] + close_affordance: Annotated[ + StrictBool, + Field( + description='Whether this state visibly renders a seller-controlled close affordance. When true, rendering MUST follow IAB New Ad Portfolio close-button guidance (top-right, minimum 50×50 dp, available from state entry).' + ), + ] + + +class Reveal(StrEnum): + none = 'none' + clip_window = 'clip_window' + scroll_parallax = 'scroll_parallax' + + +class Trigger(StrEnum): + timer = 'timer' + in_view_timer = 'in_view_timer' + scroll_threshold = 'scroll_threshold' + scroll_progress = 'scroll_progress' + user_action = 'user_action' + media_event = 'media_event' + + +class Input(StrEnum): + tap = 'tap' + hover = 'hover' + swipe_up = 'swipe_up' + swipe_down = 'swipe_down' + swipe_left = 'swipe_left' + swipe_right = 'swipe_right' + scroll = 'scroll' + expand_control = 'expand_control' + collapse_control = 'collapse_control' + + +class MediaEvent(StrEnum): + video_start = 'video_start' + video_complete = 'video_complete' + + +class Direction(StrEnum): + down = 'down' + up = 'up' + + +class TransitionMode(StrEnum): + instant = 'instant' + animated = 'animated' + scroll_linked = 'scroll_linked' + instant_1 = 'instant' + animated_1 = 'animated' + + +class ScrollReference(StrEnum): + document_progress = 'document_progress' + containing_scroller_progress = 'containing_scroller_progress' + + +class Transitions(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + transition_id: Annotated[ + str, + Field( + description='Stable transition identifier for preview and reporting.', + pattern='^[a-z][a-z0-9_]*$', + ), + ] + from_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] + to_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] + trigger: Annotated[ + Literal['timer'], + Field( + description='Cause of the transition. `timer` counts from state entry; `in_view_timer` counts viewable time in the current state (industry auto-collapse is N seconds in view). `media_event` fires on `video_main` playback milestones. The outcome is expressed separately by `to_state_id`.' + ), + ] = 'timer' + input: Annotated[ + Input | None, + Field( + description='Input driving a user or scroll transition. `hover` expansion is disallowed by IAB NAP guidance and emits a LEAN policy warning visible to buyers.' + ), + ] = None + media_event: Annotated[ + MediaEvent | None, + Field( + description='Playback milestone of `video_main` driving a `media_event` transition (endframes, collapse-on-complete).' + ), + ] = None + direction: Annotated[ + Direction | None, + Field( + description='Scroll direction that arms a `scroll_threshold` transition; enables direction-aware expand/collapse cycles. Re-crossing in the opposite direction does not re-fire this transition.' + ), + ] = Direction.down + transition_mode: Annotated[ + TransitionMode, + Field( + description='`scroll_linked` continuously interpolates the seller-owned layout between the two declared endpoint states; it is valid only with trigger `scroll_progress` and does not permit buyer scripting.' + ), + ] + delay_ms: Annotated[ + SchemaInt, + Field( + description='For `timer`: delay after `from_state_id` activates (re-entry restarts it). For `in_view_timer`: accumulated viewable milliseconds in `from_state_id`. Timer-class transitions in a state cycle MUST declare at least 1000 (anti-strobe floor).', + ge=0, + ), + ] + duration_ms: Annotated[ + SchemaInt | None, + Field(description='Duration of a seller-rendered animated transition.', ge=0), + ] = None + scroll_threshold_percent: Annotated[ + StrictFloat | None, + Field( + description='Viewport/page scroll threshold that starts a `scroll_threshold` transition.', + ge=0.0, + le=100.0, + ), + ] = None + scroll_reference: Annotated[ + ScrollReference | None, + Field( + description='Reference frame for scroll percentages. Progress is `scroll_offset / max(scroll_extent - viewport_extent, 1) * 100`, measured on either the page document or the nearest seller-declared containing scroller.' + ), + ] = None + scroll_start_percent: Annotated[ + StrictFloat | None, + Field( + description='Start of the bounded scroll interval for a `scroll_progress` transition.', + ge=0.0, + le=100.0, + ), + ] = None + scroll_end_percent: Annotated[ + StrictFloat | None, + Field( + description='End of the bounded scroll interval for a `scroll_progress` transition. MUST be greater than `scroll_start_percent`.', + ge=0.0, + le=100.0, + ), + ] = None + preserve_playback: Annotated[ + StrictBool | None, + Field( + description='Whether `video_main` continues without restart while the seller changes state.' + ), + ] = False + + +class Transitions7(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + transition_id: Annotated[ + str, + Field( + description='Stable transition identifier for preview and reporting.', + pattern='^[a-z][a-z0-9_]*$', + ), + ] + from_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] + to_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] + trigger: Annotated[ + Literal['in_view_timer'], + Field( + description='Cause of the transition. `timer` counts from state entry; `in_view_timer` counts viewable time in the current state (industry auto-collapse is N seconds in view). `media_event` fires on `video_main` playback milestones. The outcome is expressed separately by `to_state_id`.' + ), + ] = 'in_view_timer' + input: Annotated[ + Input | None, + Field( + description='Input driving a user or scroll transition. `hover` expansion is disallowed by IAB NAP guidance and emits a LEAN policy warning visible to buyers.' + ), + ] = None + media_event: Annotated[ + MediaEvent | None, + Field( + description='Playback milestone of `video_main` driving a `media_event` transition (endframes, collapse-on-complete).' + ), + ] = None + direction: Annotated[ + Direction | None, + Field( + description='Scroll direction that arms a `scroll_threshold` transition; enables direction-aware expand/collapse cycles. Re-crossing in the opposite direction does not re-fire this transition.' + ), + ] = Direction.down + transition_mode: Annotated[ + TransitionMode, + Field( + description='`scroll_linked` continuously interpolates the seller-owned layout between the two declared endpoint states; it is valid only with trigger `scroll_progress` and does not permit buyer scripting.' + ), + ] + delay_ms: Annotated[ + SchemaInt, + Field( + description='For `timer`: delay after `from_state_id` activates (re-entry restarts it). For `in_view_timer`: accumulated viewable milliseconds in `from_state_id`. Timer-class transitions in a state cycle MUST declare at least 1000 (anti-strobe floor).', + ge=0, + ), + ] + duration_ms: Annotated[ + SchemaInt | None, + Field(description='Duration of a seller-rendered animated transition.', ge=0), + ] = None + scroll_threshold_percent: Annotated[ + StrictFloat | None, + Field( + description='Viewport/page scroll threshold that starts a `scroll_threshold` transition.', + ge=0.0, + le=100.0, + ), + ] = None + scroll_reference: Annotated[ + ScrollReference | None, + Field( + description='Reference frame for scroll percentages. Progress is `scroll_offset / max(scroll_extent - viewport_extent, 1) * 100`, measured on either the page document or the nearest seller-declared containing scroller.' + ), + ] = None + scroll_start_percent: Annotated[ + StrictFloat | None, + Field( + description='Start of the bounded scroll interval for a `scroll_progress` transition.', + ge=0.0, + le=100.0, + ), + ] = None + scroll_end_percent: Annotated[ + StrictFloat | None, + Field( + description='End of the bounded scroll interval for a `scroll_progress` transition. MUST be greater than `scroll_start_percent`.', + ge=0.0, + le=100.0, + ), + ] = None + preserve_playback: Annotated[ + StrictBool | None, + Field( + description='Whether `video_main` continues without restart while the seller changes state.' + ), + ] = False + + +class Transitions8(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + transition_id: Annotated[ + str, + Field( + description='Stable transition identifier for preview and reporting.', + pattern='^[a-z][a-z0-9_]*$', + ), + ] + from_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] + to_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] + trigger: Annotated[ + Literal['scroll_threshold'], + Field( + description='Cause of the transition. `timer` counts from state entry; `in_view_timer` counts viewable time in the current state (industry auto-collapse is N seconds in view). `media_event` fires on `video_main` playback milestones. The outcome is expressed separately by `to_state_id`.' + ), + ] = 'scroll_threshold' + input: Annotated[ + Literal['scroll'], + Field( + description='Input driving a user or scroll transition. `hover` expansion is disallowed by IAB NAP guidance and emits a LEAN policy warning visible to buyers.' + ), + ] = 'scroll' + media_event: Annotated[ + MediaEvent | None, + Field( + description='Playback milestone of `video_main` driving a `media_event` transition (endframes, collapse-on-complete).' + ), + ] = None + direction: Annotated[ + Direction | None, + Field( + description='Scroll direction that arms a `scroll_threshold` transition; enables direction-aware expand/collapse cycles. Re-crossing in the opposite direction does not re-fire this transition.' + ), + ] = Direction.down + transition_mode: Annotated[ + TransitionMode, + Field( + description='`scroll_linked` continuously interpolates the seller-owned layout between the two declared endpoint states; it is valid only with trigger `scroll_progress` and does not permit buyer scripting.' + ), + ] + delay_ms: Annotated[ + SchemaInt | None, + Field( + description='For `timer`: delay after `from_state_id` activates (re-entry restarts it). For `in_view_timer`: accumulated viewable milliseconds in `from_state_id`. Timer-class transitions in a state cycle MUST declare at least 1000 (anti-strobe floor).', + ge=0, + ), + ] = None + duration_ms: Annotated[ + SchemaInt | None, + Field(description='Duration of a seller-rendered animated transition.', ge=0), + ] = None + scroll_threshold_percent: Annotated[ + StrictFloat, + Field( + description='Viewport/page scroll threshold that starts a `scroll_threshold` transition.', + ge=0.0, + le=100.0, + ), + ] + scroll_reference: Annotated[ + ScrollReference, + Field( + description='Reference frame for scroll percentages. Progress is `scroll_offset / max(scroll_extent - viewport_extent, 1) * 100`, measured on either the page document or the nearest seller-declared containing scroller.' + ), + ] + scroll_start_percent: Annotated[ + StrictFloat | None, + Field( + description='Start of the bounded scroll interval for a `scroll_progress` transition.', + ge=0.0, + le=100.0, + ), + ] = None + scroll_end_percent: Annotated[ + StrictFloat | None, + Field( + description='End of the bounded scroll interval for a `scroll_progress` transition. MUST be greater than `scroll_start_percent`.', + ge=0.0, + le=100.0, + ), + ] = None + preserve_playback: Annotated[ + StrictBool | None, + Field( + description='Whether `video_main` continues without restart while the seller changes state.' + ), + ] = False + + +class TransitionMode9(StrEnum): + instant = 'instant' + animated = 'animated' + scroll_linked = 'scroll_linked' + + +class Transitions9(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + transition_id: Annotated[ + str, + Field( + description='Stable transition identifier for preview and reporting.', + pattern='^[a-z][a-z0-9_]*$', + ), + ] + from_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] + to_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] + trigger: Annotated[ + Literal['scroll_progress'], + Field( + description='Cause of the transition. `timer` counts from state entry; `in_view_timer` counts viewable time in the current state (industry auto-collapse is N seconds in view). `media_event` fires on `video_main` playback milestones. The outcome is expressed separately by `to_state_id`.' + ), + ] = 'scroll_progress' + input: Annotated[ + Literal['scroll'], + Field( + description='Input driving a user or scroll transition. `hover` expansion is disallowed by IAB NAP guidance and emits a LEAN policy warning visible to buyers.' + ), + ] = 'scroll' + media_event: Annotated[ + MediaEvent | None, + Field( + description='Playback milestone of `video_main` driving a `media_event` transition (endframes, collapse-on-complete).' + ), + ] = None + direction: Annotated[ + Direction | None, + Field( + description='Scroll direction that arms a `scroll_threshold` transition; enables direction-aware expand/collapse cycles. Re-crossing in the opposite direction does not re-fire this transition.' + ), + ] = Direction.down + transition_mode: Annotated[ + Literal['scroll_linked'], + Field( + description='`scroll_linked` continuously interpolates the seller-owned layout between the two declared endpoint states; it is valid only with trigger `scroll_progress` and does not permit buyer scripting.' + ), + ] = 'scroll_linked' + delay_ms: Annotated[ + SchemaInt | None, + Field( + description='For `timer`: delay after `from_state_id` activates (re-entry restarts it). For `in_view_timer`: accumulated viewable milliseconds in `from_state_id`. Timer-class transitions in a state cycle MUST declare at least 1000 (anti-strobe floor).', + ge=0, + ), + ] = None + duration_ms: Annotated[ + SchemaInt | None, + Field(description='Duration of a seller-rendered animated transition.', ge=0), + ] = None + scroll_threshold_percent: Annotated[ + StrictFloat | None, + Field( + description='Viewport/page scroll threshold that starts a `scroll_threshold` transition.', + ge=0.0, + le=100.0, + ), + ] = None + scroll_reference: Annotated[ + ScrollReference, + Field( + description='Reference frame for scroll percentages. Progress is `scroll_offset / max(scroll_extent - viewport_extent, 1) * 100`, measured on either the page document or the nearest seller-declared containing scroller.' + ), + ] + scroll_start_percent: Annotated[ + StrictFloat, + Field( + description='Start of the bounded scroll interval for a `scroll_progress` transition.', + ge=0.0, + le=100.0, + ), + ] + scroll_end_percent: Annotated[ + StrictFloat, + Field( + description='End of the bounded scroll interval for a `scroll_progress` transition. MUST be greater than `scroll_start_percent`.', + ge=0.0, + le=100.0, + ), + ] + preserve_playback: Annotated[ + StrictBool | None, + Field( + description='Whether `video_main` continues without restart while the seller changes state.' + ), + ] = False + + +class Transitions10(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + transition_id: Annotated[ + str, + Field( + description='Stable transition identifier for preview and reporting.', + pattern='^[a-z][a-z0-9_]*$', + ), + ] + from_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] + to_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] + trigger: Annotated[ + Literal['user_action'], + Field( + description='Cause of the transition. `timer` counts from state entry; `in_view_timer` counts viewable time in the current state (industry auto-collapse is N seconds in view). `media_event` fires on `video_main` playback milestones. The outcome is expressed separately by `to_state_id`.' + ), + ] = 'user_action' + input: Annotated[ + Input, + Field( + description='Input driving a user or scroll transition. `hover` expansion is disallowed by IAB NAP guidance and emits a LEAN policy warning visible to buyers.' + ), + ] + media_event: Annotated[ + MediaEvent | None, + Field( + description='Playback milestone of `video_main` driving a `media_event` transition (endframes, collapse-on-complete).' + ), + ] = None + direction: Annotated[ + Direction | None, + Field( + description='Scroll direction that arms a `scroll_threshold` transition; enables direction-aware expand/collapse cycles. Re-crossing in the opposite direction does not re-fire this transition.' + ), + ] = Direction.down + transition_mode: Annotated[ + TransitionMode, + Field( + description='`scroll_linked` continuously interpolates the seller-owned layout between the two declared endpoint states; it is valid only with trigger `scroll_progress` and does not permit buyer scripting.' + ), + ] + delay_ms: Annotated[ + SchemaInt | None, + Field( + description='For `timer`: delay after `from_state_id` activates (re-entry restarts it). For `in_view_timer`: accumulated viewable milliseconds in `from_state_id`. Timer-class transitions in a state cycle MUST declare at least 1000 (anti-strobe floor).', + ge=0, + ), + ] = None + duration_ms: Annotated[ + SchemaInt | None, + Field(description='Duration of a seller-rendered animated transition.', ge=0), + ] = None + scroll_threshold_percent: Annotated[ + StrictFloat | None, + Field( + description='Viewport/page scroll threshold that starts a `scroll_threshold` transition.', + ge=0.0, + le=100.0, + ), + ] = None + scroll_reference: Annotated[ + ScrollReference | None, + Field( + description='Reference frame for scroll percentages. Progress is `scroll_offset / max(scroll_extent - viewport_extent, 1) * 100`, measured on either the page document or the nearest seller-declared containing scroller.' + ), + ] = None + scroll_start_percent: Annotated[ + StrictFloat | None, + Field( + description='Start of the bounded scroll interval for a `scroll_progress` transition.', + ge=0.0, + le=100.0, + ), + ] = None + scroll_end_percent: Annotated[ + StrictFloat | None, + Field( + description='End of the bounded scroll interval for a `scroll_progress` transition. MUST be greater than `scroll_start_percent`.', + ge=0.0, + le=100.0, + ), + ] = None + preserve_playback: Annotated[ + StrictBool | None, + Field( + description='Whether `video_main` continues without restart while the seller changes state.' + ), + ] = False + + +class Transitions11(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + transition_id: Annotated[ + str, + Field( + description='Stable transition identifier for preview and reporting.', + pattern='^[a-z][a-z0-9_]*$', + ), + ] + from_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] + to_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] + trigger: Annotated[ + Literal['media_event'], + Field( + description='Cause of the transition. `timer` counts from state entry; `in_view_timer` counts viewable time in the current state (industry auto-collapse is N seconds in view). `media_event` fires on `video_main` playback milestones. The outcome is expressed separately by `to_state_id`.' + ), + ] = 'media_event' + input: Annotated[ + Input | None, + Field( + description='Input driving a user or scroll transition. `hover` expansion is disallowed by IAB NAP guidance and emits a LEAN policy warning visible to buyers.' + ), + ] = None + media_event: Annotated[ + MediaEvent, + Field( + description='Playback milestone of `video_main` driving a `media_event` transition (endframes, collapse-on-complete).' + ), + ] + direction: Annotated[ + Direction | None, + Field( + description='Scroll direction that arms a `scroll_threshold` transition; enables direction-aware expand/collapse cycles. Re-crossing in the opposite direction does not re-fire this transition.' + ), + ] = Direction.down + transition_mode: Annotated[ + TransitionMode, + Field( + description='`scroll_linked` continuously interpolates the seller-owned layout between the two declared endpoint states; it is valid only with trigger `scroll_progress` and does not permit buyer scripting.' + ), + ] + delay_ms: Annotated[ + SchemaInt | None, + Field( + description='For `timer`: delay after `from_state_id` activates (re-entry restarts it). For `in_view_timer`: accumulated viewable milliseconds in `from_state_id`. Timer-class transitions in a state cycle MUST declare at least 1000 (anti-strobe floor).', + ge=0, + ), + ] = None + duration_ms: Annotated[ + SchemaInt | None, + Field(description='Duration of a seller-rendered animated transition.', ge=0), + ] = None + scroll_threshold_percent: Annotated[ + StrictFloat | None, + Field( + description='Viewport/page scroll threshold that starts a `scroll_threshold` transition.', + ge=0.0, + le=100.0, + ), + ] = None + scroll_reference: Annotated[ + ScrollReference | None, + Field( + description='Reference frame for scroll percentages. Progress is `scroll_offset / max(scroll_extent - viewport_extent, 1) * 100`, measured on either the page document or the nearest seller-declared containing scroller.' + ), + ] = None + scroll_start_percent: Annotated[ + StrictFloat | None, + Field( + description='Start of the bounded scroll interval for a `scroll_progress` transition.', + ge=0.0, + le=100.0, + ), + ] = None + scroll_end_percent: Annotated[ + StrictFloat | None, + Field( + description='End of the bounded scroll interval for a `scroll_progress` transition. MUST be greater than `scroll_start_percent`.', + ge=0.0, + le=100.0, + ), + ] = None + preserve_playback: Annotated[ + StrictBool | None, + Field( + description='Whether `video_main` continues without restart while the seller changes state.' + ), + ] = False + + +class Clickthrough(StrEnum): + required = 'required' + optional = 'optional' + none = 'none' + + +class UserControls(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + dismissible: StrictBool + user_collapsible: StrictBool + + +class DurationMsRange(ScalarInt): + __slots__ = () + _constraints = {'ge': 0} + + +class Container(StrEnum): + mp4 = 'mp4' + webm = 'webm' + mov = 'mov' + + +class VideoPlayback(StrEnum): + none = 'none' + auto_muted = 'auto_muted' + user_initiated = 'user_initiated' -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.formats.canonical.seller_rendered_stateful_display import ( - Anchoring, - Breakpoint, - CanonicalFormatSellerRenderedStatefulDisplay, - Clickthrough, - Container, - Direction, - DurationMsRange, - HeightRangeItem, - Input, - MediaEvent, - Motion, - Reveal, - ScrollReference, - SlotBinding, - State, - SupplyMode, - TransitionMode, - TransitionMode9, - Transitions, - Transitions10, - Transitions11, - Transitions7, - Transitions8, - Transitions9, - Trigger, - UserControls, - VideoPlayback, - WidthMode, - WidthRangeItem, -) - -# Explicit exports -__all__ = [ - "Anchoring", - "Breakpoint", - "CanonicalFormatSellerRenderedStatefulDisplay", - "Clickthrough", - "Container", - "Direction", - "DurationMsRange", - "HeightRangeItem", - "Input", - "MediaEvent", - "Motion", - "Reveal", - "ScrollReference", - "SlotBinding", - "State", - "SupplyMode", - "TransitionMode", - "TransitionMode9", - "Transitions", - "Transitions10", - "Transitions11", - "Transitions7", - "Transitions8", - "Transitions9", - "Trigger", - "UserControls", - "VideoPlayback", - "WidthMode", - "WidthRangeItem", -] +class CanonicalFormatSellerRenderedStatefulDisplay(CanonicalFormatBase): + model_config = ConfigDict( + extra='allow', + ) + experimental: Annotated[ + Any | None, + Field( + description='Experimental in AdCP 3.2 while the creative working group gathers implementation evidence across premium web and mobile/app sellers.' + ), + ] = True + v1_translatable: Annotated[ + Any | None, + Field( + description='No v1 named-format equivalent can express multiple seller-rendered states and their breakpoint bindings.' + ), + ] = False + since_version: Any | None = '3.2' + composition_model: Any | None = 'deterministic' + supply_mode: Annotated[ + SupplyMode | None, + Field( + description='Which end of the template contract the buyer feeds. `components`: buyer supplies component slots; seller renders states (no `state_canvases`/`layered_source` assets allowed). `rendered_canvases`: buyer supplies exactly one `state_canvases` image per declared state × breakpoint pair. `layered_source`: buyer ships design source (+ optional `font_files`); seller production derives states (`production_window_business_days` applies) — transitional for sellers without executable templates.' + ), + ] = SupplyMode.components + slots: Annotated[ + Any | None, + Field( + description='Default manifest slots; which are consumed depends on `supply_mode`. `state_canvases` images MUST carry `state_id` and `breakpoint_id`, and `state_click_urls` entries MUST carry `state_id` (semantic validators resolve the bindings). Component images SHOULD carry `focal_point` for deterministic seller cropping. `landing_page_url` is the default destination (see `clickthrough`). `font_files` MUST contain only buyer-licensed fonts; publisher-proprietary fonts never travel in manifests. Only image, video, text, url, zip, and pixel_tracker slot asset types are accepted — executable types (javascript, html, css, webhook) are rejected even via `slots` overrides.' + ), + ] = [ + {'asset_group_id': 'state_canvases', 'asset_type': 'image', 'required': False, 'min': 1}, + {'asset_group_id': 'logo', 'asset_type': 'image', 'required': False}, + {'asset_group_id': 'imagery', 'asset_type': 'image', 'required': False, 'min': 0}, + {'asset_group_id': 'headline', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'messaging', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'cta', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'legal_text', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'video_main', 'asset_type': 'video', 'required': False}, + {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': True}, + {'asset_group_id': 'state_click_urls', 'asset_type': 'url', 'required': False, 'min': 0}, + {'asset_group_id': 'layered_source', 'asset_type': 'zip', 'required': False}, + {'asset_group_id': 'font_files', 'asset_type': 'zip', 'required': False}, + ] + states: Annotated[ + list[State], + Field( + description='Finite visual states known at buy time; state and breakpoint IDs form the canvas-key matrix. Runtime causes live in `transitions[]`. A single-state unit (topscroll, interscroller, skin) declares one state, no transitions, and typically a `reveal` mechanic.', + min_length=1, + ), + ] + initial_state_id: Annotated[ + str, + Field( + description='State rendered when the unit first becomes visible. MUST resolve to `states[].state_id`; for a single-state unit it MUST equal the sole state.', + pattern='^[a-z][a-z0-9_]*$', + ), + ] + reveal: Annotated[ + Reveal | None, + Field( + description='How the unit enters view, distinct from state changes. `clip_window`: canvas fixed and progressively exposed through a scrolling window (interscroller, topscroll). `scroll_parallax`: canvas moves at a different rate than content. Reveal is presentation of one canvas, not a transition; do not fabricate a second state to express it.' + ), + ] = Reveal.none + transitions: Annotated[ + list[ + Transitions | Transitions7 | Transitions8 | Transitions9 | Transitions10 | Transitions11 + ] + | None, + Field( + description='Bounded seller-rendered transitions between declared visual states. Required when `states` has more than one entry; MUST be omitted for single-state units. Every non-initial state MUST be reachable from `initial_state_id`. Dismissal is terminal unit behavior declared by `user_controls.dismissible`, not a hidden visual state.', + min_length=1, + ), + ] = None + clickthrough: Annotated[ + Clickthrough | None, + Field( + description='Destination policy. `required` (default): manifest MUST supply `landing_page_url`. `optional`: click-optional units (in-feed brand units) may omit it. `none`: unit is non-clickable; manifests MUST NOT supply `landing_page_url` or `state_click_urls`. Per-state overrides via `state_click_urls` entries carrying `state_id`; `landing_page_url` is the fallback for unlisted states.' + ), + ] = Clickthrough.required + user_controls: Annotated[ + UserControls, + Field( + description="When any state anchors as `overlay` or `fullscreen_overlay`, either `dismissible` MUST be true or that state's `close_affordance` MUST be true (dismissibility floor; semantic validators enforce)." + ), + ] + canvas_constraints: Annotated[ + list[canvas_constraint.CanvasConstraint] | None, + Field( + description='Rectangular areas constraining buyer artwork. Omitted state/breakpoint selectors apply the constraint to every canvas. For fluid or range-sized breakpoints, use percent-unit regions.' + ), + ] = None + duration_ms_range: Annotated[ + list[DurationMsRange | None] | None, + Field( + description='Accepted embedded-video duration [min, max]. `duration_ms_exact` takes precedence when both are present.', + max_length=2, + min_length=2, + ), + ] = None + duration_ms_exact: Annotated[SchemaInt | None, Field(ge=1)] = None + aspect_ratio: Annotated[ + str | None, + Field( + description='Embedded-video aspect ratio.', + pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', + ), + ] = None + containers: list[Container] | None = None + video_playback: VideoPlayback | None = None + max_initial_load_kb: Annotated[SchemaInt | None, Field(ge=1)] = None + max_subload_kb: Annotated[ + SchemaInt | None, + Field( + description='Ceiling on assets loaded after the window load event (IAB LEAN subload). Pairs with `max_initial_load_kb` to mirror the New Ad Portfolio initial/subload weight pair.', + ge=1, + ), + ] = None + polite_load: Annotated[ + StrictBool | None, + Field( + description="When true, non-initial assets load only after the host page's window load event (IAB LEAN subload boundary)." + ), + ] = None diff --git a/src/adcp/types/domains/formats/canonical/sponsored_placement.py b/src/adcp/types/domains/formats/canonical/sponsored_placement.py index 4d98218f7..b3ab0970d 100644 --- a/src/adcp/types/domains/formats/canonical/sponsored_placement.py +++ b/src/adcp/types/domains/formats/canonical/sponsored_placement.py @@ -1,28 +1,111 @@ -"""Types declared by the AdCP ``formats/canonical/sponsored_placement`` schema. +# generated by datamodel-codegen: +# filename: formats/canonical/sponsored_placement.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.formats.canonical.sponsored_placement import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import SchemaInt +from pydantic import ConfigDict, Field, StrictBool + +from ...enums import catalog_type +from ...enums import ctv_ad_experience as ctv_ad_experience_1 +from ._base import CanonicalFormatBase + + +class FanoutMode(StrEnum): + per_item = 'per_item' + multi_item_in_creative = 'multi_item_in_creative' + single_item = 'single_item' + + +class SupportedIdType(StrEnum): + asin = 'asin' + sku = 'sku' + gtin = 'gtin' + offering_id = 'offering_id' + store_id = 'store_id' + hotel_id = 'hotel_id' + flight_id = 'flight_id' + vehicle_id = 'vehicle_id' + listing_id = 'listing_id' + program_id = 'program_id' + destination_id = 'destination_id' + app_id = 'app_id' + job_id = 'job_id' + + +class ItemProductionModel(StrEnum): + buyer_uploaded = 'buyer_uploaded' + seller_pre_rendered_from_brief = 'seller_pre_rendered_from_brief' + seller_human_designed = 'seller_human_designed' + agent_synthesized = 'agent_synthesized' -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.formats.canonical.sponsored_placement import ( - CanonicalFormatSponsoredPlacementRetailMediaCatalogDriven, - FanoutMode, - ItemProductionModel, - SupportedIdType, -) - -# Explicit exports -__all__ = [ - "CanonicalFormatSponsoredPlacementRetailMediaCatalogDriven", - "FanoutMode", - "ItemProductionModel", - "SupportedIdType", -] +class CanonicalFormatSponsoredPlacementRetailMediaCatalogDriven(CanonicalFormatBase): + model_config = ConfigDict( + extra='allow', + ) + experimental: Annotated[ + Any | None, + Field( + description='Marked experimental at 3.1 GA: the canonical covers 4 meaningfully different retail-media adapter contracts (Amazon SP, Criteo SP / CitrusAd SP, Pinterest Collection, generative-per-SKU). Adopter contracts vary; buyers MUST validate per-adapter behavior before routing budget. Promotion to non-experimental gated on the #4592 adapter-contract docs work.' + ), + ] = True + v1_translatable: Annotated[ + Any | None, + Field( + description="Inherently new in v2 — retail-media catalog placements weren't expressible as v1 named formats. SDKs MUST NOT emit `FORMAT_PROJECTION_FAILED` for products using this canonical; the v1-unreachability is structural, not a registry-coverage gap." + ), + ] = False + slots: Any | None = [ + {'asset_group_id': 'source_catalog', 'required': True, 'asset_type': 'catalog'}, + {'asset_group_id': 'hero_asset', 'required': False, 'asset_type': 'image'}, + {'asset_group_id': 'landing_page_url', 'required': False, 'asset_type': 'url'}, + ] + supported_catalog_types: Annotated[ + list[catalog_type.CatalogType] | None, + Field(description='Catalog types this product accepts.'), + ] = None + min_items: Annotated[ + SchemaInt | None, Field(description='Minimum catalog item count buyer must supply.', ge=1) + ] = None + max_items: Annotated[ + SchemaInt | None, Field(description='Maximum items considered for placement.') + ] = None + fanout_mode: Annotated[ + FanoutMode | None, + Field( + description='How items map to delivery: per_item = one ad per catalog item; multi_item_in_creative = composed multi-item ad (Pinterest Collection, Snap Collection); single_item = one ad showing one item.' + ), + ] = None + required_catalog_fields: Annotated[ + list[str] | None, + Field( + description="Catalog item fields the seller requires (e.g., ['title', 'image_url', 'price'])." + ), + ] = None + supported_id_types: Annotated[ + list[SupportedIdType] | None, + Field(description='Catalog identifier types the placement renders against.'), + ] = None + hero_asset_supported: Annotated[ + StrictBool | None, + Field( + description='Whether the buyer can supply a hero/banner asset alongside the catalog (Pinterest Collection pattern).' + ), + ] = None + item_production_model: Annotated[ + ItemProductionModel | None, + Field( + description='How each per-item creative is produced. Covers the same production-source axis as `asset_source` on `image` / `video_hosted` / `audio_hosted` but with a 4-value subset — drops `publisher_host_recorded` because it\'s audio-specific and doesn\'t apply to retail-media catalog placements. SDK codegen MAY share a base enum and narrow per-canonical, or emit two distinct enums; either way the wire values overlap exactly for the 4 retained values. `buyer_uploaded` (default, current Amazon/Criteo/CitrusAd pattern): the buyer\'s catalog already contains rendered assets per item; the seller composes the placement using those assets. ("Uploaded" reads slightly off for catalog-keyed items where the buyer didn\'t actively upload bytes — the catalog ingestion already supplied them — but the semantic is the same: rendered bytes are buyer-supplied, not seller-produced.) `seller_pre_rendered_from_brief`: the buyer ships a brief plus the catalog reference; the seller renders one creative per catalog item from the brief at sync_creatives time. `seller_human_designed`: seller\'s design team produces per-item renders manually. `agent_synthesized`: AI synthesis pipeline produces per-item renders; pair with `synthesis_nondeterministic: true` for Veo/Sora-class generative video applied per item. Captures the multi-output generative pattern (1 brief × N catalog items → N rendered creatives) under the existing canonical without requiring a separate canonical. Distinct from `fanout_mode`, which describes how items map to delivery slots after rendering.' + ), + ] = ItemProductionModel.buyer_uploaded + ctv_ad_experience: Annotated[ + ctv_ad_experience_1.CtvAdExperience | None, + Field( + description='CTV experience this option serves. On `sponsored_placement`: `menu` (sponsored app/content tiles and rows whose assets derive from a catalog listing — Fire-TV-tile pattern), `squeezeback`, and `in_scene` (seller-composited brand integrations produced from the catalog/brief rather than a buyer wire creative). Asset-bundle menu heroes route to `native_in_feed`.' + ), + ] = None diff --git a/src/adcp/types/domains/formats/canonical/video_hosted.py b/src/adcp/types/domains/formats/canonical/video_hosted.py index bf18019d8..b5bcd0e21 100644 --- a/src/adcp/types/domains/formats/canonical/video_hosted.py +++ b/src/adcp/types/domains/formats/canonical/video_hosted.py @@ -1,42 +1,169 @@ -"""Types declared by the AdCP ``formats/canonical/video_hosted`` schema. +# generated by datamodel-codegen: +# filename: formats/canonical/video_hosted.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.formats.canonical.video_hosted import +from adcp.types._scalar import ScalarInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any + +from adcp.types.base import SchemaInt +from pydantic import ConfigDict, Field, StrictBool, StrictFloat + +from ...enums import ctv_ad_experience as ctv_ad_experience_1 +from ._base import CanonicalFormatBase + + +class Orientation(StrEnum): + vertical = 'vertical' + horizontal = 'horizontal' + square = 'square' + + +class DurationMsRange(ScalarInt): + __slots__ = () + _constraints = {'ge': 0} + + +class VideoCodec(StrEnum): + h264 = 'h264' + h265 = 'h265' + vp8 = 'vp8' + vp9 = 'vp9' + av1 = 'av1' + prores = 'prores' + + +class AudioCodec(StrEnum): + aac = 'aac' + mp3 = 'mp3' + opus = 'opus' + pcm = 'pcm' + + +class Container(StrEnum): + mp4 = 'mp4' + webm = 'webm' + mov = 'mov' + + +class Captions(StrEnum): + required = 'required' + recommended = 'recommended' + not_required = 'not_required' + + +class CompanionBannerWidth(ScalarInt): + __slots__ = () + _constraints = {'ge': 1} + + +class CompanionBannerHeight(CompanionBannerWidth): + pass + + +class AssetSource(StrEnum): + buyer_uploaded = 'buyer_uploaded' + publisher_host_recorded = 'publisher_host_recorded' + seller_pre_rendered_from_brief = 'seller_pre_rendered_from_brief' + seller_human_designed = 'seller_human_designed' + agent_synthesized = 'agent_synthesized' + publisher_owned_reference = 'publisher_owned_reference' + + +class BuyerAssetAcceptance(StrEnum): + accepted = 'accepted' + rejected = 'rejected' -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.formats.canonical.video_hosted import ( - AssetSource, - AudioCodec, - BuyerAssetAcceptance, - CanonicalFormatHostedVideo, - Captions, - CompanionBannerHeight, - CompanionBannerWidth, - Container, - DurationMsRange, - Orientation, - VideoCodec, -) - -# Explicit exports -__all__ = [ - "AssetSource", - "AudioCodec", - "BuyerAssetAcceptance", - "CanonicalFormatHostedVideo", - "Captions", - "CompanionBannerHeight", - "CompanionBannerWidth", - "Container", - "DurationMsRange", - "Orientation", - "VideoCodec", -] +class CanonicalFormatHostedVideo(CanonicalFormatBase): + model_config = ConfigDict( + extra='allow', + ) + slots: Annotated[ + Any | None, + Field( + description='Default slots for video_hosted canonical. Buyer ships a video asset (file or hosted URL); optional headline, primary text (long-form caption), CTA (typically constrained via `cta_values`), brand_name (typical for vertical short-form), companion_banner (typical for horizontal instream), and clickthrough URL. Products MAY override or extend the default — e.g., remove `companion_banner` for short-form vertical, narrow `cta` to a value enum, mark `landing_page_url` as required.' + ), + ] = [ + {'asset_group_id': 'video_main', 'asset_type': 'video', 'required': True}, + {'asset_group_id': 'headline', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'primary_text', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'cta', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'brand_name', 'asset_type': 'text', 'required': False}, + {'asset_group_id': 'companion_banner', 'asset_type': 'image', 'required': False}, + {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, + ] + orientation: Annotated[ + Orientation | None, + Field( + description='Video orientation. Vertical = 9:16 (Reels, Stories, Shorts). Horizontal = 16:9 (instream, CTV). Square = 1:1 (in-feed).' + ), + ] = None + aspect_ratio: Annotated[ + str | None, + Field( + description='Aspect ratio. Inferred from orientation if omitted.', + pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', + ), + ] = None + min_width: Annotated[SchemaInt | None, Field(ge=1)] = None + min_height: Annotated[SchemaInt | None, Field(ge=1)] = None + max_width: Annotated[SchemaInt | None, Field(ge=1)] = None + max_height: Annotated[SchemaInt | None, Field(ge=1)] = None + duration_ms_range: Annotated[ + list[DurationMsRange | None] | None, + Field( + description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', + max_length=2, + min_length=2, + ), + ] = None + duration_ms_exact: Annotated[ + SchemaInt | None, + Field( + description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship (see `duration_ms_range` description).', + ge=1, + ), + ] = None + video_codecs: list[VideoCodec] | None = None + audio_codecs: list[AudioCodec] | None = None + containers: list[Container] | None = None + min_bitrate_kbps: Annotated[SchemaInt | None, Field(ge=1)] = None + max_bitrate_kbps: Annotated[SchemaInt | None, Field(ge=1)] = None + max_file_size_mb: Annotated[ + SchemaInt | None, + Field(description='Maximum file size, where 1 MB is exactly 1,000,000 bytes.', ge=1), + ] = None + frame_rates: list[StrictFloat] | None = None + captions: Captions | None = None + om_sdk_required: StrictBool | None = None + headline_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None + primary_text_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None + brand_name_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None + cta_values: list[str] | None = None + companion_banner_widths: Annotated[ + list[CompanionBannerWidth] | None, + Field(description='Permitted companion banner widths (instream video).'), + ] = None + companion_banner_heights: list[CompanionBannerHeight] | None = None + asset_source: Annotated[ + AssetSource | None, + Field( + description='Where the rendered asset bytes come from. Single shared enum across canonicals. See `image.json#asset_source` for the full semantics. `publisher_host_recorded` is audio-specific and has no defined behavior on video. `publisher_owned_reference` is valid when the product accepts an existing post reference via a `published_post` slot instead of uploaded video bytes. Adopters MUST select a value appropriate to the canonical.' + ), + ] = AssetSource.buyer_uploaded + buyer_asset_acceptance: Annotated[ + BuyerAssetAcceptance | None, + Field( + description='Whether the product accepts buyer-uploaded video. When `rejected`, the buyer cannot ship a video asset directly — they must use build_creative, sync_creatives with brief inputs, or sync_creatives with an accepted reference asset so the seller produces or resolves the video.' + ), + ] = BuyerAssetAcceptance.accepted + ctv_ad_experience: Annotated[ + ctv_ad_experience_1.CtvAdExperience | None, + Field( + description='CTV experience this option serves. On `video_hosted` only `screensaver` is valid (ambient looping video the platform plays on idle). Other experiences route per the matrix in docs/creative/ctv-experiences.mdx; linear CTV video declares no experience.' + ), + ] = None diff --git a/src/adcp/types/domains/formats/canonical/video_vast.py b/src/adcp/types/domains/formats/canonical/video_vast.py index 8a6fd0324..4d746ee51 100644 --- a/src/adcp/types/domains/formats/canonical/video_vast.py +++ b/src/adcp/types/domains/formats/canonical/video_vast.py @@ -1,30 +1,176 @@ -"""Types declared by the AdCP ``formats/canonical/video_vast`` schema. +# generated by datamodel-codegen: +# filename: formats/canonical/video_vast.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.formats.canonical.video_vast import +from adcp.types._scalar import ScalarInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any + +from adcp.types.base import SchemaInt +from pydantic import ConfigDict, Field, StrictBool + +from ...core import vast_media_file_requirements +from ...enums import activation_method +from ...enums import ctv_ad_experience as ctv_ad_experience_1 +from ...enums import motion_level as motion_level_1 +from ...enums import vast_version as vast_version_1 +from ._base import CanonicalFormatBase + + +class Orientation(StrEnum): + vertical = 'vertical' + horizontal = 'horizontal' + square = 'square' + + +class VpaidVersion(StrEnum): + field_1_0 = '1.0' + field_2_0 = '2.0' + + +class DurationMsRangeItem(ScalarInt): + __slots__ = () + _constraints = {'ge': 0} + + +class CreativeType(StrEnum): + linear = 'linear' + nonlinear = 'nonlinear' + either = 'either' -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.formats.canonical.video_vast import ( - CanonicalFormatVastVideo, - CreativeType, - DurationMsRangeItem, - Orientation, - VpaidVersion, -) - -# Explicit exports -__all__ = [ - "CanonicalFormatVastVideo", - "CreativeType", - "DurationMsRangeItem", - "Orientation", - "VpaidVersion", -] +class CanonicalFormatVastVideo(CanonicalFormatBase): + model_config = ConfigDict( + extra='allow', + ) + slots: Annotated[ + Any | None, + Field( + description="Default slots for video_vast canonical. Buyer ships a VAST tag (URL or inline XML, VAST 2.x-4.x) plus an optional clickthrough URL (which falls back to the VAST `ClickThrough` element when omitted). Tracking events are inherent to VAST and don't require explicit slots." + ), + ] = [ + {'asset_group_id': 'vast_tag', 'asset_type': 'vast', 'required': True}, + {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, + ] + orientation: Orientation | None = None + aspect_ratio: Annotated[ + str | None, Field(pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$') + ] = None + vast_versions: Annotated[ + list[vast_version_1.VastVersion] | None, + Field( + description='VAST versions accepted by this product format option. The asset still declares exactly one `vast_version`; compatibility requires membership in this set and the seller-wide execution set.', + min_length=1, + ), + ] = None + vast_version: Annotated[ + vast_version_1.VastVersion | None, + Field( + deprecated=True, + description='Deprecated one-element alias for `vast_versions`. Producers use either the singular legacy alias or the plural 3.2 field, never both.', + ), + ] = None + media_file_requirements: Annotated[ + vast_media_file_requirements.VastMediafileRequirements | None, + Field( + description='Technical acceptance constraints for alternative VAST MediaFile renditions. Each applicable resolved InLine linear creative needs at least one MediaFile satisfying all declared constraints.' + ), + ] = None + vpaid_enabled: Annotated[ + StrictBool | None, + Field( + description='Whether VPAID interactivity is supported. When true, the VAST tag may carry VPAID JS/Flash payloads.' + ), + ] = None + vpaid_version: VpaidVersion | None = None + simid_supported: Annotated[ + StrictBool | None, + Field( + description='Whether the seller accepts IAB SIMID through `` on a Linear VAST creative. SIMID is not a generic VAST extension and cannot be serialized under NonLinearAds; every `ctv_ad_experience` profile therefore forbids `true`.' + ), + ] = None + duration_ms_range: Annotated[ + list[DurationMsRangeItem] | None, + Field( + description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', + max_length=2, + min_length=2, + ), + ] = None + duration_ms_exact: Annotated[ + SchemaInt | None, + Field( + description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', + ge=1, + ), + ] = None + min_width: Annotated[ + SchemaInt | None, + Field( + description='Minimum placement/player width in pixels. MediaFile rendition dimensions are declared in `media_file_requirements`.', + ge=1, + ), + ] = None + max_width: Annotated[ + SchemaInt | None, + Field( + description='Maximum placement/player width in pixels. MediaFile rendition dimensions are declared in `media_file_requirements`.', + ge=1, + ), + ] = None + min_height: Annotated[ + SchemaInt | None, + Field( + description='Minimum placement/player height in pixels. MediaFile rendition dimensions are declared in `media_file_requirements`.', + ge=1, + ), + ] = None + max_height: Annotated[ + SchemaInt | None, + Field( + description='Maximum placement/player height in pixels. MediaFile rendition dimensions are declared in `media_file_requirements`.', + ge=1, + ), + ] = None + creative_type: Annotated[ + CreativeType | None, + Field( + description='Required VAST creative class: `linear` (in-stream Linear), `nonlinear` (NonLinearAds overlay-class), or `either`. Supersedes `linear_required`; when both are present `creative_type` wins, and validators treat `linear_required: true` with no `creative_type` as `linear`.' + ), + ] = None + ctv_ad_experience: Annotated[ + ctv_ad_experience_1.CtvAdExperience | None, + Field( + description='CTV experience this option is eligible to serve. On video_vast only `pause`, `screensaver`, `overlay`, `squeezeback`, and `in_scene` are valid (`menu` routes to native_in_feed or sponsored_placement), and `creative_type` MUST be `nonlinear`. Because VAST places `` only under Linear ``, `simid_supported` MUST NOT be true on any of these NonLinear profiles. Per-experience floors: `overlay` and `squeezeback` require a 10s minimum duration; `in_scene` requires a 3s minimum brand-exposure duration and forbids interactivity (`vpaid_enabled` MUST NOT be true); `pause` has no duration floor and ends on viewer or device action.' + ), + ] = None + motion_level: Annotated[ + motion_level_1.CreativeMotionLevel | None, + Field(description='Accepted motion class for the rendered creative (AdCOM attrs 21-23).'), + ] = None + activation_methods: Annotated[ + list[activation_method.CreativeActivationMethod] | None, + Field( + description='Viewer activation mechanisms this option offers. Activations are engagement events, not impressions.' + ), + ] = None + linear_required: Annotated[ + StrictBool | None, + Field( + description='Whether the VAST creative must be linear (non-skippable in-stream). Superseded by `creative_type`; retained for pre-3.2 declarations.' + ), + ] = None + skippable_after_ms: Annotated[ + SchemaInt | None, + Field( + description='When skippable, the buyer-side skip threshold in milliseconds (e.g., 5000 for 5-second skippable pre-roll).', + ge=0, + ), + ] = None + max_wrapper_depth: Annotated[ + SchemaInt | None, Field(description='Maximum VAST wrapper redirect depth permitted.', ge=0) + ] = None + ssl_required: StrictBool | None = None diff --git a/src/adcp/types/domains/governance/__init__.py b/src/adcp/types/domains/governance/__init__.py index b4f9c03c7..1c22fcdca 100644 --- a/src/adcp/types/domains/governance/__init__.py +++ b/src/adcp/types/domains/governance/__init__.py @@ -9,26 +9,23 @@ it from its own schema's module, ``adcp.types.domains.governance.``. Nothing here is renamed. -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:33:59 UTC +Auto-generated from the generated domain tree. DO NOT EDIT MANUALLY. +Generation date: 2026-10-04 18:45:11 UTC """ # ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.governance.accepted_governance_agents import ( +from adcp.types.domains.governance.accepted_governance_agents import ( AcceptedGovernanceAgents, AnyOf, AnyOf1, AnyOf2, VerificationMode, ) -from adcp.types.generated_poc.governance.attribute_definition import ( - AttributeDefinition, - RegulatoryBasi, -) -from adcp.types.generated_poc.governance.audience_constraints import AudienceConstraints -from adcp.types.generated_poc.governance.check_governance_request import ( +from adcp.types.domains.governance.attribute_definition import AttributeDefinition, RegulatoryBasi +from adcp.types.domains.governance.audience_constraints import AudienceConstraints +from adcp.types.domains.governance.check_governance_request import ( AudienceDistribution, AudienceDistribution1, Baseline, @@ -48,14 +45,14 @@ Subject8, Subject9, ) -from adcp.types.generated_poc.governance.check_governance_response import ( +from adcp.types.domains.governance.check_governance_response import ( ActionBinding, CheckGovernanceResponse, Condition, RuntimeAttestationEvaluation, ) -from adcp.types.generated_poc.governance.get_plan_audit_logs_request import GetPlanAuditLogsRequest -from adcp.types.generated_poc.governance.get_plan_audit_logs_response import ( +from adcp.types.domains.governance.get_plan_audit_logs_request import GetPlanAuditLogsRequest +from adcp.types.domains.governance.get_plan_audit_logs_response import ( ChannelAllocation, DeliveryReportingPeriod, DriftMetrics, @@ -69,36 +66,31 @@ Thresholds, Type, ) -from adcp.types.generated_poc.governance.policy_category_definition import ( +from adcp.types.domains.governance.policy_category_definition import ( Facet, PolicyCategoryDefinition, RegulatoryFramework, ) -from adcp.types.generated_poc.governance.policy_entry import ( - Exemplar, - Exemplars, - Issuer, - PolicyEntry, -) -from adcp.types.generated_poc.governance.policy_ref import PolicyReference -from adcp.types.generated_poc.governance.report_plan_adjustment_request import ( +from adcp.types.domains.governance.policy_entry import Exemplar, Exemplars, Issuer, PolicyEntry +from adcp.types.domains.governance.policy_ref import PolicyReference +from adcp.types.domains.governance.report_plan_adjustment_request import ( Action, Decision, ReportPlanAdjustmentRequest, ) -from adcp.types.generated_poc.governance.report_plan_adjustment_response import ( +from adcp.types.domains.governance.report_plan_adjustment_response import ( ReportPlanAdjustmentResponse, ) -from adcp.types.generated_poc.governance.report_plan_outcome_request import ( +from adcp.types.domains.governance.report_plan_outcome_request import ( Package, ReportPlanOutcomeRequest, SellerResponse, ) -from adcp.types.generated_poc.governance.report_plan_outcome_response import ( +from adcp.types.domains.governance.report_plan_outcome_response import ( OutcomeState, ReportPlanOutcomeResponse, ) -from adcp.types.generated_poc.governance.reported_outcome_error import ( +from adcp.types.domains.governance.reported_outcome_error import ( BoundedObject, BoundedScalar, BoundedScalar1, @@ -112,7 +104,7 @@ Recovery, ReportedOutcomeError, ) -from adcp.types.generated_poc.governance.sync_plans_request import ( +from adcp.types.domains.governance.sync_plans_request import ( Allocations, Budget2, BudgetLimit, @@ -124,7 +116,7 @@ SyncPlansRequest, TotalBudgetCap, ) -from adcp.types.generated_poc.governance.sync_plans_response import ( +from adcp.types.domains.governance.sync_plans_response import ( Category, ResolvedPolicy, Status51, diff --git a/src/adcp/types/domains/governance/accepted_governance_agents.py b/src/adcp/types/domains/governance/accepted_governance_agents.py index 40f5df641..07468ee00 100644 --- a/src/adcp/types/domains/governance/accepted_governance_agents.py +++ b/src/adcp/types/domains/governance/accepted_governance_agents.py @@ -1,24 +1,81 @@ -"""Types declared by the AdCP ``governance/accepted_governance_agents`` schema. +# generated by datamodel-codegen: +# filename: governance/accepted_governance_agents.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.governance.accepted_governance_agents import +from adcp.types._str_enum import StrEnum +from typing import Any, Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AnyUrl, ConfigDict, Field, RootModel + + +class AnyOf1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['agent_url'] = 'agent_url' + agent_url: Annotated[ + AnyUrl, + Field( + description='Exact canonical agent endpoint without userinfo, query, or fragment. Redirect targets, DNS aliases, and URLs asserted by the candidate do not satisfy this matcher.' + ), + ] + + +class VerificationMode(StrEnum): + spec = 'spec' + live = 'live' + + +class AnyOf2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['verification'] = 'verification' + registry: Annotated[ + AnyUrl, + Field( + description='Seller-configured trusted verification registry without userinfo, query, or fragment. The candidate cannot supply or override this URL; fetches use the registry-resolution security contract.' + ), + ] + role: Annotated[ + str, + Field( + description="Role asserted by the trusted registry's verified record, never by candidate self-description.", + pattern='^[a-z][a-z0-9_-]*$', + ), + ] + adcp_version: Annotated[ + str, + Field( + description='Registry protocol version in canonical MAJOR.MINOR form.', + pattern='^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)$', + ), + ] + verification_modes: Annotated[list[VerificationMode], Field(min_length=1)] + max_age_seconds: Annotated[ + SchemaInt, + Field( + description='Maximum age of the registry evidence at binding time. Zero requires a fresh result. Evidence is pinned to the accepted binding; later registry drift does not silently revoke an existing binding.', + ge=0, + ), + ] + + +class AnyOf(RootModel[AnyOf1 | AnyOf2]): + root: Annotated[AnyOf1 | AnyOf2, Field(discriminator='kind')] + def __getattr__(self, name: str) -> Any: + """Proxy attribute access to the wrapped type.""" + if name.startswith('_'): + raise AttributeError(name) + return getattr(self.root, name) -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.governance.accepted_governance_agents import ( - AcceptedGovernanceAgents, - AnyOf, - AnyOf1, - AnyOf2, - VerificationMode, -) -# Explicit exports -__all__ = ["AcceptedGovernanceAgents", "AnyOf", "AnyOf1", "AnyOf2", "VerificationMode"] +class AcceptedGovernanceAgents(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + any_of: Annotated[list[AnyOf], Field(min_length=1)] diff --git a/src/adcp/types/domains/governance/attribute_definition.py b/src/adcp/types/domains/governance/attribute_definition.py index 794ab4318..5946ab296 100644 --- a/src/adcp/types/domains/governance/attribute_definition.py +++ b/src/adcp/types/domains/governance/attribute_definition.py @@ -1,21 +1,72 @@ -"""Types declared by the AdCP ``governance/attribute_definition`` schema. +# generated by datamodel-codegen: +# filename: governance/attribute_definition.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.governance.attribute_definition import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.governance.attribute_definition import ( - AttributeDefinition, - RegulatoryBasi, -) +class RegulatoryBasi(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + name: Annotated[str, Field(description="Name of the regulation (e.g., 'GDPR Article 9(1)').")] + jurisdictions: Annotated[ + list[str] | None, + Field(description='ISO 3166-1 alpha-2 codes where this regulation applies.'), + ] = None + summary: Annotated[ + str, Field(description='How this regulation defines or restricts the attribute.') + ] + -# Explicit exports -__all__ = ["AttributeDefinition", "RegulatoryBasi"] +class AttributeDefinition(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + attribute_id: Annotated[ + str, + Field( + description='Unique identifier for this attribute. Used in plan.restricted_attributes, signal-definition.restricted_attributes, and data marketplace catalog entries.', + pattern='^[a-z][a-z0-9_]*$', + ), + ] + name: Annotated[str, Field(description="Human-readable name (e.g., 'Health Data').")] + description: Annotated[ + str, + Field( + description='What this attribute category covers. Defines the boundary — what is and is not included.' + ), + ] + regulatory_basis: Annotated[ + list[RegulatoryBasi] | None, + Field(description='Regulations that define or restrict this attribute category.'), + ] = None + includes: Annotated[ + list[str] | None, + Field( + description="Specific data types that fall within this category (e.g., for health_data: 'medical conditions', 'disability status', 'prescription history', 'inferred health from behavioral signals')." + ), + ] = None + excludes: Annotated[ + list[str] | None, + Field( + description='Data types that might seem related but are explicitly outside this category. Helps with boundary cases.' + ), + ] = None + signal_patterns: Annotated[ + list[str] | None, + Field( + description="Common signal naming or tagging patterns that indicate this attribute (e.g., 'health:', 'condition_', 'diagnosis_'). Data providers and governance agents use these as hints when signals lack explicit restricted_attributes declarations." + ), + ] = None + guidance: Annotated[ + str | None, + Field( + description='Implementation notes. Covers edge cases, inferred vs. declared data, and common pitfalls.' + ), + ] = None diff --git a/src/adcp/types/domains/governance/audience_constraints.py b/src/adcp/types/domains/governance/audience_constraints.py index d2b57518d..7364e4f4e 100644 --- a/src/adcp/types/domains/governance/audience_constraints.py +++ b/src/adcp/types/domains/governance/audience_constraints.py @@ -1,18 +1,32 @@ -"""Types declared by the AdCP ``governance/audience_constraints`` schema. +# generated by datamodel-codegen: +# filename: governance/audience_constraints.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.governance.audience_constraints import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import audience_selector -from adcp.types.generated_poc.governance.audience_constraints import AudienceConstraints -# Explicit exports -__all__ = ["AudienceConstraints"] +class AudienceConstraints(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + include: Annotated[ + list[audience_selector.AudienceSelector] | None, + Field( + description="Desired audience criteria. The seller's targeting should align with these. Each criterion is evaluated independently — the combined targeting should satisfy at least one inclusion criterion.", + min_length=1, + ), + ] = None + exclude: Annotated[ + list[audience_selector.AudienceSelector] | None, + Field( + description="Excluded audience criteria. The seller's targeting must not overlap with these. Exclusions take precedence over inclusions. Used for protected groups, vulnerable communities, regulatory restrictions, or brand safety.", + min_length=1, + ), + ] = None diff --git a/src/adcp/types/domains/governance/check_governance_request.py b/src/adcp/types/domains/governance/check_governance_request.py index de96983c0..e5ad75148 100644 --- a/src/adcp/types/domains/governance/check_governance_request.py +++ b/src/adcp/types/domains/governance/check_governance_request.py @@ -1,60 +1,601 @@ -"""Types declared by the AdCP ``governance/check_governance_request`` schema. +# generated by datamodel-codegen: +# filename: governance/check_governance_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.governance.check_governance_request import +from adcp.types.base import AdcpRequest, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any, Literal + +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictFloat, StringConstraints + +from ..core import brand_ref, business_entity, canonical_proposal +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core import planned_delivery as planned_delivery_1 +from ..core.attestation_reference import AttestationReference +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import governance_phase +from ..enums import purchase_type as purchase_type_1 + + +class CheckGovernanceRequest1(AdCPBaseModel): + pass + + +class ProposedCommitment(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + amount: Annotated[StrictFloat, Field(ge=0.0)] + currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] + + +class ExecutionCommitment(ProposedCommitment): + pass + + +class ReportingPeriod(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + start: AwareDatetime + end: AwareDatetime + + +class Pacing(StrEnum): + ahead = 'ahead' + on_track = 'on_track' + behind = 'behind' + + +class Baseline(StrEnum): + census = 'census' + platform = 'platform' + custom = 'custom' + + +class AudienceDistribution(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + baseline: Annotated[ + Baseline, + Field( + description="Population baseline used for index calculation. 'census': national census or equivalent population data. 'platform': the seller's active user base. 'custom': a custom baseline defined by the seller (describe in baseline_description)." + ), + ] + baseline_description: Annotated[ + str | None, + Field( + description="Description of the baseline when baseline is 'custom' (e.g., 'US adults 18+ with broadband access')." + ), + ] = None + indices: Annotated[ + dict[Annotated[str, StringConstraints(pattern=r'^[a-z_]+:.+$')], StrictFloat], + Field( + description="Audience index values for the current reporting period. Keys are seller-defined dimension:value strings (e.g., 'age:25-34', 'gender:female', 'income:high'). The protocol does not mandate a taxonomy — dimensions and value labels vary by seller. Values are index relative to the declared baseline (1.0 = at parity, >1.0 = over-indexed, <1.0 = under-indexed)." + ), + ] + cumulative_indices: Annotated[ + dict[Annotated[str, StringConstraints(pattern=r'^[a-z_]+:.+$')], StrictFloat] | None, + Field( + description='Cumulative audience index values since the governed action started. Same key format as indices (dimension:value). Use for detecting sustained bias drift that may not appear in a single reporting period.' + ), + ] = None + + +class DeliveryMetrics(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + statement_id: Annotated[ + str | None, + Field( + description='Stable seller-issued identifier for this immutable delivery statement.', + max_length=255, + min_length=1, + ), + ] = None + statement_digest: Annotated[ + str | None, + Field( + description='SHA-256 digest of RFC 8785 JCS over {seller_reference, delivery_metrics}, excluding statement_digest itself. The authenticated submission binds the seller to this digest.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + sequence: Annotated[ + SchemaInt | None, + Field( + description='Monotonically increasing sequence for this governed action. A statement ID or sequence cannot be reused with different content.', + ge=1, + ), + ] = None + issued_at: Annotated[ + AwareDatetime | None, Field(description='When the seller issued the canonical statement.') + ] = None + reporting_period: Annotated[ + ReportingPeriod, Field(description='Start and end timestamps for the reporting window.') + ] + spend: Annotated[ + StrictFloat | None, Field(description='Total spend during the reporting period.', ge=0.0) + ] = None + cumulative_spend: Annotated[ + StrictFloat | None, + Field(description='Total spend since the governed action started.', ge=0.0), + ] = None + currency: Annotated[ + str | None, + Field( + description='Currency of spend fields; must match the plan and planned delivery.', + pattern='^[A-Z]{3}$', + ), + ] = None + impressions: Annotated[ + SchemaInt | None, + Field(description='Impressions delivered during the reporting period.', ge=0), + ] = None + cumulative_impressions: Annotated[ + SchemaInt | None, + Field(description='Total impressions since the governed action started.', ge=0), + ] = None + geo_distribution: Annotated[ + dict[str, StrictFloat] | None, + Field( + description='Actual geographic distribution. Keys are ISO 3166-1 alpha-2 codes, values are percentages.' + ), + ] = None + channel_distribution: Annotated[ + dict[str, StrictFloat] | None, + Field( + description='Actual channel distribution. Keys are channel enum values, values are percentages.' + ), + ] = None + pacing: Annotated[ + Pacing | None, + Field( + description='Whether delivery is ahead of, on track with, or behind the planned pace.' + ), + ] = None + audience_distribution: Annotated[ + AudienceDistribution | None, + Field( + description='Actual audience composition during the reporting period. Enables mid-flight drift detection when actual delivery skews from planned audience targeting.' + ), + ] = None + + +class AudienceDistribution1(AudienceDistribution): + pass + + +class DeliveryMetrics2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + statement_id: Annotated[ + str | None, + Field( + description='Stable seller-issued identifier for this immutable delivery statement.', + max_length=255, + min_length=1, + ), + ] = None + statement_digest: Annotated[ + str | None, + Field( + description='SHA-256 digest of RFC 8785 JCS over {seller_reference, delivery_metrics}, excluding statement_digest itself. The authenticated submission binds the seller to this digest.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + sequence: Annotated[ + SchemaInt | None, + Field( + description='Monotonically increasing sequence for this governed action. A statement ID or sequence cannot be reused with different content.', + ge=1, + ), + ] = None + issued_at: Annotated[ + AwareDatetime | None, Field(description='When the seller issued the canonical statement.') + ] = None + reporting_period: Annotated[ + ReportingPeriod, Field(description='Start and end timestamps for the reporting window.') + ] + spend: Annotated[ + StrictFloat | None, Field(description='Total spend during the reporting period.', ge=0.0) + ] = None + cumulative_spend: Annotated[ + StrictFloat | None, + Field(description='Total spend since the governed action started.', ge=0.0), + ] = None + currency: Annotated[ + str | None, + Field( + description='Currency of spend fields; must match the plan and planned delivery.', + pattern='^[A-Z]{3}$', + ), + ] = None + impressions: Annotated[ + SchemaInt | None, + Field(description='Impressions delivered during the reporting period.', ge=0), + ] = None + cumulative_impressions: Annotated[ + SchemaInt | None, + Field(description='Total impressions since the governed action started.', ge=0), + ] = None + geo_distribution: Annotated[ + dict[str, StrictFloat] | None, + Field( + description='Actual geographic distribution. Keys are ISO 3166-1 alpha-2 codes, values are percentages.' + ), + ] = None + channel_distribution: Annotated[ + dict[str, StrictFloat] | None, + Field( + description='Actual channel distribution. Keys are channel enum values, values are percentages.' + ), + ] = None + pacing: Annotated[ + Pacing | None, + Field( + description='Whether delivery is ahead of, on track with, or behind the planned pace.' + ), + ] = None + audience_distribution: Annotated[ + AudienceDistribution1 | None, + Field( + description='Actual audience composition during the reporting period. Enables mid-flight drift detection when actual delivery skews from planned audience targeting.' + ), + ] = None + + +class Subject(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Literal['agent'] = 'agent' + resource_type: Literal['https://adcontextprotocol.org/claims/subjects/signal'] = 'https://adcontextprotocol.org/claims/subjects/signal' + agent_url: Annotated[ + AnyUrl, Field(description='Canonical HTTPS endpoint of the agent the claim concerns.') + ] + ext: ext_1.ExtensionObject | None = None + + +class Subject6(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Literal['resource'] = 'resource' + resource_type: Annotated[ + Literal['https://adcontextprotocol.org/claims/subjects/signal'], + Field( + description='Open, absolute URI naming the subject vocabulary, such as https://adcontextprotocol.org/claims/subjects/signal. AdCP does not maintain an exhaustive enum.' + ), + ] = 'https://adcontextprotocol.org/claims/subjects/signal' + namespace: Annotated[ + AnyUrl, + Field( + description='Absolute URI identifying the namespace in which id is unique. This may be an AdCP agent endpoint, a catalog origin, or a domain-specific namespace URI.' + ), + ] + id: Annotated[ + str, + Field( + description='Stable identifier for the subject within namespace. It MUST NOT be compared without resource_type and namespace.', + max_length=1024, + min_length=1, + ), + ] + content_digest: Annotated[ + str | None, + Field( + description='Optional SHA-256 pin for the exact content or immutable snapshot identified by this resource subject. This is part of the complete typed subject identity and is distinct from AttestationReference.content_digest, which pins credential bytes.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + ext: ext_1.ExtensionObject | None = None + + +class Subject8(Subject): + type: Literal['agent'] = 'agent' + + +class Subject9(Subject6): + type: Literal['resource'] = 'resource' + + +class Subject4(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Literal['brand'] = 'brand' + resource_type: Literal['https://adcontextprotocol.org/claims/subjects/signal'] = 'https://adcontextprotocol.org/claims/subjects/signal' + brand: brand_ref.BrandReference + ext: ext_1.ExtensionObject | None = None + + +class RuntimeAttestation(AttestationReference): + subject: Annotated[ + Subject4 | Subject | Subject6 | None, + Field( + description='Typed identity of the entity or object an attestation credential is about. Brand and agent subjects reuse canonical AdCP identities. Other resources use an open, URI-namespaced resource_type plus an identifier whose namespace is explicit. Evaluators MUST compare the resolved credential subject to this complete typed identity, not to id alone.', + discriminator='type', + examples=[ + { + 'type': 'brand', + 'brand': {'domain': 'nova-brands.example', 'brand_id': 'nova_motors'}, + }, + { + 'type': 'resource', + 'resource_type': 'https://adcontextprotocol.org/claims/subjects/signal', + 'namespace': 'https://signals.meridian.example/adcp', + 'id': 'signal_urban_commuters', + }, + ], + title='Attestation Subject', + ), + ] = None + + +class CheckGovernanceRequest2(AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + plan_id: Annotated[ + str | None, + Field( + description="Campaign governance plan identifier. Required on the initial intent or availability check, before a governance_context exists. Optional on subsequent checks: the governance agent derives the plan from its own signed governance_context. If both are present, the governance agent MUST reject the request when plan_id does not match the token's plan binding. Services MUST treat governance_context as authoritative and MUST NOT require a buyer to disclose plan_id. A plan is owned by the authenticated buyer principal that synchronized it; plan_id is an identifier, not an account credential." + ), + ] = None + caller: Annotated[ + AnyUrl, + Field( + description='Claimed URL of the agent making the request. The transport credential MUST resolve to an agent URL; the governance agent requires an exact match and uses only that resolved URL for authorization, audit, and signed context issuance. On intent checks the authenticated buyer must be the plan owner or hold an active delegation, while approved_sellers is evaluated against the target service that becomes the token audience. On execution checks the authenticated caller MUST equal that preserved audience. An unresolved body assertion never grants plan access or authorization.' + ), + ] + purchase_type: Annotated[ + purchase_type_1.PurchaseType | None, + Field( + description="The type of financial commitment being checked. Determines which budget allocation (if any) to validate against. Defaults to 'media_buy' when omitted." + ), + ] = purchase_type_1.PurchaseType.media_buy + target_agent: Annotated[ + AnyUrl | None, + Field( + description='Exact agent URL of the downstream service that will receive the governed task. Required on intent checks and copied byte-for-byte into the signed governance_context aud claim. This routing and authorization field is not part of payload: payload remains exactly the downstream task arguments. A consultation re-check MUST use the same target_agent.' + ), + ] = None + proposed_commitment: Annotated[ + ProposedCommitment | None, + Field( + description='Task-neutral monetary amount the intent would authorize. For update_media_buy and control_media_buy this is the buyer-computed positive incremental commitment, not the post-update total. For accept_proposal it is derived from the supplied proposal commercial_terms; for buy_products it is derived from the purchase payload. Amount 0 explicitly represents a verified no-cost action. The governance agent persists this value as authoritative check state.' + ), + ] = None + execution_commitment: Annotated[ + ExecutionCommitment | None, + Field( + description='Seller-computed positive incremental commitment for a MediaBuy execution check. The seller MUST derive this atomically from its authoritative proposal or current revision and the requested operation, and the governance agent MUST reject it when it exceeds the prior intent ceiling or uses another currency.' + ), + ] = None + tool: Annotated[ + str | None, + Field( + description="The AdCP tool being checked (e.g., 'create_media_buy', 'acquire_rights', 'activate_signal'). Present on intent checks (orchestrator). The governance agent uses the presence of tool+payload to identify an intent check." + ), + ] = None + payload: Annotated[ + dict[str, Any] | None, + Field( + description='The full downstream tool arguments exactly as they will be sent to target_agent. Present on intent checks. Governance routing metadata is carried by target_agent, never injected into this object. The governance agent can inspect any field to validate against the plan.' + ), + ] = None + proposal: Annotated[ + canonical_proposal.CanonicalProposal | None, + Field( + description='Exact committed proposal being authorized for accept_proposal. The governance agent verifies proposal.terms_digest against commercial_terms and binds that digest into its decision state; the downstream payload carries the same digest without repeating the terms.' + ), + ] = None + governance_context: Annotated[ + str | None, + Field( + description='Opaque authorization context from a prior approved check_governance response. Services pass it verbatim on execution and lifecycle checks; the issuing governance agent derives the plan and prior decision from the token. Intermediaries MUST NOT parse it for business logic. Governance agents MUST emit a compact JWS per the AdCP JWS profile.', + max_length=4096, + min_length=1, + pattern='^[\\x20-\\x7E]+$', + ), + ] = None + consultation_context: Annotated[ + str | None, + Field( + description='Opaque, non-authorizing handle returned with an intent conditions verdict. Pass it only when re-checking the adjusted intent so the governance agent can correlate negotiation attempts. The governance agent MUST resolve it under the authenticated principal and reject the re-check unless principal, caller, plan_id, tool, purchase_type, and target audience match the original conditions check. Services MUST NOT receive or accept this value as authorization.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]+$', + ), + ] = None + phase: Annotated[ + governance_phase.GovernancePhase | None, + Field( + description="The phase of an execution-shaped governed action. Ignored for intent-shaped tool+payload requests. 'purchase': initial commitment; 'modification': update to an existing commitment; 'delivery': periodic delivery reporting. Defaults to purchase if omitted. planned_delivery_1.media_buy_id is optional for purchase and required for modification/delivery." + ), + ] = governance_phase.GovernancePhase.purchase + planned_delivery: Annotated[ + planned_delivery_1.PlannedDelivery | None, + Field(description='What the seller will actually deliver. Present on execution checks.'), + ] = None + delivery_metrics: Annotated[ + DeliveryMetrics | None, + Field( + description="Seller-attributed canonical delivery statement. MUST be present for 'delivery' phase. The authenticated seller binds one immutable statement_id and digest to a monotonically increasing sequence; the buyer can later submit the copy it received or an independent observation through report_plan_outcome." + ), + ] = None + modification_summary: Annotated[ + str | None, + Field( + description="Human-readable summary of what changed. SHOULD be present for 'modification' phase.", + max_length=1000, + ), + ] = None + runtime_attestations: Annotated[ + list[RuntimeAttestation] | None, + Field( + description='Optional independently issued runtime evidence for an activate_signal intent check whose payload action is activate (or omitted, which defaults to activate). It MUST NOT be supplied for deactivate. Each item is the shared portable AttestationReference from the core #4529 contract; it carries no authoritative buyer-supplied decision or confidence. The governance agent MUST evaluate every item under adcp.attestations plus governance.runtime_attestations capability policy, preserve input order in response runtime_attestation_evaluations[], and reject off-policy issuers, resolvers, credential origins, and verifier nominations without network access. This field is per-check evidence outside the synced plan and therefore outside the plan_hash preimage. Other tools and purchase types cannot carry this field.', + max_length=10, + min_length=1, + ), + ] = None + invoice_recipient: Annotated[ + business_entity.BusinessEntity | None, + Field( + description='Invoice recipient from the purchase request. MUST be present when the tool payload includes invoice_recipient, so the governance agent can validate billing changes.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class RuntimeAttestation1(AttestationReference): + subject: Annotated[ + Subject4 | Subject8 | Subject9 | None, + Field( + description='Typed identity of the entity or object an attestation credential is about. Brand and agent subjects reuse canonical AdCP identities. Other resources use an open, URI-namespaced resource_type plus an identifier whose namespace is explicit. Evaluators MUST compare the resolved credential subject to this complete typed identity, not to id alone.', + discriminator='type', + examples=[ + { + 'type': 'brand', + 'brand': {'domain': 'nova-brands.example', 'brand_id': 'nova_motors'}, + }, + { + 'type': 'resource', + 'resource_type': 'https://adcontextprotocol.org/claims/subjects/signal', + 'namespace': 'https://signals.meridian.example/adcp', + 'id': 'signal_urban_commuters', + }, + ], + title='Attestation Subject', + ), + ] = None + + +class CheckGovernanceRequest3(CheckGovernanceRequest1, CheckGovernanceRequest2): + model_config = ConfigDict( + extra='allow', + ) + plan_id: Annotated[ + str | None, + Field( + description="Campaign governance plan identifier. Required on the initial intent or availability check, before a governance_context exists. Optional on subsequent checks: the governance agent derives the plan from its own signed governance_context. If both are present, the governance agent MUST reject the request when plan_id does not match the token's plan binding. Services MUST treat governance_context as authoritative and MUST NOT require a buyer to disclose plan_id. A plan is owned by the authenticated buyer principal that synchronized it; plan_id is an identifier, not an account credential." + ), + ] = None + caller: Annotated[ + AnyUrl, + Field( + description='Claimed URL of the agent making the request. The transport credential MUST resolve to an agent URL; the governance agent requires an exact match and uses only that resolved URL for authorization, audit, and signed context issuance. On intent checks the authenticated buyer must be the plan owner or hold an active delegation, while approved_sellers is evaluated against the target service that becomes the token audience. On execution checks the authenticated caller MUST equal that preserved audience. An unresolved body assertion never grants plan access or authorization.' + ), + ] + purchase_type: Annotated[ + purchase_type_1.PurchaseType | None, + Field( + description="The type of financial commitment being checked. Determines which budget allocation (if any) to validate against. Defaults to 'media_buy' when omitted." + ), + ] = purchase_type_1.PurchaseType.media_buy + target_agent: Annotated[ + AnyUrl | None, + Field( + description='Exact agent URL of the downstream service that will receive the governed task. Required on intent checks and copied byte-for-byte into the signed governance_context aud claim. This routing and authorization field is not part of payload: payload remains exactly the downstream task arguments. A consultation re-check MUST use the same target_agent.' + ), + ] = None + proposed_commitment: Annotated[ + ProposedCommitment | None, + Field( + description='Task-neutral monetary amount the intent would authorize. For update_media_buy and control_media_buy this is the buyer-computed positive incremental commitment, not the post-update total. For accept_proposal it is derived from the supplied proposal commercial_terms; for buy_products it is derived from the purchase payload. Amount 0 explicitly represents a verified no-cost action. The governance agent persists this value as authoritative check state.' + ), + ] = None + execution_commitment: Annotated[ + ExecutionCommitment | None, + Field( + description='Seller-computed positive incremental commitment for a MediaBuy execution check. The seller MUST derive this atomically from its authoritative proposal or current revision and the requested operation, and the governance agent MUST reject it when it exceeds the prior intent ceiling or uses another currency.' + ), + ] = None + tool: Annotated[ + str | None, + Field( + description="The AdCP tool being checked (e.g., 'create_media_buy', 'acquire_rights', 'activate_signal'). Present on intent checks (orchestrator). The governance agent uses the presence of tool+payload to identify an intent check." + ), + ] = None + payload: Annotated[ + dict[str, Any] | None, + Field( + description='The full downstream tool arguments exactly as they will be sent to target_agent. Present on intent checks. Governance routing metadata is carried by target_agent, never injected into this object. The governance agent can inspect any field to validate against the plan.' + ), + ] = None + proposal: Annotated[ + canonical_proposal.CanonicalProposal | None, + Field( + description='Exact committed proposal being authorized for accept_proposal. The governance agent verifies proposal.terms_digest against commercial_terms and binds that digest into its decision state; the downstream payload carries the same digest without repeating the terms.' + ), + ] = None + governance_context: Annotated[ + str | None, + Field( + description='Opaque authorization context from a prior approved check_governance response. Services pass it verbatim on execution and lifecycle checks; the issuing governance agent derives the plan and prior decision from the token. Intermediaries MUST NOT parse it for business logic. Governance agents MUST emit a compact JWS per the AdCP JWS profile.', + max_length=4096, + min_length=1, + pattern='^[\\x20-\\x7E]+$', + ), + ] = None + consultation_context: Annotated[ + str | None, + Field( + description='Opaque, non-authorizing handle returned with an intent conditions verdict. Pass it only when re-checking the adjusted intent so the governance agent can correlate negotiation attempts. The governance agent MUST resolve it under the authenticated principal and reject the re-check unless principal, caller, plan_id, tool, purchase_type, and target audience match the original conditions check. Services MUST NOT receive or accept this value as authorization.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]+$', + ), + ] = None + phase: Annotated[ + governance_phase.GovernancePhase | None, + Field( + description="The phase of an execution-shaped governed action. Ignored for intent-shaped tool+payload requests. 'purchase': initial commitment; 'modification': update to an existing commitment; 'delivery': periodic delivery reporting. Defaults to purchase if omitted. planned_delivery_1.media_buy_id is optional for purchase and required for modification/delivery." + ), + ] = governance_phase.GovernancePhase.purchase + planned_delivery: Annotated[ + planned_delivery_1.PlannedDelivery | None, + Field(description='What the seller will actually deliver. Present on execution checks.'), + ] = None + delivery_metrics: Annotated[ + DeliveryMetrics2 | None, + Field( + description="Seller-attributed canonical delivery statement. MUST be present for 'delivery' phase. The authenticated seller binds one immutable statement_id and digest to a monotonically increasing sequence; the buyer can later submit the copy it received or an independent observation through report_plan_outcome." + ), + ] = None + modification_summary: Annotated[ + str | None, + Field( + description="Human-readable summary of what changed. SHOULD be present for 'modification' phase.", + max_length=1000, + ), + ] = None + runtime_attestations: Annotated[ + list[RuntimeAttestation1] | None, + Field( + description='Optional independently issued runtime evidence for an activate_signal intent check whose payload action is activate (or omitted, which defaults to activate). It MUST NOT be supplied for deactivate. Each item is the shared portable AttestationReference from the core #4529 contract; it carries no authoritative buyer-supplied decision or confidence. The governance agent MUST evaluate every item under adcp.attestations plus governance.runtime_attestations capability policy, preserve input order in response runtime_attestation_evaluations[], and reject off-policy issuers, resolvers, credential origins, and verifier nominations without network access. This field is per-check evidence outside the synced plan and therefore outside the plan_hash preimage. Other tools and purchase types cannot carry this field.', + max_length=10, + min_length=1, + ), + ] = None + invoice_recipient: Annotated[ + business_entity.BusinessEntity | None, + Field( + description='Invoice recipient from the purchase request. MUST be present when the tool payload includes invoice_recipient, so the governance agent can validate billing changes.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.governance.check_governance_request import ( - AudienceDistribution, - AudienceDistribution1, - Baseline, - CheckGovernanceRequest, - CheckGovernanceRequest1, - CheckGovernanceRequest2, - CheckGovernanceRequest3, - DeliveryMetrics, - DeliveryMetrics2, - ExecutionCommitment, - Pacing, - ProposedCommitment, - ReportingPeriod, - RuntimeAttestation, - RuntimeAttestation1, - Subject, - Subject4, - Subject6, - Subject8, - Subject9, -) - -# Explicit exports -__all__ = [ - "AudienceDistribution", - "AudienceDistribution1", - "Baseline", - "CheckGovernanceRequest", - "CheckGovernanceRequest1", - "CheckGovernanceRequest2", - "CheckGovernanceRequest3", - "DeliveryMetrics", - "DeliveryMetrics2", - "ExecutionCommitment", - "Pacing", - "ProposedCommitment", - "ReportingPeriod", - "RuntimeAttestation", - "RuntimeAttestation1", - "Subject", - "Subject4", - "Subject6", - "Subject8", - "Subject9", -] +class CheckGovernanceRequest(AdcpRequest, CheckGovernanceRequest3): + 'Universal governance check for campaign actions. The governance agent infers the check type from the fields present: tool+payload = intent check (proposed, orchestrator-side); planned_delivery or delivery_metrics with governance_context = execution or lifecycle check (committed, service-side). Proposal acceptance supplies the immutable proposal separately so governance can inspect its typed commercial terms while payload remains the exact downstream arguments. MediaBuy controls use buyer-proposed and seller-computed positive-delta ceilings. The first check is addressed by plan_id. Subsequent service-side checks use the opaque governance_context as the authoritative plan binding.' diff --git a/src/adcp/types/domains/governance/check_governance_response.py b/src/adcp/types/domains/governance/check_governance_response.py index 6d277d878..95abdfd38 100644 --- a/src/adcp/types/domains/governance/check_governance_response.py +++ b/src/adcp/types/domains/governance/check_governance_response.py @@ -1,38 +1,289 @@ -"""Types declared by the AdCP ``governance/check_governance_response`` schema. +# generated by datamodel-codegen: +# filename: governance/check_governance_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.governance.check_governance_response import +from adcp.types.base import AdcpResponse, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any, Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat, model_validator + +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.attestation_evaluation import AttestationEvaluation +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import escalation_severity, governance_decision, governance_mode + + +class CheckType(StrEnum): + intent = 'intent' + execution = 'execution' + + +class Condition(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + field: Annotated[ + str, + Field( + description='Dot-path rooted at the complete check_governance request arguments (for example payload.total_budget.amount or proposed_commitment.amount). Conditions are not valid for committed execution checks.' + ), + ] + required_value: Annotated[ + Any | None, + Field( + description='The value the field must have for approval. When present, the condition is machine-actionable. When absent, the condition is advisory.' + ), + ] = None + reason: Annotated[str, Field(description='Why this condition is required.')] + + +class ReportingPeriod(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + start: AwareDatetime + end: AwareDatetime + + +class CanonicalPayload(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + seller_reference: str + delivery_metrics: dict[str, Any] + + +class DeliveryStatement(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + statement_id: str + statement_digest: Annotated[str, Field(pattern='^sha256:[a-f0-9]{64}$')] + sequence: Annotated[SchemaInt, Field(ge=1)] + issued_at: AwareDatetime + seller_reference: str + reporting_period: ReportingPeriod + cumulative_spend: Annotated[StrictFloat, Field(ge=0.0)] + currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] + canonical_payload: Annotated[ + CanonicalPayload, + Field( + description='Exact retained digest-bearing payload. To verify statement_digest, apply RFC 8785 JCS after removing delivery_metrics.statement_digest.' + ), + ] + + +class ActionBinding(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + action_type: Annotated[ + Literal['https://adcontextprotocol.org/actions/governance-check'], + Field( + description='Absolute URI naming the action vocabulary, such as an AdCP governance-check or audience-evidence snapshot type.' + ), + ] = 'https://adcontextprotocol.org/actions/governance-check' + action_id: Annotated[ + str, + Field( + description="Stable identifier of the consuming action within the domain consumer's namespace.", + max_length=1024, + min_length=1, + ), + ] + action_digest: Annotated[ + str | None, + Field( + description='SHA-256 digest of the domain-defined canonical action preimage.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + + +class Finding(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + category_id: Annotated[ + str, + Field( + description="Validation category that flagged the issue (e.g., 'budget_compliance', 'regulatory_compliance', 'brand_safety'). This is an **agent-internal** taxonomy: the string is defined by the governance agent's own policy model and is not constrained to any protocol-level enum. Sellers and buyers MUST NOT pattern-match `category_id` values against a fixed list — treat them as opaque labels with human-readable significance for audit but no machine-level contract. See the Campaign Governance specification for how an agent composes internal specialist review behind one endpoint." + ), + ] + policy_id: Annotated[ + str | None, + Field( + description="ID of the policy that triggered this finding. May reference a registry policy (with source: registry) or a bespoke inline policy (with source: inline). Bespoke policy_ids are unique within their authoring container; use source_plan_id when findings aggregate across multiple plans (e.g., portfolio evaluations). When the violation traces to a producer-tagged surface (feature-requirement, creative-feature-result, or validation-result feature) carrying policy_id, governance agents SHOULD echo that policy_id here for end-to-end traceability, and MUST NOT invent a policy_id that wasn't present on the originating surface. See /docs/governance/policy-attribution." + ), + ] = None + source_plan_id: Annotated[ + str | None, + Field( + description="For portfolio or aggregated evaluations where findings draw on bespoke policies from multiple member plans: identifies the plan whose policy triggered this finding. Omit when the finding's policy_id is unambiguous within the response context (e.g., single-plan check_governance)." + ), + ] = None + severity: escalation_severity.EscalationSeverity + explanation: Annotated[str, Field(description='Human-readable description of the issue.')] + details: Annotated[ + dict[str, Any] | None, Field(description='Structured details for programmatic consumption.') + ] = None + confidence: Annotated[ + StrictFloat | None, + Field( + description="Confidence score (0-1) in this finding. Distinguishes 'this definitely violates the policy' (0.95) from 'this might violate depending on how audience segments resolve' (0.6). When absent, the finding is presented without a confidence qualifier.", + ge=0.0, + le=1.0, + ), + ] = None + uncertainty_reason: Annotated[ + str | None, + Field( + description="Explanation of why confidence is below 1.0 (e.g., 'Targeting includes regions that partially overlap jurisdiction boundaries'). Present when confidence is below a governance-agent-defined threshold." + ), + ] = None + attestation_reference_digest: Annotated[ + str | None, + Field( + description='When this finding relies on or reports a runtime attestation evaluation, the reference_digest of the exact AttestationReference in runtime_attestation_evaluations[]. Findings MUST NOT copy credential bodies or treat a presenter assertion as verified evidence.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + + +class RuntimeAttestationEvaluation(AttestationEvaluation): + action_binding: Annotated[ + ActionBinding, + Field( + description='Optional binding to the consuming action or readback. Domain consumers that rely on an evaluation MUST carry either an action id or an action digest so the result cannot be transplanted to an unrelated decision.' + ), + ] + + +class CheckGovernanceResponse(AdcpResponse, AdcpVersionEnvelope): + @model_validator(mode='before') + @classmethod + def _status_to_verdict(cls, data: Any) -> Any: + if isinstance(data, dict) and 'verdict' not in data and 'status' in data: + data = dict(data) + data['verdict'] = data['status'] + return data -from adcp.types.generated_poc.governance.check_governance_response import ( - ActionBinding, - CanonicalPayload, - CheckGovernanceResponse, - CheckType, - Condition, - DeliveryStatement, - Finding, - ReportingPeriod, - RuntimeAttestationEvaluation, -) - -# Explicit exports -__all__ = [ - "ActionBinding", - "CanonicalPayload", - "CheckGovernanceResponse", - "CheckType", - "Condition", - "DeliveryStatement", - "Finding", - "ReportingPeriod", - "RuntimeAttestationEvaluation", -] + model_config = ConfigDict( + extra='allow', + ) + check_id: Annotated[ + str, + Field( + description='Unique identifier for this governance check record. Use in report_plan_outcome to link outcomes to the check that authorized them.' + ), + ] + verdict: Annotated[ + governance_decision.GovernanceDecision, + Field( + description='Governance verdict: approved | denied | conditions. Renamed from `status` in 3.1 to free the top-level `status` key for the envelope task-status (TaskStatus) under MCP flat-on-the-wire serialization. The enum values are unchanged; only the property name moved.' + ), + ] + check_type: Annotated[ + CheckType | None, + Field( + description='Check shape that produced the verdict. Required for the cross-role governance_enforcement contract. Its presence selects the modern verdict-specific response rules; its absence selects the deprecated legacy 3.x compatibility shape. Intent checks may return conditions; execution checks are binary approved or denied.' + ), + ] = None + plan_id: Annotated[ + str | None, + Field( + description='Plan identifier echoed on an initial plan-addressed check. Optional on continuation checks addressed by governance_context; services do not need this value and MUST treat the token binding as authoritative.' + ), + ] = None + explanation: Annotated[ + str, Field(description='Human-readable explanation of the governance decision.') + ] + findings: Annotated[ + list[Finding] | None, + Field( + description="Specific issues found during the governance check. Present when verdict is 'denied' or 'conditions'. MAY also be present on 'approved' for informational findings (e.g., budget approaching limit)." + ), + ] = None + conditions: Annotated[ + list[Condition] | None, + Field( + description="Intent-phase counterproposal. Present only when verdict is 'conditions'. It does not authorize execution and MUST NOT be returned for execution or lifecycle checks. Each field path is rooted at the complete check_governance request arguments, so both payload.* and proposed_commitment.* can be addressed. After applying conditions, the caller MUST re-call check_governance with the adjusted parameters and receive approved before proceeding." + ), + ] = None + consultation_context: Annotated[ + str | None, + Field( + description='Opaque negotiation handle present only with modern conditions responses. It carries no authorization and MUST NOT be sent to a downstream service. The governance agent MUST bind it server-side to the authenticated principal, caller, plan, tool, purchase type, and target audience, and reject a re-check if any binding changes. The buyer returns it only on the adjusted intent re-check.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]+$', + ), + ] = None + expires_at: Annotated[ + AwareDatetime | None, + Field( + description="When this approval expires. In the cross-role shape, present only when verdict is 'approved'. Deprecated legacy conditions responses may also carry it for 3.x compatibility. The caller must act before this time or re-call check_governance. A lapsed approval is no approval." + ), + ] = None + next_check: Annotated[ + AwareDatetime | None, + Field( + description='When the seller should next call check_governance with delivery metrics. Present when the governance agent expects ongoing delivery reporting.' + ), + ] = None + delivery_statement: Annotated[ + DeliveryStatement | None, + Field( + description='Canonical seller-attributed delivery statement retained by governance. Present on delivery execution checks. The buyer binds any later observation to this exact statement through report_plan_outcome.' + ), + ] = None + categories_evaluated: Annotated[ + list[str] | None, + Field( + description="Governance categories evaluated during this check. Each value is an **agent-internal** label (e.g., `budget_authority`, `regulatory_compliance`, or any internal-reviewer key the agent's policy model defines) — not a protocol-level enum. Since one governance agent per account composes all specialist review behind its single endpoint, `categories_evaluated` is how that internal decomposition surfaces to auditors. Consumers MUST treat values as opaque labels for display and audit, not as a machine-level contract." + ), + ] = None + policies_evaluated: Annotated[ + list[str] | None, + Field( + description="Policy IDs evaluated during this check. Includes registry policy IDs (resolved via the policy registry) and any inline `policy_id`s declared in the plan's `custom_policies`." + ), + ] = None + mode: Annotated[ + governance_mode.GovernanceMode | None, + Field( + description='Governance enforcement mode active when this check was evaluated. Allows counterparties, regulators, and auditors to distinguish whether a finding blocked execution (enforce) or was logged silently (audit).' + ), + ] = None + runtime_attestation_evaluations: Annotated[ + list[RuntimeAttestationEvaluation] | None, + Field( + description="Evaluator-of-record results for request runtime_attestations[], in the same order and with exactly one result per presentation. Each result is the shared AttestationEvaluation and MUST bind to this response's check_id through action_binding.action_type = https://adcontextprotocol.org/actions/governance-check and action_binding.action_id = check_id. The signed governance_context MUST bind the same reference_digest/outcome pairs; large evidence stays in the audit log rather than the token.", + max_length=10, + min_length=1, + ), + ] = None + runtime_attestation_binding_digest: Annotated[ + str | None, + Field( + description='SHA-256 of RFC 8785 JCS({ evaluations: runtime_attestation_evaluations, findings: attestation_bound_findings }), where attestation_bound_findings is the response findings[] subset carrying attestation_reference_digest, preserved in response order. Required whenever runtime_attestation_evaluations is present. The governance_context JWS carries this exact value as runtime_attestation_binding_digest; get_plan_audit_logs retains ordered {reference, evaluation} pairs so auditors can first recompute every reference_digest and then prove which evaluations and findings the signed decision relied on.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + governance_context: Annotated[ + str | None, + Field( + description='Opaque authorization context for this governed action. Present only when verdict is approved; denied and conditions responses MUST NOT carry it. The buyer attaches it to the protocol envelope when sending the governed request. The service persists and forwards it on subsequent execution and lifecycle checks without requiring plan_id.\n\nGovernance agents MUST emit a compact JWS per the AdCP JWS profile. Verifiers validate the standard authorization claims but MUST NOT interpret embedded governance state for business logic. The issuing governance agent uses the token to recover its internal plan and decision state.', + max_length=4096, + min_length=1, + pattern='^[\\x20-\\x7E]+$', + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/governance/get_plan_audit_logs_request.py b/src/adcp/types/domains/governance/get_plan_audit_logs_request.py index 863f4ca59..35331821f 100644 --- a/src/adcp/types/domains/governance/get_plan_audit_logs_request.py +++ b/src/adcp/types/domains/governance/get_plan_audit_logs_request.py @@ -1,18 +1,68 @@ -"""Types declared by the AdCP ``governance/get_plan_audit_logs_request`` schema. +# generated by datamodel-codegen: +# filename: governance/get_plan_audit_logs_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.governance.get_plan_audit_logs_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated + +from pydantic import ConfigDict, Field, StrictBool, model_validator + +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import purchase_type -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.governance.get_plan_audit_logs_request import GetPlanAuditLogsRequest +class GetPlanAuditLogsRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + plan_ids: Annotated[ + list[str] | None, + Field( + description='Plan IDs to retrieve. For a single plan, pass a one-element array. Plans uniquely scope account and operator; do not include a separate `account` field — the governance agent resolves account from each plan. Including `account` is rejected by `additionalProperties: false`.', + min_length=1, + ), + ] = None + portfolio_plan_ids: Annotated[ + list[str] | None, + Field( + description='Portfolio plan IDs. The governance agent expands each to its member_plan_ids and returns combined audit data.', + min_length=1, + ), + ] = None + governance_contexts: Annotated[ + list[str] | None, + Field( + description='Filter audit entries by governance context. Returns only checks and outcomes that share these governance contexts, enabling lifecycle tracing across purchase types.', + min_length=1, + ), + ] = None + purchase_types: Annotated[ + list[purchase_type.PurchaseType] | None, + Field( + description="Filter audit entries by purchase type. Returns only checks and outcomes matching these purchase types (e.g., ['rights_license'] to see all rights activity).", + min_length=1, + ), + ] = None + include_entries: Annotated[ + StrictBool | None, Field(description='Include the full audit trail. Default: false.') + ] = False + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None -# Explicit exports -__all__ = ["GetPlanAuditLogsRequest"] + @model_validator(mode='after') + def _require_schema_required_group(self) -> GetPlanAuditLogsRequest: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('plan_ids',), ('portfolio_plan_ids',), ('governance_contexts',),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'GetPlanAuditLogsRequest requires at least one of these field groups: plan_ids | portfolio_plan_ids | governance_contexts' + ) diff --git a/src/adcp/types/domains/governance/get_plan_audit_logs_response.py b/src/adcp/types/domains/governance/get_plan_audit_logs_response.py index 5f168b4d2..5e41b33ee 100644 --- a/src/adcp/types/domains/governance/get_plan_audit_logs_response.py +++ b/src/adcp/types/domains/governance/get_plan_audit_logs_response.py @@ -1,82 +1,729 @@ -"""Types declared by the AdCP ``governance/get_plan_audit_logs_response`` schema. +# generated by datamodel-codegen: +# filename: governance/get_plan_audit_logs_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.governance.get_plan_audit_logs_response import +from adcp.types.base import AdcpResponse, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any + +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat + +from ..core import attestation_evaluation, attestation_reference +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import escalation_severity, governance_decision, governance_mode, outcome_type +from ..enums import purchase_type as purchase_type_1 +from . import reported_outcome_error + + +class Status(StrEnum): + active = 'active' + suspended = 'suspended' + completed = 'completed' + + +class AccountingMode(StrEnum): + gross_commitment = 'gross_commitment' + verified_net_cost = 'verified_net_cost' + + +class Budget(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + authorized: Annotated[ + StrictFloat | None, Field(description='Total authorized budget from the plan.') + ] = None + accounting_mode: AccountingMode | None = None + committed: Annotated[ + StrictFloat | None, + Field( + description='Legacy 3.x alias for net_committed after verified headroom-restoring adjustments.' + ), + ] = None + gross_committed: Annotated[ + StrictFloat | None, + Field( + description='Total authoritative commitment recorded from completed outcomes before adjustments.' + ), + ] = None + adjustments_reported: Annotated[ + StrictFloat | None, + Field( + description='Sum of all append-only adjustment amounts, including audit-only refunds, credits, and makegoods.' + ), + ] = None + adjustments_verified: Annotated[ + StrictFloat | None, + Field( + description='Sum of buyer-accepted economic reductions in plan currency; accepted makegoods contribute zero.' + ), + ] = None + net_cost: Annotated[ + StrictFloat | None, + Field( + description='Gross commitment minus verified economic reductions, independent of whether accounting_mode restores headroom.' + ), + ] = None + headroom_restored: Annotated[ + StrictFloat | None, + Field( + description='Sum of verified adjustments eligible to restore reusable headroom under accounting_mode.' + ), + ] = None + net_committed: Annotated[ + StrictFloat | None, + Field(description='Current plan obligation after verified headroom-restoring adjustments.'), + ] = None + ledger_committed: Annotated[ + StrictFloat | None, + Field(description='Current amount charged against plan headroom under accounting_mode.'), + ] = None + remaining: Annotated[StrictFloat | None, Field(description='Authorized minus committed.')] = ( + None + ) + utilization_pct: Annotated[ + StrictFloat | None, Field(description='Committed as a percentage of authorized.') + ] = None + + +class ChannelAllocation(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + committed: Annotated[ + StrictFloat | None, Field(description='Budget committed to this channel.') + ] = None + pct: Annotated[ + StrictFloat | None, Field(description="Channel's share of the authorized total budget.") + ] = None + + +class Statuses(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + approved: SchemaInt | None = None + denied: SchemaInt | None = None + conditions: SchemaInt | None = None + human_reviewed: Annotated[ + SchemaInt | None, + Field( + description='Supplementary count of checks that went through internal human review. These checks are also counted in approved or denied.' + ), + ] = None + + +class Escalation(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + check_id: Annotated[str, Field(description='The escalated governance check.')] + reason: Annotated[str, Field(description='Why it was escalated.')] + resolution: Annotated[ + str | None, + Field(description="How it was resolved (e.g., 'approved_by_human', 'rejected_by_human')."), + ] = None + resolved_at: Annotated[ + AwareDatetime | None, Field(description='ISO 8601 resolution timestamp.') + ] = None + + +class EscalationRateTrend(StrEnum): + increasing = 'increasing' + stable = 'stable' + declining = 'declining' + + +class Thresholds(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + escalation_rate_max: Annotated[ + StrictFloat | None, + Field( + description='Maximum acceptable escalation rate. A rate above this suggests policy miscalibration.', + ge=0.0, + le=1.0, + ), + ] = None + escalation_rate_min: Annotated[ + StrictFloat | None, + Field( + description='Minimum acceptable escalation rate. A rate below this may indicate eroding oversight.', + ge=0.0, + le=1.0, + ), + ] = None + auto_approval_rate_max: Annotated[ + StrictFloat | None, + Field(description='Maximum acceptable auto-approval rate.', ge=0.0, le=1.0), + ] = None + human_override_rate_max: Annotated[ + StrictFloat | None, + Field( + description="Maximum acceptable human override rate. A high rate suggests the governance agent's recommendations are poorly calibrated.", + ge=0.0, + le=1.0, + ), + ] = None + + +class DriftMetrics(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + escalation_rate: Annotated[ + StrictFloat | None, + Field(description='Fraction of checks that resulted in escalation.', ge=0.0, le=1.0), + ] = None + escalation_rate_trend: Annotated[ + EscalationRateTrend | None, + Field(description="Direction of escalation rate over the plan's lifetime."), + ] = None + auto_approval_rate: Annotated[ + StrictFloat | None, + Field( + description='Fraction of checks approved without human intervention.', ge=0.0, le=1.0 + ), + ] = None + human_override_rate: Annotated[ + StrictFloat | None, + Field( + description="Fraction of escalations where the human overrode the governance agent's recommendation.", + ge=0.0, + le=1.0, + ), + ] = None + mean_confidence: Annotated[ + StrictFloat | None, + Field( + description='Average confidence score across all findings. Present when findings include confidence scores.', + ge=0.0, + le=1.0, + ), + ] = None + thresholds: Annotated[ + Thresholds | None, + Field( + description='Organization-defined thresholds for drift metrics. When a metric crosses its threshold, the governance agent SHOULD include a finding on the next check. Set by the organization in governance agent configuration, echoed here for visibility.' + ), + ] = None + + +class Summary(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + checks_performed: Annotated[ + SchemaInt | None, Field(description='Total governance checks performed.') + ] = None + outcomes_reported: Annotated[ + SchemaInt | None, Field(description='Total outcomes reported.') + ] = None + adjustments_reported: Annotated[ + SchemaInt | None, Field(description='Total append-only adjustment records reported.') + ] = None + adjustments_verified: Annotated[ + SchemaInt | None, + Field( + description='Count of adjustment records accepted by the plan owner, including makegoods.' + ), + ] = None + statuses: Annotated[ + Statuses | None, Field(description='Count of each governance check status.') + ] = None + findings_count: Annotated[ + SchemaInt | None, Field(description='Total findings across all checks and outcomes.') + ] = None + escalations: Annotated[ + list[Escalation] | None, Field(description='All escalations and their resolutions.') + ] = None + drift_metrics: Annotated[ + DriftMetrics | None, + Field( + description='Aggregate governance metrics for detecting oversight drift. A declining escalation rate may indicate well-calibrated governance or eroding human oversight -- surfacing the trend lets the organization make that judgment.' + ), + ] = None + + +class Type(StrEnum): + check = 'check' + outcome = 'outcome' + adjustment = 'adjustment' + + +class CheckType(StrEnum): + intent = 'intent' + execution = 'execution' + + +class Finding(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + category_id: str + policy_id: str | None = None + source_plan_id: Annotated[ + str | None, + Field( + description='Plan whose policy triggered this finding when the audit entry aggregates findings across plans.' + ), + ] = None + severity: escalation_severity.EscalationSeverity + explanation: str + details: Annotated[ + dict[str, Any] | None, + Field( + description='Structured finding details retained exactly as emitted by check_governance.' + ), + ] = None + confidence: Annotated[StrictFloat | None, Field(ge=0.0, le=1.0)] = None + uncertainty_reason: Annotated[ + str | None, + Field( + description='Reason the finding carries less than full confidence, retained exactly as emitted by check_governance.' + ), + ] = None + attestation_reference_digest: Annotated[ + str | None, + Field( + description='reference_digest of the runtime attestation evaluation this finding relied on.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + + +class ReportingPeriod(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + start: AwareDatetime + end: AwareDatetime + + +class CanonicalPayload(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + seller_reference: str + delivery_metrics: dict[str, Any] + + +class DeliveryStatement(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + statement_id: str + statement_digest: Annotated[str, Field(pattern='^sha256:[a-f0-9]{64}$')] + sequence: Annotated[SchemaInt, Field(ge=1)] + issued_at: AwareDatetime + seller_reference: str + cumulative_spend: Annotated[StrictFloat, Field(ge=0.0)] + currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] + reporting_period: ReportingPeriod + canonical_payload: Annotated[ + CanonicalPayload, + Field( + description='Exact retained payload used to verify statement_digest after removing delivery_metrics.statement_digest.' + ), + ] + + +class AdjustmentType(StrEnum): + decommitment = 'decommitment' + refund = 'refund' + credit = 'credit' + makegood = 'makegood' + + +class Amount(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + amount: Annotated[StrictFloat, Field(gt=0.0)] + currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] + + +class Source(StrEnum): + seller_statement_copy = 'seller_statement_copy' + buyer_measurement = 'buyer_measurement' + + +class Delivery(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + observation_id: Annotated[ + str | None, + Field( + description='Stable buyer-issued identifier for this observation.', + max_length=255, + min_length=1, + ), + ] = None + source: Annotated[ + Source | None, + Field( + description='Whether the buyer is forwarding the seller statement it received or reporting an independently measured observation. A forwarded seller statement is not independent evidence.' + ), + ] = None + observed_at: AwareDatetime | None = None + seller_statement_id: Annotated[ + str | None, + Field( + description='Seller statement identifier as received by the buyer. Required for seller_statement_copy.' + ), + ] = None + seller_statement_digest: Annotated[ + str | None, + Field( + description='Digest received with the seller statement. Governance compares it to the seller-authenticated canonical digest.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + reporting_period: Annotated[ + ReportingPeriod | None, + Field(description='Start and end timestamps for the reporting window.'), + ] = None + cumulative_spend: Annotated[ + StrictFloat | None, + Field(description='Buyer-attributed cumulative spend for the governed action.', ge=0.0), + ] = None + currency: Annotated[str | None, Field(pattern='^[A-Z]{3}$')] = None + period_closed: Annotated[ + StrictBool | None, + Field( + description='Buyer-declared operational closure of this governance reporting period. When true, governance freezes the period after recording this observation. An unresolved mismatch becomes closed_unresolved and no longer blocks later governance. Closure is not final billing or commercial settlement.' + ), + ] = False + impressions: Annotated[SchemaInt | None, Field(ge=0)] = None + + +class DeliveryReconciliationStatus(StrEnum): + consistent = 'consistent' + measurement_variance = 'measurement_variance' + disputed = 'disputed' + unmatched = 'unmatched' + closed_unresolved = 'closed_unresolved' + + +class DeliveryPeriodState(StrEnum): + open = 'open' + closed = 'closed' + + +class AdjustmentState(StrEnum): + reported = 'reported' + verified = 'verified' + disputed = 'disputed' + + +class EvidenceType(StrEnum): + decommitment_agreement = 'decommitment_agreement' + refund_settlement = 'refund_settlement' + credit_note = 'credit_note' + makegood_agreement = 'makegood_agreement' + + +class Evidence(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + evidence_id: str + evidence_type: EvidenceType + digest: Annotated[str, Field(pattern='^sha256:[a-f0-9]{64}$')] + issued_at: AwareDatetime + + +class DeliveryReportingPeriod(ReportingPeriod): + pass + + +class GovernedAction(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + governance_context: Annotated[ + str, Field(description="Governance context correlating this action's lifecycle.") + ] + purchase_type: Annotated[ + purchase_type_1.PurchaseType, Field(description='Type of financial commitment.') + ] + status: Annotated[Status, Field(description='Action status.')] + committed: Annotated[ + StrictFloat, Field(description='Gross authoritative commitment recorded for this action.') + ] + adjustments_reported: Annotated[ + StrictFloat | None, + Field(description='Sum of all append-only adjustment amounts for this action.'), + ] = None + adjustments_verified: Annotated[ + StrictFloat | None, Field(description='Buyer-accepted economic reductions for this action.') + ] = None + net_cost: Annotated[ + StrictFloat | None, + Field(description='Gross action commitment minus verified economic reductions.'), + ] = None + headroom_restored: Annotated[ + StrictFloat | None, + Field( + description='Verified adjustments eligible to restore headroom under the plan accounting mode.' + ), + ] = None + net_committed: Annotated[ + StrictFloat | None, + Field(description='Current ledger obligation for this action after restored headroom.'), + ] = None + seller_reported_spend: Annotated[StrictFloat | None, Field(ge=0.0)] = None + buyer_observed_spend: Annotated[StrictFloat | None, Field(ge=0.0)] = None + delivery_reporting_period: Annotated[ + DeliveryReportingPeriod | None, + Field(description='Current seller statement period summarized for this governed action.'), + ] = None + conservative_exposure: Annotated[ + StrictFloat | None, + Field( + description='Greater of seller-reported and buyer-observed spend while evidence is unresolved.', + ge=0.0, + ), + ] = None + delivery_reconciliation_status: DeliveryReconciliationStatus | None = None + delivery_period_state: Annotated[ + DeliveryPeriodState | None, + Field( + description='Whether the current governance reporting period remains actionable. Closure is not final billing.' + ), + ] = None + check_count: Annotated[ + SchemaInt, Field(description='Number of governance checks performed for this action.') + ] + seller_reference: Annotated[ + str | None, + Field( + description="The seller's identifier for the resource (e.g., media_buy_id, rights_grant_id). Present when reported via report_plan_outcome." + ), + ] = None + + +class RuntimeAttestation(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + reference: attestation_reference.AttestationReference + evaluation: attestation_evaluation.AttestationEvaluation + + +class Entry(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + id: Annotated[str, Field(description='Entry identifier.')] + type: Annotated[Type, Field(description='Entry type.')] + timestamp: Annotated[AwareDatetime, Field(description='ISO 8601 timestamp.')] + plan_id: Annotated[ + str | None, + Field( + description='Plan this entry belongs to. Present when querying multiple plans or a portfolio.' + ), + ] = None + caller: Annotated[ + AnyUrl | None, + Field( + description='URL of the agent that made the request. Resolved from the credentials used on the governance callback.' + ), + ] = None + tool: Annotated[str | None, Field(description='The AdCP tool (present for check entries).')] = ( + None + ) + verdict: Annotated[ + governance_decision.GovernanceDecision | None, + Field( + description='Governance verdict (present for check entries). Renamed from `status` in 3.1 alongside check-governance-response for vocabulary consistency.' + ), + ] = None + check_type: Annotated[ + CheckType | None, + Field( + description='Whether the check was an intent check (orchestrator) or execution check (seller). Inferred from the fields present on the original check request. Present for check entries.' + ), + ] = None + mode: Annotated[ + governance_mode.GovernanceMode | None, + Field( + description="Governance mode active at the moment this specific check was evaluated. Governance agents SHOULD populate this field on check entries, recording the mode from their runtime configuration at the moment check_governance was processed — not derived from a plan field. This is a per-check value: if the operator changes mode between checks for the same governed action, each entry records the mode active for that entry. A future `governed_actions[].mode` field would describe the action's current mode, which may differ from the most recent entry's `mode` if the plan has since been re-synced. Absent for outcome entries and for pre-3.1 governance agents that do not surface mode on audit responses." + ), + ] = None + explanation: Annotated[ + str | None, + Field( + description='Human-readable explanation of the governance decision (present for check entries).' + ), + ] = None + policies_evaluated: Annotated[ + list[str] | None, + Field( + description="Policy IDs evaluated during this check. Includes registry policy IDs (resolved via the policy registry) and any inline `policy_id`s declared in the plan's `custom_policies`. Present for check entries." + ), + ] = None + categories_evaluated: Annotated[ + list[str] | None, + Field( + description="Governance categories evaluated (e.g., 'budget_authority', 'regulatory_compliance'). Present for check entries." + ), + ] = None + findings: Annotated[ + list[Finding] | None, + Field( + description='Findings from this check or outcome. Same structure as check_governance response findings.' + ), + ] = None + delivery_statement: Annotated[ + DeliveryStatement | None, + Field(description='Canonical seller statement retained on a delivery check entry.'), + ] = None + outcome: Annotated[ + outcome_type.OutcomeType | None, + Field(description='Outcome type (present for outcome entries).'), + ] = None + error: Annotated[ + reported_outcome_error.ReportedOutcomeError | None, + Field( + description='Buyer-attributed error retained from a failed report_plan_outcome call. This is a copy or classification supplied by the reporter, not authenticated seller evidence.' + ), + ] = None + outcome_id: Annotated[ + str | None, Field(description='Source completed outcome (present for adjustment entries).') + ] = None + seller_adjustment_id: Annotated[ + str | None, + Field( + description='Seller-issued source adjustment identifier (present for adjustment entries).' + ), + ] = None + adjustment_type: Annotated[ + AdjustmentType | None, + Field(description='Commercial adjustment type (present for adjustment entries).'), + ] = None + amount: Annotated[ + Amount | None, + Field(description='Adjustment amount and currency (present for adjustment entries).'), + ] = None + headroom_restored: Annotated[ + StrictFloat | None, + Field( + description='Current-obligation headroom restored by this adjustment. Zero for audit-only types.', + ge=0.0, + ), + ] = None + reason: Annotated[str | None, Field(description='Seller-supplied adjustment reason.')] = None + effective_at: Annotated[ + AwareDatetime | None, Field(description="When the seller's adjustment became effective.") + ] = None + committed_budget: Annotated[ + StrictFloat | None, + Field( + description='Governance-authorized budget reserved (present for completed outcome entries).' + ), + ] = None + reported_committed_budget: Annotated[ + StrictFloat | None, + Field( + description='Caller-reported seller amount retained for reconciliation. Never ledger authority.', + ge=0.0, + ), + ] = None + seller_reference: Annotated[ + str | None, Field(description='Seller resource identifier retained on an outcome entry.') + ] = None + delivery: Annotated[ + Delivery | None, + Field( + description='Buyer-attributed observation retained as audit evidence on a delivery outcome. It never overwrites the seller statement.' + ), + ] = None + governance_context: Annotated[ + str | None, + Field( + description='Governance context for this entry (present for check and outcome entries).' + ), + ] = None + plan_hash: Annotated[ + str | None, + Field( + description='Audit-layer binding to the plan revision this attestation was evaluated over — base64url_no_pad(SHA-256(JCS(plan_payload))) per Plan binding and audit in the campaign-governance specification. Present on check entries. Auditors and buyer-side compliance verify by recomputing over the retained plan revision and byte-comparing the decoded 32-byte digests.', + pattern='^[A-Za-z0-9_-]{43}$', + ), + ] = None + runtime_attestations: Annotated[ + list[RuntimeAttestation] | None, + Field( + description='Portable attestation presentations and their evaluator-of-record results retained as ordered pairs for this check entry. Auditors recompute each evaluation.reference_digest from JCS(reference), then recompute runtime_attestation_binding_digest from the ordered evaluations and attestation-bound findings.', + max_length=10, + min_length=1, + ), + ] = None + runtime_attestation_binding_digest: Annotated[ + str | None, + Field( + description="Binding digest copied from the check response and signed governance_context. Auditors recompute it from this entry's ordered runtime_attestations[].evaluation values plus findings carrying attestation_reference_digest after verifying each paired reference digest.", + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + purchase_type: Annotated[ + purchase_type_1.PurchaseType | None, Field(description='Purchase type for this entry.') + ] = None + outcome_status: Annotated[ + str | None, Field(description='Outcome status (present for outcome entries).') + ] = None + delivery_reconciliation_status: DeliveryReconciliationStatus | None = None + delivery_period_state: Annotated[ + DeliveryPeriodState | None, + Field(description='Operational governance-window state for this delivery observation.'), + ] = None + adjustment_state: AdjustmentState | None = None + verified_amount: Annotated[StrictFloat | None, Field(ge=0.0)] = None + evidence: Evidence | None = None + reviewed_by: AnyUrl | None = None + reviewed_at: AwareDatetime | None = None + review_reason: str | None = None + + +class Plan(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + plan_id: Annotated[str, Field(description='Plan identifier.')] + plan_version: Annotated[SchemaInt, Field(description='Current plan version.')] + status: Annotated[Status, Field(description='Plan lifecycle status.')] + budget: Annotated[Budget, Field(description='Budget state.')] + channel_allocation: Annotated[ + dict[str, ChannelAllocation] | None, + Field(description='Current channel mix. Keyed by channel ID.'), + ] = None + summary: Annotated[Summary, Field(description='Aggregate validation and outcome statistics.')] + entries: Annotated[ + list[Entry] | None, + Field(description='Ordered audit trail. Only present when include_entries is true.'), + ] = None + governed_actions: Annotated[ + list[GovernedAction], + Field( + description="Per-action breakdown joined by the governance agent's stable opaque action binding. governance_context is the original intent context exposed for correlation; lifecycle tokens may differ because each approval is freshly signed." + ), + ] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.governance.get_plan_audit_logs_response import ( - AccountingMode, - AdjustmentState, - AdjustmentType, - Amount, - Budget, - CanonicalPayload, - ChannelAllocation, - CheckType, - Delivery, - DeliveryPeriodState, - DeliveryReconciliationStatus, - DeliveryReportingPeriod, - DeliveryStatement, - DriftMetrics, - Entry, - Escalation, - EscalationRateTrend, - Evidence, - EvidenceType, - Finding, - GetPlanAuditLogsResponse, - GovernedAction, - Plan, - ReportingPeriod, - RuntimeAttestation, - Source, - Status, - Statuses, - Summary, - Thresholds, - Type, -) - -# Explicit exports -__all__ = [ - "AccountingMode", - "AdjustmentState", - "AdjustmentType", - "Amount", - "Budget", - "CanonicalPayload", - "ChannelAllocation", - "CheckType", - "Delivery", - "DeliveryPeriodState", - "DeliveryReconciliationStatus", - "DeliveryReportingPeriod", - "DeliveryStatement", - "DriftMetrics", - "Entry", - "Escalation", - "EscalationRateTrend", - "Evidence", - "EvidenceType", - "Finding", - "GetPlanAuditLogsResponse", - "GovernedAction", - "Plan", - "ReportingPeriod", - "RuntimeAttestation", - "Source", - "Status", - "Statuses", - "Summary", - "Thresholds", - "Type", -] +class GetPlanAuditLogsResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + plans: Annotated[list[Plan], Field(description='Audit data for each requested plan.')] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/governance/policy_category_definition.py b/src/adcp/types/domains/governance/policy_category_definition.py index 6e09ef28d..248d8c0ac 100644 --- a/src/adcp/types/domains/governance/policy_category_definition.py +++ b/src/adcp/types/domains/governance/policy_category_definition.py @@ -1,22 +1,105 @@ -"""Types declared by the AdCP ``governance/policy_category_definition`` schema. +# generated by datamodel-codegen: +# filename: governance/policy_category_definition.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.governance.policy_category_definition import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import restricted_attribute + + +class Facet(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + facet_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] + name: Annotated[str, Field(min_length=1)] + description: Annotated[str, Field(min_length=1)] + + +class RegulatoryFramework(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + name: Annotated[ + str, Field(description="Name of the regulation or standard (e.g., 'US COPPA').") + ] + jurisdictions: Annotated[ + list[str] | None, + Field(description='ISO 3166-1 alpha-2 codes where this framework applies.'), + ] = None + summary: Annotated[ + str, Field(description='Brief summary of what the framework requires or prohibits.') + ] + policy_ids: Annotated[ + list[str] | None, Field(description='Registry policy IDs that implement this framework.') + ] = None -from adcp.types.generated_poc.governance.policy_category_definition import ( - Facet, - PolicyCategoryDefinition, - RegulatoryFramework, -) -# Explicit exports -__all__ = ["Facet", "PolicyCategoryDefinition", "RegulatoryFramework"] +class PolicyCategoryDefinition(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + category_id: Annotated[ + str, + Field( + description='Unique identifier for this category. Used in plan.policy_categories, signal-definition.policy_categories, and policy-entry.policy_categories.', + pattern='^[a-z][a-z0-9_]*$', + ), + ] + name: Annotated[ + str, Field(description="Human-readable name (e.g., 'Children-Directed Content').") + ] + description: Annotated[ + str, + Field( + description='What this category covers. Defines the boundary — what campaigns or data fall under this category.' + ), + ] + facets: Annotated[ + list[Facet] | None, + Field( + description='Registry-defined distinctions within the category that materially affect policy applicability or seller acceptance. Facets refine a category without creating a new top-level policy category.', + min_length=1, + ), + ] = None + regulatory_frameworks: Annotated[ + list[RegulatoryFramework] | None, + Field( + description='Key regulations and standards grouped under this category. Governance agents use this to resolve specific policies from the registry.' + ), + ] = None + restricted_attributes: Annotated[ + list[restricted_attribute.RestrictedAttribute] | None, + Field( + description="Restricted attribute categories that regulations in this category prohibit for targeting. Governance agents enforce these when the category is active on a plan — if a plan declares policy_categories: ['fair_housing'], the governance agent restricts targeting on these attributes." + ), + ] = None + requires_human_review: Annotated[ + StrictBool | None, + Field( + description='When true, any plan declaring this category MUST set plan.human_review_required = true. Use for regulatory regimes that mandate human oversight under GDPR Art 22 or EU AI Act Annex III — fair_housing, fair_lending, fair_employment, pharmaceutical_advertising, and similar high-risk categories. Category-level setting applies to all policies and plans referencing it; policies can override on policy-entry.requires_human_review. Effective immediately regardless of individual policy `effective_date` fields.' + ), + ] = False + industries: Annotated[ + list[str] | None, + Field( + description="Industries where this category commonly applies (e.g., 'pharmaceutical' for age_restricted). Governance agents MAY suggest relevant categories when a plan's brand industry matches but no policy_categories are declared." + ), + ] = None + guidance: Annotated[ + str | None, + Field( + description='Implementation notes for governance agents. Edge cases, disambiguation, and common pitfalls.' + ), + ] = None + related_categories: Annotated[ + list[str] | None, + Field( + description="Categories that frequently co-occur (e.g., 'children_directed' often appears with 'age_restricted')." + ), + ] = None diff --git a/src/adcp/types/domains/governance/policy_entry.py b/src/adcp/types/domains/governance/policy_entry.py index 1abb23afc..ebf6dff1f 100644 --- a/src/adcp/types/domains/governance/policy_entry.py +++ b/src/adcp/types/domains/governance/policy_entry.py @@ -1,24 +1,194 @@ -"""Types declared by the AdCP ``governance/policy_entry`` schema. +# generated by datamodel-codegen: +# filename: governance/policy_entry.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.governance.policy_entry import +from datetime import date +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import ext as ext_1 +from ..enums import channels as channels_1 +from ..enums import governance_domain, policy_category, policy_enforcement +from ..media_buy import acceptance_policy_profile + + +class Source(StrEnum): + registry = 'registry' + inline = 'inline' + + +class Issuer(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + domain: Annotated[ + str, + Field( + description='Lowercase registrable or organizational domain used as the stable issuer identifier.', + pattern='^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z]{2,63}$', + ), + ] + name: Annotated[str | None, Field(min_length=1)] = None + + +class Exemplar(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + scenario: Annotated[ + str, + Field(description='A concrete scenario describing an advertising action or configuration.'), + ] + explanation: Annotated[str, Field(description='Why this scenario passes or fails the policy.')] + + +class Exemplars(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + pass_: Annotated[ + list[Exemplar] | None, + Field(alias='pass', description='Scenarios that comply with this policy.'), + ] = None + fail: Annotated[ + list[Exemplar] | None, Field(description='Scenarios that violate this policy.') + ] = None -from adcp.types.generated_poc.governance.policy_entry import ( - Exemplar, - Exemplars, - Issuer, - PolicyEntry, - Source, -) -# Explicit exports -__all__ = ["Exemplar", "Exemplars", "Issuer", "PolicyEntry", "Source"] +class PolicyEntry(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + policy_id: Annotated[ + str, + Field( + description='Unique identifier for this policy. Registry-published ids are canonical (e.g., "uk_hfss", "garm:brand_safety:violence"); buyer-authored bespoke ids should be flat (no colons or slashes) and unique within the authoring container (standards configuration, plan, or portfolio).' + ), + ] + source: Annotated[ + Source | None, + Field( + description="Origin of this policy. 'registry' = published to the shared AdCP policy registry with full regulatory metadata. 'inline' = authored bespoke for a specific standards configuration, plan, or portfolio. Defaults to 'inline'. Governance agents MUST set 'registry' when publishing to the registry. Within AdCP *task* payloads (every `$ref` to this schema in a request or response), the field is always 'inline' — registry entries are served by the policy registry API, not embedded in task traffic. The x-entity annotation on `policy_id` assumes the task-payload invariant; if a future task schema adopts registry-publishing, split the annotation accordingly (see issue #2685)." + ), + ] = Source.inline + version: Annotated[ + str | None, + Field( + description='Semver version string (e.g., "1.0.0"). Incremented when policy content changes. Optional for inline bespoke policies — defaults to "1.0.0". SHOULD be provided for registry-published policies.' + ), + ] = None + name: Annotated[ + str | None, + Field( + description='Human-readable name (e.g., "UK HFSS Restrictions"). Optional for inline bespoke policies — servers MAY default to policy_id.' + ), + ] = None + description: Annotated[ + str | None, Field(description='Brief summary of what this policy covers.', max_length=500) + ] = None + category: Annotated[ + policy_category.PolicyCategory | None, + Field( + description='The nature of the obligation: regulation (legal requirement) or standard (best practice). Optional for inline bespoke policies — defaults to "standard".' + ), + ] = None + enforcement: Annotated[ + policy_enforcement.PolicyEnforcementLevel, + Field( + description='How governance agents treat violations. Regulations are typically "must"; standards are typically "should".' + ), + ] + requires_human_review: Annotated[ + StrictBool | None, + Field( + description='When true, plans subject to this policy MUST set plan.human_review_required = true. Use for policies that mandate human oversight of decisions affecting data subjects — e.g., GDPR Article 22 (solely automated decisions with legal or similarly significant effects) and EU AI Act Annex III high-risk categories (credit, insurance pricing, recruitment, housing allocation). Governance agents MUST escalate any plan action whose resolved policies include requires_human_review: true. Unlike `enforcement`, this flag applies as soon as the policy is resolved — it is NOT gated by `effective_date`. Art 22 GDPR and similar foundational obligations may predate an AI-Act-specific effective date; the human-review requirement fires regardless.' + ), + ] = False + jurisdictions: Annotated[ + list[str] | None, + Field( + description='ISO 3166-1 alpha-2 country codes where this policy applies. Empty array means the policy is not jurisdiction-specific.' + ), + ] = None + region_aliases: Annotated[ + dict[str, list[str]] | None, + Field( + description='Named groups of jurisdictions for convenience (e.g., {"EU": ["AT","BE","BG",...]}). Governance agents expand aliases when matching against a plan\'s target jurisdictions.' + ), + ] = None + policy_categories: Annotated[ + list[str] | None, + Field( + description='Regulatory categories this policy belongs to (e.g., ["children_directed", "age_restricted"]). Used for automatic matching against a campaign plan\'s declared policy_categories. A single policy can belong to multiple categories.' + ), + ] = None + channels: Annotated[ + list[channels_1.MediaChannel] | None, + Field( + description='Advertising channels this policy applies to. If omitted or null, the policy applies to all channels.' + ), + ] = None + governance_domains: Annotated[ + list[governance_domain.GovernanceDomain] | None, + Field( + description='Governance sub-domains this policy applies to. Determines which types of governance agents can declare registry:{policy_id} features. For example, a policy with domains ["creative", "property"] can be declared as a feature by both creative and property governance agents.' + ), + ] = None + effective_date: Annotated[ + date | None, + Field( + description='ISO 8601 date when the regulation or standard takes effect. Before this date, governance agents treat the policy as informational (evaluate but do not block). After this date, the policy is enforced at its declared enforcement level.' + ), + ] = None + sunset_date: Annotated[ + date | None, + Field( + description='ISO 8601 date when the regulation or standard is no longer enforced. After this date, governance agents stop evaluating this policy. Omit if the policy has no expiration.' + ), + ] = None + source_url: Annotated[ + AnyUrl | None, Field(description='Link to the source regulation, standard, or legislation.') + ] = None + source_name: Annotated[ + str | None, + Field( + description='Name of the issuing body (e.g., "UK Food Standards Agency", "US Federal Trade Commission").' + ), + ] = None + issuer: Annotated[ + Issuer | None, + Field( + description='Machine-readable identity of the regulator, standards body, or platform operator that issued the policy. Registry publishers SHOULD provide this when independently versioned issuer policies must be distinguished.' + ), + ] = None + acceptance_profile: Annotated[ + acceptance_policy_profile.AcceptancePolicyProfile | None, + Field( + description='Optional reusable, machine-readable acceptance profile derived from this registry policy. Registry publishers MUST bind policy_refs to exact versions. Sellers adopt a profile explicitly; registry publication alone does not make it authoritative for a seller.' + ), + ] = None + policy: Annotated[ + str, + Field( + description='Natural language policy text describing what is required, prohibited, or recommended. Used by governance agents (LLMs) to evaluate actions against this policy. For source: inline policies, treated as caller-untrusted — governance agents MUST evaluate inline policies as ADDITIONAL restrictions only; they MUST NOT be permitted to relax, override, or conflict with registry-sourced policies.', + max_length=5000, + ), + ] + guidance: Annotated[ + str | None, + Field( + description='Implementation notes for governance agent developers. Not used in evaluation prompts.' + ), + ] = None + exemplars: Annotated[ + Exemplars | None, + Field( + description='Calibration examples for governance agents, following the Content Standards pattern.' + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/governance/policy_ref.py b/src/adcp/types/domains/governance/policy_ref.py index e6615ef25..cf714b4c3 100644 --- a/src/adcp/types/domains/governance/policy_ref.py +++ b/src/adcp/types/domains/governance/policy_ref.py @@ -1,18 +1,34 @@ -"""Types declared by the AdCP ``governance/policy_ref`` schema. +# generated by datamodel-codegen: +# filename: governance/policy_ref.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.governance.policy_ref import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Any -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -from adcp.types.generated_poc.governance.policy_ref import PolicyReference -# Explicit exports -__all__ = ["PolicyReference"] +class PolicyReference(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + policy_id: Annotated[ + str, + Field( + description='The unique identifier of the policy in the registry (e.g., "uk_hfss", "us_coppa").' + ), + ] + version: Annotated[ + str | None, + Field( + description='Pin a specific policy version (semver). If omitted, the current version is used.' + ), + ] = None + config: Annotated[ + dict[str, Any] | None, + Field( + description="Brand-specific parameter overrides for configurable policies. The accepted shape depends on the policy's config_schema." + ), + ] = None diff --git a/src/adcp/types/domains/governance/report_plan_adjustment_request.py b/src/adcp/types/domains/governance/report_plan_adjustment_request.py index 7a5112b13..fcb5eeb10 100644 --- a/src/adcp/types/domains/governance/report_plan_adjustment_request.py +++ b/src/adcp/types/domains/governance/report_plan_adjustment_request.py @@ -1,34 +1,160 @@ -"""Types declared by the AdCP ``governance/report_plan_adjustment_request`` schema. +# generated by datamodel-codegen: +# filename: governance/report_plan_adjustment_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.governance.report_plan_adjustment_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated + +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat + +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope + + +class Action(StrEnum): + report = 'report' + review = 'review' + + +class Decision(StrEnum): + accept = 'accept' + dispute = 'dispute' + + +class AdjustmentType(StrEnum): + decommitment = 'decommitment' + refund = 'refund' + credit = 'credit' + makegood = 'makegood' + + +class Amount(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + amount: Annotated[ + StrictFloat, + Field( + description='Finite positive amount. Cumulative adjustments for an outcome cannot exceed its authoritative commitment.', + gt=0.0, + ), + ] + currency: Annotated[ + str, + Field( + description='Three-letter uppercase currency code matching the plan currency.', + pattern='^[A-Z]{3}$', + ), + ] + + +class EvidenceType(StrEnum): + decommitment_agreement = 'decommitment_agreement' + refund_settlement = 'refund_settlement' + credit_note = 'credit_note' + makegood_agreement = 'makegood_agreement' + + +class Evidence(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + evidence_id: Annotated[ + str, + Field( + description="Immutable identifier in the authenticated seller's namespace. Reuse with different adjustment evidence is a conflict.", + max_length=255, + min_length=1, + ), + ] + evidence_type: EvidenceType + digest: Annotated[str, Field(pattern='^sha256:[a-f0-9]{64}$')] + issued_at: AwareDatetime -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.governance.report_plan_adjustment_request import ( - Action, - AdjustmentType, - Amount, - Decision, - Evidence, - EvidenceType, - ReportPlanAdjustmentRequest, -) - -# Explicit exports -__all__ = [ - "Action", - "AdjustmentType", - "Amount", - "Decision", - "Evidence", - "EvidenceType", - "ReportPlanAdjustmentRequest", -] +class ReportPlanAdjustmentRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + action: Annotated[ + Action, + Field( + description='report is seller-authenticated and creates a non-authoritative record; review is plan-owner-authenticated and accepts or disputes it.' + ), + ] + plan_id: Annotated[str, Field(description='Plan containing the source outcome.', min_length=1)] + outcome_id: Annotated[ + str | None, + Field( + description='Completed outcome whose authoritative commitment is being adjusted.', + min_length=1, + ), + ] = None + adjustment_id: Annotated[ + str | None, Field(description='Adjustment to accept or dispute. Required for review.') + ] = None + decision: Annotated[ + Decision | None, + Field( + description='Buyer review decision. Acceptance is blocked while delivery evidence for the governed action is disputed in an open governance period; a historical closed_unresolved period is audit evidence, not a billing determination.' + ), + ] = None + seller_reference: Annotated[ + str | None, + Field( + description='Seller resource identifier. Must exactly match the reference retained on the source outcome.', + max_length=255, + min_length=1, + ), + ] = None + seller_adjustment_id: Annotated[ + str | None, + Field( + description="Stable identifier of the adjustment in the authenticated seller's system.", + max_length=255, + min_length=1, + ), + ] = None + adjustment_type: Annotated[ + AdjustmentType | None, + Field( + description='Commercial meaning. Verified decommitments restore headroom in every mode; verified refunds and credits restore it only in verified_net_cost mode; makegoods never restore cash headroom.' + ), + ] = None + amount: Annotated[ + Amount | None, Field(description='Positive adjustment amount in the plan currency.') + ] = None + reason: Annotated[ + str | None, + Field( + description='Human-readable reason retained in the audit trail.', + max_length=1000, + min_length=1, + ), + ] = None + effective_at: Annotated[ + AwareDatetime | None, Field(description="When the seller's adjustment became effective.") + ] = None + evidence: Annotated[ + Evidence | None, + Field( + description='Integrity-bound commercial source record supplied by the seller. Its evidence_type must correspond to adjustment_type.' + ), + ] = None + idempotency_key: Annotated[ + str, + Field( + description='Caller-generated retry key. Exact replays return the original report or review; reuse with another payload is rejected.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/governance/report_plan_adjustment_response.py b/src/adcp/types/domains/governance/report_plan_adjustment_response.py index 5c4df3173..2ce2ee41d 100644 --- a/src/adcp/types/domains/governance/report_plan_adjustment_response.py +++ b/src/adcp/types/domains/governance/report_plan_adjustment_response.py @@ -1,32 +1,91 @@ -"""Types declared by the AdCP ``governance/report_plan_adjustment_response`` schema. +# generated by datamodel-codegen: +# filename: governance/report_plan_adjustment_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.governance.report_plan_adjustment_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated + +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictBool, StrictFloat + +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope + + +class AdjustmentState(StrEnum): + reported = 'reported' + verified = 'verified' + disputed = 'disputed' + + +class AdjustmentType(StrEnum): + decommitment = 'decommitment' + refund = 'refund' + credit = 'credit' + makegood = 'makegood' + + +class Amount(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + amount: Annotated[StrictFloat, Field(gt=0.0)] + currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] + + +class AccountingMode(StrEnum): + gross_commitment = 'gross_commitment' + verified_net_cost = 'verified_net_cost' + + +class PlanSummary(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + accounting_mode: AccountingMode + gross_committed: Annotated[StrictFloat, Field(ge=0.0)] + adjustments_reported: Annotated[StrictFloat, Field(ge=0.0)] + adjustments_verified: Annotated[StrictFloat, Field(ge=0.0)] + net_cost: Annotated[StrictFloat, Field(ge=0.0)] + headroom_restored: Annotated[StrictFloat, Field(ge=0.0)] + ledger_committed: Annotated[StrictFloat, Field(ge=0.0)] + net_committed: Annotated[StrictFloat, Field(ge=0.0)] + budget_remaining: StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.governance.report_plan_adjustment_response import ( - AccountingMode, - AdjustmentState, - AdjustmentType, - Amount, - PlanSummary, - ReportPlanAdjustmentResponse, -) - -# Explicit exports -__all__ = [ - "AccountingMode", - "AdjustmentState", - "AdjustmentType", - "Amount", - "PlanSummary", - "ReportPlanAdjustmentResponse", -] +class ReportPlanAdjustmentResponse(AdcpResponse, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + adjustment_id: Annotated[ + str, Field(description='Governance-agent identifier for the adjustment record.') + ] + adjustment_state: Annotated[ + AdjustmentState, + Field( + description='reported has seller evidence but no buyer decision; verified was accepted by the plan owner; disputed was rejected by the plan owner. Only verified records can affect net cost or headroom.' + ), + ] + adjustment_type: AdjustmentType + amount: Amount + headroom_restored: Annotated[ + StrictFloat, + Field( + description='Amount by which current ledger commitment was reduced under the plan accounting mode.', + ge=0.0, + ), + ] + plan_summary: PlanSummary + replayed: Annotated[ + StrictBool | None, + Field( + description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. `replayed` appears only when the request actually resolved through the idempotency cache. Pure reads may ignore an optional `idempotency_key`; when a seller voluntarily caches keyed reads, those responses use the same replay indicator and full cache contract." + ), + ] = False + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/governance/report_plan_outcome_request.py b/src/adcp/types/domains/governance/report_plan_outcome_request.py index 8353d5b7c..9ce76b49d 100644 --- a/src/adcp/types/domains/governance/report_plan_outcome_request.py +++ b/src/adcp/types/domains/governance/report_plan_outcome_request.py @@ -1,32 +1,184 @@ -"""Types declared by the AdCP ``governance/report_plan_outcome_request`` schema. +# generated by datamodel-codegen: +# filename: governance/report_plan_outcome_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.governance.report_plan_outcome_request import +from adcp.types.base import AdcpRequest, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated + +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat + +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core import planned_delivery as planned_delivery_1 +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import outcome_type +from ..enums import purchase_type as purchase_type_1 +from . import reported_outcome_error + + +class Package(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + budget: Annotated[StrictFloat | None, Field(ge=0.0)] = None + + +class Source(StrEnum): + seller_statement_copy = 'seller_statement_copy' + buyer_measurement = 'buyer_measurement' + + +class ReportingPeriod(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + start: AwareDatetime + end: AwareDatetime + + +class Delivery(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + observation_id: Annotated[ + str, + Field( + description='Stable buyer-issued identifier for this observation.', + max_length=255, + min_length=1, + ), + ] + source: Annotated[ + Source, + Field( + description='Whether the buyer is forwarding the seller statement it received or reporting an independently measured observation. A forwarded seller statement is not independent evidence.' + ), + ] + observed_at: AwareDatetime + seller_statement_id: Annotated[ + str | None, + Field( + description='Seller statement identifier as received by the buyer. Required for seller_statement_copy.' + ), + ] = None + seller_statement_digest: Annotated[ + str | None, + Field( + description='Digest received with the seller statement. Governance compares it to the seller-authenticated canonical digest.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] = None + reporting_period: Annotated[ + ReportingPeriod, Field(description='Start and end timestamps for the reporting window.') + ] + cumulative_spend: Annotated[ + StrictFloat, + Field(description='Buyer-attributed cumulative spend for the governed action.', ge=0.0), + ] + currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] + period_closed: Annotated[ + StrictBool | None, + Field( + description='Buyer-declared operational closure of this governance reporting period. When true, governance freezes the period after recording this observation. An unresolved mismatch becomes closed_unresolved and no longer blocks later governance. Closure is not final billing or commercial settlement. Only the authenticated plan owner may set this to true; the governance agent MUST reject it from any other authenticated reporter.' + ), + ] = False + impressions: Annotated[SchemaInt | None, Field(ge=0)] = None + + +class SellerResponse(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + seller_reference: Annotated[ + str | None, + Field( + description="The seller's identifier for the created resource (e.g., media_buy_id, rights_grant_id, deployment_id). Not interpreted by the governance agent — included in audit logs for human-readable traceability alongside the opaque governance_context.", + max_length=255, + ), + ] = None + committed_budget: Annotated[ + StrictFloat | None, + Field( + description='Buyer-reported seller amount retained for reconciliation and audit. It is never ledger authority: the governance agent derives the reserved commitment from its own approved intent check, or from the matching purchase execution check when one exists. A report above that authorized amount is rejected; a lower report does not restore headroom.', + ge=0.0, + ), + ] = None + packages: Annotated[ + list[Package] | None, + Field(description='Confirmed packages with actual budget and targeting.'), + ] = None + planned_delivery: Annotated[ + planned_delivery_1.PlannedDelivery | None, + Field( + description="What the seller said it will deliver. When seller-side governance is not configured, this is the governance agent's only view of the seller's delivery parameters." + ), + ] = None + creative_deadline: Annotated[ + AwareDatetime | None, Field(description='ISO 8601 deadline for creative submission.') + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.governance.report_plan_outcome_request import ( - Delivery, - Package, - ReportPlanOutcomeRequest, - ReportingPeriod, - SellerResponse, - Source, -) - -# Explicit exports -__all__ = [ - "Delivery", - "Package", - "ReportPlanOutcomeRequest", - "ReportingPeriod", - "SellerResponse", - "Source", -] +class ReportPlanOutcomeRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + plan_id: Annotated[ + str, + Field( + description='The plan this outcome is for. The plan is owned by the authenticated buyer that synchronized it; plan_id is an identifier, not an account credential. Completed and failed settlements inherit their commercial binding from the exact approved check tuple.' + ), + ] + check_id: Annotated[ + str | None, + Field( + description='The check_id from check_governance. Required for completed and failed outcomes and for buyer delivery observations. A delivery observation names the exact seller delivery check whose canonical statement is being compared.' + ), + ] = None + idempotency_key: Annotated[ + str, + Field( + description='Buyer-generated unique key for this outcome report. An identical retry returns the cached response without another settlement; reuse with a different canonical payload returns IDEMPOTENCY_CONFLICT. Use a fresh UUID v4 for each distinct report.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + purchase_type: Annotated[ + purchase_type_1.PurchaseType | None, + Field( + description="The type of financial commitment this outcome is for. Must equal the original approved intent's purchase_type. Determines which budget allocation (if any) to charge against. Defaults to 'media_buy' when omitted." + ), + ] = purchase_type_1.PurchaseType.media_buy + outcome: Annotated[outcome_type.OutcomeType, Field(description='Outcome type.')] + seller_response: Annotated[ + SellerResponse | None, + Field(description="The seller's full response. Required when outcome is 'completed'."), + ] = None + delivery: Annotated[ + Delivery | None, + Field( + description='Buyer-attributed observation compared with the canonical seller delivery statement identified by check_id. This evidence never overwrites seller evidence or creates a second commitment. A conflict produces an explicit disputed reconciliation state while the operational period is open; the plan owner may close it without asserting final billing truth.' + ), + ] = None + error: Annotated[ + reported_outcome_error.ReportedOutcomeError | None, + Field( + description='Buyer-attributed error associated with a failed seller interaction. Required when outcome is failed; classification_source=seller_response_copy preserves what the buyer received without claiming seller-attested provenance.' + ), + ] = None + governance_context: Annotated[ + str | None, + Field( + description='Opaque governance context from the check_governance response. Required with check_id for completed and failed outcomes and buyer delivery observations.', + max_length=4096, + min_length=1, + pattern='^[\\x20-\\x7E]+$', + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/governance/report_plan_outcome_response.py b/src/adcp/types/domains/governance/report_plan_outcome_response.py index bc535262f..b58d97148 100644 --- a/src/adcp/types/domains/governance/report_plan_outcome_response.py +++ b/src/adcp/types/domains/governance/report_plan_outcome_response.py @@ -1,32 +1,120 @@ -"""Types declared by the AdCP ``governance/report_plan_outcome_response`` schema. +# generated by datamodel-codegen: +# filename: governance/report_plan_outcome_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.governance.report_plan_outcome_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, model_validator, StrictBool, StrictFloat + +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import escalation_severity + + +class OutcomeState(StrEnum): + accepted = 'accepted' + findings = 'findings' + + +class DeliveryReconciliationStatus(StrEnum): + consistent = 'consistent' + measurement_variance = 'measurement_variance' + disputed = 'disputed' + unmatched = 'unmatched' + closed_unresolved = 'closed_unresolved' + + +class DeliveryPeriodState(StrEnum): + open = 'open' + closed = 'closed' + + +class Finding(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + category_id: Annotated[str, Field(description='Which validation category flagged the issue.')] + severity: Annotated[ + escalation_severity.EscalationSeverity, Field(description='Finding severity.') + ] + explanation: Annotated[str, Field(description='Human-readable description of the issue.')] + details: Annotated[ + dict[str, Any] | None, Field(description='Structured details for programmatic consumption.') + ] = None + + +class PlanSummary(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + total_committed: Annotated[ + StrictFloat | None, + Field(description='Total budget committed across all campaigns in the plan.'), + ] = None + budget_remaining: Annotated[ + StrictFloat | None, Field(description='Authorized budget minus total committed.') + ] = None + + +class ReportPlanOutcomeResponse(AdcpResponse, AdcpVersionEnvelope): + @model_validator(mode='before') + @classmethod + def _status_to_outcome_state(cls, data: Any) -> Any: + if isinstance(data, dict) and 'outcome_state' not in data and 'status' in data: + data = dict(data) + data['outcome_state'] = data['status'] + return data -from adcp.types.generated_poc.governance.report_plan_outcome_response import ( - DeliveryPeriodState, - DeliveryReconciliationStatus, - Finding, - OutcomeState, - PlanSummary, - ReportPlanOutcomeResponse, -) - -# Explicit exports -__all__ = [ - "DeliveryPeriodState", - "DeliveryReconciliationStatus", - "Finding", - "OutcomeState", - "PlanSummary", - "ReportPlanOutcomeResponse", -] + model_config = ConfigDict( + extra='allow', + ) + outcome_id: Annotated[str, Field(description='Unique identifier for this outcome record.')] + outcome_state: Annotated[ + OutcomeState, + Field( + description="Outcome state. 'accepted' means state updated with no issues. 'findings' means issues were detected. Renamed from `status` in 3.1 to free the top-level `status` key for the envelope task-status (TaskStatus) under MCP flat-on-the-wire serialization." + ), + ] + committed_budget: Annotated[ + StrictFloat | None, + Field( + description="Budget committed from this outcome. Present for 'completed' and 'failed' outcomes." + ), + ] = None + delivery_reconciliation_status: Annotated[ + DeliveryReconciliationStatus | None, + Field( + description='Comparison state between the buyer-attributed observation and canonical seller statement. Present for delivery outcomes. A disputed state blocks adjustment verification while the period is open. measurement_variance records a buyer-measured cumulative_spend that differs from the seller statement; it never blocks verification — the higher amount bounds conservative exposure and decommitments instead. closed_unresolved preserves the discrepancy after operational closure without claiming a final billing result.' + ), + ] = None + delivery_period_state: Annotated[ + DeliveryPeriodState | None, + Field( + description='Operational state of the governance reporting period. Closure freezes governance evidence for that period but is not billing settlement.' + ), + ] = None + findings: Annotated[ + list[Finding] | None, + Field(description="Issues detected. Present only when outcome_state is 'findings'."), + ] = None + plan_summary: Annotated[ + PlanSummary | None, + Field( + description="Updated plan budget state. Present for 'completed' and 'failed' outcomes." + ), + ] = None + replayed: Annotated[ + StrictBool | None, + Field( + description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. `replayed` appears only when the request actually resolved through the idempotency cache. Pure reads may ignore an optional `idempotency_key`; when a seller voluntarily caches keyed reads, those responses use the same replay indicator and full cache contract." + ), + ] = False + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/governance/reported_outcome_error.py b/src/adcp/types/domains/governance/reported_outcome_error.py index 8a91da1e7..38574a17f 100644 --- a/src/adcp/types/domains/governance/reported_outcome_error.py +++ b/src/adcp/types/domains/governance/reported_outcome_error.py @@ -1,44 +1,97 @@ -"""Types declared by the AdCP ``governance/reported_outcome_error`` schema. +# generated by datamodel-codegen: +# filename: governance/reported_outcome_error.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.governance.reported_outcome_error import +from adcp.types._str_enum import StrEnum +from typing import Any, Annotated, Dict -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field, RootModel, StrictBool, StrictFloat, StringConstraints + + +class Recovery(StrEnum): + transient = 'transient' + correctable = 'correctable' + terminal = 'terminal' + + +class ClassificationSource(StrEnum): + seller_response_copy = 'seller_response_copy' + buyer_classification = 'buyer_classification' + + +class BoundedScalar1(ScalarStr): + __slots__ = () + _constraints = {'max_length': 4000} + + +class BoundedScalar(RootModel[StrictBool | StrictFloat | BoundedScalar1 | None]): + root: StrictBool | StrictFloat | BoundedScalar1 | None + def __getattr__(self, name: str) -> Any: + """Proxy attribute access to the wrapped type.""" + if name.startswith('_'): + raise AttributeError(name) + return getattr(self.root, name) + + + +class BoundedValueLevel31(RootModel[list[BoundedScalar | None]]): + root: Annotated[list[BoundedScalar | None], Field(max_length=32)] + def __getattr__(self, name: str) -> Any: + """Proxy attribute access to the wrapped type.""" + if name.startswith('_'): + raise AttributeError(name) + return getattr(self.root, name) + + + +BoundedValueLevel3 = BoundedScalar | BoundedValueLevel31 | dict[str, BoundedScalar | None] | None + + +class BoundedValueLevel21(RootModel[list[BoundedValueLevel3]]): + root: Annotated[list[BoundedValueLevel3], Field(max_length=32)] + + +BoundedValueLevel2 = BoundedScalar | BoundedValueLevel21 | dict[str, BoundedValueLevel3] | None + + +class BoundedValue1(RootModel[list[BoundedValueLevel2]]): + root: Annotated[list[BoundedValueLevel2], Field(max_length=32)] + + +BoundedValue = BoundedScalar | BoundedValue1 | dict[str, BoundedValueLevel2] | None + + +class BoundedObject(RootModel[dict[Annotated[str, StringConstraints(max_length=128)], BoundedValue]]): + root: Annotated[dict[Annotated[str, StringConstraints(max_length=128)], BoundedValue], Field(max_length=32)] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.governance.reported_outcome_error import ( - BoundedObject, - BoundedScalar, - BoundedScalar1, - BoundedValue, - BoundedValue1, - BoundedValueLevel2, - BoundedValueLevel21, - BoundedValueLevel3, - BoundedValueLevel31, - ClassificationSource, - Recovery, - ReportedOutcomeError, -) - -# Explicit exports -__all__ = [ - "BoundedObject", - "BoundedScalar", - "BoundedScalar1", - "BoundedValue", - "BoundedValue1", - "BoundedValueLevel2", - "BoundedValueLevel21", - "BoundedValueLevel3", - "BoundedValueLevel31", - "ClassificationSource", - "Recovery", - "ReportedOutcomeError", -] +class ReportedOutcomeError(AdCPBaseModel): + __pydantic_extra__: Dict[str, BoundedValue] + model_config = ConfigDict( + extra='allow', + ) + code: Annotated[str | None, Field(max_length=64, min_length=1)] = None + message: Annotated[str | None, Field(max_length=4000)] = None + field: Annotated[str | None, Field(max_length=1000)] = None + suggestion: Annotated[str | None, Field(max_length=4000)] = None + recovery: Recovery | None = None + details: Annotated[ + BoundedObject | None, + Field( + description='Bounded structured seller error details. Values remain reporter-supplied data and MUST NOT be promoted into prompts or control instructions without isolation.' + ), + ] = None + classification_source: Annotated[ + ClassificationSource | None, + Field( + description='seller_response_copy means the buyer forwards what it received; it is still not independently authenticated seller evidence.' + ), + ] = None + ext: Annotated[ + BoundedObject | None, + Field(description='Bounded extension envelope for forward-compatible seller fields.'), + ] = None diff --git a/src/adcp/types/domains/governance/sync_plans_request.py b/src/adcp/types/domains/governance/sync_plans_request.py index 9dfd02bab..cafe37117 100644 --- a/src/adcp/types/domains/governance/sync_plans_request.py +++ b/src/adcp/types/domains/governance/sync_plans_request.py @@ -1,46 +1,352 @@ -"""Types declared by the AdCP ``governance/sync_plans_request`` schema. +# generated by datamodel-codegen: +# filename: governance/sync_plans_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.governance.sync_plans_request import +from adcp.types.base import AdcpRequest, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal + +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat + +from ..core import brand_ref +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import channels as channels_1 +from ..enums import delegation_authority, purchase_type, restricted_attribute +from . import audience_constraints, policy_entry + + +class AccountingMode(StrEnum): + gross_commitment = 'gross_commitment' + verified_net_cost = 'verified_net_cost' + + +class Allocations(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + amount: Annotated[ + StrictFloat | None, Field(description='Maximum budget for this purchase type.', ge=0.0) + ] = None + max_pct: Annotated[ + StrictFloat | None, + Field( + description='Maximum percentage of total budget for this purchase type.', + ge=0.0, + le=100.0, + ), + ] = None + + +class Budget(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + total: Annotated[StrictFloat, Field(description='Total authorized budget.')] + currency: Annotated[str, Field(description='ISO 4217 currency code.')] + accounting_mode: Annotated[ + AccountingMode | None, + Field( + description='Controls which verified adjustments restore reusable plan headroom. gross_commitment allows verified decommitments only; verified_net_cost also allows buyer-accepted refunds and credits. Both modes retain the original commitment in anti-fragmentation history and expose economic net cost separately.' + ), + ] = AccountingMode.gross_commitment + per_seller_max_pct: Annotated[ + StrictFloat | None, + Field(description='Maximum percentage of budget that can go to a single seller.'), + ] = None + reallocation_threshold: Annotated[ + StrictFloat, + Field( + description='Amount above which budget reallocations require human escalation. The orchestrator can reallocate spend across sellers, channels, or purchase types up to this threshold per change without asking a human. Set equal to `total` for effectively unlimited reallocation; set to 0 to require approval for every reallocation. Separate from `plan.human_review_required`, which governs decisions affecting data subjects (targeting, creative, delivery) under GDPR Art 22 / EU AI Act Annex III. Denominated in `budget.currency`.', + ge=0.0, + ), + ] + reallocation_unlimited: Annotated[ + StrictBool | None, + Field( + description='Set to true to allow the orchestrator to reallocate without any limit up to `total`. Mutually exclusive with `reallocation_threshold`. Use this for deliberate full-autonomy declarations rather than setting `reallocation_threshold: total` (which silently tightens when `total` changes).' + ), + ] = None + allocations: Annotated[ + dict[purchase_type.PurchaseType, Allocations] | None, + Field( + description='Optional budget partition across purchase types. Keys are purchase-type enum values (media_buy, rights_license, signal_activation, creative_services). When present, the governance agent validates spend against both the total and the per-type allocation. When absent, all spend counts against the single total regardless of purchase type.' + ), + ] = None + + +class Budget2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + total: Annotated[StrictFloat, Field(description='Total authorized budget.')] + currency: Annotated[str, Field(description='ISO 4217 currency code.')] + accounting_mode: Annotated[ + AccountingMode | None, + Field( + description='Controls which verified adjustments restore reusable plan headroom. gross_commitment allows verified decommitments only; verified_net_cost also allows buyer-accepted refunds and credits. Both modes retain the original commitment in anti-fragmentation history and expose economic net cost separately.' + ), + ] = AccountingMode.gross_commitment + per_seller_max_pct: Annotated[ + StrictFloat | None, + Field(description='Maximum percentage of budget that can go to a single seller.'), + ] = None + reallocation_threshold: Annotated[ + StrictFloat | None, + Field( + description='Amount above which budget reallocations require human escalation. The orchestrator can reallocate spend across sellers, channels, or purchase types up to this threshold per change without asking a human. Set equal to `total` for effectively unlimited reallocation; set to 0 to require approval for every reallocation. Separate from `plan.human_review_required`, which governs decisions affecting data subjects (targeting, creative, delivery) under GDPR Art 22 / EU AI Act Annex III. Denominated in `budget.currency`.', + ge=0.0, + ), + ] = None + reallocation_unlimited: Annotated[ + Literal[True], + Field( + description='Set to true to allow the orchestrator to reallocate without any limit up to `total`. Mutually exclusive with `reallocation_threshold`. Use this for deliberate full-autonomy declarations rather than setting `reallocation_threshold: total` (which silently tightens when `total` changes).' + ), + ] + allocations: Annotated[ + dict[purchase_type.PurchaseType, Allocations] | None, + Field( + description='Optional budget partition across purchase types. Keys are purchase-type enum values (media_buy, rights_license, signal_activation, creative_services). When present, the governance agent validates spend against both the total and the per-type allocation. When absent, all spend counts against the single total regardless of purchase type.' + ), + ] = None + + +class MixTargets(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + min_pct: StrictFloat | None = None + max_pct: StrictFloat | None = None + + +class Channels(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + required: Annotated[ + list[channels_1.MediaChannel] | None, + Field(description='Channels that must be included in the media mix.'), + ] = None + allowed: Annotated[ + list[channels_1.MediaChannel] | None, + Field(description='Channels the orchestrator may use.'), + ] = None + mix_targets: Annotated[ + dict[str, MixTargets] | None, + Field(description='Target allocation ranges per channel, keyed by channel ID.'), + ] = None + + +class Flight(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + start: Annotated[AwareDatetime, Field(description='Flight start (ISO 8601).')] + end: Annotated[AwareDatetime, Field(description='Flight end (ISO 8601).')] + + +class BudgetLimit(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + amount: StrictFloat + currency: str + + +class Delegation(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + agent_url: Annotated[AnyUrl, Field(description='URL of the delegated agent.')] + authority: Annotated[ + delegation_authority.DelegationAuthority, + Field(description='Authority level granted to this agent.'), + ] + budget_limit: Annotated[ + BudgetLimit | None, + Field( + description="Maximum budget this agent can commit. When omitted, the agent can commit up to the plan's total budget." + ), + ] = None + markets: Annotated[ + list[str] | None, + Field( + description='ISO 3166-1/3166-2 codes this agent is authorized for. When omitted, the agent can operate in all plan markets.' + ), + ] = None + expires_at: Annotated[ + AwareDatetime | None, + Field( + description='When this delegation expires. After expiration, the governance agent denies actions from this agent.' + ), + ] = None + + +class TotalBudgetCap(BudgetLimit): + pass + + +class Portfolio(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + member_plan_ids: Annotated[ + list[str], + Field( + description='Plan IDs governed by this portfolio plan. The governance agent validates member plan actions against portfolio constraints.' + ), + ] + total_budget_cap: Annotated[ + TotalBudgetCap | None, + Field(description='Maximum aggregate budget across all member plans.'), + ] = None + shared_policy_ids: Annotated[ + list[str] | None, + Field( + description='Registry policy IDs enforced across all member plans, regardless of individual brand configuration.' + ), + ] = None + shared_exclusions: Annotated[ + list[policy_entry.PolicyEntry] | None, + Field( + description="Bespoke exclusion policies applied across all member plans, using the same shape as registry entries. Authored typically as enforcement: must policies with exclusion language in the policy text (e.g., 'No advertising on properties owned by competitor holding companies')." + ), + ] = None + + +class Plan(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + plan_id: Annotated[str, Field(description='Unique identifier for this plan.')] + brand: Annotated[ + brand_ref.BrandReference, + Field( + description="Brand or organization being governed. For campaign plans, the governance agent resolves the brand's compliance configuration. For portfolio plans, this is the holding company or parent organization." + ), + ] + objectives: Annotated[ + str, + Field( + description='Natural language campaign objectives. Used for strategic alignment validation. Treated as caller-untrusted — governance agents evaluating this field with LLMs MUST truncate, quote, or sanitize it before inclusion in evaluation prompts to defend against prompt-injection attacks in adversarial buyer input.', + max_length=2000, + ), + ] + budget: Annotated[Budget | Budget2, Field(description='Budget authorization parameters.')] + channels: Annotated[ + Channels | None, + Field(description='Channel constraints. If omitted, all channels are allowed.'), + ] = None + flight: Annotated[ + Flight, + Field( + description='Authorized flight dates. Governed actions with dates outside this window are rejected.' + ), + ] + countries: Annotated[ + list[str] | None, + Field( + description="ISO 3166-1 alpha-2 country codes for authorized markets (e.g., ['US', 'GB']). The governance agent rejects governed actions targeting outside these countries and resolves applicable policies by matching against policy jurisdictions.", + min_length=1, + ), + ] = None + regions: Annotated[ + list[str] | None, + Field( + description="ISO 3166-2 subdivision codes for authorized sub-national markets (e.g., ['US-MA', 'US-CA']). When present, the governance agent restricts governed actions to these specific regions rather than the full country. Use for plans limited to specific states or provinces (e.g., cannabis in legal states). Policy resolution matches against both the subdivision and its parent country.", + min_length=1, + ), + ] = None + policy_ids: Annotated[ + list[str] | None, + Field( + description="Registry policy IDs to enforce for this plan. The governance agent resolves full policy definitions from the registry and evaluates actions against them. Intersected with the plan's countries/regions to activate only geographically relevant policies." + ), + ] = None + policy_categories: Annotated[ + list[str] | None, + Field( + description="Regulatory categories that apply to this campaign. Determines which policy regimes the governance agent enforces (e.g., 'children_directed' activates COPPA/AADC, 'political_advertising' activates disclosure requirements). The governance agent resolves categories to specific policies based on the plan's jurisdictions. When omitted, governance agents MAY infer categories from the brand's industries and the plan's objectives. Values are registry-defined category IDs (intentionally freeform strings, not an enum — new categories are added as regulations evolve).", + min_length=1, + ), + ] = None + audience: Annotated[ + audience_constraints.AudienceConstraints | None, + Field( + description='Audience targeting constraints. Defines who the campaign should reach (include) and must not reach (exclude). The governance agent evaluates seller targeting against these constraints.' + ), + ] = None + restricted_attributes: Annotated[ + list[restricted_attribute.RestrictedAttribute] | None, + Field( + description='Personal data categories that must not be used for targeting in this campaign. Applies horizontally across all audience criteria. Used for EU DSA Article 26 compliance (prohibits targeting on GDPR Article 9 special categories) and similar regulations. The governance agent flags any audience targeting that references these attributes.', + min_length=1, + ), + ] = None + restricted_attributes_custom: Annotated[ + list[str] | None, + Field( + description="Additional restricted attributes not covered by the restricted-attribute enum. Freeform strings for jurisdiction-specific or brand-specific restrictions beyond GDPR Article 9 categories (e.g., 'financial_status', 'immigration_status'). Governance agents use semantic matching for these.", + min_length=1, + ), + ] = None + min_audience_size: Annotated[ + SchemaInt | None, + Field( + description='Minimum audience segment size. Prevents micro-targeting by ensuring segments meet a k-anonymity threshold. Applies to the estimated combined (intersection) audience when multiple criteria are used, not just individual criterion sizes. The governance agent validates this by querying published signal metadata or seller-reported segment sizes. When segment size data is unavailable, the governance agent SHOULD produce a finding with reduced confidence rather than silently passing.', + ge=1, + ), + ] = None + human_review_required: Annotated[ + StrictBool | None, + Field( + description='When true, the governance agent MUST escalate any action on this plan for human review before execution — regardless of budget. Required by regulations that prohibit solely automated decisions affecting data subjects (GDPR Art 22, EU AI Act Annex III): credit, insurance pricing, recruitment, housing allocation. Governance agents MUST set this to true automatically when any resolved policy or policy_category has requires_human_review: true. Distinct from budget.reallocation_threshold, which only governs budget reallocation autonomy — human_review_required covers decisions affecting individuals (targeting, creative, delivery).' + ), + ] = False + custom_policies: Annotated[ + list[policy_entry.PolicyEntry] | None, + Field( + description='Bespoke policies specific to this campaign, using the same shape as registry entries. Each policy has a policy_id, enforcement (must|should), and natural-language policy text. Governance findings reference policy_id to identify which policy triggered. For quick authoring, omit version/name/category — servers default them.' + ), + ] = None + approved_sellers: Annotated[ + list[AnyUrl] | None, + Field(description='List of approved seller agent URLs. null means any seller.'), + ] = None + delegations: Annotated[ + list[Delegation] | None, + Field( + description="Agents authorized to execute against this plan. Each delegation scopes an agent's authority by budget, markets, and expiration. The governance agent validates that the requesting agent matches a delegation before approving actions." + ), + ] = None + portfolio: Annotated[ + Portfolio | None, + Field( + description='Portfolio-level governance constraints. When present, this plan acts as a portfolio plan that governs member plans. Portfolio plans define cross-brand constraints that no individual brand plan can override.' + ), + ] = None + ext: ext_1.ExtensionObject | None = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.governance.sync_plans_request import ( - AccountingMode, - Allocations, - Budget, - Budget2, - BudgetLimit, - Channels, - Delegation, - Flight, - MixTargets, - Plan, - Portfolio, - SyncPlansRequest, - TotalBudgetCap, -) - -# Explicit exports -__all__ = [ - "AccountingMode", - "Allocations", - "Budget", - "Budget2", - "BudgetLimit", - "Channels", - "Delegation", - "Flight", - "MixTargets", - "Plan", - "Portfolio", - "SyncPlansRequest", - "TotalBudgetCap", -] +class SyncPlansRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + idempotency_key: Annotated[ + str, + Field( + description='Client-generated unique key for at-most-once execution. `plan_id` gives resource-level dedup per plan, but the sync envelope emits audit events and can trigger governance reapproval — this key prevents those side effects from firing twice on retry. MUST be unique per (seller, request) pair. Use a fresh UUID v4 for each request.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + plans: Annotated[list[Plan], Field(description='One or more campaign plans to sync.')] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/governance/sync_plans_response.py b/src/adcp/types/domains/governance/sync_plans_response.py index e41a93cae..09558cb01 100644 --- a/src/adcp/types/domains/governance/sync_plans_response.py +++ b/src/adcp/types/domains/governance/sync_plans_response.py @@ -1,34 +1,103 @@ -"""Types declared by the AdCP ``governance/sync_plans_response`` schema. +# generated by datamodel-codegen: +# filename: governance/sync_plans_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.governance.sync_plans_response import +from adcp.types.base import AdcpResponse, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated + +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictBool + +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import policy_enforcement + + +class Status(StrEnum): + active = 'active' + error = 'error' + + +class Status51(StrEnum): + active = 'active' + inactive = 'inactive' + + +class Category(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + category_id: Annotated[str, Field(description='Validation category identifier.')] + status: Annotated[Status51, Field(description='Whether this category is active for this plan.')] + + +class Source(StrEnum): + explicit = 'explicit' + auto_applied = 'auto_applied' + + +class ResolvedPolicy(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + policy_id: Annotated[str, Field(description='Registry policy ID.')] + source: Annotated[ + Source, + Field( + description="How this policy was included. 'explicit': referenced in the brand compliance configuration. 'auto_applied': matched automatically by jurisdiction or policy category." + ), + ] + enforcement: Annotated[ + policy_enforcement.PolicyEnforcementLevel, + Field(description='Enforcement level for this policy.'), + ] + reason: Annotated[ + str | None, + Field( + description="Why this policy was included (e.g., 'Matched jurisdiction US and policy category pharmaceutical_advertising')." + ), + ] = None + + +class Plan(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + plan_id: Annotated[str, Field(description='Plan identifier.')] + status: Annotated[ + Status, + Field( + description="Sync result status. 'active' means sync succeeded; 'error' means sync failed." + ), + ] + version: Annotated[SchemaInt, Field(description='Plan version (increments on each sync).')] + categories: Annotated[ + list[Category] | None, Field(description='Validation categories active for this plan.') + ] = None + resolved_policies: Annotated[ + list[ResolvedPolicy] | None, + Field( + description='Policies the governance agent will enforce for this plan. Includes explicitly referenced policies from the brand compliance configuration and auto-applied policies matched by jurisdiction or policy category. Present when the governance agent supports policy resolution.' + ), + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.governance.sync_plans_response import ( - Category, - Plan, - ResolvedPolicy, - Source, - Status, - Status51, - SyncPlansResponse, -) - -# Explicit exports -__all__ = [ - "Category", - "Plan", - "ResolvedPolicy", - "Source", - "Status", - "Status51", - "SyncPlansResponse", -] +class SyncPlansResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + plans: Annotated[list[Plan], Field(description='Status for each synced plan.')] + replayed: Annotated[ + StrictBool | None, + Field( + description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. `replayed` appears only when the request actually resolved through the idempotency cache. Pure reads may ignore an optional `idempotency_key`; when a seller voluntarily caches keyed reads, those responses use the same replay indicator and full cache contract." + ), + ] = False + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/manifest.py b/src/adcp/types/domains/manifest.py similarity index 74% rename from src/adcp/types/generated_poc/manifest.py rename to src/adcp/types/domains/manifest.py index 63898a7f0..4c1f80764 100644 --- a/src/adcp/types/generated_poc/manifest.py +++ b/src/adcp/types/domains/manifest.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: manifest.json -# timestamp: 2026-05-02T19:36:29+00:00 +# timestamp: 2026-10-04T18:54:18+00:00 from __future__ import annotations @@ -11,3 +11,8 @@ class Model(RootModel[Any]): root: Any + + +__all__ = [ + "Model", +] diff --git a/src/adcp/types/domains/manifest/__init__.py b/src/adcp/types/domains/manifest/__init__.py deleted file mode 100644 index 395581fe7..000000000 --- a/src/adcp/types/domains/manifest/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -"""Types the AdCP ``manifest`` schemas declare. - -Importing from the domain says which variant you mean, where the flat -``adcp.types`` namespace can only bind one class per name: - - from adcp.types.domains.manifest import - -This domain has one schema, so every type it declares is here. - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:33:59 UTC -""" - -# ruff: noqa: E501, I001 -from __future__ import annotations - -from adcp.types.generated_poc.manifest import Model - -# Explicit exports -__all__ = ["Model"] diff --git a/src/adcp/types/generated_poc/manifest_schema.py b/src/adcp/types/domains/manifest_schema.py similarity index 97% rename from src/adcp/types/generated_poc/manifest_schema.py rename to src/adcp/types/domains/manifest_schema.py index 8529b2e91..0f4783a5e 100644 --- a/src/adcp/types/generated_poc/manifest_schema.py +++ b/src/adcp/types/domains/manifest_schema.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: manifest.schema.json -# timestamp: 2026-10-04T00:59:59+00:00 +# timestamp: 2026-10-04T18:54:18+00:00 from __future__ import annotations @@ -317,3 +317,22 @@ class AdcpManifest(AdCPBaseModel): description='Every storyboard specialism declared in static/compliance/source/specialisms/, keyed by specialism ID. Lets SDKs declare which specialisms they implement and verify their tool surface covers the required tools.' ), ] + + +__all__ = [ + "AdcpManifest", + "DefaultUnknownRecovery", + "ErrorCodePolicy", + "ErrorCodes", + "IdempotencyRequirement", + "LegacyFallback", + "LegacyFallback1", + "LegacyFallback2", + "Mode", + "Protocol", + "SchemaPath", + "Specialisms", + "TaskResultResolution", + "ToolName", + "Tools", +] diff --git a/src/adcp/types/domains/manifest_schema/__init__.py b/src/adcp/types/domains/manifest_schema/__init__.py deleted file mode 100644 index 3c1405963..000000000 --- a/src/adcp/types/domains/manifest_schema/__init__.py +++ /dev/null @@ -1,52 +0,0 @@ -"""Types the AdCP ``manifest_schema`` schemas declare. - -Importing from the domain says which variant you mean, where the flat -``adcp.types`` namespace can only bind one class per name: - - from adcp.types.domains.manifest_schema import - -This domain has one schema, so every type it declares is here. - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:33:59 UTC -""" - -# ruff: noqa: E501, I001 -from __future__ import annotations - -from adcp.types.generated_poc.manifest_schema import ( - AdcpManifest, - DefaultUnknownRecovery, - ErrorCodePolicy, - ErrorCodes, - IdempotencyRequirement, - LegacyFallback, - LegacyFallback1, - LegacyFallback2, - Mode, - Protocol, - SchemaPath, - Specialisms, - TaskResultResolution, - ToolName, - Tools, -) - -# Explicit exports -__all__ = [ - "AdcpManifest", - "DefaultUnknownRecovery", - "ErrorCodePolicy", - "ErrorCodes", - "IdempotencyRequirement", - "LegacyFallback", - "LegacyFallback1", - "LegacyFallback2", - "Mode", - "Protocol", - "SchemaPath", - "Specialisms", - "TaskResultResolution", - "ToolName", - "Tools", -] diff --git a/src/adcp/types/domains/media_buy/__init__.py b/src/adcp/types/domains/media_buy/__init__.py index 997e89ce3..1b71063f6 100644 --- a/src/adcp/types/domains/media_buy/__init__.py +++ b/src/adcp/types/domains/media_buy/__init__.py @@ -9,24 +9,24 @@ it from its own schema's module, ``adcp.types.domains.media_buy.``. Nothing here is renamed. -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-04 01:19:01 UTC +Auto-generated from the generated domain tree. DO NOT EDIT MANUALLY. +Generation date: 2026-10-04 18:45:11 UTC """ # ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.media_buy.accept_proposal_async_response_input_required import ( +from adcp.types.domains.media_buy.accept_proposal_async_response_input_required import ( AcceptProposalInputRequired, ) -from adcp.types.generated_poc.media_buy.accept_proposal_async_response_submitted import ( +from adcp.types.domains.media_buy.accept_proposal_async_response_submitted import ( AcceptProposalSubmitted, ) -from adcp.types.generated_poc.media_buy.accept_proposal_async_response_working import ( +from adcp.types.domains.media_buy.accept_proposal_async_response_working import ( AcceptProposalWorking, ) -from adcp.types.generated_poc.media_buy.accept_proposal_request import AcceptProposalRequest -from adcp.types.generated_poc.media_buy.accept_proposal_response import ( +from adcp.types.domains.media_buy.accept_proposal_request import AcceptProposalRequest +from adcp.types.domains.media_buy.accept_proposal_response import ( AcceptProposalResponse, AcceptProposalResponse1, AcceptProposalResponse2, @@ -36,24 +36,24 @@ AcceptProposalResponse6, AcceptProposalResponse7, ) -from adcp.types.generated_poc.media_buy.acceptance_context import ( +from adcp.types.domains.media_buy.acceptance_context import ( AcceptanceContext, AdvertiserJurisdiction, DeliveryJurisdiction, Subject, ) -from adcp.types.generated_poc.media_buy.acceptance_policy_catalog import AcceptancePolicyCatalog -from adcp.types.generated_poc.media_buy.acceptance_policy_profile import ( +from adcp.types.domains.media_buy.acceptance_policy_catalog import AcceptancePolicyCatalog +from adcp.types.domains.media_buy.acceptance_policy_profile import ( AcceptancePolicyProfile, Coverage, PolicyRef, RegionAliase, SubjectCategory, ) -from adcp.types.generated_poc.media_buy.acceptance_policy_profile_ref import ( +from adcp.types.domains.media_buy.acceptance_policy_profile_ref import ( RegistryAcceptancePolicyProfileReference, ) -from adcp.types.generated_poc.media_buy.acceptance_policy_requirement import ( +from adcp.types.domains.media_buy.acceptance_policy_requirement import ( AcceptancePolicyRequirement, AcceptancePolicyRequirement1, AcceptancePolicyRequirement10, @@ -75,20 +75,15 @@ Criterion, FormatId, ) -from adcp.types.generated_poc.media_buy.acceptance_policy_rule import ( - AcceptancePolicyRule, - Disposition, -) -from adcp.types.generated_poc.media_buy.build_creative_async_response_input_required import ( +from adcp.types.domains.media_buy.acceptance_policy_rule import AcceptancePolicyRule, Disposition +from adcp.types.domains.media_buy.build_creative_async_response_input_required import ( BuildCreativeInputRequired, ) -from adcp.types.generated_poc.media_buy.build_creative_async_response_submitted import ( +from adcp.types.domains.media_buy.build_creative_async_response_submitted import ( BuildCreativeSubmitted, ) -from adcp.types.generated_poc.media_buy.build_creative_async_response_working import ( - BuildCreativeWorking, -) -from adcp.types.generated_poc.media_buy.build_creative_request import ( +from adcp.types.domains.media_buy.build_creative_async_response_working import BuildCreativeWorking +from adcp.types.domains.media_buy.build_creative_request import ( BuildCreativeRequest, Dimension, KeepMode, @@ -106,7 +101,7 @@ TargetCapabilityId, VariantAxis, ) -from adcp.types.generated_poc.media_buy.build_creative_response import ( +from adcp.types.domains.media_buy.build_creative_response import ( BuildCreativeResponse, BuildCreativeResponse1, BuildCreativeResponse2, @@ -126,17 +121,13 @@ Preview4, Variant, ) -from adcp.types.generated_poc.media_buy.buy_products_async_response_input_required import ( +from adcp.types.domains.media_buy.buy_products_async_response_input_required import ( BuyProductsInputRequired, ) -from adcp.types.generated_poc.media_buy.buy_products_async_response_submitted import ( - BuyProductsSubmitted, -) -from adcp.types.generated_poc.media_buy.buy_products_async_response_working import ( - BuyProductsWorking, -) -from adcp.types.generated_poc.media_buy.buy_products_request import BuyProductsRequest -from adcp.types.generated_poc.media_buy.buy_products_response import ( +from adcp.types.domains.media_buy.buy_products_async_response_submitted import BuyProductsSubmitted +from adcp.types.domains.media_buy.buy_products_async_response_working import BuyProductsWorking +from adcp.types.domains.media_buy.buy_products_request import BuyProductsRequest +from adcp.types.domains.media_buy.buy_products_response import ( BuyProductsResponse, BuyProductsResponse1, BuyProductsResponse2, @@ -146,12 +137,8 @@ BuyProductsResponse6, BuyProductsResponse7, ) -from adcp.types.generated_poc.media_buy.change_term import ( - AllowedStatus, - Condition, - MediaBuyChangeTerm, -) -from adcp.types.generated_poc.media_buy.change_term_constraints import ( +from adcp.types.domains.media_buy.change_term import AllowedStatus, Condition, MediaBuyChangeTerm +from adcp.types.domains.media_buy.change_term_constraints import ( MediaBuyChangeTermConstraints, MediaBuyChangeTermConstraints1, MediaBuyChangeTermConstraints2, @@ -159,62 +146,62 @@ MediaBuyChangeTermConstraints4, Money, ) -from adcp.types.generated_poc.media_buy.commercial_terms import ( +from adcp.types.domains.media_buy.commercial_terms import ( CancellationTerms, CommercialTerms, Fee, ReportingCommitment, ) -from adcp.types.generated_poc.media_buy.control_media_buy_async_response_input_required import ( +from adcp.types.domains.media_buy.control_media_buy_async_response_input_required import ( ControlMediaBuyInputRequired, ) -from adcp.types.generated_poc.media_buy.control_media_buy_async_response_submitted import ( +from adcp.types.domains.media_buy.control_media_buy_async_response_submitted import ( ControlMediaBuySubmitted, ) -from adcp.types.generated_poc.media_buy.control_media_buy_async_response_working import ( +from adcp.types.domains.media_buy.control_media_buy_async_response_working import ( ControlMediaBuyWorking, ) -from adcp.types.generated_poc.media_buy.control_media_buy_request import ControlMediaBuyRequest -from adcp.types.generated_poc.media_buy.control_media_buy_response import ( +from adcp.types.domains.media_buy.control_media_buy_request import ControlMediaBuyRequest +from adcp.types.domains.media_buy.control_media_buy_response import ( AffectedPackageId, ControlMediaBuyResponse, ControlMediaBuyResponse1, ControlMediaBuyResponse2, ControlMediaBuyResponse3, ) -from adcp.types.generated_poc.media_buy.create_media_buy_async_response_input_required import ( +from adcp.types.domains.media_buy.create_media_buy_async_response_input_required import ( CreateMediaBuyInputRequired, ) -from adcp.types.generated_poc.media_buy.create_media_buy_async_response_submitted import ( +from adcp.types.domains.media_buy.create_media_buy_async_response_submitted import ( CreateMediaBuySubmitted, ) -from adcp.types.generated_poc.media_buy.create_media_buy_async_response_working import ( +from adcp.types.domains.media_buy.create_media_buy_async_response_working import ( CreateMediaBuyWorking, ) -from adcp.types.generated_poc.media_buy.create_media_buy_request import ( +from adcp.types.domains.media_buy.create_media_buy_request import ( ArtifactWebhook, Authentication, BatchFrequency, CreateMediaBuyRequest, DeliveryMode, ) -from adcp.types.generated_poc.media_buy.create_media_buy_response import ( +from adcp.types.domains.media_buy.create_media_buy_response import ( CreateMediaBuyResponse, CreateMediaBuyResponse1, CreateMediaBuyResponse2, CreateMediaBuyResponse3, ) -from adcp.types.generated_poc.media_buy.decline_proposals_async_response_input_required import ( +from adcp.types.domains.media_buy.decline_proposals_async_response_input_required import ( DeclineProposalsInputRequired, ) -from adcp.types.generated_poc.media_buy.decline_proposals_async_response_submitted import ( +from adcp.types.domains.media_buy.decline_proposals_async_response_submitted import ( DeclineProposalsSubmitted, ) -from adcp.types.generated_poc.media_buy.decline_proposals_async_response_working import ( +from adcp.types.domains.media_buy.decline_proposals_async_response_working import ( DeclineProposalsWorking, ) -from adcp.types.generated_poc.media_buy.decline_proposals_request import DeclineProposalsRequest -from adcp.types.generated_poc.media_buy.decline_proposals_response import ( +from adcp.types.domains.media_buy.decline_proposals_request import DeclineProposalsRequest +from adcp.types.domains.media_buy.decline_proposals_response import ( DeclineProposalsResponse, DeclineProposalsResponse1, DeclineProposalsResponse2, @@ -224,7 +211,7 @@ Results4, Results5, ) -from adcp.types.generated_poc.media_buy.get_media_buy_delivery_request import ( +from adcp.types.domains.media_buy.get_media_buy_delivery_request import ( AttributionWindow, CatalogItem, Demographic, @@ -238,7 +225,7 @@ ReportingDimensions, Spot, ) -from adcp.types.generated_poc.media_buy.get_media_buy_delivery_response import ( +from adcp.types.domains.media_buy.get_media_buy_delivery_response import ( AggregatedTotals, ByAudienceItem, ByDemographicItem, @@ -253,8 +240,8 @@ ReportingRevisionBinding, Window, ) -from adcp.types.generated_poc.media_buy.get_media_buys_request import GetMediaBuysRequest -from adcp.types.generated_poc.media_buy.get_media_buys_response import ( +from adcp.types.domains.media_buy.get_media_buys_request import GetMediaBuysRequest +from adcp.types.domains.media_buy.get_media_buys_response import ( Cancellation, Cancellation1, CreativeApproval, @@ -270,17 +257,13 @@ Package, Snapshot, ) -from adcp.types.generated_poc.media_buy.get_products_async_response_input_required import ( +from adcp.types.domains.media_buy.get_products_async_response_input_required import ( GetProductsInputRequired, ) -from adcp.types.generated_poc.media_buy.get_products_async_response_submitted import ( - GetProductsSubmitted, -) -from adcp.types.generated_poc.media_buy.get_products_async_response_working import ( - GetProductsWorking, -) -from adcp.types.generated_poc.media_buy.get_products_rejected import GetProductsRejected -from adcp.types.generated_poc.media_buy.get_products_request import ( +from adcp.types.domains.media_buy.get_products_async_response_submitted import GetProductsSubmitted +from adcp.types.domains.media_buy.get_products_async_response_working import GetProductsWorking +from adcp.types.domains.media_buy.get_products_rejected import GetProductsRejected +from adcp.types.domains.media_buy.get_products_request import ( Action9, BuyingMode, Field1, @@ -291,7 +274,7 @@ Refine2, Refine3, ) -from adcp.types.generated_poc.media_buy.get_products_response import ( +from adcp.types.domains.media_buy.get_products_response import ( ExcludedBy, Extensions, FilterDiagnostics, @@ -302,79 +285,74 @@ RefinementApplied3, Semantics, ) -from adcp.types.generated_poc.media_buy.get_products_targeting_resolution import ( +from adcp.types.domains.media_buy.get_products_targeting_resolution import ( ProductDiscoveryTargetingResolution, ) -from adcp.types.generated_poc.media_buy.get_reporting_status_request import ( +from adcp.types.domains.media_buy.get_reporting_status_request import ( DeliveryConfigId, GetReportingStatusRequest, Period, ReportingStatusView, ) -from adcp.types.generated_poc.media_buy.get_reporting_status_response import ( +from adcp.types.domains.media_buy.get_reporting_status_response import ( DeliveryConfigGeneration, GetReportingStatusResponse, ObligationCounts, ) -from adcp.types.generated_poc.media_buy.legacy_purchase_continuation_input import ( +from adcp.types.domains.media_buy.legacy_purchase_continuation_input import ( AcceptedLoss, CompatibilityPurchaseCoordinatorInput, SelectedProductId, ) -from adcp.types.generated_poc.media_buy.list_creative_formats_request import ( - ListCreativeFormatsRequest, -) -from adcp.types.generated_poc.media_buy.list_creative_formats_response import ( +from adcp.types.domains.media_buy.list_creative_formats_request import ListCreativeFormatsRequest +from adcp.types.domains.media_buy.list_creative_formats_response import ( CreativeAgent, ListCreativeFormatsResponse, ) -from adcp.types.generated_poc.media_buy.list_products_request import ListProductsRequest -from adcp.types.generated_poc.media_buy.list_products_response import ( +from adcp.types.domains.media_buy.list_products_request import ListProductsRequest +from adcp.types.domains.media_buy.list_products_response import ( IncompleteItem3, ListProductsResponse, ListProductsResponse1, ListProductsResponse2, ) -from adcp.types.generated_poc.media_buy.log_event_request import LogEventRequest -from adcp.types.generated_poc.media_buy.log_event_response import ( +from adcp.types.domains.media_buy.log_event_request import LogEventRequest +from adcp.types.domains.media_buy.log_event_response import ( LogEventResponse, LogEventResponse1, LogEventResponse2, PartialFailure, ) -from adcp.types.generated_poc.media_buy.media_buy_commitment_response import ( +from adcp.types.domains.media_buy.media_buy_commitment_response import ( MediaBuyCommitmentResponse, MediaBuyCommitmentResponse1, MediaBuyCommitmentResponse2, MediaBuyCommitmentResponse3, ) -from adcp.types.generated_poc.media_buy.media_buy_delivery_webhook_result import ( +from adcp.types.domains.media_buy.media_buy_delivery_webhook_result import ( MediaBuyDeliveryWebhookResult, ) -from adcp.types.generated_poc.media_buy.outcome_target import Goal, Goal1, OutcomeTarget -from adcp.types.generated_poc.media_buy.package_control import PackageControl -from adcp.types.generated_poc.media_buy.package_request import ( +from adcp.types.domains.media_buy.outcome_target import Goal, Goal1, OutcomeTarget +from adcp.types.domains.media_buy.package_control import PackageControl +from adcp.types.domains.media_buy.package_request import ( CommittedMetrics, CommittedMetrics1, CommittedMetrics2, PackageRequest, Qualifier, ) -from adcp.types.generated_poc.media_buy.package_update import ( +from adcp.types.domains.media_buy.package_update import ( KeywordTargetsAddItem, KeywordTargetsRemoveItem, NegativeKeywordsAddItem, NegativeKeywordsRemoveItem, PackageUpdate, ) -from adcp.types.generated_poc.media_buy.product_discovery_criteria import ProductDiscoveryCriteria -from adcp.types.generated_poc.media_buy.product_fields import ( - ProductResponseField, - ProductResponseFields, -) -from adcp.types.generated_poc.media_buy.product_purchase import ProductPurchase -from adcp.types.generated_poc.media_buy.product_purchase_input import ProductPurchaseInput -from adcp.types.generated_poc.media_buy.product_refinement import ( +from adcp.types.domains.media_buy.product_discovery_criteria import ProductDiscoveryCriteria +from adcp.types.domains.media_buy.product_fields import ProductResponseField, ProductResponseFields +from adcp.types.domains.media_buy.product_purchase import ProductPurchase +from adcp.types.domains.media_buy.product_purchase_input import ProductPurchaseInput +from adcp.types.domains.media_buy.product_refinement import ( Action11, ProductRefinementRequests, ProductRefinementRequests1, @@ -382,9 +360,9 @@ ProductRefinementRequests3, ProductRefinementRequests4, ) -from adcp.types.generated_poc.media_buy.proposal_budget_constraint import ProposalBudgetConstraint -from adcp.types.generated_poc.media_buy.proposal_decline import ProposalDecline -from adcp.types.generated_poc.media_buy.proposal_refinement import ( +from adcp.types.domains.media_buy.proposal_budget_constraint import ProposalBudgetConstraint +from adcp.types.domains.media_buy.proposal_decline import ProposalDecline +from adcp.types.domains.media_buy.proposal_refinement import ( Alternatives, ChangeKind, Constraints, @@ -408,28 +386,28 @@ ProposalRefinement8, ProposalRefinement9, ) -from adcp.types.generated_poc.media_buy.provide_performance_feedback_request import ( +from adcp.types.domains.media_buy.provide_performance_feedback_request import ( ProvidePerformanceFeedbackRequest, ) -from adcp.types.generated_poc.media_buy.provide_performance_feedback_response import ( +from adcp.types.domains.media_buy.provide_performance_feedback_response import ( ProvidePerformanceFeedbackResponse, ProvidePerformanceFeedbackResponse1, ProvidePerformanceFeedbackResponse2, ) -from adcp.types.generated_poc.media_buy.refine_proposals_async_response_input_required import ( +from adcp.types.domains.media_buy.refine_proposals_async_response_input_required import ( RefineProposalsInputRequired, ) -from adcp.types.generated_poc.media_buy.refine_proposals_async_response_submitted import ( +from adcp.types.domains.media_buy.refine_proposals_async_response_submitted import ( RefineProposalsSubmitted, ) -from adcp.types.generated_poc.media_buy.refine_proposals_async_response_working import ( +from adcp.types.domains.media_buy.refine_proposals_async_response_working import ( RefineProposalsWorking, ) -from adcp.types.generated_poc.media_buy.refine_proposals_request import ( +from adcp.types.domains.media_buy.refine_proposals_request import ( RefineProposalsRequest, Refinements, ) -from adcp.types.generated_poc.media_buy.refine_proposals_response import ( +from adcp.types.domains.media_buy.refine_proposals_response import ( Proposal2, Proposal3, Proposal4, @@ -452,17 +430,17 @@ TotalBudgetGuidance, UnsatisfiedConstraint, ) -from adcp.types.generated_poc.media_buy.request_proposals_async_response_input_required import ( +from adcp.types.domains.media_buy.request_proposals_async_response_input_required import ( RequestProposalsInputRequired, ) -from adcp.types.generated_poc.media_buy.request_proposals_async_response_submitted import ( +from adcp.types.domains.media_buy.request_proposals_async_response_submitted import ( RequestProposalsSubmitted, ) -from adcp.types.generated_poc.media_buy.request_proposals_async_response_working import ( +from adcp.types.domains.media_buy.request_proposals_async_response_working import ( RequestProposalsWorking, ) -from adcp.types.generated_poc.media_buy.request_proposals_request import RequestProposalsRequest -from adcp.types.generated_poc.media_buy.request_proposals_response import ( +from adcp.types.domains.media_buy.request_proposals_request import RequestProposalsRequest +from adcp.types.domains.media_buy.request_proposals_response import ( IncompleteItem5, IncompleteItem6, IncompleteItem7, @@ -483,29 +461,27 @@ RequestProposalsResponse4, SourceAdcpVersion, ) -from adcp.types.generated_poc.media_buy.sync_audiences_request import ( +from adcp.types.domains.media_buy.sync_audiences_request import ( AudienceType, SyncAudiencesRequest, Tag, ) -from adcp.types.generated_poc.media_buy.sync_audiences_response import ( +from adcp.types.domains.media_buy.sync_audiences_response import ( MatchBreakdown, SyncAudiencesResponse, SyncAudiencesResponse1, SyncAudiencesResponse2, SyncAudiencesResponse3, ) -from adcp.types.generated_poc.media_buy.sync_catalogs_async_response_input_required import ( +from adcp.types.domains.media_buy.sync_catalogs_async_response_input_required import ( SyncCatalogsInputRequired, ) -from adcp.types.generated_poc.media_buy.sync_catalogs_async_response_submitted import ( +from adcp.types.domains.media_buy.sync_catalogs_async_response_submitted import ( SyncCatalogsSubmitted, ) -from adcp.types.generated_poc.media_buy.sync_catalogs_async_response_working import ( - SyncCatalogsWorking, -) -from adcp.types.generated_poc.media_buy.sync_catalogs_request import SyncCatalogsRequest -from adcp.types.generated_poc.media_buy.sync_catalogs_response import ( +from adcp.types.domains.media_buy.sync_catalogs_async_response_working import SyncCatalogsWorking +from adcp.types.domains.media_buy.sync_catalogs_request import SyncCatalogsRequest +from adcp.types.domains.media_buy.sync_catalogs_response import ( Catalog, ItemIssue, SyncCatalogsResponse, @@ -513,45 +489,43 @@ SyncCatalogsResponse2, SyncCatalogsResponse3, ) -from adcp.types.generated_poc.media_buy.sync_event_sources_request import ( +from adcp.types.domains.media_buy.sync_event_sources_request import ( SyncEventSourcesRequest, ValueCurrency, ) -from adcp.types.generated_poc.media_buy.sync_event_sources_response import ( +from adcp.types.domains.media_buy.sync_event_sources_response import ( Setup, SyncEventSourcesResponse, SyncEventSourcesResponse1, SyncEventSourcesResponse2, ) -from adcp.types.generated_poc.media_buy.sync_reporting_receipts_request import ( +from adcp.types.domains.media_buy.sync_reporting_receipts_request import ( SyncReportingReceiptsRequest, ) -from adcp.types.generated_poc.media_buy.sync_reporting_receipts_response import ( +from adcp.types.domains.media_buy.sync_reporting_receipts_response import ( Results20, Results21, Results22, Results23, SyncReportingReceiptsResponse, ) -from adcp.types.generated_poc.media_buy.sync_reporting_status_request import ( - SyncReportingStatusRequest, -) -from adcp.types.generated_poc.media_buy.sync_reporting_status_response import ( +from adcp.types.domains.media_buy.sync_reporting_status_request import SyncReportingStatusRequest +from adcp.types.domains.media_buy.sync_reporting_status_response import ( Results26, Results27, SyncReportingStatusResponse, ) -from adcp.types.generated_poc.media_buy.update_media_buy_async_response_input_required import ( +from adcp.types.domains.media_buy.update_media_buy_async_response_input_required import ( UpdateMediaBuyInputRequired, ) -from adcp.types.generated_poc.media_buy.update_media_buy_async_response_submitted import ( +from adcp.types.domains.media_buy.update_media_buy_async_response_submitted import ( UpdateMediaBuySubmitted, ) -from adcp.types.generated_poc.media_buy.update_media_buy_async_response_working import ( +from adcp.types.domains.media_buy.update_media_buy_async_response_working import ( UpdateMediaBuyWorking, ) -from adcp.types.generated_poc.media_buy.update_media_buy_request import UpdateMediaBuyRequest -from adcp.types.generated_poc.media_buy.update_media_buy_response import ( +from adcp.types.domains.media_buy.update_media_buy_request import UpdateMediaBuyRequest +from adcp.types.domains.media_buy.update_media_buy_response import ( UpdateMediaBuyResponse, UpdateMediaBuyResponse1, UpdateMediaBuyResponse2, diff --git a/src/adcp/types/domains/media_buy/accept_proposal_async_response_input_required.py b/src/adcp/types/domains/media_buy/accept_proposal_async_response_input_required.py index d3e72a2af..4f4331caf 100644 --- a/src/adcp/types/domains/media_buy/accept_proposal_async_response_input_required.py +++ b/src/adcp/types/domains/media_buy/accept_proposal_async_response_input_required.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``media_buy/accept_proposal_async_response_input_required`` schema. +# generated by datamodel-codegen: +# filename: media_buy/accept_proposal_async_response_input_required.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.media_buy.accept_proposal_async_response_input_required import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.media_buy.accept_proposal_async_response_input_required import ( - AcceptProposalInputRequired, -) +from ..core.compact_task_input_required import CompactTaskInputRequired + -# Explicit exports -__all__ = ["AcceptProposalInputRequired"] +class AcceptProposalInputRequired(CompactTaskInputRequired): + pass diff --git a/src/adcp/types/domains/media_buy/accept_proposal_async_response_submitted.py b/src/adcp/types/domains/media_buy/accept_proposal_async_response_submitted.py index 9f9d35ee0..2a8b8d39b 100644 --- a/src/adcp/types/domains/media_buy/accept_proposal_async_response_submitted.py +++ b/src/adcp/types/domains/media_buy/accept_proposal_async_response_submitted.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``media_buy/accept_proposal_async_response_submitted`` schema. +# generated by datamodel-codegen: +# filename: media_buy/accept_proposal_async_response_submitted.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.media_buy.accept_proposal_async_response_submitted import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.media_buy.accept_proposal_async_response_submitted import ( - AcceptProposalSubmitted, -) +from ..core.compact_task_submitted import CompactTaskSubmitted + -# Explicit exports -__all__ = ["AcceptProposalSubmitted"] +class AcceptProposalSubmitted(CompactTaskSubmitted): + pass diff --git a/src/adcp/types/domains/media_buy/accept_proposal_async_response_working.py b/src/adcp/types/domains/media_buy/accept_proposal_async_response_working.py index 415846653..fa34afec8 100644 --- a/src/adcp/types/domains/media_buy/accept_proposal_async_response_working.py +++ b/src/adcp/types/domains/media_buy/accept_proposal_async_response_working.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``media_buy/accept_proposal_async_response_working`` schema. +# generated by datamodel-codegen: +# filename: media_buy/accept_proposal_async_response_working.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.media_buy.accept_proposal_async_response_working import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.media_buy.accept_proposal_async_response_working import ( - AcceptProposalWorking, -) +from ..core.compact_task_working import CompactTaskWorking + -# Explicit exports -__all__ = ["AcceptProposalWorking"] +class AcceptProposalWorking(CompactTaskWorking): + pass diff --git a/src/adcp/types/domains/media_buy/accept_proposal_request.py b/src/adcp/types/domains/media_buy/accept_proposal_request.py index 77b631729..db7a98330 100644 --- a/src/adcp/types/domains/media_buy/accept_proposal_request.py +++ b/src/adcp/types/domains/media_buy/accept_proposal_request.py @@ -1,24 +1,118 @@ -"""Types declared by the AdCP ``media_buy/accept_proposal_request`` schema. +# generated by datamodel-codegen: +# filename: media_buy/accept_proposal_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.accept_proposal_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat + +from ..core import canonical_account_ref +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core import push_notification_config as push_notification_config_1 +from ..core import reporting_webhook as reporting_webhook_1 +from ..core.opportunity_context import OpportunityContext +from ..core.version_envelope import AdcpVersionEnvelope + + +class TotalBudget(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + amount: Annotated[StrictFloat, Field(ge=0.0)] + currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] + + +class IoAcceptance(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + io_id: Annotated[str, Field(min_length=1)] + accepted_at: AwareDatetime + signatory: Annotated[str, Field(max_length=250, min_length=1)] + signature_id: Annotated[str | None, Field(min_length=1)] = None + + +class Status(StrEnum): + open = 'open' + closed = 'closed' + + +class Opportunity(OpportunityContext): + status: Annotated[ + Literal['closed'], + Field( + description='Whether the planning cycle remains open. On calls after initial creation, omission means no status update and MUST NOT reopen or close the opportunity implicitly, except that successful create_media_buy proposal execution explicitly infers accepted closure.' + ), + ] = 'closed' -from adcp.types.generated_poc.media_buy.accept_proposal_request import ( - AcceptProposalRequest, - IoAcceptance, - Opportunity, - Status, - TotalBudget, -) -# Explicit exports -__all__ = ["AcceptProposalRequest", "IoAcceptance", "Opportunity", "Status", "TotalBudget"] +class AcceptProposalRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='forbid', + ) + idempotency_key: Annotated[ + str, Field(max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$') + ] + name: Annotated[ + str | None, + Field( + description='Human-readable MediaBuy name supplied by the buyer for trafficking UI display and operational communication. When supplied, this value wins over proposal.name; the seller MUST persist it and echo it unchanged on the commitment success response and subsequent get_media_buys reads. It is operational metadata outside accepted_proposal and is not covered by proposal_terms_digest or terms_digest. When an acceptance creates a MediaBuy and name is absent, the seller MAY seed the MediaBuy name from proposal.name only when proposal.name already satisfies the MediaBuy name constraints (non-whitespace and no longer than 255 characters); the seller MUST NOT silently truncate or otherwise rewrite it. A seeded value counts as a name created through AdCP and MUST be reported on commitment and read surfaces. This display label is not an identifier or financial reference.', + max_length=255, + min_length=1, + pattern='\\S', + ), + ] = None + account: canonical_account_ref.CanonicalAccountReference + proposal_id: Annotated[str, Field(min_length=1)] + proposal_terms_digest: Annotated[ + str, + Field( + description='terms_digest from the committed proposal. The seller MUST atomically verify both ID and digest before acceptance.', + pattern='^sha256:[A-Za-z0-9_-]{43}$', + ), + ] + total_budget: Annotated[ + TotalBudget | None, + Field( + description='Execution amount when the committed proposal defines scalable percentages or constraints rather than a fixed total.' + ), + ] = None + daily_budget_cap: Annotated[ + StrictFloat | None, + Field( + description="Optional hard aggregate daily spend ceiling applied when the committed proposal is accepted. It constrains execution without changing the proposal's negotiated pricing.", + ge=0.0, + ), + ] = None + budget_cap_timezone: Annotated[ + str | None, + Field( + description='Optional shared IANA cap-day timezone override. Requires buyer_timezone_override support. When omitted, budget_capping.timezone_basis selects Account.timezone or fixed_timezone.', + min_length=1, + ), + ] = None + io_acceptance: IoAcceptance | None = None + purchase_order_ref: Annotated[str | None, Field(max_length=255, min_length=1)] = None + governance_context: Annotated[str | None, Field(max_length=4096, min_length=1)] = None + push_notification_config: push_notification_config_1.PushNotificationConfig | None = None + reporting_webhook: Annotated[ + reporting_webhook_1.ReportingWebhook | None, + Field( + description='Optional reporting delivery configuration established atomically when the proposal is accepted. This is execution metadata and does not alter the accepted commercial terms digest.' + ), + ] = None + opportunity: Annotated[ + Opportunity | None, + Field( + description='Optional planning-cycle closure. Success infers closed with accepted_with_seller when status is omitted. If the proposal carries opportunity_id, a supplied ID MUST match; the accepted proposal preserves that association.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/accept_proposal_response.py b/src/adcp/types/domains/media_buy/accept_proposal_response.py index 05674c4d2..6f82cf86b 100644 --- a/src/adcp/types/domains/media_buy/accept_proposal_response.py +++ b/src/adcp/types/domains/media_buy/accept_proposal_response.py @@ -1,46 +1,156 @@ -"""Types declared by the AdCP ``media_buy/accept_proposal_response`` schema. +# generated by datamodel-codegen: +# filename: media_buy/accept_proposal_response.json +# timestamp: 2026-10-04T04:34:38+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.accept_proposal_response import +from adcp.types.base import AdcpResponse, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any, Literal + +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field + +from ..core import canonical_media_buy_action +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 +from ..core.canonical_proposal import CanonicalProposal +from ..core.warning import Warning as Warning_1 +from ..enums import media_buy_status as media_buy_status_1 + + +class ProposalStatus(StrEnum): + draft = 'draft' + committed = 'committed' + accepted = 'accepted' + + +class PurchaseBinding(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + purchase_index: Annotated[SchemaInt, Field(ge=0)] + product_id: Annotated[str, Field(min_length=1)] + package_id: Annotated[str, Field(min_length=1)] + + +class Code(StrEnum): + inventory_shortfall_forecast = 'inventory_shortfall_forecast' + flight_change_creates_pacing_risk = 'flight_change_creates_pacing_risk' + fields_ignored_due_to_precedence = 'fields_ignored_due_to_precedence' + inventory_shortfall_forecast_1 = 'inventory_shortfall_forecast' + flight_change_creates_pacing_risk_1 = 'flight_change_creates_pacing_risk' + + +class AcceptProposalResponse4(AdCPBaseModel): + pass + + +class AcceptProposalResponse2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + status: Literal['failed'] = 'failed' + errors: Annotated[list[error.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + replayed: Literal[True] | None = None + + +class AcceptProposalResponse3(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + status: Literal['submitted'] = 'submitted' + task_id: Annotated[str, Field(min_length=1)] + message: Annotated[str | None, Field(max_length=2000)] = None + errors: list[error.Error] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + replayed: Literal[True] | None = None + + +class AcceptProposalResponse6(AdcpResponse, AcceptProposalResponse2, AcceptProposalResponse4): + pass + + +class AcceptProposalResponse7(AdcpResponse, AcceptProposalResponse3, AcceptProposalResponse4): + pass + + +class Warning(Warning_1): + code: Annotated[ + Code | None, + Field( + description='Closed warning vocabulary for the negotiated AdCP release. A non-blocking condition observed while an operation still succeeded. New values are added through normal protocol releases; buyers validate against the negotiated release.', + title='Warning Code', + ), + ] = None + + +class AcceptedProposal(CanonicalProposal): + proposal_status: Annotated[ + Literal['accepted'], + Field( + description='draft is indicative and unreserved; committed has firm terms with inventory reserved until expires_at; accepted is the historical snapshot attached to a MediaBuy.', + title='Proposal Status', + ), + ] = 'accepted' + media_buy_id: Annotated[str, Field(min_length=1)] + accepted_at: Any + commercial_terms: Any + terms_digest: Any + + +class AcceptProposalResponse1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + status: Literal['completed'] = 'completed' + media_buy_id: Annotated[str, Field(min_length=1)] + name: Annotated[ + str | None, + Field( + description='Persisted human-readable MediaBuy name for trafficking UI display and operational communication. The seller MUST echo a buyer-supplied request name unchanged; when the seller seeded a new MediaBuy name from an already-valid proposal.name, it MUST return that value unchanged here. Existing named MediaBuys return the stored value on amendment or cancellation commitments. This operational metadata is outside accepted_proposal and is not covered by terms_digest. This display label is not an identifier or financial reference.', + max_length=255, + min_length=1, + pattern='\\S', + ), + ] = None + revision: Annotated[SchemaInt, Field(ge=1)] + media_buy_status: media_buy_status_1.MediaBuyStatus | None = None + confirmed_at: AwareDatetime | None = None + accepted_proposal: AcceptedProposal + purchase_bindings: Annotated[ + list[PurchaseBinding], + Field( + description='Execution identities assigned to the immutable purchases. purchase_index is the zero-based position in accepted_proposal.commercial_terms.purchases and disambiguates repeated product IDs.', + min_length=1, + ), + ] + available_actions: list[canonical_media_buy_action.CanonicalMediaBuyAction] + warnings: Annotated[ + list[Warning] | None, + Field( + description='Non-blocking observations about this completed commitment. The MediaBuy was still created or amended exactly as represented. Continuing conditions also appear as indicators on get_media_buys.', + min_length=1, + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + replayed: Literal[True] | None = None + + +class AcceptProposalResponse5(AdcpResponse, AcceptProposalResponse1, AcceptProposalResponse4): + pass -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.media_buy.accept_proposal_response import ( - AcceptProposalResponse, - AcceptProposalResponse1, - AcceptProposalResponse2, - AcceptProposalResponse3, - AcceptProposalResponse4, - AcceptProposalResponse5, - AcceptProposalResponse6, - AcceptProposalResponse7, - AcceptedProposal, - Code, - ProposalStatus, - PurchaseBinding, - Warning, -) - -# Explicit exports -__all__ = [ - "AcceptProposalResponse", - "AcceptProposalResponse1", - "AcceptProposalResponse2", - "AcceptProposalResponse3", - "AcceptProposalResponse4", - "AcceptProposalResponse5", - "AcceptProposalResponse6", - "AcceptProposalResponse7", - "AcceptedProposal", - "Code", - "ProposalStatus", - "PurchaseBinding", - "Warning", +AcceptProposalResponse = Annotated[ + AcceptProposalResponse5 | AcceptProposalResponse6 | AcceptProposalResponse7, + Field( + description='Compact proposal-acceptance result containing the resulting MediaBuy identity and accepted immutable proposal snapshot.', + title='Accept Proposal Response', + ), ] diff --git a/src/adcp/types/domains/media_buy/acceptance_context.py b/src/adcp/types/domains/media_buy/acceptance_context.py index 1cb0d296c..82430b235 100644 --- a/src/adcp/types/domains/media_buy/acceptance_context.py +++ b/src/adcp/types/domains/media_buy/acceptance_context.py @@ -1,32 +1,64 @@ -"""Types declared by the AdCP ``media_buy/acceptance_context`` schema. +# generated by datamodel-codegen: +# filename: media_buy/acceptance_context.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.acceptance_context import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field + +from ..core import ext as ext_1 +from ..enums import advertiser_industry as advertiser_industry_1 + + +class SubjectFacet(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[a-z][a-z0-9_]*$'} + + +class Subject(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + subject_category: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] + subject_facets: Annotated[list[SubjectFacet] | None, Field(min_length=1)] = None + + +class AdvertiserRole(SubjectFacet): + pass + + +class AdvertiserJurisdiction(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{2}$'} + + +class DeliveryJurisdiction(AdvertiserJurisdiction): + pass -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.media_buy.acceptance_context import ( - AcceptanceContext, - AdvertiserJurisdiction, - AdvertiserRole, - DeliveryJurisdiction, - Subject, - SubjectFacet, -) - -# Explicit exports -__all__ = [ - "AcceptanceContext", - "AdvertiserJurisdiction", - "AdvertiserRole", - "DeliveryJurisdiction", - "Subject", - "SubjectFacet", -] +class AcceptanceContext(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + subjects: Annotated[list[Subject] | None, Field(min_length=1)] = None + advertiser_roles: Annotated[list[AdvertiserRole] | None, Field(min_length=1)] = None + advertiser_industry: advertiser_industry_1.AdvertiserIndustry | None = None + advertiser_jurisdictions: Annotated[ + list[AdvertiserJurisdiction] | None, + Field( + description='Jurisdictions in which the advertiser is established or legally organized. This is distinct from where an ad will be delivered.', + min_length=1, + ), + ] = None + delivery_jurisdictions: Annotated[ + list[DeliveryJurisdiction] | None, + Field( + description="Jurisdictions in which the proposed advertising will be delivered. Seller acceptance rules' jurisdictions and jurisdiction_groups match this field.", + min_length=1, + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/acceptance_policy_catalog.py b/src/adcp/types/domains/media_buy/acceptance_policy_catalog.py index 25c692ba3..faa7f51ea 100644 --- a/src/adcp/types/domains/media_buy/acceptance_policy_catalog.py +++ b/src/adcp/types/domains/media_buy/acceptance_policy_catalog.py @@ -1,18 +1,45 @@ -"""Types declared by the AdCP ``media_buy/acceptance_policy_catalog`` schema. +# generated by datamodel-codegen: +# filename: media_buy/acceptance_policy_catalog.json +# timestamp: 2026-10-04T01:14:11+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.acceptance_policy_catalog import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field, model_validator + +from ..core import ext as ext_1 +from . import acceptance_policy_profile, acceptance_policy_profile_ref -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.media_buy.acceptance_policy_catalog import AcceptancePolicyCatalog +class AcceptancePolicyCatalog(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + catalog_version: Annotated[str, Field(min_length=1)] + generated_at: AwareDatetime | None = None + profiles: Annotated[ + list[acceptance_policy_profile.AcceptancePolicyProfile] | None, Field(min_length=1) + ] = None + registry_profiles: Annotated[ + list[acceptance_policy_profile_ref.RegistryAcceptancePolicyProfileReference] | None, + Field( + description='Exact reusable profiles adopted from the shared policy registry. Resolution failure is unknown, never allowed. A seller adds a distinct local profile to narrow a registry profile.', + min_length=1, + ), + ] = None + ext: ext_1.ExtensionObject | None = None -# Explicit exports -__all__ = ["AcceptancePolicyCatalog"] + @model_validator(mode='after') + def _require_schema_required_group(self) -> AcceptancePolicyCatalog: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('profiles',), ('registry_profiles',),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'AcceptancePolicyCatalog requires at least one of these field groups: profiles | registry_profiles' + ) diff --git a/src/adcp/types/domains/media_buy/acceptance_policy_profile.py b/src/adcp/types/domains/media_buy/acceptance_policy_profile.py index 734d80374..63d64d68b 100644 --- a/src/adcp/types/domains/media_buy/acceptance_policy_profile.py +++ b/src/adcp/types/domains/media_buy/acceptance_policy_profile.py @@ -1,38 +1,126 @@ -"""Types declared by the AdCP ``media_buy/acceptance_policy_profile`` schema. +# generated by datamodel-codegen: +# filename: media_buy/acceptance_policy_profile.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.acceptance_policy_profile import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field, StringConstraints + +from ..core import ext as ext_1 +from . import acceptance_policy_rule + + +class PolicyRef(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + policy_id: Annotated[str, Field(min_length=1)] + version: Annotated[str, Field(min_length=1)] + content_digest: Annotated[ + str, + Field( + description="SHA-256 digest of the referenced policy entry's RFC 8785 JCS serialization with acceptance_profile omitted.", + pattern='^sha256:[a-f0-9]{64}$', + ), + ] + + +class Coverage(StrEnum): + partial = 'partial' + complete = 'complete' + + +class SubjectCategory(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[a-z][a-z0-9_]*$'} + + +class AppliesToEnum(StrEnum): + account = 'account' + media_buy = 'media_buy' + creative = 'creative' + landing_page = 'landing_page' + targeting = 'targeting' + delivery = 'delivery' + format = 'format' # type: ignore[assignment] + + +class Jurisdiction(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{2}$'} + + +class JurisdictionGroup(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z][A-Z0-9_-]*$'} + + +class Scope(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + subject_categories: Annotated[list[SubjectCategory], Field(min_length=1)] + applies_to: Annotated[list[AppliesToEnum], Field(min_length=1)] + jurisdictions: Annotated[list[Jurisdiction] | None, Field(min_length=1)] = None + jurisdiction_groups: Annotated[ + list[JurisdictionGroup] | None, + Field(description="Named groups declared in this profile's region_aliases.", min_length=1), + ] = None + all_jurisdictions: Annotated[ + Literal[True] | None, + Field( + description='Explicitly states that the scope is not limited by delivery jurisdiction.' + ), + ] = None + + +class RegionAliase(Jurisdiction): + pass -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.media_buy.acceptance_policy_profile import ( - AcceptancePolicyProfile, - AppliesToEnum, - Coverage, - Jurisdiction, - JurisdictionGroup, - PolicyRef, - RegionAliase, - Scope, - SubjectCategory, -) - -# Explicit exports -__all__ = [ - "AcceptancePolicyProfile", - "AppliesToEnum", - "Coverage", - "Jurisdiction", - "JurisdictionGroup", - "PolicyRef", - "RegionAliase", - "Scope", - "SubjectCategory", -] +class AcceptancePolicyProfile(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + profile_id: Annotated[str, Field(pattern='^[A-Za-z0-9_.:-]+$')] + version: Annotated[str, Field(min_length=1)] + content_digest: Annotated[ + str, + Field( + description='SHA-256 digest of the RFC 8785 JCS serialization of this profile with content_digest omitted. A profile_id/version pair is immutable; consumers reject a resolved profile whose digest differs.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] + policy_refs: Annotated[ + list[PolicyRef], + Field( + description='Exact registry policy versions from which this profile was derived. Consumers MUST NOT silently substitute a different version.', + min_length=1, + ), + ] + coverage: Annotated[ + Coverage, + Field( + description='partial means additional unpublished rules may apply and omission is unknown. complete means this profile is exhaustive only for its declared scope and version.' + ), + ] + scope: Annotated[ + Scope | None, + Field( + description='The boundary within which a complete profile claims exhaustiveness. It is informative for partial profiles and mandatory for complete profiles.' + ), + ] = None + region_aliases: Annotated[ + dict[Annotated[str, StringConstraints(pattern=r'^[A-Z][A-Z0-9_-]*$')], list[RegionAliase]] | None, + Field( + description='Profile-local named country groups. Rules may reference only keys declared here; consumers expand them before matching.' + ), + ] = None + description: Annotated[str | None, Field(min_length=1)] = None + rules: Annotated[list[acceptance_policy_rule.AcceptancePolicyRule], Field(min_length=1)] + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/acceptance_policy_profile_ref.py b/src/adcp/types/domains/media_buy/acceptance_policy_profile_ref.py index 6307a18e6..2fee5fa12 100644 --- a/src/adcp/types/domains/media_buy/acceptance_policy_profile_ref.py +++ b/src/adcp/types/domains/media_buy/acceptance_policy_profile_ref.py @@ -1,20 +1,22 @@ -"""Types declared by the AdCP ``media_buy/acceptance_policy_profile_ref`` schema. +# generated by datamodel-codegen: +# filename: media_buy/acceptance_policy_profile_ref.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.media_buy.acceptance_policy_profile_ref import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -from adcp.types.generated_poc.media_buy.acceptance_policy_profile_ref import ( - RegistryAcceptancePolicyProfileReference, -) -# Explicit exports -__all__ = ["RegistryAcceptancePolicyProfileReference"] +class RegistryAcceptancePolicyProfileReference(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + policy_id: Annotated[str, Field(min_length=1)] + policy_version: Annotated[str, Field(min_length=1)] + policy_digest: Annotated[str, Field(pattern='^sha256:[a-f0-9]{64}$')] + profile_id: Annotated[str, Field(pattern='^[A-Za-z0-9_.:-]+$')] + profile_version: Annotated[str, Field(min_length=1)] + profile_digest: Annotated[str, Field(pattern='^sha256:[a-f0-9]{64}$')] diff --git a/src/adcp/types/domains/media_buy/acceptance_policy_requirement.py b/src/adcp/types/domains/media_buy/acceptance_policy_requirement.py index e847ab0c2..978f25770 100644 --- a/src/adcp/types/domains/media_buy/acceptance_policy_requirement.py +++ b/src/adcp/types/domains/media_buy/acceptance_policy_requirement.py @@ -1,60 +1,221 @@ -"""Types declared by the AdCP ``media_buy/acceptance_policy_requirement`` schema. +# generated by datamodel-codegen: +# filename: media_buy/acceptance_policy_requirement.json +# timestamp: 2026-10-04T00:53:27+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.acceptance_policy_requirement import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import AwareDatetime, ConfigDict, Field + +from ..core import ext as ext_1 +from ..enums import restricted_attribute + + +class AcceptancePolicyRequirement1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['category_declaration'] = 'category_declaration' + declaration: Annotated[str | None, Field(min_length=1)] = None + description: Annotated[str | None, Field(min_length=1)] = None + + +class AcceptancePolicyRequirement2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['advertiser_verification'] = 'advertiser_verification' + verification_scheme: Annotated[str | None, Field(min_length=1)] = None + description: Annotated[str | None, Field(min_length=1)] = None + + +class Criterion(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[a-z][a-z0-9_.:-]*$'} + + +class AcceptancePolicyRequirement3(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['advertiser_eligibility'] = 'advertiser_eligibility' + criteria: Annotated[ + list[Criterion], + Field( + description='Stable criteria such as domestic_entity, citizen_or_resident, official_election_authority, or eligible_agency.', + min_length=1, + ), + ] + description: Annotated[str | None, Field(min_length=1)] = None + + +class AcceptancePolicyRequirement4(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['funding_restriction'] = 'funding_restriction' + criteria: Annotated[ + list[Criterion], + Field( + description='Stable restrictions such as no_foreign_funding or sponsor_identity_required.', + min_length=1, + ), + ] + description: Annotated[str | None, Field(min_length=1)] = None + + +class AcceptancePolicyRequirement5(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['certification'] = 'certification' + credential: Annotated[str | None, Field(min_length=1)] = None + description: Annotated[str | None, Field(min_length=1)] = None + + +class AcceptancePolicyRequirement6(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['license'] = 'license' + credential: Annotated[str | None, Field(min_length=1)] = None + description: Annotated[str | None, Field(min_length=1)] = None + + +class AcceptancePolicyRequirement7(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['prior_authorization'] = 'prior_authorization' + description: Annotated[str | None, Field(min_length=1)] = None + + +class AcceptancePolicyRequirement8(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['account_setup'] = 'account_setup' + description: Annotated[str | None, Field(min_length=1)] = None + + +class AcceptancePolicyRequirement9(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['sales_assisted'] = 'sales_assisted' + description: Annotated[str | None, Field(min_length=1)] = None + + +class AcceptancePolicyRequirement10(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['disclosure'] = 'disclosure' + format: Annotated[str | None, Field(min_length=1)] = None + placement: Annotated[str | None, Field(min_length=1)] = None + description: Annotated[str | None, Field(min_length=1)] = None + + +class AcceptancePolicyRequirement12(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['creative_restriction'] = 'creative_restriction' + description: Annotated[str, Field(min_length=1)] + + +class AcceptancePolicyRequirement13(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['destination_restriction'] = 'destination_restriction' + description: Annotated[str, Field(min_length=1)] + + +class FormatId(ScalarStr): + __slots__ = () + _constraints = {'min_length': 1} + + +class AcceptancePolicyRequirement14(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['format_restriction'] = 'format_restriction' + format_ids: Annotated[ + list[FormatId] | None, + Field( + deprecated=True, + description='Deprecated in AdCP 3.2 and removed in AdCP 4.0. This named-format restriction is retained for 3.x compatibility; new policies identify canonical format options in description or an extension until a registry-stable format-option identity is standardized.', + min_length=1, + ), + ] = None + description: Annotated[str | None, Field(min_length=1)] = None + + +class AcceptancePolicyRequirement15(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['time_restriction'] = 'time_restriction' + starts_at: AwareDatetime | None = None + ends_at: AwareDatetime | None = None + description: Annotated[str | None, Field(min_length=1)] = None + + +class AcceptancePolicyRequirement16(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['transparency_reporting'] = 'transparency_reporting' + description: Annotated[str | None, Field(min_length=1)] = None + + +class AcceptancePolicyRequirement11(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['targeting_restriction'] = 'targeting_restriction' + restricted_attributes: Annotated[ + list[restricted_attribute.RestrictedAttribute] | None, Field(min_length=1) + ] = None + description: Annotated[str | None, Field(min_length=1)] = None + + +class AcceptancePolicyRequirement17(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['custom'] = 'custom' + id: Annotated[str, Field(pattern='^[a-z][a-z0-9_.:-]*$')] + description: Annotated[str, Field(min_length=1)] + ext: ext_1.ExtensionObject | None = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.media_buy.acceptance_policy_requirement import ( - AcceptancePolicyRequirement, - AcceptancePolicyRequirement1, - AcceptancePolicyRequirement10, - AcceptancePolicyRequirement11, - AcceptancePolicyRequirement12, - AcceptancePolicyRequirement13, - AcceptancePolicyRequirement14, - AcceptancePolicyRequirement15, - AcceptancePolicyRequirement16, - AcceptancePolicyRequirement17, - AcceptancePolicyRequirement2, - AcceptancePolicyRequirement3, - AcceptancePolicyRequirement4, - AcceptancePolicyRequirement5, - AcceptancePolicyRequirement6, - AcceptancePolicyRequirement7, - AcceptancePolicyRequirement8, - AcceptancePolicyRequirement9, - Criterion, - FormatId, -) - -# Explicit exports -__all__ = [ - "AcceptancePolicyRequirement", - "AcceptancePolicyRequirement1", - "AcceptancePolicyRequirement10", - "AcceptancePolicyRequirement11", - "AcceptancePolicyRequirement12", - "AcceptancePolicyRequirement13", - "AcceptancePolicyRequirement14", - "AcceptancePolicyRequirement15", - "AcceptancePolicyRequirement16", - "AcceptancePolicyRequirement17", - "AcceptancePolicyRequirement2", - "AcceptancePolicyRequirement3", - "AcceptancePolicyRequirement4", - "AcceptancePolicyRequirement5", - "AcceptancePolicyRequirement6", - "AcceptancePolicyRequirement7", - "AcceptancePolicyRequirement8", - "AcceptancePolicyRequirement9", - "Criterion", - "FormatId", +AcceptancePolicyRequirement = Annotated[ + AcceptancePolicyRequirement1 + | AcceptancePolicyRequirement2 + | AcceptancePolicyRequirement3 + | AcceptancePolicyRequirement4 + | AcceptancePolicyRequirement5 + | AcceptancePolicyRequirement6 + | AcceptancePolicyRequirement7 + | AcceptancePolicyRequirement8 + | AcceptancePolicyRequirement9 + | AcceptancePolicyRequirement10 + | AcceptancePolicyRequirement11 + | AcceptancePolicyRequirement12 + | AcceptancePolicyRequirement13 + | AcceptancePolicyRequirement14 + | AcceptancePolicyRequirement15 + | AcceptancePolicyRequirement16 + | AcceptancePolicyRequirement17, + Field( + description='A typed prerequisite or restriction attached to a conditional seller-acceptance rule. The kind is coarse and machine-actionable; exact obligations remain in the referenced registry policies.', + discriminator='kind', + title='Acceptance Policy Requirement', + ), ] diff --git a/src/adcp/types/domains/media_buy/acceptance_policy_rule.py b/src/adcp/types/domains/media_buy/acceptance_policy_rule.py index 91099f401..3186497ba 100644 --- a/src/adcp/types/domains/media_buy/acceptance_policy_rule.py +++ b/src/adcp/types/domains/media_buy/acceptance_policy_rule.py @@ -1,36 +1,120 @@ -"""Types declared by the AdCP ``media_buy/acceptance_policy_rule`` schema. +# generated by datamodel-codegen: +# filename: media_buy/acceptance_policy_rule.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.acceptance_policy_rule import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import AwareDatetime, ConfigDict, Field + +from ..core import ext as ext_1 +from . import acceptance_policy_requirement + + +class SubjectFacet(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[a-z][a-z0-9_]*$'} + + +class AdvertiserRole(SubjectFacet): + pass + + +class Jurisdiction(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{2}$'} + + +class JurisdictionGroup(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z][A-Z0-9_-]*$'} + + +class AppliesToEnum(StrEnum): + account = 'account' + media_buy = 'media_buy' + creative = 'creative' + landing_page = 'landing_page' + targeting = 'targeting' + delivery = 'delivery' + format = 'format' # type: ignore[assignment] + + +class Disposition(StrEnum): + allowed = 'allowed' + conditional = 'conditional' + prohibited = 'prohibited' + + +class PolicyId(ScalarStr): + __slots__ = () + _constraints = {'min_length': 1} -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.media_buy.acceptance_policy_rule import ( - AcceptancePolicyRule, - AdvertiserRole, - AppliesToEnum, - Disposition, - Jurisdiction, - JurisdictionGroup, - PolicyId, - SubjectFacet, -) - -# Explicit exports -__all__ = [ - "AcceptancePolicyRule", - "AdvertiserRole", - "AppliesToEnum", - "Disposition", - "Jurisdiction", - "JurisdictionGroup", - "PolicyId", - "SubjectFacet", -] +class AcceptancePolicyRule(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + rule_id: Annotated[str, Field(pattern='^[A-Za-z0-9_.:-]+$')] + subject_category: Annotated[ + str, + Field( + description='Registry policy-category-definition category_id. Named subject_category to avoid collision with PolicyEntry.category, whose values are regulation and standard.', + pattern='^[a-z][a-z0-9_]*$', + ), + ] + subject_facets: Annotated[ + list[SubjectFacet] | None, + Field( + description='Facet IDs defined by the selected policy category. Omission means the rule applies to every facet in the category.', + min_length=1, + ), + ] = None + advertiser_roles: Annotated[ + list[AdvertiserRole] | None, + Field( + description='Registry-extensible roles such as political_actor, election_authority, government_entity, news_publisher, or commercial_advertiser.', + min_length=1, + ), + ] = None + jurisdictions: Annotated[ + list[Jurisdiction] | None, + Field( + description='Delivery jurisdictions where this rule applies. Omission means every jurisdiction served by the seller.', + min_length=1, + ), + ] = None + jurisdiction_groups: Annotated[ + list[JurisdictionGroup] | None, + Field( + description="Named country groups declared by the containing profile's region_aliases. Unknown group IDs invalidate the profile; they never match permissively.", + min_length=1, + ), + ] = None + applies_to: Annotated[list[AppliesToEnum], Field(min_length=1)] + disposition: Disposition + requirements: Annotated[ + list[acceptance_policy_requirement.AcceptancePolicyRequirement] | None, Field(min_length=1) + ] = None + policy_ids: Annotated[ + list[PolicyId] | None, + Field( + description='Registry policies that define the exact obligations behind this coarse rule.', + min_length=1, + ), + ] = None + description: Annotated[ + str | None, + Field( + description='Display-only explanation. Matchers MUST NOT interpret this text as executable instructions or use it to override typed fields.', + max_length=1000, + min_length=1, + ), + ] = None + effective_at: AwareDatetime | None = None + expires_at: AwareDatetime | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/build_creative_async_response_input_required.py b/src/adcp/types/domains/media_buy/build_creative_async_response_input_required.py index 4be27e035..2114f2a69 100644 --- a/src/adcp/types/domains/media_buy/build_creative_async_response_input_required.py +++ b/src/adcp/types/domains/media_buy/build_creative_async_response_input_required.py @@ -1,21 +1,38 @@ -"""Types declared by the AdCP ``media_buy/build_creative_async_response_input_required`` schema. +# generated by datamodel-codegen: +# filename: media_buy/build_creative_async_response_input_required.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.build_creative_async_response_input_required import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 + + +class Reason(StrEnum): + APPROVAL_REQUIRED = 'APPROVAL_REQUIRED' + CREATIVE_DIRECTION_NEEDED = 'CREATIVE_DIRECTION_NEEDED' + ASSET_SELECTION_NEEDED = 'ASSET_SELECTION_NEEDED' -from adcp.types.generated_poc.media_buy.build_creative_async_response_input_required import ( - BuildCreativeInputRequired, - Reason, -) -# Explicit exports -__all__ = ["BuildCreativeInputRequired", "Reason"] +class BuildCreativeInputRequired(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + reason: Annotated[ + Reason | None, Field(description='Reason code indicating why input is needed') + ] = None + errors: Annotated[ + list[error.Error] | None, + Field( + description='Optional validation errors or warnings explaining why input is required.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/build_creative_async_response_submitted.py b/src/adcp/types/domains/media_buy/build_creative_async_response_submitted.py index 3da07171d..d4f0712e5 100644 --- a/src/adcp/types/domains/media_buy/build_creative_async_response_submitted.py +++ b/src/adcp/types/domains/media_buy/build_creative_async_response_submitted.py @@ -1,20 +1,47 @@ -"""Types declared by the AdCP ``media_buy/build_creative_async_response_submitted`` schema. +# generated by datamodel-codegen: +# filename: media_buy/build_creative_async_response_submitted.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.build_creative_async_response_submitted import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 -from adcp.types.generated_poc.media_buy.build_creative_async_response_submitted import ( - BuildCreativeSubmitted, -) -# Explicit exports -__all__ = ["BuildCreativeSubmitted"] +class BuildCreativeSubmitted(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + status: Annotated[ + Literal['submitted'], + Field( + description='Task-level status literal. Discriminates this async envelope from the synchronous success shapes, whose creative_manifest or creative_manifests are issued in-line. See task-status.json for the full task-status enum.' + ), + ] = 'submitted' + task_id: Annotated[ + str, + Field( + description='Task handle the buyer uses with get_task_status (or the legacy AdCP tasks/get alias), and that the seller references on push-notification callbacks. This AdCP application-layer handle remains the snake_case task_id in every transport payload and is distinct from any transport-native A2A Task id.' + ), + ] + message: Annotated[ + str | None, + Field( + description="Optional human-readable explanation of why the task is submitted — e.g., 'Generative build queued; typical turnaround 3–5 minutes.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", + max_length=2000, + ), + ] = None + errors: Annotated[ + list[error.Error] | None, + Field( + description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/build_creative_async_response_working.py b/src/adcp/types/domains/media_buy/build_creative_async_response_working.py index be2aca94e..58a3cfee1 100644 --- a/src/adcp/types/domains/media_buy/build_creative_async_response_working.py +++ b/src/adcp/types/domains/media_buy/build_creative_async_response_working.py @@ -1,20 +1,34 @@ -"""Types declared by the AdCP ``media_buy/build_creative_async_response_working`` schema. +# generated by datamodel-codegen: +# filename: media_buy/build_creative_async_response_working.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.build_creative_async_response_working import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import context as context_1 +from ..core import ext as ext_1 -from adcp.types.generated_poc.media_buy.build_creative_async_response_working import ( - BuildCreativeWorking, -) -# Explicit exports -__all__ = ["BuildCreativeWorking"] +class BuildCreativeWorking(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + percentage: Annotated[ + StrictFloat | None, Field(description='Completion percentage (0-100)', ge=0.0, le=100.0) + ] = None + current_step: Annotated[ + str | None, + Field( + description="Current step or phase of the operation (e.g., 'generating_assets', 'resolving_macros', 'rendering_preview')" + ), + ] = None + total_steps: Annotated[ + SchemaInt | None, Field(description='Total number of steps in the operation', ge=1) + ] = None + step_number: Annotated[SchemaInt | None, Field(description='Current step number', ge=1)] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/build_creative_request.py b/src/adcp/types/domains/media_buy/build_creative_request.py index ef91efebf..6a3c5a0f4 100644 --- a/src/adcp/types/domains/media_buy/build_creative_request.py +++ b/src/adcp/types/domains/media_buy/build_creative_request.py @@ -1,52 +1,473 @@ -"""Types declared by the AdCP ``media_buy/build_creative_request`` schema. +# generated by datamodel-codegen: +# filename: media_buy/build_creative_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.build_creative_request import +from adcp.types.base import AdcpRequest, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any, Literal + +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field, StrictBool, StrictFloat + +from ..core import account_ref, brand_ref +from ..core import context as context_1 +from ..core import creative_manifest as creative_manifest_1 +from ..core import creative_representation_set as creative_representation_set_1 +from ..core import evaluator_spec +from ..core import ext as ext_1 +from ..core import format_id +from ..core import push_notification_config as push_notification_config_1 +from ..core import representation_destination as representation_destination_1 +from ..core import signal_id as signal_id_1 +from ..core import signal_ref as signal_ref_1 +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import creative_quality, creative_selection_strategy +from ..enums import preview_output_format as preview_output_format_1 +from ..enums import representation_selection_strategy as representation_selection_strategy_1 + + +class TargetCapabilityId(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[a-zA-Z0-9_-]+$'} + + +class Mode(StrEnum): + execute = 'execute' + estimate = 'estimate' + + +class MaxSpend(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + amount: Annotated[ + StrictFloat, + Field( + description='Maximum aggregate vendor_cost to incur on this call, in `currency`.', + ge=0.0, + ), + ] + currency: Annotated[ + str, Field(description='ISO 4217 currency; MUST match the rate card.', pattern='^[A-Z]{3}$') + ] + + +class SignalCondition4(AdCPBaseModel): + signal_agent_segment_id: Annotated[ + str | None, + Field( + description="Optional opaque resolved-segment handle for this fan-out condition — the RESOLVED condition identity, distinct from signal_ref's DEFINITION identity. When get_signals or a product signal_targeting_options entry exposed a signal_agent_segment_id for the targeted signal, echo it here verbatim so the produced creative's signal_condition carries the resolved-segment identity that the trafficking-compatibility check (SIGNAL_TARGETING_INCOMPATIBLE) matches on exactly. Providers MAY namespace it (e.g. provider_a:weather:rain_realtime vs provider_b:precip:high) so cross-provider conditions stay distinct without a shared taxonomy registry; treat as opaque, do not parse the namespace for business logic. Prefer it over reconstructing condition identity from categorical values — categorical {signal_ref,value} identity is the weaker fallback, appropriate only for inherently-categorical signals with no resolved handle, and never a cross-provider equivalence claim." + ), + ] = None + + +class Dimension(StrEnum): + voice = 'voice' + theme = 'theme' + best_of_n = 'best_of_n' + transformer_config = 'transformer_config' + custom = 'custom' + + +class VariantAxis(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + dimension: Annotated[ + Dimension, + Field( + description='What varies across variants. `transformer_config` varies a named config param (name it in `field`); `best_of_n` lets the agent explore and rank; `custom` is agent-defined (describe in `label`).' + ), + ] + field: Annotated[ + str | None, + Field( + description='The transformer `config` param `field` this axis sweeps. REQUIRED when `dimension` is `transformer_config`; `values[]` are then interpreted as values for `config[field]`. Omit for other dimensions.' + ), + ] = None + values: Annotated[ + list[Any] | None, + Field( + description='Caller-fixed set of values for the axis (e.g. ["isaac", "sara"] for dimension `voice`). When present, len(values) is the variant count and is authoritative over max_variants.', + min_length=1, + ), + ] = None + label: Annotated[ + str | None, + Field(description='Human-readable description of the axis, especially for `custom`.'), + ] = None + + +class KeepMode(StrEnum): + keep_all = 'keep_all' + keep_one = 'keep_one' + keep_some = 'keep_some' + + +class PreviewInput(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + name: Annotated[ + str, + Field( + description="Human-readable name for this input set (e.g., 'Sunny morning on mobile', 'Evening podcast ad')" + ), + ] + macros: Annotated[ + dict[str, str] | None, Field(description='Macro values to use for this preview variant') + ] = None + context_description: Annotated[ + str | None, + Field(description='Natural language description of the context for AI-generated content'), + ] = None + + +class SignalCondition1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + signal_ref: Annotated[ + signal_ref_1.SignalRef | None, + Field(description='The signal to target. New targeting constraints SHOULD use signal_ref.'), + ] = None + signal_id: Annotated[ + signal_id_1.SignalId | None, + Field( + deprecated=True, + description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', + ), + ] = None + value_type: Annotated[Literal['binary'], Field(description='Discriminator for binary signals')] = 'binary' + value: Annotated[ + StrictBool, + Field( + description='Whether to include (true) or exclude (false) users matching this signal' + ), + ] + + +class SignalCondition2(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + signal_ref: Annotated[ + signal_ref_1.SignalRef | None, + Field(description='The signal to target. New targeting constraints SHOULD use signal_ref.'), + ] = None + signal_id: Annotated[ + signal_id_1.SignalId | None, + Field( + deprecated=True, + description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', + ), + ] = None + value_type: Annotated[ + Literal['categorical'], Field(description='Discriminator for categorical signals') + ] = 'categorical' + values: Annotated[ + list[str], + Field( + description='Values to target. Users with any of these values will be included.', + min_length=1, + ), + ] + + +class SignalCondition3(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + signal_ref: Annotated[ + signal_ref_1.SignalRef | None, + Field(description='The signal to target. New targeting constraints SHOULD use signal_ref.'), + ] = None + signal_id: Annotated[ + signal_id_1.SignalId | None, + Field( + deprecated=True, + description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', + ), + ] = None + value_type: Annotated[ + Literal['numeric'], Field(description='Discriminator for numeric signals') + ] = 'numeric' + min_value: Annotated[ + StrictFloat | None, + Field( + description="Minimum value (inclusive). Omit for no minimum. Must be <= max_value when both are provided. Should be >= signal's range.min if defined." + ), + ] = None + max_value: Annotated[ + StrictFloat | None, + Field( + description="Maximum value (inclusive). Omit for no maximum. Must be >= min_value when both are provided. Should be <= signal's range.max if defined." + ), + ] = None + + +class SignalCondition5(SignalCondition1, SignalCondition4): + pass + + +class SignalCondition6(SignalCondition2, SignalCondition4): + pass + + +class SignalCondition7(SignalCondition3, SignalCondition4): + pass + + +SignalCondition = SignalCondition5 | SignalCondition6 | SignalCondition7 -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.media_buy.build_creative_request import ( - BuildCreativeRequest, - Dimension, - KeepMode, - MaxSpend, - Mode, - PreviewInput, - SignalCondition, - SignalCondition1, - SignalCondition2, - SignalCondition3, - SignalCondition4, - SignalCondition5, - SignalCondition6, - SignalCondition7, - TargetCapabilityId, - VariantAxis, -) - -# Explicit exports -__all__ = [ - "BuildCreativeRequest", - "Dimension", - "KeepMode", - "MaxSpend", - "Mode", - "PreviewInput", - "SignalCondition", - "SignalCondition1", - "SignalCondition2", - "SignalCondition3", - "SignalCondition4", - "SignalCondition5", - "SignalCondition6", - "SignalCondition7", - "TargetCapabilityId", - "VariantAxis", -] +class BuildCreativeRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + governance_context: Annotated[ + str | None, + Field( + description='Opaque intent authorization when this creative execution incurs vendor cost.', + max_length=4096, + min_length=1, + pattern='^[\\x20-\\x7E]+$', + ), + ] = None + message: Annotated[ + str | None, + Field( + description='Natural language instructions for the transformation or generation. For pure generation, this is the creative brief. For transformation, this provides guidance on how to adapt the creative. For refinement, this describes the desired changes.' + ), + ] = None + creative_manifest: Annotated[ + creative_manifest_1.CreativeManifest | None, + Field( + description='Creative manifest to transform or generate from. On the canonical 3.2 path it carries `format_kind`, optional `format_option_ref`, and the required input assets. For transformation (for example resizing or reformatting), this is the complete creative to adapt. When creative_id is provided, the agent resolves the creative from its library and this field is ignored.' + ), + ] = None + creative_representation_set: Annotated[ + creative_representation_set_1.CreativeRepresentationSet | None, + Field( + description="Complete creative revision containing equivalent trafficking representations, of which exactly one is selected for this seller-bound output. This mode is accepted only by the destination sales agent and requires representation_destination plus representation_selection_strategy. The target capability selects the seller's build route; representation_destination supplies the binding inventory contract. The resolver verifies revision_content_digest against the complete set, retains every representation unchanged, selects exactly one compatible representation, and returns a manifest carrying representation_selection. If macro_values is present, selection happens first and binding affects only the derived output; the retained representation set and its revision binding never change. When none is compatible, the request fails with CREATIVE_REPRESENTATION_UNRESOLVED and one representation_rejections entry per candidate." + ), + ] = None + representation_destination: Annotated[ + representation_destination_1.RepresentationDestination | None, + Field( + description='Seller-owned product and effective format context for representation resolution. Required only with creative_representation_set and meaningful only when this endpoint is the destination sales agent.' + ), + ] = None + representation_selection_strategy: Annotated[ + representation_selection_strategy_1.RepresentationSelectionStrategy | None, + Field( + description='Deterministic strategy to apply after compatibility filtering. Required with creative_representation_set and MUST be advertised by creative.representation_resolution.strategies.' + ), + ] = None + creative_id: Annotated[ + str | None, + Field( + description="Reference to a creative in the agent's library. The creative agent resolves this to a manifest from its library. Use this instead of creative_manifest when retrieving an existing creative for tag generation or format adaptation." + ), + ] = None + concept_id: Annotated[ + str | None, + Field( + description='Creative concept containing the creative. Creative agents SHOULD assign globally unique creative_id values; when they cannot guarantee uniqueness, concept_id is REQUIRED to disambiguate.' + ), + ] = None + media_buy_id: Annotated[ + str | None, + Field( + description='Media buy identifier for tag generation context. When the creative agent is also the ad server, this provides the trafficking context needed to generate placement-specific tags (e.g., CM360 placement ID). Not needed when tags are generated at the creative level (most creative platforms).' + ), + ] = None + package_id: Annotated[ + str | None, + Field( + description='Package identifier within the media buy. Used with media_buy_id when the creative agent needs line-item-level context for tag generation. Omit to get a tag not scoped to a specific package.' + ), + ] = None + target_format_id: Annotated[ + format_id.FormatReferenceStructuredObject | None, + Field( + deprecated=True, + description='**DEPRECATED in 3.2.** Legacy named-format selector. Use `target_capability_id` with a value advertised in `get_adcp_capabilities.creative.supported_formats[].capability_id`.', + ), + ] = None + target_format_ids: Annotated[ + list[format_id.FormatReferenceStructuredObject] | None, + Field( + deprecated=True, + description='**DEPRECATED in 3.2.** Legacy named-format selectors. Use `target_capability_ids` with values advertised in `get_adcp_capabilities.creative.supported_formats[].capability_id`.', + max_length=50, + min_length=1, + ), + ] = None + target_capability_id: Annotated[ + str | None, + Field( + description='Canonical 3.2 single-output selector. Matches exactly one `get_adcp_capabilities.creative.supported_formats[].capability_id` advertised by this creative agent. The matched entry supplies the canonical `format` declaration used to validate inputs and the returned manifest. Mutually exclusive with `target_capability_ids` and the deprecated target_format_id fields.', + pattern='^[a-zA-Z0-9_-]+$', + ), + ] = None + target_capability_ids: Annotated[ + list[TargetCapabilityId] | None, + Field( + description='Canonical 3.2 multi-output selector. Each value matches a `get_adcp_capabilities.creative.supported_formats[].capability_id`. The creative agent produces one canonical manifest per capability in request order. Mutually exclusive with `target_capability_id` and the deprecated target_format_id fields.', + max_length=50, + min_length=1, + ), + ] = None + transformer_id: Annotated[ + str | None, + Field( + description="Selects an account-scoped transformer (discovered via list_transformers) to perform the build. One transformer per call. When present, the build uses this transformer and target_capability_id/target_capability_ids select which of its outputs to produce — they MUST be a subset of the transformer's output_capability_ids. Deprecated target_format_id fields use the legacy output_format_ids compatibility path. Render configuration goes in `config`." + ), + ] = None + config: Annotated[ + dict[str, Any] | None, + Field( + description='Typed render configuration for the selected transformer, keyed by each param\'s `field` (from the transformer\'s params[] in list_transformers). Example: { "voice": "isaac", "speaking_rate": 1.1, "mastering_preset": "podcast" }. The agent MUST validate `config` against the transformer\'s live params for this account and reject unrecognized keys and out-of-range / non-enumerated values with a field-attributed error (e.g. `config.voice`) rather than silently ignoring them — config drives a paid render. Genuinely vendor-specific or experimental knobs not declared as params belong in `ext`, not here. (The schema leaves this object open because legal keys are dynamic per transformer; strict validation is a normative agent obligation.) When `refine_from_build_variant_id` is set, `config` is applied as a DELTA over the parent leaf\'s config.' + ), + ] = None + refine_from_build_variant_id: Annotated[ + str | None, + Field( + description='Refine a previously produced variant and return new lineage-linked variants. The transformer and target capability are inherited from the parent leaf. A refinement request MUST omit transformer_id, target_capability_id(s), and deprecated target_format_id(s); changing transformer or output format is a new transformation build, not refinement. Requires creative.supports_refinement.' + ), + ] = None + mode: Annotated[ + Mode | None, + Field( + description="`execute` (default) produces and bills the creative(s). `estimate` is a DRY RUN: the agent produces nothing and bills nothing, and returns a BuildCreativeEstimate with a projected cost band (cost_low/cost_high) computed against THIS request's actual inputs (script length, brief, catalog size, max_creatives × max_variants) — the band the buyer cannot derive itself, since per_unit gives the rate but not the unit count. Requires the agent to advertise `creative.supports_spend_controls`; otherwise rejected with `UNSUPPORTED_FEATURE`." + ), + ] = Mode.execute + max_spend: Annotated[ + MaxSpend | None, + Field( + description='Hard per-call spend ceiling. The agent produces leaves until the NEXT leaf would push the run\'s aggregate vendor_cost over `amount`, then STOPS and returns the partial BuildCreativeVariantSuccess produced so far with `budget_status: "capped"` (every returned leaf is real, trafficable, and billed — nothing produced is discarded; the leaf shortfall is `leaves_returned` < `leaves_total`). If even the first leaf would exceed the cap, the call fails with BUDGET_CAP_REACHED. `currency` MUST match the rate card\'s currency (the agent does not FX-convert) or the request is rejected with INVALID_REQUEST (error.field `max_spend.currency`). Requires `creative.supports_spend_controls`. Caps a SINGLE call — to bound a refinement loop, track aggregate vendor_cost across calls and stop issuing them (buyer responsibility in this revision). max_spend bounds only build-time vendor_cost: CPM-priced builds (estimate basis `cpm_deferred`) have build-time vendor_cost 0 and accrue at serve time, so max_spend never engages for them — bound a CPM fan-out with max_creatives instead.' + ), + ] = None + max_creatives: Annotated[ + SchemaInt | None, + Field( + description='Caps how many DISTINCT creatives to produce along the catalog/item fan-out axis — one creative per catalog item. Use it to sample a large catalog (e.g. send 150 job openings, set max_creatives: 5 to preview five). Distinct from item_limit, which caps how many catalog items a SINGLE creative consumes (DCO-style). Omitted with a catalog input means one creative per item up to the catalog/format bound; omitted without a catalog collapses to a single creative. Large fan-outs may return asynchronously. Mutually exclusive with `refine_from_build_variant_id` (refinement targets one prior creative, not a catalog fan-out). Supported only when the agent advertises `creative.multiplicity.supports_catalog_fanout`; values above `max_creatives_limit` are clamped. Pair with `max_spend` to bound the bill of a large fan-out.', + ge=1, + ), + ] = None + signal_conditions: Annotated[ + list[SignalCondition] | None, + Field( + description="Advisory keep-all PRODUCTION axis: produce one distinct creative group per signal condition, each kept and trafficked with its own signal targeting (e.g. a rain creative AND a sun creative). Sibling to max_creatives (catalog axis), NOT a variant_axis value (which is choose-among). Each item reuses SignalTargeting (value_type-discriminated binary/categorical/numeric over signal_ref) so the produced group's signal_condition resolves condition identity through the SAME schema the sales-side package targeting uses, plus an optional signal_agent_segment_id carrying the RESOLVED-segment identity (vs signal_ref's definition identity) — echo a provider-exposed handle verbatim; it is the primary trafficking-compatibility key, with categorical signal_ref+value as the weaker fallback. Per #5280 this is an ADVISORY context pointer — it informs production and MUST NOT hard-block at the build_creative layer; trafficking-compatibility (a sun creative MUST NOT serve into rain-targeted packages) is enforced reject-at-trafficking on the sales side (SIGNAL_TARGETING_INCOMPATIBLE), not here. Triggers the BuildCreativeVariantSuccess shape. Supported only when the agent advertises creative.multiplicity.supports_signal_fanout; condition counts above max_signal_conditions_limit are CLAMPED (not rejected), consistent with max_creatives. Composes with max_creatives (catalog × conditions cross-product) and max_variants (variants per group).", + min_length=1, + ), + ] = None + max_variants: Annotated[ + SchemaInt | None, + Field( + description='Caps how many ALTERNATIVES to produce per creative (different voices, themes, best-of-N, etc.). Default 1 preserves single-output behavior. Each variant is a real, independently-billed build (you pay for all produced); the buyer keeps one or many. When variant_axis.values[] is provided, its length is authoritative over max_variants. Resolutions/quality tiers are NOT variants — request them as additional target formats.', + ge=1, + ), + ] = 1 + variant_axis: Annotated[ + VariantAxis | None, + Field( + description='Declares the dimension along which variants differ. When `values` is provided, the agent produces exactly one variant per value (e.g. an A/B of two voices). When only `dimension` is provided, the agent chooses up to max_variants variants along that dimension (e.g. best-of-N, themes).' + ), + ] = None + keep_mode: Annotated[ + KeepMode | None, + Field( + description='Advisory hint for how the buyer intends to use the variants. `keep_one` (best-of-N) and `keep_some` signal the agent to set `recommended`/`rank` on returned variants. Advisory only — it does not change what is returned or billed; every produced variant is returned and charged. Keeping is a client act of trafficking the chosen build_variant_id(s).' + ), + ] = KeepMode.keep_all + selection_strategy: Annotated[ + creative_selection_strategy.CreativeSelectionStrategy | None, + Field( + description='Governs HOW the agent samples when max_creatives < items_total (folds #5262). audience_relevance draws its ranking input from the SAME signal_ref pointers in signal_conditions / package targeting — NOT a parallel signals[] array. proximity takes a location input (geo shape TBD — WG open). inventory_priority is seller-side catalog metadata (margin/overstock/promo; no buyer input). random is the status-quo default. Per-creative selection ordering surfaces on the existing rank / recommended fields of creatives[].variants[], not a new selection_rank. Advisory; absent => agent default (random).' + ), + ] = None + account: Annotated[ + account_ref.AccountReference | None, + Field( + description='Account reference for pricing and billing. When present, the creative agent applies account-specific pricing from the rate card, records the build against the account for billing, and can enforce account-level quotas or entitlements. Required by creative agents that charge for their services.' + ), + ] = None + brand: Annotated[ + brand_ref.BrandReference | None, + Field( + description='Brand reference for creative generation. Resolved to full brand identity (colors, logos, tone) at execution time.' + ), + ] = None + quality: Annotated[ + creative_quality.CreativeQuality | None, + Field( + description="Quality tier for generation. 'draft' produces fast, lower-fidelity output for iteration and review. 'production' produces full-quality output for final delivery. If omitted, the creative agent uses its own default. For non-generative transforms (e.g., format resizing), creative agents MAY ignore this field." + ), + ] = None + evaluator: Annotated[ + evaluator_spec.EvaluatorSpec | None, + Field( + description="Optional advisory evaluator (buyer-attached pointer, #5280) declaring how produced variants should be evaluated and ranked — the rank-side of the get_creative_features feature oracle. Experimental (x-status: experimental): the whole evaluator surface is new and unfrozen, and requires creative.supports_evaluator, which sellers MUST pair with `creative.evaluator` in experimental_features. Drives the producing agent's gate-then-rank pipeline over its best_of_n exploration: per leaf, evaluate (the chosen form) → optionally GATE (`evaluator.feature_requirement[]`, drop fails — internal pruning of which leaves the agent recommends, never an AdCP-layer block of an already-produced billable leaf) → RANK survivors (`evaluator.rank_by`, an explicit {feature_id, direction} ordering). Feature discovery uses get_adcp_capabilities governance.creative_features for rank_by, feature_requirement, and eval.features[]; evaluator_id is a pre-provisioned/account-arranged preset, not an ID discovered from that catalog. Populates a per-leaf `eval` block of creative-feature values (creative-feature-result[]) when supports_evaluator. When the evaluator names an external agent (`evaluator.feature_agent.agent_url` or the agent-form `agent_url`), that agent MUST appear in the seller's `creative_policy.accepted_verifiers[]` (the same allowlist #5280 established for provenance verify_agent); an off-list agent is rejected with `EVALUATOR_AGENT_NOT_ACCEPTED`. The outbound evaluator call authenticates on the transport (request signing/JWKS, mTLS, or a pre-provisioned static credential); credentials and caller-supplied trust material MUST NOT appear in evaluator, context, ext, or creative payload fields, and credential- or trust-material keys should be rejected with `CREDENTIAL_IN_ARGS`. With no `feature_requirement`, evaluation is advisory only and does not change what is produced or billed; an unreachable/unknown on-list agent degrades to seller-default ranking (advisory errors[] note), not a failure. Requires creative.supports_evaluator; otherwise ignored." + ), + ] = None + item_limit: Annotated[ + SchemaInt | None, + Field( + description="Maximum number of catalog items a SINGLE creative consumes when generating (DCO-style — e.g. how many items fill one carousel/feed creative). When a catalog asset contains more items than this limit, the creative agent selects the top items based on relevance or catalog ordering. When item_limit exceeds the format's max_items, the creative agent SHOULD use the lesser of the two. Ignored when the manifest contains no catalog assets. Distinct from `max_creatives`, which fans OUT across catalog items to produce one distinct creative per item.", + ge=1, + ), + ] = None + include_preview: Annotated[ + StrictBool | None, + Field( + description="When true, requests the creative agent to include preview renders in the response alongside the manifest. Agents that support this return a 'preview' object in the response using the same structure as preview_creative. Agents that do not support inline preview simply omit the field. This avoids a separate preview_creative round trip for platforms that generate previews as a byproduct of building." + ), + ] = None + preview_inputs: Annotated[ + list[PreviewInput] | None, + Field( + description='Input sets for preview generation when include_preview is true. Supported with a single target_capability_id; multi-capability requests generate one default preview per output. Deprecated target-format selectors retain equivalent compatibility behavior.', + min_length=1, + ), + ] = None + preview_quality: Annotated[ + creative_quality.CreativeQuality | None, + Field( + description="Render quality for inline preview when include_preview is true. 'draft' produces fast, lower-fidelity renderings. 'production' produces full-quality renderings. Independent of the build quality parameter — you can build at draft quality and preview at production quality, or vice versa. If omitted, the creative agent uses its own default. Ignored when include_preview is false or omitted." + ), + ] = None + preview_output_format: Annotated[ + preview_output_format_1.PreviewOutputFormat | None, + Field( + description="Output format for preview renders when include_preview is true. 'url' returns preview_url (iframe-embeddable URL), 'html' returns preview_html (raw HTML). Ignored when include_preview is false or omitted." + ), + ] = preview_output_format_1.PreviewOutputFormat.url + macro_values: Annotated[ + dict[str, str] | None, + Field( + description="Raw concrete values offered for build-time binding, keyed by AdCP universal semantic (for example CLICK_URL or CACHEBUSTER). With declarations, a value binds only a verified-universal `resolve_value` occurrence performed_by `creative_agent`, using that declaration's exact context and encoding; callers MUST NOT pre-encode it. The selected `creative.supported_formats[]` route's macro_resolution_capabilities is the binding build/preview capability set; seller-wide and product sets apply only on the sales execution path. Values never short-circuit `translate_to_native`: translation emits its target declaration and the complete target capability chain remains required. For creative_representation_set, selection happens before binding and the complete representation set remains byte-identical. Without declarations, the 3.x legacy path remains: creative agents may translate recognized AdCP tokens using the existing `translateUniversalMacros` contract, preserve omitted placeholders for the sales agent, and ignore unknown keys. Existing unmapped/frozen-consent diagnostics remain required." + ), + ] = None + idempotency_key: Annotated[ + str, + Field( + description='Client-generated unique key for this request. Prevents duplicate creative generation on retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + push_notification_config: Annotated[ + push_notification_config_1.PushNotificationConfig | None, + Field( + description='Optional webhook configuration for async terminal completion/failure notifications on build_creative. Meaningful only when the request enters the async lifecycle and returns a Submitted envelope. Submitted envelopes with `task_id` remain pollable through `get_task_status` (legacy `tasks/get`) whether or not this field is present. If a request includes this field and the agent returns a Submitted envelope, the agent MUST deliver at least the terminal completion/failure notification to the configured URL; intermediate progress notifications are MAY. If the agent cannot honor the webhook channel, it MUST reject the request with a structured error instead of silently accepting. This field does not change response timing semantics: agents MUST NOT route a request through the async/Submitted arm or emit async delivery solely because `push_notification_config` is present; requests that can be completed inline still return the synchronous success shape.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/build_creative_response.py b/src/adcp/types/domains/media_buy/build_creative_response.py index c7c8b8b28..46eea9ddf 100644 --- a/src/adcp/types/domains/media_buy/build_creative_response.py +++ b/src/adcp/types/domains/media_buy/build_creative_response.py @@ -1,58 +1,237 @@ -"""Types declared by the AdCP ``media_buy/build_creative_response`` schema. +# generated by datamodel-codegen: +# filename: media_buy/build_creative_response.json +# timestamp: preserved-by-post-generate-fixes -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.build_creative_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Any, Literal, TypeAlias + +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StringConstraints + +from ..core.version_envelope import AdcpVersionEnvelope +from ..core import context as context_1 +from ..core import creative_consumption as creative_consumption_1 +from ..core import creative_manifest as creative_manifest_1 +from ..core import error as error_1 +from ..core import ext as ext_1 +from ..core import format_id as format_id_1 +from ..core import signal_targeting as signal_targeting_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..creative import creative_feature_result as creative_feature_result_1 +from ..creative import preview_render as preview_render_1 +from ..enums import creative_selection_strategy as creative_selection_strategy_1 +from ..enums import task_status as task_status_1 + + +class Input(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + name: str + macros: dict[str, str] | None = None + context_description: str | None = None + + +class Preview2(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + preview_id: str + renders: Annotated[list[preview_render_1.PreviewRender], Field(min_length=1)] + input: Input + + +class Preview(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + previews: Annotated[list[Preview2], Field(min_length=1)] + interactive_url: AnyUrl | None = None + expires_at: AwareDatetime + + +class Input2(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + name: str + macros: dict[str, str] | None = None + context_description: str | None = None + + +class Preview4(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + preview_id: str + format_id: format_id_1.FormatReferenceStructuredObject | None = None + capability_id: Annotated[str, StringConstraints(pattern='^[a-zA-Z0-9_-]+$')] | None = None + renders: Annotated[list[preview_render_1.PreviewRender], Field(min_length=1)] + input: Input2 + + +class Preview3(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + previews: Annotated[list[Preview4], Field(min_length=1)] + interactive_url: AnyUrl | None = None + expires_at: AwareDatetime + + +class CatalogItemRef(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + catalog_type: str | None = None + item_id: str + + +class Eval(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + features: list[creative_feature_result_1.CreativeFeatureResult] | None = None + ranked_against: Annotated[int, Field(ge=1)] | None = None + calls_used: Annotated[int, Field(ge=0)] | None = None + seconds_used: Annotated[float, Field(ge=0)] | None = None + ext: ext_1.ExtensionObject | None = None + + +class Variant(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + build_variant_id: str + recipe_hash: str | None = None + parent_build_variant_id: str | None = None + creative_manifest: creative_manifest_1.CreativeManifest + variant_axis_value: Any | None = None + recommended: bool | None = None + rank: Annotated[int, Field(ge=1)] | None = None + eval: Eval | None = None + pricing_option_id: str | None = None + vendor_cost: Annotated[float, Field(ge=0)] | None = None + currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] | None = None + consumption: creative_consumption_1.CreativeConsumption | None = None + + +class Creative(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + build_creative_id: str | None = None + catalog_item_ref: CatalogItemRef | None = None + signal_condition: signal_targeting_1.SignalTargeting | None = None + variants: Annotated[list[Variant], Field(min_length=1)] | None = None + errors: Annotated[list[error_1.Error], Field(min_length=1)] | None = None + + +class PerLeaf(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + catalog_item_ref: dict[str, Any] | None = None + variant_axis_value: Any | None = None + pricing_option_id: str | None = None + cost_low: Annotated[float, Field(ge=0)] | None = None + cost_high: Annotated[float, Field(ge=0)] | None = None + consumption_estimate: creative_consumption_1.CreativeConsumption | None = None + + +class Estimate(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + items_total: Annotated[int, Field(ge=0)] | None = None + items_to_produce: Annotated[int, Field(ge=0)] | None = None + conditions_total: Annotated[int, Field(ge=1)] | None = None + variants_per_item: Annotated[int, Field(ge=1)] | None = None + leaves_total: Annotated[int, Field(ge=0)] | None = None + currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] + cost_low: Annotated[float, Field(ge=0)] + cost_high: Annotated[float, Field(ge=0)] + cost_expected: Annotated[float, Field(ge=0)] | None = None + basis: Literal['fixed', 'estimated_units', 'cpm_deferred'] + per_leaf: list[PerLeaf] | None = None + + +class BuildCreativeResponse1(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + creative_manifest: creative_manifest_1.CreativeManifest + build_variant_id: str | None = None + recipe_hash: str | None = None + sandbox: bool | None = None + expires_at: AwareDatetime | None = None + preview: Preview | None = None + preview_error: error_1.Error | None = None + pricing_option_id: str | None = None + vendor_cost: Annotated[float, Field(ge=0)] | None = None + currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] | None = None + consumption: creative_consumption_1.CreativeConsumption | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class BuildCreativeResponse2(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + errors: Annotated[list[error_1.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class BuildCreativeResponse3(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + creative_manifests: Annotated[list[creative_manifest_1.CreativeManifest], Field(min_length=1)] + sandbox: bool | None = None + expires_at: AwareDatetime | None = None + preview: Preview3 | None = None + preview_error: error_1.Error | None = None + pricing_option_id: str | None = None + vendor_cost: Annotated[float, Field(ge=0)] | None = None + currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] | None = None + consumption: creative_consumption_1.CreativeConsumption | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class BuildCreativeResponse4(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + creatives: Annotated[list[Creative], Field(min_length=1)] + items_total: Annotated[int, Field(ge=0)] | None = None + items_returned: Annotated[int, Field(ge=0)] | None = None + leaves_total: Annotated[int, Field(ge=0)] | None = None + leaves_returned: Annotated[int, Field(ge=0)] | None = None + vendor_cost: Annotated[float, Field(ge=0)] | None = None + currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] | None = None + keep_mode_applied: Literal['keep_all', 'keep_one', 'keep_some'] | None = None + selection_strategy_applied: creative_selection_strategy_1.CreativeSelectionStrategy | None = None + budget_status: Literal['complete', 'capped'] | None = None + errors: list[error_1.Error] | None = None + sandbox: bool | None = None + expires_at: AwareDatetime | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class BuildCreativeResponse5(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + mode: Literal['estimate'] = 'estimate' + estimate: Estimate + expires_at: AwareDatetime | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class BuildCreativeResponse6(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow', validate_default=True) + status: Literal[task_status_1.TaskStatus.submitted] = task_status_1.TaskStatus.submitted + task_id: str + message: Annotated[str, StringConstraints(max_length=2000)] | None = None + errors: list[error_1.Error] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +BuildCreativeResponse: TypeAlias = BuildCreativeResponse1 | BuildCreativeResponse2 | BuildCreativeResponse3 | BuildCreativeResponse4 | BuildCreativeResponse5 | BuildCreativeResponse6 -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.media_buy.build_creative_response import ( - BuildCreativeResponse, - BuildCreativeResponse1, - BuildCreativeResponse2, - BuildCreativeResponse3, - BuildCreativeResponse4, - BuildCreativeResponse5, - BuildCreativeResponse6, - CatalogItemRef, - Creative, - Estimate, - Eval, - Input, - Input2, - PerLeaf, - Preview, - Preview2, - Preview3, - Preview4, - Variant, -) - -# Explicit exports __all__ = [ - "BuildCreativeResponse", - "BuildCreativeResponse1", - "BuildCreativeResponse2", - "BuildCreativeResponse3", - "BuildCreativeResponse4", - "BuildCreativeResponse5", - "BuildCreativeResponse6", - "CatalogItemRef", - "Creative", - "Estimate", - "Eval", - "Input", - "Input2", - "PerLeaf", - "Preview", - "Preview2", - "Preview3", - "Preview4", - "Variant", + 'BuildCreativeResponse', + 'BuildCreativeResponse1', + 'BuildCreativeResponse2', + 'BuildCreativeResponse3', + 'BuildCreativeResponse4', + 'BuildCreativeResponse5', + 'BuildCreativeResponse6', + 'CatalogItemRef', + 'Creative', + 'Estimate', + 'Eval', + 'Input', + 'Input2', + 'PerLeaf', + 'Preview', + 'Preview2', + 'Preview3', + 'Preview4', + 'Variant', ] diff --git a/src/adcp/types/domains/media_buy/buy_products_async_response_input_required.py b/src/adcp/types/domains/media_buy/buy_products_async_response_input_required.py index 7ce69920e..5c992fc9a 100644 --- a/src/adcp/types/domains/media_buy/buy_products_async_response_input_required.py +++ b/src/adcp/types/domains/media_buy/buy_products_async_response_input_required.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``media_buy/buy_products_async_response_input_required`` schema. +# generated by datamodel-codegen: +# filename: media_buy/buy_products_async_response_input_required.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.media_buy.buy_products_async_response_input_required import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.media_buy.buy_products_async_response_input_required import ( - BuyProductsInputRequired, -) +from ..core.compact_task_input_required import CompactTaskInputRequired + -# Explicit exports -__all__ = ["BuyProductsInputRequired"] +class BuyProductsInputRequired(CompactTaskInputRequired): + pass diff --git a/src/adcp/types/domains/media_buy/buy_products_async_response_submitted.py b/src/adcp/types/domains/media_buy/buy_products_async_response_submitted.py index 5f7297653..831747a61 100644 --- a/src/adcp/types/domains/media_buy/buy_products_async_response_submitted.py +++ b/src/adcp/types/domains/media_buy/buy_products_async_response_submitted.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``media_buy/buy_products_async_response_submitted`` schema. +# generated by datamodel-codegen: +# filename: media_buy/buy_products_async_response_submitted.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.media_buy.buy_products_async_response_submitted import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.media_buy.buy_products_async_response_submitted import ( - BuyProductsSubmitted, -) +from ..core.compact_task_submitted import CompactTaskSubmitted + -# Explicit exports -__all__ = ["BuyProductsSubmitted"] +class BuyProductsSubmitted(CompactTaskSubmitted): + pass diff --git a/src/adcp/types/domains/media_buy/buy_products_async_response_working.py b/src/adcp/types/domains/media_buy/buy_products_async_response_working.py index fd2e5662b..a0fcc2ad2 100644 --- a/src/adcp/types/domains/media_buy/buy_products_async_response_working.py +++ b/src/adcp/types/domains/media_buy/buy_products_async_response_working.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``media_buy/buy_products_async_response_working`` schema. +# generated by datamodel-codegen: +# filename: media_buy/buy_products_async_response_working.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.media_buy.buy_products_async_response_working import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.media_buy.buy_products_async_response_working import ( - BuyProductsWorking, -) +from ..core.compact_task_working import CompactTaskWorking + -# Explicit exports -__all__ = ["BuyProductsWorking"] +class BuyProductsWorking(CompactTaskWorking): + pass diff --git a/src/adcp/types/domains/media_buy/buy_products_request.py b/src/adcp/types/domains/media_buy/buy_products_request.py index 116df7f21..48e1630b5 100644 --- a/src/adcp/types/domains/media_buy/buy_products_request.py +++ b/src/adcp/types/domains/media_buy/buy_products_request.py @@ -1,23 +1,159 @@ -"""Types declared by the AdCP ``media_buy/buy_products_request`` schema. +# generated by datamodel-codegen: +# filename: media_buy/buy_products_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.buy_products_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat -from adcp.types.generated_poc.media_buy.buy_products_request import ( - BuyProductsRequest, - Opportunity, - Status, - TotalBudget, +from ..core import ( + bidding_policy, + brand_key, + business_entity, + canonical_account_ref, + canonical_budget_allocation, ) +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core import media_buy_frequency_cap +from ..core import push_notification_config as push_notification_config_1 +from ..core import reporting_webhook as reporting_webhook_1 +from ..core import start_timing +from ..core.opportunity_context import OpportunityContext +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import advertiser_industry as advertiser_industry_1 +from ..enums import pacing as pacing_1 +from . import product_purchase_input + + +class TotalBudget(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + amount: Annotated[StrictFloat, Field(ge=0.0)] + currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] + + +class Status(StrEnum): + open = 'open' + closed = 'closed' + + +class Opportunity(OpportunityContext): + status: Annotated[ + Literal['closed'], + Field( + description='Whether the planning cycle remains open. On calls after initial creation, omission means no status update and MUST NOT reopen or close the opportunity implicitly, except that successful create_media_buy proposal execution explicitly infers accepted closure.' + ), + ] = 'closed' + -# Explicit exports -__all__ = ["BuyProductsRequest", "Opportunity", "Status", "TotalBudget"] +class BuyProductsRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='forbid', + ) + idempotency_key: Annotated[ + str, Field(max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$') + ] + name: Annotated[ + str | None, + Field( + description='Human-readable name for this media buy, shared by buyer and seller for trafficking UI display and operational communication. When supplied, the seller MUST persist it and echo it unchanged on the commitment success response and subsequent get_media_buys reads. The name is operational metadata outside accepted_proposal and is not covered by terms_digest. This display label is not an identifier or financial reference.', + max_length=255, + min_length=1, + pattern='\\S', + ), + ] = None + account: Annotated[ + canonical_account_ref.CanonicalAccountReference, + Field( + description='Execution account. A natural-key account is the single brand source and MUST NOT be combined with top-level brand.' + ), + ] + brand: Annotated[ + brand_key.BrandKey | None, + Field( + description='Brand source required when account is ID-only. Omit when account already contains brand and operator.' + ), + ] = None + advertiser_industry: Annotated[ + advertiser_industry_1.AdvertiserIndustry | None, + Field( + description='Industry classification for this campaign. Sellers may infer it from the resolved brand manifest when omitted.' + ), + ] = None + feed_version: Annotated[ + str, + Field( + description='list_products feed_version containing the published offers being accepted. Sellers reject stale or mismatched versions rather than silently applying changed terms.', + min_length=1, + ), + ] + pricing_version: Annotated[ + str | None, + Field( + description='list_products pricing_version containing the accepted rate. Buyers MUST include this whenever list_products returned one; omission means the seller does not version pricing separately.', + min_length=1, + ), + ] = None + purchases: Annotated[list[product_purchase_input.ProductPurchaseInput], Field(min_length=1)] + total_budget: TotalBudget | None = None + daily_budget_cap: Annotated[ + StrictFloat | None, + Field( + description="Optional hard aggregate daily spend ceiling in total_budget.currency or the media buy's derived currency. It bounds the shared daily spend pool without allocating or reserving amounts for purchases.", + ge=0.0, + ), + ] = None + frequency_cap: Annotated[ + media_buy_frequency_cap.MediaBuyFrequencyCap | None, + Field( + description='Optional max-impression cap using one counter across purchases. Buyers send it only when aggregate_frequency_capping is advertised. Every selected product must declare compatible media_buy_support; otherwise the purchase is rejected atomically with UNSUPPORTED_FEATURE before any mutation, never clamped.' + ), + ] = None + budget_cap_timezone: Annotated[ + str | None, + Field( + description='Optional IANA timezone override shared by every aggregate and purchase daily cap. Requires buyer_timezone_override support; otherwise rejected with UNSUPPORTED_FEATURE. When omitted, budget_capping.timezone_basis selects Account.timezone or fixed_timezone.', + min_length=1, + ), + ] = None + budget_allocation: canonical_budget_allocation.CanonicalBudgetAllocation | None = None + start_time: start_timing.StartTiming + end_time: AwareDatetime + pacing: Annotated[ + pacing_1.Pacing | None, + Field( + description='Aggregate media-buy pacing. In seller-optimized allocation, a seller declaring media_buy.features.seller_optimized_budget MUST accept omission and `even`; it MAY reject `asap` or `front_loaded` with UNSUPPORTED_FEATURE (error.field `pacing`) before any provider mutation and MUST NOT silently coerce them to `even`. Fixed-allocation semantics are unchanged.' + ), + ] = None + bidding: bidding_policy.BiddingPolicy | None = None + paused: StrictBool | None = False + purchase_order_ref: Annotated[str | None, Field(max_length=255, min_length=1)] = None + agency_estimate_number: Annotated[str | None, Field(max_length=100)] = None + invoice_recipient: Annotated[ + business_entity.BusinessEntity | None, + Field(description='Authorized per-buy billing entity override.'), + ] = None + governance_context: Annotated[str | None, Field(max_length=4096, min_length=1)] = None + push_notification_config: push_notification_config_1.PushNotificationConfig | None = None + reporting_webhook: Annotated[ + reporting_webhook_1.ReportingWebhook | None, + Field( + description='Optional reporting delivery configuration established atomically with the MediaBuy. This is execution metadata and is not part of the immutable product pricing terms.' + ), + ] = None + opportunity: Annotated[ + Opportunity | None, + Field( + description='Optional planning-cycle closure for a direct product purchase. Success infers closed with accepted_with_seller when status is omitted; an explicit status MUST carry that same closure.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/buy_products_response.py b/src/adcp/types/domains/media_buy/buy_products_response.py index 2f3f61259..5a09de7ab 100644 --- a/src/adcp/types/domains/media_buy/buy_products_response.py +++ b/src/adcp/types/domains/media_buy/buy_products_response.py @@ -1,46 +1,156 @@ -"""Types declared by the AdCP ``media_buy/buy_products_response`` schema. +# generated by datamodel-codegen: +# filename: media_buy/buy_products_response.json +# timestamp: 2026-10-04T04:34:38+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.buy_products_response import +from adcp.types.base import AdcpResponse, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any, Literal + +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field + +from ..core import canonical_media_buy_action +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 +from ..core.canonical_proposal import CanonicalProposal +from ..core.warning import Warning as Warning_1 +from ..enums import media_buy_status as media_buy_status_1 + + +class ProposalStatus(StrEnum): + draft = 'draft' + committed = 'committed' + accepted = 'accepted' + + +class PurchaseBinding(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + purchase_index: Annotated[SchemaInt, Field(ge=0)] + product_id: Annotated[str, Field(min_length=1)] + package_id: Annotated[str, Field(min_length=1)] + + +class Code(StrEnum): + inventory_shortfall_forecast = 'inventory_shortfall_forecast' + flight_change_creates_pacing_risk = 'flight_change_creates_pacing_risk' + fields_ignored_due_to_precedence = 'fields_ignored_due_to_precedence' + inventory_shortfall_forecast_1 = 'inventory_shortfall_forecast' + flight_change_creates_pacing_risk_1 = 'flight_change_creates_pacing_risk' + + +class BuyProductsResponse4(AdCPBaseModel): + pass + + +class BuyProductsResponse2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + status: Literal['failed'] = 'failed' + errors: Annotated[list[error.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + replayed: Literal[True] | None = None + + +class BuyProductsResponse3(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + status: Literal['submitted'] = 'submitted' + task_id: Annotated[str, Field(min_length=1)] + message: Annotated[str | None, Field(max_length=2000)] = None + errors: list[error.Error] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + replayed: Literal[True] | None = None + + +class BuyProductsResponse6(AdcpResponse, BuyProductsResponse2, BuyProductsResponse4): + pass + + +class BuyProductsResponse7(AdcpResponse, BuyProductsResponse3, BuyProductsResponse4): + pass + + +class Warning(Warning_1): + code: Annotated[ + Code | None, + Field( + description='Closed warning vocabulary for the negotiated AdCP release. A non-blocking condition observed while an operation still succeeded. New values are added through normal protocol releases; buyers validate against the negotiated release.', + title='Warning Code', + ), + ] = None + + +class AcceptedProposal(CanonicalProposal): + proposal_status: Annotated[ + Literal['accepted'], + Field( + description='draft is indicative and unreserved; committed has firm terms with inventory reserved until expires_at; accepted is the historical snapshot attached to a MediaBuy.', + title='Proposal Status', + ), + ] = 'accepted' + media_buy_id: Annotated[str, Field(min_length=1)] + accepted_at: Any + commercial_terms: Any + terms_digest: Any + + +class BuyProductsResponse1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + status: Literal['completed'] = 'completed' + media_buy_id: Annotated[str, Field(min_length=1)] + name: Annotated[ + str | None, + Field( + description='Persisted human-readable MediaBuy name for trafficking UI display and operational communication. The seller MUST echo a buyer-supplied request name unchanged; when the seller seeded a new MediaBuy name from an already-valid proposal.name, it MUST return that value unchanged here. Existing named MediaBuys return the stored value on amendment or cancellation commitments. This operational metadata is outside accepted_proposal and is not covered by terms_digest. This display label is not an identifier or financial reference.', + max_length=255, + min_length=1, + pattern='\\S', + ), + ] = None + revision: Annotated[SchemaInt, Field(ge=1)] + media_buy_status: media_buy_status_1.MediaBuyStatus | None = None + confirmed_at: AwareDatetime | None = None + accepted_proposal: AcceptedProposal + purchase_bindings: Annotated[ + list[PurchaseBinding], + Field( + description='Execution identities assigned to the immutable purchases. purchase_index is the zero-based position in accepted_proposal.commercial_terms.purchases and disambiguates repeated product IDs.', + min_length=1, + ), + ] + available_actions: list[canonical_media_buy_action.CanonicalMediaBuyAction] + warnings: Annotated[ + list[Warning] | None, + Field( + description='Non-blocking observations about this completed commitment. The MediaBuy was still created or amended exactly as represented. Continuing conditions also appear as indicators on get_media_buys.', + min_length=1, + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + replayed: Literal[True] | None = None + + +class BuyProductsResponse5(AdcpResponse, BuyProductsResponse1, BuyProductsResponse4): + pass -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.media_buy.buy_products_response import ( - AcceptedProposal, - BuyProductsResponse, - BuyProductsResponse1, - BuyProductsResponse2, - BuyProductsResponse3, - BuyProductsResponse4, - BuyProductsResponse5, - BuyProductsResponse6, - BuyProductsResponse7, - Code, - ProposalStatus, - PurchaseBinding, - Warning, -) - -# Explicit exports -__all__ = [ - "AcceptedProposal", - "BuyProductsResponse", - "BuyProductsResponse1", - "BuyProductsResponse2", - "BuyProductsResponse3", - "BuyProductsResponse4", - "BuyProductsResponse5", - "BuyProductsResponse6", - "BuyProductsResponse7", - "Code", - "ProposalStatus", - "PurchaseBinding", - "Warning", +BuyProductsResponse = Annotated[ + BuyProductsResponse5 | BuyProductsResponse6 | BuyProductsResponse7, + Field( + description='Compact direct-purchase result containing the MediaBuy identity and accepted immutable proposal snapshot.', + title='Buy Products Response', + ), ] diff --git a/src/adcp/types/domains/media_buy/change_term.py b/src/adcp/types/domains/media_buy/change_term.py index 3ad361a40..538ea99ed 100644 --- a/src/adcp/types/domains/media_buy/change_term.py +++ b/src/adcp/types/domains/media_buy/change_term.py @@ -1,22 +1,100 @@ -"""Types declared by the AdCP ``media_buy/change_term`` schema. +# generated by datamodel-codegen: +# filename: media_buy/change_term.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.change_term import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field, model_validator + +from ..core import ext as ext_1 +from ..core import sla_window +from ..enums import canonical_media_buy_action, canonical_media_buy_action_mode +from . import change_term_constraints + + +class AllowedStatus(StrEnum): + pending_creatives = 'pending_creatives' + pending_start = 'pending_start' + active = 'active' + paused = 'paused' + + +class Condition(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Za-z0-9][A-Za-z0-9_.:-]{0,199}$'} -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.media_buy.change_term import ( - AllowedStatus, - Condition, - MediaBuyChangeTerm, -) +class MediaBuyChangeTerm(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + term_id: Annotated[str, Field(pattern='^[A-Za-z0-9_.:-]+$')] + action: canonical_media_buy_action.CanonicalMediaBuyActionName + service_mode: canonical_media_buy_action_mode.CanonicalMediaBuyActionMode + allowed_statuses: Annotated[ + list[AllowedStatus] | None, + Field( + description='Non-terminal MediaBuy statuses in which this negotiated right may be exercised. When absent, the right applies in every non-terminal status where the canonical action itself is meaningful. This field describes contractual lifecycle scope; available_actions[] remains authoritative for the current instant.', + min_length=1, + ), + ] = None + processing_sla: Annotated[ + sla_window.SlaWindow | None, + Field( + description='Binding elapsed-time acknowledgement and completion commitment. Sellers account for weekends and non-working periods when declaring the maximum.' + ), + ] = None + conditions: Annotated[ + list[Condition] | None, + Field( + description='Opaque stable condition identifiers defined by terms_ref or bilateral commercial documentation. Implementations compare identifiers; they MUST NOT execute or interpret them as instructions.', + min_length=1, + ), + ] = None + constraints: Annotated[ + change_term_constraints.MediaBuyChangeTermConstraints | None, + Field( + description='Portable bounds that buyer and seller SDKs can preflight. Omission means no machine-readable bound was promised; opaque conditions remain unevaluated.' + ), + ] = None + terms_ref: Annotated[ + str | None, + Field( + description="Stable contract reference. Resolving it cannot expand the typed right and MUST use the caller's normal authenticated contract-document path, never ambient seller credentials.", + max_length=1000, + min_length=1, + ), + ] = None + description: Annotated[ + str | None, + Field( + description='Display-only summary; it cannot grant authority, add an action, or override typed fields.', + max_length=1000, + min_length=1, + ), + ] = None + ext: ext_1.ExtensionObject | None = None -# Explicit exports -__all__ = ["AllowedStatus", "Condition", "MediaBuyChangeTerm"] + @model_validator(mode='after') + def _validate_constraint_action(self) -> MediaBuyChangeTerm: + if self.constraints is None: + return self + kind = self.constraints.kind + allowed = { + 'budget': { + 'increase_budget', 'decrease_budget', 'reallocate_budget', + 'update_budget_allocation', 'update_spend_target', + }, + 'flight': {'extend_flight', 'shorten_flight', 'update_flight_dates'}, + 'package_count': {'add_packages', 'remove_packages'}, + 'effective_timing': {'pause', 'resume', 'cancel'}, + } + action = self.action.value + if action not in allowed.get(kind, set()): + raise ValueError('constraint kind is incompatible with action') + return self diff --git a/src/adcp/types/domains/media_buy/change_term_constraints.py b/src/adcp/types/domains/media_buy/change_term_constraints.py index de0977511..af8157059 100644 --- a/src/adcp/types/domains/media_buy/change_term_constraints.py +++ b/src/adcp/types/domains/media_buy/change_term_constraints.py @@ -1,32 +1,149 @@ -"""Types declared by the AdCP ``media_buy/change_term_constraints`` schema. +# generated by datamodel-codegen: +# filename: media_buy/change_term_constraints.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.change_term_constraints import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat, model_validator + +from ..core import duration + + +class MediaBuyChangeTermConstraints3(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['package_count'] = 'package_count' + max_additions: Annotated[ + SchemaInt | None, + Field(description='Maximum packages that may be added by one exercise of the right.', ge=0), + ] = None + max_removals: Annotated[ + SchemaInt | None, + Field( + description='Maximum packages that may be removed by one exercise of the right.', ge=0 + ), + ] = None + max_result_count: Annotated[ + SchemaInt | None, Field(description='Maximum active package count after the change.', ge=0) + ] = None + + + @model_validator(mode='after') + def _require_portable_bound(self) -> MediaBuyChangeTermConstraints3: + if not any(getattr(self, name) is not None for name in ('max_additions', 'max_removals', 'max_result_count')): + raise ValueError('at least one portable constraint bound is required') + return self + + +class Money(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + amount: Annotated[StrictFloat, Field(ge=0.0)] + currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] + + +class MediaBuyChangeTermConstraints1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['budget'] = 'budget' + max_delta_amount: Annotated[ + Money | None, + Field( + description='Maximum absolute amount by which the affected budget may change in the direction named by the action.' + ), + ] = None + max_delta_percent: Annotated[ + StrictFloat | None, + Field( + description='Maximum percentage change relative to the current committed value. Values above 100 are valid for increases greater than the current value.', + ge=0.0, + ), + ] = None + min_result_amount: Annotated[ + Money | None, Field(description='Minimum resulting committed value after the change.') + ] = None + max_result_amount: Annotated[ + Money | None, Field(description='Maximum resulting committed value after the change.') + ] = None + + + @model_validator(mode='after') + def _require_portable_bound(self) -> MediaBuyChangeTermConstraints1: + if not any(getattr(self, name) is not None for name in ('max_delta_amount', 'max_delta_percent', 'min_result_amount', 'max_result_amount')): + raise ValueError('at least one portable constraint bound is required') + return self + + +class MediaBuyChangeTermConstraints2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['flight'] = 'flight' + max_change: Annotated[ + duration.Duration | None, + Field( + description='Maximum extension, shortening, or shift in the direction named by the action.' + ), + ] = None + earliest_result: Annotated[ + AwareDatetime | None, + Field(description='Earliest resulting start or end timestamp accepted for this action.'), + ] = None + latest_result: Annotated[ + AwareDatetime | None, + Field(description='Latest resulting start or end timestamp accepted for this action.'), + ] = None + minimum_notice: Annotated[ + duration.Duration | None, + Field( + description='Minimum elapsed notice before the requested flight change may take effect.' + ), + ] = None + + + @model_validator(mode='after') + def _require_portable_bound(self) -> MediaBuyChangeTermConstraints2: + if not any(getattr(self, name) is not None for name in ('max_change', 'earliest_result', 'latest_result', 'minimum_notice')): + raise ValueError('at least one portable constraint bound is required') + return self + + +class MediaBuyChangeTermConstraints4(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['effective_timing'] = 'effective_timing' + minimum_notice: Annotated[ + duration.Duration | None, + Field( + description='Minimum elapsed notice before pause, resume, cancellation, or another operational action may take effect.' + ), + ] = None + earliest_effective_at: AwareDatetime | None = None + latest_effective_at: AwareDatetime | None = None + + + @model_validator(mode='after') + def _require_portable_bound(self) -> MediaBuyChangeTermConstraints4: + if not any(getattr(self, name) is not None for name in ('minimum_notice', 'earliest_effective_at', 'latest_effective_at')): + raise ValueError('at least one portable constraint bound is required') + return self -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.media_buy.change_term_constraints import ( - MediaBuyChangeTermConstraints, - MediaBuyChangeTermConstraints1, - MediaBuyChangeTermConstraints2, - MediaBuyChangeTermConstraints3, - MediaBuyChangeTermConstraints4, - Money, -) - -# Explicit exports -__all__ = [ - "MediaBuyChangeTermConstraints", - "MediaBuyChangeTermConstraints1", - "MediaBuyChangeTermConstraints2", - "MediaBuyChangeTermConstraints3", - "MediaBuyChangeTermConstraints4", - "Money", +MediaBuyChangeTermConstraints = Annotated[ + MediaBuyChangeTermConstraints1 + | MediaBuyChangeTermConstraints2 + | MediaBuyChangeTermConstraints3 + | MediaBuyChangeTermConstraints4, + Field( + description='Optional machine-readable bounds on a proposal-bound change right. These constraints narrow the action named by the containing change term; they never grant an additional action or expand the accepted commercial envelope. Sellers omit this object when no portable bound is available, and buyers then treat any opaque conditions as unevaluated rather than assuming the change will be accepted.', + discriminator='kind', + title='Media Buy Change Term Constraints', + ), ] diff --git a/src/adcp/types/domains/media_buy/commercial_terms.py b/src/adcp/types/domains/media_buy/commercial_terms.py index d6b9a5cab..e9eff5bf8 100644 --- a/src/adcp/types/domains/media_buy/commercial_terms.py +++ b/src/adcp/types/domains/media_buy/commercial_terms.py @@ -1,24 +1,180 @@ -"""Types declared by the AdCP ``media_buy/commercial_terms`` schema. +# generated by datamodel-codegen: +# filename: media_buy/commercial_terms.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.media_buy.commercial_terms import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat, model_validator -from adcp.types.generated_poc.media_buy.commercial_terms import ( - CancellationTerms, - CommercialTerms, - Fee, - ReportingCommitment, - TotalBudget, +from ..core import ( + bidding_policy, + brand_key, + business_entity, + canonical_budget_allocation, + canonical_reporting_commitment, + media_buy_frequency_cap, + start_timing, ) +from ..enums import advertiser_industry as advertiser_industry_1 +from ..enums import pacing as pacing_1 +from . import change_term, product_purchase + + +class TotalBudget(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + amount: Annotated[StrictFloat, Field(ge=0.0)] + currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] + + +class Fee(TotalBudget): + pass + + +class CancellationTerms(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + effective_at: AwareDatetime + fee: Fee | None = None + reason: Annotated[str | None, Field(max_length=500, min_length=1)] = None + + +class ReportingCommitment(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + purchase_index: Annotated[SchemaInt, Field(ge=0)] + metrics: Annotated[ + list[canonical_reporting_commitment.CanonicalReportingCommitment], Field(min_length=1) + ] + + +class CommercialTerms(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + source_feed_version: Annotated[ + str | None, + Field( + description='Wholesale product feed version against which direct published offers were accepted. Omitted when the seller authored terms outside a wholesale snapshot.', + min_length=1, + ), + ] = None + source_pricing_version: Annotated[ + str | None, + Field( + description='Pricing-layer version against which published rates were accepted.', + min_length=1, + ), + ] = None + brand: brand_key.BrandKey + advertiser_industry: advertiser_industry_1.AdvertiserIndustry | None = None + purchases: Annotated[ + list[product_purchase.ProductPurchase], + Field( + description='Exact canonical product, pricing, format, catalog, budget, targeting, bidding, optimization, resolved flight, measurement, and performance terms in the commercial envelope.', + min_length=1, + ), + ] + start_time: start_timing.StartTiming + end_time: AwareDatetime + total_budget: TotalBudget | None = None + daily_budget_cap: Annotated[ + StrictFloat | None, + Field( + description='Hard aggregate daily spend ceiling accepted as part of these terms. It bounds total spend without creating purchase allocations.', + ge=0.0, + ), + ] = None + frequency_cap: Annotated[ + media_buy_frequency_cap.MediaBuyFrequencyCap | None, + Field( + description='Hard MediaBuy-level cap accepted as part of these terms. One counter aggregates exposures across every purchase; purchase targeting caps remain independently binding.' + ), + ] = None + budget_cap_timezone: Annotated[ + str | None, + Field( + description='Shared IANA calendar-day boundary for aggregate and purchase daily caps in these terms.', + min_length=1, + ), + ] = None + budget_allocation: canonical_budget_allocation.CanonicalBudgetAllocation | None = None + pacing: pacing_1.Pacing | None = None + bidding: Annotated[ + bidding_policy.BiddingPolicy | None, + Field( + description="Media-buy bidding policy. A proposal answering criteria.outcome_target.cost_per states here the cost the seller can plan to, which the buyer adopts on acceptance: the requested strength, and an amount greater than or equal to the ask (the ask when the seller can forecast goal volume under it within the buyer's budget, otherwise the lowest such amount), denominated in the purchases' pricing currency, which equals cost_per.currency. It is an execution control, not an expected price; when the planned spend at that amount is below total_budget, forecast points carry metrics.spend. See outcome-target.json for goal binding." + ), + ] = None + invoice_recipient: business_entity.BusinessEntity | None = None + purchase_order_ref: Annotated[str | None, Field(max_length=255, min_length=1)] = None + agency_estimate_number: Annotated[str | None, Field(max_length=100)] = None + reporting_commitments: Annotated[ + list[ReportingCommitment] | None, + Field( + description='Binding reporting contract keyed by position in purchases. Amendments preserve prior entries and add metrics with effective_at; seller-assigned package IDs live in the execution binding, outside this digest.', + min_length=1, + ), + ] = None + cancellation_terms: CancellationTerms | None = None + change_terms: Annotated[ + list[change_term.MediaBuyChangeTerm] | None, + Field( + description='Binding buyer change rights included in the commercial envelope and therefore covered by terms_digest. Entries are uniquely keyed by action. When this field is present, an omitted action is not a negotiated change right. Omission of the entire field means legacy-unspecified rights, not a prohibition.', + min_length=1, + ), + ] = None -# Explicit exports -__all__ = ["CancellationTerms", "CommercialTerms", "Fee", "ReportingCommitment", "TotalBudget"] + @model_validator(mode='after') + def _validate_change_term_set(self) -> CommercialTerms: + if self.change_terms is None: + return self + actions = [term.action.value for term in self.change_terms] + term_ids = [term.term_id for term in self.change_terms] + if len(set(actions)) != len(actions): + raise ValueError('change_terms must be uniquely keyed by action') + if len(set(term_ids)) != len(term_ids): + raise ValueError('change_terms term_id values must be unique') + currencies = set() + for purchase in self.purchases: + if purchase.pricing is None: + raise ValueError('accepted commercial-term purchases require resolved pricing') + currencies.add(purchase.pricing.currency) + for term in self.change_terms: + if term.constraints is None: + continue + constraint = term.constraints + if constraint.kind == 'budget': + money_fields = ( + constraint.max_delta_amount, + constraint.min_result_amount, + constraint.max_result_amount, + ) + if any(money is not None and money.currency not in currencies for money in money_fields): + raise ValueError('change-term monetary constraint currency must match purchases') + if ( + constraint.min_result_amount is not None + and constraint.max_result_amount is not None + and constraint.min_result_amount.amount > constraint.max_result_amount.amount + ): + raise ValueError('change-term minimum result exceeds maximum result') + elif constraint.kind == 'flight': + if ( + constraint.earliest_result is not None + and constraint.latest_result is not None + and constraint.earliest_result > constraint.latest_result + ): + raise ValueError('change-term earliest result exceeds latest result') + elif constraint.kind == 'effective_timing' and ( + constraint.earliest_effective_at is not None + and constraint.latest_effective_at is not None + and constraint.earliest_effective_at > constraint.latest_effective_at + ): + raise ValueError('change-term earliest effective time exceeds latest time') + return self diff --git a/src/adcp/types/domains/media_buy/control_media_buy_async_response_input_required.py b/src/adcp/types/domains/media_buy/control_media_buy_async_response_input_required.py index ea30a6791..f8fc35474 100644 --- a/src/adcp/types/domains/media_buy/control_media_buy_async_response_input_required.py +++ b/src/adcp/types/domains/media_buy/control_media_buy_async_response_input_required.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``media_buy/control_media_buy_async_response_input_required`` schema. +# generated by datamodel-codegen: +# filename: media_buy/control_media_buy_async_response_input_required.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.media_buy.control_media_buy_async_response_input_required import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.media_buy.control_media_buy_async_response_input_required import ( - ControlMediaBuyInputRequired, -) +from ..core.compact_task_input_required import CompactTaskInputRequired + -# Explicit exports -__all__ = ["ControlMediaBuyInputRequired"] +class ControlMediaBuyInputRequired(CompactTaskInputRequired): + pass diff --git a/src/adcp/types/domains/media_buy/control_media_buy_async_response_submitted.py b/src/adcp/types/domains/media_buy/control_media_buy_async_response_submitted.py index 7b2b9c7c9..51056eb18 100644 --- a/src/adcp/types/domains/media_buy/control_media_buy_async_response_submitted.py +++ b/src/adcp/types/domains/media_buy/control_media_buy_async_response_submitted.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``media_buy/control_media_buy_async_response_submitted`` schema. +# generated by datamodel-codegen: +# filename: media_buy/control_media_buy_async_response_submitted.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.media_buy.control_media_buy_async_response_submitted import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.media_buy.control_media_buy_async_response_submitted import ( - ControlMediaBuySubmitted, -) +from ..core.compact_task_submitted import CompactTaskSubmitted + -# Explicit exports -__all__ = ["ControlMediaBuySubmitted"] +class ControlMediaBuySubmitted(CompactTaskSubmitted): + pass diff --git a/src/adcp/types/domains/media_buy/control_media_buy_async_response_working.py b/src/adcp/types/domains/media_buy/control_media_buy_async_response_working.py index ff3a5958b..97c710f41 100644 --- a/src/adcp/types/domains/media_buy/control_media_buy_async_response_working.py +++ b/src/adcp/types/domains/media_buy/control_media_buy_async_response_working.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``media_buy/control_media_buy_async_response_working`` schema. +# generated by datamodel-codegen: +# filename: media_buy/control_media_buy_async_response_working.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.media_buy.control_media_buy_async_response_working import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.media_buy.control_media_buy_async_response_working import ( - ControlMediaBuyWorking, -) +from ..core.compact_task_working import CompactTaskWorking + -# Explicit exports -__all__ = ["ControlMediaBuyWorking"] +class ControlMediaBuyWorking(CompactTaskWorking): + pass diff --git a/src/adcp/types/domains/media_buy/control_media_buy_request.py b/src/adcp/types/domains/media_buy/control_media_buy_request.py index 441c9d7cb..24e2f2a9e 100644 --- a/src/adcp/types/domains/media_buy/control_media_buy_request.py +++ b/src/adcp/types/domains/media_buy/control_media_buy_request.py @@ -1,21 +1,119 @@ -"""Types declared by the AdCP ``media_buy/control_media_buy_request`` schema. +# generated by datamodel-codegen: +# filename: media_buy/control_media_buy_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.control_media_buy_request import +from adcp.types.base import AdcpRequest, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Literal + +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictBool, StrictFloat, model_validator + +from ..core import bidding_policy, canonical_account_ref, canonical_budget_allocation +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core import media_buy_frequency_cap +from ..core import push_notification_config as push_notification_config_1 +from ..core import reporting_webhook as reporting_webhook_1 +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import pacing as pacing_1 +from . import package_control + + +class TotalBudget(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + amount: Annotated[StrictFloat, Field(ge=0.0)] + currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.media_buy.control_media_buy_request import ( - ControlMediaBuyRequest, - TotalBudget, -) +class ControlMediaBuyRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='forbid', + ) + idempotency_key: Annotated[ + str, Field(max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$') + ] + account: canonical_account_ref.CanonicalAccountReference + media_buy_id: Annotated[str, Field(min_length=1)] + revision: Annotated[ + SchemaInt, + Field( + description='Required optimistic-concurrency revision from the latest MediaBuy snapshot.', + ge=1, + ), + ] + name: Annotated[ + str | None, + Field( + description='Replace the human-readable MediaBuy name as revision-checked operational metadata. This display label is not an identifier, financial reference, or change to the accepted commercial terms.', + max_length=255, + min_length=1, + pattern='\\S', + ), + ] = None + paused: StrictBool | None = None + canceled: Annotated[ + Literal[True] | None, + Field( + description='Exercise an already-accepted unilateral cancellation right. A cancellation requiring seller agreement is requested by refining the accepted proposal.' + ), + ] = None + cancellation_reason: Annotated[str | None, Field(max_length=500, min_length=1)] = None + total_budget: TotalBudget | None = None + daily_budget_cap: Annotated[ + StrictFloat | None, + Field( + description='Replace the hard aggregate daily cap; null removes it. Numeric changes apply immediately with current-cap-day spend counted and do not redistribute purchase caps.', + ge=0.0, + ), + ] = None + frequency_cap: Annotated[ + media_buy_frequency_cap.MediaBuyFrequencyCap | None, + Field( + description='Replace the shared MediaBuy frequency cap; null removes it. The change applies immediately without resetting counters: qualifying prior exposures still count in the resulting active window. Sellers MUST reject the complete mutation with UNSUPPORTED_FEATURE, before any change, if the cap is outside declared constraints or any active package cannot participate in the resulting shared counter, and MUST NOT clamp it. Requires update_media_buy_frequency_cap in available_actions.' + ), + ] = None + budget_cap_timezone: Annotated[ + str | None, + Field( + description='Replace the shared IANA cap-day timezone override; null restores the default selected by budget_capping.timezone_basis (Account.timezone or fixed_timezone). A timezone change begins at the next boundary under the previously effective timezone.', + min_length=1, + ), + ] = None + budget_allocation: canonical_budget_allocation.CanonicalBudgetAllocation | None = None + pacing: Annotated[ + pacing_1.Pacing | None, + Field( + description='Replace aggregate media-buy pacing. In seller-optimized allocation, a seller declaring media_buy.features.seller_optimized_budget MUST accept omission and `even`; it MAY reject `asap` or `front_loaded` with UNSUPPORTED_FEATURE (error.field `pacing`) before any provider mutation and MUST NOT silently coerce them to `even`. Fixed-allocation semantics are unchanged.' + ), + ] = None + bidding: bidding_policy.BiddingPolicy | None = None + packages: Annotated[ + list[package_control.PackageControl] | None, + Field( + description='Operational patches keyed by package_id. Each package_id MUST appear at most once; sellers reject duplicate IDs atomically.', + min_length=1, + ), + ] = None + reporting_webhook: reporting_webhook_1.ReportingWebhook | None = None + governance_context: Annotated[str | None, Field(max_length=4096, min_length=1)] = None + push_notification_config: push_notification_config_1.PushNotificationConfig | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None -# Explicit exports -__all__ = ["ControlMediaBuyRequest", "TotalBudget"] + @model_validator(mode='after') + def _require_schema_required_group(self) -> ControlMediaBuyRequest: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('name',), ('paused',), ('canceled',), ('total_budget',), ('daily_budget_cap',), ('frequency_cap',), ('budget_cap_timezone',), ('budget_allocation',), ('pacing',), ('bidding',), ('packages',), ('reporting_webhook',),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'ControlMediaBuyRequest requires at least one of these field groups: name | paused | canceled | total_budget | daily_budget_cap | frequency_cap | budget_cap_timezone | budget_allocation | pacing | bidding | packages | reporting_webhook' + ) diff --git a/src/adcp/types/domains/media_buy/control_media_buy_response.py b/src/adcp/types/domains/media_buy/control_media_buy_response.py index f0853f4db..1636b92ad 100644 --- a/src/adcp/types/domains/media_buy/control_media_buy_response.py +++ b/src/adcp/types/domains/media_buy/control_media_buy_response.py @@ -1,34 +1,99 @@ -"""Types declared by the AdCP ``media_buy/control_media_buy_response`` schema. +# generated by datamodel-codegen: +# filename: media_buy/control_media_buy_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.control_media_buy_response import +from adcp.types.base import AdcpResponse, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal + +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import AwareDatetime, ConfigDict, Field + +from ..core import canonical_media_buy_action +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 +from ..core.warning import Warning as Warning_1 +from ..enums import media_buy_status as media_buy_status_1 + + +class AffectedPackageId(ScalarStr): + __slots__ = () + _constraints = {'min_length': 1} + + +class Code(StrEnum): + inventory_shortfall_forecast = 'inventory_shortfall_forecast' + flight_change_creates_pacing_risk = 'flight_change_creates_pacing_risk' + fields_ignored_due_to_precedence = 'fields_ignored_due_to_precedence' + + +class ControlMediaBuyResponse2(AdcpResponse, AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + status: Literal['failed'] = 'failed' + errors: Annotated[list[error.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + replayed: Literal[True] | None = None + + +class ControlMediaBuyResponse3(AdcpResponse, AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + status: Literal['submitted'] = 'submitted' + task_id: Annotated[str, Field(min_length=1)] + message: Annotated[str | None, Field(max_length=2000)] = None + errors: list[error.Error] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + replayed: Literal[True] | None = None + + +class Warning(Warning_1): + code: Annotated[ + Literal['inventory_shortfall_forecast'], + Field( + description='Closed warning vocabulary for the negotiated AdCP release. A non-blocking condition observed while an operation still succeeded. New values are added through normal protocol releases; buyers validate against the negotiated release.', + title='Warning Code', + ), + ] = 'inventory_shortfall_forecast' + + +class ControlMediaBuyResponse1(AdcpResponse, AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + status: Literal['completed'] = 'completed' + media_buy_id: Annotated[str, Field(min_length=1)] + revision: Annotated[SchemaInt, Field(ge=1)] + media_buy_status: media_buy_status_1.MediaBuyStatus | None = None + implementation_date: AwareDatetime | None = None + affected_package_ids: list[AffectedPackageId] | None = None + available_actions: list[canonical_media_buy_action.CanonicalMediaBuyAction] | None = None + warnings: Annotated[ + list[Warning] | None, + Field( + description='Non-blocking observations about this completed control. The control was still applied exactly as represented. Continuing conditions also appear as indicators on get_media_buys.', + min_length=1, + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + replayed: Literal[True] | None = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.media_buy.control_media_buy_response import ( - AffectedPackageId, - Code, - ControlMediaBuyResponse, - ControlMediaBuyResponse1, - ControlMediaBuyResponse2, - ControlMediaBuyResponse3, - Warning, -) - -# Explicit exports -__all__ = [ - "AffectedPackageId", - "Code", - "ControlMediaBuyResponse", - "ControlMediaBuyResponse1", - "ControlMediaBuyResponse2", - "ControlMediaBuyResponse3", - "Warning", +ControlMediaBuyResponse = Annotated[ + ControlMediaBuyResponse1 | ControlMediaBuyResponse2 | ControlMediaBuyResponse3, + Field( + description='Result of applying operational controls without embedding the package or creative object graphs.', + discriminator='status', + title='Control Media Buy Response', + ), ] diff --git a/src/adcp/types/domains/media_buy/create_media_buy_async_response_input_required.py b/src/adcp/types/domains/media_buy/create_media_buy_async_response_input_required.py index c8669ecab..414b45bfb 100644 --- a/src/adcp/types/domains/media_buy/create_media_buy_async_response_input_required.py +++ b/src/adcp/types/domains/media_buy/create_media_buy_async_response_input_required.py @@ -1,21 +1,37 @@ -"""Types declared by the AdCP ``media_buy/create_media_buy_async_response_input_required`` schema. +# generated by datamodel-codegen: +# filename: media_buy/create_media_buy_async_response_input_required.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.create_media_buy_async_response_input_required import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 + + +class Reason(StrEnum): + APPROVAL_REQUIRED = 'APPROVAL_REQUIRED' + BUDGET_EXCEEDS_LIMIT = 'BUDGET_EXCEEDS_LIMIT' -from adcp.types.generated_poc.media_buy.create_media_buy_async_response_input_required import ( - CreateMediaBuyInputRequired, - Reason, -) -# Explicit exports -__all__ = ["CreateMediaBuyInputRequired", "Reason"] +class CreateMediaBuyInputRequired(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + reason: Annotated[ + Reason | None, Field(description='Reason code indicating why input is needed') + ] = None + errors: Annotated[ + list[error.Error] | None, + Field( + description='Optional validation errors or warnings for debugging purposes. Helps explain why input is required.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/create_media_buy_async_response_submitted.py b/src/adcp/types/domains/media_buy/create_media_buy_async_response_submitted.py index ca87df27f..ff9d898ff 100644 --- a/src/adcp/types/domains/media_buy/create_media_buy_async_response_submitted.py +++ b/src/adcp/types/domains/media_buy/create_media_buy_async_response_submitted.py @@ -1,20 +1,47 @@ -"""Types declared by the AdCP ``media_buy/create_media_buy_async_response_submitted`` schema. +# generated by datamodel-codegen: +# filename: media_buy/create_media_buy_async_response_submitted.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.create_media_buy_async_response_submitted import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 -from adcp.types.generated_poc.media_buy.create_media_buy_async_response_submitted import ( - CreateMediaBuySubmitted, -) -# Explicit exports -__all__ = ["CreateMediaBuySubmitted"] +class CreateMediaBuySubmitted(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + status: Annotated[ + Literal['submitted'], + Field( + description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, which carries media-buy lifecycle state in `media_buy_status`. See task-status.json for the full task-status enum.' + ), + ] = 'submitted' + task_id: Annotated[ + str, + Field( + description='Task handle the buyer uses with get_task_status (or the legacy AdCP tasks/get alias), and that the seller references on push-notification callbacks. The media_buy_id is issued on the completion artifact, not here. This AdCP application-layer handle remains the snake_case task_id in every transport payload and is distinct from any transport-native A2A Task id.' + ), + ] + message: Annotated[ + str | None, + Field( + description="Optional human-readable explanation of why the task is submitted — e.g., 'Awaiting IO signature from sales team; typical turnaround 2–4 hours.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", + max_length=2000, + ), + ] = None + errors: Annotated[ + list[error.Error] | None, + Field( + description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/create_media_buy_async_response_working.py b/src/adcp/types/domains/media_buy/create_media_buy_async_response_working.py index 3caf4e45d..d7feb5362 100644 --- a/src/adcp/types/domains/media_buy/create_media_buy_async_response_working.py +++ b/src/adcp/types/domains/media_buy/create_media_buy_async_response_working.py @@ -1,20 +1,31 @@ -"""Types declared by the AdCP ``media_buy/create_media_buy_async_response_working`` schema. +# generated by datamodel-codegen: +# filename: media_buy/create_media_buy_async_response_working.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.create_media_buy_async_response_working import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import context as context_1 +from ..core import ext as ext_1 -from adcp.types.generated_poc.media_buy.create_media_buy_async_response_working import ( - CreateMediaBuyWorking, -) -# Explicit exports -__all__ = ["CreateMediaBuyWorking"] +class CreateMediaBuyWorking(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + percentage: Annotated[ + StrictFloat | None, Field(description='Completion percentage (0-100)', ge=0.0, le=100.0) + ] = None + current_step: Annotated[ + str | None, Field(description='Current step or phase of the operation') + ] = None + total_steps: Annotated[ + SchemaInt | None, Field(description='Total number of steps in the operation', ge=1) + ] = None + step_number: Annotated[SchemaInt | None, Field(description='Current step number', ge=1)] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/create_media_buy_request.py b/src/adcp/types/domains/media_buy/create_media_buy_request.py index ad3720410..a7559818a 100644 --- a/src/adcp/types/domains/media_buy/create_media_buy_request.py +++ b/src/adcp/types/domains/media_buy/create_media_buy_request.py @@ -1,38 +1,332 @@ -"""Types declared by the AdCP ``media_buy/create_media_buy_request`` schema. +# generated by datamodel-codegen: +# filename: media_buy/create_media_buy_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.create_media_buy_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from collections.abc import Sequence +from typing import Annotated, Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat, model_validator + +from ..core import account_ref, bidding_policy, brand_ref +from ..core import budget_allocation as budget_allocation_1 +from ..core import business_entity +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core import media_buy_frequency_cap +from ..core import push_notification_config as push_notification_config_1 +from ..core import reporting_webhook as reporting_webhook_1 +from ..core import start_timing +from ..core.opportunity_context import OpportunityContext +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import advertiser_industry as advertiser_industry_1 +from ..enums import auth_scheme +from ..enums import pacing as pacing_1 +from . import package_request + + +class Status(StrEnum): + open = 'open' + closed = 'closed' + + +class TotalBudget(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + amount: Annotated[StrictFloat, Field(description='Total budget amount', ge=0.0)] + currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] + + +class IoAcceptance(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + io_id: Annotated[ + str, Field(description="The io_id from the proposal's insertion_order being accepted") + ] + accepted_at: Annotated[ + AwareDatetime, Field(description='ISO 8601 timestamp when the IO was accepted') + ] + signatory: Annotated[ + str, + Field( + description='Who accepted the IO — agent identifier or human name', + max_length=250, + min_length=1, + ), + ] + signature_id: Annotated[ + str | None, + Field( + description='Reference to the electronic signature from the signing service, when signing_url was used' + ), + ] = None + + +class Authentication(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + schemes: Annotated[ + list[auth_scheme.AuthenticationScheme], + Field( + description="Array of authentication schemes. ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD use the RFC 9421 webhook signing profile instead.", + max_length=1, + min_length=1, + ), + ] + credentials: Annotated[ + str, + Field( + description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', + min_length=32, + ), + ] + + +class DeliveryMode(StrEnum): + realtime = 'realtime' + batched = 'batched' + + +class BatchFrequency(StrEnum): + hourly = 'hourly' + daily = 'daily' + + +class ArtifactWebhook(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + url: Annotated[AnyUrl, Field(description='Webhook endpoint URL for artifact delivery')] + token: Annotated[ + str | None, + Field( + description='Optional client-provided token for webhook validation. Echoed back in webhook payload to validate request authenticity.', + min_length=16, + ), + ] = None + authentication: Annotated[ + Authentication, + Field( + deprecated=True, + description="Legacy authentication configuration for webhook delivery (A2A-compatible). Opts the receiver into Bearer or HMAC-SHA256 signing. Both schemes are deprecated; the preferred signing profile for new integrations is RFC 9421, where the seller signs with a key published at its brand.json agents[] entry and the buyer verifies against the seller's JWKS — no shared secret crosses the wire (see docs/building/implementation/security.mdx#webhook-callbacks). This field is required in AdCP 3.x; the requirement is removed in AdCP 4.0 when the default RFC 9421 path becomes the only path.", + ), + ] + delivery_mode: Annotated[ + DeliveryMode, + Field( + description="How artifacts are delivered. 'realtime' pushes artifacts as impressions occur. 'batched' aggregates artifacts and pushes periodically (see batch_frequency)." + ), + ] + batch_frequency: Annotated[ + BatchFrequency | None, + Field( + description="For batched delivery, how often to push artifacts. Required when delivery_mode is 'batched'." + ), + ] = None + sampling_rate: Annotated[ + StrictFloat | None, + Field( + description='Fraction of impressions to include (0-1). 1.0 = all impressions, 0.1 = 10% sample. Default: 1.0', + ge=0.0, + le=1.0, + ), + ] = None + + +class Opportunity(OpportunityContext): + status: Annotated[ + Literal['closed'], + Field( + description='Whether the planning cycle remains open. On calls after initial creation, omission means no status update and MUST NOT reopen or close the opportunity implicitly, except that successful create_media_buy proposal execution explicitly infers accepted closure.' + ), + ] = 'closed' + + +class CreateMediaBuyRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + governance_context: Annotated[ + str | None, + Field( + description='Opaque intent authorization for this media-buy commitment. Required when governance applies to the resolved account.', + max_length=4096, + min_length=1, + pattern='^[\\x20-\\x7E]+$', + ), + ] = None + idempotency_key: Annotated[ + str, + Field( + description='Client-generated unique key for this request. If a request with the same idempotency_key and account has already been processed, the seller returns the existing media buy rather than creating a duplicate. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + plan_id: Annotated[ + str | None, + Field( + deprecated=True, + description='DEPRECATED on seller-facing requests. New buyers send the approved governance_context on the protocol envelope; the seller forwards that opaque context and does not need the plan identifier. If both are present, the governance agent MUST reject a mismatch. Removed in 4.0.', + ), + ] = None + account: Annotated[ + account_ref.AccountReference, + Field( + description='Account to bill for this media buy. Pass a natural key (brand, operator, optional sandbox) or a seller-assigned account_id from list_accounts.' + ), + ] + proposal_id: Annotated[ + str | None, + Field( + description="ID of the exact committed proposal snapshot to execute. With total_budget, the publisher creates packages using the proposal's fixed percentages or seller-optimized constraints. Mutually exclusive: provide packages or proposal_id, not both. AdCP 3.2 request_proposals and ordinary refine_proposals revisions issue drafts; refine_proposals action finalize creates the executable committed hold. Sellers reject draft, declined, or previously executed snapshots, while exact retries with the original idempotency key replay historical success. Changed commercial terms are issued under a new proposal_id, so no separate proposal version is required." + ), + ] = None + opportunity: Annotated[ + Opportunity | None, + Field( + description='Optional planning-cycle closure. Sellers infer successful proposal execution as closed with close_reason accepted_with_seller when status is omitted; when status is present it MUST be closed with that reason. If the proposal was issued under an opportunity_id, a supplied ID MUST match it.' + ), + ] = None + total_budget: Annotated[ + TotalBudget | None, + Field( + description='Hard aggregate lifetime budget for the media buy. Required when executing a proposal and for seller-optimized explicit packages. Optional in fixed explicit-package mode; when present there, amount MUST equal the sum of package budgets. For a fixed proposal, the publisher applies allocation percentages to this amount. For a seller-optimized proposal or explicit buy, packages draw dynamically from this shared total.' + ), + ] = None + daily_budget_cap: Annotated[ + StrictFloat | None, + Field( + description='Optional hard aggregate daily spend ceiling in the media-buy currency. It limits total spend without allocating package amounts. Package caps are subordinate and need not sum to it. Requires advertised media_buy budget-capping scope; otherwise rejected with UNSUPPORTED_FEATURE.', + ge=0.0, + ), + ] = None + frequency_cap: Annotated[ + media_buy_frequency_cap.MediaBuyFrequencyCap | None, + Field( + description='Optional max-impression cap using one counter across explicit packages. Requires advertised aggregate_frequency_capping and compatible media_buy_support on every product; otherwise sellers MUST reject with UNSUPPORTED_FEATURE before any mutation and MUST NOT silently drop, soften, or clamp the cap. Omit when executing proposal_id because accepted terms are authoritative.' + ), + ] = None + budget_cap_timezone: Annotated[ + str | None, + Field( + description='Optional shared IANA day boundary override for all caps. Requires buyer_timezone_override; otherwise rejected with UNSUPPORTED_FEATURE. When omitted, budget_capping.timezone_basis selects Account.timezone or the advertised fixed_timezone.', + min_length=1, + ), + ] = None + budget_allocation: Annotated[ + budget_allocation_1.BudgetAllocation | None, + Field( + description='How budget is allocated across explicit packages. Omission means fixed allocation (legacy-compatible). Buyer agents SHOULD send budget_allocation explicitly ({mode: "fixed"} or seller_optimized) rather than rely on omission, and when the principal\'s instruction does not determine whether the budget is one shared pool across packages or split per package, SHOULD ask the principal rather than guess. seller_optimized requires advertised media_buy.features.seller_optimized_budget; inside it, package budget caps, min_spend_target, and package pacing each require their own advertised sub-capability (seller_optimized_package_budgets, seller_optimized_min_spend_targets, seller_optimized_package_pacing), otherwise the request is rejected with UNSUPPORTED_FEATURE before any over-subscription validation. In proposal mode the committed proposal supplies this configuration and callers MUST omit it here.' + ), + ] = None + packages: Annotated[ + Sequence[package_request.PackageRequest] | None, + Field( + description='Array of package configurations. Required when not using proposal_id. Mutually exclusive: provide packages or proposal_id, not both. Fixed allocation requires budget on every package. Seller-optimized allocation permits package budget to be omitted or to act as a hard cap. When executing a proposal, omit packages; the seller derives them from the committed proposal.', + min_length=1, + ), + ] = None + brand: Annotated[ + brand_ref.BrandReference, + Field( + description='Brand reference for this media buy. Resolved to full brand identity at execution time from brand.json or the registry.' + ), + ] + advertiser_industry: Annotated[ + advertiser_industry_1.AdvertiserIndustry | None, + Field( + description="Industry classification for this specific campaign. A brand may operate across multiple industries (brand.json industries field), but each media buy targets one. For example, a consumer health company running a wellness campaign sends 'healthcare.wellness', not 'cpg'. Sellers map this to platform-native codes (e.g., Spotify ADV categories, LinkedIn industry IDs). When omitted, sellers may infer from the brand manifest's industries field." + ), + ] = None + invoice_recipient: Annotated[ + business_entity.BusinessEntity | None, + Field( + description="Override the account's default billing entity for this specific buy. When provided, the seller invoices this entity instead. The seller MUST validate the invoice recipient is authorized for this account. When governance_agents are configured, the seller MUST include invoice_recipient in the check_governance request." + ), + ] = None + io_acceptance: Annotated[ + IoAcceptance | None, + Field( + description="Acceptance of an insertion order from a committed proposal. Required when the proposal's insertion_order has requires_signature: true. References the io_id from the proposal's insertion_order." + ), + ] = None + po_number: Annotated[str | None, Field(description='Purchase order number for tracking')] = None + name: Annotated[ + str | None, + Field( + description='Human-readable name for this media buy, shared by buyer and seller for trafficking UI display and operational communication. When supplied, the seller MUST persist it and echo it unchanged on the create success response and subsequent get_media_buys reads. This display label is not an identifier or financial reference.', + max_length=255, + min_length=1, + pattern='\\S', + ), + ] = None + agency_estimate_number: Annotated[ + str | None, + Field( + description="Agency estimate or authorization number. Primary financial reference for broadcast buys — links the order to the agency's media plan and billing system. Travels with the order and creative traffic identifiers through the transaction lifecycle.", + max_length=100, + ), + ] = None + start_time: start_timing.StartTiming + end_time: Annotated[ + AwareDatetime, Field(description='Campaign end date/time in ISO 8601 format') + ] + pacing: Annotated[ + pacing_1.Pacing | None, + Field( + description="Aggregate pacing strategy for the media-buy budget across the media-buy flight. This controls how much the buy spends over time. Package pacing is subordinate and influences which package receives the aggregate spend; package pacing MUST NOT cause aggregate delivery to exceed this strategy. Defaults to even when total_budget is present. When executing a proposal that carries pacing, omit this field or send the identical value; the seller MUST reject a conflicting override with TERMS_REJECTED. In a seller-optimized buy, a seller declaring media_buy.features.seller_optimized_budget MUST accept omission and `even`; it MAY reject `asap` or `front_loaded` with UNSUPPORTED_FEATURE (error.field `pacing`) before any provider mutation and MUST NOT silently coerce them to `even`. Pacing carried by the seller's own committed proposal is not subject to that rejection. Fixed-allocation semantics are unchanged." + ), + ] = None + bidding: Annotated[ + bidding_policy.BiddingPolicy | None, + Field( + description="Complete media-buy bidding default inherited by packages that omit package.bidding. `{automatic:true}` records an explicit automatic policy. In seller-optimized mode, cost_per/roas bind to the primary budget_allocation_1.optimization_goals goal. In fixed mode, inherited cost_per is valid only when inheriting package primary-goal result units are compatible; inherited roas requires value-bearing primary goals. Every monetary field uses total_budget.currency or the single currency derived for the media buy, and every affected pricing option MUST declare that currency. Sellers MUST reject incompatible units, combinations, currency, or overrides before mutation with BIDDING_PLACEMENT_CONFLICT. Media-buy bidding combined with any inheriting package's legacy bid_price or legacy monetary optimization-goal target is ambiguous and MUST be rejected with AMBIGUOUS_BIDDING_POLICY." + ), + ] = None + paused: Annotated[ + StrictBool | None, + Field( + description="Create the media buy in a paused delivery state. When true, and the buy would otherwise be active because creatives are assigned and the flight has started, the seller returns media_buy_status 'paused'. Setup blockers still take precedence: a buy with no creatives remains 'pending_creatives', and a future-dated buy remains 'pending_start' until its flight can start. Defaults to false." + ), + ] = False + push_notification_config: Annotated[ + push_notification_config_1.PushNotificationConfig | None, + Field( + description='Optional webhook configuration for async task status notifications. Publisher will send webhooks when status changes (working, input-required, completed, failed, canceled). Buyers SHOULD supply `push_notification_config_1.operation_id` as the canonical correlation value; publishers echo that field back verbatim in webhook payloads and MUST NOT parse the URL to derive it.' + ), + ] = None + reporting_webhook: Annotated[ + reporting_webhook_1.ReportingWebhook | None, + Field(description='Optional webhook configuration for automated reporting delivery'), + ] = None + artifact_webhook: Annotated[ + ArtifactWebhook | None, + Field( + description='Optional webhook configuration for content artifact delivery. Used by governance agents to validate content adjacency. Seller pushes artifacts to this endpoint; orchestrator forwards to governance agent for validation.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None -from adcp.types.generated_poc.media_buy.create_media_buy_request import ( - ArtifactWebhook, - Authentication, - BatchFrequency, - CreateMediaBuyRequest, - DeliveryMode, - IoAcceptance, - Opportunity, - Status, - TotalBudget, -) - -# Explicit exports -__all__ = [ - "ArtifactWebhook", - "Authentication", - "BatchFrequency", - "CreateMediaBuyRequest", - "DeliveryMode", - "IoAcceptance", - "Opportunity", - "Status", - "TotalBudget", -] + @model_validator(mode='after') + def _require_schema_required_group(self) -> CreateMediaBuyRequest: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('packages',), ('packages', 'total_budget', 'budget_allocation'), ('proposal_id', 'total_budget'),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'CreateMediaBuyRequest requires at least one of these field groups: packages | packages+total_budget+budget_allocation | proposal_id+total_budget' + ) diff --git a/src/adcp/types/domains/media_buy/create_media_buy_response.py b/src/adcp/types/domains/media_buy/create_media_buy_response.py index 999db3e86..d3ccf3dd0 100644 --- a/src/adcp/types/domains/media_buy/create_media_buy_response.py +++ b/src/adcp/types/domains/media_buy/create_media_buy_response.py @@ -1,28 +1,109 @@ -"""Types declared by the AdCP ``media_buy/create_media_buy_response`` schema. +# generated by datamodel-codegen: +# filename: media_buy/create_media_buy_response.json +# timestamp: preserved-by-post-generate-fixes -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.create_media_buy_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Any, Literal, TypeAlias -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import AwareDatetime, ConfigDict, Field, StringConstraints, model_validator + +from ..core.version_envelope import AdcpVersionEnvelope +from ..core import account as account_1 +from ..core import business_entity as business_entity_1 +from ..core import context as context_1 +from ..core import error as error_1 +from ..core import ext as ext_1 +from ..core import media_buy_available_action as media_buy_available_action_1 +from ..core import media_buy_frequency_cap as media_buy_frequency_cap_1 +from ..core import package as package_1 +from ..core import planned_delivery as planned_delivery_1 +from ..core import warning as warning_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..enums import media_buy_status as media_buy_status_1 +from ..enums import media_buy_valid_action as media_buy_valid_action_1 +from ..enums import pacing as pacing_1 +from ..enums import task_status as task_status_1 +from adcp.types.media_buy_status_helpers import MEDIA_BUY_LEGACY_STATUS_VALUES, unwrap_enum_value + + +class CreateMediaBuyResponse1(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + status: Literal['completed'] = 'completed' + proposal_id: Annotated[str, StringConstraints(min_length=1)] | None = None + media_buy_id: str + name: Annotated[str, StringConstraints(pattern='\\S', min_length=1, max_length=255)] | None = None + account: account_1.Account | None = None + invoice_recipient: business_entity_1.BusinessEntity | None = None + media_buy_status: media_buy_status_1.MediaBuyStatus | None = None + confirmed_at: AwareDatetime | None + creative_deadline: AwareDatetime | None = None + revision: Annotated[int, Field(ge=1)] + currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] | None = None + total_budget: Annotated[float, Field(ge=0)] | None = None + daily_budget_cap: Annotated[float, Field(ge=0)] | None = None + frequency_cap: media_buy_frequency_cap_1.MediaBuyFrequencyCap | None = None + budget_cap_timezone: str | None = None + budget_allocation: Any | None = None + pacing: pacing_1.Pacing | None = None + bidding: Any | None = None + valid_actions: list[media_buy_valid_action_1.MediaBuyValidAction] | None = None + available_actions: list[media_buy_available_action_1.MediaBuyAvailableAction] | None = None + packages: list[package_1.Package] + planned_delivery: planned_delivery_1.PlannedDelivery | None = None + warnings: list[warning_1.Warning] | None = None + sandbox: bool | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + @model_validator(mode='before') + @classmethod + def _normalize_legacy_status(cls, data: Any) -> Any: + if not isinstance(data, dict): + return data + raw_status = unwrap_enum_value(data.get('status')) + media_buy_status = unwrap_enum_value(data.get('media_buy_status')) + if raw_status is None: + data = dict(data) + data['status'] = 'completed' + elif raw_status == 'completed': + data = dict(data) + data['status'] = 'completed' + elif media_buy_status is None and raw_status in MEDIA_BUY_LEGACY_STATUS_VALUES: + data = dict(data) + data['media_buy_status'] = raw_status + data['status'] = 'completed' + elif media_buy_status is not None and raw_status == media_buy_status: + data = dict(data) + data['status'] = 'completed' + return data + + +class CreateMediaBuyResponse2(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + errors: Annotated[list[error_1.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class CreateMediaBuyResponse3(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow', validate_default=True) + status: Literal[task_status_1.TaskStatus.submitted] = task_status_1.TaskStatus.submitted + task_id: str + message: Annotated[str, StringConstraints(max_length=2000)] | None = None + errors: list[error_1.Error] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +CreateMediaBuyResponse: TypeAlias = CreateMediaBuyResponse1 | CreateMediaBuyResponse2 | CreateMediaBuyResponse3 -from adcp.types.generated_poc.media_buy.create_media_buy_response import ( - CreateMediaBuyResponse, - CreateMediaBuyResponse1, - CreateMediaBuyResponse2, - CreateMediaBuyResponse3, -) -# Explicit exports __all__ = [ - "CreateMediaBuyResponse", - "CreateMediaBuyResponse1", - "CreateMediaBuyResponse2", - "CreateMediaBuyResponse3", + 'CreateMediaBuyResponse', + 'CreateMediaBuyResponse1', + 'CreateMediaBuyResponse2', + 'CreateMediaBuyResponse3', ] diff --git a/src/adcp/types/domains/media_buy/decline_proposals_async_response_input_required.py b/src/adcp/types/domains/media_buy/decline_proposals_async_response_input_required.py index 4698c87b1..0b5157524 100644 --- a/src/adcp/types/domains/media_buy/decline_proposals_async_response_input_required.py +++ b/src/adcp/types/domains/media_buy/decline_proposals_async_response_input_required.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``media_buy/decline_proposals_async_response_input_required`` schema. +# generated by datamodel-codegen: +# filename: media_buy/decline_proposals_async_response_input_required.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.media_buy.decline_proposals_async_response_input_required import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.media_buy.decline_proposals_async_response_input_required import ( - DeclineProposalsInputRequired, -) +from ..core.compact_task_input_required import CompactTaskInputRequired + -# Explicit exports -__all__ = ["DeclineProposalsInputRequired"] +class DeclineProposalsInputRequired(CompactTaskInputRequired): + pass diff --git a/src/adcp/types/domains/media_buy/decline_proposals_async_response_submitted.py b/src/adcp/types/domains/media_buy/decline_proposals_async_response_submitted.py index 725d934ee..841dcf86a 100644 --- a/src/adcp/types/domains/media_buy/decline_proposals_async_response_submitted.py +++ b/src/adcp/types/domains/media_buy/decline_proposals_async_response_submitted.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``media_buy/decline_proposals_async_response_submitted`` schema. +# generated by datamodel-codegen: +# filename: media_buy/decline_proposals_async_response_submitted.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.media_buy.decline_proposals_async_response_submitted import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.media_buy.decline_proposals_async_response_submitted import ( - DeclineProposalsSubmitted, -) +from ..core.compact_task_submitted import CompactTaskSubmitted + -# Explicit exports -__all__ = ["DeclineProposalsSubmitted"] +class DeclineProposalsSubmitted(CompactTaskSubmitted): + pass diff --git a/src/adcp/types/domains/media_buy/decline_proposals_async_response_working.py b/src/adcp/types/domains/media_buy/decline_proposals_async_response_working.py index 1862c6c4c..39dec4352 100644 --- a/src/adcp/types/domains/media_buy/decline_proposals_async_response_working.py +++ b/src/adcp/types/domains/media_buy/decline_proposals_async_response_working.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``media_buy/decline_proposals_async_response_working`` schema. +# generated by datamodel-codegen: +# filename: media_buy/decline_proposals_async_response_working.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.media_buy.decline_proposals_async_response_working import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.media_buy.decline_proposals_async_response_working import ( - DeclineProposalsWorking, -) +from ..core.compact_task_working import CompactTaskWorking + -# Explicit exports -__all__ = ["DeclineProposalsWorking"] +class DeclineProposalsWorking(CompactTaskWorking): + pass diff --git a/src/adcp/types/domains/media_buy/decline_proposals_request.py b/src/adcp/types/domains/media_buy/decline_proposals_request.py index 457638cde..f7902d7d1 100644 --- a/src/adcp/types/domains/media_buy/decline_proposals_request.py +++ b/src/adcp/types/domains/media_buy/decline_proposals_request.py @@ -1,18 +1,56 @@ -"""Types declared by the AdCP ``media_buy/decline_proposals_request`` schema. +# generated by datamodel-codegen: +# filename: media_buy/decline_proposals_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.decline_proposals_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field + +from ..core import context as context_1 +from ..core import opportunity_context +from ..core import push_notification_config as push_notification_config_1 +from ..core.version_envelope import AdcpVersionEnvelope +from . import proposal_decline -from adcp.types.generated_poc.media_buy.decline_proposals_request import DeclineProposalsRequest -# Explicit exports -__all__ = ["DeclineProposalsRequest"] +class DeclineProposalsRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='forbid', + ) + context_id: Annotated[ + str | None, + Field( + description='MCP compatibility field: servers ignore this value; A2A uses transport-native Message/Task contextId.', + min_length=1, + ), + ] = None + context: context_1.ContextObject | None = None + governance_context: Annotated[str | None, Field(max_length=4096, min_length=1)] = None + push_notification_config: push_notification_config_1.PushNotificationConfig | None = None + idempotency_key: Annotated[ + str, + Field( + description='Client-generated key required for retry-safe proposal decline.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + declines: Annotated[ + list[proposal_decline.ProposalDecline], + Field( + description='Proposal declines to apply. proposal_id is the semantic uniqueness key and values MUST be unique even when two entries otherwise differ; implementations enforce this rule because JSON Schema uniqueItems only compares whole objects. Results preserve request order.', + max_length=25, + min_length=1, + ), + ] + opportunity: Annotated[ + opportunity_context.OpportunityContext | None, + Field( + description='Optional planning-cycle update. Every named proposal MUST belong to this opportunity_id. Sellers apply the update only when every result is declined; if any result is unable, the opportunity remains unchanged. Use status closed when these declines end the broader opportunity.' + ), + ] = None diff --git a/src/adcp/types/domains/media_buy/decline_proposals_response.py b/src/adcp/types/domains/media_buy/decline_proposals_response.py index 0ce6e816d..aeb50bc62 100644 --- a/src/adcp/types/domains/media_buy/decline_proposals_response.py +++ b/src/adcp/types/domains/media_buy/decline_proposals_response.py @@ -1,40 +1,110 @@ -"""Types declared by the AdCP ``media_buy/decline_proposals_response`` schema. +# generated by datamodel-codegen: +# filename: media_buy/decline_proposals_response.json +# timestamp: 2026-10-04T02:05:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.decline_proposals_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal + +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field + +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 +from ..core.compact_task_submitted import CompactTaskSubmitted + + +class Outcome(StrEnum): + declined = 'declined' + unable = 'unable' + + +class Results1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + proposal_id: Annotated[str, Field(min_length=1)] + outcome: Literal['declined'] = 'declined' + reason: Annotated[ + str | None, + Field( + description='Why the seller could not apply a decline. Present only for outcome unable.', + min_length=1, + ), + ] = None + + +class Results2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + proposal_id: Annotated[str, Field(min_length=1)] + outcome: Literal['unable'] = 'unable' + reason: Annotated[ + str, + Field( + description='Why the seller could not apply a decline. Present only for outcome unable.', + min_length=1, + ), + ] + + +Results = Annotated[ + Results1 | Results2, + Field(discriminator='outcome'), +] + + +class Results4(Results1): + outcome: Literal['declined'] = 'declined' + + +class Results5(Results2): + outcome: Literal['unable'] = 'unable' + + +Results3 = Annotated[ + Results4 | Results5, + Field(discriminator='outcome'), +] + + +class DeclineProposalsResponse1(AdcpResponse, AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + results: Annotated[list[Results], Field(min_length=1)] + status: Literal['submitted'] = 'submitted' + task_id: Annotated[str | None, Field(min_length=1)] = None + message: Annotated[str | None, Field(max_length=2000)] = None + errors: list[error.Error] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + replayed: Literal[True] | None = None + + +class DeclineProposalsResponse2(AdcpResponse, CompactTaskSubmitted): + model_config = ConfigDict( + extra='forbid', + ) + results: Annotated[list[Results3] | None, Field(min_length=1)] = None + status: Literal['submitted'] = 'submitted' + task_id: Annotated[str | None, Field(min_length=1)] = None + message: Annotated[str | None, Field(max_length=2000)] = None + errors: list[error.Error] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + replayed: Literal[True] | None = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.media_buy.decline_proposals_response import ( - DeclineProposalsResponse, - DeclineProposalsResponse1, - DeclineProposalsResponse2, - Outcome, - Results, - Results1, - Results2, - Results3, - Results4, - Results5, -) - -# Explicit exports -__all__ = [ - "DeclineProposalsResponse", - "DeclineProposalsResponse1", - "DeclineProposalsResponse2", - "Outcome", - "Results", - "Results1", - "Results2", - "Results3", - "Results4", - "Results5", +DeclineProposalsResponse = Annotated[ + DeclineProposalsResponse1 | DeclineProposalsResponse2, + Field( + description='One ordered terminal result for each requested proposal decline.', + title='Decline Proposals Response', + ), ] diff --git a/src/adcp/types/domains/media_buy/get_media_buy_delivery_request.py b/src/adcp/types/domains/media_buy/get_media_buy_delivery_request.py index e55f0ef4d..482f387ee 100644 --- a/src/adcp/types/domains/media_buy/get_media_buy_delivery_request.py +++ b/src/adcp/types/domains/media_buy/get_media_buy_delivery_request.py @@ -1,50 +1,566 @@ -"""Types declared by the AdCP ``media_buy/get_media_buy_delivery_request`` schema. +# generated by datamodel-codegen: +# filename: media_buy/get_media_buy_delivery_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.get_media_buy_delivery_request import +from adcp.types.base import AdcpRequest, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Any +from pydantic import SerializerFunctionWrapHandler, model_serializer, model_validator -# ruff: noqa: E501, I001 -from __future__ import annotations +from typing import Annotated + +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, RootModel, StrictBool -from adcp.types.generated_poc.media_buy.get_media_buy_delivery_request import ( - AttributionWindow, - Audience, - CatalogItem, - Creative, - Demographic, - DevicePlatform, - DeviceType, - Format, - Geo, - GetMediaBuyDeliveryRequest, - Keyword, - Placement, - ReportingDimensions, - Spot, - StatusFilter, +from ..core import account_ref +from ..core import context as context_1 +from ..core import delivery_breakdown_controls, demographic_age_range, duration +from ..core import ext as ext_1 +from ..core import pagination_request +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import attribution_model, available_metric +from ..enums import geo_level as geo_level_1 +from ..enums import ( + legacy_postal_system, + media_buy_status, + metro_system, + postal_system, + reporting_frequency, ) +from ..enums import sort_direction as sort_direction_1 +from ..enums import sort_metric + + +class AttributionWindow(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + post_click: Annotated[ + duration.Duration | None, Field(description='Post-click attribution window to apply.') + ] = None + post_view: Annotated[ + duration.Duration | None, Field(description='Post-view attribution window to apply.') + ] = None + model: Annotated[ + attribution_model.AttributionModel | None, + Field( + description='Attribution model to use. When omitted, the seller applies their default model.' + ), + ] = None + + +class Spot(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + limit: Annotated[ + SchemaInt | None, + Field( + description='Optional maximum number of spot rows to return. When the response is incomplete because of this limit or a seller-imposed maximum, by_spot_truncated is true.', + ge=1, + ), + ] = None + + +class StatusFilter(RootModel[list[media_buy_status.MediaBuyStatus]]): + root: Annotated[ + list[media_buy_status.MediaBuyStatus], + Field( + description='Filter by status. Can be a single status or array of statuses', + min_length=1, + ), + ] + + +class CatalogItem(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + limit: Annotated[ + SchemaInt | None, + Field( + description='Maximum number of catalog_item entries to return. When omitted, the seller returns its automatic default set.', + ge=1, + ), + ] = None + sort_by: Annotated[ + sort_metric.SortMetric | None, + Field( + description="Metric to sort breakdown rows by, in `sort_direction` order (descending by default). Falls back to 'spend' when the seller does not report the requested metric at this breakdown's row grain; on fallback the sort direction resets to 'desc'. Rows lacking a value for the applied sort metric order last regardless of direction. The applied sort is echoed in the response." + ), + ] = sort_metric.SortMetric.spend + sort_direction: Annotated[ + sort_direction_1.SortDirection | None, + Field( + description="Direction for sort_by ordering. Defaults to 'desc' (largest first). 'asc' enables bottom-N queries (e.g., the 25 worst placements by viewable_rate) that cannot be recovered from a truncated descending pull. Sellers MUST apply the requested direction to the applied sort metric — direction has no availability fallback." + ), + ] = sort_direction_1.SortDirection.desc + + +class Creative(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + limit: Annotated[ + SchemaInt | None, + Field( + description='Maximum number of creative entries to return. When omitted, the seller returns its automatic default set.', + ge=1, + ), + ] = None + sort_by: Annotated[ + sort_metric.SortMetric | None, + Field( + description="Metric to sort breakdown rows by, in `sort_direction` order (descending by default). Falls back to 'spend' when the seller does not report the requested metric at this breakdown's row grain; on fallback the sort direction resets to 'desc'. Rows lacking a value for the applied sort metric order last regardless of direction. The applied sort is echoed in the response." + ), + ] = sort_metric.SortMetric.spend + sort_direction: Annotated[ + sort_direction_1.SortDirection | None, + Field( + description="Direction for sort_by ordering. Defaults to 'desc' (largest first). 'asc' enables bottom-N queries (e.g., the 25 worst placements by viewable_rate) that cannot be recovered from a truncated descending pull. Sellers MUST apply the requested direction to the applied sort metric — direction has no availability fallback." + ), + ] = sort_direction_1.SortDirection.desc + + +class Keyword(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + limit: Annotated[ + SchemaInt | None, + Field( + description='Maximum number of keyword entries to return. When omitted, the seller returns its automatic default set.', + ge=1, + ), + ] = None + sort_by: Annotated[ + sort_metric.SortMetric | None, + Field( + description="Metric to sort breakdown rows by, in `sort_direction` order (descending by default). Falls back to 'spend' when the seller does not report the requested metric at this breakdown's row grain; on fallback the sort direction resets to 'desc'. Rows lacking a value for the applied sort metric order last regardless of direction. The applied sort is echoed in the response." + ), + ] = sort_metric.SortMetric.spend + sort_direction: Annotated[ + sort_direction_1.SortDirection | None, + Field( + description="Direction for sort_by ordering. Defaults to 'desc' (largest first). 'asc' enables bottom-N queries (e.g., the 25 worst placements by viewable_rate) that cannot be recovered from a truncated descending pull. Sellers MUST apply the requested direction to the applied sort metric — direction has no availability fallback." + ), + ] = sort_direction_1.SortDirection.desc + + +class Geo(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + geo_level: Annotated[ + geo_level_1.GeographicTargetingLevel, + Field(description='Geographic granularity level for the breakdown'), + ] + system: Annotated[ + metro_system.MetroAreaSystem + | postal_system.PostalCodeSystem + | legacy_postal_system.CountryFusedPostalCodeSystem + | None, + Field( + description="Optional classification system for metro or postal_area levels. Metro uses metro-system values (e.g., 'nielsen_dma'); native postal_area uses country-local postal-system values with country (e.g., country 'US', system 'zip'); deprecated legacy postal_area requests may use legacy-postal-system values such as 'us_zip'. Omit to request the level without selecting a specific system." + ), + ] = None + country: Annotated[ + str | None, + Field( + description='ISO 3166-1 alpha-2 country code. Required for native postal_area requests; omitted for legacy postal_area and non-postal geo requests.', + pattern='^[A-Z]{2}$', + ), + ] = None + limit: Annotated[ + SchemaInt | None, + Field( + description='Maximum number of geo entries to return. Defaults to 25. When truncated, by_geo_truncated is true in the response.', + ge=1, + ), + ] = 25 + sort_by: Annotated[ + sort_metric.SortMetric | None, + Field( + description="Metric to sort breakdown rows by, in `sort_direction` order (descending by default). Falls back to 'spend' when the seller does not report the requested metric at this breakdown's row grain; on fallback the sort direction resets to 'desc'. Rows lacking a value for the applied sort metric order last regardless of direction. The applied sort is echoed in the response." + ), + ] = sort_metric.SortMetric.spend + sort_direction: Annotated[ + sort_direction_1.SortDirection | None, + Field( + description="Direction for sort_by ordering. Defaults to 'desc' (largest first). 'asc' enables bottom-N queries (e.g., the 25 worst placements by viewable_rate) that cannot be recovered from a truncated descending pull. Sellers MUST apply the requested direction to the applied sort metric — direction has no availability fallback." + ), + ] = sort_direction_1.SortDirection.desc + + +class DeviceType(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + limit: Annotated[ + SchemaInt | None, + Field( + description='Maximum number of entries to return. When omitted, all entries are returned (the enum is small and bounded).', + ge=1, + ), + ] = None + sort_by: Annotated[ + sort_metric.SortMetric | None, + Field( + description="Metric to sort breakdown rows by, in `sort_direction` order (descending by default). Falls back to 'spend' when the seller does not report the requested metric at this breakdown's row grain; on fallback the sort direction resets to 'desc'. Rows lacking a value for the applied sort metric order last regardless of direction. The applied sort is echoed in the response." + ), + ] = sort_metric.SortMetric.spend + sort_direction: Annotated[ + sort_direction_1.SortDirection | None, + Field( + description="Direction for sort_by ordering. Defaults to 'desc' (largest first). 'asc' enables bottom-N queries (e.g., the 25 worst placements by viewable_rate) that cannot be recovered from a truncated descending pull. Sellers MUST apply the requested direction to the applied sort metric — direction has no availability fallback." + ), + ] = sort_direction_1.SortDirection.desc + cursor: Annotated[ + str | None, + Field( + description="Opaque cursor from a previous response's by_device_type_pagination to fetch the next page of a truncated by_device_type breakdown. Omit for the first page. limit, sort_by, and sort_direction MUST be repeated unchanged across paged requests for the same logical query." + ), + ] = None + + +class DevicePlatform(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + limit: Annotated[ + SchemaInt | None, + Field( + description='Maximum number of entries to return. When omitted, all entries are returned (the enum is small and bounded).', + ge=1, + ), + ] = None + sort_by: Annotated[ + sort_metric.SortMetric | None, + Field( + description="Metric to sort breakdown rows by, in `sort_direction` order (descending by default). Falls back to 'spend' when the seller does not report the requested metric at this breakdown's row grain; on fallback the sort direction resets to 'desc'. Rows lacking a value for the applied sort metric order last regardless of direction. The applied sort is echoed in the response." + ), + ] = sort_metric.SortMetric.spend + sort_direction: Annotated[ + sort_direction_1.SortDirection | None, + Field( + description="Direction for sort_by ordering. Defaults to 'desc' (largest first). 'asc' enables bottom-N queries (e.g., the 25 worst placements by viewable_rate) that cannot be recovered from a truncated descending pull. Sellers MUST apply the requested direction to the applied sort metric — direction has no availability fallback." + ), + ] = sort_direction_1.SortDirection.desc + cursor: Annotated[ + str | None, + Field( + description="Opaque cursor from a previous response's by_device_platform_pagination to fetch the next page of a truncated by_device_platform breakdown. Omit for the first page. limit, sort_by, and sort_direction MUST be repeated unchanged across paged requests for the same logical query." + ), + ] = None + + +class Format(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + limit: Annotated[ + SchemaInt | None, + Field( + description='Maximum number of format rows to return. When omitted, all rows are returned because the canonical format-kind vocabulary is small and bounded.', + ge=1, + ), + ] = None + sort_by: Annotated[ + sort_metric.SortMetric | None, + Field( + description="Metric to sort breakdown rows by, in `sort_direction` order (descending by default). Falls back to 'spend' when the seller does not report the requested metric at this breakdown's row grain; on fallback the sort direction resets to 'desc'. Rows lacking a value for the applied sort metric order last regardless of direction. The applied sort is echoed in the response." + ), + ] = sort_metric.SortMetric.spend + sort_direction: Annotated[ + sort_direction_1.SortDirection | None, + Field( + description="Direction for sort_by ordering. Defaults to 'desc' (largest first). 'asc' enables bottom-N queries (e.g., the 25 worst placements by viewable_rate) that cannot be recovered from a truncated descending pull. Sellers MUST apply the requested direction to the applied sort metric — direction has no availability fallback." + ), + ] = sort_direction_1.SortDirection.desc + + +class Audience(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + limit: Annotated[ + SchemaInt | None, + Field(description='Maximum number of entries to return. Defaults to 25.', ge=1), + ] = 25 + sort_by: Annotated[ + sort_metric.SortMetric | None, + Field( + description="Metric to sort breakdown rows by, in `sort_direction` order (descending by default). Falls back to 'spend' when the seller does not report the requested metric at this breakdown's row grain; on fallback the sort direction resets to 'desc'. Rows lacking a value for the applied sort metric order last regardless of direction. The applied sort is echoed in the response." + ), + ] = sort_metric.SortMetric.spend + sort_direction: Annotated[ + sort_direction_1.SortDirection | None, + Field( + description="Direction for sort_by ordering. Defaults to 'desc' (largest first). 'asc' enables bottom-N queries (e.g., the 25 worst placements by viewable_rate) that cannot be recovered from a truncated descending pull. Sellers MUST apply the requested direction to the applied sort metric — direction has no availability fallback." + ), + ] = sort_direction_1.SortDirection.desc + cursor: Annotated[ + str | None, + Field( + description="Opaque cursor from a previous response's by_audience_pagination to fetch the next page of a truncated by_audience breakdown. Omit for the first page. limit, sort_by, and sort_direction MUST be repeated unchanged across paged requests for the same logical query." + ), + ] = None + + +class Demographic(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + age_ranges: Annotated[ + list[demographic_age_range.DemographicAgeRange] | None, + Field( + description="Optional canonical age ranges to return as distinct rows. Copying an applied targeting predicate here requests aligned reporting only when the reporting capability supports that exact predicate. Omit to request the product's declared native demographic breakdown.", + min_length=1, + ), + ] = None + limit: Annotated[ + SchemaInt | None, + Field(description='Maximum number of demographic entries to return. Defaults to 25.', ge=1), + ] = 25 + sort_by: Annotated[ + sort_metric.SortMetric | None, + Field( + description="Metric to sort breakdown rows by, in `sort_direction` order (descending by default). Falls back to 'spend' when the seller does not report the requested metric at this breakdown's row grain; on fallback the sort direction resets to 'desc'. Rows lacking a value for the applied sort metric order last regardless of direction. The applied sort is echoed in the response." + ), + ] = sort_metric.SortMetric.spend + sort_direction: Annotated[ + sort_direction_1.SortDirection | None, + Field( + description="Direction for sort_by ordering. Defaults to 'desc' (largest first). 'asc' enables bottom-N queries (e.g., the 25 worst placements by viewable_rate) that cannot be recovered from a truncated descending pull. Sellers MUST apply the requested direction to the applied sort metric — direction has no availability fallback." + ), + ] = sort_direction_1.SortDirection.desc + + +class Placement(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + limit: Annotated[ + SchemaInt | None, + Field(description='Maximum number of entries to return. Defaults to 25.', ge=1), + ] = 25 + sort_by: Annotated[ + sort_metric.SortMetric | None, + Field( + description="Metric to sort breakdown rows by, in `sort_direction` order (descending by default). Falls back to 'spend' when the seller does not report the requested metric at this breakdown's row grain; on fallback the sort direction resets to 'desc'. Rows lacking a value for the applied sort metric order last regardless of direction. The applied sort is echoed in the response." + ), + ] = sort_metric.SortMetric.spend + sort_direction: Annotated[ + sort_direction_1.SortDirection | None, + Field( + description="Direction for sort_by ordering. Defaults to 'desc' (largest first). 'asc' enables bottom-N queries (e.g., the 25 worst placements by viewable_rate) that cannot be recovered from a truncated descending pull. Sellers MUST apply the requested direction to the applied sort metric — direction has no availability fallback." + ), + ] = sort_direction_1.SortDirection.desc + cursor: Annotated[ + str | None, + Field( + description="Opaque cursor from a previous response's by_placement_pagination to fetch the next page of a truncated by_placement breakdown. Omit for the first page. limit, sort_by, and sort_direction MUST be repeated unchanged across paged requests for the same logical query." + ), + ] = None + + +class ReportingDimensions(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + catalog_item: Annotated[ + CatalogItem | None, + Field( + description='Request a negotiated catalog_item breakdown. Omitting this key preserves the automatic behavior — sellers return catalog_item rows at their discretion with no truncation contract. Including it (even as {}) makes the truncation disclosure and applied-sort echo binding.' + ), + ] = None + creative: Annotated[ + Creative | None, + Field( + description='Request a negotiated creative breakdown. Omitting this key preserves the automatic behavior — sellers return creative rows at their discretion with no truncation contract. Including it (even as {}) makes the truncation disclosure and applied-sort echo binding.' + ), + ] = None + keyword: Annotated[ + Keyword | None, + Field( + description='Request a negotiated keyword breakdown. Omitting this key preserves the automatic behavior — sellers return keyword rows at their discretion with no truncation contract. Including it (even as {}) makes the truncation disclosure and applied-sort echo binding.' + ), + ] = None + geo: Annotated[ + Geo | None, + Field( + description='Request geographic breakdown. Check reporting_capabilities.supports_geo_breakdown for available levels and systems.' + ), + ] = None + device_type: Annotated[ + DeviceType | None, Field(description='Request device type breakdown.') + ] = None + device_platform: Annotated[ + DevicePlatform | None, Field(description='Request device platform breakdown.') + ] = None + format: Annotated[ + Format | None, + Field( + description='Request delivery broken down by canonical creative format kind. This dimension is negotiated on the GET path. Reporting webhook configuration does not negotiate or guarantee dimensional breakdowns, although a webhook payload may carry the same fields as an extension.' + ), + ] = None + audience: Annotated[ + Audience | None, Field(description='Request audience segment breakdown.') + ] = None + demographic: Annotated[ + Demographic | None, + Field( + description="Request delivery broken down by demographic. Check the product's reporting_capabilities.supports_demographic_breakdown independently from demographic_targeting. When age_ranges is present, every requested range MUST be exactly supported by exact_predicates or equal one of the declared enumerated_intervals; sellers MUST reject unsupported ranges with UNSUPPORTED_FEATURE rather than silently widen or narrow them." + ), + ] = None + spot: Annotated[ + Spot | None, + Field( + description='Request a spot-level as-run airing log for broadcast TV, radio, or other scheduled inventory. Rows are ordered by aired_at ascending. When limit is omitted, sellers SHOULD return the complete log for the requested reporting period.' + ), + ] = None + placement: Annotated[Placement | None, Field(description='Request placement breakdown.')] = None + property: Annotated[ + delivery_breakdown_controls.DeliveryBreakdownControls | None, + Field( + description='Request delivery by canonical publisher property. Check reporting_capabilities.supports_property_breakdown.' + ), + ] = None + collection: Annotated[ + delivery_breakdown_controls.DeliveryBreakdownControls | None, + Field( + description='Request delivery by canonical content collection. Check reporting_capabilities.supports_collection_breakdown.' + ), + ] = None + installment: Annotated[ + delivery_breakdown_controls.DeliveryBreakdownControls | None, + Field( + description='Request delivery by canonical installment within a collection. Check reporting_capabilities.supports_installment_breakdown.' + ), + ] = None + collection_property: Annotated[ + delivery_breakdown_controls.DeliveryBreakdownControls | None, + Field( + description='Request delivery at the collection × property intersection. This is the reporting grain that can prove a collection delivered on a particular host service; independent collection and property marginals cannot. Check reporting_capabilities.supports_collection_property_breakdown.' + ), + ] = None + installment_property: Annotated[ + delivery_breakdown_controls.DeliveryBreakdownControls | None, + Field( + description='Request delivery at the installment × property intersection. This is the reporting grain that can prove a specific airing, episode, issue, or programming block delivered on a particular host service; independent installment and property marginals cannot. Check reporting_capabilities.supports_installment_property_breakdown.' + ), + ] = None + placement_property: Annotated[ + delivery_breakdown_controls.DeliveryBreakdownControls | None, + Field( + description='Request delivery at the placement × property intersection. Use when a placement may span multiple web, app, social, or assistant properties. Check reporting_capabilities.supports_placement_property_breakdown.' + ), + ] = None + + +class GetMediaBuyDeliveryRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + account: Annotated[ + account_ref.AccountReference | None, + Field( + description='Filter delivery data to a specific account. When omitted, returns data across all accessible accounts.' + ), + ] = None + media_buy_ids: Annotated[ + list[str] | None, + Field(description='Array of media buy IDs to get delivery data for', min_length=1), + ] = None + reporting_revision_id: Annotated[ + str | None, + Field( + description='Exact immutable reporting revision to retrieve. This additive Reliable Reporting selector returns content bound to that revision, including its immutable row count, control totals, and content SHA-256. It is mutually exclusive with media-buy, date, metric, and breakdown selectors.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] = None + pagination: Annotated[ + pagination_request.PaginationRequest | None, + Field( + description='Cursor pagination. With reporting_revision_id, pages immutable authoritative rows while all revision metadata repeats on every page.' + ), + ] = None + status_filter: Annotated[ + media_buy_status.MediaBuyStatus | StatusFilter | None, + Field(description='Filter by status. Can be a single status or array of statuses'), + ] = None + start_date: Annotated[ + str | None, + Field( + description="Inclusive start date for the reporting period (YYYY-MM-DD), as a calendar date in the reporting timezone: the reporting_capabilities.timezone of the products behind the in-scope packages. It is a UTC day only when that timezone is UTC. When omitted along with end_date, returns campaign lifetime data. Only accepted when the product's reporting_capabilities.date_range_support is 'date_range'. A date-bounded request whose in-scope packages span more than one reporting timezone MUST be rejected with VALIDATION_ERROR. The buyer narrows media_buy_ids to buys that share one reporting timezone, or omits both dates when a single buy's packages span reporting timezones.", + pattern='^\\d{4}-\\d{2}-\\d{2}$', + ), + ] = None + end_date: Annotated[ + str | None, + Field( + description="Exclusive end date for the reporting period (YYYY-MM-DD), as a calendar date in the same reporting timezone as start_date. Must be later than start_date. When omitted along with start_date, returns campaign lifetime data. Only accepted when the product's reporting_capabilities.date_range_support is 'date_range'.", + pattern='^\\d{4}-\\d{2}-\\d{2}$', + ), + ] = None + include_package_daily_breakdown: Annotated[ + StrictBool | None, + Field( + description='When true, include daily_breakdown arrays within each package in by_package. Useful for per-package pacing analysis and line-item monitoring. Omit or set false to reduce response size — package daily data can be large for multi-package buys over long flights.' + ), + ] = False + requested_metrics: Annotated[ + list[available_metric.AvailableMetric] | None, + Field( + description="Optional list of metrics to include in the response. When omitted, all available metrics are included (unchanged behavior). Applies to every metrics-bearing object in the response: totals, by_package, daily and window slices, and breakdown rows. impressions and spend are always included regardless of this list, except that a legacy or externally created mixed-currency buy MUST omit monetary and money-derived values from media-buy and window totals, MUST omit daily_breakdown, and MUST report monetary values only on currency-qualified package rows (including window package rows). Requesting a leaf metric identity returns its canonical nested carrier — e.g. requesting viewable_rate returns the viewability object, requesting quartile_75 returns quartile_data — never a flat duplicate. Metrics requested but not available for this buy are omitted from the response without error; contract accountability is unchanged — missing_metrics still reconciles against committed_metrics, but sellers MUST NOT list a metric in missing_metrics when its absence is solely due to this narrowing. Must be a subset of the product's reporting_capabilities.available_metrics; values outside the declared set are ignored. Subset evaluation follows the container-subsumption rule in enums/available-metric.json. Sort is evaluated before narrowing: excluding a metric from this list never triggers the sort_by fallback, and breakdown rows may be ordered by a metric absent from the narrowed payload — the applied-sort echo still names it. Same narrowing semantics as reporting_webhook.requested_metrics, with one shape difference: this field requires at least one entry when present (omit it entirely for full payloads), while the webhook field permits an empty array with the same meaning as omission.", + min_length=1, + ), + ] = None + time_granularity: Annotated[ + reporting_frequency.ReportingFrequency | None, + Field( + description="Per-window slice granularity for the pull, using the same vocabulary as reporting_webhook.reporting_frequency. When set, the seller returns per-window delivery slices over the date range — useful for reconstructing data a buyer's webhook receiver missed, since the slice payload is shape-aligned with what reporting_webhook would have delivered for the same window. Capability-scoped: the value MUST be one of the seller's declared reporting_capabilities.windowed_pull_granularities; otherwise the seller MUST return UNSUPPORTED_GRANULARITY. When set to daily, weekly, monthly, or quarterly and the in-scope packages span more than one reporting timezone, the seller MUST return VALIDATION_ERROR. When omitted, behavior is unchanged (cumulative aggregates plus optional daily breakdowns per existing fields)." + ), + ] = None + include_window_breakdown: Annotated[ + StrictBool | None, + Field( + description="When true, the response includes media_buy_deliveries[].windows[] — an array of per-window delivery slices over the date range at the requested time_granularity. Ignored when time_granularity is omitted. Each window's payload mirrors what reporting_webhook would have delivered for the same window, enabling lossless GET-path recovery for buyers who missed webhook fires. Omit or set false to reduce response size when only cumulative aggregates are needed." + ), + ] = False + attribution_window: Annotated[ + AttributionWindow | None, + Field( + description='Attribution window to apply for conversion metrics. When provided, the seller returns conversion data using the requested lookback windows instead of their platform default. The seller echoes the applied window in the response. Sellers that do not support configurable windows ignore this field and return their default. Check get_adcp_capabilities conversion_tracking.attribution_windows for available options.' + ), + ] = None + reporting_dimensions: Annotated[ + ReportingDimensions | None, + Field( + description='Request dimensional breakdowns in delivery reporting. Each key enables a specific breakdown dimension within by_package — include as an empty object (e.g., "device_type": {}) to activate with defaults. Omit entirely for no breakdowns (backward compatible). Unsupported dimensions are silently omitted from the response. For every requested dimension that the product declares supported, the seller MUST return the corresponding array (possibly empty) and its truncated flag. Metric-sorted dimensions also return their applied-sort echoes; demographic and property-grain arrays also return their suppressed flag. Spot uses aired_at ordering and has no sort echoes. Note: keyword, catalog_item, and creative breakdowns are returned automatically when the seller supports them; including their keys here is optional and upgrades them to this negotiated contract without changing the automatic default.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + @model_validator(mode='after') + def _validate_delivery_selector_mode(self) -> GetMediaBuyDeliveryRequest: + if self.reporting_revision_id is not None: + if self.model_fields_set.intersection(('media_buy_ids', 'start_date', 'end_date', 'status_filter', 'requested_metrics', 'reporting_dimensions', 'attribution_window', 'include_package_daily_breakdown', 'time_granularity', 'include_window_breakdown')): + raise ValueError('exact revision requests forbid aggregate selectors, even false or null') + elif self.pagination is not None: + raise ValueError('pagination requires reporting_revision_id') + return self -# Explicit exports -__all__ = [ - "AttributionWindow", - "Audience", - "CatalogItem", - "Creative", - "Demographic", - "DevicePlatform", - "DeviceType", - "Format", - "Geo", - "GetMediaBuyDeliveryRequest", - "Keyword", - "Placement", - "ReportingDimensions", - "Spot", - "StatusFilter", -] + @model_serializer(mode='wrap') + def _serialize_delivery_selector_mode(self, handler: SerializerFunctionWrapHandler) -> dict[str, Any]: + value: dict[str, Any] = handler(self) + if self.reporting_revision_id is not None: + for name in ('media_buy_ids', 'start_date', 'end_date', 'status_filter', 'requested_metrics', 'reporting_dimensions', 'attribution_window', 'include_package_daily_breakdown', 'time_granularity', 'include_window_breakdown'): + if name not in self.model_fields_set: + value.pop(name, None) + return value diff --git a/src/adcp/types/domains/media_buy/get_media_buy_delivery_response.py b/src/adcp/types/domains/media_buy/get_media_buy_delivery_response.py index 449269bbd..624db149a 100644 --- a/src/adcp/types/domains/media_buy/get_media_buy_delivery_response.py +++ b/src/adcp/types/domains/media_buy/get_media_buy_delivery_response.py @@ -1,58 +1,1236 @@ -"""Types declared by the AdCP ``media_buy/get_media_buy_delivery_response`` schema. +# generated by datamodel-codegen: +# filename: media_buy/get_media_buy_delivery_response.json +# timestamp: 2026-10-05T13:49:50+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.get_media_buy_delivery_response import +from adcp.types.base import AdcpResponse, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from collections.abc import Sequence +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat -from adcp.types.generated_poc.media_buy.get_media_buy_delivery_response import ( - AggregatedTotals, - ByAudienceItem, - ByDemographicItem, - ByDevicePlatformItem, - ByDeviceTypeItem, - ByFormatItem, - ByPackageItem, - ByPackageItem1, - BySpotItem, - DailyBreakdownItem, - DailyBreakdownItem1, - GetMediaBuyDeliveryResponse, - MediaBuyDelivery, - NotificationType, - ReportingPeriod, - ReportingRevisionBinding, - Status, - Totals, - Window, +from ..core import attribution_window as attribution_window_1 +from ..core import ( + catalog_item_delivery_metrics, + collection_delivery_metrics, + collection_property_delivery_metrics, +) +from ..core import context as context_1 +from ..core import ( + creative_delivery_metrics, + delivery_metric_aggregate, + delivery_metrics, + demographic_age_range, + error, ) +from ..core import ext as ext_1 +from ..core import ( + geo_delivery_metrics, + installment_delivery_metrics, + installment_property_delivery_metrics, + keyword_delivery_metrics, + missing_metric, + package_delivery_metric_value, + pagination_response, + placement_delivery_metrics, + placement_property_delivery_metrics, + property_delivery_metrics, + reporting_control_total, +) +from ..core import reporting_revision as reporting_revision_1 +from ..core.delivery_metrics import DeliveryMetrics +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import audience_source as audience_source_1 +from ..enums import delivery_status as delivery_status_1 +from ..enums import demographic_system as demographic_system_1 +from ..enums import device_platform as device_platform_1 +from ..enums import device_type as device_type_1 +from ..enums import pricing_model as pricing_model_1 +from ..enums import reach_aggregation as reach_aggregation_1 +from ..enums import reach_unit as reach_unit_1 +from ..enums import sort_direction, sort_metric + + +class NotificationType(StrEnum): + scheduled = 'scheduled' + final = 'final' + delayed = 'delayed' + adjusted = 'adjusted' + window_update = 'window_update' + + +class ReportingPeriod(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + start: Annotated[ + AwareDatetime, + Field( + description="Inclusive RFC 3339 start instant. For a date-bounded request, the start of start_date in the reporting timezone, written with that zone's UTC offset or as the equivalent UTC instant (e.g., 2026-04-15T00:00:00-04:00 or 2026-04-15T04:00:00Z for America/New_York; 2026-04-15T00:00:00Z for UTC)." + ), + ] + end: Annotated[ + AwareDatetime, + Field( + description='Exclusive RFC 3339 end instant. For a date-bounded request, the start of end_date in the reporting timezone (e.g., 2026-04-16T00:00:00-04:00 for a report covering 2026-04-15 in America/New_York).' + ), + ] + timezone: Annotated[ + str | None, + Field( + description="Reporting timezone applied to this report, as 'UTC' or an IANA timezone identifier. It equals the in-scope products' reporting_capabilities.timezone. start_date, end_date, the reporting_period boundaries, daily_breakdown[].date, and daily, weekly, or monthly windows[] all use it, so a stored report stays interpretable if the product's capability later changes. Sellers SHOULD return it on reads without reporting_revision_id whenever every in-scope media buy shares one reporting timezone. Exact reporting_revision_id reads return the revision period, which carries source_timezone instead.", + examples=['UTC', 'America/New_York'], + min_length=1, + ), + ] = None + + +class Status(StrEnum): + pending_creatives = 'pending_creatives' + pending_start = 'pending_start' + pending = 'pending' + active = 'active' + paused = 'paused' + completed = 'completed' + rejected = 'rejected' + canceled = 'canceled' + failed = 'failed' + reporting_delayed = 'reporting_delayed' + + +class DailyBreakdownItem(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + date: Annotated[ + str, + Field( + description="Calendar date (YYYY-MM-DD) in the reporting timezone: reporting_period.timezone when present, otherwise this package's product reporting_capabilities.timezone. The row covers that local day, which can be 23 or 25 hours long across a DST change.", + pattern='^\\d{4}-\\d{2}-\\d{2}$', + ), + ] + impressions: Annotated[ + StrictFloat, Field(description='Daily impressions for this package', ge=0.0) + ] + spend: Annotated[StrictFloat, Field(description='Daily spend for this package', ge=0.0)] + conversions: Annotated[ + StrictFloat | None, Field(description='Daily conversions for this package', ge=0.0) + ] = None + conversion_value: Annotated[ + StrictFloat | None, Field(description='Daily conversion value for this package', ge=0.0) + ] = None + commissionable_value: Annotated[ + StrictFloat | None, + Field( + description='Daily settled conversion value eligible for revenue-share commission for this package', + ge=0.0, + ), + ] = None + roas: Annotated[ + StrictFloat | None, + Field(description='Daily return on ad spend (conversion_value / spend)', ge=0.0), + ] = None + new_to_brand_rate: Annotated[ + StrictFloat | None, + Field( + description='Daily fraction of conversions from first-time brand buyers (0 = none, 1 = all)', + ge=0.0, + le=1.0, + ), + ] = None + + +class DailyBreakdownItem1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + date: Annotated[ + str, + Field( + description="Calendar date (YYYY-MM-DD) in the reporting timezone: reporting_period.timezone when present, otherwise this media buy's product reporting_capabilities.timezone. The row covers that local day, which can be 23 or 25 hours long across a DST change.", + pattern='^\\d{4}-\\d{2}-\\d{2}$', + ), + ] + impressions: Annotated[StrictFloat, Field(description='Daily impressions', ge=0.0)] + spend: Annotated[StrictFloat, Field(description='Daily spend', ge=0.0)] + conversions: Annotated[StrictFloat | None, Field(description='Daily conversions', ge=0.0)] = ( + None + ) + conversion_value: Annotated[ + StrictFloat | None, Field(description='Daily conversion value', ge=0.0) + ] = None + commissionable_value: Annotated[ + StrictFloat | None, + Field( + description='Daily settled conversion value eligible for revenue-share commission', + ge=0.0, + ), + ] = None + roas: Annotated[ + StrictFloat | None, + Field(description='Daily return on ad spend (conversion_value / spend)', ge=0.0), + ] = None + new_to_brand_rate: Annotated[ + StrictFloat | None, + Field( + description='Daily fraction of conversions from first-time brand buyers (0 = none, 1 = all)', + ge=0.0, + le=1.0, + ), + ] = None + + +class ReportingRevisionBinding(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + reporting_revision_id: Annotated[str, Field(max_length=255, min_length=1)] + row_count: Annotated[SchemaInt, Field(ge=0)] + control_totals: list[reporting_control_total.ReportingControlTotal] + content_sha256: Annotated[ + str, + Field( + description='SHA-256 of RFC 8785 JCS of {reporting_revision_id,row_count,control_totals,reporting_rows}, where reporting_rows is the complete ordered sequence concatenated across every cursor page; identical to reporting_revision_1.revision_content_sha256.', + pattern='^[A-Fa-f0-9]{64}$', + ), + ] + + +class AggregatedTotals(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + impressions: Annotated[ + StrictFloat, Field(description='Total impressions delivered across all media buys', ge=0.0) + ] + spend: Annotated[ + StrictFloat, Field(description='Total amount spent across all media buys', ge=0.0) + ] + clicks: Annotated[ + StrictFloat | None, + Field(description='Total clicks across all media buys (if applicable)', ge=0.0), + ] = None + completed_views: Annotated[ + StrictFloat | None, + Field( + description='Total audio/video completions across all media buys (if applicable)', + ge=0.0, + ), + ] = None + views: Annotated[ + StrictFloat | None, + Field(description='Total views across all media buys (if applicable)', ge=0.0), + ] = None + conversions: Annotated[ + StrictFloat | None, + Field(description='Total conversions across all media buys (if applicable)', ge=0.0), + ] = None + conversion_value: Annotated[ + StrictFloat | None, + Field(description='Total conversion value across all media buys (if applicable)', ge=0.0), + ] = None + commissionable_value: Annotated[ + StrictFloat | None, + Field( + description='Total settled conversion value eligible for revenue-share commission across all media buys (if applicable)', + ge=0.0, + ), + ] = None + roas: Annotated[ + StrictFloat | None, + Field( + description='Aggregate return on ad spend across all media buys (total conversion_value / total spend)', + ge=0.0, + ), + ] = None + new_to_brand_rate: Annotated[ + StrictFloat | None, + Field( + description='Fraction of total conversions across all media buys from first-time brand buyers (weighted by conversion volume, not a simple average of per-buy rates)', + ge=0.0, + le=1.0, + ), + ] = None + cost_per_acquisition: Annotated[ + StrictFloat | None, + Field( + description='Aggregate cost per conversion across all media buys (total spend / total conversions)', + ge=0.0, + ), + ] = None + completion_rate: Annotated[ + StrictFloat | None, + Field( + description='Aggregate completion rate across all media buys (weighted by impressions, not a simple average of per-buy rates). Null indicates the metric is not applicable to the aggregated buys (e.g. all non-video inventory).', + ge=0.0, + le=1.0, + ), + ] = None + reach: Annotated[ + StrictFloat | None, + Field( + description='Reach across all media buys. Only present when all media buys share the same reach_unit. Omitted when reach units are heterogeneous — use per-buy reach values instead. The optional reach_aggregation field declares whether this value is deduplicated across buys or is a sum of constituent reach values.', + ge=0.0, + ), + ] = None + reach_aggregation: Annotated[ + reach_aggregation_1.ReachAggregation | None, + Field( + description='How reach was combined across the media buys in this aggregate. When omitted, legacy reach semantics are unknown and consumers MUST NOT use reach as the denominator for frequency.' + ), + ] = None + reach_unit: Annotated[ + reach_unit_1.ReachUnit | None, + Field( + description='Unit of measurement for reach. Only present when all aggregated media buys use the same reach_unit.' + ), + ] = None + frequency: Annotated[ + StrictFloat | None, + Field( + description='Average frequency per reach unit across all media buys (impressions / reach). In new payloads, only present when reach is present and reach_aggregation is deduplicated. MUST be omitted when reach_aggregation is sum_of_constituent_reach. Legacy payloads that omit reach_aggregation remain schema-valid, but consumers MUST NOT treat their reach as a safe frequency denominator.', + ge=0.0, + ), + ] = None + media_buy_count: Annotated[ + SchemaInt, Field(description='Number of media buys included in the response', ge=0) + ] + metric_aggregates: Annotated[ + list[delivery_metric_aggregate.DeliveryMetricAggregate] | None, + Field( + description="Cross-buy delivery aggregates partitioned by qualifier. Row-symmetric with `package.committed_metrics` and `by_package[].missing_metrics` — same atomic unit `(scope, metric_id, qualifier)` — so reconciliation collapses to a row-level join on the tuple. Granularity rule: one row per `(metric_id, full-qualifier-set)`, reported at the finest available granularity; buyers re-aggregate up if they want a coarser view. Used only for metrics with non-empty qualifier sets — unqualified metrics (`impressions`, `spend`, `media_buy_count`, etc.) remain at the top of `aggregated_totals`. **Mutual exclusion MUST**: for any `metric_id` appearing in `metric_aggregates`, the corresponding top-level scalar in `aggregated_totals` MUST be omitted (not zeroed) — avoids duplicate sources of truth. The qualifier vocabulary on this delivery surface is closed today (`additionalProperties: false`, same content as `committed_metrics.qualifier`) but is expected to **diverge from contract qualifier in future minors** as transparency disclosures buyers don't commit to ship delivery-only (e.g., `tracker_firing` pending #3832 resolution). Each row carries a `value` plus inlined per-metric component fields (e.g., `measurable_impressions` and `viewable_impressions` for `viewable_rate`; `spend` and `conversions` for `cost_per_acquisition`). Per-buy `totals` keeps its flat shape — each buy is single-qualifier by definition; only the aggregate spans qualifiers. **Qualifier-set drift across reports**: when a campaign gains a new qualifier mid-flight (e.g., adds `tracker_firing` partitioning in week 2), prior periods' rows remain valid at their original granularity; buyers SHOULD NOT retroactively repartition.", + examples=[ + [ + { + 'scope': 'standard', + 'metric_id': 'viewable_rate', + 'qualifier': {'viewability_standard': 'mrc'}, + 'value': 0.7286, + 'measurable_impressions': 700000, + 'viewable_impressions': 510000, + }, + { + 'scope': 'standard', + 'metric_id': 'viewable_rate', + 'qualifier': {'viewability_standard': 'groupm'}, + 'value': 0.55, + 'measurable_impressions': 180000, + 'viewable_impressions': 99000, + }, + { + 'scope': 'vendor', + 'vendor': {'domain': 'attentionvendor.example'}, + 'metric_id': 'attention_units', + 'qualifier': {}, + 'value': 4.2, + 'measurable_impressions': 800000, + }, + ] + ], + ), + ] = None + + +class Totals(DeliveryMetrics): + effective_rate: Annotated[ + StrictFloat | None, + Field( + description="Effective rate paid per unit based on pricing_model (e.g., actual CPM for 'cpm', actual cost per completed view for 'cpcv', actual cost per point for 'cpp')", + ge=0.0, + ), + ] = None + + +class ByFormatItem(DeliveryMetrics): + format_kind: Annotated[ + str, + Field( + description='Canonical creative format kind aggregated by this row. This identifies creative shape, not duration or other format-option parameters.' + ), + ] + impressions: Any + spend: Any + + +class ByDeviceTypeItem(DeliveryMetrics): + device_type: Annotated[ + device_type_1.DeviceType, + Field(description='Device form factor (desktop, mobile, tablet, ctv, dooh, unknown)'), + ] + impressions: Any + spend: Any + + +class ByDevicePlatformItem(DeliveryMetrics): + device_platform: Annotated[ + device_platform_1.DevicePlatform, Field(description='Operating system platform') + ] + impressions: Any + spend: Any + + +class ByAudienceItem(DeliveryMetrics): + audience_id: Annotated[ + str, + Field( + description="Audience segment identifier. For 'synced' source, matches audience_id from sync_audiences. For other sources, seller-defined." + ), + ] + audience_source: Annotated[ + audience_source_1.AudienceSource, + Field( + description='Origin of the audience segment (synced, platform, third_party, lookalike, retargeting, unknown)' + ), + ] + audience_name: Annotated[ + str | None, Field(description='Human-readable audience segment name') + ] = None + impressions: Any + spend: Any + + +class ByDemographicItem(DeliveryMetrics): + demographic: Annotated[ + str, + Field( + description="Demographic code in the declared system's notation (for example, P25-54 for Nielsen).", + min_length=1, + pattern='\\S', + ), + ] + demographic_system: Annotated[ + demographic_system_1.DemographicSystem, + Field(description='Measurement system that defines the demographic code.'), + ] + age: Annotated[ + demographic_age_range.DemographicAgeRange | None, + Field( + description="Canonical age interval represented by this row. Required by protocol semantics for age ranges requested through reporting_dimensions.demographic.age_ranges and for intervals declared in the product's demographic reporting capability." + ), + ] = None + impressions: Any + spend: Any + + +class BySpotItem(DeliveryMetrics): + spot_id: Annotated[ + str, + Field( + description='Stable seller-scoped identifier for this scheduled spot occurrence. Reused across measurement-window updates and available to later preemption or makegood workflows. Distinct from creative_id.', + min_length=1, + ), + ] + creative_id: Annotated[ + str | None, + Field( + description="Optional identifier for the creative that aired in this spot occurrence, matching the package's creative asset or assignment. For library-backed sellers this is the sync_creatives identifier; inline-only sellers use the packages[].creatives creative_id.", + min_length=1, + ), + ] = None + aired_at: Annotated[ + AwareDatetime, + Field( + description='RFC 3339 timestamp when the spot actually aired. Its presence proves an airing; it is not inferred from impressions.' + ), + ] + network: Annotated[ + str | None, + Field( + description="Optional network or syndicated service name, for example 'USA Network' or 'Westwood One'.", + min_length=1, + ), + ] = None + station: Annotated[ + str | None, + Field( + description="Optional station identifier or call sign, for example 'WABC-TV' or 'WNYC-FM'.", + min_length=1, + ), + ] = None + daypart: Annotated[ + str | None, + Field( + description="Optional seller-defined daypart label, for example 'prime_time' or 'morning_drive'.", + min_length=1, + ), + ] = None + + +class ByPackageItem1(DeliveryMetrics): + package_id: Annotated[str, Field(description="Seller's package identifier")] + currency: Annotated[ + str | None, + Field( + description="ISO 4217 denomination for this package window's monetary values. Required by the runtime currency-coherence rule when neither the parent media-buy row nor deprecated response-wide field supplies a currency and this package window contains monetary values; otherwise it MUST match the row or response currency when present.", + pattern='^[A-Z]{3}$', + ), + ] = None + + +class Window(AdCPBaseModel): + window_start: Annotated[ + AwareDatetime, + Field( + description='ISO 8601 start of the window slice (inclusive). Daily, weekly, monthly, and quarterly slices start on reporting-timezone calendar boundaries (see reporting_period.timezone), which are UTC midnights only when that timezone is UTC. Weekly slices start Monday 00:00 in the reporting timezone (ISO 8601 weeks). Quarterly slices start on 1 January, 1 April, 1 July and 1 October.' + ), + ] + window_end: Annotated[ + AwareDatetime, + Field( + description="ISO 8601 end of the window slice (exclusive). The next row's window_start equals this value when slices are contiguous." + ), + ] + totals: Annotated[ + delivery_metrics.DeliveryMetrics, + Field( + description='Aggregate metrics for this window slice across all packages. Shape-aligned with reporting_webhook fire payloads at the same granularity.' + ), + ] + by_package: Annotated[ + list[ByPackageItem1] | None, + Field( + description='Per-package metrics for this window slice, using the same metric envelope and package identity as the parent media_buy_deliveries[].by_package row but scoped to the window. Requested reporting_dimensions do not apply to these webhook-aligned recovery rows; sellers may include dimensional fields only as webhook payload extensions, not as a guaranteed result of the GET request. Sellers MAY omit by_package when per-package window-level data is unavailable; when present, package_id values MUST match the parent by_package entries.' + ), + ] = None + is_final: Annotated[ + StrictBool | None, + Field( + description='Whether the slice data is final for this window. Same semantics as the per-package is_final flag — when false, a later read may return revised numbers for the same window as measurement matures (broadcast C3 → C7, post-IVT filtering, etc.).' + ), + ] = None + measurement_window: Annotated[ + str | None, + Field( + description="Which measurement-window stage this slice represents (e.g., 'live', 'c3', 'c7'), referencing a window_id from the product's reporting_capabilities.measurement_windows. Absent when the data is not phased.", + max_length=50, + ), + ] = None + + +class ByPackageItem(DeliveryMetrics): + package_id: Annotated[str, Field(description="Seller's package identifier")] + pacing_index: Annotated[ + StrictFloat | None, + Field( + description='Package delivery pace relative to its package-level pacing plan (1.0 = on track, <1.0 = behind, >1.0 = ahead). In seller-optimized mode this is a subordinate diagnostic and may be absent when no package pacing preference exists.', + ge=0.0, + ), + ] = None + pricing_model: Annotated[ + pricing_model_1.PricingModel, + Field( + description='The pricing model used for this package (e.g., cpm, cpcv, cpp). Indicates how the package is billed and which metrics are most relevant for optimization.' + ), + ] + rate: Annotated[ + StrictFloat, + Field( + description='The pricing rate for this package. For fixed-rate pricing, this is the agreed currency-denominated unit rate (e.g., CPM rate of 12.50 means $12.50 per 1,000 impressions). For auction-based pricing, this is the effective rate based on actual delivery. For revenue_share, this is the decimal commission rate (e.g., 0.04 means 4%) and is not itself currency-denominated.', + ge=0.0, + ), + ] + currency: Annotated[ + str, + Field( + description="ISO 4217 currency code for this package's spend and currency-denominated pricing rate. The rate for revenue_share is a dimensionless commission fraction, but attributed monetary values still use this currency. When the enclosing media_buy_deliveries[].currency is present, this value MUST equal it. For AdCP-authored buys both values MUST equal the media-buy currency. A different package currency is permitted only for a legacy or externally created mixed-currency buy whose row currency, daily_breakdown, and row/window monetary totals are omitted. AdCP does not perform currency conversion.", + pattern='^[A-Z]{3}$', + ), + ] + delivery_status: Annotated[ + delivery_status_1.DeliveryStatus | None, + Field( + description="System-reported operational state of this package. Reflects actual delivery state independent of buyer pause control. 'not_delivering' means zero impressions were recorded for the entire reporting_period while the package was in-flight. Sellers SHOULD only report 'not_delivering' once the package's data is_final for the period — a provisional (is_final: false) zero may still be measurement catching up, not genuine non-delivery." + ), + ] = None + paused: Annotated[ + StrictBool | None, + Field(description='Whether this package is currently paused by the buyer'), + ] = None + is_final: Annotated[ + StrictBool | None, + Field( + description="Whether this delivery data is final for the reporting period. When false, the data may be updated as measurement matures (e.g., broadcast C7 window accumulating DVR playback) or as processing completes (e.g., IVT filtering, deduplication). When true, the seller considers this data closed — no further updates for this period — and is willing to invoice on it subject to the buy's `measurement_terms.billing_measurement`. Absent means the seller does not distinguish provisional from final data." + ), + ] = None + finalized_at: Annotated[ + AwareDatetime | None, + Field( + description="ISO 8601 timestamp at which this package's data became final. Present only when `is_final: true`. Anchors reconciliation and (when later defined) dispute-window clocks against the buy's `measurement_terms.billing_measurement.measurement_window`." + ), + ] = None + measurement_window: Annotated[ + str | None, + Field( + description="Which measurement window this data represents, referencing a window_id from the product's reporting_capabilities.measurement_windows. For broadcast: 'live', 'c3', 'c7'. When absent, the data is not windowed (standard digital reporting). When present with is_final: false, a later report for the same period will provide a wider window or more complete data.", + examples=['live', 'c3', 'c7'], + max_length=50, + ), + ] = None + supersedes_window: Annotated[ + str | None, + Field( + description="Which measurement window this data replaces. Present on window_update notifications to indicate progression (e.g., 'live' when reporting C3 data that supersedes live-only numbers). Absent on the first report for a period. Buyers should replace stored data for the superseded window with this report's data.", + examples=['live', 'c3'], + max_length=50, + ), + ] = None + missing_metrics: Annotated[ + list[missing_metric.MissingMetric] | None, + Field( + description="Metrics that the binding reporting contract declared but that are NOT populated in this report. Reconciliation source: when `package.committed_metrics` is present, `missing_metrics` is computed against entries where `committed_at < reporting_period.end` — independent of subsequent product mutations and respecting the commitment timestamp on each entry (a metric committed mid-flight is only flagged missing in reports for periods after its commitment). When `package.committed_metrics` is absent, fall back to the product's current `reporting_capabilities.available_metrics` (no timestamp filter). Empty array (or absent) indicates clean delivery against the contract. Non-empty signals an accountability breach — the seller committed to the metric but did not produce the value here. Sellers MUST exclude metrics that are not yet measurable for the current `measurement_window` (e.g., post-IVT counts during the live window) — those will appear (or not) when a wider window supersedes this report via `supersedes_window`. Each entry uses an explicit `scope` discriminator: `standard` for entries from the closed `available-metric.json` enum, `vendor` for vendor-defined metrics anchored on a BrandRef. Symmetric with `committed_metrics`. When the request narrowed the payload via requested_metrics, sellers MUST NOT list a committed metric here solely because the buyer excluded it — missing_metrics reports delivery gaps, not request narrowing.", + examples=[ + [], + [{'scope': 'standard', 'metric_id': 'completed_views'}], + [ + {'scope': 'standard', 'metric_id': 'completed_views'}, + { + 'scope': 'vendor', + 'vendor': {'domain': 'attentionvendor.example'}, + 'metric_id': 'attention_units', + }, + ], + ], + ), + ] = None + metric_values: Annotated[ + list[package_delivery_metric_value.PackageDeliveryMetricValue] | None, + Field( + description='Qualified standard delivery values for this package. Each entry is the delivered counterpart to a standard-scope package.committed_metrics or by_package[].missing_metrics row, using the same atomic key (scope, metric_id, qualifier). Sellers report one row per full qualifier set at package grain; when a metric appears here, its flat scalar counterpart on the package MUST be omitted to avoid two sources of truth. Vendor-scope values continue to use vendor_metric_values. Buyers reconcile rows directly and perform any compatible cross-package aggregation themselves.' + ), + ] = None + by_catalog_item: Annotated[ + list[catalog_item_delivery_metrics.CatalogItemDeliveryMetrics] | None, + Field( + description='Delivery by catalog item within this package. Available for catalog-driven packages when the seller supports item-level reporting.' + ), + ] = None + by_catalog_item_truncated: Annotated[ + StrictBool | None, + Field( + description='Whether by_catalog_item was truncated due to the requested limit or a seller-imposed maximum. Sellers MUST return this flag whenever by_catalog_item is present and the request included reporting_dimensions.catalog_item (false means the list is complete). When the breakdown was returned automatically without a request key, the flag is RECOMMENDED but not required — automatic rows carry no completeness contract.' + ), + ] = None + by_catalog_item_sorted_by: Annotated[ + sort_metric.SortMetric | None, + Field( + description="The metric actually used to order by_catalog_item rows. Sellers MUST return this field whenever by_catalog_item is present and the request included reporting_dimensions.catalog_item. When the seller cannot sort by the requested sort_by metric it falls back to 'spend'; this echo makes the fallback visible instead of silently returning rows the buyer will misread as ordered by the requested metric. When the breakdown was returned automatically without a request key, the field is RECOMMENDED but not required — automatic rows carry no completeness contract." + ), + ] = None + by_catalog_item_sort_direction: Annotated[ + sort_direction.SortDirection | None, + Field( + description='The direction actually applied to by_catalog_item ordering. Sellers MUST return this field whenever by_catalog_item is present and the request included reporting_dimensions.catalog_item, alongside by_catalog_item_sorted_by. When the breakdown was returned automatically without a request key, the field is RECOMMENDED but not required — automatic rows carry no completeness contract.' + ), + ] = None + by_creative: Annotated[ + list[creative_delivery_metrics.CreativeDeliveryMetrics] | None, + Field( + description='Metrics broken down by creative within this package. Available when the seller supports creative-level reporting.' + ), + ] = None + by_format: Annotated[ + list[ByFormatItem] | None, + Field( + description="Delivery by canonical creative format kind within this package. Negotiated on the GET path when the buyer requests reporting_dimensions.format and the product declares supports_format_breakdown; reporting webhook configuration does not negotiate or guarantee this breakdown. Each row aggregates every served creative of that format kind. Sellers MUST aggregate all delivery using adopter-defined shapes into one format_kind 'custom' row. When by_format_truncated is false, additive metrics such as impressions and spend across the rows SHOULD reconcile to the corresponding package totals, subject to the measurement and attribution semantics of each metric. Buyers MUST NOT expect row-level correspondence between by_format and by_creative because the two breakdowns are independently produced at different grains." + ), + ] = None + by_format_truncated: Annotated[ + StrictBool | None, + Field( + description='Whether by_format was truncated due to the requested limit or a seller-imposed maximum. Sellers MUST return this flag whenever by_format is present (false means the list is complete).' + ), + ] = None + by_format_sorted_by: Annotated[ + sort_metric.SortMetric | None, + Field( + description="The metric actually used to order by_format rows. Sellers MUST return this field whenever by_format is present. When the seller cannot sort by the requested sort_by metric it falls back to 'spend'; this echo makes the fallback visible instead of silently returning rows the buyer will misread as ordered by the requested metric." + ), + ] = None + by_format_sort_direction: Annotated[ + sort_direction.SortDirection | None, + Field( + description='The direction actually applied to by_format ordering. Sellers MUST return this field whenever by_format is present.' + ), + ] = None + by_creative_truncated: Annotated[ + StrictBool | None, + Field( + description='Whether by_creative was truncated due to the requested limit or a seller-imposed maximum. Sellers MUST return this flag whenever by_creative is present and the request included reporting_dimensions.creative (false means the list is complete). When the breakdown was returned automatically without a request key, the flag is RECOMMENDED but not required — automatic rows carry no completeness contract.' + ), + ] = None + by_creative_sorted_by: Annotated[ + sort_metric.SortMetric | None, + Field( + description="The metric actually used to order by_creative rows. Sellers MUST return this field whenever by_creative is present and the request included reporting_dimensions.creative. When the seller cannot sort by the requested sort_by metric it falls back to 'spend'; this echo makes the fallback visible instead of silently returning rows the buyer will misread as ordered by the requested metric. When the breakdown was returned automatically without a request key, the field is RECOMMENDED but not required — automatic rows carry no completeness contract." + ), + ] = None + by_creative_sort_direction: Annotated[ + sort_direction.SortDirection | None, + Field( + description='The direction actually applied to by_creative ordering. Sellers MUST return this field whenever by_creative is present and the request included reporting_dimensions.creative, alongside by_creative_sorted_by. When the breakdown was returned automatically without a request key, the field is RECOMMENDED but not required — automatic rows carry no completeness contract.' + ), + ] = None + by_keyword: Annotated[ + list[keyword_delivery_metrics.KeywordDeliveryMetrics] | None, + Field( + description='Metrics broken down by keyword within this package. One row per (keyword, match_type) pair — the same keyword with different match types appears as separate rows. Keyword-grain only: rows reflect aggregate performance of each targeted keyword, not individual search queries. Rows may not sum to package totals when a single impression is attributed to the triggering keyword only. Available for search and retail media packages when the seller supports keyword-level reporting.' + ), + ] = None + by_keyword_truncated: Annotated[ + StrictBool | None, + Field( + description='Whether by_keyword was truncated due to the requested limit or a seller-imposed maximum. Sellers MUST return this flag whenever by_keyword is present and the request included reporting_dimensions.keyword (false means the list is complete). When the breakdown was returned automatically without a request key, the flag is RECOMMENDED but not required — automatic rows carry no completeness contract.' + ), + ] = None + by_keyword_sorted_by: Annotated[ + sort_metric.SortMetric | None, + Field( + description="The metric actually used to order by_keyword rows. Sellers MUST return this field whenever by_keyword is present and the request included reporting_dimensions.keyword. When the seller cannot sort by the requested sort_by metric it falls back to 'spend'; this echo makes the fallback visible instead of silently returning rows the buyer will misread as ordered by the requested metric. When the breakdown was returned automatically without a request key, the field is RECOMMENDED but not required — automatic rows carry no completeness contract." + ), + ] = None + by_keyword_sort_direction: Annotated[ + sort_direction.SortDirection | None, + Field( + description='The direction actually applied to by_keyword ordering. Sellers MUST return this field whenever by_keyword is present and the request included reporting_dimensions.keyword, alongside by_keyword_sorted_by. When the breakdown was returned automatically without a request key, the field is RECOMMENDED but not required — automatic rows carry no completeness contract.' + ), + ] = None + by_geo: Annotated[ + list[geo_delivery_metrics.GeoDeliveryMetrics] | None, + Field( + description="Delivery by geographic area within this package. Available when the buyer requests geo breakdown via reporting_dimensions and the seller supports it. Each dimension's rows are independent slices that should sum to the package total." + ), + ] = None + by_geo_truncated: Annotated[ + StrictBool | None, + Field( + description='Whether by_geo was truncated due to the requested limit or a seller-imposed maximum. Sellers MUST return this flag whenever by_geo is present (false means the list is complete).' + ), + ] = None + by_geo_sorted_by: Annotated[ + sort_metric.SortMetric | None, + Field( + description="The metric actually used to order by_geo rows. Sellers MUST return this field whenever by_geo is present. When the seller cannot sort by the requested sort_by metric it falls back to 'spend'; this echo makes the fallback visible instead of silently returning rows the buyer will misread as ordered by the requested metric." + ), + ] = None + by_geo_sort_direction: Annotated[ + sort_direction.SortDirection | None, + Field( + description='The direction actually applied to by_geo ordering. Sellers MUST return this field whenever by_geo is present.' + ), + ] = None + by_device_type: Annotated[ + list[ByDeviceTypeItem] | None, + Field( + description='Delivery by device form factor within this package. Available when the buyer requests device_type breakdown via reporting_dimensions and the seller supports it.' + ), + ] = None + by_device_type_truncated: Annotated[ + StrictBool | None, + Field( + description='Whether by_device_type was truncated. Sellers MUST return this flag whenever by_device_type is present (false means the list is complete).' + ), + ] = None + by_device_type_sorted_by: Annotated[ + sort_metric.SortMetric | None, + Field( + description="The metric actually used to order by_device_type rows. Sellers MUST return this field whenever by_device_type is present. When the seller cannot sort by the requested sort_by metric it falls back to 'spend'; this echo makes the fallback visible instead of silently returning rows the buyer will misread as ordered by the requested metric." + ), + ] = None + by_device_type_sort_direction: Annotated[ + sort_direction.SortDirection | None, + Field( + description='The direction actually applied to by_device_type ordering. Sellers MUST return this field whenever by_device_type is present.' + ), + ] = None + by_device_type_pagination: Annotated[ + pagination_response.PaginationResponse | None, + Field( + description="Cursor to retrieve the remaining by_device_type rows when by_device_type_truncated is true. Sellers MUST return this field whenever by_device_type_truncated is true; omit when false or when by_device_type is absent. Pass the cursor back in the corresponding request's reporting_dimensions.device_type_1.cursor to fetch the next page; other reporting_dimensions.device_type request fields (limit, sort_by, sort_direction) MUST be repeated unchanged across paged requests." + ), + ] = None + by_device_platform: Annotated[ + list[ByDevicePlatformItem] | None, + Field( + description='Delivery by operating system within this package. Available when the buyer requests device_platform breakdown via reporting_dimensions and the seller supports it. Useful for CTV campaigns where tvOS vs Roku OS vs Fire OS matters.' + ), + ] = None + by_device_platform_truncated: Annotated[ + StrictBool | None, + Field( + description='Whether by_device_platform was truncated. Sellers MUST return this flag whenever by_device_platform is present (false means the list is complete).' + ), + ] = None + by_device_platform_sorted_by: Annotated[ + sort_metric.SortMetric | None, + Field( + description="The metric actually used to order by_device_platform rows. Sellers MUST return this field whenever by_device_platform is present. When the seller cannot sort by the requested sort_by metric it falls back to 'spend'; this echo makes the fallback visible instead of silently returning rows the buyer will misread as ordered by the requested metric." + ), + ] = None + by_device_platform_sort_direction: Annotated[ + sort_direction.SortDirection | None, + Field( + description='The direction actually applied to by_device_platform ordering. Sellers MUST return this field whenever by_device_platform is present.' + ), + ] = None + by_device_platform_pagination: Annotated[ + pagination_response.PaginationResponse | None, + Field( + description="Cursor to retrieve the remaining by_device_platform rows when by_device_platform_truncated is true. Sellers MUST return this field whenever by_device_platform_truncated is true; omit when false or when by_device_platform is absent. Pass the cursor back in the corresponding request's reporting_dimensions.device_platform_1.cursor to fetch the next page; other reporting_dimensions.device_platform request fields (limit, sort_by, sort_direction) MUST be repeated unchanged across paged requests." + ), + ] = None + by_audience: Annotated[ + list[ByAudienceItem] | None, + Field( + description="Delivery by audience segment within this package. Available when the buyer requests audience breakdown via reporting_dimensions and the seller supports it. Only 'synced' audiences are directly targetable via the targeting overlay; other sources are informational." + ), + ] = None + by_audience_truncated: Annotated[ + StrictBool | None, + Field( + description='Whether by_audience was truncated. Sellers MUST return this flag whenever by_audience is present (false means the list is complete).' + ), + ] = None + by_audience_sorted_by: Annotated[ + sort_metric.SortMetric | None, + Field( + description="The metric actually used to order by_audience rows. Sellers MUST return this field whenever by_audience is present. When the seller cannot sort by the requested sort_by metric it falls back to 'spend'; this echo makes the fallback visible instead of silently returning rows the buyer will misread as ordered by the requested metric." + ), + ] = None + by_audience_sort_direction: Annotated[ + sort_direction.SortDirection | None, + Field( + description='The direction actually applied to by_audience ordering. Sellers MUST return this field whenever by_audience is present.' + ), + ] = None + by_audience_pagination: Annotated[ + pagination_response.PaginationResponse | None, + Field( + description="Cursor to retrieve the remaining by_audience rows when by_audience_truncated is true. Sellers MUST return this field whenever by_audience_truncated is true; omit when false or when by_audience is absent. Pass the cursor back in the corresponding request's reporting_dimensions.audience.cursor to fetch the next page; other reporting_dimensions.audience request fields (limit, sort_by, sort_direction) MUST be repeated unchanged across paged requests." + ), + ] = None + by_demographic: Annotated[ + list[ByDemographicItem] | None, + Field( + description='Delivery by demographic within this package. Available when the buyer requests demographic breakdown and the product declares supports_demographic_breakdown. A free-form measurement code does not prove alignment with buyer targeting. When age is present it is the authoritative machine-comparable interval; for requested age_ranges, sellers MUST echo the exact requested interval and MUST NOT substitute a wider or narrower native bucket.' + ), + ] = None + by_demographic_truncated: Annotated[ + StrictBool | None, + Field( + description='Whether non-suppressed by_demographic rows were truncated due to the requested limit or a seller-imposed maximum. Sellers MUST return this flag whenever by_demographic is present. False means every non-suppressed row is present; inspect by_demographic_suppressed separately before reconciling rows to package totals.' + ), + ] = None + by_demographic_sorted_by: Annotated[ + sort_metric.SortMetric | None, + Field( + description="The metric actually used to order by_demographic rows. Sellers MUST return this field whenever by_demographic is present. When the seller cannot sort by the requested sort_by metric it falls back to 'spend'; this echo makes the fallback visible instead of silently returning rows the buyer will misread as ordered by the requested metric." + ), + ] = None + by_demographic_sort_direction: Annotated[ + sort_direction.SortDirection | None, + Field( + description='The direction actually applied to by_demographic ordering. Sellers MUST return this field whenever by_demographic is present.' + ), + ] = None + by_demographic_suppressed: Annotated[ + StrictBool | None, + Field( + description='Whether one or more otherwise reportable demographic rows were omitted due to privacy, policy, or measurement thresholds. Sellers MUST return this flag whenever by_demographic is present. False means no rows were threshold-suppressed.' + ), + ] = None + by_placement: Annotated[ + list[placement_delivery_metrics.PlacementDeliveryMetrics] | None, + Field( + description='Delivery by placement within this package. placement_id remains required for 3.1 compatibility. New 3.2 sellers also emit placement_identity, whose discriminator separates publisher-catalog identity from sales-agent-defined inline identity.' + ), + ] = None + by_placement_truncated: Annotated[ + StrictBool | None, + Field( + description='Whether by_placement was truncated. Sellers MUST return this flag whenever by_placement is present (false means the list is complete).' + ), + ] = None + by_placement_sorted_by: Annotated[ + sort_metric.SortMetric | None, + Field( + description="The metric actually used to order by_placement rows. Sellers MUST return this field whenever by_placement is present. When the seller cannot sort by the requested sort_by metric it falls back to 'spend'; this echo makes the fallback visible instead of silently returning rows the buyer will misread as ordered by the requested metric." + ), + ] = None + by_placement_sort_direction: Annotated[ + sort_direction.SortDirection | None, + Field( + description='The direction actually applied to by_placement ordering. Sellers MUST return this field whenever by_placement is present.' + ), + ] = None + by_placement_pagination: Annotated[ + pagination_response.PaginationResponse | None, + Field( + description="Cursor to retrieve the remaining by_placement rows when by_placement_truncated is true. Sellers MUST return this field whenever by_placement_truncated is true; omit when false or when by_placement is absent. Pass the cursor back in the corresponding request's reporting_dimensions.placement.cursor to fetch the next page; other reporting_dimensions.placement request fields (limit, sort_by, sort_direction) MUST be repeated unchanged across paged requests." + ), + ] = None + by_property: Annotated[ + list[property_delivery_metrics.PropertyDeliveryMetrics] | None, + Field( + description='Delivery by publisher property within this package. Each row identifies the actual surface with an operational identifier and adds property_ref when it resolves to a canonical publisher catalog entry. Rows are independent of by_collection.' + ), + ] = None + by_property_truncated: Annotated[ + StrictBool | None, + Field( + description='Whether non-suppressed by_property rows were truncated. Sellers MUST return this flag whenever by_property is present. False means every non-suppressed row is present; inspect by_property_suppressed before reconciling rows to package totals.' + ), + ] = None + by_property_suppressed: Annotated[ + StrictBool | None, + Field( + description='Whether one or more otherwise reportable by_property rows were omitted from this response due to privacy, policy, or measurement thresholds. Sellers MUST return this flag whenever by_property is present. False means no rows were threshold-suppressed. Both suppression and truncation may be true.' + ), + ] = None + by_property_sorted_by: Annotated[ + sort_metric.SortMetric | None, + Field( + description='The metric actually used to order by_property rows. Sellers MUST return this field whenever by_property is present.' + ), + ] = None + by_property_sort_direction: Annotated[ + sort_direction.SortDirection | None, + Field( + description='The direction actually applied to by_property rows. Sellers MUST return this field whenever by_property is present.' + ), + ] = None + by_collection: Annotated[ + list[collection_delivery_metrics.CollectionDeliveryMetrics] | None, + Field( + description='Delivery by publisher-scoped collection within this package. This is a marginal breakdown and does not by itself prove which property carried a collection.' + ), + ] = None + by_collection_truncated: Annotated[ + StrictBool | None, + Field( + description='Whether by_collection was truncated. Sellers MUST return this flag whenever by_collection is present.' + ), + ] = None + by_collection_sorted_by: Annotated[ + sort_metric.SortMetric | None, + Field( + description='The metric actually used to order by_collection rows. Sellers MUST return this field whenever by_collection is present.' + ), + ] = None + by_collection_sort_direction: Annotated[ + sort_direction.SortDirection | None, + Field( + description='The direction actually applied to by_collection rows. Sellers MUST return this field whenever by_collection is present.' + ), + ] = None + by_installment: Annotated[ + list[installment_delivery_metrics.InstallmentDeliveryMetrics] | None, + Field(description='Delivery by canonically identified installment within this package.'), + ] = None + by_installment_truncated: Annotated[ + StrictBool | None, + Field( + description='Whether by_installment was truncated. Sellers MUST return this flag whenever by_installment is present.' + ), + ] = None + by_installment_sorted_by: Annotated[ + sort_metric.SortMetric | None, + Field( + description='The metric actually used to order by_installment rows. Sellers MUST return this field whenever by_installment is present.' + ), + ] = None + by_installment_sort_direction: Annotated[ + sort_direction.SortDirection | None, + Field( + description='The direction actually applied to by_installment rows. Sellers MUST return this field whenever by_installment is present.' + ), + ] = None + by_collection_property: Annotated[ + list[collection_property_delivery_metrics.CollectionPropertyDeliveryMetrics] | None, + Field( + description='Delivery at the collection × property intersection. A row is affirmative delivery evidence that the referenced collection ran on the referenced property; it is not merely a carriage or catalog assertion.' + ), + ] = None + by_collection_property_truncated: Annotated[ + StrictBool | None, + Field( + description='Whether non-suppressed by_collection_property rows were truncated. Sellers MUST return this flag whenever by_collection_property is present.' + ), + ] = None + by_collection_property_suppressed: Annotated[ + StrictBool | None, + Field( + description='Whether one or more otherwise reportable by_collection_property rows were omitted from this response due to privacy, policy, or measurement thresholds. Sellers MUST return this flag whenever by_collection_property is present. False means no rows were threshold-suppressed. Both suppression and truncation may be true.' + ), + ] = None + by_collection_property_sorted_by: Annotated[ + sort_metric.SortMetric | None, + Field( + description='The metric actually used to order by_collection_property rows. Sellers MUST return this field whenever by_collection_property is present.' + ), + ] = None + by_collection_property_sort_direction: Annotated[ + sort_direction.SortDirection | None, + Field( + description='The direction actually applied to by_collection_property rows. Sellers MUST return this field whenever by_collection_property is present.' + ), + ] = None + by_installment_property: Annotated[ + list[installment_property_delivery_metrics.InstallmentPropertyDeliveryMetrics] | None, + Field( + description='Delivery at the installment × property intersection. A row is affirmative delivery evidence that the referenced airing, episode, issue, or programming block ran on the referenced property; it is not inferred from collection carriage or independent marginals.' + ), + ] = None + by_installment_property_truncated: Annotated[ + StrictBool | None, + Field( + description='Whether non-suppressed by_installment_property rows were truncated. Sellers MUST return this flag whenever by_installment_property is present.' + ), + ] = None + by_installment_property_suppressed: Annotated[ + StrictBool | None, + Field( + description='Whether one or more otherwise reportable by_installment_property rows were omitted from this response due to privacy, policy, or measurement thresholds. Sellers MUST return this flag whenever by_installment_property is present. False means no rows were threshold-suppressed. Both suppression and truncation may be true.' + ), + ] = None + by_installment_property_sorted_by: Annotated[ + sort_metric.SortMetric | None, + Field( + description='The metric actually used to order by_installment_property rows. Sellers MUST return this field whenever by_installment_property is present.' + ), + ] = None + by_installment_property_sort_direction: Annotated[ + sort_direction.SortDirection | None, + Field( + description='The direction actually applied to by_installment_property rows. Sellers MUST return this field whenever by_installment_property is present.' + ), + ] = None + by_placement_property: Annotated[ + list[placement_property_delivery_metrics.PlacementPropertyDeliveryMetrics] | None, + Field( + description='Delivery at the placement × property intersection. This proves which actual property carried a placement that may span more than one property.' + ), + ] = None + by_placement_property_truncated: Annotated[ + StrictBool | None, + Field( + description='Whether non-suppressed by_placement_property rows were truncated. Sellers MUST return this flag whenever by_placement_property is present.' + ), + ] = None + by_placement_property_suppressed: Annotated[ + StrictBool | None, + Field( + description='Whether one or more otherwise reportable by_placement_property rows were omitted from this response due to privacy, policy, or measurement thresholds. Sellers MUST return this flag whenever by_placement_property is present. False means no rows were threshold-suppressed. Both suppression and truncation may be true.' + ), + ] = None + by_placement_property_sorted_by: Annotated[ + sort_metric.SortMetric | None, + Field( + description='The metric actually used to order by_placement_property rows. Sellers MUST return this field whenever by_placement_property is present.' + ), + ] = None + by_placement_property_sort_direction: Annotated[ + sort_direction.SortDirection | None, + Field( + description='The direction actually applied to by_placement_property rows. Sellers MUST return this field whenever by_placement_property is present.' + ), + ] = None + by_spot: Annotated[ + list[BySpotItem] | None, + Field( + description='Spot-level as-run airing records for broadcast TV, radio, or other scheduled inventory. Available when the buyer requests spot breakdown and the product declares supports_spot_breakdown. Sellers MUST order rows by aired_at ascending. The same spot_id is reused when a later package measurement_window adds or revises metrics. Network and station are optional so station-direct radio and network-level TV records use the same channel-neutral shape. Sellers SHOULD populate creative_id whenever they can associate a specific airing with a creative, particularly when the package could serve more than one creative during any part of the reporting period, including a mid-period replacement. Sellers that cannot make that association MUST omit creative_id rather than emit a default or placeholder. Buyers MUST NOT aggregate by_spot rows by creative_id and expect the result to equal by_creative impressions for the same creative: the two dimensions are independently produced at different granularities and are subject to different metric-maturation and attribution semantics within the package measurement_window. by_creative is the authoritative creative-performance aggregate; creative_id on a by_spot row identifies which creative aired, not an independent metric roll-up source.' + ), + ] = None + by_spot_truncated: Annotated[ + StrictBool | None, + Field( + description='Whether by_spot is incomplete because of the requested limit or a seller-imposed maximum. Sellers MUST return this flag whenever by_spot is present (false means the as-run log is complete for the requested reporting period).' + ), + ] = None + daily_breakdown: Annotated[ + list[DailyBreakdownItem] | None, + Field( + description='Day-by-day delivery for this package. Only present when include_package_daily_breakdown is true in the request. Enables per-package pacing analysis and line-item monitoring.' + ), + ] = None + spend: Any + + +class MediaBuyDelivery(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + media_buy_id: Annotated[str, Field(description="Seller's media buy identifier")] + currency: Annotated[ + str | None, + Field( + description="ISO 4217 denomination for monetary values in this media-buy delivery row, including totals, package spend, and currency-denominated package rates. Sellers SHOULD populate this field whenever all monetary values in the row share one currency. For AdCP-authored buys it MUST equal the media-buy currency, and every by_package[].currency MUST equal it. For a legacy or externally created mixed-currency buy, omit this field, daily_breakdown, and all monetary or money-derived values from row and window totals; report those values only at package grain with each package's own currency. AdCP does not perform currency conversion.", + pattern='^[A-Z]{3}$', + ), + ] = None + status: Annotated[ + Status, + Field( + description='Current media buy status. Lifecycle states use the same taxonomy as media-buy-status (`pending_creatives`, `pending_start`, `active`, `paused`, `completed`, `rejected`, `canceled`). In webhook context, reporting_delayed indicates data temporarily unavailable. `pending` is accepted as a legacy alias for pending_start.' + ), + ] + expected_availability: Annotated[ + AwareDatetime | None, + Field( + description='When delayed data is expected to be available (only present when status is reporting_delayed)' + ), + ] = None + is_adjusted: Annotated[ + StrictBool | None, + Field( + description='Indicates this delivery contains updated data for a previously reported period. Buyer should replace previous period data with these totals.' + ), + ] = None + is_final: Annotated[ + StrictBool | None, + Field( + description="Whether this row's delivery data is final for the reporting period. The row does not carry its own `measurement_window` — that lives on each `by_package[*]` entry. Reconciliation joins on per-package `measurement_window`; this row-level flag is a convenience roll-up. Sellers MUST NOT emit `is_final: true` at the row level unless every entry in `by_package` has `is_final: true` for the same `measurement_window` as the buy's `measurement_terms.billing_measurement.measurement_window` (or for the row's natural window when no `billing_measurement.measurement_window` is set). On any disagreement between row-level and package-level finality, package-level is authoritative. When true, the seller considers these numbers closed and is willing to invoice on them subject to `measurement_terms.billing_measurement`. When false, numbers may still move as measurement matures (broadcast C3 → C7) or processing completes (IVT scrubbing, dedup). When absent, the seller does not distinguish provisional from final at the row level — consult per-package `is_final`." + ), + ] = None + finalized_at: Annotated[ + AwareDatetime | None, + Field( + description="ISO 8601 timestamp at which this row became final. Present only when `is_final: true`. Anchors the buyer's reconciliation and (when later defined) dispute-window clocks against the buy's `measurement_terms.billing_measurement`. Computed as the latest `finalized_at` across the row's packages for the reconciliation window." + ), + ] = None + pricing_model: Annotated[ + pricing_model_1.PricingModel | None, + Field(description='Pricing model used for this media buy'), + ] = None + pacing_index: Annotated[ + StrictFloat | None, + Field( + description='Aggregate media-buy delivery pace relative to the media-buy pacing plan (1.0 = on track, <1.0 = behind, >1.0 = ahead). This is the authoritative pacing signal for seller-optimized buys; package pacing indexes are subordinate diagnostics.', + ge=0.0, + ), + ] = None + totals: Totals + by_package: Annotated[list[ByPackageItem], Field(description='Metrics broken down by package')] + windows: Annotated[ + list[Window] | None, + Field( + description="Per-window delivery slices over the reporting period at the requested time_granularity. Only present when the request set time_granularity and include_window_breakdown: true. Each slice mirrors what reporting_webhook would have delivered for the same window — buyers who missed webhook fires can reconstruct identical data by reading this array. Slice rows are ordered by window_start ascending; consecutive rows are contiguous (each row's window_end equals the next row's window_start) and partition the requested date range at the chosen granularity. For a legacy or external mixed-currency media buy, monetary and money-derived values MUST be omitted from each window totals object and reported only in currency-qualified windows[].by_package rows. Sellers MUST exclude this field when time_granularity is omitted; when set, sellers MUST honor pulls at any granularity in reporting_capabilities.windowed_pull_granularities (otherwise return UNSUPPORTED_GRANULARITY). See snapshot-and-log Rule 4 for the two-paths-parity contract this surface anchors." + ), + ] = None + daily_breakdown: Annotated[ + list[DailyBreakdownItem1] | None, + Field( + description="Day-by-day delivery for a media-buy row with one currency. Sellers MUST omit this aggregate breakdown for a legacy or externally created mixed-currency buy because these rows have no package currency field. Sellers MUST also omit this aggregate breakdown when the media buy's packages span more than one reporting timezone; package-level daily_breakdown remains, each in its own product's reporting timezone." + ), + ] = None + -# Explicit exports -__all__ = [ - "AggregatedTotals", - "ByAudienceItem", - "ByDemographicItem", - "ByDevicePlatformItem", - "ByDeviceTypeItem", - "ByFormatItem", - "ByPackageItem", - "ByPackageItem1", - "BySpotItem", - "DailyBreakdownItem", - "DailyBreakdownItem1", - "GetMediaBuyDeliveryResponse", - "MediaBuyDelivery", - "NotificationType", - "ReportingPeriod", - "ReportingRevisionBinding", - "Status", - "Totals", - "Window", -] +class GetMediaBuyDeliveryResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + notification_type: Annotated[ + NotificationType | None, + Field( + description='Type of webhook notification (only present in webhook deliveries): scheduled = regular periodic update, final = campaign completed, delayed = data not yet available, adjusted = resending period with corrected data (same window), window_update = resending period with a wider measurement window (e.g., C3 superseding live, C7 superseding C3)' + ), + ] = None + partial_data: Annotated[ + StrictBool | None, + Field( + description='Indicates if any media buys in this webhook have missing/delayed data (only present in webhook deliveries)' + ), + ] = None + unavailable_count: Annotated[ + SchemaInt | None, + Field( + description='Number of media buys with reporting_delayed or failed status (only present in webhook deliveries when partial_data is true)', + ge=0, + ), + ] = None + sequence_number: Annotated[ + SchemaInt | None, + Field( + description='Sequential notification number (only present in webhook deliveries, starts at 1)', + ge=1, + ), + ] = None + next_expected_at: Annotated[ + AwareDatetime | None, + Field( + description="ISO 8601 timestamp for next expected notification (only present in webhook deliveries when notification_type is not 'final')" + ), + ] = None + reporting_period: Annotated[ + ReportingPeriod, + Field( + description="Half-open period for the report: start is inclusive and end is exclusive. Both are instants. For a date-bounded request they are the instants at which start_date and end_date begin in the reporting timezone (the in-scope products' reporting_capabilities.timezone, echoed in timezone). They fall on UTC midnight only when that timezone is UTC. An exact reporting_revision_id read returns the revision's period instead, whose source_timezone names its calendar." + ), + ] + reporting_revision_binding: Annotated[ + ReportingRevisionBinding | None, + Field( + description='Present only for a reporting_revision_id selector. Binds the complete ordered reporting_rows sequence obtained by concatenating every cursor page to one immutable Reliable Reporting revision; consumers verify content_sha256 over RFC 8785 JCS of {reporting_revision_id,row_count,control_totals,reporting_rows} before using it as revision evidence.' + ), + ] = None + reporting_revision: Annotated[ + reporting_revision_1.ReportingRevision | None, + Field( + description='Full immutable revision metadata, identical to the get_reporting_status revision record.' + ), + ] = None + reporting_rows: Annotated[ + list[dict[str, Any]] | None, + Field( + description="One page of authoritative logical rows for an exact reporting_revision_id read. Validate every row against the revision's digest-pinned schema; concatenate pages in cursor order before verifying the binding digest." + ), + ] = None + pagination: Annotated[ + pagination_response.PaginationResponse | None, + Field( + description='Exact revision pages are frozen for the cursor walk. total_count is mandatory for reporting_revision_id and equals reporting_revision_1.row_count and reporting_revision_binding.row_count; it counts the complete ordered row sequence, not this page.' + ), + ] = None + currency: Annotated[ + str | None, + Field( + deprecated=True, + description='Deprecated in AdCP 3.2 and removed in AdCP 4.0. Optional legacy response-wide ISO 4217 currency code. It may be used only when every monetary value in the response has that denomination. A delivery response can contain media buys with different currencies, so buyers MUST NOT interpret this field as an aggregation currency or evidence of currency conversion. Prefer media_buy_deliveries[].currency when present and package-level currency otherwise.', + pattern='^[A-Z]{3}$', + ), + ] = None + attribution_window: Annotated[ + attribution_window_1.AttributionWindow | None, + Field( + description='Attribution methodology and lookback windows used for conversion metrics in this response. All media buys from a single seller share the same attribution methodology. Enables cross-platform comparison (e.g., Amazon 14-day click vs. Criteo 30-day click).' + ), + ] = None + aggregated_totals: Annotated[ + AggregatedTotals | None, + Field( + deprecated=True, + description='Deprecated in AdCP 3.2 and removed in AdCP 4.0. Legacy combined metrics across all returned media buys. When this field is present, the deprecated response-wide currency is required and denominates its spend. Cross-buy totals are unsafe when currencies, metric qualifiers, measurement windows, finality, or deduplication semantics differ. Sellers SHOULD omit this field; buyers SHOULD aggregate media_buy_deliveries[] only when the relevant row semantics are compatible.', + ), + ] = None + media_buy_deliveries: Annotated[ + Sequence[MediaBuyDelivery], + Field( + description='Array of delivery data for media buys. When used in webhook notifications, may contain multiple media buys aggregated by publisher. When used in get_media_buy_delivery API responses, typically contains requested media buys.' + ), + ] + errors: Annotated[ + list[error.Error] | None, + Field( + description='Task-specific errors and warnings (e.g., missing delivery data, reporting platform issues)' + ), + ] = None + sandbox: Annotated[ + StrictBool | None, + Field(description='When true, this response contains simulated data from sandbox mode.'), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/get_media_buys_request.py b/src/adcp/types/domains/media_buy/get_media_buys_request.py index fcd499167..4a1d6c6e9 100644 --- a/src/adcp/types/domains/media_buy/get_media_buys_request.py +++ b/src/adcp/types/domains/media_buy/get_media_buys_request.py @@ -1,21 +1,96 @@ -"""Types declared by the AdCP ``media_buy/get_media_buys_request`` schema. +# generated by datamodel-codegen: +# filename: media_buy/get_media_buys_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.get_media_buys_request import +from adcp.types.base import AdcpRequest, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field, RootModel, StrictBool + +from ..core import account_ref +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core import pagination_request +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import indicator_type, media_buy_status + + +class StatusFilter(RootModel[list[media_buy_status.MediaBuyStatus]]): + root: Annotated[ + list[media_buy_status.MediaBuyStatus], + Field( + description='Filter by status. Can be a single status or array of statuses. Defaults to ["active"] when media_buy_ids is omitted. When media_buy_ids is provided, no implicit status filter is applied.', + min_length=1, + ), + ] -from adcp.types.generated_poc.media_buy.get_media_buys_request import ( - GetMediaBuysRequest, - StatusFilter, -) -# Explicit exports -__all__ = ["GetMediaBuysRequest", "StatusFilter"] +class GetMediaBuysRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + account: Annotated[ + account_ref.AccountReference | None, + Field( + description='Account to retrieve media buys for. When omitted, returns data across all accessible accounts.' + ), + ] = None + media_buy_ids: Annotated[ + list[str] | None, + Field( + description='Array of media buy IDs to retrieve. When omitted, returns a paginated set of accessible media buys matching status_filter.', + min_length=1, + ), + ] = None + status_filter: Annotated[ + media_buy_status.MediaBuyStatus | StatusFilter | None, + Field( + description='Filter by status. Can be a single status or array of statuses. Defaults to ["active"] when media_buy_ids is omitted. When media_buy_ids is provided, no implicit status filter is applied.' + ), + ] = None + indicator_types: Annotated[ + list[indicator_type.IndicatorType] | None, + Field( + description='Return media buys with at least one matching current indicator on the media buy, a package, or a package–creative assignment. Values within this field use OR logic; this field composes with status_filter using AND logic. Buyers MUST NOT send this filter unless the seller advertises every requested type in get_adcp_capabilities.media_buy.supported_indicator_types. A seller MAY reject a request that violates this precondition with UNSUPPORTED_FEATURE rather than silently returning an unfiltered superset.', + min_length=1, + ), + ] = None + include_snapshot: Annotated[ + StrictBool | None, + Field( + description='When true, include a near-real-time delivery snapshot for each package. Snapshots reflect the latest available entity-level stats from the platform (e.g., updated every ~15 minutes on GAM, ~1 hour on batch-only platforms). The staleness_seconds field on each snapshot indicates data freshness. If a snapshot cannot be returned, package.snapshot_unavailable_reason explains why. Defaults to false.' + ), + ] = False + include_history: Annotated[ + SchemaInt | None, + Field( + description='When present, include the last N revision history entries for each media buy (returns min(N, available entries)). Each entry contains revision number, timestamp, actor, and a summary of what changed. Omit or set to 0 to exclude history (default). Recommended: 5-10 for monitoring, 50+ for audit.', + ge=0, + le=1000, + ), + ] = 0 + include_webhook_activity: Annotated[ + StrictBool | None, + Field( + description="When true, each returned media buy includes a `webhook_activity` array describing recent delivery-report webhook fires for the calling principal. Used by buyer agents to verify whether a publisher actually fired against the buyer's registered endpoint and what the endpoint returned — closes the operator-ticket loop for webhook debugging. Scoped to the calling principal: a buyer sees only fires targeting its own endpoint, even when multiple principals share visibility into the same media buy. Defaults to false. See `webhook_activity_limit` for the per-buy cap." + ), + ] = False + webhook_activity_limit: Annotated[ + SchemaInt | None, + Field( + description="Maximum number of webhook delivery records to return per media buy, ordered most-recent first. Ignored when `include_webhook_activity` is false. Sellers that surface webhook activity MUST retain records for at least 30 days from each record's `completed_at` (see `webhook_activity` description in the response schema for the `pending`-status carve-out); sellers unable to honor that floor MUST omit the field entirely rather than truncate. When a buy has more historical fires than the limit, only the most recent are returned — there is no cursor for older fires; this surface is a debug aid, not a full audit log.", + ge=1, + le=200, + ), + ] = 50 + pagination: Annotated[ + pagination_request.PaginationRequest | None, + Field( + description='Cursor-based pagination controls. Strongly recommended when querying broad scopes (for example, all active media buys in an account).' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/get_media_buys_response.py b/src/adcp/types/domains/media_buy/get_media_buys_response.py index fb567998d..f502e3021 100644 --- a/src/adcp/types/domains/media_buy/get_media_buys_response.py +++ b/src/adcp/types/domains/media_buy/get_media_buys_response.py @@ -1,52 +1,809 @@ -"""Types declared by the AdCP ``media_buy/get_media_buys_response`` schema. +# generated by datamodel-codegen: +# filename: media_buy/get_media_buys_response.json +# timestamp: 2026-10-05T13:49:50+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.get_media_buys_response import +from adcp.types.base import AdcpResponse, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from collections.abc import Sequence +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any, Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat -from adcp.types.generated_poc.media_buy.get_media_buys_response import ( - AcceptedProposal, - Cancellation, - Cancellation1, - CreativeApproval, - GetMediaBuysResponse, - HistoryItem, - Indicator, - Indicator1, - Indicator2, - IndicatorTypesEvaluatedEnum, - IndicatorTypesEvaluatedEnum1, - IndicatorTypesEvaluatedEnum2, - MediaBuy, - Package, - ProposalStatus, - Snapshot, +from ..core import account as account_1 +from ..core import bidding_policy +from ..core import budget_allocation as budget_allocation_1 +from ..core import business_entity, canonical_media_buy_action +from ..core import context as context_1 +from ..core import creative_approval_scope, error +from ..core import ext as ext_1 +from ..core import ( + format_id, + format_option_ref, + impairment, + indicator_scope, + media_buy_available_action, + media_buy_frequency_cap, + optimization_goal, + package_format_snapshot, + package_targeting_resolution, + pagination_response, + targeting, + webhook_activity_record, ) +from ..core.canonical_proposal import CanonicalProposal +from ..core.indicator_bearing import IndicatorBearingResourceState +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import canceled_by as canceled_by_1 +from ..enums import creative_approval_status +from ..enums import delivery_status as delivery_status_1 +from ..enums import media_buy_health, media_buy_status, media_buy_valid_action +from ..enums import pacing as pacing_1 +from ..enums import snapshot_unavailable_reason as snapshot_unavailable_reason_1 + + +class IndicatorTypesEvaluatedEnum(StrEnum): + creative_fatigue = 'creative_fatigue' + creative_quality_opportunity = 'creative_quality_opportunity' + creative_diversity_low = 'creative_diversity_low' + audience_saturation = 'audience_saturation' + inventory_shortfall_forecast = 'inventory_shortfall_forecast' + pacing_risk = 'pacing_risk' + budget_constrained = 'budget_constrained' + budget_constrained_1 = 'budget_constrained' + + +class ProposalStatus(StrEnum): + draft = 'draft' + committed = 'committed' + accepted = 'accepted' + + +class Cancellation(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + canceled_at: Annotated[ + AwareDatetime, Field(description='ISO 8601 timestamp when this media buy was canceled.') + ] + canceled_by: Annotated[ + canceled_by_1.CanceledBy, Field(description='Which party initiated the cancellation.') + ] + reason: Annotated[ + str | None, Field(description='Reason the media buy was canceled.', max_length=500) + ] = None + + +class HistoryItem(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + revision: Annotated[ + SchemaInt, Field(description='Revision number after this change was applied.', ge=1) + ] + timestamp: Annotated[AwareDatetime, Field(description='When this change occurred.')] + actor: Annotated[ + str | None, + Field( + description='Identity of who made the change — derived from authentication context, not caller-provided. Format is seller-defined (e.g., agent URL, user email, API key label).' + ), + ] = None + action: Annotated[ + str, + Field( + description='What happened. Standard actions: created, activated, paused, resumed, canceled, rejected, completed, updated_budget, updated_dates, updated_packages, package_canceled, package_paused, package_resumed. Sellers MAY use additional platform-specific actions (e.g., creative_approved, targeting_updated) — use ext on the history entry for structured metadata about custom actions.' + ), + ] + summary: Annotated[ + str | None, + Field( + description="Human-readable summary of the change (e.g., 'Budget increased from $5,000 to $7,500 on pkg_abc').", + max_length=500, + ), + ] = None + package_id: Annotated[ + str | None, + Field(description='Package affected, when the change targeted a specific package.'), + ] = None + ext: ext_1.ExtensionObject | None = None + + +class IndicatorTypesEvaluatedEnum1(StrEnum): + creative_fatigue = 'creative_fatigue' + creative_quality_opportunity = 'creative_quality_opportunity' + creative_diversity_low = 'creative_diversity_low' + audience_saturation = 'audience_saturation' + inventory_shortfall_forecast = 'inventory_shortfall_forecast' + pacing_risk = 'pacing_risk' + budget_constrained = 'budget_constrained' + creative_diversity_low_1 = 'creative_diversity_low' + audience_saturation_1 = 'audience_saturation' + inventory_shortfall_forecast_1 = 'inventory_shortfall_forecast' + pacing_risk_1 = 'pacing_risk' + budget_constrained_1 = 'budget_constrained' + + +class Cancellation1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + canceled_at: Annotated[ + AwareDatetime, Field(description='ISO 8601 timestamp when this package was canceled.') + ] + canceled_by: Annotated[ + canceled_by_1.CanceledBy, + Field(description='Which party initiated the package cancellation.'), + ] + reason: Annotated[ + str | None, Field(description='Reason the package was canceled.', max_length=500) + ] = None + + +class IndicatorTypesEvaluatedEnum2(StrEnum): + creative_fatigue = 'creative_fatigue' + creative_quality_opportunity = 'creative_quality_opportunity' + creative_diversity_low = 'creative_diversity_low' + audience_saturation = 'audience_saturation' + inventory_shortfall_forecast = 'inventory_shortfall_forecast' + pacing_risk = 'pacing_risk' + budget_constrained = 'budget_constrained' + creative_fatigue_1 = 'creative_fatigue' + creative_quality_opportunity_1 = 'creative_quality_opportunity' + + +class Snapshot(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + as_of: Annotated[ + AwareDatetime, + Field(description='ISO 8601 timestamp when this snapshot was captured by the platform'), + ] + staleness_seconds: Annotated[ + SchemaInt, + Field( + description='Maximum age of this data in seconds. For example, 900 means the data may be up to 15 minutes old. Use this to interpret zero delivery: a value of 900 means zero impressions is likely real; a value of 14400 means reporting may still be catching up.', + ge=0, + ), + ] + impressions: Annotated[ + StrictFloat, Field(description='Total impressions delivered since package start', ge=0.0) + ] + spend: Annotated[ + StrictFloat, + Field( + description='Total spend since package start, denominated in snapshot.currency when present, otherwise package.currency or media_buy.currency', + ge=0.0, + ), + ] + currency: Annotated[ + str | None, + Field( + description='ISO 4217 currency code for spend in this snapshot. Optional when unchanged from package.currency or media_buy.currency.', + pattern='^[A-Z]{3}$', + ), + ] = None + clicks: Annotated[ + StrictFloat | None, + Field(description='Total clicks since package start (when available)', ge=0.0), + ] = None + pacing_index: Annotated[ + StrictFloat | None, + Field( + description='Current delivery pace relative to expected (1.0 = on track, <1.0 = behind, >1.0 = ahead). Absent when pacing cannot be determined.', + ge=0.0, + ), + ] = None + delivery_status: Annotated[ + delivery_status_1.DeliveryStatus | None, + Field( + description="Operational delivery state of this package. 'not_delivering' means the package is within its scheduled flight but has delivered zero impressions for at least one full staleness cycle — the signal for automated price adjustments or buyer alerts. Implementers must not return 'not_delivering' until at least staleness_seconds have elapsed since package activation." + ), + ] = None + ext: Annotated[ + ext_1.ExtensionObject | None, + Field(description='Optional extension object for seller-specific snapshot fields.'), + ] = None + + +class Indicator(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + type: Annotated[ + IndicatorTypesEvaluatedEnum, + Field( + description="A seller-asserted material risk or optimization opportunity that currently warrants buyer attention on an AdCP resource relationship. AdCP standardizes the broad meaning of each value for the protocol release; it does not standardize or version the seller's detection methodology or prescribe a universal remedy.", + title='Indicator Type', + ), + ] + detected_at: Annotated[ + AwareDatetime | None, + Field( + description='When the seller first detected the current uninterrupted occurrence of this indicator. Keep this value stable while the condition remains present. If the condition clears and is later detected again, use the new detection time. Optional because some upstream platforms expose current assessments without an original detection timestamp.' + ), + ] = None + scope: Annotated[ + list[indicator_scope.IndicatorScope] | None, + Field( + description='Optional narrower publisher or placement scope within the enclosing media buy, package, or package–creative assignment. Omit only when the seller evaluated and asserts the indicator across the whole enclosing resource/relationship. When partial indicators_evaluated_scope is declared, every returned indicator MUST include scope and every entry MUST fall within that coverage. This is scope, not source: the responding seller remains the source.', + min_length=1, + ), + ] = None + ext: Annotated[ + ext_1.ExtensionObject | None, + Field( + description='Seller- or provider-specific detail such as scores, thresholds, evaluation windows, methodology identifiers, or upstream attribution. Core consumers must not need ext to understand the broad meaning of type.' + ), + ] = None + + +class Indicator1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + type: Annotated[ + IndicatorTypesEvaluatedEnum1, + Field( + description="A seller-asserted material risk or optimization opportunity that currently warrants buyer attention on an AdCP resource relationship. AdCP standardizes the broad meaning of each value for the protocol release; it does not standardize or version the seller's detection methodology or prescribe a universal remedy.", + title='Indicator Type', + ), + ] + detected_at: Annotated[ + AwareDatetime | None, + Field( + description='When the seller first detected the current uninterrupted occurrence of this indicator. Keep this value stable while the condition remains present. If the condition clears and is later detected again, use the new detection time. Optional because some upstream platforms expose current assessments without an original detection timestamp.' + ), + ] = None + scope: Annotated[ + list[indicator_scope.IndicatorScope] | None, + Field( + description='Optional narrower publisher or placement scope within the enclosing media buy, package, or package–creative assignment. Omit only when the seller evaluated and asserts the indicator across the whole enclosing resource/relationship. When partial indicators_evaluated_scope is declared, every returned indicator MUST include scope and every entry MUST fall within that coverage. This is scope, not source: the responding seller remains the source.', + min_length=1, + ), + ] = None + ext: Annotated[ + ext_1.ExtensionObject | None, + Field( + description='Seller- or provider-specific detail such as scores, thresholds, evaluation windows, methodology identifiers, or upstream attribution. Core consumers must not need ext to understand the broad meaning of type.' + ), + ] = None + + +class Indicator2(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + type: Annotated[ + IndicatorTypesEvaluatedEnum2, + Field( + description="A seller-asserted material risk or optimization opportunity that currently warrants buyer attention on an AdCP resource relationship. AdCP standardizes the broad meaning of each value for the protocol release; it does not standardize or version the seller's detection methodology or prescribe a universal remedy.", + title='Indicator Type', + ), + ] + detected_at: Annotated[ + AwareDatetime | None, + Field( + description='When the seller first detected the current uninterrupted occurrence of this indicator. Keep this value stable while the condition remains present. If the condition clears and is later detected again, use the new detection time. Optional because some upstream platforms expose current assessments without an original detection timestamp.' + ), + ] = None + scope: Annotated[ + list[indicator_scope.IndicatorScope] | None, + Field( + description='Optional narrower publisher or placement scope within the enclosing media buy, package, or package–creative assignment. Omit only when the seller evaluated and asserts the indicator across the whole enclosing resource/relationship. When partial indicators_evaluated_scope is declared, every returned indicator MUST include scope and every entry MUST fall within that coverage. This is scope, not source: the responding seller remains the source.', + min_length=1, + ), + ] = None + ext: Annotated[ + ext_1.ExtensionObject | None, + Field( + description='Seller- or provider-specific detail such as scores, thresholds, evaluation windows, methodology identifiers, or upstream attribution. Core consumers must not need ext to understand the broad meaning of type.' + ), + ] = None + + +class CreativeApproval(IndicatorBearingResourceState): + model_config = ConfigDict( + extra='allow', + ) + indicator_types_evaluated: Annotated[ + list[IndicatorTypesEvaluatedEnum2] | None, + Field( + description='Indicator types covered by this snapshot. Required whenever indicators is present. Types omitted from this list remain unknown even when indicators is empty. Every returned indicator.type MUST appear in this list.', + min_length=1, + ), + ] = None + indicators: Annotated[ + list[Indicator2] | None, + Field( + description='Current seller assertions for the indicator types and publisher/placement coverage named by the sibling evaluation fields. Omitted means unknown or not evaluated. A present empty array means evaluated with no current assertion for indicator_types_evaluated in the evaluated scope.' + ), + ] = None + creative_id: Annotated[str, Field(description='Creative identifier')] + approval_status: creative_approval_status.CreativeApprovalStatus + rejection_reason: Annotated[ + str | None, + Field( + description="Human-readable explanation of why the creative was rejected. Present only when approval_status is 'rejected'." + ), + ] = None + approval_scopes: Annotated[ + list[creative_approval_scope.ScopedCreativeApproval] | None, + Field( + description='Complete, disjoint publisher/placement approval partition when approval_status is partially_approved. A normalized scope appears once. For one publisher, use either one publisher-wide row or placement-specific rows, never both. Omit when one approval_status applies uniformly to the whole assignment. The same scoped outcomes are mirrored on list_creatives.', + min_length=2, + ), + ] = None + indicators_as_of: Annotated[ + AwareDatetime | None, + Field( + description='When the seller last completed the evaluation represented by indicators for this relationship. Required whenever indicators is present, including an empty array.' + ), + ] = None + indicators_evaluated_scope: Annotated[ + list[indicator_scope.IndicatorScope] | None, + Field( + description='Optional publisher or placement scopes covered by this evaluation. Omit when indicators covers the whole package–creative assignment. When present, scopes not listed remain unknown; every returned indicator.scope entry MUST be contained by this set.', + min_length=1, + ), + ] = None + + +class AcceptedProposal(CanonicalProposal): + proposal_status: Annotated[ + Literal['accepted'], + Field( + description='draft is indicative and unreserved; committed has firm terms with inventory reserved until expires_at; accepted is the historical snapshot attached to a MediaBuy.', + title='Proposal Status', + ), + ] = 'accepted' + media_buy_id: Annotated[str, Field(min_length=1)] + accepted_at: Any + commercial_terms: Any + terms_digest: Any + + +class Package(IndicatorBearingResourceState): + model_config = ConfigDict( + extra='allow', + ) + indicator_types_evaluated: Annotated[ + list[IndicatorTypesEvaluatedEnum1] | None, + Field( + description='Indicator types covered by this snapshot. Required whenever indicators is present. Types omitted from this list remain unknown even when indicators is empty. Every returned indicator.type MUST appear in this list.', + min_length=1, + ), + ] = None + indicators: Annotated[ + list[Indicator1] | None, + Field( + description='Current seller assertions for the indicator types and publisher/placement coverage named by the sibling evaluation fields. Omitted means unknown or not evaluated. A present empty array means evaluated with no current assertion for indicator_types_evaluated in the evaluated scope.' + ), + ] = None + package_id: Annotated[str, Field(description="Seller's package identifier")] + product_id: Annotated[ + str | None, + Field( + description="Product identifier this package is purchased from. For packages created from an explicit create_media_buy package request, sellers MUST echo the request package's product_id on every response package object that represents that requested package." + ), + ] = None + budget: Annotated[ + StrictFloat | None, + Field( + description='Hard lifetime package spend cap denominated in media_buy.currency. In seller-optimized mode this is not a current allocation.', + ge=0.0, + ), + ] = None + min_spend_target: Annotated[ + StrictFloat | None, + Field( + description='Accepted soft lifetime spend target for this package under seller-optimized allocation.', + ge=0.0, + ), + ] = None + daily_budget_cap: Annotated[ + StrictFloat | None, + Field( + description='Current hard package spend ceiling per shared media-buy cap day, denominated in media_buy.currency. It is a subordinate ceiling, not a reserved allocation; media_buy.budget_cap_timezone defines the day boundary.', + ge=0.0, + ), + ] = None + currency: Annotated[ + str | None, + Field( + description='Legacy/readback package denomination for buys created outside the canonical 3.2 path. For AdCP-authored media buys this MUST equal media_buy.currency; canonical package budget and BiddingPolicy values always use media_buy.currency. Snapshot currency may still identify externally reported spend denomination.', + pattern='^[A-Z]{3}$', + ), + ] = None + bid_price: Annotated[ + StrictFloat | None, + Field( + deprecated=True, + description='DEPRECATED legacy bid representation. 3.2 sellers SHOULD normalize and echo package.bidding instead.', + ge=0.0, + ), + ] = None + bidding: Annotated[ + bidding_policy.BiddingPolicy | None, + Field( + description='Package-authored bidding override. `{automatic:true}` explicitly overrides media_buy.bidding with provider automatic delivery. Omitted when this package inherits; sellers MUST NOT copy an inherited block here. Monetary fields use media_buy.currency.' + ), + ] = None + optimization_goals: Annotated[ + list[optimization_goal.OptimizationGoal] | None, + Field( + description='Current package objective functions. Currency-bearing execution controls are returned separately in package.bidding or inherited from media_buy.bidding.', + min_length=1, + ), + ] = None + format_ids: Annotated[ + list[format_id.FormatReferenceStructuredObject] | None, + Field( + deprecated=True, + description='Deprecated in AdCP 3.2; removed in AdCP 4.0. Legacy named-format IDs supplied for this package on create_media_buy. Sellers SHOULD echo this field whenever the request included it, including dual-emission cases where another selector won precedence.', + min_length=1, + ), + ] = None + format_option_refs: Annotated[ + list[format_option_ref.FormatOptionReference] | None, + Field( + description='Structured 3.1+ format option references supplied for this package on create_media_buy. Sellers SHOULD echo this field whenever the request included it.', + min_length=1, + ), + ] = None + format_kind: Annotated[ + str | None, + Field( + description='Direct canonical selector supplied for this package on create_media_buy. Sellers SHOULD echo this field whenever the request included it, including informational-echo cases where another selector won precedence.' + ), + ] = None + params: Annotated[ + dict[str, Any] | None, + Field( + description='Parameters for the direct canonical selector in `format_kind`, echoed from the create_media_buy request whenever the request included it. Requires `format_kind`.' + ), + ] = None + impressions: Annotated[ + StrictFloat | None, + Field(description='Goal impression count for impression-based packages', ge=0.0), + ] = None + pacing: Annotated[ + pacing_1.Pacing | None, + Field( + description='Package-level pacing preference. Under seller-optimized allocation this is subordinate to the media-buy aggregate pacing.' + ), + ] = None + targeting_overlay: Annotated[ + targeting.TargetingOverlay | None, + Field( + description='Complete effective targeting applied to this package, including configured-product targeting and the most recent package-specific overlay. Sellers SHOULD echo persisted targeting so buyers can verify stored state without replaying requests. Sellers MUST echo geo_places and geo_places_exclude whenever either was persisted, including the exact applied system_version and normalized values, so buyers can audit catalog-backed targeting. Sellers using placement, property-list, or collection-list targeting MUST include the committed inventory selection here. placement_selection mode default SHOULD resolve to mode selected with committed refs when enumerable; collection_selection follows the same rule, materializing the committed selectors even when the selection was produced through collection_list references.' + ), + ] = None + targeting_resolution: Annotated[ + package_targeting_resolution.PackageTargetingResolution | None, + Field( + description='Execution details for accepted package targeting. Sellers MUST include targeting_resolution.demographics whenever demographic targeting was requested or applied; its applied predicate and execution fields report the effective booked state.' + ), + ] = None + start_time: Annotated[ + AwareDatetime | None, + Field( + description='ISO 8601 flight start time for this package. Use to determine whether the package is within its scheduled flight before interpreting delivery status.' + ), + ] = None + end_time: Annotated[ + AwareDatetime | None, Field(description='ISO 8601 flight end time for this package') + ] = None + paused: Annotated[ + StrictBool | None, + Field(description='Whether this package is currently paused by the buyer'), + ] = None + canceled: Annotated[ + StrictBool | None, + Field( + description='Whether this package has been canceled. Canceled packages stop delivery and cannot be reactivated.' + ), + ] = None + cancellation: Annotated[ + Cancellation1 | None, + Field(description='Cancellation metadata. Present only when canceled is true.'), + ] = None + creative_deadline: Annotated[ + AwareDatetime | None, + Field( + description="ISO 8601 timestamp for creative upload or change deadline for this package. After this deadline, creative changes are rejected. When absent, the media buy's creative_deadline applies." + ), + ] = None + context: Annotated[ + context_1.ContextObject | None, + Field( + description='Opaque package-level correlation data echoed unchanged from the create_media_buy package request. Sellers MUST include persisted package context on read surfaces when the package was created through AdCP with context, so buyers can reconcile seller-assigned package_id values with their own line items; this is the legacy-safe fallback when an older seller did not echo product_id on the create response. Sellers MAY omit context for packages created outside AdCP or created without context. Sellers MUST NOT parse this object for business logic.' + ), + ] = None + creative_approvals: Annotated[ + list[CreativeApproval] | None, + Field( + description='Approval status for each creative assigned to this package. Absent when no creatives have been assigned.' + ), + ] = None + formats_to_provide: Annotated[ + list[package_format_snapshot.PackageFormatSnapshot] | None, + Field( + description='The immutable PackageFormatSnapshot checklist established for this package at booking time. Contract-bearing snapshots remain present after creative coverage is complete so readback, assignment, and serving never fall back to a mutable live Product declaration.', + min_length=1, + ), + ] = None + formats_pending: Annotated[ + list[package_format_snapshot.PackageFormatSnapshot] | None, + Field( + description='PackageFormatSnapshot entries from formats_to_provide that do not yet have creative coverage. Each entry MUST be canonically equal to the corresponding checklist snapshot and, when product_snapshot_digest is present, carry the identical digest. An empty emitted array means all requirements are covered; absence means readiness was not reported.' + ), + ] = None + format_ids_to_provide: Annotated[ + list[format_id.FormatReferenceStructuredObject] | None, + Field( + deprecated=True, + description='**DEPRECATED in 3.2.** Legacy named-format projection of formats_to_provide retained for older 3.x peers. New sellers emit canonical formats_to_provide declarations.', + ), + ] = None + format_ids_pending: Annotated[ + list[format_id.FormatReferenceStructuredObject] | None, + Field( + deprecated=True, + description='**DEPRECATED in 3.2.** Legacy named-format projection of formats_pending retained for older 3.x peers. New sellers emit canonical formats_pending declarations. An empty emitted array means every projected requirement is covered. Absence means legacy readiness was not reported and MUST NOT be interpreted as full coverage.', + ), + ] = None + snapshot_unavailable_reason: Annotated[ + snapshot_unavailable_reason_1.SnapshotUnavailableReason | None, + Field( + description='Machine-readable reason the snapshot is omitted. Present only when include_snapshot was true and snapshot is unavailable for this package.' + ), + ] = None + snapshot: Annotated[ + Snapshot | None, + Field( + description='Near-real-time delivery snapshot for this package. Only present when include_snapshot was true in the request. Represents the latest available entity-level stats from the platform — not billing-grade data.' + ), + ] = None + ext: ext_1.ExtensionObject | None = None + + +class MediaBuy(IndicatorBearingResourceState): + model_config = ConfigDict( + extra='allow', + ) + indicator_types_evaluated: Annotated[ + list[IndicatorTypesEvaluatedEnum] | None, + Field( + description='Indicator types covered by this snapshot. Required whenever indicators is present. Types omitted from this list remain unknown even when indicators is empty. Every returned indicator.type MUST appear in this list.', + min_length=1, + ), + ] = None + indicators: Annotated[ + list[Indicator] | None, + Field( + description='Current seller assertions for the indicator types and publisher/placement coverage named by the sibling evaluation fields. Omitted means unknown or not evaluated. A present empty array means evaluated with no current assertion for indicator_types_evaluated in the evaluated scope.' + ), + ] = None + media_buy_id: Annotated[str, Field(description="Seller's unique identifier for the media buy")] + name: Annotated[ + str | None, + Field( + description='Persisted human-readable name for this media buy, shared by buyer and seller for trafficking UI display and operational communication. Sellers MUST include name when the media buy was created through AdCP with name. Sellers MAY omit it for media buys created outside AdCP or created without name. This display label is not an identifier or financial reference.', + max_length=255, + min_length=1, + pattern='\\S', + ), + ] = None + accepted_proposal_id: Annotated[ + str | None, + Field( + description='Current accepted commercial snapshot for compact-lifecycle refinement. Updated atomically when an amendment or negotiated cancellation is accepted.', + max_length=255, + min_length=1, + ), + ] = None + accepted_proposal_terms_digest: Annotated[ + str | None, + Field( + description='Digest of the current accepted proposal commercial_terms.', + pattern='^sha256:[A-Za-z0-9_-]{43}$', + ), + ] = None + accepted_proposal: Annotated[ + AcceptedProposal | None, + Field( + description='Current accepted compact proposal, including the complete digested commercial envelope. Required whenever accepted_proposal_id is present so restarted SDKs can recover control-versus-refinement routing without reconstructing historical offers.' + ), + ] = None + account: Annotated[ + account_1.Account | None, Field(description='Account billed for this media buy') + ] = None + invoice_recipient: Annotated[ + business_entity.BusinessEntity | None, + Field( + description='Per-buy invoice recipient when provided at creation. Confirms the seller accepted the billing override. Bank details are omitted (write-only).' + ), + ] = None + status: media_buy_status.MediaBuyStatus + status_as_of: Annotated[ + AwareDatetime | None, + Field( + description='ISO 8601 timestamp indicating when the seller last refreshed the returned media-buy-level `status` from its source of truth. Use this to interpret cached or rolled-up list statuses, especially for curator/storefront aggregators where one buyer-facing buy maps to multiple upstream legs. For rolled-up statuses, this timestamp MUST NOT be later than the oldest upstream status observation that could affect the returned roll-up, so it never overstates freshness. Omit or return null to make no freshness assertion; buyers MUST NOT infer that an omitted or null value means the status is live. This is distinct from `updated_at`, which records when the media buy was last modified.' + ), + ] = None + health: Annotated[ + media_buy_health.MediaBuyHealth | None, + Field( + description='Dependency health of the media buy, orthogonal to `status`. `ok` (default) when no upstream resource that this buy depends on is in an offline state. `impaired` when at least one such resource (audience, creative, catalog_item, event_source, property) is offline and affects delivery for one or more packages — `impairments[]` MUST be non-empty in that case. On terminal-status buys, the seller MAY leave this field in whatever state held at the terminal transition. See lifecycle.mdx § Compliance and the impairment.coherence assertion.' + ), + ] = media_buy_health.MediaBuyHealth.ok + impairments: Annotated[ + list[impairment.Impairment] | None, + Field( + description='Open impairments — upstream dependency state changes that affect delivery for at least one package on this buy. Empty when `health` is `ok`; non-empty iff `health` is `impaired` (health-iff rule on non-terminal buys). Sellers MUST add an entry on the next read after a referenced resource transitions to an offline state, and MUST remove the entry when the resource returns to a serviceable state or stops being a dependency (e.g., via assignment swap via update_media_buy). Staleness budget: the snapshot MUST reflect the impairment within 5 minutes of `impairment.observed_at` regardless of buyer poll cadence — sellers cannot rely on rare buyer polls to defer write propagation. See impairment.coherence assertion for the cross-resource invariant.' + ), + ] = None + rejection_reason: Annotated[ + str | None, + Field( + description="Reason provided by the seller when status is 'rejected'. Present only when status is 'rejected'." + ), + ] = None + currency: Annotated[ + str, + Field( + description='Single ISO 4217 denomination for total_budget, package budget constraints, and canonical BiddingPolicy monetary fields. Every selected pricing option on an AdCP-authored media buy MUST declare this currency. Legacy or externally-created mixed-currency buys must not expose canonical bidding until normalized or split.', + pattern='^[A-Z]{3}$', + ), + ] + total_budget: Annotated[ + StrictFloat, + Field( + description='Hard aggregate lifetime budget, denominated in media_buy.currency', ge=0.0 + ), + ] + daily_budget_cap: Annotated[ + StrictFloat | None, + Field( + description='Current hard aggregate spend ceiling per calendar day, denominated in media_buy.currency. It bounds total spend without allocating or reserving package spend.', + ge=0.0, + ), + ] = None + frequency_cap: Annotated[ + media_buy_frequency_cap.MediaBuyFrequencyCap | None, + Field( + description='Current hard MediaBuy-level cap. Sellers MUST echo it whenever set, separately from package targeting-overlay caps.' + ), + ] = None + budget_cap_timezone: Annotated[ + str | None, + Field( + description='IANA timezone defining the shared calendar-day boundary for every aggregate and package daily cap. Present whenever any daily cap is set on the media buy.' + ), + ] = None + budget_allocation: Annotated[ + budget_allocation_1.BudgetAllocation | None, + Field( + description='Current cross-package allocation configuration. Omitted means fixed allocation for legacy buys.' + ), + ] = None + pacing: Annotated[ + pacing_1.Pacing | None, + Field(description='Aggregate pacing strategy for the media-buy budget.'), + ] = None + bidding: Annotated[ + bidding_policy.BiddingPolicy | None, + Field( + description='Current media-buy-authored bidding policy with scope-specific goal binding and media-buy-currency denomination. Package entries omit bidding when they inherit this block; explicit package automatic overrides remain visible as `{automatic:true}`.' + ), + ] = None + start_time: Annotated[ + AwareDatetime | None, + Field( + description='ISO 8601 flight start time for this media buy (earliest package start_time). Avoids requiring buyers to compute min(packages[].start_time).' + ), + ] = None + end_time: Annotated[ + AwareDatetime | None, + Field( + description='ISO 8601 flight end time for this media buy (latest package end_time). Avoids requiring buyers to compute max(packages[].end_time).' + ), + ] = None + creative_deadline: Annotated[ + AwareDatetime | None, Field(description='ISO 8601 timestamp for creative upload deadline') + ] = None + confirmed_at: Annotated[ + AwareDatetime | None, + Field( + description='ISO 8601 timestamp when the seller committed to this media buy. May be null until seller commitment occurs in deferred/manual approval flows. Once populated, remains stable through later pause, resume, activation, completion, cancellation, and reporting transitions.' + ), + ] + cancellation: Annotated[ + Cancellation | None, + Field(description="Cancellation metadata. Present only when status is 'canceled'."), + ] = None + revision: Annotated[ + SchemaInt, + Field( + description='Current optimistic concurrency token. Pass this in update_media_buy requests intended to change state. Sellers increment it on mutating state changes/updates and reject stale tokens with CONFLICT when a revision token is provided.', + ge=1, + ), + ] + created_at: Annotated[AwareDatetime | None, Field(description='Creation timestamp')] = None + updated_at: Annotated[AwareDatetime | None, Field(description='Last update timestamp')] = None + context: Annotated[ + context_1.ContextObject | None, + Field( + description='Opaque media-buy-level correlation data echoed unchanged from the create_media_buy request. Sellers MUST include persisted context on read surfaces when the media buy was created through AdCP with context, so buyers can reconcile seller-assigned media_buy_id values with their own tracking state. Sellers MAY omit context for media buys created outside AdCP or created without context. Sellers MUST NOT parse this object for business logic.' + ), + ] = None + valid_actions: Annotated[ + list[media_buy_valid_action.MediaBuyValidAction] | None, + Field( + deprecated=True, + description='Flat-vocabulary actions the buyer can perform on this media buy in its current state. Eliminates the need for agents to internalize the state machine — the seller declares what is permitted right now. Deprecated in favor of `available_actions[]`, which carries mode, optional SLA, and a 3.2 change_term_id link. Sellers SHOULD populate both during the 3.x deprecation window; consumers MUST prefer `available_actions[]` when both are present. Removed in 4.0.', + ), + ] = None + available_actions: Annotated[ + list[ + canonical_media_buy_action.CanonicalMediaBuyAction + | media_buy_available_action.MediaBuyAvailableAction + ] + | None, + Field( + description="Structured per-buy resolution of the actions buyer can perform right now. Authoritative — divergence from product `allowed_actions[]` is expected because accepted proposal terms, current state, authorization, and governance delegation are buy-specific. Each entry carries the resolved mode, optional SLA commitment, and in 3.2 an optional change_term_id linking the accepted proposal right. Deprecated 3.1 terms_ref remains readable as an opaque compatibility pointer. Predicate queries via #4425's `requires` grammar address fields by dotted path, e.g. `available_actions.extend_flight.sla.response_max`. Absent SLA means no commitment, not zero commitment — callers composing duration predicates MUST also compose with `present: true` to avoid silently matching sellers who never declared one." + ), + ] = None + webhook_activity: Annotated[ + list[webhook_activity_record.WebhookActivityRecord] | None, + Field( + description='Recent webhook fires relevant to this buy for the calling principal, most-recent first. Includes per-buy delivery/health fires and account-anchored indicators.changed or creative.assignment_changed invalidations whose payload names this media_buy_id. Present only when include_webhook_activity was true and the seller surfaces this debug capability. Account-anchored records MUST include subscriber_id. Three-state presence and the 30-day retention floor follow snapshot-and-log.mdx § Webhook activity log pattern.', + max_length=200, + ), + ] = None + history: Annotated[ + list[HistoryItem] | None, + Field( + description='Revision history entries, most recent first. Only present when include_history > 0 in the request. Each entry represents a state change or update to the media buy. Entries are append-only: sellers MUST NOT modify or delete previously emitted history entries. Callers MAY cache entries by revision number. Returns min(N, available entries) when include_history exceeds the total.' + ), + ] = None + packages: Annotated[ + Sequence[Package], + Field( + description='Packages within this media buy, augmented with creative approval status and optional delivery snapshots' + ), + ] + ext: ext_1.ExtensionObject | None = None + -# Explicit exports -__all__ = [ - "AcceptedProposal", - "Cancellation", - "Cancellation1", - "CreativeApproval", - "GetMediaBuysResponse", - "HistoryItem", - "Indicator", - "Indicator1", - "Indicator2", - "IndicatorTypesEvaluatedEnum", - "IndicatorTypesEvaluatedEnum1", - "IndicatorTypesEvaluatedEnum2", - "MediaBuy", - "Package", - "ProposalStatus", - "Snapshot", -] +class GetMediaBuysResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + media_buys: Annotated[ + Sequence[MediaBuy], + Field( + description='Array of media buys with status, creative approval state, and optional delivery snapshots' + ), + ] + errors: Annotated[ + list[error.Error] | None, + Field( + description='Task-specific errors. A read may return media_buys plus nonfatal resource-state errors. If a pinned place target becomes unexecutable, sellers MUST include PLACE_TARGET_UNAVAILABLE with recovery=correctable, field pointing to the exact media_buys[N].packages[M].targeting_overlay.geo_places[_exclude][A].values[V] response path, and details containing media_buy_id, package_id, system, system_version, country, place_type, and value. The persisted target remains echoed until an intentional update replaces it.' + ), + ] = None + pagination: Annotated[ + pagination_response.PaginationResponse | None, + Field(description='Pagination metadata for the media_buys array.'), + ] = None + sandbox: Annotated[ + StrictBool | None, + Field(description='When true, this response contains simulated data from sandbox mode.'), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/get_products_async_response_input_required.py b/src/adcp/types/domains/media_buy/get_products_async_response_input_required.py index 144a403a6..30c6eba03 100644 --- a/src/adcp/types/domains/media_buy/get_products_async_response_input_required.py +++ b/src/adcp/types/domains/media_buy/get_products_async_response_input_required.py @@ -1,21 +1,38 @@ -"""Types declared by the AdCP ``media_buy/get_products_async_response_input_required`` schema. +# generated by datamodel-codegen: +# filename: media_buy/get_products_async_response_input_required.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.get_products_async_response_input_required import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core import product + + +class Reason(StrEnum): + CLARIFICATION_NEEDED = 'CLARIFICATION_NEEDED' + BUDGET_REQUIRED = 'BUDGET_REQUIRED' -from adcp.types.generated_poc.media_buy.get_products_async_response_input_required import ( - GetProductsInputRequired, - Reason, -) -# Explicit exports -__all__ = ["GetProductsInputRequired", "Reason"] +class GetProductsInputRequired(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + reason: Annotated[ + Reason | None, Field(description='Reason code indicating why input is needed') + ] = None + partial_results: Annotated[ + list[product.Product] | None, + Field(description='Partial product results that may help inform the clarification'), + ] = None + suggestions: Annotated[ + list[str] | None, Field(description='Suggested values or options for the required input') + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/get_products_async_response_submitted.py b/src/adcp/types/domains/media_buy/get_products_async_response_submitted.py index 8462ab08d..32193d55f 100644 --- a/src/adcp/types/domains/media_buy/get_products_async_response_submitted.py +++ b/src/adcp/types/domains/media_buy/get_products_async_response_submitted.py @@ -1,20 +1,50 @@ -"""Types declared by the AdCP ``media_buy/get_products_async_response_submitted`` schema. +# generated by datamodel-codegen: +# filename: media_buy/get_products_async_response_submitted.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.get_products_async_response_submitted import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 -from adcp.types.generated_poc.media_buy.get_products_async_response_submitted import ( - GetProductsSubmitted, -) -# Explicit exports -__all__ = ["GetProductsSubmitted"] +class GetProductsSubmitted(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + status: Annotated[ + Literal['submitted'], + Field( + description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, whose products array is issued in-line. See task-status.json for the full task-status enum.' + ), + ] = 'submitted' + task_id: Annotated[ + str, + Field( + description='Task handle the buyer uses with get_task_status (or the legacy AdCP tasks/get alias), and that the seller references on push-notification callbacks. The products array is issued on the completion artifact, not here. This AdCP application-layer handle remains the snake_case task_id in every transport payload and is distinct from any transport-native A2A Task id.' + ), + ] + message: Annotated[ + str | None, + Field( + description="Optional human-readable explanation of why the task is submitted — e.g., 'Custom curation queued; typical turnaround 10–30 minutes.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", + max_length=2000, + ), + ] = None + estimated_completion: Annotated[ + AwareDatetime | None, Field(description='Estimated completion time for the search') + ] = None + errors: Annotated[ + list[error.Error] | None, + Field( + description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/get_products_async_response_working.py b/src/adcp/types/domains/media_buy/get_products_async_response_working.py index cb220ef77..b34f235a2 100644 --- a/src/adcp/types/domains/media_buy/get_products_async_response_working.py +++ b/src/adcp/types/domains/media_buy/get_products_async_response_working.py @@ -1,20 +1,37 @@ -"""Types declared by the AdCP ``media_buy/get_products_async_response_working`` schema. +# generated by datamodel-codegen: +# filename: media_buy/get_products_async_response_working.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.get_products_async_response_working import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import context as context_1 +from ..core import ext as ext_1 -from adcp.types.generated_poc.media_buy.get_products_async_response_working import ( - GetProductsWorking, -) -# Explicit exports -__all__ = ["GetProductsWorking"] +class GetProductsWorking(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + percentage: Annotated[ + StrictFloat | None, + Field(description='Progress percentage of the search operation', ge=0.0, le=100.0), + ] = None + current_step: Annotated[ + str | None, + Field( + description="Current step in the search process (e.g., 'searching_inventory', 'validating_availability')" + ), + ] = None + total_steps: Annotated[ + SchemaInt | None, Field(description='Total number of steps in the search process') + ] = None + step_number: Annotated[ + SchemaInt | None, Field(description='Current step number (1-indexed)') + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/get_products_rejected.py b/src/adcp/types/domains/media_buy/get_products_rejected.py index dab5bc1a5..4b1326afe 100644 --- a/src/adcp/types/domains/media_buy/get_products_rejected.py +++ b/src/adcp/types/domains/media_buy/get_products_rejected.py @@ -1,18 +1,51 @@ -"""Types declared by the AdCP ``media_buy/get_products_rejected`` schema. +# generated by datamodel-codegen: +# filename: media_buy/get_products_rejected.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.get_products_rejected import +from adcp.types._scalar import ScalarStr -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field + +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope + + +class Suggestion(ScalarStr): + __slots__ = () + _constraints = {'max_length': 1000, 'min_length': 1} -from adcp.types.generated_poc.media_buy.get_products_rejected import GetProductsRejected, Suggestion -# Explicit exports -__all__ = ["GetProductsRejected", "Suggestion"] +class GetProductsRejected(AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + status: Annotated[ + Literal['rejected'], + Field( + description='Task-level business-outcome discriminator. The request was understood and processed, but the seller declined to offer products.' + ), + ] = 'rejected' + reason: Annotated[ + str, + Field( + description="Buyer-facing market explanation for the decline. MAY be sanitized to protect confidential seller rules; for example, 'The requested budget is below the minimum for this inventory' rather than naming the internal rule, candidate products, inventory identifiers, or upstream partners that caused the decision. Plain text only.", + max_length=2000, + min_length=1, + ), + ] + suggestions: Annotated[ + list[Suggestion] | None, + Field( + description='Actionable alternatives the buyer can try, such as changing budget, dates, or channel. If present, the buyer MAY submit a revised brief, but the suggestions do not guarantee acceptance. If absent, the seller is not offering a protocol-level alternative for this brief.', + max_length=20, + min_length=1, + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/get_products_request.py b/src/adcp/types/domains/media_buy/get_products_request.py index 7ed26e520..ad6425dd1 100644 --- a/src/adcp/types/domains/media_buy/get_products_request.py +++ b/src/adcp/types/domains/media_buy/get_products_request.py @@ -1,40 +1,348 @@ -"""Types declared by the AdCP ``media_buy/get_products_request`` schema. +# generated by datamodel-codegen: +# filename: media_buy/get_products_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.get_products_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -from adcp.types.generated_poc.media_buy.get_products_request import ( - Action, - Action9, - BuyingMode, - Field1, - Fields, - GetProductsRequest, - Refine, - Refine1, - Refine2, - Refine3, +from ..core import account_ref, brand_ref +from ..core import catalog as catalog_1 +from ..core import context as context_1 +from ..core import duration +from ..core import ext as ext_1 +from ..core import media_buy_frequency_cap as media_buy_frequency_cap_1 +from ..core import ( + media_buy_support_requirements, + pagination_request, + product_filters, + property_list_ref, ) +from ..core import push_notification_config as push_notification_config_1 +from ..core import targeting, targeting_overlay_requirements +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import delivery_type +from . import acceptance_context as acceptance_context_1 +from . import product_fields + + +class BuyingMode(StrEnum): + brief = 'brief' + wholesale = 'wholesale' + refine = 'refine' + + +class Refine1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + scope: Annotated[ + Literal['request'], + Field( + description='Change scoped to the overall request — direction for the selection as a whole.' + ), + ] = 'request' + ask: Annotated[ + str, + Field( + description="What the buyer is asking for at the request level (e.g., 'more video options and less display', 'suggest how to combine these products').", + min_length=1, + ), + ] + + +class Action(StrEnum): + include = 'include' + omit = 'omit' + more_like_this = 'more_like_this' + + +class Refine2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + scope: Annotated[Literal['product'], Field(description='Change scoped to a specific product.')] = 'product' + product_id: Annotated[ + str, Field(description='Product ID from a previous get_products response.', min_length=1) + ] + action: Annotated[ + Action | None, + Field( + description="'include' (default): return this product with updated pricing and data. 'omit': exclude this product from the response. 'more_like_this': find additional products similar to this one (the original is also returned). Optional — when omitted, the seller treats the entry as action: 'include'." + ), + ] = Action.include + ask: Annotated[ + str | None, + Field( + description="What the buyer is asking for on this product. For 'include': specific changes to request (e.g., 'add 16:9 format'). For 'more_like_this': what 'similar' means (e.g., 'same audience but video format'). Ignored when action is 'omit'.", + min_length=1, + ), + ] = None + -# Explicit exports -__all__ = [ - "Action", - "Action9", - "BuyingMode", - "Field1", - "Fields", - "GetProductsRequest", - "Refine", - "Refine1", - "Refine2", - "Refine3", +class Action9(StrEnum): + include = 'include' + omit = 'omit' + finalize = 'finalize' + + +class Refine3(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + scope: Annotated[ + Literal['proposal'], Field(description='Change scoped to a specific proposal.') + ] = 'proposal' + proposal_id: Annotated[ + str, Field(description='Proposal ID from a previous get_products response.', min_length=1) + ] + action: Annotated[ + Action9 | None, + Field( + description="'include' (default): return this proposal with updated allocations and pricing. 'omit': exclude this proposal from the response. 'finalize': request firm pricing and inventory hold. New callers use refine_proposals with action revise for a draft successor or action finalize for a committed held successor; terminal feedback is available through decline_proposals.\n\nLegacy finalize is exclusive within the parent `refine[]` array: see the array-level description for the finalize-exclusivity rule (mixing finalize with non-finalize entries is rejected) and multi-finalize atomicity contract." + ), + ] = Action9.include + ask: Annotated[ + str | None, + Field( + description="What the buyer is asking for on this proposal (e.g., 'shift more budget toward video', 'reduce total by 10%'). Ignored when action is omit.", + min_length=1, + ), + ] = None + + +Refine = Annotated[ + Refine1 | Refine2 | Refine3, + Field(discriminator='scope'), ] + + +class Fields(StrEnum): + format_ids = 'format_ids' + outcome_measurement = 'outcome_measurement' + delivery_measurement = 'delivery_measurement' + creative_policy = 'creative_policy' + metric_optimization = 'metric_optimization' + conversion_tracking = 'conversion_tracking' + data_provider_signals = 'data_provider_signals' + included_signals = 'included_signals' + signal_targeting_options = 'signal_targeting_options' + overlay_support = 'overlay_support' + media_buy_support = 'media_buy_support' + installments = 'installments' + is_custom = 'is_custom' + product_card = 'product_card' + product_card_detailed = 'product_card_detailed' + enforced_policies = 'enforced_policies' + trusted_match = 'trusted_match' + + +class Field1(StrEnum): + """Compatibility union of canonical and get-products-only fields.""" + + product_id = 'product_id' + name = 'name' + description = 'description' + publisher_properties = 'publisher_properties' + channels = 'channels' + video_placement_types = 'video_placement_types' + audio_distribution_types = 'audio_distribution_types' + sponsored_placement_types = 'sponsored_placement_types' + social_placement_surfaces = 'social_placement_surfaces' + format_options = 'format_options' + placements = 'placements' + delivery_type = 'delivery_type' + exclusivity = 'exclusivity' + pricing_options = 'pricing_options' + forecast = 'forecast' + reporting_capabilities = 'reporting_capabilities' + measurement_terms = 'measurement_terms' + performance_standards = 'performance_standards' + catalog_types = 'catalog_types' + signal_targeting_allowed = 'signal_targeting_allowed' + signal_targeting_rules = 'signal_targeting_rules' + demographic_targeting = 'demographic_targeting' + overlay_support = 'overlay_support' + collections = 'collections' + collection_targeting_allowed = 'collection_targeting_allowed' + media_buy_support = 'media_buy_support' + audience_evidence = 'audience_evidence' + audience_evidence_selections = 'audience_evidence_selections' + max_optimization_goals = 'max_optimization_goals' + catalog_match = 'catalog_match' + list_applications = 'list_applications' + brief_relevance = 'brief_relevance' + targeting_resolution = 'targeting_resolution' + acceptance_policy_profile_ids = 'acceptance_policy_profile_ids' + identity = 'identity' + execution_requirements = 'execution_requirements' + expires_at = 'expires_at' + allowed_actions = 'allowed_actions' + format_ids = 'format_ids' + outcome_measurement = 'outcome_measurement' + delivery_measurement = 'delivery_measurement' + creative_policy = 'creative_policy' + metric_optimization = 'metric_optimization' + conversion_tracking = 'conversion_tracking' + data_provider_signals = 'data_provider_signals' + included_signals = 'included_signals' + signal_targeting_options = 'signal_targeting_options' + installments = 'installments' + is_custom = 'is_custom' + product_card = 'product_card' + product_card_detailed = 'product_card_detailed' + enforced_policies = 'enforced_policies' + trusted_match = 'trusted_match' + + +class GetProductsRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + idempotency_key: Annotated[ + str | None, + Field( + description='Optional client-generated key on the AdCP 3.x compatibility facade. The field remains optional on every arm for wire compatibility. A seller MAY ignore a supplied key on a guaranteed side-effect-free synchronous read. Buyers SHOULD supply a key whenever the request may allocate a task, finalize a proposal, or otherwise change observable state. When a key is supplied on such a request and the seller declares adcp.idempotency.supported: true, the seller MUST apply the AdCP replay contract before that effect. If the key is omitted or the seller declares adcp.idempotency.supported: false, the buyer has no portable at-most-once retry guarantee after an ambiguous result. New callers SHOULD use the compact 3.2 tasks; each stateful split task has its own idempotency identity, so callers MUST retry with the same tool name. Keys MUST be unique per seller and logical request.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] = None + buying_mode: Annotated[ + BuyingMode, + Field( + description="Declares buyer intent for this request. 'brief': publisher curates product recommendations from the provided brief. 'wholesale': buyer requests raw product inventory to apply their own audiences — brief must not be provided, and proposals are omitted. 'refine': iterate on products and proposals from a previous get_products response using the refine array of change requests. v3 clients MUST include buying_mode. Sellers receiving requests from pre-v3 clients without buying_mode SHOULD default to 'brief'. Timing semantics: 'wholesale' is a wholesale product feed read — sellers SHOULD return a synchronous response and MUST NOT route a 'wholesale' request through the async/Submitted arm; partial completion is signalled via the response's incomplete[] field (with optional estimated_wait), not via a task-handoff envelope. 'brief' and 'refine' MAY complete synchronously, or MAY return a Submitted envelope (see get-products-async-response-submitted.json) when curation requires upstream-system queries or HITL review the seller cannot complete inside time_budget. Buyers needing predictable fast wholesale product feed access MUST use 'wholesale'; buyers open to slower curation use 'brief' or 'refine'." + ), + ] + brief: Annotated[ + str | None, + Field( + description="Natural language description of campaign requirements. Required when buying_mode is 'brief'. Must not be provided when buying_mode is 'wholesale' or 'refine'. Buyers SHOULD use structured fields for every requirement that can be expressed structurally, and reserve brief prose for goals, context, preferences, and requirements without a structured representation. Sellers MUST apply explicit hard requirements stated in the brief even when the buyer did not duplicate them in a structured field. When a seller translates hard prose into structured targeting that materially affects product eligibility, pricing, or forecasting, it MUST confirm that interpretation once in GetProductsResponse.targeting_resolution.brief_targeting; otherwise confirmation remains a best practice. If hard prose contradicts a structured field, sellers MUST reject the request with INVALID_REQUEST rather than choose one interpretation or return an unexplained empty result." + ), + ] = None + refine: Annotated[ + list[Refine] | None, + Field( + description="Array of change requests for iterating on products and proposals from a previous get_products response. Each entry declares a scope (request, product, or proposal) and what the buyer is asking for. Only valid when buying_mode is 'refine'. The seller responds to each entry via refinement_applied in the response, matched by position.\n\nFinalize-exclusivity rule: if any entry has `action: 'finalize'`, ALL entries in the array MUST be proposal-scoped with `action: 'finalize'` — mixing finalize entries with `include`/`omit` entries or with request- / product-scoped entries MUST be rejected by the seller with `INVALID_REQUEST`. Finalize is a commit, not a refinement; the buyer expressing intent to commit means refinements have already converged. Buyers needing to refine AND commit in close succession sequence the calls: first a refine call (no finalize), then a finalize call against the resulting `proposal_id`(s).\n\nMulti-finalize semantics: multiple finalize entries against different `proposal_id` values in a single call are allowed and MUST be **atomic at the observation point** — sellers MUST NOT return a success response unless every named proposal has both completed and been persisted as committed. Pre-commit validation runs before any side-effects (inventory pull, terms lock, governance attestation); if any proposal fails validation, the seller MUST reject the entire call without committing any of the named proposals. There is no rollback operation in the spec — an `unfinalize` would itself be a new mutation surface; the atomicity guarantee runs entirely on the seller's pre-commit validation gate, not on post-commit reversal. Sellers that cannot guarantee atomic pre-commit validation MUST reject multi-finalize arrays with `MULTI_FINALIZE_UNSUPPORTED` (preferred — distinguishes seller-side capability gap from a malformed request) or `INVALID_REQUEST` (acceptable fallback for sellers on a pre-3.1 error catalog). If a mid-commit failure occurs *after* validation passed but before all proposals persist (e.g., a downstream ad server fails between commits one and two), the seller MUST return `INTERNAL_ERROR` with `refinement_applied[]` carrying per-position outcomes — the spec does NOT define a recovery path for this case, and buyers SHOULD treat the resulting state as undefined and re-read via `get_media_buys` / equivalent before retrying. Buyers MUST NOT assume multi-finalize support without a successful first attempt — there is no capability flag for this; the failure response is the discovery surface. Buyers whose intent specifically requires atomic commit (e.g., budget-shared proposals where one finalizing without the other is incoherent) MUST be prepared to abandon the intent if the seller returns `MULTI_FINALIZE_UNSUPPORTED` — there is no recovery for that loss of buyer intent beyond sequencing single-finalize calls and accepting the looser commit guarantee.", + min_length=1, + ), + ] = None + brand: Annotated[ + brand_ref.BrandReference | None, + Field( + description='Brand reference for product discovery context. Resolved to full brand identity at execution time.' + ), + ] = None + acceptance_context: Annotated[ + acceptance_context_1.AcceptanceContext | None, + Field( + description='Structured campaign and advertiser facts for seller acceptance-policy preflight. Sellers may infer omitted facts from brand and brief, but uncertainty never implies acceptance.' + ), + ] = None + catalog: Annotated[ + catalog_1.Catalog | None, + Field( + description='Catalog of items the buyer wants to promote. The seller matches catalog items against its inventory and returns products where matches exist. Supports all catalog types: a job catalog finds job ad products, a product catalog finds sponsored product slots. Reference a synced catalog by catalog_id, or provide inline items.' + ), + ] = None + account: Annotated[ + account_ref.AccountReference | None, + Field( + description="Account for product lookup. Returns products with pricing specific to this account's rate card." + ), + ] = None + preferred_delivery_types: Annotated[ + list[delivery_type.DeliveryType] | None, + Field( + description='Delivery types the buyer prefers, in priority order. Unlike filters.delivery_type which excludes non-matching products, this signals preference for curation — the publisher may still include other delivery types when they match the brief well.', + min_length=1, + ), + ] = None + filters: Annotated[ + product_filters.ProductFilters | None, + Field( + description="Offer filters. Valid in brief, wholesale, and refine modes. In every mode, sellers MUST exclude products that do not satisfy them: brief controls curation, wholesale controls feed behavior, and refine controls iteration, but none changes filter semantics. On refine, presence is the complete replacement filter state; when omitted, each referenced product's bound discovery constraints remain in force. Targeting-like legacy fields remain accepted during migration but are deprecated in favor of targeting_overlay and required_overlay_support." + ), + ] = None + targeting_overlay: Annotated[ + targeting.TargetingOverlay | None, + Field( + description="Concrete delivery constraints the buyer expects to carry into create_media_buy. Buyers SHOULD use this field instead of putting equivalent exact targeting only in brief prose. Sellers evaluate these constraints during discovery and scope returned pricing and forecasts to the effective targeting. If a product cannot honor the request exactly, the seller may omit it or return a request-scoped configured product with Product targeting_resolution modifications. Absence of Product targeting_resolution means exact acceptance of this structured overlay; it does not confirm how targeting in the brief was interpreted. On refine, presence is complete replacement state for returned configurations; when omitted, each referenced product's bound targeting remains in force." + ), + ] = None + media_buy_frequency_cap: Annotated[ + media_buy_frequency_cap_1.MediaBuyFrequencyCap | None, + Field( + description='Concrete aggregate max-impression cap intended for the MediaBuy root. Every returned product must participate in one shared counter for this exact value. Legacy proposals echo it as proposals[].frequency_cap; direct buyers repeat it at purchase.' + ), + ] = None + required_overlay_support: Annotated[ + targeting_overlay_requirements.TargetingOverlayRequirements | None, + Field( + description="Minimum product-scoped targeting dimensions the buyer must be able to set independently on packages later. This requests selectable capability, not current targeting values, value-specific availability or forecasts, and not one product per possible value. A requirement true matches support true or any valid support object; an object requirement matches support true or an object containing every requested boolean and requested array subset. The backward-compatible daypart_targets support true is the exception: it satisfies inventory_local requirements only, not iana requirements. Unrequested object fields and numeric limits do not participate. Missing or unknown requirement fields do not match. Seller limit fields are response-only and cannot be requested here. On refine, presence is complete replacement state; when omitted, each referenced product's bound future-support requirements remain in force." + ), + ] = None + required_media_buy_support: Annotated[ + media_buy_support_requirements.ProductMediaBuySupportRequirements | None, + Field( + description='Minimum product participation in shared MediaBuy-level controls. For a frequency cap, every returned product must be composable in one seller-maintained aggregate counter for every requested per unit. On refine, presence is complete replacement state.' + ), + ] = None + property_list: Annotated[ + property_list_ref.PropertyListReference | None, + Field( + deprecated=True, + description='DEPRECATED discovery-only property filter. On compact discovery tasks use criteria.offer_filters.property_list to preserve product eligibility. Use targeting_overlay.property_list only when the buyer intends a delivery constraint, or required_overlay_support.property_list for future selectability. A facade MUST NOT convert this discovery filter into delivery targeting.', + ), + ] = None + fields: Annotated[ + list[product_fields.ProductResponseField | Fields] | None, + Field( + description='Specific product fields to include in the response. When omitted, all fields are returned. Use for lightweight discovery calls where only a subset of product data is needed. product_id and name are always included. `format_ids` is a deprecated 3.x compatibility projection; new integrations request canonical `format_options`. Safety-critical request-specific fields override projection: Product.targeting_resolution and expires_at MUST be included whenever the seller returns modifications, overlay_support MUST be included when required_overlay_support was requested, media_buy_support MUST be included when required_media_buy_support or media_buy_frequency_cap was requested, list_applications MUST be included when a property or collection list is in the effective targeting, and audience_evidence_selections MUST be included when filters.audience_evidence_requirements affects eligibility or ranking. fields controls the optional audience_evidence payload, not either decision receipt. Response-level brief targeting confirmation is not a projected product field.', + min_length=1, + ), + ] = None + time_budget: Annotated[ + duration.Duration | None, + Field( + description='Maximum time the buyer will commit to this request. The seller returns the best results achievable within this budget and does not start processes (human approvals, expensive external queries) that cannot complete in time. When omitted, the seller decides timing.' + ), + ] = None + push_notification_config: Annotated[ + push_notification_config_1.PushNotificationConfig | None, + Field( + description='Optional webhook configuration for async terminal completion/failure notifications on curated discovery. Meaningful only for `buying_mode: "brief"` and `buying_mode: "refine"` requests that enter the async lifecycle. Submitted envelopes with `task_id` remain pollable through `get_task_status` (legacy `tasks/get`) whether or not this field is present. If a brief/refine request includes this field and the seller returns a Submitted envelope, the seller MUST deliver at least the terminal completion/failure notification to the configured URL; intermediate progress notifications are MAY. If the seller cannot honor the webhook channel, it MUST reject the request with a structured error instead of silently accepting. This field does not change wholesale timing semantics: sellers MUST NOT route `buying_mode: "wholesale"` requests through the async/Submitted arm or emit async delivery solely because `push_notification_config` is present; partial wholesale completion is reported via `incomplete[]`.' + ), + ] = None + pagination: Annotated[ + pagination_request.PaginationRequest | None, + Field( + description="Cursor-based pagination controls for get_products. Valid in all buying modes. In brief mode, pagination bounds the seller's returned products[] for the curated answer to the brief and is not an exhaustive catalog-enumeration contract. In refine mode, pagination bounds the refined products[] result implied by refine[] and filters; proposals may accompany a page as plan metadata but are not independently counted by this pagination envelope. In wholesale mode, pagination walks the wholesale product feed and may be combined with wholesale feed versioning." + ), + ] = None + if_wholesale_feed_version: Annotated[ + str | None, + Field( + description="Opaque wholesale_feed_version token returned by a prior wholesale-mode get_products response from this agent. Only valid when buying_mode is wholesale. When provided, the seller compares against its current wholesale product feed version for the buyer's cache_scope and MAY return an unchanged: true response (with products omitted) if nothing has changed. The token is scope-keyed: buyers cache `(cache_scope, wholesale_feed_version)` pairs. Scoping dimensions: (agent, buying_mode, filters, targeting_overlay, media_buy_frequency_cap, required_overlay_support, required_media_buy_support, deprecated property_list, catalog) for cache_scope: 'public'; that tuple plus account identity for cache_scope: 'account'. pagination.cursor is NOT part of the scoping tuple. Backward-compatible: pre-v3.1 agents that ignore this field simply return the full payload, same as the unchanged-server path. See specs/wholesale-feed-webhooks.md for the full sync pattern." + ), + ] = None + if_pricing_version: Annotated[ + str | None, + Field( + description="Opaque pricing_version token from a prior get_products response. MUST only be sent together with if_wholesale_feed_version — pricing version has no structural baseline to compare against on its own. Evaluation order: (1) if_wholesale_feed_version mismatch → seller returns the full payload (pricing is implicitly stale); (2) if_wholesale_feed_version matches but if_pricing_version mismatches → seller returns the full payload so the buyer sees updated pricing_options; (3) both match → seller MAY return unchanged: true. Agents that don't track pricing separately ignore if_pricing_version and fall back to if_wholesale_feed_version semantics. Useful for storefronts that re-price compositions far more often than they re-render product mirrors." + ), + ] = None + context: context_1.ContextObject | None = None + required_policies: Annotated[ + list[str] | None, + Field( + description='Registry policy IDs that the buyer requires to be enforced for products in this response. Sellers filter products to only those that comply with or already enforce the requested policies.' + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/get_products_response.py b/src/adcp/types/domains/media_buy/get_products_response.py index 2f7954918..06e34d9e9 100644 --- a/src/adcp/types/domains/media_buy/get_products_response.py +++ b/src/adcp/types/domains/media_buy/get_products_response.py @@ -1,48 +1,351 @@ -"""Types declared by the AdCP ``media_buy/get_products_response`` schema. +# generated by datamodel-codegen: +# filename: media_buy/get_products_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.get_products_response import +from adcp.types.base import AdcpResponse, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any, Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field, StrictBool, StringConstraints + +from ..core import context as context_1 +from ..core import duration, error +from ..core import ext as ext_1 +from ..core import pagination_response, product, proposal +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from . import get_products_targeting_resolution + + +class Extensions(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + extends: Annotated[ + str, + Field( + description='Canonical concept this extension extends (e.g., `tracking`, `cta_vocabulary`, `destinations`, `placement`).' + ), + ] + fields: Annotated[ + dict[str, Any], + Field( + description='JSON Schema fragment declaring the additional fields this extension contributes.' + ), + ] + version: Annotated[ + str | None, + Field( + description='Semantic version of the extension definition. Distinct from the digest — version is human-readable; digest is the integrity check.' + ), + ] = None + description: str | None = None + + +class Suggestion(ScalarStr): + __slots__ = () + _constraints = {'max_length': 1000, 'min_length': 1} + + +class Status(StrEnum): + applied = 'applied' + partial = 'partial' + unable = 'unable' + + +class RefinementApplied1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + scope: Annotated[ + Literal['request'], + Field(description="Echoes scope 'request' from the corresponding refine entry."), + ] = 'request' + status: Annotated[ + Status, + Field( + description="'applied': the ask was fulfilled. 'partial': the ask was partially fulfilled — see notes for details. 'unable': the seller could not fulfill the ask — see notes for why." + ), + ] + notes: Annotated[ + str | None, + Field( + description="Seller explanation of what was done, what couldn't be done, or why. Recommended when status is 'partial' or 'unable'." + ), + ] = None + + +class RefinementApplied2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + scope: Annotated[ + Literal['product'], + Field(description="Echoes scope 'product' from the corresponding refine entry."), + ] = 'product' + product_id: Annotated[ + str, Field(description='Echoes product_id from the corresponding refine entry.') + ] + status: Annotated[ + Status, + Field( + description="'applied': the ask was fulfilled. 'partial': the ask was partially fulfilled — see notes for details. 'unable': the seller could not fulfill the ask — see notes for why." + ), + ] + notes: Annotated[ + str | None, + Field( + description="Seller explanation of what was done, what couldn't be done, or why. Recommended when status is 'partial' or 'unable'." + ), + ] = None + + +class RefinementApplied3(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + scope: Annotated[ + Literal['proposal'], + Field(description="Echoes scope 'proposal' from the corresponding refine entry."), + ] = 'proposal' + proposal_id: Annotated[ + str, Field(description='Echoes proposal_id from the corresponding refine entry.') + ] + status: Annotated[ + Status, + Field( + description="'applied': the ask was fulfilled. 'partial': the ask was partially fulfilled — see notes for details. 'unable': the seller could not fulfill the ask — see notes for why." + ), + ] + notes: Annotated[ + str | None, + Field( + description="Seller explanation of what was done, what couldn't be done, or why. Recommended when status is 'partial' or 'unable'." + ), + ] = None -from adcp.types.generated_poc.media_buy.get_products_response import ( - CacheScope, - ExcludedBy, - Extensions, - FilterDiagnostics, - GetProductsResponse, - IncompleteItem, - RefinementApplied, - RefinementApplied1, - RefinementApplied2, - RefinementApplied3, - Scope, - Semantics, - Status, - Suggestion, -) - -# Explicit exports -__all__ = [ - "CacheScope", - "ExcludedBy", - "Extensions", - "FilterDiagnostics", - "GetProductsResponse", - "IncompleteItem", - "RefinementApplied", - "RefinementApplied1", - "RefinementApplied2", - "RefinementApplied3", - "Scope", - "Semantics", - "Status", - "Suggestion", + +RefinementApplied = Annotated[ + RefinementApplied1 | RefinementApplied2 | RefinementApplied3, + Field(discriminator='scope'), ] + + +class Scope(StrEnum): + products = 'products' + pricing = 'pricing' + forecast = 'forecast' + proposals = 'proposals' + wholesale_feed = 'wholesale_feed' + + +class IncompleteItem(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + scope: Annotated[ + Scope, + Field( + description="'products': not all inventory sources were searched. 'pricing': products returned but pricing is absent or unconfirmed. 'forecast': products returned but forecast data is absent. 'proposals': proposals were not generated or are incomplete. 'wholesale_feed': in wholesale mode, full feed enumeration could not complete in the time budget — symmetric with get_signals' 'wholesale_feed' scope so sellers have a precise way to declare wholesale-incomplete on the products surface." + ), + ] + description: Annotated[ + str, Field(description='Human-readable explanation of what is missing and why.') + ] + estimated_wait: Annotated[ + duration.Duration | None, + Field( + description='How much additional time is expected to resolve this scope. Allows the buyer to decide whether to make a new attempt with a larger time_budget; its presence is not an automatic-retry instruction.' + ), + ] = None + + +class Semantics(StrEnum): + only = 'only' + any = 'any' + approximate = 'approximate' + + +class ExcludedBy(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + count: Annotated[ + SchemaInt, + Field( + description='Number of products excluded by this filter, interpreted per the parent `semantics` field.', + ge=0, + ), + ] + values: Annotated[ + list[str | dict[str, Any]] | None, + Field( + description='Optional list of the specific filter values that contributed to exclusions, when meaningful. For `required_metrics`: the metric names that excluded products (strings). For `required_vendor_metrics`: the vendor/metric pin entries (objects). Item shape is filter-specific; the schema admits string OR object items. Buyers without filter-specific knowledge SHOULD treat as opaque.' + ), + ] = None + notes: Annotated[ + str | None, + Field( + description="Optional human-readable note about why this filter narrowed the set (e.g., 'no products in this brief support DV viewability at the requested threshold')." + ), + ] = None + + +class FilterDiagnostics(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + semantics: Annotated[ + Semantics | None, + Field( + description="How `excluded_by[*].count` values are computed across multiple filters. `only`: counts products that would have been included if not for THIS filter alone (deterministic; the right value for 'which filter killed my result set' triage — recommended when feasible). `any`: counts products excluded by ANY filter (so multiple filters' counts may overlap and sum to more than `total_candidates`). `approximate`: sellers SHOULD use this when their pipeline can't cleanly attribute exclusions to a single filter. Buyers SHOULD inspect `semantics` before doing arithmetic on counts." + ), + ] = None + total_candidates: Annotated[ + SchemaInt | None, + Field( + description='Number of products the seller considered before applying `filters`. Baseline for interpreting per-filter exclusion counts. Approximate — sellers MAY return a sampled or capped count when their candidate pool is large. Optional; sellers whose baseline candidate set size is sensitive (revealing market posture or competitive density) MAY omit this while still emitting `excluded_by`.', + ge=0, + ), + ] = None + excluded_by: Annotated[ + dict[str, ExcludedBy] | None, + Field( + description="Per-filter exclusion counts, keyed by the filter property name as it appears in the request's `filters` object (e.g., `pricing_currencies`, `required_metrics`, `required_vendor_metrics`, `required_geo_targeting`, `budget_range`). Values are objects carrying `count` and optional filter-specific detail. Only filters that actually narrowed the set need appear here; absence of a key means that filter did not exclude anything (or was not in the request)." + ), + ] = None + + +class CacheScope(StrEnum): + public = 'public' + account = 'account' + + +class GetProductsResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + products: Annotated[ + list[product.Product] | None, Field(description='Array of matching products') + ] = None + targeting_resolution: Annotated[ + get_products_targeting_resolution.ProductDiscoveryTargetingResolution | None, + Field( + description='Request-level confirmation of structured hard targeting inferred from the brief. Sellers MUST include this when their structured interpretation of hard prose materially affects product eligibility, pricing, or forecasting; otherwise inclusion is a best practice. Omitted when no hard targeting was inferred from the brief.' + ), + ] = None + extensions: Annotated[ + dict[Annotated[str, StringConstraints(pattern=r'^https?://[^@]+@sha256:[a-f0-9]{64}$')], Extensions] | None, + Field( + description='Bundled platform-extension definitions referenced by any product in `products`. Keyed by `@` (e.g., `https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel@sha256:abc...`). When present, lets buyers resolve `platform_extensions` references on product format declarations without a separate fetch. Buyer SDKs cache by URI@digest; subsequent get_products responses MAY omit definitions the buyer already has cached and rely on the digest match. Each value is an extension definition with `extends` (the canonical concept it extends, e.g., `tracking`), `fields` (the schema for additional fields the extension contributes), `version`, and optional `description`.' + ), + ] = None + proposals: Annotated[ + list[proposal.Proposal] | None, + Field( + description='Optional legacy proposed media plans. When the request carries media_buy_frequency_cap, every returned proposal echoes the bound value as proposal.frequency_cap. Buyers may refine or execute a committed proposal by ID.' + ), + ] = None + errors: Annotated[ + list[error.Error] | None, + Field(description='Task-specific errors and warnings (e.g., product filtering issues)'), + ] = None + reason: Annotated[ + str | None, + Field( + description='Buyer-facing market explanation required only on the GetProductsRejected arm. MAY be sanitized to protect confidential seller rules. Plain text only.', + max_length=2000, + min_length=1, + ), + ] = None + suggestions: Annotated[ + list[Suggestion] | None, + Field( + description='Actionable alternatives available only on the GetProductsRejected arm.', + max_length=20, + ), + ] = None + property_list_applied: Annotated[ + StrictBool | None, + Field( + description='[AdCP 3.0] Indicates whether deprecated top-level property_list filtering was applied. True if the agent filtered products based on the provided property_list; every returned product also carries the corresponding property/include list_applications receipt. Absent or false if property_list was not provided or not supported by this agent.' + ), + ] = None + catalog_applied: Annotated[ + StrictBool | None, + Field( + description='Whether the seller filtered results based on the provided catalog. True if the seller matched catalog items against its inventory. Absent or false if no catalog was provided or the seller does not support catalog matching.' + ), + ] = None + refinement_applied: Annotated[ + list[RefinementApplied] | None, + Field( + description="Seller's response to each change request in the refine array, matched by position. Each entry acknowledges whether the corresponding ask was applied, partially applied, or unable to be fulfilled. MUST contain the same number of entries in the same order as the request's refine array. Only present when the request used buying_mode: 'refine'. Each entry MUST echo the request entry's scope and — for product and proposal scopes — the matching id field (product_id or proposal_id), so orchestrators can cross-validate alignment." + ), + ] = None + incomplete: Annotated[ + list[IncompleteItem] | None, + Field( + description="Declares what the seller could not finish within the buyer's time_budget or due to internal limits while still returning a usable response. Each entry identifies a scope that is missing or partial. Absent when the response is fully complete. This field does not classify the condition as retryable; retryability is carried by error.recovery on the error channel.", + min_length=1, + ), + ] = None + filter_diagnostics: Annotated[ + FilterDiagnostics | None, + Field( + description="Optional non-fatal diagnostic block describing how the request's `filters` narrowed the candidate set. Use this to disambiguate empty/small result lists between 'no inventory matches the brief' and 'a specific filter excluded everything', without breaking the filter-not-fail convention (sellers still silently exclude unmatched products; this block is observability, not error reporting). Sellers MAY populate this when meaningful narrowing occurred; buyers MAY use it for triage UX without depending on its presence. Counts only — products are not enumerated by name to avoid leaking competitive intelligence about adjacent campaigns or seller inventory. `total_candidates` and `excluded_by` are independently optional — sellers whose baseline candidate set size is sensitive MAY emit `excluded_by` without `total_candidates`, or vice versa.", + examples=[ + { + 'semantics': 'only', + 'total_candidates': 47, + 'excluded_by': { + 'required_metrics': {'count': 31, 'values': ['completed_views']}, + 'required_geo_targeting': {'count': 9}, + 'pricing_currencies': {'count': 3, 'values': ['USD']}, + 'budget_range': {'count': 7}, + }, + } + ], + ), + ] = None + pagination: Annotated[ + pagination_response.PaginationResponse | None, + Field( + description="Cursor metadata for paginated get_products responses. In brief/refine mode, continuation pages bound returned products[] for the seller's curated or refined answer; proposals may accompany a page as plan metadata but are not independently counted by this pagination envelope, and pagination does not convert the response into an exhaustive feed contract. In wholesale mode, continuation pages walk the wholesale product feed." + ), + ] = None + wholesale_feed_version: Annotated[ + str | None, + Field( + description="Opaque token representing the version of the wholesale product feed state used to compose this response. Sellers that implement conditional-fetch (if_wholesale_feed_version) MUST return this on every wholesale-mode response so buyers can cache and probe later. Buyers MUST treat the value as opaque — no format, no ordering, no inspection. The token is scope-keyed: it describes a version for the cache_scope declared on this response, NOT a global agent version. A buyer caches `(cache_scope, wholesale_feed_version)` pairs and presents the matching token on the next request. Scoping dimensions: (agent, buying_mode, filters, targeting_overlay, media_buy_frequency_cap, required_overlay_support, required_media_buy_support, deprecated property_list, catalog) for cache_scope: 'public'; that tuple plus account_id for cache_scope: 'account'. pagination.cursor is NOT part of the scoping tuple. See specs/wholesale-feed-webhooks.md for the full cache layering model." + ), + ] = None + pricing_version: Annotated[ + str | None, + Field( + description='Opaque token representing the version of the pricing layer, including product pricing_options and nested signal_targeting_options pricing_options. When the seller supports independent pricing versioning, pricing_version changes when prices move but wholesale_feed_version changes only when structure/metadata moves. Same cache_scope keying as wholesale_feed_version. Sellers not separating these MAY omit pricing_version and use wholesale_feed_version for both.' + ), + ] = None + cache_scope: Annotated[ + CacheScope | None, + Field( + description="Declares whether the wholesale_feed_version and pricing_version on this response describe a universal layer or an account-specific overlay. REQUIRED on every 3.1+ response (the 3.1 schema enforces this — the safety property of the two-layer cache model depends on it). 'public': this response describes the seller's published rate card; the buyer MAY dedupe under (agent, buying_mode, filters, targeting_overlay, media_buy_frequency_cap, required_overlay_support, required_media_buy_support, deprecated property_list, catalog) without scoping by account. 'account': this response includes account-specific overrides; the buyer MUST cache the version under that tuple plus account_id. When the request did NOT include `account`, the seller MUST return `cache_scope: 'public'`. When the request included `account`, the seller MUST return either: 'public' (this account prices off the public rate card — buyer dedupes) or 'account' (account-specific overrides exist — buyer caches under the account key). Sellers MAY return 'public' on an account-scoped request that previously had overrides — buyers SHOULD interpret this as a downgrade and drop their account-overlay. Without schema-required cache_scope, a seller silently omitting the field on an account-scoped response would cause buyers to mis-key the cache and serve account-overlay payloads to other accounts. **Backward-compatibility note for 3.1 validators:** SDKs that validate strictly against the 3.1 schema MUST select the validator based on the server-declared `adcp_version` (release-precision version negotiation, 3.1). For responses with `adcp_version` starting `3.0`, the 3.1 cache_scope-required constraint MUST be relaxed." + ), + ] = CacheScope.public + unchanged: Annotated[ + Literal[True] | None, + Field( + description="Present and `true` ONLY on wholesale-mode responses when the request carried if_wholesale_feed_version (and/or if_pricing_version) matching the seller's current version for the buyer's cache_scope, in which case products[] MUST be omitted; wholesale_feed_version (echoed), cache_scope (echoed), and pricing_version (echoed when used) MUST still be present. Buyers receiving unchanged: true MUST NOT mutate their local wholesale product mirror. **One shape per state:** sellers MUST NOT emit `unchanged: false` — the absence of the field IS the signal that the response carries products. Two shapes ({ unchanged: false, products: [...] } vs. { products: [...] }) for the same state would let some sellers always emit the field and some never would, creating an inconsistency the wire shouldn't carry. **Cross-scope isolation:** the comparator that decides `unchanged` MUST be keyed on `(cache_scope, wholesale_feed_version)`, not on the token value alone. A seller MUST NOT emit `unchanged: true` when it resolves the request to a different `cache_scope` than the one whose token the buyer echoed in `if_wholesale_feed_version` (and/or `if_pricing_version`): because the token is scope-keyed, a value minted for `cache_scope: 'public'` cannot match the seller's current token for `cache_scope: 'account'` (or vice-versa), so such a request MUST return the full feed for the resolved scope with that scope's own token." + ), + ] = None + sandbox: Annotated[ + StrictBool | None, + Field(description='When true, this response contains simulated data from sandbox mode.'), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/get_products_targeting_resolution.py b/src/adcp/types/domains/media_buy/get_products_targeting_resolution.py index 415473ec5..91dc300cb 100644 --- a/src/adcp/types/domains/media_buy/get_products_targeting_resolution.py +++ b/src/adcp/types/domains/media_buy/get_products_targeting_resolution.py @@ -1,20 +1,26 @@ -"""Types declared by the AdCP ``media_buy/get_products_targeting_resolution`` schema. +# generated by datamodel-codegen: +# filename: media_buy/get_products_targeting_resolution.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.get_products_targeting_resolution import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import ext as ext_1 +from ..core import targeting -from adcp.types.generated_poc.media_buy.get_products_targeting_resolution import ( - ProductDiscoveryTargetingResolution, -) -# Explicit exports -__all__ = ["ProductDiscoveryTargetingResolution"] +class ProductDiscoveryTargetingResolution(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + brief_targeting: Annotated[ + targeting.TargetingOverlay, + Field( + description="Structured delivery constraints the seller resolved from explicit hard targeting in the brief. This is the seller's literal interpretation, not a counteroffer: products that cannot honor it are omitted. Sellers MUST emit this confirmation when the interpretation materially affects product eligibility, pricing, or forecasting; otherwise it is a best practice. Unchanged structured targeting_overlay values are not repeated." + ), + ] + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/get_reporting_status_request.py b/src/adcp/types/domains/media_buy/get_reporting_status_request.py index 3938e88ac..5c1c57281 100644 --- a/src/adcp/types/domains/media_buy/get_reporting_status_request.py +++ b/src/adcp/types/domains/media_buy/get_reporting_status_request.py @@ -1,23 +1,112 @@ -"""Types declared by the AdCP ``media_buy/get_reporting_status_request`` schema. +# generated by datamodel-codegen: +# filename: media_buy/get_reporting_status_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.get_reporting_status_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import AwareDatetime, ConfigDict, Field + +from ..core import canonical_account_ref +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core import pagination_request, reporting_coverage, reporting_delivery_offering +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import reporting_finality, reporting_health + + +class DeliveryConfigId(ScalarStr): + __slots__ = () + _constraints = {'max_length': 64, 'min_length': 1, 'pattern': '^[A-Za-z0-9_.:-]{1,64}$'} + + +class Period(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + start: AwareDatetime + end: AwareDatetime + + +class ReportingStatusView(StrEnum): + summary = 'summary' + periods = 'periods' + revision = 'revision' -from adcp.types.generated_poc.media_buy.get_reporting_status_request import ( - DeliveryConfigId, - GetReportingStatusRequest, - Period, - ReportingStatusView, -) -# Explicit exports -__all__ = ["DeliveryConfigId", "GetReportingStatusRequest", "Period", "ReportingStatusView"] +class GetReportingStatusRequest(AdcpRequest, AdcpVersionEnvelope): + account: Annotated[ + canonical_account_ref.CanonicalAccountReference, + Field(description='Account whose caller-owned reporting status is queried.'), + ] + view: ReportingStatusView + media_buy_ids: Annotated[ + list[reporting_coverage.ReportingMediaBuyId] | None, + Field( + description='Optional summary/periods scope. Omit for every accessible media buy in the account.', + max_length=100, + min_length=1, + ), + ] = None + delivery_config_ids: Annotated[ + list[DeliveryConfigId] | None, + Field( + description='Optional summary/periods scope. Use to reconcile billing, analytics, and pacing independently. Omit for every active caller-owned configuration.', + max_length=16, + min_length=1, + ), + ] = None + feed_purposes: Annotated[ + list[reporting_delivery_offering.ReportingFeedPurpose] | None, + Field( + description='Optional summary/periods feed filter. The response echoes exact resolved configuration generations so this never creates an opaque aggregate.', + min_length=1, + ), + ] = None + period: Annotated[ + Period | None, + Field( + description="Half-open summary/periods horizon. Omit for the seller's documented operational default horizon; the response always echoes the evaluated scope." + ), + ] = None + health: Annotated[ + list[reporting_health.ReportingHealth] | None, + Field( + description='Periods-view result filter only; it never changes summary health.', + min_length=1, + ), + ] = None + finality: Annotated[list[reporting_finality.ReportingFinality] | None, Field(min_length=1)] = ( + None + ) + reporting_revision_id: Annotated[ + str | None, + Field( + description='Exact retained revision to resolve in revision view.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] = None + changes_after: Annotated[ + str | None, + Field( + description='Periods-view incremental-repair checkpoint previously returned as changes_checkpoint after fully consuming a response. Returns newly committed obligations, revisions, adjustments, materializations, consumer status statements, revision receipts, and adjustment receipts plus the current projection of each affected obligation. Omit for a full ledger read.', + max_length=2048, + min_length=1, + ), + ] = None + pagination: Annotated[ + pagination_request.PaginationRequest | None, + Field( + description='Periods or revision-view pagination. Cursors are bound to the authenticated caller, account, filters, and ledger snapshot.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/get_reporting_status_response.py b/src/adcp/types/domains/media_buy/get_reporting_status_response.py index 4c09ee673..c26f7bd31 100644 --- a/src/adcp/types/domains/media_buy/get_reporting_status_response.py +++ b/src/adcp/types/domains/media_buy/get_reporting_status_response.py @@ -1,30 +1,217 @@ -"""Types declared by the AdCP ``media_buy/get_reporting_status_response`` schema. +# generated by datamodel-codegen: +# filename: media_buy/get_reporting_status_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.get_reporting_status_response import +from adcp.types.base import AdcpResponse, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field, StrictBool, model_validator -from adcp.types.generated_poc.media_buy.get_reporting_status_response import ( - DeliveryConfigGeneration, - GetReportingStatusResponse, - ObligationCounts, - Scope, - Status, +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 +from ..core import ( + pagination_response, + reporting_adjustment, + reporting_adjustment_receipt, + reporting_consumer_status, + reporting_coverage, + reporting_delivery_offering, + reporting_materialization, + reporting_obligation, + reporting_receipt, + reporting_revision, + reporting_status_issue, ) +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import reporting_finality, reporting_health +from . import get_reporting_status_request + + +class ObligationCounts(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + total: Annotated[SchemaInt, Field(ge=0)] + waiting: Annotated[SchemaInt, Field(ge=0)] + healthy: Annotated[SchemaInt, Field(ge=0)] + delayed: Annotated[SchemaInt, Field(ge=0)] + action_required: Annotated[SchemaInt, Field(ge=0)] + complete: Annotated[SchemaInt, Field(ge=0)] + consumer_status_pending: Annotated[ + SchemaInt | None, + Field( + description="Obligations in this scope whose elapsed expected period has passed its consumer-status deadline — expected_at plus automated_recovery_window_seconds — without a current consumer status from the authenticated caller. A chain with any unsuperseded leaf counts as current whatever that leaf says; only an empty chain is pending. Because it counts obligations, a period the seller omitted entirely has no obligation and is not counted here — the buyer's independently derived denominator, not this field, remains the authority on omitted periods. It is a visibility count over the caller's own silence, never a health input: it MUST NOT change health, any other count, or seller-advertised reliability_statistics, and it overlaps the health counts rather than partitioning them. Required when the seller advertises consumer_status_task.", + ge=0, + ), + ] = None + + +class Status(StrEnum): + completed = 'completed' + failed = 'failed' + + +class DeliveryConfigGeneration(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + delivery_config_id: Annotated[str, Field(max_length=64, min_length=1)] + delivery_config_version: Annotated[SchemaInt, Field(ge=1)] + feed_purpose: reporting_delivery_offering.ReportingFeedPurpose + + +class Scope(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + period_start: AwareDatetime + period_end: AwareDatetime + scope_closed: Annotated[ + StrictBool, + Field(description='True only when no new obligation can enter this evaluated scope.'), + ] + media_buy_ids: list[reporting_coverage.ReportingMediaBuyId] | None = None + all_accessible_media_buys: Annotated[ + StrictBool, + Field( + description='True when media_buy_ids was omitted and the scope covers all caller-accessible account buys.' + ), + ] + delivery_config_generations: Annotated[ + list[DeliveryConfigGeneration], + Field( + description='Exact independently reconciled configuration generations in the denominator.' + ), + ] + feed_purposes: list[reporting_delivery_offering.ReportingFeedPurpose] + finality: list[reporting_finality.ReportingFinality] + ledger_retained_from: Annotated[ + AwareDatetime, + Field( + description='Earliest period boundary for which anti-entropy metadata is retained for every selected configuration generation.' + ), + ] + coverage_complete: Annotated[ + StrictBool, + Field( + description='Whether the requested horizon is fully inside retained ledger coverage. False means health cannot prove completeness for the whole requested horizon.' + ), + ] + + +class GetReportingStatusResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + view: get_reporting_status_request.ReportingStatusView | None = None + ledger_snapshot_id: Annotated[ + str | None, + Field( + description="Opaque identity of the seller's consistent reporting-ledger snapshot. Every page reached from one periods cursor MUST return the same value.", + max_length=255, + min_length=1, + ), + ] = None + ledger_as_of: Annotated[ + AwareDatetime | None, + Field( + description='Exclusive observation boundary for ledger_snapshot_id. Revisions committed later appear only in a later reconciliation.' + ), + ] = None + changes_checkpoint: Annotated[ + str | None, + Field( + description='Opaque durable incremental-repair checkpoint for this periods snapshot. Consumers persist it only after consuming every page, then send it as changes_after. It is bound to authenticated caller, account, and filters and MUST order every committed obligation, revision, adjustment, materialization, consumer status statement, revision receipt, and adjustment receipt without gaps.', + max_length=2048, + min_length=1, + ), + ] = None + account_id: Annotated[ + str | None, + Field(description='Resolved seller/storefront account identifier.', min_length=1), + ] = None + scope: Annotated[ + Scope | None, + Field( + description='Exact denominator evaluated for summary or periods health. complete is valid only when scope_closed is true.' + ), + ] = None + health: reporting_health.ReportingHealth | None = None + coverage: Annotated[ + reporting_coverage.ReportingCoverage | None, + Field( + description='Aggregated effective coverage for the exact selected scope. This remains independent of reporting health and finality so a fresh covered subset cannot look like complete campaign reporting.' + ), + ] = None + data_through: Annotated[ + AwareDatetime | None, + Field( + description='Conservative latest included event time across satisfied obligations in scope, or null when unavailable/unknown.' + ), + ] = None + next_expected_at: Annotated[ + AwareDatetime | None, + Field( + description='Next obligation due time for an open scope. In a complete summary (view: summary, health: complete), the nearest future period start, strictly after ledger_as_of, across all active committed configuration generations in scope.delivery_config_generations. Sellers MUST populate it when such a scheduled period exists outside the closed evaluated scope, and omit it when none exists. This complete-scope projection applies only to summary responses. It is derived from the configuration schedule and does not represent an open obligation in the evaluated scope; its presence does not indicate that the scope is still open. Projecting it MUST NOT create, expose, lease, count, or alter an obligation whose period has not closed, or change obligation_counts, scope, or coverage. Obligation expected_at remains period.end plus schedule.delivery_sla; get_media_buy_delivery.next_expected_at remains the next webhook notification time.' + ), + ] = None + obligation_counts: ObligationCounts | None = None + issues: list[reporting_status_issue.ReportingStatusIssue] | None = None + periods: list[reporting_obligation.ReportingObligation] | None = None + revisions: Annotated[ + list[reporting_revision.ReportingRevision] | None, + Field( + description='Revision ledger records on this page. Pagination is over the flat union of obligations, revisions, adjustments, materializations, consumer status statements, revision receipts, and adjustment receipts, avoiding unbounded nested history.' + ), + ] = None + adjustments: Annotated[ + list[reporting_adjustment.ReportingAdjustment] | None, + Field( + description='Immutable post-official accounting corrections on this page. They preserve the original invoice-to-revision binding and are included in flat ledger pagination.' + ), + ] = None + consumer_statuses: Annotated[ + list[reporting_consumer_status.ReportingConsumerStatus] | None, + Field( + description="Authenticated caller's append-only reporting status history on this page. Current leaves are identified by obligation current_consumer_status_id or, for a missing seller obligation, by the supersession chain over configuration generation, report definition, and period. No other consumer's status is disclosed." + ), + ] = None + adjustment_receipts: Annotated[ + list[reporting_adjustment_receipt.ReportingAdjustmentReceipt] | None, + Field( + description='Authenticated Reconciled Billing outcomes for adjustments on this page.' + ), + ] = None + pagination: pagination_response.PaginationResponse | None = None + revision: reporting_revision.ReportingRevision | None = None + materializations: list[reporting_materialization.ReportingMaterialization] | None = None + receipts: Annotated[ + list[reporting_receipt.ReportingReceipt] | None, + Field( + description="Authenticated caller's durable reconciliation receipts. Receipts from another consumer principal are never disclosed." + ), + ] = None + errors: list[error.Error] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + status: Status -# Explicit exports -__all__ = [ - "DeliveryConfigGeneration", - "GetReportingStatusResponse", - "ObligationCounts", - "Scope", - "Status", -] + @model_validator(mode='after') + def _require_schema_required_group(self) -> GetReportingStatusResponse: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('status',),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'GetReportingStatusResponse requires at least one of these field groups: status' + ) diff --git a/src/adcp/types/domains/media_buy/legacy_purchase_continuation_input.py b/src/adcp/types/domains/media_buy/legacy_purchase_continuation_input.py index 2289a8e7f..922139c42 100644 --- a/src/adcp/types/domains/media_buy/legacy_purchase_continuation_input.py +++ b/src/adcp/types/domains/media_buy/legacy_purchase_continuation_input.py @@ -1,22 +1,106 @@ -"""Types declared by the AdCP ``media_buy/legacy_purchase_continuation_input`` schema. +# generated by datamodel-codegen: +# filename: media_buy/legacy_purchase_continuation_input.json +# timestamp: 2026-10-04T00:53:27+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.legacy_purchase_continuation_input import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any +from uuid import UUID -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field, field_validator + +from ..core import account_ref + + +class SelectedProductId(ScalarStr): + __slots__ = () + _constraints = {'min_length': 1} + + +class AcceptedLoss(StrEnum): + feed_version_not_atomic = 'feed_version_not_atomic' + pricing_version_not_atomic = 'pricing_version_not_atomic' + mutation_idempotency_not_guaranteed = 'mutation_idempotency_not_guaranteed' + + +class CompatibilityPurchaseCoordinatorInput(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + idempotency_key: Annotated[ + UUID, + Field( + description='Replay identity for this logical coordinator operation. Exact retries resume the durable operation record instead of redeeming the continuation again.' + ), + ] + continuation_token: Annotated[ + str, + Field( + description='Opaque token returned by products_available.purchase_continuation.', + min_length=16, + ), + ] + account: Annotated[ + account_ref.AccountReference, + Field( + description='Account identity that must match the account bound into the continuation token.' + ), + ] + selected_product_ids: Annotated[ + list[SelectedProductId], + Field( + description='Non-empty subset of the product IDs bound into the continuation.', + min_length=1, + json_schema_extra={'uniqueItems': True}, + ), + ] + accepted_losses: Annotated[ + list[AcceptedLoss], + Field( + description='Exact loss set returned with the continuation. Missing, extra, or stale consent fails before mutation.', + min_length=2, + json_schema_extra={ + 'uniqueItems': True, + 'allOf': [ + {'contains': {'const': 'feed_version_not_atomic'}}, + {'contains': {'const': 'pricing_version_not_atomic'}}, + ], + }, + ), + ] + legacy_create_request: Annotated[ + dict[str, Any], + Field( + description='Proposed create_media_buy payload. Before mutation the coordinator validates this object against create-media-buy-request.json from source_adcp_version, requires explicit-package mode, and requires its package product IDs to equal selected_product_ids.', + min_length=1, + ), + ] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.media_buy.legacy_purchase_continuation_input import ( - AcceptedLoss, - CompatibilityPurchaseCoordinatorInput, - SelectedProductId, -) + @field_validator('selected_product_ids') + @classmethod + def _selected_product_ids_are_unique( + cls, values: list[SelectedProductId] + ) -> list[SelectedProductId]: + if len(values) != len(set(values)): + raise ValueError('selected_product_ids must contain unique items') + return values -# Explicit exports -__all__ = ["AcceptedLoss", "CompatibilityPurchaseCoordinatorInput", "SelectedProductId"] + @field_validator('accepted_losses') + @classmethod + def _accepted_losses_match_schema( + cls, values: list[AcceptedLoss] + ) -> list[AcceptedLoss]: + value_set = set(values) + if len(values) != len(value_set): + raise ValueError('accepted_losses must contain unique items') + required = { + AcceptedLoss.feed_version_not_atomic, + AcceptedLoss.pricing_version_not_atomic, + } + if not required.issubset(value_set): + raise ValueError('accepted_losses must include the required compatibility losses') + return values diff --git a/src/adcp/types/domains/media_buy/list_creative_formats_request.py b/src/adcp/types/domains/media_buy/list_creative_formats_request.py index c0c6af9d4..5b22fa3ab 100644 --- a/src/adcp/types/domains/media_buy/list_creative_formats_request.py +++ b/src/adcp/types/domains/media_buy/list_creative_formats_request.py @@ -1,20 +1,134 @@ -"""Types declared by the AdCP ``media_buy/list_creative_formats_request`` schema. +# generated by datamodel-codegen: +# filename: media_buy/list_creative_formats_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.list_creative_formats_request import +from adcp.types.base import AdcpRequest, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field, StrictBool + +from ..core import account_ref +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core import format_id, pagination_request +from ..core import property_id as property_id_1 +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import asset_content_type +from ..enums import disclosure_persistence as disclosure_persistence_1 +from ..enums import disclosure_position +from ..enums import wcag_level as wcag_level_1 -from adcp.types.generated_poc.media_buy.list_creative_formats_request import ( - ListCreativeFormatsRequest, -) -# Explicit exports -__all__ = ["ListCreativeFormatsRequest"] +class ListCreativeFormatsRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + format_ids: Annotated[ + list[format_id.FormatReferenceStructuredObject] | None, + Field( + deprecated=True, + description='Deprecated in AdCP 3.2; removed in AdCP 4.0. Return only these specific named-format IDs (for example, from a 3.x get_products response). Use canonical-format discovery in 4.0.', + min_length=1, + ), + ] = None + asset_types: Annotated[ + list[asset_content_type.AssetContentType] | None, + Field( + description="Filter to formats that include these asset types. For third-party tags, search for 'html' or 'javascript'. For published-post reference formats, search for 'published_post'. E.g., ['image', 'text'] returns formats with images and text, ['javascript'] returns formats accepting JavaScript tags.", + min_length=1, + ), + ] = None + max_width: Annotated[ + SchemaInt | None, + Field( + description='Maximum width in pixels (inclusive). Returns formats where ANY render has width <= this value. For multi-render formats, matches if at least one render fits.' + ), + ] = None + max_height: Annotated[ + SchemaInt | None, + Field( + description='Maximum height in pixels (inclusive). Returns formats where ANY render has height <= this value. For multi-render formats, matches if at least one render fits.' + ), + ] = None + min_width: Annotated[ + SchemaInt | None, + Field( + description='Minimum width in pixels (inclusive). Returns formats where ANY render has width >= this value.' + ), + ] = None + min_height: Annotated[ + SchemaInt | None, + Field( + description='Minimum height in pixels (inclusive). Returns formats where ANY render has height >= this value.' + ), + ] = None + is_responsive: Annotated[ + StrictBool | None, + Field( + description='Filter for responsive formats that adapt to container size. When true, returns formats without fixed dimensions.' + ), + ] = None + name_search: Annotated[ + str | None, Field(description='Search for formats by name (case-insensitive partial match)') + ] = None + publisher_domain: Annotated[ + str | None, + Field( + deprecated=True, + description="Deprecated compatibility filter for older 3.x callers. A compatibility implementation MAY project publisher-origin or community-catalog declarations obtained through the registry publisher lookup, but MUST NOT synthesize a publisher catalog from seller products. New callers use `GET /api/registry/publisher?domain=...` for publisher acceptance and `get_products` for this seller's deliverability. The pattern below is a syntactic floor, not an SSRF guard.", + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] = None + property_id: Annotated[ + property_id_1.PropertyId | None, + Field( + description="Filter to formats supported on the named property within the publisher's catalog. Resolves to a property in the publisher's `adagents.json` `properties[]`; the agent returns only `formats[]` entries whose `applies_to_property_ids` includes this property (or entries with no scope, which apply to all properties). Typically used in combination with `publisher_domain`." + ), + ] = None + wcag_level: Annotated[ + wcag_level_1.WcagLevel | None, + Field( + description='Filter to formats that meet at least this WCAG conformance level (A < AA < AAA)' + ), + ] = None + disclosure_positions: Annotated[ + list[disclosure_position.DisclosurePosition] | None, + Field( + description="Filter to formats that support all of these disclosure positions. When a format has disclosure_capabilities, match against those positions. Otherwise fall back to supported_disclosure_positions. Use to find formats compatible with a brief's compliance requirements.", + min_length=1, + ), + ] = None + disclosure_persistence: Annotated[ + list[disclosure_persistence_1.DisclosurePersistence] | None, + Field( + description='Filter to formats where each requested persistence mode is supported by at least one position in disclosure_capabilities. Different positions may satisfy different modes. Use to find formats compatible with jurisdiction-specific persistence requirements (e.g., continuous for EU AI Act).', + min_length=1, + ), + ] = None + output_format_ids: Annotated[ + list[format_id.FormatReferenceStructuredObject] | None, + Field( + description="Filter to formats whose output_format_ids includes any of these format IDs. Returns formats that can produce these outputs — inspect each result's input_format_ids to see what inputs they accept.", + min_length=1, + ), + ] = None + input_format_ids: Annotated[ + list[format_id.FormatReferenceStructuredObject] | None, + Field( + description="Filter to formats whose input_format_ids includes any of these format IDs. Returns formats that accept these creatives as input — inspect each result's output_format_ids to see what they can produce.", + min_length=1, + ), + ] = None + account: Annotated[ + account_ref.AccountReference | None, + Field( + deprecated=True, + description="**DEPRECATED with `list_creative_formats` in 3.2. Removed at 4.0.** Use `get_products` with `account`; each `Product.format_options[]` lists the formats this seller can deliver for that account. *Legacy 3.x behavior:* scopes the returned formats to this account. Sellers that keep account-specific format catalogs (including sandbox accounts) return that account's formats; sellers with a single catalog MAY ignore this field.", + ), + ] = None + pagination: pagination_request.PaginationRequest | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/list_creative_formats_response.py b/src/adcp/types/domains/media_buy/list_creative_formats_response.py index f089125e0..e38f6bfd3 100644 --- a/src/adcp/types/domains/media_buy/list_creative_formats_response.py +++ b/src/adcp/types/domains/media_buy/list_creative_formats_response.py @@ -1,22 +1,81 @@ -"""Types declared by the AdCP ``media_buy/list_creative_formats_response`` schema. +# generated by datamodel-codegen: +# filename: media_buy/list_creative_formats_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.list_creative_formats_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field, StrictBool + +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 +from ..core import format, pagination_response +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import creative_agent_capability + + +class Source(StrEnum): + publisher = 'publisher' + aao_mirror = 'aao_mirror' + agent_derived = 'agent_derived' + + +class CreativeAgent(AdCPBaseModel): + agent_url: Annotated[ + AnyUrl, + Field( + description="Base URL for the creative agent (e.g., 'https://reference.example.com', 'https://dco.example.com')." + ), + ] + agent_name: Annotated[ + str | None, Field(description='Human-readable name for the creative agent') + ] = None + capabilities: Annotated[ + list[creative_agent_capability.CreativeAgentCapability] | None, + Field(description='Capabilities this creative agent provides'), + ] = None -from adcp.types.generated_poc.media_buy.list_creative_formats_response import ( - CreativeAgent, - ListCreativeFormatsResponse, - Source, -) -# Explicit exports -__all__ = ["CreativeAgent", "ListCreativeFormatsResponse", "Source"] +class ListCreativeFormatsResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + formats: Annotated[ + list[format.Format], + Field( + deprecated=True, + description="Deprecated named-format definitions projected for older 3.x callers. This list is neither the publisher acceptance catalog nor the seller's canonical product deliverability contract.", + ), + ] + source: Annotated[ + Source | None, + Field( + deprecated=True, + description='Deprecated compatibility provenance. `publisher` means publisher-origin catalog; `aao_mirror` means community catalog; `agent_derived` is retained only to parse historical 3.x responses and MUST NOT be produced by a new 3.2 implementation because seller products are not publisher authority.', + ), + ] = None + creative_agents: Annotated[ + list[CreativeAgent] | None, + Field( + deprecated=True, + description="Deprecated recursive discovery projection retained for historical 3.x responses. New buyers query the registry's canonical creative capability index and confirm candidates with get_adcp_capabilities; they do not recursively walk agent-provided lists.", + ), + ] = None + errors: Annotated[ + list[error.Error] | None, + Field(description='Task-specific errors and warnings (e.g., format availability issues)'), + ] = None + pagination: pagination_response.PaginationResponse | None = None + sandbox: Annotated[ + StrictBool | None, + Field(description='When true, this response contains simulated data from sandbox mode.'), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/list_products_request.py b/src/adcp/types/domains/media_buy/list_products_request.py index 9d861dc59..6da7094a0 100644 --- a/src/adcp/types/domains/media_buy/list_products_request.py +++ b/src/adcp/types/domains/media_buy/list_products_request.py @@ -1,18 +1,70 @@ -"""Types declared by the AdCP ``media_buy/list_products_request`` schema. +# generated by datamodel-codegen: +# filename: media_buy/list_products_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.list_products_request import +from adcp.types.base import AdcpRequest, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field + +from ..core import brand_key, canonical_account_ref +from ..core import context as context_1 +from ..core import push_notification_config as push_notification_config_1 +from ..core.version_envelope import AdcpVersionEnvelope +from . import product_discovery_criteria, product_fields -from adcp.types.generated_poc.media_buy.list_products_request import ListProductsRequest -# Explicit exports -__all__ = ["ListProductsRequest"] +class ListProductsRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='forbid', + ) + idempotency_key: Annotated[ + str | None, + Field( + description='Optional replay key accepted uniformly on read calls.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] = None + context_id: Annotated[ + str | None, + Field( + description='MCP compatibility field: servers ignore this value; A2A uses transport-native Message/Task contextId.', + min_length=1, + ), + ] = None + context: context_1.ContextObject | None = None + governance_context: Annotated[str | None, Field(max_length=4096, min_length=1)] = None + push_notification_config: Annotated[ + push_notification_config_1.PushNotificationConfig | None, + Field( + description='Uniform per-call envelope field accepted for SDK compatibility. This does not register a wholesale feed subscription; durable product.* and wholesale_feed.bulk_change subscribers are registered through sync_accounts notification_configs.' + ), + ] = None + account: Annotated[ + canonical_account_ref.CanonicalAccountReference | None, + Field( + description='Account scope for pricing and availability. A natural-key account is the single brand source and MUST NOT be combined with top-level brand.' + ), + ] = None + brand: brand_key.BrandKey | None = None + criteria: product_discovery_criteria.ProductDiscoveryCriteria | None = None + fields: product_fields.ProductResponseFields | None = None + cursor: Annotated[str | None, Field(min_length=1)] = None + max_results: Annotated[SchemaInt | None, Field(ge=1, le=100)] = 25 + if_feed_version: Annotated[ + str | None, + Field( + description='Opaque feed version returned by a prior list_products response or wholesale product-feed webhook for the same cache scope and canonicalized selection. Used for repair and conditional reconciliation, not routine polling when webhooks are active.' + ), + ] = None + if_pricing_version: Annotated[ + str | None, + Field( + description='Opaque pricing version returned by a prior list_products response. Valid only with if_feed_version.' + ), + ] = None diff --git a/src/adcp/types/domains/media_buy/list_products_response.py b/src/adcp/types/domains/media_buy/list_products_response.py index 57e2e45a2..329429744 100644 --- a/src/adcp/types/domains/media_buy/list_products_response.py +++ b/src/adcp/types/domains/media_buy/list_products_response.py @@ -1,36 +1,138 @@ -"""Types declared by the AdCP ``media_buy/list_products_response`` schema. +# generated by datamodel-codegen: +# filename: media_buy/list_products_response.json +# timestamp: 2026-10-04T02:05:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.list_products_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal + +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field + +from ..core import canonical_product +from ..core import context as context_1 +from ..core import duration +from ..core import ext as ext_1 + + +class Outcome(StrEnum): + listed = 'listed' + unchanged = 'unchanged' + + +class CacheScope(StrEnum): + public = 'public' + account = 'account' + + +class Scope(StrEnum): + products = 'products' + pricing = 'pricing' + forecast = 'forecast' + wholesale_feed = 'wholesale_feed' + + +class IncompleteItem(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + scope: Scope + description: Annotated[str, Field(min_length=1)] + estimated_wait: duration.Duration | None = None + + +class IncompleteItem3(IncompleteItem): + pass + + +class ListProductsResponse1(AdcpResponse, AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + outcome: Annotated[ + Literal['listed'], + Field( + description='Whether this response carries a product page or confirms that the selected feed has not changed.' + ), + ] = 'listed' + products: list[canonical_product.CanonicalProduct] + next_cursor: Annotated[ + str | None, + Field( + description='Cursor for the next page. Omitted when this is the last page.', + min_length=1, + ), + ] = None + feed_version: Annotated[ + str, + Field( + description='Opaque version of the selected offer feed. For wholesale webhook consumers this is the same scope-keyed token carried as wholesale_feed_version on product.* and wholesale_feed.bulk_change notifications.' + ), + ] + pricing_version: Annotated[ + str | None, Field(description='Opaque version of the selected pricing layer.') + ] = None + cache_scope: CacheScope + incomplete: Annotated[ + list[IncompleteItem] | None, + Field( + description='Usable partial response with explicitly missing product-list scopes.', + min_length=1, + ), + ] = None + replayed: Literal[True] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class ListProductsResponse2(AdcpResponse, AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + outcome: Annotated[ + Literal['unchanged'], + Field( + description='Whether this response carries a product page or confirms that the selected feed has not changed.' + ), + ] = 'unchanged' + products: list[canonical_product.CanonicalProduct] | None = None + next_cursor: Annotated[ + str | None, + Field( + description='Cursor for the next page. Omitted when this is the last page.', + min_length=1, + ), + ] = None + feed_version: Annotated[ + str, + Field( + description='Opaque version of the selected offer feed. For wholesale webhook consumers this is the same scope-keyed token carried as wholesale_feed_version on product.* and wholesale_feed.bulk_change notifications.' + ), + ] + pricing_version: Annotated[ + str | None, Field(description='Opaque version of the selected pricing layer.') + ] = None + cache_scope: CacheScope + incomplete: Annotated[ + list[IncompleteItem3] | None, + Field( + description='Usable partial response with explicitly missing product-list scopes.', + min_length=1, + ), + ] = None + replayed: Literal[True] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.media_buy.list_products_response import ( - CacheScope, - IncompleteItem, - IncompleteItem3, - ListProductsResponse, - ListProductsResponse1, - ListProductsResponse2, - Outcome, - Scope, -) - -# Explicit exports -__all__ = [ - "CacheScope", - "IncompleteItem", - "IncompleteItem3", - "ListProductsResponse", - "ListProductsResponse1", - "ListProductsResponse2", - "Outcome", - "Scope", +ListProductsResponse = Annotated[ + ListProductsResponse1 | ListProductsResponse2, + Field( + description="Canonical product offers and continuation state. Every product carries product_id and name; other compact detail fields follow the request's fields selection. Legacy named-format identifiers are never returned. This response never contains proposals or proposal-lifecycle fields.", + discriminator='outcome', + title='List Products Response', + ), ] diff --git a/src/adcp/types/domains/media_buy/log_event_request.py b/src/adcp/types/domains/media_buy/log_event_request.py index 85c068cf5..0daa8bc3a 100644 --- a/src/adcp/types/domains/media_buy/log_event_request.py +++ b/src/adcp/types/domains/media_buy/log_event_request.py @@ -1,18 +1,45 @@ -"""Types declared by the AdCP ``media_buy/log_event_request`` schema. +# generated by datamodel-codegen: +# filename: media_buy/log_event_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.log_event_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field + +from ..core import context as context_1 +from ..core import event +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope -from adcp.types.generated_poc.media_buy.log_event_request import LogEventRequest -# Explicit exports -__all__ = ["LogEventRequest"] +class LogEventRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + event_source_id: Annotated[ + str, Field(description='Event source configured on the account via sync_event_sources') + ] + test_event_code: Annotated[ + str | None, + Field( + description="Test event code for validation without affecting production data. Events with this code appear in the platform's test events UI." + ), + ] = None + events: Annotated[ + list[event.Event], Field(description='Events to log', max_length=10000, min_length=1) + ] + idempotency_key: Annotated[ + str, + Field( + description='Client-generated unique key for this request. Prevents duplicate event logging on retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/log_event_response.py b/src/adcp/types/domains/media_buy/log_event_response.py index 0db907a07..f0e9fd5e6 100644 --- a/src/adcp/types/domains/media_buy/log_event_response.py +++ b/src/adcp/types/domains/media_buy/log_event_response.py @@ -1,23 +1,54 @@ -"""Types declared by the AdCP ``media_buy/log_event_response`` schema. +# generated by datamodel-codegen: +# filename: media_buy/log_event_response.json +# timestamp: preserved-by-post-generate-fixes -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.log_event_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, TypeAlias -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field + +from ..core.version_envelope import AdcpVersionEnvelope +from ..core import context as context_1 +from ..core import error as error_1 +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope + + +class PartialFailure(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + event_id: str + code: str + message: str + + +class LogEventResponse1(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + events_received: Annotated[int, Field(ge=0)] + events_processed: Annotated[int, Field(ge=0)] + partial_failures: list[PartialFailure] | None = None + warnings: list[str] | None = None + match_quality: Annotated[float, Field(ge=0, le=1)] | None = None + sandbox: bool | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class LogEventResponse2(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + errors: Annotated[list[error_1.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +LogEventResponse: TypeAlias = LogEventResponse1 | LogEventResponse2 -from adcp.types.generated_poc.media_buy.log_event_response import ( - LogEventResponse, - LogEventResponse1, - LogEventResponse2, - PartialFailure, -) -# Explicit exports -__all__ = ["LogEventResponse", "LogEventResponse1", "LogEventResponse2", "PartialFailure"] +__all__ = [ + 'LogEventResponse', + 'LogEventResponse1', + 'LogEventResponse2', + 'PartialFailure', +] diff --git a/src/adcp/types/domains/media_buy/media_buy_commitment_response.py b/src/adcp/types/domains/media_buy/media_buy_commitment_response.py index 3426798c8..fe7103178 100644 --- a/src/adcp/types/domains/media_buy/media_buy_commitment_response.py +++ b/src/adcp/types/domains/media_buy/media_buy_commitment_response.py @@ -1,38 +1,139 @@ -"""Types declared by the AdCP ``media_buy/media_buy_commitment_response`` schema. +# generated by datamodel-codegen: +# filename: media_buy/media_buy_commitment_response.json +# timestamp: 2026-10-03T14:21:15+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.media_buy_commitment_response import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AwareDatetime, ConfigDict, Field + +from ..core import canonical_media_buy_action +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 +from ..core.canonical_proposal import CanonicalProposal +from ..core.warning import Warning as Warning_1 +from ..enums import media_buy_status as media_buy_status_1 + + +class ProposalStatus(StrEnum): + draft = 'draft' + committed = 'committed' + accepted = 'accepted' + + +class PurchaseBinding(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + purchase_index: Annotated[SchemaInt, Field(ge=0)] + product_id: Annotated[str, Field(min_length=1)] + package_id: Annotated[str, Field(min_length=1)] + + +class Code(StrEnum): + inventory_shortfall_forecast = 'inventory_shortfall_forecast' + flight_change_creates_pacing_risk = 'flight_change_creates_pacing_risk' + fields_ignored_due_to_precedence = 'fields_ignored_due_to_precedence' + inventory_shortfall_forecast_1 = 'inventory_shortfall_forecast' + flight_change_creates_pacing_risk_1 = 'flight_change_creates_pacing_risk' + + +class MediaBuyCommitmentResponse2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + status: Literal['failed'] = 'failed' + errors: Annotated[list[error.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + replayed: Literal[True] | None = None + + +class MediaBuyCommitmentResponse3(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + status: Literal['submitted'] = 'submitted' + task_id: Annotated[str, Field(min_length=1)] + message: Annotated[str | None, Field(max_length=2000)] = None + errors: list[error.Error] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + replayed: Literal[True] | None = None + + +class Warning(Warning_1): + code: Annotated[ + Code | None, + Field( + description='Closed warning vocabulary for the negotiated AdCP release. A non-blocking condition observed while an operation still succeeded. New values are added through normal protocol releases; buyers validate against the negotiated release.', + title='Warning Code', + ), + ] = None + + +class AcceptedProposal(CanonicalProposal): + proposal_status: Annotated[ + Literal['accepted'], + Field( + description='draft is indicative and unreserved; committed has firm terms with inventory reserved until expires_at; accepted is the historical snapshot attached to a MediaBuy.', + title='Proposal Status', + ), + ] = 'accepted' + media_buy_id: Annotated[str, Field(min_length=1)] + accepted_at: Any + commercial_terms: Any + terms_digest: Any + + +class MediaBuyCommitmentResponse1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + status: Literal['completed'] = 'completed' + media_buy_id: Annotated[str, Field(min_length=1)] + name: Annotated[ + str | None, + Field( + description='Persisted human-readable MediaBuy name for trafficking UI display and operational communication. The seller MUST echo a buyer-supplied request name unchanged; when the seller seeded a new MediaBuy name from an already-valid proposal.name, it MUST return that value unchanged here. Existing named MediaBuys return the stored value on amendment or cancellation commitments. This operational metadata is outside accepted_proposal and is not covered by terms_digest. This display label is not an identifier or financial reference.', + max_length=255, + min_length=1, + pattern='\\S', + ), + ] = None + revision: Annotated[SchemaInt, Field(ge=1)] + media_buy_status: media_buy_status_1.MediaBuyStatus | None = None + confirmed_at: AwareDatetime | None = None + accepted_proposal: AcceptedProposal + purchase_bindings: Annotated[ + list[PurchaseBinding], + Field( + description='Execution identities assigned to the immutable purchases. purchase_index is the zero-based position in accepted_proposal.commercial_terms.purchases and disambiguates repeated product IDs.', + min_length=1, + ), + ] + available_actions: list[canonical_media_buy_action.CanonicalMediaBuyAction] + warnings: Annotated[ + list[Warning] | None, + Field( + description='Non-blocking observations about this completed commitment. The MediaBuy was still created or amended exactly as represented. Continuing conditions also appear as indicators on get_media_buys.', + min_length=1, + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + replayed: Literal[True] | None = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.media_buy.media_buy_commitment_response import ( - AcceptedProposal, - Code, - MediaBuyCommitmentResponse, - MediaBuyCommitmentResponse1, - MediaBuyCommitmentResponse2, - MediaBuyCommitmentResponse3, - ProposalStatus, - PurchaseBinding, - Warning, -) - -# Explicit exports -__all__ = [ - "AcceptedProposal", - "Code", - "MediaBuyCommitmentResponse", - "MediaBuyCommitmentResponse1", - "MediaBuyCommitmentResponse2", - "MediaBuyCommitmentResponse3", - "ProposalStatus", - "PurchaseBinding", - "Warning", +MediaBuyCommitmentResponse = Annotated[ + MediaBuyCommitmentResponse1 | MediaBuyCommitmentResponse2 | MediaBuyCommitmentResponse3, + Field( + description='Shared result for clean product purchase and proposal acceptance. A successful result returns the MediaBuy identity and the immutable accepted commercial snapshot without embedding creative or package graphs. Optional warnings report non-blocking observations at the commitment boundary; continuing conditions remain readable as indicators through get_media_buys.', + discriminator='status', + title='Media Buy Commitment Response', + ), ] diff --git a/src/adcp/types/domains/media_buy/media_buy_delivery_webhook_result.py b/src/adcp/types/domains/media_buy/media_buy_delivery_webhook_result.py index d25f298fc..5ac17133c 100644 --- a/src/adcp/types/domains/media_buy/media_buy_delivery_webhook_result.py +++ b/src/adcp/types/domains/media_buy/media_buy_delivery_webhook_result.py @@ -1,34 +1,220 @@ -"""Types declared by the AdCP ``media_buy/media_buy_delivery_webhook_result`` schema. +# generated by datamodel-codegen: +# filename: media_buy/media_buy_delivery_webhook_result.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.media_buy_delivery_webhook_result import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat + +from ..core import attribution_window as attribution_window_1 +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 +from ..core import package_delivery_metric_value +from ..core.delivery_metrics import DeliveryMetrics +from ..enums import delivery_status as delivery_status_1 +from ..enums import pricing_model as pricing_model_1 + + +class NotificationType(StrEnum): + scheduled = 'scheduled' + final = 'final' + delayed = 'delayed' + adjusted = 'adjusted' + window_update = 'window_update' + + +class ReportingPeriod(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + start: AwareDatetime + end: AwareDatetime + timezone: Annotated[ + str | None, + Field( + description="Reporting timezone applied to this report, as 'UTC' or an IANA timezone identifier. Same meaning as get_media_buy_delivery reporting_period.timezone. Sellers SHOULD return it whenever every reported media buy shares one reporting timezone.", + examples=['UTC', 'America/New_York'], + min_length=1, + ), + ] = None + + +class Status(StrEnum): + pending_creatives = 'pending_creatives' + pending_start = 'pending_start' + pending = 'pending' + active = 'active' + paused = 'paused' + completed = 'completed' + rejected = 'rejected' + canceled = 'canceled' + failed = 'failed' + reporting_delayed = 'reporting_delayed' + + +class Totals(DeliveryMetrics): + effective_rate: Annotated[StrictFloat | None, Field(ge=0.0)] = None + + +class ByPackageItem(DeliveryMetrics): + package_id: str | None = None + pacing_index: Annotated[ + StrictFloat | None, + Field( + description='Subordinate package pacing diagnostic. May be absent for seller-optimized packages without package-level pacing.', + ge=0.0, + ), + ] = None + pricing_model: pricing_model_1.PricingModel | None = None + rate: Annotated[StrictFloat | None, Field(ge=0.0)] = None + currency: Annotated[ + str | None, + Field( + description="ISO 4217 currency code for this package's spend and currency-denominated pricing rate. The rate for revenue_share is a dimensionless commission fraction, but attributed monetary values still use this currency. When the enclosing media_buy_deliveries[].currency is present, this value MUST equal it. For AdCP-authored buys both values MUST equal the media-buy currency. A different package currency is permitted only for a legacy or externally created mixed-currency buy whose row currency and row-level monetary totals are omitted. When both row and deprecated response-wide currency are absent, a package with monetary values MUST include this field. AdCP does not perform currency conversion.", + pattern='^[A-Z]{3}$', + ), + ] = None + delivery_status: Annotated[ + delivery_status_1.DeliveryStatus | None, + Field( + description="System-reported operational state of this package. Reflects actual delivery state independent of buyer pause control. 'not_delivering' means zero impressions were recorded for the entire reporting_period while the package was in-flight. Sellers SHOULD only report 'not_delivering' once the package's data is_final for the period — a provisional (is_final: false) zero may still be measurement catching up, not genuine non-delivery." + ), + ] = None + paused: StrictBool | None = None + is_final: StrictBool | None = None + finalized_at: AwareDatetime | None = None + measurement_window: Annotated[str | None, Field(max_length=50)] = None + supersedes_window: Annotated[str | None, Field(max_length=50)] = None + metric_values: Annotated[ + list[package_delivery_metric_value.PackageDeliveryMetricValue] | None, + Field( + description='Qualified standard delivery values for this package. Each entry is the delivered counterpart to a standard-scope package.committed_metrics or by_package[].missing_metrics row, using the same atomic key (scope, metric_id, qualifier). Sellers report one row per full qualifier set at package grain; when a metric appears here, its flat scalar counterpart on the package MUST be omitted to avoid two sources of truth. Vendor-scope values continue to use vendor_metric_values. Buyers reconcile rows directly and perform any compatible cross-package aggregation themselves.' + ), + ] = None + spend: Any + + +class MediaBuyDelivery(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + media_buy_id: Annotated[str, Field(description="Seller's media buy identifier.")] + currency: Annotated[ + str | None, + Field( + description="ISO 4217 denomination for monetary values in this media-buy delivery row, including totals, package spend, and currency-denominated package rates. Sellers SHOULD populate this field whenever all monetary values in the row share one currency. For AdCP-authored buys it MUST equal the media-buy currency, and every by_package[].currency MUST equal it. For a legacy or externally created mixed-currency buy, omit this field and all monetary or money-derived values from row totals; report those values only at package level with each package's own currency. AdCP does not perform currency conversion.", + pattern='^[A-Z]{3}$', + ), + ] = None + status: Annotated[ + Status, + Field( + description="Current media buy lifecycle or reporting status. This is distinct from the webhook envelope's top-level task status." + ), + ] + expected_availability: Annotated[ + AwareDatetime | None, + Field( + description='When delayed data is expected to be available. Present when status is reporting_delayed.' + ), + ] = None + is_adjusted: Annotated[ + StrictBool | None, + Field( + description='Indicates this row contains updated data for a previously reported period.' + ), + ] = None + is_final: Annotated[ + StrictBool | None, + Field(description="Whether this row's delivery data is final for the reporting period."), + ] = None + finalized_at: Annotated[ + AwareDatetime | None, + Field( + description='Timestamp when this row became final. Present only when is_final is true.' + ), + ] = None + pricing_model: pricing_model_1.PricingModel | None = None + pacing_index: Annotated[ + StrictFloat | None, + Field( + description='Aggregate media-buy delivery pace relative to the media-buy pacing plan. Authoritative for seller-optimized buys.', + ge=0.0, + ), + ] = None + totals: Totals + by_package: Annotated[list[ByPackageItem], Field(description='Metrics broken down by package.')] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.media_buy.media_buy_delivery_webhook_result import ( - ByPackageItem, - MediaBuyDelivery, - MediaBuyDeliveryWebhookResult, - NotificationType, - ReportingPeriod, - Status, - Totals, -) - -# Explicit exports -__all__ = [ - "ByPackageItem", - "MediaBuyDelivery", - "MediaBuyDeliveryWebhookResult", - "NotificationType", - "ReportingPeriod", - "Status", - "Totals", -] +class MediaBuyDeliveryWebhookResult(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + notification_type: Annotated[ + NotificationType, + Field( + description='Type of delivery-report notification: scheduled = regular periodic update, final = campaign completed, delayed = data not yet available, adjusted = corrected data for the same window, window_update = a wider measurement window supersedes a prior window.' + ), + ] + partial_data: Annotated[ + StrictBool | None, + Field( + description='Indicates if any media buys in this webhook have missing or delayed data.' + ), + ] = None + unavailable_count: Annotated[ + SchemaInt | None, + Field( + description='Number of media buys with reporting_delayed or failed status when partial_data is true.', + ge=0, + ), + ] = None + sequence_number: Annotated[ + SchemaInt | None, + Field( + description='Sequential notification number for this reporting webhook stream.', ge=1 + ), + ] = None + next_expected_at: Annotated[ + AwareDatetime | None, + Field( + description='ISO 8601 timestamp for the next expected notification. Omitted on final notifications.' + ), + ] = None + reporting_period: Annotated[ + ReportingPeriod, + Field( + description="Period covered by the delivery report. start and end are instants on the reporting timezone's period boundaries (the products' reporting_capabilities.timezone, echoed in timezone). They fall on UTC midnight only when that timezone is UTC." + ), + ] + currency: Annotated[ + str | None, + Field( + deprecated=True, + description='Deprecated in AdCP 3.2 and removed in AdCP 4.0. Optional legacy report-wide ISO 4217 currency code. It may be used only when every monetary value in the report has that denomination. A report can contain media buys with different currencies, so buyers MUST NOT interpret this field as an aggregation currency or evidence of currency conversion. Prefer media_buy_deliveries[].currency when present and package-level currency otherwise.', + pattern='^[A-Z]{3}$', + ), + ] = None + attribution_window: Annotated[ + attribution_window_1.AttributionWindow | None, + Field( + description='Attribution methodology and lookback windows used for conversion metrics in this report.' + ), + ] = None + media_buy_deliveries: Annotated[ + list[MediaBuyDelivery], + Field( + description='Delivery rows for one or more media buys included in this notification.' + ), + ] + errors: Annotated[ + list[error.Error] | None, Field(description='Task-specific delivery errors or warnings.') + ] = None + sandbox: StrictBool | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/outcome_target.py b/src/adcp/types/domains/media_buy/outcome_target.py index 5d37a2877..fdb0953f2 100644 --- a/src/adcp/types/domains/media_buy/outcome_target.py +++ b/src/adcp/types/domains/media_buy/outcome_target.py @@ -1,18 +1,81 @@ -"""Types declared by the AdCP ``media_buy/outcome_target`` schema. +# generated by datamodel-codegen: +# filename: media_buy/outcome_target.json +# timestamp: 2026-10-04T01:14:11+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.outcome_target import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictFloat, model_validator + +from ..core import outcome_target_cost_per +from ..enums import event_type as event_type_1 +from ..enums import forecastable_metric + + +class Goal(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['metric'] = 'metric' + metric: Annotated[ + forecastable_metric.ForecastableMetric, + Field( + description='Delivery metric to plan for, in the same vocabulary forecast points report.' + ), + ] + + +class Goal1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['event'] = 'event' + event_type: Annotated[ + event_type_1.EventType, + Field( + description='Conversion event to plan for, in the same vocabulary forecast points report.' + ), + ] + custom_event_name: Annotated[ + str | None, + Field( + description="Required when event_type is 'custom'. Platform-specific name for the custom event.", + min_length=1, + ), + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.media_buy.outcome_target import Goal, Goal1, OutcomeTarget +class OutcomeTarget(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + goal: Annotated[ + Goal | Goal1, + Field( + description='The outcome to plan against: a seller-tracked delivery metric or an advertiser conversion event.', + discriminator='kind', + ), + ] + volume: Annotated[ + StrictFloat | None, + Field( + description="Desired total volume of the goal's metric or event across the planned flight. Alone, the seller solves for budget and answers with total_budget_guidance and a forecast. With cost_per, the seller plans toward the volume at the cost: under a cap it SHOULD keep the buyer's amount and forecast the lower volume it can deliver, unless no volume can be planned at that amount; under a target the ask is plannable when the seller can forecast the volume around it.", + gt=0.0, + ), + ] = None + cost_per: outcome_target_cost_per.OutcomeTargetCostPer | None = None -# Explicit exports -__all__ = ["Goal", "Goal1", "OutcomeTarget"] + @model_validator(mode='after') + def _require_schema_required_group(self) -> OutcomeTarget: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('volume',), ('cost_per',),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'OutcomeTarget requires at least one of these field groups: volume | cost_per' + ) diff --git a/src/adcp/types/domains/media_buy/package_control.py b/src/adcp/types/domains/media_buy/package_control.py index fe4759488..f33cbb6df 100644 --- a/src/adcp/types/domains/media_buy/package_control.py +++ b/src/adcp/types/domains/media_buy/package_control.py @@ -1,18 +1,104 @@ -"""Types declared by the AdCP ``media_buy/package_control`` schema. +# generated by datamodel-codegen: +# filename: media_buy/package_control.json +# timestamp: 2026-10-04T01:14:11+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.package_control import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field, StrictBool, StrictFloat, model_validator + +from ..core import bidding_policy, canonical_optimization_goal, keyword_target, targeting_input +from ..enums import pacing as pacing_1 + + +class CatalogId(ScalarStr): + __slots__ = () + _constraints = {'min_length': 1} -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.media_buy.package_control import CatalogId, PackageControl +class PackageControl(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + package_id: Annotated[str, Field(min_length=1)] + budget: Annotated[ + StrictFloat | None, + Field( + description="Replace this package's hard lifetime spend cap. A number in a resulting seller-optimized buy requires advertised media_buy.features.seller_optimized_package_budgets; otherwise rejected with UNSUPPORTED_FEATURE before any over-subscription validation.", + ge=0.0, + ), + ] = None + daily_budget_cap: Annotated[ + StrictFloat | None, + Field( + description="Replace this package's subordinate hard daily cap; null removes it. Numeric changes apply immediately with package spend already incurred in the shared current cap day counted.", + ge=0.0, + ), + ] = None + min_spend_target: Annotated[ + StrictFloat | None, + Field( + description="Replace this package's soft minimum-spend target. A number is valid only for seller-optimized allocation and requires advertised media_buy.features.seller_optimized_min_spend_targets; otherwise rejected with UNSUPPORTED_FEATURE before any over-subscription validation.", + ge=0.0, + ), + ] = None + impressions: Annotated[StrictFloat | None, Field(ge=0.0)] = None + pacing: Annotated[ + pacing_1.Pacing | None, + Field( + description='Replace package pacing, subordinate to media-buy pacing. In a resulting seller-optimized buy, pacing that differs from the media-buy pacing requires advertised media_buy.features.seller_optimized_package_pacing; otherwise rejected with UNSUPPORTED_FEATURE.' + ), + ] = None + bidding: bidding_policy.BiddingPolicy | None = None + paused: StrictBool | None = None + canceled: Annotated[ + Literal[True] | None, + Field( + description='Exercise an already-accepted unilateral package cancellation right. If seller agreement is required, refine the accepted proposal with change_kind amendment; cancellation proposals terminate the whole MediaBuy.' + ), + ] = None + cancellation_reason: Annotated[str | None, Field(max_length=500, min_length=1)] = None + targeting_overlay: Annotated[ + targeting_input.TargetingOverlayInput | None, + Field( + description='Per-dimension targeting patch. Omission preserves the current dimension, a non-null value replaces it, and null clears it. The resulting effective targeting must remain within the accepted commercial envelope and is read back without null dimensions.' + ), + ] = None + catalog_ids: Annotated[ + list[CatalogId] | None, + Field( + description="Replace the package's promoted catalogs with references previously managed through sync_catalogs.", + min_length=1, + ), + ] = None + keyword_targets_add: Annotated[ + list[keyword_target.KeywordTarget] | None, Field(min_length=1) + ] = None + keyword_targets_remove: Annotated[ + list[keyword_target.KeywordTarget] | None, Field(min_length=1) + ] = None + negative_keywords_add: Annotated[ + list[keyword_target.KeywordTarget] | None, Field(min_length=1) + ] = None + negative_keywords_remove: Annotated[ + list[keyword_target.KeywordTarget] | None, Field(min_length=1) + ] = None + optimization_goals: Annotated[ + list[canonical_optimization_goal.CanonicalOptimizationGoal] | None, Field(min_length=1) + ] = None -# Explicit exports -__all__ = ["CatalogId", "PackageControl"] + @model_validator(mode='after') + def _require_schema_required_group(self) -> PackageControl: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('budget',), ('daily_budget_cap',), ('min_spend_target',), ('impressions',), ('pacing',), ('bidding',), ('paused',), ('canceled',), ('targeting_overlay',), ('catalog_ids',), ('keyword_targets_add',), ('keyword_targets_remove',), ('negative_keywords_add',), ('negative_keywords_remove',), ('optimization_goals',),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'PackageControl requires at least one of these field groups: budget | daily_budget_cap | min_spend_target | impressions | pacing | bidding | paused | canceled | targeting_overlay | catalog_ids | keyword_targets_add | keyword_targets_remove | negative_keywords_add | negative_keywords_remove | optimization_goals' + ) diff --git a/src/adcp/types/domains/media_buy/package_request.py b/src/adcp/types/domains/media_buy/package_request.py index 6ccfeea6d..2882032e5 100644 --- a/src/adcp/types/domains/media_buy/package_request.py +++ b/src/adcp/types/domains/media_buy/package_request.py @@ -1,30 +1,298 @@ -"""Types declared by the AdCP ``media_buy/package_request`` schema. +# generated by datamodel-codegen: +# filename: media_buy/package_request.json +# timestamp: 2026-10-05T13:49:50+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.package_request import +from collections.abc import Sequence +from typing import Annotated, Any, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat, model_validator -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import audience_evidence_pin +from ..core import audience_evidence_requirements as audience_evidence_requirements_1 +from ..core import bidding_policy, brand_ref, catalog +from ..core import context as context_1 +from ..core import creative_asset, creative_assignment, duration +from ..core import ext as ext_1 +from ..core import format_id, format_option_ref +from ..core import measurement_terms as measurement_terms_1 +from ..core import optimization_goal, performance_standard, targeting_input, vendor_metric_id +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import attribution_methodology as attribution_methodology_1 +from ..enums import available_metric +from ..enums import completion_source as completion_source_1 +from ..enums import lift_dimension as lift_dimension_1 +from ..enums import pacing as pacing_1 +from ..enums import viewability_standard as viewability_standard_1 + + +class Qualifier(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + viewability_standard: viewability_standard_1.ViewabilityStandard | None = None + completion_source: completion_source_1.CompletionSource | None = None + attribution_methodology: attribution_methodology_1.AttributionMethodology | None = None + attribution_window: duration.Duration | None = None + lift_dimension: lift_dimension_1.LiftDimension | None = None + + +class CommittedMetrics1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + scope: Annotated[ + Literal['standard'], + Field(description='Standard metric from the closed `available-metric.json` enum.'), + ] = 'standard' + metric_id: Annotated[ + available_metric.AvailableMetric, + Field( + description="Identifier for the standard metric. MUST be present in the product's `reporting_capabilities.available_metrics`." + ), + ] + qualifier: Annotated[ + Qualifier | None, + Field( + description='Disambiguator — same shape as on the response-side `committed_metrics`. Required when the buyer wants to pin a specific measurement path: `viewability_standard` for MRC vs GroupM viewability; `completion_source` for seller- vs vendor-attested `completion_rate`; `attribution_methodology` for how attribution was computed (deterministic_purchase, probabilistic, panel_based, modeled); `attribution_window` for the time window over which outcomes are attributed. See response-side description for full semantics.' + ), + ] = None -from adcp.types.generated_poc.media_buy.package_request import ( - CommittedMetrics, - CommittedMetrics1, - CommittedMetrics2, - PackageRequest, - Qualifier, -) - -# Explicit exports -__all__ = [ - "CommittedMetrics", - "CommittedMetrics1", - "CommittedMetrics2", - "PackageRequest", - "Qualifier", + +class CommittedMetrics2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + scope: Annotated[ + Literal['vendor'], + Field(description='Vendor-defined metric, identified by the tuple `(vendor, metric_id)`.'), + ] = 'vendor' + vendor: Annotated[ + brand_ref.BrandReference, + Field( + description="Vendor that defines and computes this metric. The vendor's `brand.json` `agents[type='measurement']` is the canonical anchor." + ), + ] + metric_id: Annotated[ + vendor_metric_id.VendorMetricId, + Field( + description="Identifier for the metric within the vendor's vocabulary. MUST be present in the product's `reporting_capabilities.vendor_metrics` for the same vendor." + ), + ] + methodology_version: Annotated[ + str | None, + Field( + description="Optional buyer-proposed pin of the vendor's `measurement.metrics[].methodology_version`. The seller accepts by echoing it on the confirmed package's `committed_metrics` entry, normalizes to the version it can actually deliver, or rejects with `TERMS_REJECTED`. Opaque string — equality comparison only." + ), + ] = None + qualifier: Annotated[ + Qualifier | None, + Field( + description='Optional disambiguator for vendor metrics committed under more than one methodology or window — same closed key set as standard-scope entries.' + ), + ] = None + + +CommittedMetrics = Annotated[ + CommittedMetrics1 | CommittedMetrics2, + Field(discriminator='scope'), ] + + +class PackageRequest(AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + product_id: Annotated[ + str, + Field( + description="Opaque configured product ID returned by get_products. Selecting it accepts the product's disclosed targeting_resolution, pricing, forecast assumptions, and terms. Sellers MUST echo this value on every response package object that represents this requested package." + ), + ] + format_ids: Annotated[ + list[format_id.FormatReferenceStructuredObject] | None, + Field( + deprecated=True, + description='Deprecated in AdCP 3.2; removed in AdCP 4.0. Legacy named-format selector retained for older 3.x peers. New buyers MUST NOT emit this field. Sellers MUST normalize every entry through the canonical mapping path before product satisfaction checks; an entry that cannot be normalized is rejected with `UNSUPPORTED_FEATURE` before any equivalence check. When this field coexists with `format_option_refs` or `format_kind` plus `params`, sellers MUST compare the product option sets selected by each resolved route. Legacy parameter compatibility follows the asymmetric v2-narrows-v1 relation defined by canonical formats, not raw object equality. Different format shapes, selected option sets, or incompatible dimensions are rejected with `CONFLICTING_SELECTORS`; sellers MUST NOT silently ignore the legacy projection. Equivalent dual emission remains valid during the 3.x compatibility window. If omitted and no canonical selector is present, all formats supported by the product are active.', + min_length=1, + ), + ] = None + format_option_refs: Annotated[ + list[format_option_ref.FormatOptionReference] | None, + Field( + description='Canonical 3.2 format-option selector. Each reference matches one target product `format_options[]` entry. Publisher-backed options match `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match `{ scope: "product", format_option_id }`. Sellers reject unresolved options with `UNSUPPORTED_FEATURE` and a field path to the failing entry before comparing co-present routes. New buyers MUST use this route by itself and MUST NOT dual-emit either a direct canonical selector or deprecated `format_ids`. Receivers handling older 3.x multi-route requests MUST resolve every present route, require each route to select the same product option set, and reject disagreement with `CONFLICTING_SELECTORS` before treating `format_option_refs` as authoritative.', + min_length=1, + ), + ] = None + format_kind: Annotated[ + str | None, + Field( + description='Canonical 3.2 direct selector. Names the canonical format shape this package targets when the buyer is not selecting a published `format_option_ref`. Pair with `params` for dimensions, duration, codecs, or other constraints. New buyers MUST NOT combine this route with `format_option_refs` or deprecated `format_ids`. Receivers handling older 3.x multi-route requests MUST equivalence-check every present route before applying precedence and reject disagreement with `CONFLICTING_SELECTORS`. Product satisfaction is directional: broad `{ format_kind: "image" }` does not satisfy a fixed-size product declaration.' + ), + ] = None + params: Annotated[ + dict[str, Any] | None, + Field( + description="Parameters for the direct canonical selector in `format_kind`. Shape follows the selected canonical's parameter vocabulary: dimensions (`width`, `height`, `sizes`), duration (`duration_ms_exact`, `duration_ms_range`), codecs, asset-source and slot narrowing, or other canonical-specific constraints. Requires `format_kind`. For fixed-size image selectors, `width` and `height` MUST co-occur; a selector containing only one dimension is schema-invalid. New buyers omit `params` when selecting by `format_option_refs` or `format_ids`; older multi-route requests are accepted only when every route selects the same product option set." + ), + ] = None + budget: Annotated[ + StrictFloat | None, + Field( + description="Hard lifetime spend cap for this package in the media buy's currency. Required in fixed allocation mode. Optional in seller-optimized mode; when omitted, the package is bounded by the shared total_budget and any other package constraints. In seller-optimized mode this is a ceiling, not a reserved or current allocation, and requires advertised media_buy.features.seller_optimized_package_budgets; otherwise rejected with UNSUPPORTED_FEATURE before any over-subscription validation.", + ge=0.0, + ), + ] = None + min_spend_target: Annotated[ + StrictFloat | None, + Field( + description="Soft lifetime spend target for this package in the media buy's currency. Only valid with seller-optimized budget allocation. Requires advertised media_buy.features.seller_optimized_min_spend_targets; otherwise rejected with UNSUPPORTED_FEATURE before any over-subscription validation. The seller SHOULD attempt to deliver at least this amount before allocating incremental spend elsewhere, but inventory, policy, optimization targets, or other delivery constraints may prevent it. This is not a billing guarantee. Must not exceed the package budget when both are present; a seller advertising both seller_optimized_min_spend_targets and seller_optimized_package_budgets MUST reject a violation with `INVALID_REQUEST`, while a seller missing either capability rejects the undeclared control with UNSUPPORTED_FEATURE before any over-subscription validation.", + ge=0.0, + ), + ] = None + pacing: Annotated[ + pacing_1.Pacing | None, + Field( + description='Package pacing, subordinate to media-buy pacing. In seller-optimized mode, pacing that differs from the media-buy pacing requires advertised media_buy.features.seller_optimized_package_pacing; otherwise rejected with UNSUPPORTED_FEATURE.' + ), + ] = None + pricing_option_id: Annotated[ + str, + Field( + description="ID of the selected pricing option from the product's pricing_options array" + ), + ] + bid_price: Annotated[ + StrictFloat | None, + Field( + deprecated=True, + description='DEPRECATED in 3.2 and removed in the next major. Use bidding.bid_amount or bidding.max_bid. Legacy normalization: selected pricing_option.max_bid=true maps to bidding.max_bid; otherwise maps to bidding.bid_amount. A package MUST NOT supply both representations.', + ge=0.0, + ), + ] = None + bidding: Annotated[ + bidding_policy.BiddingPolicy | None, + Field( + description='Package-authored bidding policy. This complete block replaces, rather than field-merges with, any media-buy bidding policy for this package. `{automatic:true}` explicitly overrides a media-buy policy with provider automatic bidding; omission inherits the complete media-buy block. Monetary fields use the media-buy currency, while the selected pricing option supplies only the auction unit and MUST declare that same currency. Sellers MUST reject a new bidding block combined with legacy bid_price or legacy monetary optimization-goal targets on the same effective package with AMBIGUOUS_BIDDING_POLICY.' + ), + ] = None + impressions: Annotated[ + StrictFloat | None, Field(description='Impression goal for this package', ge=0.0) + ] = None + daily_budget_cap: Annotated[ + StrictFloat | None, + Field( + description="Optional hard package daily spend ceiling in the media-buy currency. It is subordinate, not a reserved allocation; package caps need not sum to the aggregate cap. Uses the media buy's cap timezone. Requires advertised package budget-capping scope; otherwise rejected with UNSUPPORTED_FEATURE.", + ge=0.0, + ), + ] = None + start_time: Annotated[ + AwareDatetime | None, + Field( + description="Flight start date/time for this package in ISO 8601 format. When omitted, the package inherits the media buy's start_time. Must fall within the media buy's date range." + ), + ] = None + end_time: Annotated[ + AwareDatetime | None, + Field( + description="Flight end date/time for this package in ISO 8601 format. When omitted, the package inherits the media buy's end_time. Must fall within the media buy's date range." + ), + ] = None + paused: Annotated[ + StrictBool | None, + Field( + description='Whether this package should be created in a paused state. Paused packages do not deliver impressions. Defaults to false.' + ), + ] = False + catalogs: Annotated[ + list[catalog.Catalog] | None, + Field( + description='Catalogs this package promotes. Each catalog MUST have a distinct type (e.g., one product catalog, one store catalog). This constraint is enforced at the application level — sellers MUST reject requests containing multiple catalogs of the same type with a validation_error. Makes the package catalog-driven: one budget envelope, platform optimizes across items.' + ), + ] = None + optimization_goals: Annotated[ + list[optimization_goal.OptimizationGoal] | None, + Field( + description='Optimization targets for this package. The seller optimizes delivery toward these goals in priority order. Common pattern: event goals (purchase, install) as primary targets at priority 1; metric goals (clicks, views) as secondary proxy signals at priority 2+.', + min_length=1, + ), + ] = None + targeting_overlay: Annotated[ + targeting_input.TargetingOverlayInput | None, + Field( + description="Optional package-specific targeting input with three states per dimension. Omission inherits targeting already bound to the configured product, a non-null value replaces that dimension, and null explicitly suppresses the configured/product default for that dimension. Null cannot remove inherent product scope: sellers reject a clear the product cannot execute rather than silently retaining the default. Non-null fields MUST be declared in the product's overlay_support unless they were already accepted during discovery. The one fixed-inventory restatement exception is placement_selection equal to the product's complete, explicitly enumerated mode: included placement set: that set is an inherent exact match across discovery, create, and update and does not require overlay_support.placement_selection; partial selection still requires a selectable product. Opaque property_list and collection_list references have no equivalent exception because their membership can change independently and cannot be proven equal from the product wire representation. Package readback echoes the complete effective targeting without null dimensions. A supported value with no current inventory returns PRODUCT_UNAVAILABLE rather than a silent substitute or reprice." + ), + ] = None + audience_evidence_requirements: Annotated[ + audience_evidence_requirements_1.AudienceEvidenceRequirements | None, + Field( + description='Buyer policy that the selected product and constructed package MUST satisfy using product audience evidence. This remains planning and suitability evidence, not a targeting instruction. Sellers MUST reject an unsatisfied required policy rather than silently drop it, and a confirmed package MUST include every evidence snapshot used to satisfy this policy in audience_evidence_selections with decision_use package_construction.' + ), + ] = None + audience_evidence_pins: Annotated[ + list[audience_evidence_pin.AudienceEvidencePin] | None, + Field( + description="Exact immutable evidence snapshots selected by the buyer during discovery. The seller MUST match evidence_id, snapshot_id, version, and content_digest against one published snapshot and MUST reject catalog mutation, snapshot reuse, missing snapshots, or substitutions. Every accepted pin MUST be echoed in the confirmed package's audience_evidence_selections with decision_use package_construction.", + min_length=1, + ), + ] = None + measurement_terms: Annotated[ + measurement_terms_1.MeasurementTerms | None, + Field( + description="Buyer's proposed billing measurement and makegood terms. Overrides product defaults. Seller accepts (echoed on confirmed package), rejects with TERMS_REJECTED, or adjusts. When absent, product's measurement_terms apply." + ), + ] = None + performance_standards: Annotated[ + list[performance_standard.PerformanceStandard] | None, + Field( + description="Buyer's proposed performance standards for this package. Overrides product defaults. Seller accepts, rejects with TERMS_REJECTED, or adjusts. When absent, product's performance_standards apply.", + min_length=1, + ), + ] = None + committed_metrics: Annotated[ + list[CommittedMetrics] | None, + Field( + description="Buyer's proposed reporting contract for this package — the metrics the buyer wants the seller to commit to populating in delivery reports. Same negotiation pattern as `measurement_terms` and `performance_standards`: seller accepts (echoes on confirmed package with `committed_at` stamped), rejects with `TERMS_REJECTED` (with explanation of which entries were unworkable), or normalizes (echoes a different but compatible list — buyer can accept by retrying with the normalized terms). When absent, the seller decides what to commit based on the product's `available_metrics` and the buyer's `required_metrics` filter on `get_products`. Each entry uses an explicit `scope` discriminator (`standard` or `vendor`) and identifies the metric — request-side entries do NOT carry `committed_at`; that timestamp is stamped by the seller on accept. Constraints on what the buyer MAY propose: each `scope: standard` entry's `metric_id` MUST be in the product's `available_metrics`, and each `scope: vendor` entry's `(vendor, metric_id)` MUST appear in the product's `vendor_metrics` — sellers SHOULD reject with `TERMS_REJECTED` and reference the offending entry when the proposal exceeds product capability.", + min_length=1, + ), + ] = None + creative_assignments: Annotated[ + list[creative_assignment.CreativeAssignment] | None, + Field( + description='Assign existing library creatives to this package with optional rotation, grouping, weights, and placement targeting. rotation_mode is package-scoped: omission resolves to weighted, and every assignment MUST resolve to the same effective mode. In sequential mode, sequence_position MUST be unique within each package-local group. Sellers reject conflicts with VALIDATION_ERROR before creating the package.', + min_length=1, + ), + ] = None + creatives: Annotated[ + Sequence[creative_asset.CreativeAsset] | None, + Field( + description="Upload creative assets inline and assign to this package. Native localization is not accepted on this path; use sync_creatives before assigning the library creative. When the seller also advertises creative.has_creative_library: true, these creatives enter the seller's creative library and can be reused by creative_id while retained; inline-only sellers may store them as package-scoped assets. Use creative_assignments instead for existing library creatives.", + max_length=100, + min_length=1, + ), + ] = None + agency_estimate_number: Annotated[ + str | None, + Field( + description='Agency estimate or authorization number for this package. Overrides the media buy-level estimate number when different packages correspond to different agency estimates (e.g., different stations or flights within the same buy).', + max_length=100, + ), + ] = None + context: Annotated[ + context_1.ContextObject | None, + Field( + description='Opaque package-level correlation data echoed unchanged in the package response, webhooks, and read surfaces. Buyers targeting mixed seller populations SHOULD include a per-package correlation value here, commonly context_1.buyer_ref, so responses from legacy sellers that do not echo product_id can still be mapped back to the requested product or line item. Do not use deprecated top-level buyer_ref for v3 correlation.' + ), + ] = None + ext: ext_1.ExtensionObject | None = None + + @model_validator(mode='after') + def _validate_format_params(self) -> PackageRequest: + if self.params is not None and self.format_kind is None: + raise ValueError('params requires format_kind') + if self.params is not None and self.format_kind == 'image': + if ('width' in self.params) != ('height' in self.params): + raise ValueError('image params width and height must co-occur') + return self diff --git a/src/adcp/types/domains/media_buy/package_update.py b/src/adcp/types/domains/media_buy/package_update.py index 81b91c3d9..4146ac47c 100644 --- a/src/adcp/types/domains/media_buy/package_update.py +++ b/src/adcp/types/domains/media_buy/package_update.py @@ -1,30 +1,197 @@ -"""Types declared by the AdCP ``media_buy/package_update`` schema. +# generated by datamodel-codegen: +# filename: media_buy/package_update.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.package_update import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat + +from ..core import bidding_policy, catalog +from ..core import context as context_1 +from ..core import creative_asset, creative_assignment +from ..core import ext as ext_1 +from ..core import optimization_goal, targeting_input +from ..enums import match_type as match_type_1 +from ..enums import pacing as pacing_1 + + +class KeywordTargetsAddItem(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + keyword: Annotated[str, Field(description='The keyword to target', min_length=1)] + match_type: match_type_1.MatchType + bid_price: Annotated[ + StrictFloat | None, + Field( + description="Per-keyword bid price. Inherits currency and max_bid interpretation from the package's pricing option.", + ge=0.0, + ), + ] = None + + +class KeywordTargetsRemoveItem(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + keyword: Annotated[str, Field(description='The keyword to stop targeting', min_length=1)] + match_type: match_type_1.MatchType + + +class NegativeKeywordsAddItem(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + keyword: Annotated[str, Field(description='The keyword to exclude', min_length=1)] + match_type: match_type_1.MatchType + + +class NegativeKeywordsRemoveItem(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + keyword: Annotated[str, Field(description='The keyword to stop excluding', min_length=1)] + match_type: match_type_1.MatchType -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.media_buy.package_update import ( - KeywordTargetsAddItem, - KeywordTargetsRemoveItem, - NegativeKeywordsAddItem, - NegativeKeywordsRemoveItem, - PackageUpdate, -) - -# Explicit exports -__all__ = [ - "KeywordTargetsAddItem", - "KeywordTargetsRemoveItem", - "NegativeKeywordsAddItem", - "NegativeKeywordsRemoveItem", - "PackageUpdate", -] +class PackageUpdate(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + package_id: Annotated[str, Field(description="Seller's ID of package to update")] + budget: Annotated[ + StrictFloat | None, + Field( + description='Updated hard spend cap for this package in the media-buy currency. Every selected pricing option in an AdCP-authored media buy MUST declare that same currency. In seller-optimized mode a number changes the package ceiling and null removes it so only the shared total and other constraints bound the package. null is invalid when the resulting allocation mode is fixed. A number in a resulting seller-optimized buy requires advertised media_buy.features.seller_optimized_package_budgets; otherwise rejected with UNSUPPORTED_FEATURE before any over-subscription validation.', + ge=0.0, + ), + ] = None + min_spend_target: Annotated[ + StrictFloat | None, + Field( + description='Updated soft lifetime spend target for this package. A number is valid only for seller-optimized allocation, requires advertised media_buy.features.seller_optimized_min_spend_targets (otherwise UNSUPPORTED_FEATURE, before any over-subscription validation), and must not exceed the resulting package budget when one exists. null removes the target. Sellers MUST validate the complete post-update state atomically.', + ge=0.0, + ), + ] = None + pacing: Annotated[ + pacing_1.Pacing | None, + Field( + description='Updated package pacing, subordinate to media-buy pacing. In a resulting seller-optimized buy, pacing that differs from the media-buy pacing requires advertised media_buy.features.seller_optimized_package_pacing; otherwise rejected with UNSUPPORTED_FEATURE.' + ), + ] = None + bid_price: Annotated[ + StrictFloat | None, + Field( + deprecated=True, + description='DEPRECATED in 3.2 and removed in the next major. Use bidding. A package update MUST NOT supply both a non-null bidding object and bid_price. During migration, bidding:null MAY accompany bid_price to clear the canonical block and set the legacy representation atomically.', + ge=0.0, + ), + ] = None + bidding: Annotated[ + bidding_policy.BiddingPolicy | None, + Field( + description='Replace the complete package-authored bidding policy. An object replaces any prior package block and remains a complete override of the media-buy default. `{automatic:true}` explicitly selects provider automatic bidding at package scope. null clears the package-authored block so the package inherits media-buy bidding; if the media-buy block is also absent, provider automatic delivery applies. Monetary fields use the media-buy currency and require the package pricing option to declare that currency. During legacy migration, null MAY accompany bid_price or monetary optimization-goal targets; only a non-null canonical bidding object conflicts with those representations.' + ), + ] = None + impressions: Annotated[ + StrictFloat | None, Field(description='Updated impression goal for this package', ge=0.0) + ] = None + daily_budget_cap: Annotated[ + StrictFloat | None, + Field( + description="Replace this package's hard daily cap; null removes it. Numeric changes apply immediately with current-day package spend counted. A cap below that spend pauses the package for the day; the aggregate cap remains independently binding.", + ge=0.0, + ), + ] = None + start_time: Annotated[ + AwareDatetime | None, + Field( + description="Updated flight start date/time for this package in ISO 8601 format. Must fall within the media buy's date range." + ), + ] = None + end_time: Annotated[ + AwareDatetime | None, + Field( + description="Updated flight end date/time for this package in ISO 8601 format. Must fall within the media buy's date range." + ), + ] = None + paused: Annotated[ + StrictBool | None, + Field(description='Pause/resume specific package (true = paused, false = active)'), + ] = None + canceled: Annotated[ + Literal[True] | None, + Field( + description='Cancel this specific package. Cancellation is irreversible — canceled packages stop delivery and cannot be reactivated. When true, package cancellation takes precedence over sibling fields on this package: the seller applies only canceled and cancellation_reason for this package and SHOULD return a structured warning naming ignored sibling fields. Root fields and other package updates still participate in the same atomic update when root canceled is absent. Sellers MAY reject with NOT_CANCELLABLE.' + ), + ] = None + cancellation_reason: Annotated[ + str | None, Field(description='Reason for canceling this package.', max_length=500) + ] = None + catalogs: Annotated[ + list[catalog.Catalog] | None, + Field( + description='Replace the catalogs this package promotes. Uses replacement semantics — the provided array replaces the current list. Omit to leave catalogs unchanged.', + min_length=1, + ), + ] = None + optimization_goals: Annotated[ + list[optimization_goal.OptimizationGoal] | None, + Field( + description='Replace all optimization goals for this package. Uses replacement semantics — omit to leave goals unchanged.', + min_length=1, + ), + ] = None + targeting_overlay: Annotated[ + targeting_input.TargetingOverlayInput | None, + Field( + description='Per-dimension targeting patch for this package. Omit targeting_overlay, or omit an individual dimension inside it, to leave the corresponding effective targeting unchanged. A non-null dimension replaces its current value; null clears it, including a value inherited from configured-product selection or a product default. Every resulting effective overlay must remain executable by the product. Sellers reject unsupported or partially applicable changes and use REQUOTE_REQUIRED when a change, including a broader inventory set, falls outside the priced envelope. placement_selection is purchased-inventory targeting; mode default restores the product default, null clears the dimension when the product permits it, and successful readback echoes the committed selected set when enumerable. If a patch removes a placement referenced by an existing creative assignment, the seller MUST reject the update unless the same atomic package mutation supplies a compatible complete creative_assignments replacement. Sellers MUST NOT silently delete assignments or retain orphan placement refs.' + ), + ] = None + keyword_targets_add: Annotated[ + list[KeywordTargetsAddItem] | None, + Field( + description='Keyword targets to add or update on this package. Upserts by (keyword, match_type) identity: if the pair already exists, its bid_price is updated; if not, a new keyword target is added. Use targeting_overlay.keyword_targets in create_media_buy to set the initial list.', + min_length=1, + ), + ] = None + keyword_targets_remove: Annotated[ + list[KeywordTargetsRemoveItem] | None, + Field( + description='Keyword targets to remove from this package. Removes matching (keyword, match_type) pairs. If a specified pair is not present, sellers SHOULD treat it as a no-op for that entry.', + min_length=1, + ), + ] = None + negative_keywords_add: Annotated[ + list[NegativeKeywordsAddItem] | None, + Field( + description='Negative keywords to add to this package. Appends to the existing negative keyword list — does not replace it. If a keyword+match_type pair already exists, sellers SHOULD treat it as a no-op for that entry. Use targeting_overlay.negative_keywords in create_media_buy to set the initial list.', + min_length=1, + ), + ] = None + negative_keywords_remove: Annotated[ + list[NegativeKeywordsRemoveItem] | None, + Field( + description='Negative keywords to remove from this package. Removes matching keyword+match_type pairs from the existing list. If a specified pair is not present, sellers SHOULD treat it as a no-op for that entry.', + min_length=1, + ), + ] = None + creative_assignments: Annotated[ + list[creative_assignment.CreativeAssignment] | None, + Field( + description='Replace creative assignments for this package with optional rotation, grouping, weights, and placement routing. Uses replacement semantics - omit to leave assignments unchanged. rotation_mode is package-scoped: omission resolves to weighted, and every assignment MUST resolve to the same effective mode. In sequential mode, sequence_position MUST be unique within each package-local group. Sellers reject conflicts with VALIDATION_ERROR before mutation. When the same mutation narrows or clears targeting_overlay.placement_selection, this complete replacement MUST remove or reroute every assignment reference that would otherwise be orphaned; the seller validates both changes atomically.' + ), + ] = None + creatives: Annotated[ + list[creative_asset.CreativeAsset] | None, + Field( + description="Replace this package's inline creative assets. Native localization is not accepted on this path; use sync_creatives before assigning the library creative. When the seller also advertises creative.has_creative_library: true, new inline creatives enter the seller's creative library and can be reused by creative_id while retained; inline-only sellers may store them as package-scoped assets. Use creative_assignments instead for existing library creatives.", + max_length=100, + min_length=1, + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/product_discovery_criteria.py b/src/adcp/types/domains/media_buy/product_discovery_criteria.py index 17dfdc9fd..565df258f 100644 --- a/src/adcp/types/domains/media_buy/product_discovery_criteria.py +++ b/src/adcp/types/domains/media_buy/product_discovery_criteria.py @@ -1,22 +1,89 @@ -"""Types declared by the AdCP ``media_buy/product_discovery_criteria`` schema. +# generated by datamodel-codegen: +# filename: media_buy/product_discovery_criteria.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.media_buy.product_discovery_criteria import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Any -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field -from adcp.types.generated_poc.media_buy.product_discovery_criteria import ( - PolicyId, - ProductDiscoveryCriteria, - ProductId, +from ..core import catalog_selection +from ..core import media_buy_frequency_cap as media_buy_frequency_cap_1 +from ..core import ( + media_buy_support_requirements, + product_offer_filters, + targeting, + targeting_overlay_requirements, ) +from . import acceptance_context as acceptance_context_1 +from . import outcome_target as outcome_target_1 + + +class ProductId(ScalarStr): + __slots__ = () + _constraints = {'min_length': 1} + + +class PolicyId(ProductId): + pass + -# Explicit exports -__all__ = ["PolicyId", "ProductDiscoveryCriteria", "ProductId"] +class ProductDiscoveryCriteria(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + product_ids: Annotated[ + list[ProductId] | None, + Field( + description='Exact seller-issued products to retrieve or use as proposal candidates.', + min_length=1, + ), + ] = None + offer_filters: Annotated[ + product_offer_filters.ProductOfferFilters | None, + Field( + description='Hard commercial and product-characteristic filters. Every returned product MUST satisfy them. These fields do not become package delivery targeting.' + ), + ] = None + targeting_overlay: Annotated[ + targeting.TargetingOverlay | None, + Field( + description='Concrete delivery constraints the buyer expects to carry into create_media_buy. Returned product pricing and forecasts MUST reflect the effective targeting. A product that cannot apply the request exactly is omitted or returned as a distinct configured product with sparse Product.targeting_resolution modifications; absence of that resolution means exact acceptance.' + ), + ] = None + media_buy_frequency_cap: Annotated[ + media_buy_frequency_cap_1.MediaBuyFrequencyCap | None, + Field( + description='Concrete aggregate max-impression cap expected at the MediaBuy root. Every returned product must participate in one shared counter for this exact value. Proposals carry it in commercial_terms.frequency_cap.' + ), + ] = None + required_overlay_support: Annotated[ + targeting_overlay_requirements.TargetingOverlayRequirements | None, + Field( + description='Minimum product-scoped targeting dimensions the buyer must be able to select independently on packages later. This requests capability, not current values, value-specific availability, a forecast for every later value, or one product per value.' + ), + ] = None + required_media_buy_support: Annotated[ + media_buy_support_requirements.ProductMediaBuySupportRequirements | None, + Field( + description='Minimum product participation in shared MediaBuy-level controls. For frequency caps, every returned product is promising composability in one aggregate seller counter for each requested per unit.' + ), + ] = None + outcome_target: Annotated[ + outcome_target_1.OutcomeTarget | None, + Field( + description='Reverse-forecast planning input for sellers declaring media_buy.outcome_target: a goal plus a volume, a cost target, or both, answered on proposals. Inert on list_products for every seller. See outcome-target.json.' + ), + ] = None + acceptance_context: Annotated[ + acceptance_context_1.AcceptanceContext | None, + Field( + description='Structured campaign and advertiser facts for coarse acceptance-policy matching. A matching product is not a guarantee of final acceptance.' + ), + ] = None + catalog: catalog_selection.CatalogSelection | None = None + policy_ids: Annotated[list[PolicyId] | None, Field(min_length=1)] = None + ext: dict[str, Any] | None = None diff --git a/src/adcp/types/domains/media_buy/product_fields.py b/src/adcp/types/domains/media_buy/product_fields.py index a2c7c1dc0..6d69a65ef 100644 --- a/src/adcp/types/domains/media_buy/product_fields.py +++ b/src/adcp/types/domains/media_buy/product_fields.py @@ -1,21 +1,62 @@ -"""Types declared by the AdCP ``media_buy/product_fields`` schema. +# generated by datamodel-codegen: +# filename: media_buy/product_fields.json +# timestamp: 2026-10-01T07:57:26+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.product_fields import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import Field, RootModel -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.media_buy.product_fields import ( - ProductResponseField, - ProductResponseFields, -) +class ProductResponseField(StrEnum): + product_id = 'product_id' + name = 'name' + description = 'description' + publisher_properties = 'publisher_properties' + channels = 'channels' + video_placement_types = 'video_placement_types' + audio_distribution_types = 'audio_distribution_types' + sponsored_placement_types = 'sponsored_placement_types' + social_placement_surfaces = 'social_placement_surfaces' + format_options = 'format_options' + placements = 'placements' + delivery_type = 'delivery_type' + exclusivity = 'exclusivity' + pricing_options = 'pricing_options' + forecast = 'forecast' + reporting_capabilities = 'reporting_capabilities' + measurement_terms = 'measurement_terms' + performance_standards = 'performance_standards' + catalog_types = 'catalog_types' + signal_targeting_allowed = 'signal_targeting_allowed' + signal_targeting_rules = 'signal_targeting_rules' + demographic_targeting = 'demographic_targeting' + overlay_support = 'overlay_support' + collections = 'collections' + collection_targeting_allowed = 'collection_targeting_allowed' + media_buy_support = 'media_buy_support' + audience_evidence = 'audience_evidence' + audience_evidence_selections = 'audience_evidence_selections' + max_optimization_goals = 'max_optimization_goals' + catalog_match = 'catalog_match' + list_applications = 'list_applications' + brief_relevance = 'brief_relevance' + targeting_resolution = 'targeting_resolution' + acceptance_policy_profile_ids = 'acceptance_policy_profile_ids' + identity = 'identity' + execution_requirements = 'execution_requirements' + expires_at = 'expires_at' + allowed_actions = 'allowed_actions' + -# Explicit exports -__all__ = ["ProductResponseField", "ProductResponseFields"] +class ProductResponseFields(RootModel[list[ProductResponseField]]): + root: Annotated[ + list[ProductResponseField], + Field( + description='Canonical product fields a buyer requests from list_products. Required product_id and name fields are always returned. list_applications also overrides projection whenever a property or collection list is in the effective targeting because it is the decision receipt for seller-specific list matching. targeting_resolution and expires_at override projection whenever the seller returns modifications, as does collection_targeting_allowed whenever returned overlay_support declares collection_list. Legacy named-format fields remain available only through get_products.', + min_length=1, + title='Product Response Fields', + ), + ] diff --git a/src/adcp/types/domains/media_buy/product_purchase.py b/src/adcp/types/domains/media_buy/product_purchase.py index 1963d0134..571908505 100644 --- a/src/adcp/types/domains/media_buy/product_purchase.py +++ b/src/adcp/types/domains/media_buy/product_purchase.py @@ -1,18 +1,150 @@ -"""Types declared by the AdCP ``media_buy/product_purchase`` schema. +# generated by datamodel-codegen: +# filename: media_buy/product_purchase.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.product_purchase import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-04 01:19:07 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import ( + audience_evidence_pin, + bidding_policy, + canonical_measurement_terms, + canonical_optimization_goal, + canonical_performance_standard, + canonical_pricing_option, +) +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core import format_option_ref, product_audience_evidence_requirements, targeting +from ..enums import pacing as pacing_1 + + +class CatalogId(ScalarStr): + __slots__ = () + _constraints = {'min_length': 1} -from adcp.types.generated_poc.media_buy.product_purchase import CatalogId, ProductPurchase -# Explicit exports -__all__ = ["CatalogId", "ProductPurchase"] +class ProductPurchase(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + product_id: Annotated[str, Field(min_length=1)] + pricing_option_id: Annotated[str, Field(min_length=1)] + pricing: Annotated[ + canonical_pricing_option.CanonicalPricingOption | None, + Field( + description='Resolved selected pricing terms. Optional on buy_products input, where pricing_option_id plus the versioned feed identifies the offer; required inside accepted commercial_terms. Its pricing_option_id MUST match the sibling field.' + ), + ] = None + format_option_refs: Annotated[ + list[format_option_ref.FormatOptionReference] | None, + Field( + description='Canonical format options selected from the published product offer. Legacy named-format identifiers are not accepted.', + min_length=1, + ), + ] = None + catalog_ids: Annotated[ + list[CatalogId] | None, + Field( + description='Previously synchronized account catalog IDs promoted by this selection. Callers manage catalog bodies through sync_catalogs rather than inlining them here.', + min_length=1, + ), + ] = None + budget: Annotated[ + StrictFloat | None, + Field( + description='Hard spend cap for this selection in the media-buy currency. In seller-optimized allocation it is an optional ceiling, not a reserved allocation, and requires advertised media_buy.features.seller_optimized_package_budgets; otherwise rejected with UNSUPPORTED_FEATURE before any over-subscription validation.', + ge=0.0, + ), + ] = None + daily_budget_cap: Annotated[ + StrictFloat | None, + Field( + description="Optional hard daily spend ceiling for this purchase. It is subordinate to the media-buy aggregate daily cap and is not a reserved daily allocation. Its day boundary is the media buy's budget_cap_timezone.", + ge=0.0, + ), + ] = None + min_spend_target: Annotated[ + StrictFloat | None, + Field( + description='Soft lifetime spend target for seller-optimized allocation. Requires advertised media_buy.features.seller_optimized_min_spend_targets; otherwise rejected with UNSUPPORTED_FEATURE before any over-subscription validation.', + ge=0.0, + ), + ] = None + impressions: Annotated[ + StrictFloat | None, Field(ge=0.0, title='Product Purchase Impressions') + ] = None + start_time: Annotated[ + AwareDatetime | None, + Field( + description='Resolved package flight start. On direct-purchase input, omission inherits the MediaBuy start; accepted proposal snapshots carry the resolved timestamp.' + ), + ] = None + end_time: Annotated[ + AwareDatetime | None, + Field( + description='Resolved package flight end. On direct-purchase input, omission inherits the MediaBuy end; accepted proposal snapshots carry the resolved timestamp.' + ), + ] = None + pacing: Annotated[ + pacing_1.Pacing | None, + Field( + description='Purchase pacing, subordinate to media-buy pacing. In seller-optimized allocation, pacing that differs from the media-buy pacing requires advertised media_buy.features.seller_optimized_package_pacing; otherwise rejected with UNSUPPORTED_FEATURE.' + ), + ] = None + bidding: bidding_policy.BiddingPolicy | None = None + targeting_overlay: Annotated[ + targeting.TargetingOverlay | None, + Field( + description="Resolved effective buyer-selected targeting, including compatible wholesale signal selections, applied within the product's published targeting contract. Cleared dimensions are omitted; null is invalid in an accepted snapshot." + ), + ] = None + optimization_goals: Annotated[ + list[canonical_optimization_goal.CanonicalOptimizationGoal] | None, Field(min_length=1) + ] = None + audience_evidence_requirements: Annotated[ + product_audience_evidence_requirements.ProductAudienceEvidenceRequirements | None, + Field( + description='Buyer evidence-admissibility policy carried into the accepted purchase snapshot.', + title='Product Purchase Audience Evidence Requirements', + ), + ] = None + audience_evidence_pins: Annotated[ + list[audience_evidence_pin.AudienceEvidencePin] | None, + Field( + description='Exact immutable audience-evidence snapshots selected for package construction.', + min_length=1, + ), + ] = None + agency_estimate_number: Annotated[ + str | None, + Field( + description='Package-level agency estimate or authorization reference.', max_length=100 + ), + ] = None + context: Annotated[ + context_1.ContextObject | None, + Field( + description='Opaque buyer package correlation preserved in the accepted snapshot and readback.' + ), + ] = None + ext: ext_1.ExtensionObject | None = None + measurement_terms: Annotated[ + canonical_measurement_terms.CanonicalMeasurementTerms | None, + Field( + description='Published or negotiated billing-measurement and makegood terms for this purchase. Direct buyers may omit this to inherit the product default; accepted proposal snapshots preserve the resolved terms.', + title='Product Purchase Measurement Terms', + ), + ] = None + performance_standards: Annotated[ + list[canonical_performance_standard.CanonicalPerformanceStandard] | None, + Field( + description='Published or negotiated metric thresholds and measurement vendors. Direct buyers may omit this to inherit the product defaults; accepted proposal snapshots preserve every applicable standard.', + min_length=1, + ), + ] = None diff --git a/src/adcp/types/domains/media_buy/product_purchase_input.py b/src/adcp/types/domains/media_buy/product_purchase_input.py index a155ef8bc..4c213947d 100644 --- a/src/adcp/types/domains/media_buy/product_purchase_input.py +++ b/src/adcp/types/domains/media_buy/product_purchase_input.py @@ -1,21 +1,150 @@ -"""Types declared by the AdCP ``media_buy/product_purchase_input`` schema. +# generated by datamodel-codegen: +# filename: media_buy/product_purchase_input.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.media_buy.product_purchase_input import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-04 01:19:07 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat -from adcp.types.generated_poc.media_buy.product_purchase_input import ( - CatalogId, - ProductPurchaseInput, +from ..core import ( + audience_evidence_pin, + bidding_policy, + canonical_measurement_terms, + canonical_optimization_goal, + canonical_performance_standard, + canonical_pricing_option, ) +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core import format_option_ref, product_audience_evidence_requirements, targeting_input +from ..enums import pacing as pacing_1 + + +class CatalogId(ScalarStr): + __slots__ = () + _constraints = {'min_length': 1} + -# Explicit exports -__all__ = ["CatalogId", "ProductPurchaseInput"] +class ProductPurchaseInput(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + product_id: Annotated[str, Field(min_length=1)] + pricing_option_id: Annotated[str, Field(min_length=1)] + pricing: Annotated[ + canonical_pricing_option.CanonicalPricingOption | None, + Field( + description='Resolved selected pricing terms. Optional on buy_products input, where pricing_option_id plus the versioned feed identifies the offer; required inside accepted commercial_terms. Its pricing_option_id MUST match the sibling field.' + ), + ] = None + format_option_refs: Annotated[ + list[format_option_ref.FormatOptionReference] | None, + Field( + description='Canonical format options selected from the published product offer. Legacy named-format identifiers are not accepted.', + min_length=1, + ), + ] = None + catalog_ids: Annotated[ + list[CatalogId] | None, + Field( + description='Previously synchronized account catalog IDs promoted by this selection. Callers manage catalog bodies through sync_catalogs rather than inlining them here.', + min_length=1, + ), + ] = None + budget: Annotated[ + StrictFloat | None, + Field( + description='Hard spend cap for this selection in the media-buy currency. In seller-optimized allocation it is an optional ceiling, not a reserved allocation, and requires advertised media_buy.features.seller_optimized_package_budgets; otherwise rejected with UNSUPPORTED_FEATURE before any over-subscription validation.', + ge=0.0, + ), + ] = None + daily_budget_cap: Annotated[ + StrictFloat | None, + Field( + description="Optional hard daily spend ceiling for this purchase. It is subordinate to the media-buy aggregate daily cap and is not a reserved daily allocation. Its day boundary is the media buy's budget_cap_timezone.", + ge=0.0, + ), + ] = None + min_spend_target: Annotated[ + StrictFloat | None, + Field( + description='Soft lifetime spend target for seller-optimized allocation. Requires advertised media_buy.features.seller_optimized_min_spend_targets; otherwise rejected with UNSUPPORTED_FEATURE before any over-subscription validation.', + ge=0.0, + ), + ] = None + impressions: Annotated[ + StrictFloat | None, Field(ge=0.0, title='Product Purchase Impressions') + ] = None + start_time: Annotated[ + AwareDatetime | None, + Field( + description='Resolved package flight start. On direct-purchase input, omission inherits the MediaBuy start; accepted proposal snapshots carry the resolved timestamp.' + ), + ] = None + end_time: Annotated[ + AwareDatetime | None, + Field( + description='Resolved package flight end. On direct-purchase input, omission inherits the MediaBuy end; accepted proposal snapshots carry the resolved timestamp.' + ), + ] = None + pacing: Annotated[ + pacing_1.Pacing | None, + Field( + description='Purchase pacing, subordinate to media-buy pacing. In seller-optimized allocation, pacing that differs from the media-buy pacing requires advertised media_buy.features.seller_optimized_package_pacing; otherwise rejected with UNSUPPORTED_FEATURE.' + ), + ] = None + bidding: bidding_policy.BiddingPolicy | None = None + targeting_overlay: Annotated[ + targeting_input.TargetingOverlayInput | None, + Field( + description="Per-dimension targeting input. Omission inherits the selected product's configured/default dimension, a non-null value replaces it, and null explicitly suppresses it. A clear cannot remove inherent product scope." + ), + ] = None + optimization_goals: Annotated[ + list[canonical_optimization_goal.CanonicalOptimizationGoal] | None, Field(min_length=1) + ] = None + audience_evidence_requirements: Annotated[ + product_audience_evidence_requirements.ProductAudienceEvidenceRequirements | None, + Field( + description='Buyer evidence-admissibility policy carried into the accepted purchase snapshot.', + title='Product Purchase Audience Evidence Requirements', + ), + ] = None + audience_evidence_pins: Annotated[ + list[audience_evidence_pin.AudienceEvidencePin] | None, + Field( + description='Exact immutable audience-evidence snapshots selected for package construction.', + min_length=1, + ), + ] = None + agency_estimate_number: Annotated[ + str | None, + Field( + description='Package-level agency estimate or authorization reference.', max_length=100 + ), + ] = None + context: Annotated[ + context_1.ContextObject | None, + Field( + description='Opaque buyer package correlation preserved in the accepted snapshot and readback.' + ), + ] = None + ext: ext_1.ExtensionObject | None = None + measurement_terms: Annotated[ + canonical_measurement_terms.CanonicalMeasurementTerms | None, + Field( + description='Published or negotiated billing-measurement and makegood terms for this purchase. Direct buyers may omit this to inherit the product default; accepted proposal snapshots preserve the resolved terms.', + title='Product Purchase Measurement Terms', + ), + ] = None + performance_standards: Annotated[ + list[canonical_performance_standard.CanonicalPerformanceStandard] | None, + Field( + description='Published or negotiated metric thresholds and measurement vendors. Direct buyers may omit this to inherit the product defaults; accepted proposal snapshots preserve every applicable standard.', + min_length=1, + ), + ] = None diff --git a/src/adcp/types/domains/media_buy/product_refinement.py b/src/adcp/types/domains/media_buy/product_refinement.py index d0f8b5986..d04a34d79 100644 --- a/src/adcp/types/domains/media_buy/product_refinement.py +++ b/src/adcp/types/domains/media_buy/product_refinement.py @@ -1,34 +1,87 @@ -"""Types declared by the AdCP ``media_buy/product_refinement`` schema. +# generated by datamodel-codegen: +# filename: media_buy/product_refinement.json +# timestamp: 2026-10-03T00:36:50+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.product_refinement import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, RootModel -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.media_buy.product_refinement import ( - Action, - Action11, - ProductRefinementRequests, - ProductRefinementRequests1, - ProductRefinementRequests2, - ProductRefinementRequests3, - ProductRefinementRequests4, -) - -# Explicit exports -__all__ = [ - "Action", - "Action11", - "ProductRefinementRequests", - "ProductRefinementRequests1", - "ProductRefinementRequests2", - "ProductRefinementRequests3", - "ProductRefinementRequests4", +class ProductRefinementRequests2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + scope: Annotated[Literal['request'], Field(description='Change scoped to the overall request.')] = 'request' + ask: Annotated[ + str, Field(description='What the buyer is asking for at the request level.', min_length=1) + ] + + +class Action(StrEnum): + include = 'include' + omit = 'omit' + more_like_this = 'more_like_this' + + +class ProductRefinementRequests3(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + scope: Annotated[Literal['product'], Field(description='Change scoped to a specific product.')] = 'product' + product_id: Annotated[ + str, + Field(description='Product ID from a previous product-discovery response.', min_length=1), + ] + action: Annotated[Action | None, Field(description='Requested product-level action.')] = ( + Action.include + ) + ask: Annotated[ + str | None, Field(description='What the buyer is asking for on this product.', min_length=1) + ] = None + + +class Action11(StrEnum): + include = 'include' + omit = 'omit' + finalize = 'finalize' + + +class ProductRefinementRequests4(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + scope: Annotated[ + Literal['proposal'], Field(description='Change scoped to a specific proposal.') + ] = 'proposal' + proposal_id: Annotated[ + str, + Field(description='Proposal ID from a previous product-discovery response.', min_length=1), + ] + action: Annotated[Action11 | None, Field(description='Requested proposal-level action.')] = ( + Action11.include + ) + ask: Annotated[ + str | None, + Field(description='What the buyer is asking for on this proposal.', min_length=1), + ] = None + + +ProductRefinementRequests1 = Annotated[ + ProductRefinementRequests2 | ProductRefinementRequests3 | ProductRefinementRequests4, + Field(discriminator='scope'), ] + + +class ProductRefinementRequests(RootModel[list[ProductRefinementRequests1]]): + root: Annotated[ + list[ProductRefinementRequests1], + Field( + description='Change requests for iterating on product discovery results and proposals.', + min_length=1, + title='Product Refinement Requests', + ), + ] diff --git a/src/adcp/types/domains/media_buy/proposal_budget_constraint.py b/src/adcp/types/domains/media_buy/proposal_budget_constraint.py index 9e761fe83..436995315 100644 --- a/src/adcp/types/domains/media_buy/proposal_budget_constraint.py +++ b/src/adcp/types/domains/media_buy/proposal_budget_constraint.py @@ -1,18 +1,32 @@ -"""Types declared by the AdCP ``media_buy/proposal_budget_constraint`` schema. +# generated by datamodel-codegen: +# filename: media_buy/proposal_budget_constraint.json +# timestamp: 2026-10-04T01:14:11+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.proposal_budget_constraint import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictFloat, model_validator -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.media_buy.proposal_budget_constraint import ProposalBudgetConstraint +class ProposalBudgetConstraint(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + min: Annotated[StrictFloat | None, Field(ge=0.0)] = None + max: Annotated[StrictFloat | None, Field(ge=0.0)] = None + currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] -# Explicit exports -__all__ = ["ProposalBudgetConstraint"] + @model_validator(mode='after') + def _require_schema_required_group(self) -> ProposalBudgetConstraint: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('min',), ('max',),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'ProposalBudgetConstraint requires at least one of these field groups: min | max' + ) diff --git a/src/adcp/types/domains/media_buy/proposal_decline.py b/src/adcp/types/domains/media_buy/proposal_decline.py index b2c7e0ae1..971962596 100644 --- a/src/adcp/types/domains/media_buy/proposal_decline.py +++ b/src/adcp/types/domains/media_buy/proposal_decline.py @@ -1,18 +1,35 @@ -"""Types declared by the AdCP ``media_buy/proposal_decline`` schema. +# generated by datamodel-codegen: +# filename: media_buy/proposal_decline.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.proposal_decline import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import proposal_decline_reason -from adcp.types.generated_poc.media_buy.proposal_decline import ProposalDecline -# Explicit exports -__all__ = ["ProposalDecline"] +class ProposalDecline(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + proposal_id: Annotated[ + str, + Field( + description='Immutable proposal ID returned by request_proposals or refine_proposals.', + max_length=255, + min_length=1, + ), + ] + reason: proposal_decline_reason.ProposalDeclineReason + detail: Annotated[ + str | None, + Field( + description='Optional short, non-identifying explanation. MUST NOT identify a competitor or disclose sensitive campaign information.', + max_length=500, + min_length=1, + ), + ] = None diff --git a/src/adcp/types/domains/media_buy/proposal_refinement.py b/src/adcp/types/domains/media_buy/proposal_refinement.py index dff01d546..7370dfeca 100644 --- a/src/adcp/types/domains/media_buy/proposal_refinement.py +++ b/src/adcp/types/domains/media_buy/proposal_refinement.py @@ -1,66 +1,583 @@ -"""Types declared by the AdCP ``media_buy/proposal_refinement`` schema. +# generated by datamodel-codegen: +# filename: media_buy/proposal_refinement.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.proposal_refinement import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat + +from ..core import product_change_map +from . import product_discovery_criteria, proposal_budget_constraint + + +class Action(StrEnum): + revise = 'revise' + finalize = 'finalize' + + +class ChangeKind(StrEnum): + amendment = 'amendment' + cancellation = 'cancellation' + + +class Cpm(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + max: Annotated[StrictFloat, Field(gt=0.0)] + currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] + + +class Impressions(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + min: Annotated[StrictFloat, Field(gt=0.0)] + + +class Flight(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + start_no_later_than: AwareDatetime | None = None + end_no_earlier_than: AwareDatetime | None = None + + +class Constraints(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + total_budget: Annotated[ + proposal_budget_constraint.ProposalBudgetConstraint | None, + Field( + description='Inclusive bounds checked against commercial_terms.total_budget. Satisfied only when commercial_terms.total_budget is present, its currency matches, and its amount falls within every supplied bound.' + ), + ] = None + cpm: Annotated[ + Cpm | None, + Field( + description='Hard ceiling on fixed CPM rates. Satisfied only when every entry of commercial_terms.purchases carries pricing with pricing_model cpm or vcpm, a matching currency, and a fixed_price no greater than max. Auction-priced, missing, or non-CPM pricing on any purchase leaves the constraint unsatisfied.' + ), + ] = None + impressions: Annotated[ + Impressions | None, + Field( + description='Hard minimum contracted impression volume. Satisfied only when every entry of commercial_terms.purchases carries impressions and their sum is at least min.' + ), + ] = None + flight: Annotated[ + Flight | None, + Field( + description='Hard flight-window bounds checked against commercial_terms.start_time and end_time. start_no_later_than is satisfied only by a concrete start_time at or before the bound; an asap start is unverifiable and therefore unsatisfied. end_no_earlier_than is satisfied only by an end_time at or after the bound.' + ), + ] = None + + +class Alternatives(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + count: Annotated[ + SchemaInt, + Field( + description="Number of draft alternatives requested. The protocol maximum is 10. Buyers MUST NOT exceed a seller's lower advertised proposal_refinement.max_alternatives; sellers reject an excessive count at task level with VALIDATION_ERROR rather than clamping it.", + ge=2, + le=10, + ), + ] + + +class Constraints1(Constraints): + pass + + +class Constraints2(Constraints): + pass + + +class Constraints3(Constraints): + pass + + +class Constraints4(Constraints): + pass + + +class Constraints5(Constraints): + pass + + +class Constraints6(Constraints): + pass + + +class Constraints7(Constraints): + pass + + +class ProposalRefinement2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + proposal_id: Annotated[str, Field(min_length=1)] + action: Annotated[ + Literal['finalize'], + Field( + description='revise creates a new draft snapshot with changed commercial terms. finalize MUST target a draft; it creates a new committed snapshot without changing terms and reserves inventory until its expires_at.' + ), + ] = 'finalize' + change_kind: Annotated[ + ChangeKind | None, + Field( + description='Desired successor proposal. cancellation is valid only when the source proposal is accepted and attached to a non-terminal MediaBuy.' + ), + ] = ChangeKind.amendment + constraints: Annotated[ + Constraints | None, + Field( + description='Hard requirements for every revised proposal. Keys are echoed in unsatisfied_constraints on partial or unable results. A draft that fails any present constraint MUST NOT be returned as revised: the result is partial or unable with reason_code constraint_unsatisfiable — which takes precedence over every other reason code — and the failing keys in unsatisfied_constraints.' + ), + ] = None + product_changes: Annotated[ + product_change_map.ProductChangeMap | None, + Field( + description='Product IDs mapped to membership actions checked against commercial_terms.purchases. include requires presence; omit requires absence. The verbs match legacy get_products refinement.' + ), + ] = None + alternatives: Annotated[ + Alternatives | None, + Field( + description='Request drafts with distinct commercial terms by count. Every returned alternative MUST carry distinct commercial_terms and therefore a unique terms_digest; diversity preferences remain in ask.' + ), + ] = None + ask: Annotated[ + str | None, + Field( + description='What the buyer is asking for on this proposal, or the cancellation reason for action revise. The seller returns a new draft proposal snapshot; this text never directly mutates the source proposal or MediaBuy.', + min_length=1, + ), + ] = None + criteria: Annotated[ + product_discovery_criteria.ProductDiscoveryCriteria | None, + Field( + description='Structured discovery changes for action revise. Each present field replaces that criterion from the source proposal; omitted fields inherit. Concrete targeting, future targeting support, and offer filters belong here rather than being repeated in ask.' + ), + ] = None + remove_media_buy_frequency_cap: Annotated[ + Literal[True] | None, + Field( + description='Request a revised draft whose commercial terms omit the existing MediaBuy frequency cap. Omission inherits it. To replace it, provide criteria.media_buy_frequency_cap instead.' + ), + ] = None + + +class ProposalRefinement3(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + proposal_id: Annotated[str, Field(min_length=1)] + action: Annotated[ + Literal['revise'], + Field( + description='revise creates a new draft snapshot with changed commercial terms. finalize MUST target a draft; it creates a new committed snapshot without changing terms and reserves inventory until its expires_at.' + ), + ] = 'revise' + change_kind: Annotated[ + ChangeKind | None, + Field( + description='Desired successor proposal. cancellation is valid only when the source proposal is accepted and attached to a non-terminal MediaBuy.' + ), + ] = ChangeKind.amendment + constraints: Annotated[ + Constraints1, + Field( + description='Hard requirements for every revised proposal. Keys are echoed in unsatisfied_constraints on partial or unable results. A draft that fails any present constraint MUST NOT be returned as revised: the result is partial or unable with reason_code constraint_unsatisfiable — which takes precedence over every other reason code — and the failing keys in unsatisfied_constraints.' + ), + ] + product_changes: Annotated[ + product_change_map.ProductChangeMap | None, + Field( + description='Product IDs mapped to membership actions checked against commercial_terms.purchases. include requires presence; omit requires absence. The verbs match legacy get_products refinement.' + ), + ] = None + alternatives: Annotated[ + Alternatives | None, + Field( + description='Request drafts with distinct commercial terms by count. Every returned alternative MUST carry distinct commercial_terms and therefore a unique terms_digest; diversity preferences remain in ask.' + ), + ] = None + ask: Annotated[ + str | None, + Field( + description='What the buyer is asking for on this proposal, or the cancellation reason for action revise. The seller returns a new draft proposal snapshot; this text never directly mutates the source proposal or MediaBuy.', + min_length=1, + ), + ] = None + criteria: Annotated[ + product_discovery_criteria.ProductDiscoveryCriteria | None, + Field( + description='Structured discovery changes for action revise. Each present field replaces that criterion from the source proposal; omitted fields inherit. Concrete targeting, future targeting support, and offer filters belong here rather than being repeated in ask.' + ), + ] = None + remove_media_buy_frequency_cap: Annotated[ + Literal[True] | None, + Field( + description='Request a revised draft whose commercial terms omit the existing MediaBuy frequency cap. Omission inherits it. To replace it, provide criteria.media_buy_frequency_cap instead.' + ), + ] = None + + +class ProposalRefinement4(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + proposal_id: Annotated[str, Field(min_length=1)] + action: Annotated[ + Literal['revise'], + Field( + description='revise creates a new draft snapshot with changed commercial terms. finalize MUST target a draft; it creates a new committed snapshot without changing terms and reserves inventory until its expires_at.' + ), + ] = 'revise' + change_kind: Annotated[ + ChangeKind | None, + Field( + description='Desired successor proposal. cancellation is valid only when the source proposal is accepted and attached to a non-terminal MediaBuy.' + ), + ] = ChangeKind.amendment + constraints: Annotated[ + Constraints2 | None, + Field( + description='Hard requirements for every revised proposal. Keys are echoed in unsatisfied_constraints on partial or unable results. A draft that fails any present constraint MUST NOT be returned as revised: the result is partial or unable with reason_code constraint_unsatisfiable — which takes precedence over every other reason code — and the failing keys in unsatisfied_constraints.' + ), + ] = None + product_changes: Annotated[ + product_change_map.ProductChangeMap, + Field( + description='Product IDs mapped to membership actions checked against commercial_terms.purchases. include requires presence; omit requires absence. The verbs match legacy get_products refinement.' + ), + ] + alternatives: Annotated[ + Alternatives | None, + Field( + description='Request drafts with distinct commercial terms by count. Every returned alternative MUST carry distinct commercial_terms and therefore a unique terms_digest; diversity preferences remain in ask.' + ), + ] = None + ask: Annotated[ + str | None, + Field( + description='What the buyer is asking for on this proposal, or the cancellation reason for action revise. The seller returns a new draft proposal snapshot; this text never directly mutates the source proposal or MediaBuy.', + min_length=1, + ), + ] = None + criteria: Annotated[ + product_discovery_criteria.ProductDiscoveryCriteria | None, + Field( + description='Structured discovery changes for action revise. Each present field replaces that criterion from the source proposal; omitted fields inherit. Concrete targeting, future targeting support, and offer filters belong here rather than being repeated in ask.' + ), + ] = None + remove_media_buy_frequency_cap: Annotated[ + Literal[True] | None, + Field( + description='Request a revised draft whose commercial terms omit the existing MediaBuy frequency cap. Omission inherits it. To replace it, provide criteria.media_buy_frequency_cap instead.' + ), + ] = None + + +class ProposalRefinement5(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + proposal_id: Annotated[str, Field(min_length=1)] + action: Annotated[ + Literal['revise'], + Field( + description='revise creates a new draft snapshot with changed commercial terms. finalize MUST target a draft; it creates a new committed snapshot without changing terms and reserves inventory until its expires_at.' + ), + ] = 'revise' + change_kind: Annotated[ + ChangeKind | None, + Field( + description='Desired successor proposal. cancellation is valid only when the source proposal is accepted and attached to a non-terminal MediaBuy.' + ), + ] = ChangeKind.amendment + constraints: Annotated[ + Constraints3 | None, + Field( + description='Hard requirements for every revised proposal. Keys are echoed in unsatisfied_constraints on partial or unable results. A draft that fails any present constraint MUST NOT be returned as revised: the result is partial or unable with reason_code constraint_unsatisfiable — which takes precedence over every other reason code — and the failing keys in unsatisfied_constraints.' + ), + ] = None + product_changes: Annotated[ + product_change_map.ProductChangeMap | None, + Field( + description='Product IDs mapped to membership actions checked against commercial_terms.purchases. include requires presence; omit requires absence. The verbs match legacy get_products refinement.' + ), + ] = None + alternatives: Annotated[ + Alternatives, + Field( + description='Request drafts with distinct commercial terms by count. Every returned alternative MUST carry distinct commercial_terms and therefore a unique terms_digest; diversity preferences remain in ask.' + ), + ] + ask: Annotated[ + str | None, + Field( + description='What the buyer is asking for on this proposal, or the cancellation reason for action revise. The seller returns a new draft proposal snapshot; this text never directly mutates the source proposal or MediaBuy.', + min_length=1, + ), + ] = None + criteria: Annotated[ + product_discovery_criteria.ProductDiscoveryCriteria | None, + Field( + description='Structured discovery changes for action revise. Each present field replaces that criterion from the source proposal; omitted fields inherit. Concrete targeting, future targeting support, and offer filters belong here rather than being repeated in ask.' + ), + ] = None + remove_media_buy_frequency_cap: Annotated[ + Literal[True] | None, + Field( + description='Request a revised draft whose commercial terms omit the existing MediaBuy frequency cap. Omission inherits it. To replace it, provide criteria.media_buy_frequency_cap instead.' + ), + ] = None + + +class ProposalRefinement6(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + proposal_id: Annotated[str, Field(min_length=1)] + action: Annotated[ + Literal['revise'], + Field( + description='revise creates a new draft snapshot with changed commercial terms. finalize MUST target a draft; it creates a new committed snapshot without changing terms and reserves inventory until its expires_at.' + ), + ] = 'revise' + change_kind: Annotated[ + ChangeKind | None, + Field( + description='Desired successor proposal. cancellation is valid only when the source proposal is accepted and attached to a non-terminal MediaBuy.' + ), + ] = ChangeKind.amendment + constraints: Annotated[ + Constraints4 | None, + Field( + description='Hard requirements for every revised proposal. Keys are echoed in unsatisfied_constraints on partial or unable results. A draft that fails any present constraint MUST NOT be returned as revised: the result is partial or unable with reason_code constraint_unsatisfiable — which takes precedence over every other reason code — and the failing keys in unsatisfied_constraints.' + ), + ] = None + product_changes: Annotated[ + product_change_map.ProductChangeMap | None, + Field( + description='Product IDs mapped to membership actions checked against commercial_terms.purchases. include requires presence; omit requires absence. The verbs match legacy get_products refinement.' + ), + ] = None + alternatives: Annotated[ + Alternatives | None, + Field( + description='Request drafts with distinct commercial terms by count. Every returned alternative MUST carry distinct commercial_terms and therefore a unique terms_digest; diversity preferences remain in ask.' + ), + ] = None + ask: Annotated[ + str, + Field( + description='What the buyer is asking for on this proposal, or the cancellation reason for action revise. The seller returns a new draft proposal snapshot; this text never directly mutates the source proposal or MediaBuy.', + min_length=1, + ), + ] + criteria: Annotated[ + product_discovery_criteria.ProductDiscoveryCriteria | None, + Field( + description='Structured discovery changes for action revise. Each present field replaces that criterion from the source proposal; omitted fields inherit. Concrete targeting, future targeting support, and offer filters belong here rather than being repeated in ask.' + ), + ] = None + remove_media_buy_frequency_cap: Annotated[ + Literal[True] | None, + Field( + description='Request a revised draft whose commercial terms omit the existing MediaBuy frequency cap. Omission inherits it. To replace it, provide criteria.media_buy_frequency_cap instead.' + ), + ] = None + + +class ProposalRefinement7(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + proposal_id: Annotated[str, Field(min_length=1)] + action: Annotated[ + Literal['revise'], + Field( + description='revise creates a new draft snapshot with changed commercial terms. finalize MUST target a draft; it creates a new committed snapshot without changing terms and reserves inventory until its expires_at.' + ), + ] = 'revise' + change_kind: Annotated[ + ChangeKind | None, + Field( + description='Desired successor proposal. cancellation is valid only when the source proposal is accepted and attached to a non-terminal MediaBuy.' + ), + ] = ChangeKind.amendment + constraints: Annotated[ + Constraints5 | None, + Field( + description='Hard requirements for every revised proposal. Keys are echoed in unsatisfied_constraints on partial or unable results. A draft that fails any present constraint MUST NOT be returned as revised: the result is partial or unable with reason_code constraint_unsatisfiable — which takes precedence over every other reason code — and the failing keys in unsatisfied_constraints.' + ), + ] = None + product_changes: Annotated[ + product_change_map.ProductChangeMap | None, + Field( + description='Product IDs mapped to membership actions checked against commercial_terms.purchases. include requires presence; omit requires absence. The verbs match legacy get_products refinement.' + ), + ] = None + alternatives: Annotated[ + Alternatives | None, + Field( + description='Request drafts with distinct commercial terms by count. Every returned alternative MUST carry distinct commercial_terms and therefore a unique terms_digest; diversity preferences remain in ask.' + ), + ] = None + ask: Annotated[ + str | None, + Field( + description='What the buyer is asking for on this proposal, or the cancellation reason for action revise. The seller returns a new draft proposal snapshot; this text never directly mutates the source proposal or MediaBuy.', + min_length=1, + ), + ] = None + criteria: Annotated[ + product_discovery_criteria.ProductDiscoveryCriteria, + Field( + description='Structured discovery changes for action revise. Each present field replaces that criterion from the source proposal; omitted fields inherit. Concrete targeting, future targeting support, and offer filters belong here rather than being repeated in ask.' + ), + ] + remove_media_buy_frequency_cap: Annotated[ + Literal[True] | None, + Field( + description='Request a revised draft whose commercial terms omit the existing MediaBuy frequency cap. Omission inherits it. To replace it, provide criteria.media_buy_frequency_cap instead.' + ), + ] = None + + +class ProposalRefinement8(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + proposal_id: Annotated[str, Field(min_length=1)] + action: Annotated[ + Literal['revise'], + Field( + description='revise creates a new draft snapshot with changed commercial terms. finalize MUST target a draft; it creates a new committed snapshot without changing terms and reserves inventory until its expires_at.' + ), + ] = 'revise' + change_kind: Annotated[ + ChangeKind | None, + Field( + description='Desired successor proposal. cancellation is valid only when the source proposal is accepted and attached to a non-terminal MediaBuy.' + ), + ] = ChangeKind.amendment + constraints: Annotated[ + Constraints6 | None, + Field( + description='Hard requirements for every revised proposal. Keys are echoed in unsatisfied_constraints on partial or unable results. A draft that fails any present constraint MUST NOT be returned as revised: the result is partial or unable with reason_code constraint_unsatisfiable — which takes precedence over every other reason code — and the failing keys in unsatisfied_constraints.' + ), + ] = None + product_changes: Annotated[ + product_change_map.ProductChangeMap | None, + Field( + description='Product IDs mapped to membership actions checked against commercial_terms.purchases. include requires presence; omit requires absence. The verbs match legacy get_products refinement.' + ), + ] = None + alternatives: Annotated[ + Alternatives | None, + Field( + description='Request drafts with distinct commercial terms by count. Every returned alternative MUST carry distinct commercial_terms and therefore a unique terms_digest; diversity preferences remain in ask.' + ), + ] = None + ask: Annotated[ + str | None, + Field( + description='What the buyer is asking for on this proposal, or the cancellation reason for action revise. The seller returns a new draft proposal snapshot; this text never directly mutates the source proposal or MediaBuy.', + min_length=1, + ), + ] = None + criteria: Annotated[ + product_discovery_criteria.ProductDiscoveryCriteria | None, + Field( + description='Structured discovery changes for action revise. Each present field replaces that criterion from the source proposal; omitted fields inherit. Concrete targeting, future targeting support, and offer filters belong here rather than being repeated in ask.' + ), + ] = None + remove_media_buy_frequency_cap: Annotated[ + Literal[True], + Field( + description='Request a revised draft whose commercial terms omit the existing MediaBuy frequency cap. Omission inherits it. To replace it, provide criteria.media_buy_frequency_cap instead.' + ), + ] + + +class ProposalRefinement9(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + proposal_id: Annotated[str, Field(min_length=1)] + action: Annotated[ + Literal['revise'], + Field( + description='revise creates a new draft snapshot with changed commercial terms. finalize MUST target a draft; it creates a new committed snapshot without changing terms and reserves inventory until its expires_at.' + ), + ] = 'revise' + change_kind: Annotated[ + Literal['cancellation'], + Field( + description='Desired successor proposal. cancellation is valid only when the source proposal is accepted and attached to a non-terminal MediaBuy.' + ), + ] = 'cancellation' + constraints: Annotated[ + Constraints7 | None, + Field( + description='Hard requirements for every revised proposal. Keys are echoed in unsatisfied_constraints on partial or unable results. A draft that fails any present constraint MUST NOT be returned as revised: the result is partial or unable with reason_code constraint_unsatisfiable — which takes precedence over every other reason code — and the failing keys in unsatisfied_constraints.' + ), + ] = None + product_changes: Annotated[ + product_change_map.ProductChangeMap | None, + Field( + description='Product IDs mapped to membership actions checked against commercial_terms.purchases. include requires presence; omit requires absence. The verbs match legacy get_products refinement.' + ), + ] = None + alternatives: Annotated[ + Alternatives | None, + Field( + description='Request drafts with distinct commercial terms by count. Every returned alternative MUST carry distinct commercial_terms and therefore a unique terms_digest; diversity preferences remain in ask.' + ), + ] = None + ask: Annotated[ + str | None, + Field( + description='What the buyer is asking for on this proposal, or the cancellation reason for action revise. The seller returns a new draft proposal snapshot; this text never directly mutates the source proposal or MediaBuy.', + min_length=1, + ), + ] = None + criteria: Annotated[ + product_discovery_criteria.ProductDiscoveryCriteria | None, + Field( + description='Structured discovery changes for action revise. Each present field replaces that criterion from the source proposal; omitted fields inherit. Concrete targeting, future targeting support, and offer filters belong here rather than being repeated in ask.' + ), + ] = None + remove_media_buy_frequency_cap: Annotated[ + Literal[True] | None, + Field( + description='Request a revised draft whose commercial terms omit the existing MediaBuy frequency cap. Omission inherits it. To replace it, provide criteria.media_buy_frequency_cap instead.' + ), + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.media_buy.proposal_refinement import ( - Action, - Alternatives, - ChangeKind, - Constraints, - Constraints1, - Constraints2, - Constraints3, - Constraints4, - Constraints5, - Constraints6, - Constraints7, - Cpm, - Flight, - Impressions, - ProposalRefinement, - ProposalRefinement2, - ProposalRefinement3, - ProposalRefinement4, - ProposalRefinement5, - ProposalRefinement6, - ProposalRefinement7, - ProposalRefinement8, - ProposalRefinement9, -) - -# Explicit exports -__all__ = [ - "Action", - "Alternatives", - "ChangeKind", - "Constraints", - "Constraints1", - "Constraints2", - "Constraints3", - "Constraints4", - "Constraints5", - "Constraints6", - "Constraints7", - "Cpm", - "Flight", - "Impressions", - "ProposalRefinement", - "ProposalRefinement2", - "ProposalRefinement3", - "ProposalRefinement4", - "ProposalRefinement5", - "ProposalRefinement6", - "ProposalRefinement7", - "ProposalRefinement8", - "ProposalRefinement9", +ProposalRefinement = Annotated[ + ProposalRefinement2 + | ProposalRefinement3 + | ProposalRefinement4 + | ProposalRefinement5 + | ProposalRefinement6 + | ProposalRefinement7 + | ProposalRefinement8 + | ProposalRefinement9, + Field( + description='Fork an immutable proposal or finalize a draft into a held committed snapshot. Revising with structured criteria, typed boundaries, product changes, requested alternatives, and/or a semantic ask creates one or more new drafts; finalizing changes no terms. Refining an accepted proposal creates a draft amendment or cancellation proposal against its MediaBuy; the source remains accepted and unchanged. A revised product mix must support the resulting root frequency cap; because omitted criteria inherit, clearing the cap is explicit through remove_media_buy_frequency_cap.', + title='Proposal Refinement', + ), ] diff --git a/src/adcp/types/domains/media_buy/provide_performance_feedback_request.py b/src/adcp/types/domains/media_buy/provide_performance_feedback_request.py index 1fe752bba..f4583aacd 100644 --- a/src/adcp/types/domains/media_buy/provide_performance_feedback_request.py +++ b/src/adcp/types/domains/media_buy/provide_performance_feedback_request.py @@ -1,20 +1,33 @@ -"""Types declared by the AdCP ``media_buy/provide_performance_feedback_request`` schema. +# generated by datamodel-codegen: +# filename: media_buy/provide_performance_feedback_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.provide_performance_feedback_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field + +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.performance_feedback_assertion import PerformanceFeedbackAssertion +from ..core.version_envelope import AdcpVersionEnvelope -from adcp.types.generated_poc.media_buy.provide_performance_feedback_request import ( - ProvidePerformanceFeedbackRequest, -) -# Explicit exports -__all__ = ["ProvidePerformanceFeedbackRequest"] +class ProvidePerformanceFeedbackRequest(AdcpRequest, AdcpVersionEnvelope, PerformanceFeedbackAssertion): + model_config = ConfigDict( + extra='allow', + ) + idempotency_key: Annotated[ + str, + Field( + description='Client-generated unique key for this logical assertion. MUST be unique per receiving agent to prevent cross-agent correlation; use a fresh UUID v4 for each new assertion. Retries use the same key and payload.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/provide_performance_feedback_response.py b/src/adcp/types/domains/media_buy/provide_performance_feedback_response.py index cfe38cc34..14ebe83cd 100644 --- a/src/adcp/types/domains/media_buy/provide_performance_feedback_response.py +++ b/src/adcp/types/domains/media_buy/provide_performance_feedback_response.py @@ -1,26 +1,47 @@ -"""Types declared by the AdCP ``media_buy/provide_performance_feedback_response`` schema. +# generated by datamodel-codegen: +# filename: media_buy/provide_performance_feedback_response.json +# timestamp: preserved-by-post-generate-fixes -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.provide_performance_feedback_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Literal, TypeAlias -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import AwareDatetime, ConfigDict, Field, StringConstraints + +from ..core.version_envelope import AdcpVersionEnvelope +from ..core import context as context_1 +from ..core import error as error_1 +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope + + +class ProvidePerformanceFeedbackResponse1(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + success: Literal[True] + feedback_id: Annotated[str, StringConstraints(min_length=1)] | None = None + application_status: Literal['accepted', 'applied', 'not_applied'] | None = None + status_reason: Annotated[str, StringConstraints(max_length=500)] | None = None + received_at: AwareDatetime | None = None + applied_at: AwareDatetime | None = None + sandbox: bool | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class ProvidePerformanceFeedbackResponse2(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + errors: Annotated[list[error_1.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +ProvidePerformanceFeedbackResponse: TypeAlias = ProvidePerformanceFeedbackResponse1 | ProvidePerformanceFeedbackResponse2 -from adcp.types.generated_poc.media_buy.provide_performance_feedback_response import ( - ProvidePerformanceFeedbackResponse, - ProvidePerformanceFeedbackResponse1, - ProvidePerformanceFeedbackResponse2, -) -# Explicit exports __all__ = [ - "ProvidePerformanceFeedbackResponse", - "ProvidePerformanceFeedbackResponse1", - "ProvidePerformanceFeedbackResponse2", + 'ProvidePerformanceFeedbackResponse', + 'ProvidePerformanceFeedbackResponse1', + 'ProvidePerformanceFeedbackResponse2', ] diff --git a/src/adcp/types/domains/media_buy/refine_proposals_async_response_input_required.py b/src/adcp/types/domains/media_buy/refine_proposals_async_response_input_required.py index 9aeeff4d6..41f47bcb5 100644 --- a/src/adcp/types/domains/media_buy/refine_proposals_async_response_input_required.py +++ b/src/adcp/types/domains/media_buy/refine_proposals_async_response_input_required.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``media_buy/refine_proposals_async_response_input_required`` schema. +# generated by datamodel-codegen: +# filename: media_buy/refine_proposals_async_response_input_required.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.media_buy.refine_proposals_async_response_input_required import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.media_buy.refine_proposals_async_response_input_required import ( - RefineProposalsInputRequired, -) +from ..core.compact_task_input_required import CompactTaskInputRequired + -# Explicit exports -__all__ = ["RefineProposalsInputRequired"] +class RefineProposalsInputRequired(CompactTaskInputRequired): + pass diff --git a/src/adcp/types/domains/media_buy/refine_proposals_async_response_submitted.py b/src/adcp/types/domains/media_buy/refine_proposals_async_response_submitted.py index 03d0f137a..a21e49ec9 100644 --- a/src/adcp/types/domains/media_buy/refine_proposals_async_response_submitted.py +++ b/src/adcp/types/domains/media_buy/refine_proposals_async_response_submitted.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``media_buy/refine_proposals_async_response_submitted`` schema. +# generated by datamodel-codegen: +# filename: media_buy/refine_proposals_async_response_submitted.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.media_buy.refine_proposals_async_response_submitted import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.media_buy.refine_proposals_async_response_submitted import ( - RefineProposalsSubmitted, -) +from ..core.compact_task_submitted import CompactTaskSubmitted + -# Explicit exports -__all__ = ["RefineProposalsSubmitted"] +class RefineProposalsSubmitted(CompactTaskSubmitted): + pass diff --git a/src/adcp/types/domains/media_buy/refine_proposals_async_response_working.py b/src/adcp/types/domains/media_buy/refine_proposals_async_response_working.py index 1f03ddf86..b885821a3 100644 --- a/src/adcp/types/domains/media_buy/refine_proposals_async_response_working.py +++ b/src/adcp/types/domains/media_buy/refine_proposals_async_response_working.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``media_buy/refine_proposals_async_response_working`` schema. +# generated by datamodel-codegen: +# filename: media_buy/refine_proposals_async_response_working.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.media_buy.refine_proposals_async_response_working import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.media_buy.refine_proposals_async_response_working import ( - RefineProposalsWorking, -) +from ..core.compact_task_working import CompactTaskWorking + -# Explicit exports -__all__ = ["RefineProposalsWorking"] +class RefineProposalsWorking(CompactTaskWorking): + pass diff --git a/src/adcp/types/domains/media_buy/refine_proposals_request.py b/src/adcp/types/domains/media_buy/refine_proposals_request.py index 0052ea7e3..0a9283d4b 100644 --- a/src/adcp/types/domains/media_buy/refine_proposals_request.py +++ b/src/adcp/types/domains/media_buy/refine_proposals_request.py @@ -1,21 +1,54 @@ -"""Types declared by the AdCP ``media_buy/refine_proposals_request`` schema. +# generated by datamodel-codegen: +# filename: media_buy/refine_proposals_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.refine_proposals_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field + +from ..core import context as context_1 +from ..core import push_notification_config as push_notification_config_1 +from ..core.version_envelope import AdcpVersionEnvelope +from . import proposal_refinement + + +class Refinements(AdCPBaseModel): + pass -from adcp.types.generated_poc.media_buy.refine_proposals_request import ( - RefineProposalsRequest, - Refinements, -) -# Explicit exports -__all__ = ["RefineProposalsRequest", "Refinements"] +class RefineProposalsRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='forbid', + ) + context_id: Annotated[ + str | None, + Field( + description='MCP compatibility field: servers ignore this value; A2A uses transport-native Message/Task contextId.', + min_length=1, + ), + ] = None + context: context_1.ContextObject | None = None + governance_context: Annotated[str | None, Field(max_length=4096, min_length=1)] = None + push_notification_config: push_notification_config_1.PushNotificationConfig | None = None + idempotency_key: Annotated[ + str, + Field( + description='Client-generated key required for retry-safe proposal refinement.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + refinements: Annotated[ + list[proposal_refinement.ProposalRefinement] | Refinements, + Field( + description='Proposal operations to apply, with at most 25 entries per request. revise creates a draft successor from a draft, committed, or accepted source. finalize MUST target a draft, reserves inventory, and creates a committed successor whose expires_at is the hold deadline. A batch containing finalize MUST contain only finalize entries and is atomic. proposal_id values MUST be unique; results preserve request order.', + max_length=25, + min_length=1, + ), + ] diff --git a/src/adcp/types/domains/media_buy/refine_proposals_response.py b/src/adcp/types/domains/media_buy/refine_proposals_response.py index bd3db357a..3a5ea7f32 100644 --- a/src/adcp/types/domains/media_buy/refine_proposals_response.py +++ b/src/adcp/types/domains/media_buy/refine_proposals_response.py @@ -1,74 +1,624 @@ -"""Types declared by the AdCP ``media_buy/refine_proposals_response`` schema. +# generated by datamodel-codegen: +# filename: media_buy/refine_proposals_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.refine_proposals_response import +from adcp.types.base import AdcpResponse, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal + +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat + +from ..core import canonical_delivery_forecast, canonical_product, canonical_proposal +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 +from ..core import insertion_order as insertion_order_1 +from ..core import product_change_map +from ..core.compact_task_submitted import CompactTaskSubmitted +from ..enums import proposal_refinement_reason +from . import commercial_terms as commercial_terms_1 +from . import get_products_targeting_resolution + + +class Outcome(StrEnum): + revised = 'revised' + partial = 'partial' + finalized = 'finalized' + unable = 'unable' + + +class ProposalKind(StrEnum): + new_media_buy = 'new_media_buy' + media_buy_update = 'media_buy_update' + media_buy_cancellation = 'media_buy_cancellation' + + +class ProposalStatus(StrEnum): + draft = 'draft' + committed = 'committed' + accepted = 'accepted' + + +class TotalBudgetGuidance(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + min: Annotated[StrictFloat | None, Field(ge=0.0)] = None + recommended: Annotated[StrictFloat | None, Field(ge=0.0)] = None + max: Annotated[StrictFloat | None, Field(ge=0.0)] = None + currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] + + +class UnsatisfiedConstraint(ScalarStr): + __slots__ = () + _constraints = {'min_length': 1} + + +class Suggestion(UnsatisfiedConstraint): + pass + + +class Results(AdCPBaseModel): + pass + + +class Status(StrEnum): + completed = 'completed' + submitted = 'submitted' + + +class Proposal(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + proposal_id: Annotated[str, Field(max_length=255, min_length=1)] + proposal_kind: ProposalKind + parent_proposal_id: Annotated[ + str, + Field( + description="Immediate predecessor this snapshot was forked from. Every proposal produced by refine_proposals carries it, equal to the request's source proposal_id, so negotiation lineage is reconstructible from proposals alone.", + max_length=255, + min_length=1, + ), + ] + media_buy_id: Annotated[str | None, Field(min_length=1)] = None + opportunity_id: Annotated[ + str | None, + Field( + description='Buyer planning cycle associated with this proposal. Revisions inherit it; it does not participate in proposal identity.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] = None + base_media_buy_revision: Annotated[SchemaInt | None, Field(ge=1)] = None + proposal_status: Annotated[ + Literal['draft'], + Field( + description='draft is indicative and unreserved; committed has firm terms with inventory reserved until expires_at; accepted is the historical snapshot attached to a MediaBuy.', + title='Proposal Status', + ), + ] = 'draft' + accepted_at: AwareDatetime | None = None + expires_at: Annotated[ + AwareDatetime | None, + Field( + description='For a draft, the indicative-terms freshness deadline. For a committed proposal, the inventory-hold deadline.' + ), + ] = None + name: Annotated[str, Field(max_length=500, min_length=1)] + description: Annotated[str | None, Field(max_length=2000)] = None + brief_alignment: Annotated[str | None, Field(max_length=2000)] = None + commercial_terms: commercial_terms_1.CommercialTerms + terms_digest: Annotated[ + str, + Field( + description='Base64url SHA-256 digest of the RFC 8785 JCS serialization of commercial_terms, prefixed with sha256:.', + pattern='^sha256:[A-Za-z0-9_-]{43}$', + ), + ] + insertion_order: insertion_order_1.InsertionOrder | None = None + total_budget_guidance: Annotated[ + TotalBudgetGuidance | None, + Field( + description="Optional budget guidance for this proposal — the planning answer to criteria.outcome_target and to open-budget briefs. commercial_terms.total_budget remains the concrete figure the plan is priced at; this band expresses the seller's recommended range around it. When criteria.outcome_target carries cost_per, the cost answer is commercial_terms.bidding.cost_per and this band's currency equals cost_per.currency." + ), + ] = None + forecast: Annotated[ + canonical_delivery_forecast.CanonicalDeliveryForecast | None, + Field( + description="Aggregate forecasted delivery for the proposal. For outcome_target requests, points carry the goal's metric or event key in metrics; with cost_per, that is the goal volume planned under the commercial_terms.bidding policy, and currency equals cost_per.currency." + ), + ] = None + + +class Proposal2(Proposal): + pass + + +class Proposal3(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + proposal_id: Annotated[str, Field(max_length=255, min_length=1)] + proposal_kind: ProposalKind + parent_proposal_id: Annotated[ + str, + Field( + description="Immediate predecessor this snapshot was forked from. Every proposal produced by refine_proposals carries it, equal to the request's source proposal_id, so negotiation lineage is reconstructible from proposals alone.", + max_length=255, + min_length=1, + ), + ] + media_buy_id: Annotated[str | None, Field(min_length=1)] = None + opportunity_id: Annotated[ + str | None, + Field( + description='Buyer planning cycle associated with this proposal. Revisions inherit it; it does not participate in proposal identity.', + max_length=255, + min_length=1, + pattern='^[A-Za-z0-9_.:-]{1,255}$', + ), + ] = None + base_media_buy_revision: Annotated[SchemaInt | None, Field(ge=1)] = None + proposal_status: Annotated[ + Literal['committed'], + Field( + description='draft is indicative and unreserved; committed has firm terms with inventory reserved until expires_at; accepted is the historical snapshot attached to a MediaBuy.', + title='Proposal Status', + ), + ] = 'committed' + accepted_at: AwareDatetime | None = None + expires_at: Annotated[ + AwareDatetime, + Field( + description='For a draft, the indicative-terms freshness deadline. For a committed proposal, the inventory-hold deadline.' + ), + ] + name: Annotated[str, Field(max_length=500, min_length=1)] + description: Annotated[str | None, Field(max_length=2000)] = None + brief_alignment: Annotated[str | None, Field(max_length=2000)] = None + commercial_terms: commercial_terms_1.CommercialTerms + terms_digest: Annotated[ + str, + Field( + description='Base64url SHA-256 digest of the RFC 8785 JCS serialization of commercial_terms, prefixed with sha256:.', + pattern='^sha256:[A-Za-z0-9_-]{43}$', + ), + ] + insertion_order: insertion_order_1.InsertionOrder | None = None + total_budget_guidance: Annotated[ + TotalBudgetGuidance | None, + Field( + description="Optional budget guidance for this proposal — the planning answer to criteria.outcome_target and to open-budget briefs. commercial_terms.total_budget remains the concrete figure the plan is priced at; this band expresses the seller's recommended range around it. When criteria.outcome_target carries cost_per, the cost answer is commercial_terms.bidding.cost_per and this band's currency equals cost_per.currency." + ), + ] = None + forecast: Annotated[ + canonical_delivery_forecast.CanonicalDeliveryForecast | None, + Field( + description="Aggregate forecasted delivery for the proposal. For outcome_target requests, points carry the goal's metric or event key in metrics; with cost_per, that is the goal volume planned under the commercial_terms.bidding policy, and currency equals cost_per.currency." + ), + ] = None + + +class Proposal4(Proposal): + pass + + +class Proposal5(Proposal): + pass + + +class Proposal6(Proposal3): + pass + + +class Results9(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + source_proposal_id: Annotated[str, Field(min_length=1)] + outcome: Literal['revised'] = 'revised' + proposal: canonical_proposal.CanonicalProposal | None = None + proposals: Annotated[ + list[Proposal], + Field( + description='Draft successors produced for a revision. Without alternatives this contains one proposal. With alternatives.count, revised contains exactly that many proposals with unique terms_digest values; fewer or commercially duplicate proposals require partial.', + min_length=1, + ), + ] + reason_code: Annotated[ + proposal_refinement_reason.ProposalRefinementReason | None, + Field( + description='Single most-significant code for this result. constraint_unsatisfiable takes precedence over every other code; an alternatives shortfall alongside an unsatisfied constraint remains visible through proposals.length.' + ), + ] = None + reason: Annotated[str | None, Field(min_length=1)] = None + unsatisfied_constraints: Annotated[ + list[UnsatisfiedConstraint] | None, + Field( + description='Stable keys from the request constraints object that were not satisfied by every returned draft. A result carrying any key here MUST use outcome partial or unable, never revised.', + min_length=1, + ), + ] = None + unsatisfied_product_changes: Annotated[ + product_change_map.ProductChangeMap | None, + Field( + description='Requested product actions not satisfied by every returned draft. This is a subset of the request product_changes map and is valid only on partial or unable results.' + ), + ] = None + suggestions: Annotated[list[Suggestion] | None, Field(min_length=1)] = None + targeting_resolution: Annotated[ + get_products_targeting_resolution.ProductDiscoveryTargetingResolution | None, + Field( + description="Confirmation of structured targeting interpreted from this refinement's explicit hard prose instructions. Required when that interpretation materially affects the revised proposal's eligibility, pricing, or forecast; otherwise a best practice." + ), + ] = None + + +class Results10(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + source_proposal_id: Annotated[str, Field(min_length=1)] + outcome: Literal['partial'] = 'partial' + proposal: canonical_proposal.CanonicalProposal | None = None + proposals: Annotated[ + list[Proposal2], + Field( + description='Draft successors produced for a revision. Without alternatives this contains one proposal. With alternatives.count, revised contains exactly that many proposals with unique terms_digest values; fewer or commercially duplicate proposals require partial.', + min_length=1, + ), + ] + reason_code: Annotated[ + proposal_refinement_reason.ProposalRefinementReason, + Field( + description='Single most-significant code for this result. constraint_unsatisfiable takes precedence over every other code; an alternatives shortfall alongside an unsatisfied constraint remains visible through proposals.length.' + ), + ] + reason: Annotated[str, Field(min_length=1)] + unsatisfied_constraints: Annotated[ + list[UnsatisfiedConstraint] | None, + Field( + description='Stable keys from the request constraints object that were not satisfied by every returned draft. A result carrying any key here MUST use outcome partial or unable, never revised.', + min_length=1, + ), + ] = None + unsatisfied_product_changes: Annotated[ + product_change_map.ProductChangeMap | None, + Field( + description='Requested product actions not satisfied by every returned draft. This is a subset of the request product_changes map and is valid only on partial or unable results.' + ), + ] = None + suggestions: Annotated[list[Suggestion] | None, Field(min_length=1)] = None + targeting_resolution: Annotated[ + get_products_targeting_resolution.ProductDiscoveryTargetingResolution | None, + Field( + description="Confirmation of structured targeting interpreted from this refinement's explicit hard prose instructions. Required when that interpretation materially affects the revised proposal's eligibility, pricing, or forecast; otherwise a best practice." + ), + ] = None + + +class Results11(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + source_proposal_id: Annotated[str, Field(min_length=1)] + outcome: Literal['finalized'] = 'finalized' + proposal: Annotated[ + Proposal3, + Field( + description='Compact immutable proposal for the AdCP 3.2 lifecycle. commercial_terms is the sole authoritative commercial envelope; narrative fields do not duplicate legacy allocation or creative graphs.', + title='Canonical Proposal', + ), + ] + proposals: Annotated[ + list[canonical_proposal.CanonicalProposal] | None, + Field( + description='Draft successors produced for a revision. Without alternatives this contains one proposal. With alternatives.count, revised contains exactly that many proposals with unique terms_digest values; fewer or commercially duplicate proposals require partial.', + min_length=1, + ), + ] = None + reason_code: Annotated[ + proposal_refinement_reason.ProposalRefinementReason | None, + Field( + description='Single most-significant code for this result. constraint_unsatisfiable takes precedence over every other code; an alternatives shortfall alongside an unsatisfied constraint remains visible through proposals.length.' + ), + ] = None + reason: Annotated[str | None, Field(min_length=1)] = None + unsatisfied_constraints: Annotated[ + list[UnsatisfiedConstraint] | None, + Field( + description='Stable keys from the request constraints object that were not satisfied by every returned draft. A result carrying any key here MUST use outcome partial or unable, never revised.', + min_length=1, + ), + ] = None + unsatisfied_product_changes: Annotated[ + product_change_map.ProductChangeMap | None, + Field( + description='Requested product actions not satisfied by every returned draft. This is a subset of the request product_changes map and is valid only on partial or unable results.' + ), + ] = None + suggestions: Annotated[list[Suggestion] | None, Field(min_length=1)] = None + targeting_resolution: Annotated[ + get_products_targeting_resolution.ProductDiscoveryTargetingResolution | None, + Field( + description="Confirmation of structured targeting interpreted from this refinement's explicit hard prose instructions. Required when that interpretation materially affects the revised proposal's eligibility, pricing, or forecast; otherwise a best practice." + ), + ] = None + + +class Results12(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + source_proposal_id: Annotated[str, Field(min_length=1)] + outcome: Literal['unable'] = 'unable' + proposal: canonical_proposal.CanonicalProposal | None = None + proposals: Annotated[ + list[canonical_proposal.CanonicalProposal] | None, + Field( + description='Draft successors produced for a revision. Without alternatives this contains one proposal. With alternatives.count, revised contains exactly that many proposals with unique terms_digest values; fewer or commercially duplicate proposals require partial.', + min_length=1, + ), + ] = None + reason_code: Annotated[ + proposal_refinement_reason.ProposalRefinementReason, + Field( + description='Single most-significant code for this result. constraint_unsatisfiable takes precedence over every other code; an alternatives shortfall alongside an unsatisfied constraint remains visible through proposals.length.' + ), + ] + reason: Annotated[str, Field(min_length=1)] + unsatisfied_constraints: Annotated[ + list[UnsatisfiedConstraint] | None, + Field( + description='Stable keys from the request constraints object that were not satisfied by every returned draft. A result carrying any key here MUST use outcome partial or unable, never revised.', + min_length=1, + ), + ] = None + unsatisfied_product_changes: Annotated[ + product_change_map.ProductChangeMap | None, + Field( + description='Requested product actions not satisfied by every returned draft. This is a subset of the request product_changes map and is valid only on partial or unable results.' + ), + ] = None + suggestions: Annotated[list[Suggestion] | None, Field(min_length=1)] = None + targeting_resolution: Annotated[ + get_products_targeting_resolution.ProductDiscoveryTargetingResolution | None, + Field( + description="Confirmation of structured targeting interpreted from this refinement's explicit hard prose instructions. Required when that interpretation materially affects the revised proposal's eligibility, pricing, or forecast; otherwise a best practice." + ), + ] = None + + +Results8 = Annotated[ + Results9 | Results10 | Results11 | Results12, + Field(discriminator='outcome'), +] + + +class Results15(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + source_proposal_id: Annotated[str, Field(min_length=1)] + outcome: Literal['revised'] = 'revised' + proposal: canonical_proposal.CanonicalProposal | None = None + proposals: Annotated[ + list[Proposal4], + Field( + description='Draft successors produced for a revision. Without alternatives this contains one proposal. With alternatives.count, revised contains exactly that many proposals with unique terms_digest values; fewer or commercially duplicate proposals require partial.', + min_length=1, + ), + ] + reason_code: Annotated[ + proposal_refinement_reason.ProposalRefinementReason | None, + Field( + description='Single most-significant code for this result. constraint_unsatisfiable takes precedence over every other code; an alternatives shortfall alongside an unsatisfied constraint remains visible through proposals.length.' + ), + ] = None + reason: Annotated[str | None, Field(min_length=1)] = None + unsatisfied_constraints: Annotated[ + list[UnsatisfiedConstraint] | None, + Field( + description='Stable keys from the request constraints object that were not satisfied by every returned draft. A result carrying any key here MUST use outcome partial or unable, never revised.', + min_length=1, + ), + ] = None + unsatisfied_product_changes: Annotated[ + product_change_map.ProductChangeMap | None, + Field( + description='Requested product actions not satisfied by every returned draft. This is a subset of the request product_changes map and is valid only on partial or unable results.' + ), + ] = None + suggestions: Annotated[list[Suggestion] | None, Field(min_length=1)] = None + targeting_resolution: Annotated[ + get_products_targeting_resolution.ProductDiscoveryTargetingResolution | None, + Field( + description="Confirmation of structured targeting interpreted from this refinement's explicit hard prose instructions. Required when that interpretation materially affects the revised proposal's eligibility, pricing, or forecast; otherwise a best practice." + ), + ] = None + + +class Results16(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + source_proposal_id: Annotated[str, Field(min_length=1)] + outcome: Literal['partial'] = 'partial' + proposal: canonical_proposal.CanonicalProposal | None = None + proposals: Annotated[ + list[Proposal5], + Field( + description='Draft successors produced for a revision. Without alternatives this contains one proposal. With alternatives.count, revised contains exactly that many proposals with unique terms_digest values; fewer or commercially duplicate proposals require partial.', + min_length=1, + ), + ] + reason_code: Annotated[ + proposal_refinement_reason.ProposalRefinementReason, + Field( + description='Single most-significant code for this result. constraint_unsatisfiable takes precedence over every other code; an alternatives shortfall alongside an unsatisfied constraint remains visible through proposals.length.' + ), + ] + reason: Annotated[str, Field(min_length=1)] + unsatisfied_constraints: Annotated[ + list[UnsatisfiedConstraint] | None, + Field( + description='Stable keys from the request constraints object that were not satisfied by every returned draft. A result carrying any key here MUST use outcome partial or unable, never revised.', + min_length=1, + ), + ] = None + unsatisfied_product_changes: Annotated[ + product_change_map.ProductChangeMap | None, + Field( + description='Requested product actions not satisfied by every returned draft. This is a subset of the request product_changes map and is valid only on partial or unable results.' + ), + ] = None + suggestions: Annotated[list[Suggestion] | None, Field(min_length=1)] = None + targeting_resolution: Annotated[ + get_products_targeting_resolution.ProductDiscoveryTargetingResolution | None, + Field( + description="Confirmation of structured targeting interpreted from this refinement's explicit hard prose instructions. Required when that interpretation materially affects the revised proposal's eligibility, pricing, or forecast; otherwise a best practice." + ), + ] = None + + +class Results17(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + source_proposal_id: Annotated[str, Field(min_length=1)] + outcome: Literal['finalized'] = 'finalized' + proposal: Annotated[ + Proposal6, + Field( + description='Compact immutable proposal for the AdCP 3.2 lifecycle. commercial_terms is the sole authoritative commercial envelope; narrative fields do not duplicate legacy allocation or creative graphs.', + title='Canonical Proposal', + ), + ] + proposals: Annotated[ + list[canonical_proposal.CanonicalProposal] | None, + Field( + description='Draft successors produced for a revision. Without alternatives this contains one proposal. With alternatives.count, revised contains exactly that many proposals with unique terms_digest values; fewer or commercially duplicate proposals require partial.', + min_length=1, + ), + ] = None + reason_code: Annotated[ + proposal_refinement_reason.ProposalRefinementReason | None, + Field( + description='Single most-significant code for this result. constraint_unsatisfiable takes precedence over every other code; an alternatives shortfall alongside an unsatisfied constraint remains visible through proposals.length.' + ), + ] = None + reason: Annotated[str | None, Field(min_length=1)] = None + unsatisfied_constraints: Annotated[ + list[UnsatisfiedConstraint] | None, + Field( + description='Stable keys from the request constraints object that were not satisfied by every returned draft. A result carrying any key here MUST use outcome partial or unable, never revised.', + min_length=1, + ), + ] = None + unsatisfied_product_changes: Annotated[ + product_change_map.ProductChangeMap | None, + Field( + description='Requested product actions not satisfied by every returned draft. This is a subset of the request product_changes map and is valid only on partial or unable results.' + ), + ] = None + suggestions: Annotated[list[Suggestion] | None, Field(min_length=1)] = None + targeting_resolution: Annotated[ + get_products_targeting_resolution.ProductDiscoveryTargetingResolution | None, + Field( + description="Confirmation of structured targeting interpreted from this refinement's explicit hard prose instructions. Required when that interpretation materially affects the revised proposal's eligibility, pricing, or forecast; otherwise a best practice." + ), + ] = None + + +class Results18(Results12): + outcome: Literal['unable'] = 'unable' + + +Results14 = Annotated[ + Results15 | Results16 | Results17 | Results18, + Field(discriminator='outcome'), +] + + +class RefineProposalsResponse1(AdcpResponse, AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + adcp_version: Annotated[ + str | None, + Field( + description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', + examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], + pattern='^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?)?$', + ), + ] = None + results: Annotated[ + list[Results8] | Results, + Field( + description="Ordered results. If any result is finalized, every result MUST be finalized; a finalize batch either creates every requested hold or none. Every returned proposal carries parent_proposal_id equal to the result's source_proposal_id, making negotiation lineage reconstructible from the proposals alone.", + min_length=1, + ), + ] + products: Annotated[ + list[canonical_product.CanonicalProduct], + Field( + description="Canonical products needed to evaluate the resulting terms. For revised or partial results whose effective criteria contain property or collection lists, each affected product MUST carry fresh list_applications receipts from the revision's product reevaluation. Finalization changes no terms and MAY repeat the receipts already bound to the source proposal rather than reevaluating them." + ), + ] + status: Literal['completed'] = 'completed' + task_id: Annotated[str | None, Field(min_length=1)] = None + message: Annotated[str | None, Field(max_length=2000)] = None + errors: list[error.Error] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + replayed: Literal[True] | None = None + + +class RefineProposalsResponse2(AdcpResponse, CompactTaskSubmitted): + model_config = ConfigDict( + extra='forbid', + ) + adcp_version: Annotated[ + str | None, + Field( + description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', + examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], + pattern='^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?)?$', + ), + ] = None + results: Annotated[ + list[Results14] | Results | None, + Field( + description="Ordered results. If any result is finalized, every result MUST be finalized; a finalize batch either creates every requested hold or none. Every returned proposal carries parent_proposal_id equal to the result's source_proposal_id, making negotiation lineage reconstructible from the proposals alone.", + min_length=1, + ), + ] = None + products: Annotated[ + list[canonical_product.CanonicalProduct] | None, + Field( + description="Canonical products needed to evaluate the resulting terms. For revised or partial results whose effective criteria contain property or collection lists, each affected product MUST carry fresh list_applications receipts from the revision's product reevaluation. Finalization changes no terms and MAY repeat the receipts already bound to the source proposal rather than reevaluating them." + ), + ] = None + status: Status | None = None + task_id: Annotated[str | None, Field(min_length=1)] = None + message: Annotated[str | None, Field(max_length=2000)] = None + errors: list[error.Error] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + replayed: Literal[True] | None = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.media_buy.refine_proposals_response import ( - Outcome, - Proposal, - Proposal2, - Proposal3, - Proposal4, - Proposal5, - Proposal6, - ProposalKind, - ProposalStatus, - RefineProposalsResponse, - RefineProposalsResponse1, - RefineProposalsResponse2, - Results, - Results10, - Results11, - Results12, - Results14, - Results15, - Results16, - Results17, - Results18, - Results8, - Results9, - Status, - Suggestion, - TotalBudgetGuidance, - UnsatisfiedConstraint, -) - -# Explicit exports -__all__ = [ - "Outcome", - "Proposal", - "Proposal2", - "Proposal3", - "Proposal4", - "Proposal5", - "Proposal6", - "ProposalKind", - "ProposalStatus", - "RefineProposalsResponse", - "RefineProposalsResponse1", - "RefineProposalsResponse2", - "Results", - "Results10", - "Results11", - "Results12", - "Results14", - "Results15", - "Results16", - "Results17", - "Results18", - "Results8", - "Results9", - "Status", - "Suggestion", - "TotalBudgetGuidance", - "UnsatisfiedConstraint", +RefineProposalsResponse = Annotated[ + RefineProposalsResponse1 | RefineProposalsResponse2, + Field( + description='One ordered result per requested source proposal. Revision results may carry multiple immutable draft proposals when alternatives were requested; finalization remains one committed proposal per source. Products contains the compact canonical products needed to evaluate the resulting terms.', + title='Refine Proposals Response', + ), ] diff --git a/src/adcp/types/domains/media_buy/request_proposals_async_response_input_required.py b/src/adcp/types/domains/media_buy/request_proposals_async_response_input_required.py index 142d38ae6..217b29631 100644 --- a/src/adcp/types/domains/media_buy/request_proposals_async_response_input_required.py +++ b/src/adcp/types/domains/media_buy/request_proposals_async_response_input_required.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``media_buy/request_proposals_async_response_input_required`` schema. +# generated by datamodel-codegen: +# filename: media_buy/request_proposals_async_response_input_required.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.media_buy.request_proposals_async_response_input_required import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.media_buy.request_proposals_async_response_input_required import ( - RequestProposalsInputRequired, -) +from ..core.compact_task_input_required import CompactTaskInputRequired + -# Explicit exports -__all__ = ["RequestProposalsInputRequired"] +class RequestProposalsInputRequired(CompactTaskInputRequired): + pass diff --git a/src/adcp/types/domains/media_buy/request_proposals_async_response_submitted.py b/src/adcp/types/domains/media_buy/request_proposals_async_response_submitted.py index ef6012d4b..d24f29179 100644 --- a/src/adcp/types/domains/media_buy/request_proposals_async_response_submitted.py +++ b/src/adcp/types/domains/media_buy/request_proposals_async_response_submitted.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``media_buy/request_proposals_async_response_submitted`` schema. +# generated by datamodel-codegen: +# filename: media_buy/request_proposals_async_response_submitted.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.media_buy.request_proposals_async_response_submitted import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.media_buy.request_proposals_async_response_submitted import ( - RequestProposalsSubmitted, -) +from ..core.compact_task_submitted import CompactTaskSubmitted + -# Explicit exports -__all__ = ["RequestProposalsSubmitted"] +class RequestProposalsSubmitted(CompactTaskSubmitted): + pass diff --git a/src/adcp/types/domains/media_buy/request_proposals_async_response_working.py b/src/adcp/types/domains/media_buy/request_proposals_async_response_working.py index 266b87199..b9461e8d8 100644 --- a/src/adcp/types/domains/media_buy/request_proposals_async_response_working.py +++ b/src/adcp/types/domains/media_buy/request_proposals_async_response_working.py @@ -1,20 +1,11 @@ -"""Types declared by the AdCP ``media_buy/request_proposals_async_response_working`` schema. +# generated by datamodel-codegen: +# filename: media_buy/request_proposals_async_response_working.json +# timestamp: 2026-08-17T23:02:13+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.media_buy.request_proposals_async_response_working import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.media_buy.request_proposals_async_response_working import ( - RequestProposalsWorking, -) +from ..core.compact_task_working import CompactTaskWorking + -# Explicit exports -__all__ = ["RequestProposalsWorking"] +class RequestProposalsWorking(CompactTaskWorking): + pass diff --git a/src/adcp/types/domains/media_buy/request_proposals_request.py b/src/adcp/types/domains/media_buy/request_proposals_request.py index 8e1c1c977..22ceeb8ec 100644 --- a/src/adcp/types/domains/media_buy/request_proposals_request.py +++ b/src/adcp/types/domains/media_buy/request_proposals_request.py @@ -1,22 +1,97 @@ -"""Types declared by the AdCP ``media_buy/request_proposals_request`` schema. +# generated by datamodel-codegen: +# filename: media_buy/request_proposals_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.request_proposals_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal + +from pydantic import ConfigDict, Field, model_validator + +from ..core import brand_key, canonical_account_ref +from ..core import context as context_1 +from ..core import push_notification_config as push_notification_config_1 +from ..core.opportunity_context import OpportunityContext +from ..core.version_envelope import AdcpVersionEnvelope +from . import product_discovery_criteria + + +class Status(StrEnum): + open = 'open' + closed = 'closed' + + +class Opportunity(OpportunityContext): + status: Annotated[ + Literal['open'], + Field( + description='Whether the planning cycle remains open. On calls after initial creation, omission means no status update and MUST NOT reopen or close the opportunity implicitly, except that successful create_media_buy proposal execution explicitly infers accepted closure.' + ), + ] = 'open' -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.media_buy.request_proposals_request import ( - Opportunity, - RequestProposalsRequest, - Status, -) +class RequestProposalsRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='forbid', + ) + context_id: Annotated[ + str | None, + Field( + description='MCP compatibility field: servers ignore this value; A2A uses transport-native Message/Task contextId.', + min_length=1, + ), + ] = None + context: context_1.ContextObject | None = None + governance_context: Annotated[str | None, Field(max_length=4096, min_length=1)] = None + push_notification_config: push_notification_config_1.PushNotificationConfig | None = None + idempotency_key: Annotated[ + str, + Field( + description='Client-generated key required for retry-safe proposal creation.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + account: Annotated[ + canonical_account_ref.CanonicalAccountReference | None, + Field( + description='Alternative brand source for proposal terms. Provide either this natural-key account containing brand and operator or top-level brand, not both.' + ), + ] = None + brand: Annotated[ + brand_key.BrandKey | None, + Field( + description='Alternative brand source for proposal terms. Provide either top-level brand or a natural-key account containing brand and operator, not both.' + ), + ] = None + brief: Annotated[ + str, + Field( + description='Campaign goal, strategy, and requirements that are not represented in structured criteria.', + min_length=1, + ), + ] + criteria: product_discovery_criteria.ProductDiscoveryCriteria | None = None + opportunity: Annotated[ + Opportunity | None, + Field( + description='Optional planning-cycle context that the seller associates with every proposal created by this request.' + ), + ] = None -# Explicit exports -__all__ = ["Opportunity", "RequestProposalsRequest", "Status"] + @model_validator(mode='after') + def _require_schema_required_group(self) -> RequestProposalsRequest: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('brand',), ('account',),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'RequestProposalsRequest requires at least one of these field groups: brand | account' + ) diff --git a/src/adcp/types/domains/media_buy/request_proposals_response.py b/src/adcp/types/domains/media_buy/request_proposals_response.py index 98b208c19..a83da6b01 100644 --- a/src/adcp/types/domains/media_buy/request_proposals_response.py +++ b/src/adcp/types/domains/media_buy/request_proposals_response.py @@ -1,74 +1,400 @@ -"""Types declared by the AdCP ``media_buy/request_proposals_response`` schema. +# generated by datamodel-codegen: +# filename: media_buy/request_proposals_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.request_proposals_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any, Literal + +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import AwareDatetime, ConfigDict, Field + +from ..core import canonical_product +from ..core import context as context_1 +from ..core import duration, error +from ..core import ext as ext_1 +from ..core.canonical_proposal import CanonicalProposal +from ..core.compact_task_submitted import CompactTaskSubmitted +from . import get_products_targeting_resolution + + +class Outcome(StrEnum): + proposed = 'proposed' + products_available = 'products_available' + rejected = 'rejected' + + +class Suggestion(ScalarStr): + __slots__ = () + _constraints = {'min_length': 1} + + +class ProposalStatus(StrEnum): + draft = 'draft' + committed = 'committed' + accepted = 'accepted' + + +class Scope(StrEnum): + products = 'products' + pricing = 'pricing' + forecast = 'forecast' + proposals = 'proposals' + wholesale_feed = 'wholesale_feed' + + +class IncompleteItem(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + scope: Scope + description: Annotated[str, Field(min_length=1)] + estimated_wait: duration.Duration | None = None + + +class ProductId(Suggestion): + pass + + +class PurchaseContinuation(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['listed_purchase'] = 'listed_purchase' + product_ids: Annotated[ + list[ProductId], + Field( + description='Exact products the coordinator promoted and re-read through account-scoped list_products before returning this result. The set MUST match products[].product_id.', + min_length=1, + ), + ] + cache_scope: Literal['account'] = 'account' + feed_version: Annotated[ + str, + Field( + description='Real seller-issued account-scoped feed fence obtained by re-reading the promoted products through list_products.', + min_length=1, + ), + ] + pricing_version: Annotated[ + str | None, + Field( + description='Real seller-issued pricing fence from the same account-scoped list_products response, when the seller versions pricing independently.', + min_length=1, + ), + ] = None + + +class SourceAdcpVersion(StrEnum): + field_2_5 = '2.5' + field_3_0 = '3.0' + field_3_1 = '3.1' + + +class Loss(StrEnum): + feed_version_not_atomic = 'feed_version_not_atomic' + pricing_version_not_atomic = 'pricing_version_not_atomic' + mutation_idempotency_not_guaranteed = 'mutation_idempotency_not_guaranteed' + + +class Losses(AdCPBaseModel): + pass + + +class PurchaseContinuation1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['legacy_create'] = 'legacy_create' + continuation_token: Annotated[ + str, + Field( + description='Opaque, short-lived coordinator token bound to the caller principal, account, and complete observed product/pricing payload. It is not a seller-issued feed fence.', + min_length=16, + ), + ] + continuation_expires_at: Annotated[ + AwareDatetime, + Field(description='Absolute expiry of the single-use compatibility continuation.'), + ] + source_adcp_version: Annotated[ + SourceAdcpVersion, + Field( + description='Established AdCP version actually negotiated with the peer. This provenance is required; the coordinator MUST NOT infer stronger guarantees from the label.' + ), + ] + product_ids: Annotated[ + list[ProductId], + Field( + description='Exact products bound to this continuation. A follow-up MUST select a non-empty subset and MUST NOT substitute an ID from another discovery result.', + min_length=1, + ), + ] + losses: Annotated[ + list[Loss] | Losses, + Field( + description='Guarantees that the established create_media_buy continuation cannot provide. The coordinator MUST fail before mutation unless the caller explicitly accepts every listed loss.', + min_length=2, + ), + ] + requires_explicit_acceptance: Literal[True] + + +class Status(StrEnum): + completed = 'completed' + submitted = 'submitted' + + +class IncompleteItem5(IncompleteItem): + pass + + +class PurchaseContinuation2(PurchaseContinuation): + pass + + +class PurchaseContinuation3(PurchaseContinuation1): + pass + + +class IncompleteItem6(IncompleteItem): + pass + + +class PurchaseContinuation4(PurchaseContinuation): + pass + + +class PurchaseContinuation5(PurchaseContinuation1): + pass + + +class IncompleteItem7(IncompleteItem): + pass + + +class PurchaseContinuation6(PurchaseContinuation): + pass + + +class PurchaseContinuation7(PurchaseContinuation1): + pass + + +class Proposal(CanonicalProposal): + proposal_status: Annotated[ + Literal['draft'], + Field( + description='draft is indicative and unreserved; committed has firm terms with inventory reserved until expires_at; accepted is the historical snapshot attached to a MediaBuy.', + title='Proposal Status', + ), + ] = 'draft' + expires_at: Annotated[ + AwareDatetime, + Field( + description='For a draft, the indicative-terms freshness deadline. For a committed proposal, the inventory-hold deadline.' + ), + ] + proposal_kind: Any + commercial_terms: Any + terms_digest: Any + + +class RequestProposalsResponse1(AdcpResponse, AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + adcp_version: Annotated[ + str | None, + Field( + description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', + examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], + pattern='^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?)?$', + ), + ] = None + outcome: Literal['proposed'] = 'proposed' + reason: Annotated[str | None, Field(min_length=1)] = None + suggestions: Annotated[list[Suggestion] | None, Field(min_length=1)] = None + proposals: Annotated[list[Proposal], Field(min_length=1)] + products: Annotated[list[canonical_product.CanonicalProduct], Field(min_length=1)] + incomplete: Annotated[ + list[IncompleteItem] | None, + Field( + description='Usable partial discovery result retained from the established get_products response. Absence means the source response did not declare an incomplete scope; it does not authorize an adapter to infer missing proposal terms.', + min_length=1, + ), + ] = None + purchase_continuation: Annotated[ + PurchaseContinuation | PurchaseContinuation1 | None, + Field( + deprecated=True, + description='Deprecated AdCP 3.x projection instruction for purchasing products returned without a proposal. This is coordinator state, not a claim that the established seller implements a compact task. A native 3.2 seller MUST NOT emit it.', + ), + ] = None + targeting_resolution: Annotated[ + get_products_targeting_resolution.ProductDiscoveryTargetingResolution | None, + Field( + description='Response-level confirmation of structured targeting interpreted from explicit hard requirements in the brief. Sellers MUST emit this when that interpretation materially affects eligibility, pricing, or forecasting; otherwise it remains a best practice. Product-specific changes to explicit criteria.targeting_overlay values remain on Product.targeting_resolution.' + ), + ] = None + status: Literal['completed'] = 'completed' + task_id: Annotated[str | None, Field(min_length=1)] = None + message: Annotated[str | None, Field(max_length=2000)] = None + errors: list[error.Error] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + replayed: Literal[True] | None = None + + +class RequestProposalsResponse2(AdcpResponse, AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + adcp_version: Annotated[ + str | None, + Field( + description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', + examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], + pattern='^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?)?$', + ), + ] = None + outcome: Literal['products_available'] = 'products_available' + reason: Annotated[str | None, Field(min_length=1)] = None + suggestions: Annotated[list[Suggestion] | None, Field(min_length=1)] = None + proposals: Annotated[list[Proposal] | None, Field(min_length=1)] = None + products: Annotated[list[canonical_product.CanonicalProduct], Field(min_length=1)] + incomplete: Annotated[ + list[IncompleteItem5] | None, + Field( + description='Usable partial discovery result retained from the established get_products response. Absence means the source response did not declare an incomplete scope; it does not authorize an adapter to infer missing proposal terms.', + min_length=1, + ), + ] = None + purchase_continuation: Annotated[ + PurchaseContinuation2 | PurchaseContinuation3, + Field( + deprecated=True, + description='Deprecated AdCP 3.x projection instruction for purchasing products returned without a proposal. This is coordinator state, not a claim that the established seller implements a compact task. A native 3.2 seller MUST NOT emit it.', + ), + ] + targeting_resolution: Annotated[ + get_products_targeting_resolution.ProductDiscoveryTargetingResolution | None, + Field( + description='Response-level confirmation of structured targeting interpreted from explicit hard requirements in the brief. Sellers MUST emit this when that interpretation materially affects eligibility, pricing, or forecasting; otherwise it remains a best practice. Product-specific changes to explicit criteria.targeting_overlay values remain on Product.targeting_resolution.' + ), + ] = None + status: Literal['completed'] = 'completed' + task_id: Annotated[str | None, Field(min_length=1)] = None + message: Annotated[str | None, Field(max_length=2000)] = None + errors: list[error.Error] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + replayed: Literal[True] | None = None + + +class RequestProposalsResponse3(AdcpResponse, AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + adcp_version: Annotated[ + str | None, + Field( + description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', + examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], + pattern='^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?)?$', + ), + ] = None + outcome: Literal['rejected'] = 'rejected' + reason: Annotated[str, Field(min_length=1)] + suggestions: Annotated[list[Suggestion] | None, Field(min_length=1)] = None + proposals: Annotated[list[Proposal] | None, Field(min_length=1)] = None + products: Annotated[list[canonical_product.CanonicalProduct] | None, Field(min_length=1)] = None + incomplete: Annotated[ + list[IncompleteItem6] | None, + Field( + description='Usable partial discovery result retained from the established get_products response. Absence means the source response did not declare an incomplete scope; it does not authorize an adapter to infer missing proposal terms.', + min_length=1, + ), + ] = None + purchase_continuation: Annotated[ + PurchaseContinuation4 | PurchaseContinuation5 | None, + Field( + deprecated=True, + description='Deprecated AdCP 3.x projection instruction for purchasing products returned without a proposal. This is coordinator state, not a claim that the established seller implements a compact task. A native 3.2 seller MUST NOT emit it.', + ), + ] = None + targeting_resolution: Annotated[ + get_products_targeting_resolution.ProductDiscoveryTargetingResolution | None, + Field( + description='Response-level confirmation of structured targeting interpreted from explicit hard requirements in the brief. Sellers MUST emit this when that interpretation materially affects eligibility, pricing, or forecasting; otherwise it remains a best practice. Product-specific changes to explicit criteria.targeting_overlay values remain on Product.targeting_resolution.' + ), + ] = None + status: Literal['completed'] = 'completed' + task_id: Annotated[str | None, Field(min_length=1)] = None + message: Annotated[str | None, Field(max_length=2000)] = None + errors: list[error.Error] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + replayed: Literal[True] | None = None + + +class RequestProposalsResponse4(AdcpResponse, CompactTaskSubmitted): + model_config = ConfigDict( + extra='forbid', + ) + adcp_version: Annotated[ + str | None, + Field( + description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', + examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], + pattern='^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?)?$', + ), + ] = None + outcome: Outcome | None = None + reason: Annotated[str | None, Field(min_length=1)] = None + suggestions: Annotated[list[Suggestion] | None, Field(min_length=1)] = None + proposals: Annotated[list[Proposal] | None, Field(min_length=1)] = None + products: Annotated[list[canonical_product.CanonicalProduct] | None, Field(min_length=1)] = None + incomplete: Annotated[ + list[IncompleteItem7] | None, + Field( + description='Usable partial discovery result retained from the established get_products response. Absence means the source response did not declare an incomplete scope; it does not authorize an adapter to infer missing proposal terms.', + min_length=1, + ), + ] = None + purchase_continuation: Annotated[ + PurchaseContinuation6 | PurchaseContinuation7 | None, + Field( + deprecated=True, + description='Deprecated AdCP 3.x projection instruction for purchasing products returned without a proposal. This is coordinator state, not a claim that the established seller implements a compact task. A native 3.2 seller MUST NOT emit it.', + ), + ] = None + targeting_resolution: Annotated[ + get_products_targeting_resolution.ProductDiscoveryTargetingResolution | None, + Field( + description='Response-level confirmation of structured targeting interpreted from explicit hard requirements in the brief. Sellers MUST emit this when that interpretation materially affects eligibility, pricing, or forecasting; otherwise it remains a best practice. Product-specific changes to explicit criteria.targeting_overlay values remain on Product.targeting_resolution.' + ), + ] = None + status: Status | None = None + task_id: Annotated[str | None, Field(min_length=1)] = None + message: Annotated[str | None, Field(max_length=2000)] = None + errors: list[error.Error] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + replayed: Literal[True] | None = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.media_buy.request_proposals_response import ( - IncompleteItem, - IncompleteItem5, - IncompleteItem6, - IncompleteItem7, - Loss, - Losses, - Outcome, - ProductId, - Proposal, - ProposalStatus, - PurchaseContinuation, - PurchaseContinuation1, - PurchaseContinuation2, - PurchaseContinuation3, - PurchaseContinuation4, - PurchaseContinuation5, - PurchaseContinuation6, - PurchaseContinuation7, - RequestProposalsResponse, - RequestProposalsResponse1, - RequestProposalsResponse2, - RequestProposalsResponse3, - RequestProposalsResponse4, - Scope, - SourceAdcpVersion, - Status, - Suggestion, -) - -# Explicit exports -__all__ = [ - "IncompleteItem", - "IncompleteItem5", - "IncompleteItem6", - "IncompleteItem7", - "Loss", - "Losses", - "Outcome", - "ProductId", - "Proposal", - "ProposalStatus", - "PurchaseContinuation", - "PurchaseContinuation1", - "PurchaseContinuation2", - "PurchaseContinuation3", - "PurchaseContinuation4", - "PurchaseContinuation5", - "PurchaseContinuation6", - "PurchaseContinuation7", - "RequestProposalsResponse", - "RequestProposalsResponse1", - "RequestProposalsResponse2", - "RequestProposalsResponse3", - "RequestProposalsResponse4", - "Scope", - "SourceAdcpVersion", - "Status", - "Suggestion", +RequestProposalsResponse = Annotated[ + RequestProposalsResponse1 + | RequestProposalsResponse2 + | RequestProposalsResponse3 + | RequestProposalsResponse4, + Field( + description='One or more immutable draft media-plan proposals and compact canonical products referenced by their purchases. Products always carry product_id and name and never carry legacy named-format identifiers. During the AdCP 3.x compatibility window, an SDK projecting a valid products-only get_products brief result may instead return the deprecated products_available outcome with an explicit purchase continuation. Native 3.2 sellers MUST NOT use that compatibility outcome, and adapters MUST NOT fabricate a proposal, terms digest, or feed version.', + title='Request Proposals Response', + ), ] diff --git a/src/adcp/types/domains/media_buy/sync_audiences_request.py b/src/adcp/types/domains/media_buy/sync_audiences_request.py index 861b8272a..dacc27e58 100644 --- a/src/adcp/types/domains/media_buy/sync_audiences_request.py +++ b/src/adcp/types/domains/media_buy/sync_audiences_request.py @@ -1,23 +1,127 @@ -"""Types declared by the AdCP ``media_buy/sync_audiences_request`` schema. +# generated by datamodel-codegen: +# filename: media_buy/sync_audiences_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.sync_audiences_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field, StrictBool + +from ..core import account_ref, audience_member, audience_source +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import consent_basis as consent_basis_1 + + +class AudienceType(StrEnum): + crm = 'crm' + suppression = 'suppression' + lookalike_seed = 'lookalike_seed' + + +class Tag(ScalarStr): + __slots__ = () + _constraints = {'min_length': 1} + + +class Audience(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + audience_id: Annotated[ + str, + Field( + description="Buyer's identifier for this audience. Used to reference the audience in targeting overlays." + ), + ] + name: Annotated[str | None, Field(description='Human-readable name for this audience')] = None + description: Annotated[ + str | None, + Field( + description="Human-readable description of this audience's composition or purpose (e.g., 'High-value customers who purchased in the last 90 days')." + ), + ] = None + audience_type: Annotated[ + AudienceType | None, + Field( + description="Intended use for this audience. 'crm': target these users. 'suppression': exclude these users from delivery. 'lookalike_seed': use as a seed for the seller's lookalike modeling. Sellers may handle audiences differently based on type (e.g., suppression lists bypass minimum size requirements on some platforms)." + ), + ] = None + tags: Annotated[ + list[Tag] | None, + Field( + description="Buyer-defined tags for organizing and filtering audiences (e.g., 'holiday_2026', 'high_ltv'). Tags are stored by the seller and returned in discovery-only calls." + ), + ] = None + add: Annotated[ + list[audience_member.AudienceMember] | None, + Field( + description='Members to add to this audience. Hashed before sending — normalize emails to lowercase+trim, phones to E.164.', + min_length=1, + ), + ] = None + remove: Annotated[ + list[audience_member.AudienceMember] | None, + Field( + description='Members to remove from this audience. If the same identifier appears in both add and remove in a single request, remove takes precedence.', + min_length=1, + ), + ] = None + source: Annotated[ + audience_source.AudienceSource | None, + Field( + description='External source reference: the seller ingests membership from a shared dataset or vendor-distributed segment instead of inline member deltas. Mutually exclusive with add/remove — an audience is either buyer-pushed or externally sourced, and transport is fixed at creation: a cross-transport upsert (member deltas against a sourced audience, or source against a pushed one) is rejected with CONFLICT (error.field: audience_id); convert by delete-and-recreate. Only send source kinds whose activation pattern the seller declared via audience_activation (undeclared kinds are rejected with UNSUPPORTED_FEATURE). Experimental — see the media_buy.audience_activation feature.' + ), + ] = None + delete: Annotated[ + StrictBool | None, + Field( + description='When true, delete this audience from the account entirely. All other fields on this audience object are ignored. Use this to delete a specific audience without affecting others.' + ), + ] = None + consent_basis: Annotated[ + consent_basis_1.ConsentBasis | None, + Field( + description='GDPR lawful basis for processing this audience list. Informational — not validated by the protocol, but required by some sellers operating in regulated markets (e.g. EU). When omitted, the buyer asserts they have a lawful basis appropriate to their jurisdiction.' + ), + ] = None -from adcp.types.generated_poc.media_buy.sync_audiences_request import ( - Audience, - AudienceType, - SyncAudiencesRequest, - Tag, -) -# Explicit exports -__all__ = ["Audience", "AudienceType", "SyncAudiencesRequest", "Tag"] +class SyncAudiencesRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + idempotency_key: Annotated[ + str, + Field( + description='Client-generated unique key for at-most-once execution. `audience_id` gives resource-level dedup per audience, but the sync envelope emits audit events and may trigger downstream refreshes — this key prevents those side effects from firing twice on retry. Also serves as a request ID on discovery-only calls (when `audiences` is omitted). MUST be unique per (seller, request) pair. Use a fresh UUID v4 for each request.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + account: Annotated[ + account_ref.AccountReference, Field(description='Account to manage audiences for.') + ] + audiences: Annotated[ + list[Audience] | None, + Field( + description='Audiences to sync (create or update). When omitted, the call is discovery-only and returns all existing audiences on the account without modification.', + min_length=1, + ), + ] = None + delete_missing: Annotated[ + StrictBool | None, + Field( + description='When true, buyer-managed audiences on the account not included in this sync will be removed. Does not affect seller-managed audiences. Do not combine with an omitted audiences array or all buyer-managed audiences will be deleted.' + ), + ] = False + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/sync_audiences_response.py b/src/adcp/types/domains/media_buy/sync_audiences_response.py index 3afbc1773..6dd1e2a27 100644 --- a/src/adcp/types/domains/media_buy/sync_audiences_response.py +++ b/src/adcp/types/domains/media_buy/sync_audiences_response.py @@ -1,34 +1,96 @@ -"""Types declared by the AdCP ``media_buy/sync_audiences_response`` schema. +# generated by datamodel-codegen: +# filename: media_buy/sync_audiences_response.json +# timestamp: preserved-by-post-generate-fixes -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.sync_audiences_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Literal, TypeAlias + +from pydantic import AwareDatetime, ConfigDict, Field, StringConstraints + +from ..core.version_envelope import AdcpVersionEnvelope +from ..core import brand_ref as brand_ref_1 +from ..core import context as context_1 +from ..core import error as error_1 +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..enums import audience_status as audience_status_1 +from ..enums import match_id_type as match_id_type_1 +from ..enums import task_status as task_status_1 + + +class MatchBreakdown(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + id_type: match_id_type_1.MatchIdType + submitted: Annotated[int, Field(ge=0)] + matched: Annotated[int, Field(ge=0)] + match_rate: Annotated[float, Field(ge=0, le=1)] + + +class Source(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + kind: Literal['dataset', 'platform_segment'] + vendor: brand_ref_1.BrandReference + locator: str | None = None + segment_ref: str | None = None + columns_read: Annotated[list[str], Field(min_length=1)] | None = None + access_status: Literal['active', 'unavailable'] | None = None + + +class Audience(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + audience_id: str + name: str | None = None + seller_id: str | None = None + action: Literal['created', 'updated', 'unchanged', 'deleted', 'failed'] + status: audience_status_1.AudienceStatus | None = None + uploaded_count: Annotated[int, Field(ge=0)] | None = None + total_uploaded_count: Annotated[int, Field(ge=0)] | None = None + matched_count: Annotated[int, Field(ge=0)] | None = None + effective_match_rate: Annotated[float, Field(ge=0, le=1)] | None = None + match_breakdown: Annotated[list[MatchBreakdown], Field(min_length=1)] | None = None + last_synced_at: AwareDatetime | None = None + source: Source | None = None + minimum_size: Annotated[int, Field(ge=1)] | None = None + errors: list[error_1.Error] | None = None + + +class SyncAudiencesResponse1(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + audiences: list[Audience] + sandbox: bool | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class SyncAudiencesResponse2(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + errors: Annotated[list[error_1.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class SyncAudiencesResponse3(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow', validate_default=True) + status: Literal[task_status_1.TaskStatus.submitted] = task_status_1.TaskStatus.submitted + task_id: str + message: Annotated[str, StringConstraints(max_length=2000)] | None = None + errors: list[error_1.Error] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +SyncAudiencesResponse: TypeAlias = SyncAudiencesResponse1 | SyncAudiencesResponse2 | SyncAudiencesResponse3 -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.media_buy.sync_audiences_response import ( - Audience, - MatchBreakdown, - Source, - SyncAudiencesResponse, - SyncAudiencesResponse1, - SyncAudiencesResponse2, - SyncAudiencesResponse3, -) - -# Explicit exports __all__ = [ - "Audience", - "MatchBreakdown", - "Source", - "SyncAudiencesResponse", - "SyncAudiencesResponse1", - "SyncAudiencesResponse2", - "SyncAudiencesResponse3", + 'SyncAudiencesResponse', + 'SyncAudiencesResponse1', + 'SyncAudiencesResponse2', + 'SyncAudiencesResponse3', + 'Audience', + 'MatchBreakdown', + 'Source', ] diff --git a/src/adcp/types/domains/media_buy/sync_catalogs_async_response_input_required.py b/src/adcp/types/domains/media_buy/sync_catalogs_async_response_input_required.py index a82f663e9..c6801acb0 100644 --- a/src/adcp/types/domains/media_buy/sync_catalogs_async_response_input_required.py +++ b/src/adcp/types/domains/media_buy/sync_catalogs_async_response_input_required.py @@ -1,21 +1,35 @@ -"""Types declared by the AdCP ``media_buy/sync_catalogs_async_response_input_required`` schema. +# generated by datamodel-codegen: +# filename: media_buy/sync_catalogs_async_response_input_required.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.sync_catalogs_async_response_input_required import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import context as context_1 +from ..core import ext as ext_1 + + +class Reason(StrEnum): + APPROVAL_REQUIRED = 'APPROVAL_REQUIRED' + FEED_VALIDATION = 'FEED_VALIDATION' + ITEM_REVIEW = 'ITEM_REVIEW' + FEED_ACCESS = 'FEED_ACCESS' -from adcp.types.generated_poc.media_buy.sync_catalogs_async_response_input_required import ( - Reason, - SyncCatalogsInputRequired, -) -# Explicit exports -__all__ = ["Reason", "SyncCatalogsInputRequired"] +class SyncCatalogsInputRequired(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + reason: Annotated[ + Reason | None, + Field( + description='Reason code indicating why buyer input is needed. APPROVAL_REQUIRED: platform requires explicit approval before activating the catalog. FEED_VALIDATION: feed URL returned unexpected format or schema errors. ITEM_REVIEW: platform flagged items for manual review. FEED_ACCESS: platform cannot access the feed URL (authentication, CORS, etc.).' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/sync_catalogs_async_response_submitted.py b/src/adcp/types/domains/media_buy/sync_catalogs_async_response_submitted.py index 9adc688ae..a2852c7fe 100644 --- a/src/adcp/types/domains/media_buy/sync_catalogs_async_response_submitted.py +++ b/src/adcp/types/domains/media_buy/sync_catalogs_async_response_submitted.py @@ -1,20 +1,47 @@ -"""Types declared by the AdCP ``media_buy/sync_catalogs_async_response_submitted`` schema. +# generated by datamodel-codegen: +# filename: media_buy/sync_catalogs_async_response_submitted.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.sync_catalogs_async_response_submitted import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 -from adcp.types.generated_poc.media_buy.sync_catalogs_async_response_submitted import ( - SyncCatalogsSubmitted, -) -# Explicit exports -__all__ = ["SyncCatalogsSubmitted"] +class SyncCatalogsSubmitted(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + status: Annotated[ + Literal['submitted'], + Field( + description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, whose catalogs array is issued in-line. See task-status.json for the full task-status enum.' + ), + ] = 'submitted' + task_id: Annotated[ + str, + Field( + description='Task handle the buyer uses with get_task_status (or the legacy AdCP tasks/get alias), and that the seller references on push-notification callbacks. This AdCP application-layer handle remains the snake_case task_id in every transport payload and is distinct from any transport-native A2A Task id.' + ), + ] + message: Annotated[ + str | None, + Field( + description="Optional human-readable explanation of why the task is submitted — e.g., 'Catalog ingestion queued; typical turnaround 5–15 minutes.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", + max_length=2000, + ), + ] = None + errors: Annotated[ + list[error.Error] | None, + Field( + description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/sync_catalogs_async_response_working.py b/src/adcp/types/domains/media_buy/sync_catalogs_async_response_working.py index aeab74690..d9691add8 100644 --- a/src/adcp/types/domains/media_buy/sync_catalogs_async_response_working.py +++ b/src/adcp/types/domains/media_buy/sync_catalogs_async_response_working.py @@ -1,20 +1,48 @@ -"""Types declared by the AdCP ``media_buy/sync_catalogs_async_response_working`` schema. +# generated by datamodel-codegen: +# filename: media_buy/sync_catalogs_async_response_working.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.sync_catalogs_async_response_working import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import context as context_1 +from ..core import ext as ext_1 -from adcp.types.generated_poc.media_buy.sync_catalogs_async_response_working import ( - SyncCatalogsWorking, -) -# Explicit exports -__all__ = ["SyncCatalogsWorking"] +class SyncCatalogsWorking(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + percentage: Annotated[ + StrictFloat | None, Field(description='Completion percentage (0-100)', ge=0.0, le=100.0) + ] = None + current_step: Annotated[ + str | None, + Field( + description="Current step or phase of the operation (e.g., 'Fetching product feed', 'Validating items', 'Platform review')" + ), + ] = None + total_steps: Annotated[ + SchemaInt | None, Field(description='Total number of steps in the operation', ge=1) + ] = None + step_number: Annotated[SchemaInt | None, Field(description='Current step number', ge=1)] = None + catalogs_processed: Annotated[ + SchemaInt | None, Field(description='Number of catalogs processed so far', ge=0) + ] = None + catalogs_total: Annotated[ + SchemaInt | None, Field(description='Total number of catalogs to process', ge=0) + ] = None + items_processed: Annotated[ + SchemaInt | None, + Field(description='Total number of catalog items processed across all catalogs', ge=0), + ] = None + items_total: Annotated[ + SchemaInt | None, + Field(description='Total number of catalog items to process across all catalogs', ge=0), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/sync_catalogs_request.py b/src/adcp/types/domains/media_buy/sync_catalogs_request.py index ea86a1ff3..df99e8e9f 100644 --- a/src/adcp/types/domains/media_buy/sync_catalogs_request.py +++ b/src/adcp/types/domains/media_buy/sync_catalogs_request.py @@ -1,18 +1,100 @@ -"""Types declared by the AdCP ``media_buy/sync_catalogs_request`` schema. +# generated by datamodel-codegen: +# filename: media_buy/sync_catalogs_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.sync_catalogs_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field, StrictBool + +from ..core import ( + account_ref, + catalog, + catalog_item_availability_ref, + catalog_item_availability_update, +) +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core import push_notification_config as push_notification_config_1 +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import validation_mode as validation_mode_1 -from adcp.types.generated_poc.media_buy.sync_catalogs_request import SyncCatalogsRequest -# Explicit exports -__all__ = ["SyncCatalogsRequest"] +class SyncCatalogsRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + idempotency_key: Annotated[ + str, + Field( + description='Client-generated unique key for at-most-once execution. Catalog upserts and item availability transitions can emit audit events or trigger platform work — this key prevents those side effects from firing twice on retry. Also serves as a request ID on discovery-only calls. MUST be unique per (seller, request) pair. Use a fresh UUID v4 for each request.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + account: Annotated[ + account_ref.AccountReference, + Field(description='Seller account containing these buyer-managed catalogs.'), + ] + catalogs: Annotated[ + list[catalog.Catalog] | None, + Field( + description='Array of catalog feeds to sync (create or update). When omitted together with item_availability_updates and item_availability_queries, the call is discovery-only and returns all existing catalogs on the account without modification.', + max_length=50, + min_length=1, + ), + ] = None + item_availability_updates: Annotated[ + list[catalog_item_availability_update.CatalogItemAvailabilityUpdate] | None, + Field( + description='Immediate suppress or restore operations for items in buyer-managed catalogs. Sellers declaring media_buy.features.catalog_item_availability_updates MUST process these updates synchronously and MUST NOT silently ignore them or return a submitted task. A seller that does not declare the capability MUST reject the request with UNSUPPORTED_FEATURE before lookup or mutation and MUST NOT interpret it as discovery. The combined number of item_availability_updates and item_availability_queries MUST NOT exceed 1,000; excess entries are an operation-level INVALID_REQUEST before lookup or mutation. Each (catalog_id, catalog_generation, item_id) tuple MUST appear at most once in updates; a duplicate is an operation-level INVALID_REQUEST before mutation in every validation mode. For mixed catalog/update requests, the seller MUST validate and stage the entire request against the post-upsert candidate state, then commit catalog and availability changes atomically. It MUST reject before any mutation if synchronous atomic commit is unavailable. A successful suppress acknowledgement means the seller MUST stop selecting or rendering the item and every cached or pre-generated creative it materialized from the item. Seller-internal generation lineage MUST retain resolved_account_id, catalog_id, catalog_generation, and item_id. If the seller cannot enforce that guarantee, it MUST return a failed per-item result. Suppression persists across scheduled feed fetches and catalog upserts until explicit restore, expires_at, or deletion of the containing catalog. Restore removes only an existing buyer-authored overlay or tombstone in the same catalog generation and cannot override seller rejection, withdrawal, policy, rights, or inventory controls. A restore for an absent item without such prior state fails with REFERENCE_NOT_FOUND.', + max_length=1000, + min_length=1, + ), + ] = None + item_availability_queries: Annotated[ + list[catalog_item_availability_ref.CatalogItemAvailabilityReference] | None, + Field( + description='Read current buyer-authored availability state. Queries require media_buy.features.catalog_item_availability_updates; a seller that does not declare it rejects with UNSUPPORTED_FEATURE before lookup. Any request containing queries is synchronous. In a mixed request the seller validates and stages catalog upserts and availability updates first, evaluates queries against that post-upsert/post-update candidate state, and atomically commits the staged mutations before returning those query results. If the mixed work cannot commit synchronously, it rejects before mutation. The seller returns exactly one item_availability_states entry per query in the same order and echoes request_index and the complete identity. Unknown, inaccessible, stale-generation, and unauthorized references use the normalized REFERENCE_NOT_FOUND shape described by validation_mode. Use a fresh idempotency_key for a current read; a replayed response is a historical snapshot.', + max_length=1000, + min_length=1, + ), + ] = None + catalog_ids: Annotated[ + list[str] | None, + Field( + description='Optional filter to limit sync scope to specific catalog IDs. When provided, only these catalogs will be created/updated. Other catalogs on the account are unaffected.', + max_length=50, + min_length=1, + ), + ] = None + delete_missing: Annotated[ + StrictBool | None, + Field( + description='When true, buyer-managed catalogs on the account not included in this sync will be removed. Does not affect seller-managed catalogs. Requires catalogs; item_availability_updates alone cannot define deletion scope.' + ), + ] = False + dry_run: Annotated[ + StrictBool | None, + Field( + description='When true, preview catalog create, update, and delete changes without applying them. MUST NOT be combined with item_availability_updates.' + ), + ] = False + validation_mode: Annotated[ + validation_mode_1.ValidationMode | None, + Field( + description="Validation strictness for semantically valid-looking catalog and item entries. In strict mode (default), an unknown, inaccessible, unauthorized, or stale-generation catalog/item reference, a known seller-managed catalog, a stale expected_overlay_revision, or another per-entry error fails the entire operation before any catalog or availability mutation. In lenient mode, the seller returns a positionally matched failed result for each such item entry and processes the remaining valid entries. Unknown, inaccessible, unauthorized, and stale-generation references MUST be observationally equivalent: code REFERENCE_NOT_FOUND, message exactly 'Catalog item not found', recovery 'correctable', and no field, suggestion, retry_after, issues, details, or resource metadata. Authorization and lookup MUST use the same externally observable failure path and SHOULD avoid materially distinguishable timing. A known seller-managed catalog may use INVALID_REQUEST only after catalog access is authorized. Request-schema failures, duplicate identity tuples, unsupported capability, batch-limit excess, dry_run conflicts, and mixed requests that cannot commit synchronously and atomically are operation-level failures before lookup or mutation in both modes. A stale revision uses CONFLICT without mutation." + ), + ] = validation_mode_1.ValidationMode.strict + push_notification_config: Annotated[ + push_notification_config_1.PushNotificationConfig | None, + Field( + description='Optional webhook configuration for async sync notifications. Publisher will send webhook when sync completes if operation takes longer than immediate response time (common for large feeds requiring platform review).' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/sync_catalogs_response.py b/src/adcp/types/domains/media_buy/sync_catalogs_response.py index 8770a59a5..9337b697d 100644 --- a/src/adcp/types/domains/media_buy/sync_catalogs_response.py +++ b/src/adcp/types/domains/media_buy/sync_catalogs_response.py @@ -1,32 +1,89 @@ -"""Types declared by the AdCP ``media_buy/sync_catalogs_response`` schema. +# generated by datamodel-codegen: +# filename: media_buy/sync_catalogs_response.json +# timestamp: preserved-by-post-generate-fixes -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.sync_catalogs_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Any, Literal, TypeAlias -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import AwareDatetime, ConfigDict, Field, StringConstraints + +from ..core.version_envelope import AdcpVersionEnvelope +from ..core import catalog_item_availability_state as catalog_item_availability_state_1 +from ..core import catalog_item_availability_update_result as catalog_item_availability_update_result_1 +from ..core import context as context_1 +from ..core import error as error_1 +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..enums import catalog_action as catalog_action_1 +from ..enums import catalog_item_status as catalog_item_status_1 +from ..enums import task_status as task_status_1 + + +class ItemIssue(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + item_id: str + status: catalog_item_status_1.CatalogItemStatus + reasons: list[str] | None = None + + +class Catalog(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + catalog_id: str + catalog_generation: Annotated[str, StringConstraints(min_length=1, max_length=255)] | None = None + action: catalog_action_1.CatalogAction + platform_id: str | None = None + item_count: Annotated[int, Field(ge=0)] | None = None + items_approved: Annotated[int, Field(ge=0)] | None = None + items_pending: Annotated[int, Field(ge=0)] | None = None + items_rejected: Annotated[int, Field(ge=0)] | None = None + item_issues: list[ItemIssue] | None = None + last_synced_at: AwareDatetime | None = None + next_fetch_at: AwareDatetime | None = None + changes: list[str] | None = None + errors: list[error_1.Error] | None = None + warnings: list[str] | None = None + + +class SyncCatalogsResponse1(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + status: Literal['completed'] | None = None + dry_run: bool | None = None + catalogs: list[Catalog] + item_availability_updates: Annotated[list[catalog_item_availability_update_result_1.CatalogItemAvailabilityUpdateResult], Field(min_length=1, max_length=1000)] | None = None + item_availability_states: Annotated[list[catalog_item_availability_state_1.CatalogItemAvailabilityState], Field(min_length=1, max_length=1000)] | None = None + sandbox: bool | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class SyncCatalogsResponse2(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + errors: Annotated[list[Any], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class SyncCatalogsResponse3(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow', validate_default=True) + status: Literal[task_status_1.TaskStatus.submitted] = task_status_1.TaskStatus.submitted + task_id: str + message: Annotated[str, StringConstraints(max_length=2000)] | None = None + errors: list[error_1.Error] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +SyncCatalogsResponse: TypeAlias = SyncCatalogsResponse1 | SyncCatalogsResponse2 | SyncCatalogsResponse3 -from adcp.types.generated_poc.media_buy.sync_catalogs_response import ( - Catalog, - ItemIssue, - SyncCatalogsResponse, - SyncCatalogsResponse1, - SyncCatalogsResponse2, - SyncCatalogsResponse3, -) -# Explicit exports __all__ = [ - "Catalog", - "ItemIssue", - "SyncCatalogsResponse", - "SyncCatalogsResponse1", - "SyncCatalogsResponse2", - "SyncCatalogsResponse3", + 'SyncCatalogsResponse', + 'SyncCatalogsResponse1', + 'SyncCatalogsResponse2', + 'SyncCatalogsResponse3', + 'Catalog', + 'ItemIssue', ] diff --git a/src/adcp/types/domains/media_buy/sync_event_sources_request.py b/src/adcp/types/domains/media_buy/sync_event_sources_request.py index 08fd56266..4a2abca4d 100644 --- a/src/adcp/types/domains/media_buy/sync_event_sources_request.py +++ b/src/adcp/types/domains/media_buy/sync_event_sources_request.py @@ -1,22 +1,97 @@ -"""Types declared by the AdCP ``media_buy/sync_event_sources_request`` schema. +# generated by datamodel-codegen: +# filename: media_buy/sync_event_sources_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.sync_event_sources_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field, StrictBool + +from ..core import account_ref +from ..core import context as context_1 +from ..core import event_surface +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import action_source as action_source_1 +from ..enums import event_type + + +class ValueCurrency(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{3}$'} + + +class EventSource(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + event_source_id: Annotated[str, Field(description='Unique identifier for this event source')] + name: Annotated[str | None, Field(description='Human-readable name for this event source')] = ( + None + ) + event_types: Annotated[ + list[event_type.EventType] | None, + Field( + description='Event types this source handles (e.g. purchase, lead). If omitted, accepts all event types.', + min_length=1, + ), + ] = None + value_currencies: Annotated[ + list[ValueCurrency] | None, + Field( + description='ISO 4217 currencies this source accepts for monetary custom_data values. Required for a source used by canonical ROAS. Once declared, log_event monetary records for this source MUST include a currency from this list. Sources omitting the field retain legacy event compatibility but are ineligible for canonical ROAS. One source may list multiple currencies; each media buy consumes only records matching its own currency and no conversion occurs.', + min_length=1, + ), + ] = None + action_source: Annotated[ + action_source_1.ActionSource | None, + Field( + description='Flat action-source category for this event source, such as website, app, in_store, system_generated, or other. Producers SHOULD also populate `surface` when the flat value is too coarse, and SHOULD keep this field aligned with `surface.category` when the category is also an ActionSource value.' + ), + ] = None + allowed_domains: Annotated[ + list[str] | None, + Field(description='Domains authorized to send events for this event source', min_length=1), + ] = None + surface: Annotated[ + event_surface.EventSurface | None, + Field( + description='Optional structured surface this source represents, such as an owned channel, profile, feed, podcast, newsletter list, website, app, or store. Complements the flat `action_source` returned by sellers and avoids putting optimization-relevant surface meaning only in `ext`.' + ), + ] = None -from adcp.types.generated_poc.media_buy.sync_event_sources_request import ( - EventSource, - SyncEventSourcesRequest, - ValueCurrency, -) -# Explicit exports -__all__ = ["EventSource", "SyncEventSourcesRequest", "ValueCurrency"] +class SyncEventSourcesRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + idempotency_key: Annotated[ + str, + Field( + description='Client-generated unique key for at-most-once execution. `event_source_id` gives resource-level dedup per source, but the sync envelope emits audit events and can trigger downstream pixel provisioning — this key prevents those side effects from firing twice on retry. Also serves as a request ID on discovery-only calls (when `event_sources` is omitted). MUST be unique per (seller, request) pair. Use a fresh UUID v4 for each request.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + account: Annotated[ + account_ref.AccountReference, Field(description='Account to configure event sources for.') + ] + event_sources: Annotated[ + list[EventSource] | None, + Field( + description='Event sources to sync (create or update). When omitted, the call is discovery-only and returns all existing event sources on the account without modification.', + min_length=1, + ), + ] = None + delete_missing: Annotated[ + StrictBool | None, + Field(description='When true, event sources not included in this sync will be removed'), + ] = False + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/sync_event_sources_response.py b/src/adcp/types/domains/media_buy/sync_event_sources_response.py index a141a2ced..47e0ef5f5 100644 --- a/src/adcp/types/domains/media_buy/sync_event_sources_response.py +++ b/src/adcp/types/domains/media_buy/sync_event_sources_response.py @@ -1,30 +1,72 @@ -"""Types declared by the AdCP ``media_buy/sync_event_sources_response`` schema. +# generated by datamodel-codegen: +# filename: media_buy/sync_event_sources_response.json +# timestamp: preserved-by-post-generate-fixes -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.sync_event_sources_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Literal, TypeAlias -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field, StringConstraints + +from ..core.version_envelope import AdcpVersionEnvelope +from ..core import context as context_1 +from ..core import error as error_1 +from ..core import event_source_health as event_source_health_1 +from ..core import event_surface as event_surface_1 +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..enums import action_source as action_source_1 +from ..enums import event_type as event_type_1 + + +class Setup(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + snippet: str | None = None + snippet_type: Literal['javascript', 'html', 'pixel_url', 'server_only'] | None = None + instructions: str | None = None + + +class EventSource(AdcpVersionEnvelope): + model_config = ConfigDict(extra='allow') + event_source_id: str + name: str | None = None + seller_id: str | None = None + event_types: list[event_type_1.EventType] | None = None + value_currencies: Annotated[list[Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')]], Field(min_length=1)] | None = None + action_source: action_source_1.ActionSource | None = None + surface: event_surface_1.EventSurface | None = None + managed_by: Literal['buyer', 'seller'] | None = None + setup: Setup | None = None + action: Literal['created', 'updated', 'unchanged', 'deleted', 'failed'] + health: event_source_health_1.EventSourceHealth | None = None + errors: list[error_1.Error] | None = None + ext: ext_1.ExtensionObject | None = None + + +class SyncEventSourcesResponse1(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + event_sources: list[EventSource] + sandbox: bool | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class SyncEventSourcesResponse2(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + errors: Annotated[list[error_1.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +SyncEventSourcesResponse: TypeAlias = SyncEventSourcesResponse1 | SyncEventSourcesResponse2 -from adcp.types.generated_poc.media_buy.sync_event_sources_response import ( - EventSource, - Setup, - SyncEventSourcesResponse, - SyncEventSourcesResponse1, - SyncEventSourcesResponse2, -) -# Explicit exports __all__ = [ - "EventSource", - "Setup", - "SyncEventSourcesResponse", - "SyncEventSourcesResponse1", - "SyncEventSourcesResponse2", + 'SyncEventSourcesResponse', + 'SyncEventSourcesResponse1', + 'SyncEventSourcesResponse2', + 'EventSource', + 'Setup', ] diff --git a/src/adcp/types/domains/media_buy/sync_reporting_receipts_request.py b/src/adcp/types/domains/media_buy/sync_reporting_receipts_request.py index 241e8808f..d85fff797 100644 --- a/src/adcp/types/domains/media_buy/sync_reporting_receipts_request.py +++ b/src/adcp/types/domains/media_buy/sync_reporting_receipts_request.py @@ -1,20 +1,76 @@ -"""Types declared by the AdCP ``media_buy/sync_reporting_receipts_request`` schema. +# generated by datamodel-codegen: +# filename: media_buy/sync_reporting_receipts_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.sync_reporting_receipts_request import +from adcp.types.base import AdcpRequest, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated + +from pydantic import ConfigDict, Field, model_validator + +from ..core import canonical_account_ref +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core import reporting_adjustment_receipt, reporting_receipt +from ..core.version_envelope import AdcpVersionEnvelope -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.media_buy.sync_reporting_receipts_request import ( - SyncReportingReceiptsRequest, -) +class SyncReportingReceiptsRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + adcp_version: Annotated[ + str | None, + Field( + description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', + examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], + pattern='^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?)?$', + ), + ] = None + adcp_major_version: Annotated[ + SchemaInt | None, + Field( + deprecated=True, + description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", + ge=1, + le=99, + ), + ] = None + account: canonical_account_ref.CanonicalAccountReference + idempotency_key: Annotated[ + str, + Field( + description='Client-generated batch key. Exact retries reuse the key and body.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + receipts: Annotated[ + list[reporting_receipt.ReportingReceipt] | None, Field(max_length=100, min_length=1) + ] = None + adjustment_receipts: Annotated[ + list[reporting_adjustment_receipt.ReportingAdjustmentReceipt] | None, + Field( + description='Consumer acceptance or rejection of exact post-official adjustments.', + max_length=100, + min_length=1, + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None -# Explicit exports -__all__ = ["SyncReportingReceiptsRequest"] + @model_validator(mode='after') + def _require_schema_required_group(self) -> SyncReportingReceiptsRequest: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('receipts',), ('adjustment_receipts',),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'SyncReportingReceiptsRequest requires at least one of these field groups: receipts | adjustment_receipts' + ) diff --git a/src/adcp/types/domains/media_buy/sync_reporting_receipts_response.py b/src/adcp/types/domains/media_buy/sync_reporting_receipts_response.py index 74bb384d8..a5f174bdc 100644 --- a/src/adcp/types/domains/media_buy/sync_reporting_receipts_response.py +++ b/src/adcp/types/domains/media_buy/sync_reporting_receipts_response.py @@ -1,32 +1,80 @@ -"""Types declared by the AdCP ``media_buy/sync_reporting_receipts_response`` schema. +# generated by datamodel-codegen: +# filename: media_buy/sync_reporting_receipts_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.sync_reporting_receipts_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Literal + +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field + +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 +from ..core import reporting_adjustment_receipt, reporting_receipt +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope + + +class Results(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + result: Literal['failed'] = 'failed' + reporting_receipt_id: Annotated[ + str, Field(max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$') + ] + errors: Annotated[list[error.Error], Field(max_length=16, min_length=1)] + + +class Results20(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + result: Literal['recorded'] = 'recorded' + receipt: reporting_receipt.ReportingReceipt + + +class Results21(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + result: Literal['unchanged'] = 'unchanged' + receipt: reporting_receipt.ReportingReceipt + + +class Results22(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + result: Literal['recorded'] = 'recorded' + adjustment_receipt: reporting_adjustment_receipt.ReportingAdjustmentReceipt + + +class Results23(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + result: Literal['unchanged'] = 'unchanged' + adjustment_receipt: reporting_adjustment_receipt.ReportingAdjustmentReceipt -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.media_buy.sync_reporting_receipts_response import ( - Results, - Results20, - Results21, - Results22, - Results23, - SyncReportingReceiptsResponse, -) - -# Explicit exports -__all__ = [ - "Results", - "Results20", - "Results21", - "Results22", - "Results23", - "SyncReportingReceiptsResponse", -] +class SyncReportingReceiptsResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + status: Annotated[ + Literal['completed'], + Field( + description='Receipt batches complete synchronously with one result per submitted receipt.' + ), + ] = 'completed' + results: Annotated[ + list[Results20 | Results21 | Results22 | Results23 | Results], + Field(max_length=100, min_length=1), + ] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/sync_reporting_status_request.py b/src/adcp/types/domains/media_buy/sync_reporting_status_request.py index 9e842287f..385c88f8b 100644 --- a/src/adcp/types/domains/media_buy/sync_reporting_status_request.py +++ b/src/adcp/types/domains/media_buy/sync_reporting_status_request.py @@ -1,20 +1,43 @@ -"""Types declared by the AdCP ``media_buy/sync_reporting_status_request`` schema. +# generated by datamodel-codegen: +# filename: media_buy/sync_reporting_status_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.sync_reporting_status_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field + +from ..core import canonical_account_ref +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core import reporting_consumer_status +from ..core.version_envelope import AdcpVersionEnvelope -from adcp.types.generated_poc.media_buy.sync_reporting_status_request import ( - SyncReportingStatusRequest, -) -# Explicit exports -__all__ = ["SyncReportingStatusRequest"] +class SyncReportingStatusRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='forbid', + ) + account: canonical_account_ref.CanonicalAccountReference + idempotency_key: Annotated[ + str, + Field( + description='Client-generated batch key. Exact retries reuse the key and body.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + statuses: Annotated[ + list[reporting_consumer_status.ReportingConsumerStatus], + Field( + description='Immutable status updates. New state uses a new reporting_status_id and explicitly supersedes the current status for that expected period. A batch contains at most one update for each logical status chain. content_mismatch entries report a consumed revision that contradicts the accepted configuration generation; they are operational disagreements about contract facts, never measurement disputes.', + max_length=100, + min_length=1, + ), + ] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/sync_reporting_status_response.py b/src/adcp/types/domains/media_buy/sync_reporting_status_response.py index 3da691fcb..c0f57cbc1 100644 --- a/src/adcp/types/domains/media_buy/sync_reporting_status_response.py +++ b/src/adcp/types/domains/media_buy/sync_reporting_status_response.py @@ -1,23 +1,61 @@ -"""Types declared by the AdCP ``media_buy/sync_reporting_status_response`` schema. +# generated by datamodel-codegen: +# filename: media_buy/sync_reporting_status_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.sync_reporting_status_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field + +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 +from ..core import reporting_consumer_status +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope + + +class Results(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + result: Literal['recorded'] = 'recorded' + consumer_status: reporting_consumer_status.ReportingConsumerStatus + + +class Results26(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + result: Literal['unchanged'] = 'unchanged' + consumer_status: reporting_consumer_status.ReportingConsumerStatus + + +class Results27(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + result: Literal['failed'] = 'failed' + reporting_status_id: Annotated[ + str, Field(max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$') + ] + errors: Annotated[list[error.Error], Field(max_length=16, min_length=1)] -from adcp.types.generated_poc.media_buy.sync_reporting_status_response import ( - Results, - Results26, - Results27, - SyncReportingStatusResponse, -) -# Explicit exports -__all__ = ["Results", "Results26", "Results27", "SyncReportingStatusResponse"] +class SyncReportingStatusResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + status: Annotated[ + Literal['completed'], + Field( + description='Status batches complete synchronously with one result per submitted statement.' + ), + ] = 'completed' + results: Annotated[list[Results | Results26 | Results27], Field(max_length=100, min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/update_media_buy_async_response_input_required.py b/src/adcp/types/domains/media_buy/update_media_buy_async_response_input_required.py index 86aab8e05..c5d5034a9 100644 --- a/src/adcp/types/domains/media_buy/update_media_buy_async_response_input_required.py +++ b/src/adcp/types/domains/media_buy/update_media_buy_async_response_input_required.py @@ -1,21 +1,30 @@ -"""Types declared by the AdCP ``media_buy/update_media_buy_async_response_input_required`` schema. +# generated by datamodel-codegen: +# filename: media_buy/update_media_buy_async_response_input_required.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.update_media_buy_async_response_input_required import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import context as context_1 +from ..core import ext as ext_1 + + +class Reason(StrEnum): + APPROVAL_REQUIRED = 'APPROVAL_REQUIRED' + CHANGE_CONFIRMATION = 'CHANGE_CONFIRMATION' -from adcp.types.generated_poc.media_buy.update_media_buy_async_response_input_required import ( - Reason, - UpdateMediaBuyInputRequired, -) -# Explicit exports -__all__ = ["Reason", "UpdateMediaBuyInputRequired"] +class UpdateMediaBuyInputRequired(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + reason: Annotated[ + Reason | None, Field(description='Reason code indicating why input is needed') + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/update_media_buy_async_response_submitted.py b/src/adcp/types/domains/media_buy/update_media_buy_async_response_submitted.py index c420598b0..5b7d9c15e 100644 --- a/src/adcp/types/domains/media_buy/update_media_buy_async_response_submitted.py +++ b/src/adcp/types/domains/media_buy/update_media_buy_async_response_submitted.py @@ -1,20 +1,47 @@ -"""Types declared by the AdCP ``media_buy/update_media_buy_async_response_submitted`` schema. +# generated by datamodel-codegen: +# filename: media_buy/update_media_buy_async_response_submitted.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.update_media_buy_async_response_submitted import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 -from adcp.types.generated_poc.media_buy.update_media_buy_async_response_submitted import ( - UpdateMediaBuySubmitted, -) -# Explicit exports -__all__ = ["UpdateMediaBuySubmitted"] +class UpdateMediaBuySubmitted(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + status: Annotated[ + Literal['submitted'], + Field( + description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, whose media_buy_id is issued in-line. See task-status.json for the full task-status enum.' + ), + ] = 'submitted' + task_id: Annotated[ + str, + Field( + description='Task handle the buyer uses with get_task_status (or the legacy AdCP tasks/get alias), and that the seller references on push-notification callbacks. This AdCP application-layer handle remains the snake_case task_id in every transport payload and is distinct from any transport-native A2A Task id.' + ), + ] + message: Annotated[ + str | None, + Field( + description="Optional human-readable explanation of why the task is submitted — e.g., 'Awaiting operator re-approval; typical turnaround 2–4 hours.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", + max_length=2000, + ), + ] = None + errors: Annotated[ + list[error.Error] | None, + Field( + description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/update_media_buy_async_response_working.py b/src/adcp/types/domains/media_buy/update_media_buy_async_response_working.py index 72224e209..1542e9dd8 100644 --- a/src/adcp/types/domains/media_buy/update_media_buy_async_response_working.py +++ b/src/adcp/types/domains/media_buy/update_media_buy_async_response_working.py @@ -1,20 +1,31 @@ -"""Types declared by the AdCP ``media_buy/update_media_buy_async_response_working`` schema. +# generated by datamodel-codegen: +# filename: media_buy/update_media_buy_async_response_working.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.update_media_buy_async_response_working import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import context as context_1 +from ..core import ext as ext_1 -from adcp.types.generated_poc.media_buy.update_media_buy_async_response_working import ( - UpdateMediaBuyWorking, -) -# Explicit exports -__all__ = ["UpdateMediaBuyWorking"] +class UpdateMediaBuyWorking(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + percentage: Annotated[ + StrictFloat | None, Field(description='Completion percentage (0-100)', ge=0.0, le=100.0) + ] = None + current_step: Annotated[ + str | None, Field(description='Current step or phase of the operation') + ] = None + total_steps: Annotated[ + SchemaInt | None, Field(description='Total number of steps in the operation', ge=1) + ] = None + step_number: Annotated[SchemaInt | None, Field(description='Current step number', ge=1)] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/update_media_buy_request.py b/src/adcp/types/domains/media_buy/update_media_buy_request.py index adc0c82ff..0cf88c012 100644 --- a/src/adcp/types/domains/media_buy/update_media_buy_request.py +++ b/src/adcp/types/domains/media_buy/update_media_buy_request.py @@ -1,21 +1,177 @@ -"""Types declared by the AdCP ``media_buy/update_media_buy_request`` schema. +# generated by datamodel-codegen: +# filename: media_buy/update_media_buy_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.update_media_buy_request import +from adcp.types.base import AdcpRequest, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from collections.abc import Sequence +from typing import Annotated, Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat + +from ..core import account_ref, bidding_policy +from ..core import budget_allocation as budget_allocation_1 +from ..core import business_entity +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core import media_buy_frequency_cap +from ..core import push_notification_config as push_notification_config_1 +from ..core import reporting_webhook as reporting_webhook_1 +from ..core import start_timing +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import pacing as pacing_1 +from . import package_request, package_update + + +class TotalBudget(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + amount: Annotated[StrictFloat, Field(ge=0.0)] + currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] -from adcp.types.generated_poc.media_buy.update_media_buy_request import ( - TotalBudget, - UpdateMediaBuyRequest, -) -# Explicit exports -__all__ = ["TotalBudget", "UpdateMediaBuyRequest"] +class UpdateMediaBuyRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + governance_context: Annotated[ + str | None, + Field( + description='Opaque intent authorization for a commitment-increasing media-buy update.', + max_length=4096, + min_length=1, + pattern='^[\\x20-\\x7E]+$', + ), + ] = None + account: Annotated[ + account_ref.AccountReference, + Field( + description='Account that owns this media buy. Pass a natural key (brand, operator, optional sandbox) or a seller-assigned account_id from list_accounts. Required for governance checks and account resolution.' + ), + ] + media_buy_id: Annotated[str, Field(description="Seller's ID of the media buy to update")] + name: Annotated[ + str | None, + Field( + description='Replacement human-readable name for this media buy, used for trafficking UI display and operational communication. Sellers that cannot update name mid-flight SHOULD echo the prior unchanged value in the success response rather than silently dropping the field. This display label is not an identifier or financial reference.', + max_length=255, + min_length=1, + pattern='\\S', + ), + ] = None + revision: Annotated[ + SchemaInt | None, + Field( + description="Expected current revision for optimistic concurrency. Optional for backward compatibility. When provided, sellers MUST reject the update with CONFLICT if the media buy's current revision does not match, and MUST enforce that comparison atomically with the write. Obtain from get_media_buys or the most recent create/update response.", + ge=1, + ), + ] = None + paused: Annotated[ + StrictBool | None, + Field(description='Pause/resume the entire media buy (true = paused, false = active)'), + ] = None + canceled: Annotated[ + Literal[True] | None, + Field( + description='Cancel the entire media buy. Cancellation is irreversible — canceled media buys cannot be reactivated. Sellers MAY reject with NOT_CANCELLABLE if the media buy cannot be canceled in its current state.' + ), + ] = None + cancellation_reason: Annotated[ + str | None, + Field( + description='Reason for cancellation. Sellers SHOULD store this and return it in subsequent get_media_buys responses.', + max_length=500, + ), + ] = None + start_time: start_timing.StartTiming | None = None + end_time: Annotated[ + AwareDatetime | None, Field(description='New end date/time in ISO 8601 format') + ] = None + total_budget: Annotated[ + TotalBudget | None, + Field( + description='Updated hard aggregate lifetime budget. currency MUST equal the existing media-buy currency; an update does not redenominate a buy. When supplied alone (without packages or new_packages), in fixed mode the seller MUST atomically scale every active package budget in proportion to its current committed budget, rejecting the entire request if any derived budget cannot be accepted. When supplied with packages or new_packages, the amount MUST equal the resulting fixed-mode package sum; the seller applies the explicit package mutations and rejects with VALIDATION_ERROR if the total is inconsistent. In seller-optimized mode this changes the shared pool without converting package caps into allocations. Already-spent amounts still count against the new total.' + ), + ] = None + daily_budget_cap: Annotated[ + StrictFloat | None, + Field( + description='Replace the hard aggregate daily cap; null removes it. Numeric changes apply immediately with current-day spend counted. A cap below that spend pauses delivery for the day. Package caps are unchanged. Requires advertised media_buy scope; otherwise rejected with UNSUPPORTED_FEATURE.', + ge=0.0, + ), + ] = None + frequency_cap: Annotated[ + media_buy_frequency_cap.MediaBuyFrequencyCap | None, + Field( + description='Replace the hard MediaBuy-level frequency cap; null removes it. Changes apply immediately without resetting the shared counter, so prior qualifying exposures in the resulting window continue to count. Every active package must support the resulting cap or the request is rejected atomically with UNSUPPORTED_FEATURE before any change; sellers MUST NOT clamp it. The check applies to the state the request would produce, so null combined with new_packages adds those packages uncapped.' + ), + ] = None + budget_cap_timezone: Annotated[ + str | None, + Field( + description='Replace the shared IANA cap-day timezone; null restores the default selected by budget_capping.timezone_basis (Account.timezone or fixed_timezone). Requires buyer_timezone_override. Changes start at the next boundary in the previously effective timezone; numeric cap changes remain immediate.', + min_length=1, + ), + ] = None + budget_allocation: Annotated[ + budget_allocation_1.BudgetAllocation | None, + Field( + description='Updated allocation configuration. Switching between fixed and seller-optimized modes is allowed only when update_budget_allocation is advertised in available_actions and the resulting package constraints are valid. A resulting seller_optimized allocation requires advertised media_buy.features.seller_optimized_budget, and any package budget caps, min_spend_target values, or package pacing it retains require their own advertised sub-capability; otherwise the update is rejected with UNSUPPORTED_FEATURE before any over-subscription validation.' + ), + ] = None + pacing: Annotated[ + pacing_1.Pacing | None, + Field( + description='Updated aggregate media-buy pacing. Package pacing remains subordinate to this aggregate strategy. When the resulting buy is seller-optimized, a seller declaring media_buy.features.seller_optimized_budget MUST accept `even`; it MAY reject `asap` or `front_loaded` with UNSUPPORTED_FEATURE (error.field `pacing`) before any provider mutation, including a switch to seller-optimized allocation that would retain such pacing, and MUST NOT silently coerce them to `even`. Fixed-allocation semantics are unchanged.' + ), + ] = None + bidding: Annotated[ + bidding_policy.BiddingPolicy | None, + Field( + description='Replace the complete media-buy-authored bidding default. An object replaces the prior block; `{automatic:true}` records an explicit automatic policy. null clears it; packages with explicit package.bidding remain explicit, while packages without overrides fall back to provider automatic delivery. Goal binding follows the resulting budget allocation: seller-optimized outcome controls bind to allocation goals, while fixed inherited cost_per requires compatible package result units. Monetary fields use the media-buy currency and all affected pricing options MUST match it. The seller MUST validate all resulting policies atomically before mutation.' + ), + ] = None + packages: Annotated[ + Sequence[package_update.PackageUpdate] | None, + Field(description='Package-specific updates for existing packages', min_length=1), + ] = None + invoice_recipient: Annotated[ + business_entity.BusinessEntity | None, + Field( + description="Update who receives the invoice for this buy. When provided, the seller invoices this entity instead of the account's default billing_entity. The seller MUST validate the invoice recipient is authorized for this account. When governance_agents are configured, the seller MUST include invoice_recipient in the check_governance request." + ), + ] = None + new_packages: Annotated[ + list[package_request.PackageRequest] | None, + Field( + description='New packages to add to this media buy. Uses the same schema as create_media_buy packages. When budget_allocation is omitted or fixed, every new package MUST carry budget and MUST NOT carry min_spend_target. To add a package without a hard cap to an existing seller-optimized buy, include its resulting seller_optimized budget_allocation block in the update so the allocation context is schema-visible. Repeating an unchanged allocation block does not itself switch modes. Sellers that support mid-flight package additions advertise `add_packages` in both `valid_actions[]` (deprecated) and as an entry in `available_actions[]` (authoritative). Sellers that do not support this MUST reject with ACTION_NOT_ALLOWED (preferred) or UNSUPPORTED_FEATURE (legacy). If the buy has a root frequency_cap, every added product must support that exact aggregate cap; otherwise the seller rejects atomically with UNSUPPORTED_FEATURE, leaving the whole update unapplied and preserving the buy and its counter history.', + min_length=1, + ), + ] = None + reporting_webhook: Annotated[ + reporting_webhook_1.ReportingWebhook | None, + Field( + description='Optional webhook configuration for automated reporting delivery. Updates the reporting configuration for this media buy.' + ), + ] = None + push_notification_config: Annotated[ + push_notification_config_1.PushNotificationConfig | None, + Field( + description='Optional webhook configuration for async update notifications. Publisher will send webhook when update completes if operation takes longer than immediate response time. This is separate from reporting_webhook which configures ongoing campaign reporting.' + ), + ] = None + idempotency_key: Annotated[ + str, + Field( + description='Client-generated idempotency key for safe retries. If an update fails without a response, resending with the same idempotency_key guarantees the update is applied at most once. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/media_buy/update_media_buy_response.py b/src/adcp/types/domains/media_buy/update_media_buy_response.py index cd993ccf9..61b6eb3b8 100644 --- a/src/adcp/types/domains/media_buy/update_media_buy_response.py +++ b/src/adcp/types/domains/media_buy/update_media_buy_response.py @@ -1,28 +1,105 @@ -"""Types declared by the AdCP ``media_buy/update_media_buy_response`` schema. +# generated by datamodel-codegen: +# filename: media_buy/update_media_buy_response.json +# timestamp: preserved-by-post-generate-fixes -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.media_buy.update_media_buy_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from collections.abc import Sequence -# ruff: noqa: E501, I001 -from __future__ import annotations +from typing import Annotated, Any, Literal, TypeAlias + +from pydantic import AwareDatetime, ConfigDict, Field, StringConstraints, model_validator + +from ..core.version_envelope import AdcpVersionEnvelope +from ..core import business_entity as business_entity_1 +from ..core import context as context_1 +from ..core import error as error_1 +from ..core import ext as ext_1 +from ..core import media_buy_available_action as media_buy_available_action_1 +from ..core import media_buy_frequency_cap as media_buy_frequency_cap_1 +from ..core import package as package_1 +from ..core import warning as warning_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..enums import media_buy_status as media_buy_status_1 +from ..enums import media_buy_valid_action as media_buy_valid_action_1 +from ..enums import pacing as pacing_1 +from ..enums import task_status as task_status_1 +from adcp.types.media_buy_status_helpers import MEDIA_BUY_LEGACY_STATUS_VALUES, unwrap_enum_value + + +class UpdateMediaBuyResponse1(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + status: Literal['completed'] = 'completed' + media_buy_id: str + name: Annotated[str, StringConstraints(pattern='\\S', min_length=1, max_length=255)] | None = None + media_buy_status: media_buy_status_1.MediaBuyStatus | None = None + revision: Annotated[int, Field(ge=1)] + currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] | None = None + total_budget: Annotated[float, Field(ge=0)] | None = None + daily_budget_cap: Annotated[float, Field(ge=0)] | None = None + frequency_cap: media_buy_frequency_cap_1.MediaBuyFrequencyCap | None = None + budget_cap_timezone: str | None = None + budget_allocation: Any | None = None + pacing: pacing_1.Pacing | None = None + bidding: Any | None = None + implementation_date: AwareDatetime | None = None + invoice_recipient: business_entity_1.BusinessEntity | None = None + affected_packages: Sequence[package_1.Package] | None = None + valid_actions: list[media_buy_valid_action_1.MediaBuyValidAction] | None = None + available_actions: list[media_buy_available_action_1.MediaBuyAvailableAction] | None = None + warnings: list[warning_1.Warning] | None = None + sandbox: bool | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + @model_validator(mode='before') + @classmethod + def _normalize_legacy_status(cls, data: Any) -> Any: + if not isinstance(data, dict): + return data + raw_status = unwrap_enum_value(data.get('status')) + media_buy_status = unwrap_enum_value(data.get('media_buy_status')) + if raw_status is None: + data = dict(data) + data['status'] = 'completed' + elif raw_status == 'completed': + data = dict(data) + data['status'] = 'completed' + elif media_buy_status is None and raw_status in MEDIA_BUY_LEGACY_STATUS_VALUES: + data = dict(data) + data['media_buy_status'] = raw_status + data['status'] = 'completed' + elif media_buy_status is not None and raw_status == media_buy_status: + data = dict(data) + data['status'] = 'completed' + return data + + +class UpdateMediaBuyResponse2(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + errors: Annotated[list[error_1.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class UpdateMediaBuyResponse3(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow', validate_default=True) + status: Literal[task_status_1.TaskStatus.submitted] = task_status_1.TaskStatus.submitted + task_id: str + message: Annotated[str, StringConstraints(max_length=2000)] | None = None + errors: list[error_1.Error] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +UpdateMediaBuyResponse: TypeAlias = UpdateMediaBuyResponse1 | UpdateMediaBuyResponse2 | UpdateMediaBuyResponse3 -from adcp.types.generated_poc.media_buy.update_media_buy_response import ( - UpdateMediaBuyResponse, - UpdateMediaBuyResponse1, - UpdateMediaBuyResponse2, - UpdateMediaBuyResponse3, -) -# Explicit exports __all__ = [ - "UpdateMediaBuyResponse", - "UpdateMediaBuyResponse1", - "UpdateMediaBuyResponse2", - "UpdateMediaBuyResponse3", + 'UpdateMediaBuyResponse', + 'UpdateMediaBuyResponse1', + 'UpdateMediaBuyResponse2', + 'UpdateMediaBuyResponse3', ] diff --git a/src/adcp/types/domains/pricing_options/__init__.py b/src/adcp/types/domains/pricing_options/__init__.py index 8fa1c4194..69b875cae 100644 --- a/src/adcp/types/domains/pricing_options/__init__.py +++ b/src/adcp/types/domains/pricing_options/__init__.py @@ -9,29 +9,29 @@ it from its own schema's module, ``adcp.types.domains.pricing_options.``. Nothing here is renamed. -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:33:59 UTC +Auto-generated from the generated domain tree. DO NOT EDIT MANUALLY. +Generation date: 2026-10-04 18:45:11 UTC """ # ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.pricing_options.cpa_option import CpaPricingOption -from adcp.types.generated_poc.pricing_options.cpc_option import CpcPricingOption -from adcp.types.generated_poc.pricing_options.cpcv_option import CpcvPricingOption -from adcp.types.generated_poc.pricing_options.cpm_option import CpmPricingOption -from adcp.types.generated_poc.pricing_options.cpp_option import CppPricingOption -from adcp.types.generated_poc.pricing_options.cpv_option import ( +from adcp.types.domains.pricing_options.cpa_option import CpaPricingOption +from adcp.types.domains.pricing_options.cpc_option import CpcPricingOption +from adcp.types.domains.pricing_options.cpcv_option import CpcvPricingOption +from adcp.types.domains.pricing_options.cpm_option import CpmPricingOption +from adcp.types.domains.pricing_options.cpp_option import CppPricingOption +from adcp.types.domains.pricing_options.cpv_option import ( CpvPricingOption, ViewThreshold, ViewThreshold1, ) -from adcp.types.generated_poc.pricing_options.flat_rate_option import FlatRatePricingOption -from adcp.types.generated_poc.pricing_options.price_breakdown import Adjustment, PriceBreakdown -from adcp.types.generated_poc.pricing_options.price_guidance import PriceGuidance -from adcp.types.generated_poc.pricing_options.revenue_share_option import RevenueSharePricingOption -from adcp.types.generated_poc.pricing_options.time_option import TimeBasedPricingOption, TimeUnit -from adcp.types.generated_poc.pricing_options.vcpm_option import VcpmPricingOption +from adcp.types.domains.pricing_options.flat_rate_option import FlatRatePricingOption +from adcp.types.domains.pricing_options.price_breakdown import Adjustment, PriceBreakdown +from adcp.types.domains.pricing_options.price_guidance import PriceGuidance +from adcp.types.domains.pricing_options.revenue_share_option import RevenueSharePricingOption +from adcp.types.domains.pricing_options.time_option import TimeBasedPricingOption, TimeUnit +from adcp.types.domains.pricing_options.vcpm_option import VcpmPricingOption # Explicit exports __all__ = [ diff --git a/src/adcp/types/domains/pricing_options/cpa_option.py b/src/adcp/types/domains/pricing_options/cpa_option.py index 0a44820ee..5b62dca51 100644 --- a/src/adcp/types/domains/pricing_options/cpa_option.py +++ b/src/adcp/types/domains/pricing_options/cpa_option.py @@ -1,18 +1,75 @@ -"""Types declared by the AdCP ``pricing_options/cpa_option`` schema. +# generated by datamodel-codegen: +# filename: pricing_options/cpa_option.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.pricing_options.cpa_option import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import adjustment_kind +from ..enums import event_type as event_type_1 +from . import price_breakdown as price_breakdown_1 -from adcp.types.generated_poc.pricing_options.cpa_option import CpaPricingOption -# Explicit exports -__all__ = ["CpaPricingOption"] +class CpaPricingOption(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + pricing_option_id: Annotated[ + str, Field(description='Unique identifier for this pricing option within the product') + ] + pricing_model: Annotated[ + Literal['cpa'], Field(description='Cost per acquisition (conversion event)') + ] = 'cpa' + event_type: Annotated[ + event_type_1.EventType, + Field( + description='The conversion event type that triggers billing (e.g., purchase, lead, app_install)' + ), + ] + custom_event_name: Annotated[ + str | None, + Field( + description="Name of the custom event when event_type is 'custom'. Required when event_type is 'custom', ignored otherwise." + ), + ] = None + event_source_id: Annotated[ + str | None, + Field( + description='When present, only events from this specific event source count toward billing. Allows different CPA rates for different sources (e.g., online vs in-store purchases). Must match an event source configured via sync_event_sources.' + ), + ] = None + currency: Annotated[ + str, + Field( + description='ISO 4217 currency code', + examples=['USD', 'EUR', 'GBP', 'JPY'], + pattern='^[A-Z]{3}$', + ), + ] + fixed_price: Annotated[ + StrictFloat, + Field(description='Fixed price per acquisition in the specified currency', gt=0.0), + ] + min_spend_per_package: Annotated[ + StrictFloat | None, + Field( + description='Minimum spend requirement per package using this pricing option, in the specified currency', + ge=0.0, + ), + ] = None + price_breakdown: Annotated[ + price_breakdown_1.PriceBreakdown | None, + Field( + description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.' + ), + ] = None + eligible_adjustments: Annotated[ + list[adjustment_kind.PriceAdjustmentKind] | None, + Field( + description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' + ), + ] = None diff --git a/src/adcp/types/domains/pricing_options/cpc_option.py b/src/adcp/types/domains/pricing_options/cpc_option.py index 4ffe1f0d3..6ca810aa5 100644 --- a/src/adcp/types/domains/pricing_options/cpc_option.py +++ b/src/adcp/types/domains/pricing_options/cpc_option.py @@ -1,18 +1,76 @@ -"""Types declared by the AdCP ``pricing_options/cpc_option`` schema. +# generated by datamodel-codegen: +# filename: pricing_options/cpc_option.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.pricing_options.cpc_option import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictBool, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import adjustment_kind +from . import price_breakdown as price_breakdown_1 +from . import price_guidance as price_guidance_1 -from adcp.types.generated_poc.pricing_options.cpc_option import CpcPricingOption -# Explicit exports -__all__ = ["CpcPricingOption"] +class CpcPricingOption(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + pricing_option_id: Annotated[ + str, Field(description='Unique identifier for this pricing option within the product') + ] + pricing_model: Annotated[Literal['cpc'], Field(description='Cost per click')] = 'cpc' + currency: Annotated[ + str, + Field( + description='ISO 4217 currency code', + examples=['USD', 'EUR', 'GBP', 'JPY'], + pattern='^[A-Z]{3}$', + ), + ] + fixed_price: Annotated[ + StrictFloat | None, + Field( + description='Fixed price per click. If present, this is fixed pricing. If absent, auction-based.', + ge=0.0, + ), + ] = None + floor_price: Annotated[ + StrictFloat | None, + Field( + description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', + ge=0.0, + ), + ] = None + max_bid: Annotated[ + StrictBool | None, + Field( + deprecated=True, + description='DEPRECATED in 3.2 and removed in the next major. Legacy hint used only to normalize package bid_price to bidding.max_bid (true) or bidding.bid_amount (false/absent). New buyers express intent directly in bidding.', + ), + ] = False + price_guidance: Annotated[ + price_guidance_1.PriceGuidance | None, + Field(description='Optional pricing guidance for auction-based bidding'), + ] = None + min_spend_per_package: Annotated[ + StrictFloat | None, + Field( + description='Minimum spend requirement per package using this pricing option, in the specified currency', + ge=0.0, + ), + ] = None + price_breakdown: Annotated[ + price_breakdown_1.PriceBreakdown | None, + Field( + description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.' + ), + ] = None + eligible_adjustments: Annotated[ + list[adjustment_kind.PriceAdjustmentKind] | None, + Field( + description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' + ), + ] = None diff --git a/src/adcp/types/domains/pricing_options/cpcv_option.py b/src/adcp/types/domains/pricing_options/cpcv_option.py index 40996cd51..744c699cb 100644 --- a/src/adcp/types/domains/pricing_options/cpcv_option.py +++ b/src/adcp/types/domains/pricing_options/cpcv_option.py @@ -1,18 +1,78 @@ -"""Types declared by the AdCP ``pricing_options/cpcv_option`` schema. +# generated by datamodel-codegen: +# filename: pricing_options/cpcv_option.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.pricing_options.cpcv_option import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictBool, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import adjustment_kind +from . import price_breakdown as price_breakdown_1 +from . import price_guidance as price_guidance_1 -from adcp.types.generated_poc.pricing_options.cpcv_option import CpcvPricingOption -# Explicit exports -__all__ = ["CpcvPricingOption"] +class CpcvPricingOption(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + pricing_option_id: Annotated[ + str, Field(description='Unique identifier for this pricing option within the product') + ] + pricing_model: Annotated[ + Literal['cpcv'], Field(description='Cost per completed view (100% completion)') + ] = 'cpcv' + currency: Annotated[ + str, + Field( + description='ISO 4217 currency code', + examples=['USD', 'EUR', 'GBP', 'JPY'], + pattern='^[A-Z]{3}$', + ), + ] + fixed_price: Annotated[ + StrictFloat | None, + Field( + description='Fixed price per completed view. If present, this is fixed pricing. If absent, auction-based.', + ge=0.0, + ), + ] = None + floor_price: Annotated[ + StrictFloat | None, + Field( + description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', + ge=0.0, + ), + ] = None + max_bid: Annotated[ + StrictBool | None, + Field( + deprecated=True, + description='DEPRECATED in 3.2 and removed in the next major. Legacy hint used only to normalize package bid_price to bidding.max_bid (true) or bidding.bid_amount (false/absent). New buyers express intent directly in bidding.', + ), + ] = False + price_guidance: Annotated[ + price_guidance_1.PriceGuidance | None, + Field(description='Optional pricing guidance for auction-based bidding'), + ] = None + min_spend_per_package: Annotated[ + StrictFloat | None, + Field( + description='Minimum spend requirement per package using this pricing option, in the specified currency', + ge=0.0, + ), + ] = None + price_breakdown: Annotated[ + price_breakdown_1.PriceBreakdown | None, + Field( + description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.' + ), + ] = None + eligible_adjustments: Annotated[ + list[adjustment_kind.PriceAdjustmentKind] | None, + Field( + description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' + ), + ] = None diff --git a/src/adcp/types/domains/pricing_options/cpm_option.py b/src/adcp/types/domains/pricing_options/cpm_option.py index 808e900e4..63c207f99 100644 --- a/src/adcp/types/domains/pricing_options/cpm_option.py +++ b/src/adcp/types/domains/pricing_options/cpm_option.py @@ -1,18 +1,76 @@ -"""Types declared by the AdCP ``pricing_options/cpm_option`` schema. +# generated by datamodel-codegen: +# filename: pricing_options/cpm_option.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.pricing_options.cpm_option import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictBool, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import adjustment_kind +from . import price_breakdown as price_breakdown_1 +from . import price_guidance as price_guidance_1 -from adcp.types.generated_poc.pricing_options.cpm_option import CpmPricingOption -# Explicit exports -__all__ = ["CpmPricingOption"] +class CpmPricingOption(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + pricing_option_id: Annotated[ + str, Field(description='Unique identifier for this pricing option within the product') + ] + pricing_model: Annotated[Literal['cpm'], Field(description='Cost per 1,000 impressions')] = 'cpm' + currency: Annotated[ + str, + Field( + description='ISO 4217 currency code', + examples=['USD', 'EUR', 'GBP', 'JPY'], + pattern='^[A-Z]{3}$', + ), + ] + fixed_price: Annotated[ + StrictFloat | None, + Field( + description='Fixed price per unit. If present, this is fixed pricing. If absent, auction-based.', + ge=0.0, + ), + ] = None + floor_price: Annotated[ + StrictFloat | None, + Field( + description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', + ge=0.0, + ), + ] = None + max_bid: Annotated[ + StrictBool | None, + Field( + deprecated=True, + description='DEPRECATED in 3.2 and removed in the next major. Legacy hint used only to normalize package bid_price to bidding.max_bid (true) or bidding.bid_amount (false/absent). New buyers express intent directly in bidding.', + ), + ] = False + price_guidance: Annotated[ + price_guidance_1.PriceGuidance | None, + Field(description='Optional pricing guidance for auction-based bidding'), + ] = None + min_spend_per_package: Annotated[ + StrictFloat | None, + Field( + description='Minimum spend requirement per package using this pricing option, in the specified currency', + ge=0.0, + ), + ] = None + price_breakdown: Annotated[ + price_breakdown_1.PriceBreakdown | None, + Field( + description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.' + ), + ] = None + eligible_adjustments: Annotated[ + list[adjustment_kind.PriceAdjustmentKind] | None, + Field( + description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' + ), + ] = None diff --git a/src/adcp/types/domains/pricing_options/cpp_option.py b/src/adcp/types/domains/pricing_options/cpp_option.py index 1d2444cc1..53e5c30de 100644 --- a/src/adcp/types/domains/pricing_options/cpp_option.py +++ b/src/adcp/types/domains/pricing_options/cpp_option.py @@ -1,18 +1,94 @@ -"""Types declared by the AdCP ``pricing_options/cpp_option`` schema. +# generated by datamodel-codegen: +# filename: pricing_options/cpp_option.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.pricing_options.cpp_option import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import adjustment_kind +from ..enums import demographic_system as demographic_system_1 +from . import price_breakdown as price_breakdown_1 +from . import price_guidance as price_guidance_1 + + +class Parameters(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + demographic_system: Annotated[ + demographic_system_1.DemographicSystem | None, + Field( + description='Measurement system for the demographic field. Defaults to nielsen when omitted.' + ), + ] = None + demographic: Annotated[ + str, + Field( + description='Target demographic code within the specified demographic_system (e.g., P18-49 for Nielsen, ABC1 Adults for BARB)' + ), + ] + min_points: Annotated[ + StrictFloat | None, Field(description='Minimum GRPs/TRPs required', ge=0.0) + ] = None -from adcp.types.generated_poc.pricing_options.cpp_option import CppPricingOption, Parameters -# Explicit exports -__all__ = ["CppPricingOption", "Parameters"] +class CppPricingOption(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + pricing_option_id: Annotated[ + str, Field(description='Unique identifier for this pricing option within the product') + ] + pricing_model: Annotated[Literal['cpp'], Field(description='Cost per Gross Rating Point')] = 'cpp' + currency: Annotated[ + str, + Field( + description='ISO 4217 currency code', + examples=['USD', 'EUR', 'GBP', 'JPY'], + pattern='^[A-Z]{3}$', + ), + ] + fixed_price: Annotated[ + StrictFloat | None, + Field( + description='Fixed price per rating point. If present, this is fixed pricing. If absent, auction-based.', + ge=0.0, + ), + ] = None + floor_price: Annotated[ + StrictFloat | None, + Field( + description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', + ge=0.0, + ), + ] = None + price_guidance: Annotated[ + price_guidance_1.PriceGuidance | None, + Field(description='Optional pricing guidance for auction-based bidding'), + ] = None + parameters: Annotated[ + Parameters, Field(description='CPP-specific parameters for demographic targeting') + ] + min_spend_per_package: Annotated[ + StrictFloat | None, + Field( + description='Minimum spend requirement per package using this pricing option, in the specified currency', + ge=0.0, + ), + ] = None + price_breakdown: Annotated[ + price_breakdown_1.PriceBreakdown | None, + Field( + description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.' + ), + ] = None + eligible_adjustments: Annotated[ + list[adjustment_kind.PriceAdjustmentKind] | None, + Field( + description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' + ), + ] = None diff --git a/src/adcp/types/domains/pricing_options/cpv_option.py b/src/adcp/types/domains/pricing_options/cpv_option.py index e005c98e3..138e39a4c 100644 --- a/src/adcp/types/domains/pricing_options/cpv_option.py +++ b/src/adcp/types/domains/pricing_options/cpv_option.py @@ -1,23 +1,102 @@ -"""Types declared by the AdCP ``pricing_options/cpv_option`` schema. +# generated by datamodel-codegen: +# filename: pricing_options/cpv_option.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.pricing_options.cpv_option import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from adcp.types._scalar import ScalarFloat +from pydantic import ConfigDict, Field, StrictBool, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import adjustment_kind +from . import price_breakdown as price_breakdown_1 +from . import price_guidance as price_guidance_1 + + +class ViewThreshold(ScalarFloat): + __slots__ = () + _constraints = {'ge': 0.0, 'le': 1.0} + _json_schema_extra = { + 'description': 'Percentage completion threshold (0.0 to 1.0, e.g., 0.5 = 50%)', + } + + +class ViewThreshold1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + duration_seconds: Annotated[SchemaInt, Field(description='Seconds of viewing required', ge=1)] + + +class Parameters(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + view_threshold: ViewThreshold | ViewThreshold1 -from adcp.types.generated_poc.pricing_options.cpv_option import ( - CpvPricingOption, - Parameters, - ViewThreshold, - ViewThreshold1, -) -# Explicit exports -__all__ = ["CpvPricingOption", "Parameters", "ViewThreshold", "ViewThreshold1"] +class CpvPricingOption(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + pricing_option_id: Annotated[ + str, Field(description='Unique identifier for this pricing option within the product') + ] + pricing_model: Annotated[Literal['cpv'], Field(description='Cost per view at threshold')] = 'cpv' + currency: Annotated[ + str, + Field( + description='ISO 4217 currency code', + examples=['USD', 'EUR', 'GBP', 'JPY'], + pattern='^[A-Z]{3}$', + ), + ] + fixed_price: Annotated[ + StrictFloat | None, + Field( + description='Fixed price per view. If present, this is fixed pricing. If absent, auction-based.', + ge=0.0, + ), + ] = None + floor_price: Annotated[ + StrictFloat | None, + Field( + description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', + ge=0.0, + ), + ] = None + max_bid: Annotated[ + StrictBool | None, + Field( + deprecated=True, + description='DEPRECATED in 3.2 and removed in the next major. Legacy hint used only to normalize package bid_price to bidding.max_bid (true) or bidding.bid_amount (false/absent). New buyers express intent directly in bidding.', + ), + ] = False + price_guidance: Annotated[ + price_guidance_1.PriceGuidance | None, + Field(description='Optional pricing guidance for auction-based bidding'), + ] = None + parameters: Annotated[ + Parameters, Field(description='CPV-specific parameters defining the view threshold') + ] + min_spend_per_package: Annotated[ + StrictFloat | None, + Field( + description='Minimum spend requirement per package using this pricing option, in the specified currency', + ge=0.0, + ), + ] = None + price_breakdown: Annotated[ + price_breakdown_1.PriceBreakdown | None, + Field( + description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.' + ), + ] = None + eligible_adjustments: Annotated[ + list[adjustment_kind.PriceAdjustmentKind] | None, + Field( + description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' + ), + ] = None diff --git a/src/adcp/types/domains/pricing_options/flat_rate_option.py b/src/adcp/types/domains/pricing_options/flat_rate_option.py index 49e8abbb9..bf19ed369 100644 --- a/src/adcp/types/domains/pricing_options/flat_rate_option.py +++ b/src/adcp/types/domains/pricing_options/flat_rate_option.py @@ -1,21 +1,144 @@ -"""Types declared by the AdCP ``pricing_options/flat_rate_option`` schema. +# generated by datamodel-codegen: +# filename: pricing_options/flat_rate_option.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.pricing_options.flat_rate_option import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import adjustment_kind +from . import price_breakdown as price_breakdown_1 +from . import price_guidance as price_guidance_1 + + +class Parameters(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + type: Annotated[ + Literal['dooh'], Field(description='Discriminator identifying this as DOOH parameters') + ] = 'dooh' + sov_percentage: Annotated[ + StrictFloat | None, + Field( + description='Guaranteed share of voice on a 0-100 percentage scale. To compare this contracted value with delivery dooh_metrics.sov_achieved, divide sov_percentage by 100. Share is time-weighted: the sum of the reserved segment durations divided by the full loop duration. On equal-duration loops this equals the slot-count ratio.', + ge=0.0, + le=100.0, + ), + ] = None + slot_span: Annotated[ + SchemaInt | None, + Field( + description='Number of consecutive loop slots reserved by this seller-declared pre-packaged offer. Contiguity is guaranteed; position is not guaranteed unless loop_position is also present. This field does not carry a buyer-requested custom span for an offer the seller has not pre-packaged.', + ge=1, + ), + ] = None + loop_position: Annotated[ + str | None, + Field( + description='Seller-defined position label for the reserved span within the loop, such as any, first, last, or adjacent_to_content_break. This is deliberately an open string because network vocabularies differ. Consumers MUST accept unrecognized values as opaque and treat them as conveying no position guarantee the consumer understands; an unrecognized value is not a protocol error.', + min_length=1, + ), + ] = None + loop_duration_seconds: Annotated[ + SchemaInt | None, + Field( + deprecated=True, + description='Deprecated compatibility copy of the placement-level dooh_placement_attributes.loop_duration_seconds, which is the canonical source. Retained for backward compatibility; new integrations read the placement-level field. When both are present they MUST agree. A product that offers different loop durations under different prices MUST expose distinct placements or products rather than vary this compatibility copy by pricing option.', + ge=1, + ), + ] = None + min_plays_per_hour: Annotated[ + SchemaInt | None, Field(description='Minimum number of plays per hour guaranteed', ge=1) + ] = None + venue_package: Annotated[ + str | None, + Field( + description='Named collection of DOOH venues or installed surfaces included in this buy' + ), + ] = None + duration_hours: Annotated[ + StrictFloat | None, + Field( + description='Duration of the DOOH slot in hours (e.g., 24 for a full-day takeover)', + ge=0.0, + ), + ] = None + daypart: Annotated[ + str | None, + Field(description='Named daypart for this slot (e.g., morning_commute, evening_rush)'), + ] = None + estimated_impressions: Annotated[ + SchemaInt | None, + Field( + description='Estimated audience impressions for this slot (informational, not a delivery guarantee)', + ge=0, + ), + ] = None -from adcp.types.generated_poc.pricing_options.flat_rate_option import ( - FlatRatePricingOption, - Parameters, -) -# Explicit exports -__all__ = ["FlatRatePricingOption", "Parameters"] +class FlatRatePricingOption(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + pricing_option_id: Annotated[ + str, Field(description='Unique identifier for this pricing option within the product') + ] + pricing_model: Annotated[ + Literal['flat_rate'], Field(description='Fixed cost regardless of delivery volume') + ] = 'flat_rate' + currency: Annotated[ + str, + Field( + description='ISO 4217 currency code', + examples=['USD', 'EUR', 'GBP', 'JPY'], + pattern='^[A-Z]{3}$', + ), + ] + fixed_price: Annotated[ + StrictFloat | None, + Field( + description='Flat rate cost. If present, this is fixed pricing. If absent, auction-based.', + ge=0.0, + ), + ] = None + floor_price: Annotated[ + StrictFloat | None, + Field( + description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', + ge=0.0, + ), + ] = None + price_guidance: Annotated[ + price_guidance_1.PriceGuidance | None, + Field(description='Optional pricing guidance for auction-based bidding'), + ] = None + parameters: Annotated[ + Parameters | None, + Field( + description='DOOH inventory allocation parameters. Sponsorship and takeover flat_rate options omit this field entirely — only include for digital out-of-home inventory.', + title='DoohParameters', + ), + ] = None + min_spend_per_package: Annotated[ + StrictFloat | None, + Field( + description='Minimum spend requirement per package using this pricing option, in the specified currency', + ge=0.0, + ), + ] = None + price_breakdown: Annotated[ + price_breakdown_1.PriceBreakdown | None, + Field( + description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.' + ), + ] = None + eligible_adjustments: Annotated[ + list[adjustment_kind.PriceAdjustmentKind] | None, + Field( + description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' + ), + ] = None diff --git a/src/adcp/types/domains/pricing_options/price_breakdown.py b/src/adcp/types/domains/pricing_options/price_breakdown.py index bc0557e8e..88bfc3165 100644 --- a/src/adcp/types/domains/pricing_options/price_breakdown.py +++ b/src/adcp/types/domains/pricing_options/price_breakdown.py @@ -1,18 +1,88 @@ -"""Types declared by the AdCP ``pricing_options/price_breakdown`` schema. +# generated by datamodel-codegen: +# filename: pricing_options/price_breakdown.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.pricing_options.price_breakdown import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import adjustment_kind + + +class Adjustment(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + kind: adjustment_kind.PriceAdjustmentKind + name: Annotated[ + str, + Field( + description='Specific adjustment name. Use well-known values where applicable for interoperability.', + examples=[ + 'ad_serving', + 'data_targeting', + 'brand_safety', + 'volume', + 'negotiated', + 'early_booking', + 'agency', + 'intermediary', + 'cash_discount', + 'early_payment', + ], + max_length=64, + ), + ] + rate: Annotated[ + StrictFloat | None, + Field( + description='Adjustment as a decimal proportion (e.g., 0.15 for 15%). Always positive — kind determines the economic effect. Mutually exclusive with amount.', + gt=0.0, + lt=1.0, + ), + ] = None + amount: Annotated[ + StrictFloat | None, + Field( + description="Adjustment as a fixed monetary amount in the pricing option's currency. Always positive — kind determines the economic effect. Mutually exclusive with rate.", + gt=0.0, + ), + ] = None + description: Annotated[ + str | None, + Field( + description="Human-readable description of this adjustment (e.g., 'Malstaffel 12x', '2% Skonto 10 Tage')", + max_length=256, + ), + ] = None + beneficiary: Annotated[ + str | None, + Field( + description="Identifies who receives this adjustment's value. For commissions, the intermediary (e.g., a sellers.json domain, an AdCP account ID, or a human-readable party name). Optional but recommended for multi-intermediary transparency.", + max_length=256, + ), + ] = None -from adcp.types.generated_poc.pricing_options.price_breakdown import Adjustment, PriceBreakdown -# Explicit exports -__all__ = ["Adjustment", "PriceBreakdown"] +class PriceBreakdown(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + list_price: Annotated[ + StrictFloat, + Field( + description='Rate card or base price before any adjustments. The starting point from which fixed_price is derived by applying fee and discount adjustments sequentially.', + gt=0.0, + ), + ] + adjustments: Annotated[ + list[Adjustment], + Field( + description="Ordered list of price adjustments. Fee and discount adjustments walk list_price to fixed_price — fees increase the running price, discounts reduce it. Commission and settlement adjustments are disclosed for transparency but do not affect the buyer's committed price.", + max_length=20, + min_length=1, + ), + ] diff --git a/src/adcp/types/domains/pricing_options/price_guidance.py b/src/adcp/types/domains/pricing_options/price_guidance.py index 1b041a78c..8b96a35d9 100644 --- a/src/adcp/types/domains/pricing_options/price_guidance.py +++ b/src/adcp/types/domains/pricing_options/price_guidance.py @@ -1,18 +1,28 @@ -"""Types declared by the AdCP ``pricing_options/price_guidance`` schema. +# generated by datamodel-codegen: +# filename: pricing_options/price_guidance.json +# timestamp: 2026-10-03T15:09:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.pricing_options.price_guidance import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictFloat -from adcp.types.generated_poc.pricing_options.price_guidance import PriceGuidance -# Explicit exports -__all__ = ["PriceGuidance"] +class PriceGuidance(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + p25: Annotated[ + StrictFloat | None, Field(description='25th percentile of recent winning bids', ge=0.0) + ] = None + p50: Annotated[ + StrictFloat | None, Field(description='Median of recent winning bids', ge=0.0) + ] = None + p75: Annotated[ + StrictFloat | None, Field(description='75th percentile of recent winning bids', ge=0.0) + ] = None + p90: Annotated[ + StrictFloat | None, Field(description='90th percentile of recent winning bids', ge=0.0) + ] = None diff --git a/src/adcp/types/domains/pricing_options/revenue_share_option.py b/src/adcp/types/domains/pricing_options/revenue_share_option.py index c33c3c813..c9efe72d4 100644 --- a/src/adcp/types/domains/pricing_options/revenue_share_option.py +++ b/src/adcp/types/domains/pricing_options/revenue_share_option.py @@ -1,18 +1,72 @@ -"""Types declared by the AdCP ``pricing_options/revenue_share_option`` schema. +# generated by datamodel-codegen: +# filename: pricing_options/revenue_share_option.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.pricing_options.revenue_share_option import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import event_type as event_type_1 -from adcp.types.generated_poc.pricing_options.revenue_share_option import RevenueSharePricingOption -# Explicit exports -__all__ = ["RevenueSharePricingOption"] +class RevenueSharePricingOption(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + pricing_option_id: Annotated[ + str, + Field( + description='Unique identifier for this pricing option within the product', min_length=1 + ), + ] + pricing_model: Annotated[ + Literal['revenue_share'], + Field(description='Percentage commission on settled attributed conversion value'), + ] = 'revenue_share' + event_type: Annotated[ + event_type_1.EventType, + Field( + description='Conversion event whose attributed value may become commissionable (for example purchase or subscribe)' + ), + ] + custom_event_name: Annotated[ + str | None, + Field( + description='Name of the custom event when event_type is custom. Required for custom events and absent otherwise.', + min_length=1, + ), + ] = None + event_source_id: Annotated[ + str, + Field( + description='Event source whose attribution is authoritative for billing. Must match an event source configured through sync_event_sources.', + min_length=1, + ), + ] + commission_rate: Annotated[ + StrictFloat, + Field( + description='Decimal proportion applied to commissionable_value. For example, 0.04 means 4%.', + gt=0.0, + le=1.0, + ), + ] + currency: Annotated[ + str, + Field( + description='ISO 4217 currency code for commissionable_value and resulting spend', + examples=['USD', 'EUR', 'GBP', 'JPY'], + pattern='^[A-Z]{3}$', + ), + ] + commission_basis_description: Annotated[ + str, + Field( + description='Human-readable definition of inclusions, exclusions, and return or cancellation treatment used by the billing authority to calculate commissionable_value.', + max_length=1000, + min_length=1, + ), + ] diff --git a/src/adcp/types/domains/pricing_options/time_option.py b/src/adcp/types/domains/pricing_options/time_option.py index c3d5b0052..c8d6f7b26 100644 --- a/src/adcp/types/domains/pricing_options/time_option.py +++ b/src/adcp/types/domains/pricing_options/time_option.py @@ -1,22 +1,103 @@ -"""Types declared by the AdCP ``pricing_options/time_option`` schema. +# generated by datamodel-codegen: +# filename: pricing_options/time_option.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.pricing_options.time_option import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import adjustment_kind +from . import price_breakdown as price_breakdown_1 +from . import price_guidance as price_guidance_1 + + +class TimeUnit(StrEnum): + hour = 'hour' + day = 'day' + week = 'week' + month = 'month' + + +class Parameters(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + time_unit: Annotated[ + TimeUnit, + Field( + description='The time unit for pricing. Total cost = fixed_price × number of time_units in the campaign flight.' + ), + ] + min_duration: Annotated[ + SchemaInt | None, Field(description='Minimum booking duration in time_units', ge=1) + ] = None + max_duration: Annotated[ + SchemaInt | None, + Field( + description='Maximum booking duration in time_units. Must be >= min_duration when both are present.', + ge=1, + ), + ] = None -from adcp.types.generated_poc.pricing_options.time_option import ( - Parameters, - TimeBasedPricingOption, - TimeUnit, -) -# Explicit exports -__all__ = ["Parameters", "TimeBasedPricingOption", "TimeUnit"] +class TimeBasedPricingOption(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + pricing_option_id: Annotated[ + str, Field(description='Unique identifier for this pricing option within the product') + ] + pricing_model: Annotated[ + Literal['time'], + Field(description='Cost per time unit - rate scales with campaign duration'), + ] = 'time' + currency: Annotated[ + str, + Field( + description='ISO 4217 currency code', + examples=['USD', 'EUR', 'GBP', 'JPY'], + pattern='^[A-Z]{3}$', + ), + ] + fixed_price: Annotated[ + StrictFloat | None, + Field( + description='Cost per time unit. If present, this is fixed pricing. If absent, auction-based.', + ge=0.0, + ), + ] = None + floor_price: Annotated[ + StrictFloat | None, + Field( + description='Minimum acceptable bid per time unit for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', + ge=0.0, + ), + ] = None + price_guidance: Annotated[ + price_guidance_1.PriceGuidance | None, + Field(description='Optional pricing guidance for auction-based bidding'), + ] = None + parameters: Annotated[Parameters, Field(description='Time-based pricing parameters')] + min_spend_per_package: Annotated[ + StrictFloat | None, + Field( + description='Minimum spend requirement per package using this pricing option, in the specified currency', + ge=0.0, + ), + ] = None + price_breakdown: Annotated[ + price_breakdown_1.PriceBreakdown | None, + Field( + description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.' + ), + ] = None + eligible_adjustments: Annotated[ + list[adjustment_kind.PriceAdjustmentKind] | None, + Field( + description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' + ), + ] = None diff --git a/src/adcp/types/domains/pricing_options/vcpm_option.py b/src/adcp/types/domains/pricing_options/vcpm_option.py index 290b6d85c..eaa2a6992 100644 --- a/src/adcp/types/domains/pricing_options/vcpm_option.py +++ b/src/adcp/types/domains/pricing_options/vcpm_option.py @@ -1,18 +1,78 @@ -"""Types declared by the AdCP ``pricing_options/vcpm_option`` schema. +# generated by datamodel-codegen: +# filename: pricing_options/vcpm_option.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.pricing_options.vcpm_option import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictBool, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..enums import adjustment_kind +from . import price_breakdown as price_breakdown_1 +from . import price_guidance as price_guidance_1 -from adcp.types.generated_poc.pricing_options.vcpm_option import VcpmPricingOption -# Explicit exports -__all__ = ["VcpmPricingOption"] +class VcpmPricingOption(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + pricing_option_id: Annotated[ + str, Field(description='Unique identifier for this pricing option within the product') + ] + pricing_model: Annotated[ + Literal['vcpm'], Field(description='Cost per 1,000 viewable impressions (MRC standard)') + ] = 'vcpm' + currency: Annotated[ + str, + Field( + description='ISO 4217 currency code', + examples=['USD', 'EUR', 'GBP', 'JPY'], + pattern='^[A-Z]{3}$', + ), + ] + fixed_price: Annotated[ + StrictFloat | None, + Field( + description='Fixed price per unit. If present, this is fixed pricing. If absent, auction-based.', + ge=0.0, + ), + ] = None + floor_price: Annotated[ + StrictFloat | None, + Field( + description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', + ge=0.0, + ), + ] = None + max_bid: Annotated[ + StrictBool | None, + Field( + deprecated=True, + description='DEPRECATED in 3.2 and removed in the next major. Legacy hint used only to normalize package bid_price to bidding.max_bid (true) or bidding.bid_amount (false/absent). New buyers express intent directly in bidding.', + ), + ] = False + price_guidance: Annotated[ + price_guidance_1.PriceGuidance | None, + Field(description='Optional pricing guidance for auction-based bidding'), + ] = None + min_spend_per_package: Annotated[ + StrictFloat | None, + Field( + description='Minimum spend requirement per package using this pricing option, in the specified currency', + ge=0.0, + ), + ] = None + price_breakdown: Annotated[ + price_breakdown_1.PriceBreakdown | None, + Field( + description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.' + ), + ] = None + eligible_adjustments: Annotated[ + list[adjustment_kind.PriceAdjustmentKind] | None, + Field( + description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' + ), + ] = None diff --git a/src/adcp/types/domains/property/__init__.py b/src/adcp/types/domains/property/__init__.py index 7e42c813f..e46344424 100644 --- a/src/adcp/types/domains/property/__init__.py +++ b/src/adcp/types/domains/property/__init__.py @@ -9,76 +9,60 @@ it from its own schema's module, ``adcp.types.domains.property.``. Nothing here is renamed. -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:33:59 UTC +Auto-generated from the generated domain tree. DO NOT EDIT MANUALLY. +Generation date: 2026-10-04 18:45:11 UTC """ # ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.property.authorization_result import AuthorizationResult, Violation -from adcp.types.generated_poc.property.base_property_source import ( +from adcp.types.domains.property.authorization_result import AuthorizationResult, Violation +from adcp.types.domains.property.base_property_source import ( BasePropertySource, BasePropertySource1, BasePropertySource2, BasePropertySource3, ) -from adcp.types.generated_poc.property.create_property_list_request import CreatePropertyListRequest -from adcp.types.generated_poc.property.create_property_list_response import ( - CreatePropertyListResponse, -) -from adcp.types.generated_poc.property.delete_property_list_request import DeletePropertyListRequest -from adcp.types.generated_poc.property.delete_property_list_response import ( - DeletePropertyListResponse, -) -from adcp.types.generated_poc.property.delivery_record import DeliveryRecord -from adcp.types.generated_poc.property.get_property_list_request import ( - GetPropertyListRequest, - Pagination, -) -from adcp.types.generated_poc.property.get_property_list_response import GetPropertyListResponse -from adcp.types.generated_poc.property.list_property_lists_request import ListPropertyListsRequest -from adcp.types.generated_poc.property.list_property_lists_response import ListPropertyListsResponse -from adcp.types.generated_poc.property.property_error import Code, PropertyError -from adcp.types.generated_poc.property.property_feature import PropertyFeature -from adcp.types.generated_poc.property.property_feature_definition import ( +from adcp.types.domains.property.create_property_list_request import CreatePropertyListRequest +from adcp.types.domains.property.create_property_list_response import CreatePropertyListResponse +from adcp.types.domains.property.delete_property_list_request import DeletePropertyListRequest +from adcp.types.domains.property.delete_property_list_response import DeletePropertyListResponse +from adcp.types.domains.property.delivery_record import DeliveryRecord +from adcp.types.domains.property.get_property_list_request import GetPropertyListRequest, Pagination +from adcp.types.domains.property.get_property_list_response import GetPropertyListResponse +from adcp.types.domains.property.list_property_lists_request import ListPropertyListsRequest +from adcp.types.domains.property.list_property_lists_response import ListPropertyListsResponse +from adcp.types.domains.property.property_error import Code, PropertyError +from adcp.types.domains.property.property_feature import PropertyFeature +from adcp.types.domains.property.property_feature_definition import ( Coverage, PropertyFeatureDefinition, Range, Type, ) -from adcp.types.generated_poc.property.property_feature_result import ( +from adcp.types.domains.property.property_feature_result import ( CoverageStatus, PropertyFeatureResult, ) -from adcp.types.generated_poc.property.property_feature_value import PropertyFeatureValue -from adcp.types.generated_poc.property.property_list import PropertyList -from adcp.types.generated_poc.property.property_list_changed_webhook import ( +from adcp.types.domains.property.property_feature_value import PropertyFeatureValue +from adcp.types.domains.property.property_list import PropertyList +from adcp.types.domains.property.property_list_changed_webhook import ( ChangeSummary, PropertyListChangedWebhook, ) -from adcp.types.generated_poc.property.property_list_filters import ( - CountriesAllItem, - PropertyListFilters, -) -from adcp.types.generated_poc.property.update_property_list_request import UpdatePropertyListRequest -from adcp.types.generated_poc.property.update_property_list_response import ( - UpdatePropertyListResponse, -) -from adcp.types.generated_poc.property.validate_property_delivery_request import ( +from adcp.types.domains.property.property_list_filters import CountriesAllItem, PropertyListFilters +from adcp.types.domains.property.update_property_list_request import UpdatePropertyListRequest +from adcp.types.domains.property.update_property_list_response import UpdatePropertyListResponse +from adcp.types.domains.property.validate_property_delivery_request import ( ValidatePropertyDeliveryRequest, ) -from adcp.types.generated_poc.property.validate_property_delivery_response import ( +from adcp.types.domains.property.validate_property_delivery_response import ( Aggregate, AuthorizationSummary, Summary, ValidatePropertyDeliveryResponse, ) -from adcp.types.generated_poc.property.validation_result import ( - Feature, - Requirement, - ValidationResult, -) +from adcp.types.domains.property.validation_result import Feature, Requirement, ValidationResult # Explicit exports __all__ = [ diff --git a/src/adcp/types/domains/property/authorization_result.py b/src/adcp/types/domains/property/authorization_result.py index b8ea28df6..91ca344ee 100644 --- a/src/adcp/types/domains/property/authorization_result.py +++ b/src/adcp/types/domains/property/authorization_result.py @@ -1,22 +1,49 @@ -"""Types declared by the AdCP ``property/authorization_result`` schema. +# generated by datamodel-codegen: +# filename: property/authorization_result.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.property.authorization_result import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.property.authorization_result import ( - AuthorizationResult, - Status, - Violation, -) +class Status(StrEnum): + authorized = 'authorized' + unauthorized = 'unauthorized' + unknown = 'unknown' + + +class Violation(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + code: Annotated[str, Field(description='Machine-readable violation code')] + message: Annotated[str, Field(description='Human-readable violation description')] + -# Explicit exports -__all__ = ["AuthorizationResult", "Status", "Violation"] +class AuthorizationResult(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + status: Annotated[ + Status, + Field( + description='Authorization status: authorized (agent in adagents.json), unauthorized (agent not in adagents.json), unknown (could not fetch or parse adagents.json)' + ), + ] + publisher_domain: Annotated[ + str | None, Field(description='The publisher domain where adagents.json was checked') + ] = None + sales_agent_url: Annotated[ + AnyUrl | None, Field(description='The sales agent URL that was validated') + ] = None + violation: Annotated[ + Violation | None, + Field( + description='Details about the authorization failure (only present for unauthorized status)' + ), + ] = None diff --git a/src/adcp/types/domains/property/base_property_source.py b/src/adcp/types/domains/property/base_property_source.py index 12db9fa7f..3b90cc6f3 100644 --- a/src/adcp/types/domains/property/base_property_source.py +++ b/src/adcp/types/domains/property/base_property_source.py @@ -1,28 +1,83 @@ -"""Types declared by the AdCP ``property/base_property_source`` schema. +# generated by datamodel-codegen: +# filename: property/base_property_source.json +# timestamp: 2026-10-02T23:18:44+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.property.base_property_source import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field + +from ..core import identifier, property_id, property_tag + + +class BasePropertySource1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + selection_type: Annotated[ + Literal['publisher_tags'], + Field(description='Discriminator indicating selection by property tags within a publisher'), + ] = 'publisher_tags' + publisher_domain: Annotated[ + str, + Field( + description="Domain where publisher's adagents.json is hosted (e.g., 'raptive.com')", + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] + tags: Annotated[ + list[property_tag.PropertyTag], + Field( + description="Property tags from the publisher's adagents.json. Selects all properties with these tags.", + min_length=1, + ), + ] + + +class BasePropertySource2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + selection_type: Annotated[ + Literal['publisher_ids'], + Field( + description='Discriminator indicating selection by specific property IDs within a publisher' + ), + ] = 'publisher_ids' + publisher_domain: Annotated[ + str, + Field( + description="Domain where publisher's adagents.json is hosted (e.g., 'raptive.com')", + pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + ), + ] + property_ids: Annotated[ + list[property_id.PropertyId], + Field(description="Specific property IDs from the publisher's adagents.json", min_length=1), + ] + + +class BasePropertySource3(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + selection_type: Annotated[ + Literal['identifiers'], + Field(description='Discriminator indicating selection by direct identifiers'), + ] = 'identifiers' + identifiers: Annotated[ + list[identifier.Identifier], + Field(description='Direct property identifiers (domains, app IDs, etc.)', min_length=1), + ] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.property.base_property_source import ( - BasePropertySource, - BasePropertySource1, - BasePropertySource2, - BasePropertySource3, -) - -# Explicit exports -__all__ = [ - "BasePropertySource", - "BasePropertySource1", - "BasePropertySource2", - "BasePropertySource3", +BasePropertySource = Annotated[ + BasePropertySource1 | BasePropertySource2 | BasePropertySource3, + Field( + description='A source of properties for a property list. Supports three selection patterns: publisher with tags, publisher with property IDs, or direct identifiers.', + discriminator='selection_type', + title='Base Property Source', + ), ] diff --git a/src/adcp/types/domains/property/create_property_list_request.py b/src/adcp/types/domains/property/create_property_list_request.py index 19b9fd0bf..ab99feb5a 100644 --- a/src/adcp/types/domains/property/create_property_list_request.py +++ b/src/adcp/types/domains/property/create_property_list_request.py @@ -1,18 +1,61 @@ -"""Types declared by the AdCP ``property/create_property_list_request`` schema. +# generated by datamodel-codegen: +# filename: property/create_property_list_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.property.create_property_list_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field + +from ..core import account_ref, brand_ref +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope +from . import base_property_source, property_list_filters -from adcp.types.generated_poc.property.create_property_list_request import CreatePropertyListRequest -# Explicit exports -__all__ = ["CreatePropertyListRequest"] +class CreatePropertyListRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + account: Annotated[ + account_ref.AccountReference | None, + Field( + description='Account that will own the list. Pass a natural key (brand, operator, optional sandbox) or a seller-assigned account_id from list_accounts. When omitted, this task applies its task-local single-account shortcut: if exactly one account is accessible to the authenticated caller, the seller may assign the list to that account; otherwise it MUST return an account-required or ambiguous-account error. Omission MUST NOT mean an undocumented credential-local default account.' + ), + ] = None + name: Annotated[str, Field(description='Human-readable name for the list')] + description: Annotated[str | None, Field(description="Description of the list's purpose")] = ( + None + ) + base_properties: Annotated[ + list[base_property_source.BasePropertySource] | None, + Field( + description="Array of property sources to evaluate. Each entry is a discriminated union: publisher_tags (publisher_domain + tags), publisher_ids (publisher_domain + property_ids), or identifiers (direct identifiers). If omitted, queries the agent's entire property database.", + min_length=1, + ), + ] = None + filters: Annotated[ + property_list_filters.PropertyListFilters | None, + Field(description='Dynamic filters to apply when resolving the list'), + ] = None + brand: Annotated[ + brand_ref.BrandReference | None, + Field( + description='Brand reference. When provided, the agent automatically applies appropriate rules based on brand characteristics (industry, target_audience, etc.). Resolved at execution time.' + ), + ] = None + idempotency_key: Annotated[ + str, + Field( + description='Client-generated unique key for this request. Prevents duplicate property list creation on retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/property/create_property_list_response.py b/src/adcp/types/domains/property/create_property_list_response.py index ca3e33b5e..79517ce84 100644 --- a/src/adcp/types/domains/property/create_property_list_response.py +++ b/src/adcp/types/domains/property/create_property_list_response.py @@ -1,20 +1,38 @@ -"""Types declared by the AdCP ``property/create_property_list_response`` schema. +# generated by datamodel-codegen: +# filename: property/create_property_list_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.property.create_property_list_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field, StrictBool + +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from . import property_list -from adcp.types.generated_poc.property.create_property_list_response import ( - CreatePropertyListResponse, -) -# Explicit exports -__all__ = ["CreatePropertyListResponse"] +class CreatePropertyListResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + list: Annotated[property_list.PropertyList, Field(description='The created property list')] + auth_token: Annotated[ + str, + Field( + description='Token that can be shared with sellers to authorize fetching this list. Store this - it is only returned at creation time.' + ), + ] + replayed: Annotated[ + StrictBool | None, + Field( + description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. `replayed` appears only when the request actually resolved through the idempotency cache. Pure reads may ignore an optional `idempotency_key`; when a seller voluntarily caches keyed reads, those responses use the same replay indicator and full cache contract." + ), + ] = False + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/property/delete_property_list_request.py b/src/adcp/types/domains/property/delete_property_list_request.py index 963269dcb..fc8d246cf 100644 --- a/src/adcp/types/domains/property/delete_property_list_request.py +++ b/src/adcp/types/domains/property/delete_property_list_request.py @@ -1,18 +1,40 @@ -"""Types declared by the AdCP ``property/delete_property_list_request`` schema. +# generated by datamodel-codegen: +# filename: property/delete_property_list_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.property.delete_property_list_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field + +from ..core import account_ref +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope -from adcp.types.generated_poc.property.delete_property_list_request import DeletePropertyListRequest -# Explicit exports -__all__ = ["DeletePropertyListRequest"] +class DeletePropertyListRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + list_id: Annotated[str, Field(description='ID of the property list to delete')] + account: Annotated[ + account_ref.AccountReference | None, + Field( + description='Account that owns the list. Required when the authenticated agent has access to multiple accounts; optional otherwise.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + idempotency_key: Annotated[ + str, + Field( + description='Client-generated unique key for at-most-once execution. If a request with the same key has already been processed, the server returns the original response without re-processing. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] diff --git a/src/adcp/types/domains/property/delete_property_list_response.py b/src/adcp/types/domains/property/delete_property_list_response.py index 6484e9594..922e36f1c 100644 --- a/src/adcp/types/domains/property/delete_property_list_response.py +++ b/src/adcp/types/domains/property/delete_property_list_response.py @@ -1,20 +1,32 @@ -"""Types declared by the AdCP ``property/delete_property_list_response`` schema. +# generated by datamodel-codegen: +# filename: property/delete_property_list_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.property.delete_property_list_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field, StrictBool + +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope -from adcp.types.generated_poc.property.delete_property_list_response import ( - DeletePropertyListResponse, -) -# Explicit exports -__all__ = ["DeletePropertyListResponse"] +class DeletePropertyListResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + deleted: Annotated[StrictBool, Field(description='Whether the list was successfully deleted')] + list_id: Annotated[str, Field(description='ID of the deleted list')] + replayed: Annotated[ + StrictBool | None, + Field( + description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. `replayed` appears only when the request actually resolved through the idempotency cache. Pure reads may ignore an optional `idempotency_key`; when a seller voluntarily caches keyed reads, those responses use the same replay indicator and full cache contract." + ), + ] = False + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/property/delivery_record.py b/src/adcp/types/domains/property/delivery_record.py index 660be03f4..d09d59e17 100644 --- a/src/adcp/types/domains/property/delivery_record.py +++ b/src/adcp/types/domains/property/delivery_record.py @@ -1,18 +1,39 @@ -"""Types declared by the AdCP ``property/delivery_record`` schema. +# generated by datamodel-codegen: +# filename: property/delivery_record.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.property.delivery_record import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AnyUrl, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import ext as ext_1 +from ..core import identifier as identifier_1 -from adcp.types.generated_poc.property.delivery_record import DeliveryRecord -# Explicit exports -__all__ = ["DeliveryRecord"] +class DeliveryRecord(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + identifier: Annotated[ + identifier_1.Identifier, + Field(description='The property identifier where impressions were delivered'), + ] + impressions: Annotated[ + SchemaInt, Field(description='Number of impressions delivered to this identifier', ge=0) + ] + record_id: Annotated[ + str | None, + Field( + description='Optional client-provided ID for correlating results back to source data' + ), + ] = None + sales_agent_url: Annotated[ + AnyUrl | None, + Field( + description="URL of the sales agent that sold this inventory. If provided, authorization is validated against the property's adagents.json." + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/property/get_property_list_request.py b/src/adcp/types/domains/property/get_property_list_request.py index 8169dda01..3d32408f1 100644 --- a/src/adcp/types/domains/property/get_property_list_request.py +++ b/src/adcp/types/domains/property/get_property_list_request.py @@ -1,21 +1,58 @@ -"""Types declared by the AdCP ``property/get_property_list_request`` schema. +# generated by datamodel-codegen: +# filename: property/get_property_list_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.property.get_property_list_request import +from adcp.types.base import AdcpRequest, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictBool + +from ..core import account_ref +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope + + +class Pagination(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + max_results: Annotated[ + SchemaInt | None, + Field(description='Maximum number of identifiers to return per page', ge=1, le=10000), + ] = 1000 + cursor: Annotated[ + str | None, + Field(description='Opaque cursor from a previous response to fetch the next page'), + ] = None -from adcp.types.generated_poc.property.get_property_list_request import ( - GetPropertyListRequest, - Pagination, -) -# Explicit exports -__all__ = ["GetPropertyListRequest", "Pagination"] +class GetPropertyListRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + list_id: Annotated[str, Field(description='ID of the property list to retrieve')] + account: Annotated[ + account_ref.AccountReference | None, + Field( + description='Account that owns the list. Required when the authenticated agent has access to multiple accounts and the list_id is not globally unique within that scope; optional otherwise.' + ), + ] = None + resolve: Annotated[ + StrictBool | None, + Field( + description='Whether to apply filters and return resolved identifiers (default: true)' + ), + ] = True + pagination: Annotated[ + Pagination | None, + Field( + description='Pagination parameters. Uses higher limits than standard pagination because property lists can contain tens of thousands of identifiers.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/property/get_property_list_response.py b/src/adcp/types/domains/property/get_property_list_response.py index acd86d67b..95d601ef9 100644 --- a/src/adcp/types/domains/property/get_property_list_response.py +++ b/src/adcp/types/domains/property/get_property_list_response.py @@ -1,18 +1,55 @@ -"""Types declared by the AdCP ``property/get_property_list_response`` schema. +# generated by datamodel-codegen: +# filename: property/get_property_list_response.json +# timestamp: 2026-10-04T02:05:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.property.get_property_list_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import AwareDatetime, ConfigDict, Field + +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core import identifier, pagination_response +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from . import property_list + + +_list = list # alias to avoid shadowing by field name -from adcp.types.generated_poc.property.get_property_list_response import GetPropertyListResponse -# Explicit exports -__all__ = ["GetPropertyListResponse"] +class GetPropertyListResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + list: Annotated[ + property_list.PropertyList, + Field(description='The property list metadata (always returned)'), + ] + identifiers: Annotated[ + _list[identifier.Identifier] | None, + Field( + description='Resolved identifiers that passed filters (if resolve=true). Cache these locally for real-time use.' + ), + ] = None + pagination: pagination_response.PaginationResponse | None = None + resolved_at: Annotated[ + AwareDatetime | None, Field(description='When the list was resolved') + ] = None + cache_valid_until: Annotated[ + AwareDatetime | None, + Field( + description='Cache expiration timestamp. Re-fetch the list after this time to get updated identifiers.' + ), + ] = None + coverage_gaps: Annotated[ + dict[str, _list[identifier.Identifier]] | None, + Field( + description="Properties included in the list despite missing feature data. Only present when a feature_requirement has if_not_covered='include'. Maps feature_id to list of identifiers not covered for that feature." + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/property/list_property_lists_request.py b/src/adcp/types/domains/property/list_property_lists_request.py index 6ee17437a..dedb9247c 100644 --- a/src/adcp/types/domains/property/list_property_lists_request.py +++ b/src/adcp/types/domains/property/list_property_lists_request.py @@ -1,18 +1,35 @@ -"""Types declared by the AdCP ``property/list_property_lists_request`` schema. +# generated by datamodel-codegen: +# filename: property/list_property_lists_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.property.list_property_lists_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field + +from ..core import account_ref +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core import pagination_request +from ..core.version_envelope import AdcpVersionEnvelope -from adcp.types.generated_poc.property.list_property_lists_request import ListPropertyListsRequest -# Explicit exports -__all__ = ["ListPropertyListsRequest"] +class ListPropertyListsRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + account: Annotated[ + account_ref.AccountReference | None, + Field( + description='Filter to lists owned by this account. When omitted, returns lists across all accounts accessible to the authenticated agent.' + ), + ] = None + name_contains: Annotated[ + str | None, Field(description='Filter to lists whose name contains this string') + ] = None + pagination: pagination_request.PaginationRequest | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/property/list_property_lists_response.py b/src/adcp/types/domains/property/list_property_lists_response.py index 544c2c1a5..0fc65146d 100644 --- a/src/adcp/types/domains/property/list_property_lists_response.py +++ b/src/adcp/types/domains/property/list_property_lists_response.py @@ -1,18 +1,31 @@ -"""Types declared by the AdCP ``property/list_property_lists_response`` schema. +# generated by datamodel-codegen: +# filename: property/list_property_lists_response.json +# timestamp: 2026-10-04T02:05:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.property.list_property_lists_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field + +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core import pagination_response +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from . import property_list -from adcp.types.generated_poc.property.list_property_lists_response import ListPropertyListsResponse -# Explicit exports -__all__ = ["ListPropertyListsResponse"] +class ListPropertyListsResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + lists: Annotated[ + list[property_list.PropertyList], + Field(description='Array of property lists (metadata only, not resolved properties)'), + ] + pagination: pagination_response.PaginationResponse | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/property/property_error.py b/src/adcp/types/domains/property/property_error.py index 4fcd69d70..d71392538 100644 --- a/src/adcp/types/domains/property/property_error.py +++ b/src/adcp/types/domains/property/property_error.py @@ -1,18 +1,33 @@ -"""Types declared by the AdCP ``property/property_error`` schema. +# generated by datamodel-codegen: +# filename: property/property_error.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.property.property_error import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import property as property_1 + + +class Code(StrEnum): + PROPERTY_NOT_FOUND = 'PROPERTY_NOT_FOUND' + PROPERTY_NOT_MONITORED = 'PROPERTY_NOT_MONITORED' + LIST_NOT_FOUND = 'LIST_NOT_FOUND' + LIST_ACCESS_DENIED = 'LIST_ACCESS_DENIED' + METHODOLOGY_NOT_SUPPORTED = 'METHODOLOGY_NOT_SUPPORTED' + JURISDICTION_NOT_SUPPORTED = 'JURISDICTION_NOT_SUPPORTED' -from adcp.types.generated_poc.property.property_error import Code, PropertyError -# Explicit exports -__all__ = ["Code", "PropertyError"] +class PropertyError(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + code: Annotated[Code, Field(description='Error code')] + property: Annotated[ + property_1.Property | None, Field(description='The property that caused the error') + ] = None + message: Annotated[str, Field(description='Human-readable error message')] diff --git a/src/adcp/types/domains/property/property_feature.py b/src/adcp/types/domains/property/property_feature.py index 679871d36..4d7a1cbef 100644 --- a/src/adcp/types/domains/property/property_feature.py +++ b/src/adcp/types/domains/property/property_feature.py @@ -1,18 +1,22 @@ -"""Types declared by the AdCP ``property/property_feature`` schema. +# generated by datamodel-codegen: +# filename: property/property_feature.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.property.property_feature import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -from adcp.types.generated_poc.property.property_feature import PropertyFeature -# Explicit exports -__all__ = ["PropertyFeature"] +class PropertyFeature(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + feature_id: Annotated[str, Field(description='Identifier for the feature being assessed')] + value: Annotated[str, Field(description='The feature value')] + source: Annotated[ + str | None, + Field(description='Source of the feature data (e.g., app_store_privacy_label, tcf_string)'), + ] = None diff --git a/src/adcp/types/domains/property/property_feature_definition.py b/src/adcp/types/domains/property/property_feature_definition.py index 6d4950b71..56cbfd1fd 100644 --- a/src/adcp/types/domains/property/property_feature_definition.py +++ b/src/adcp/types/domains/property/property_feature_definition.py @@ -1,23 +1,80 @@ -"""Types declared by the AdCP ``property/property_feature_definition`` schema. +# generated by datamodel-codegen: +# filename: property/property_feature_definition.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.property.property_feature_definition import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import ext as ext_1 + + +class Type(StrEnum): + binary = 'binary' + quantitative = 'quantitative' + categorical = 'categorical' + + +class Range(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + min: Annotated[StrictFloat, Field(description='Minimum value')] + max: Annotated[StrictFloat, Field(description='Maximum value')] + + +class Coverage(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + property_types: Annotated[ + list[str] | None, Field(description='Property types this feature applies to') + ] = None + countries: Annotated[ + list[str] | None, Field(description='Countries where this feature is available') + ] = None -from adcp.types.generated_poc.property.property_feature_definition import ( - Coverage, - PropertyFeatureDefinition, - Range, - Type, -) -# Explicit exports -__all__ = ["Coverage", "PropertyFeatureDefinition", "Range", "Type"] +class PropertyFeatureDefinition(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + feature_id: Annotated[ + str, + Field( + description="Unique identifier for this feature (e.g., 'consent_quality', 'carbon_score'). Features prefixed with 'registry:' reference standardized policies from the shared policy registry (e.g., 'registry:us_coppa', 'registry:uk_hfss'). Unprefixed feature IDs are agent-defined." + ), + ] + name: Annotated[str, Field(description='Human-readable name for the feature')] + description: Annotated[ + str | None, Field(description='Description of what this feature measures or represents') + ] = None + type: Annotated[ + Type, + Field( + description='The type of values this feature produces: binary (true/false), quantitative (numeric range), categorical (enumerated values)' + ), + ] + range: Annotated[ + Range | None, Field(description='For quantitative features, the valid range of values') + ] = None + allowed_values: Annotated[ + list[str] | None, Field(description='For categorical features, the set of valid values') + ] = None + coverage: Annotated[ + Coverage | None, Field(description='What this feature covers (empty arrays = all)') + ] = None + methodology_url: Annotated[ + AnyUrl, + Field( + description='URL to documentation explaining how this feature is calculated/measured' + ), + ] + methodology_version: Annotated[ + str | None, Field(description='Version identifier for the methodology (for audit trails)') + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/property/property_feature_result.py b/src/adcp/types/domains/property/property_feature_result.py index 9220649cc..2fe9a72f7 100644 --- a/src/adcp/types/domains/property/property_feature_result.py +++ b/src/adcp/types/domains/property/property_feature_result.py @@ -1,21 +1,45 @@ -"""Types declared by the AdCP ``property/property_feature_result`` schema. +# generated by datamodel-codegen: +# filename: property/property_feature_result.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.property.property_feature_result import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import ext as ext_1 +from ..core import property_id +from . import property_feature_value + + +class CoverageStatus(StrEnum): + covered = 'covered' + not_covered = 'not_covered' + pending = 'pending' -from adcp.types.generated_poc.property.property_feature_result import ( - CoverageStatus, - PropertyFeatureResult, -) -# Explicit exports -__all__ = ["CoverageStatus", "PropertyFeatureResult"] +class PropertyFeatureResult(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + property: Annotated[ + str | property_id.PropertyId, Field(description='The property these features apply to') + ] + features: Annotated[ + dict[str, property_feature_value.PropertyFeatureValue] | None, + Field(description='Map of feature_id to feature value'), + ] = None + coverage_status: Annotated[ + CoverageStatus, + Field( + description='Whether this property is covered by this governance agent: covered (has data), not_covered (not measured), pending (measurement in progress)' + ), + ] + last_evaluated: Annotated[ + AwareDatetime | None, + Field(description='When features were last evaluated for this property'), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/property/property_feature_value.py b/src/adcp/types/domains/property/property_feature_value.py index e42a07fed..0b2e7b40b 100644 --- a/src/adcp/types/domains/property/property_feature_value.py +++ b/src/adcp/types/domains/property/property_feature_value.py @@ -1,18 +1,51 @@ -"""Types declared by the AdCP ``property/property_feature_value`` schema. +# generated by datamodel-codegen: +# filename: property/property_feature_value.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.property.property_feature_value import +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import ext as ext_1 -from adcp.types.generated_poc.property.property_feature_value import PropertyFeatureValue -# Explicit exports -__all__ = ["PropertyFeatureValue"] +class PropertyFeatureValue(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + value: Annotated[ + StrictBool | StrictFloat | str, + Field( + description='The feature value. Type depends on feature definition: boolean for binary, number for quantitative, string for categorical.' + ), + ] + unit: Annotated[ + str | None, + Field( + description="Unit of measurement for quantitative values (e.g., 'gCO2e/1000_impressions', 'percentage')" + ), + ] = None + confidence: Annotated[ + StrictFloat | None, + Field(description='Confidence score for this value (0-1)', ge=0.0, le=1.0), + ] = None + measured_at: Annotated[ + AwareDatetime | None, Field(description='When this specific value was measured') + ] = None + expires_at: Annotated[ + AwareDatetime | None, + Field( + description='When this certification/value expires (for time-limited certifications)' + ), + ] = None + methodology_version: Annotated[ + str | None, Field(description='Version of the methodology used to calculate this value') + ] = None + details: Annotated[ + dict[str, Any] | None, + Field(description='Additional vendor-specific details about this measurement'), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/property/property_list.py b/src/adcp/types/domains/property/property_list.py index 9a1f90e42..d68a10c20 100644 --- a/src/adcp/types/domains/property/property_list.py +++ b/src/adcp/types/domains/property/property_list.py @@ -1,18 +1,74 @@ -"""Types declared by the AdCP ``property/property_list`` schema. +# generated by datamodel-codegen: +# filename: property/property_list.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.property.property_list import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import account_ref, brand_ref, vendor_pricing_option +from . import base_property_source, property_list_filters -from adcp.types.generated_poc.property.property_list import PropertyList -# Explicit exports -__all__ = ["PropertyList"] +class PropertyList(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + list_id: Annotated[str, Field(description='Unique identifier for this property list')] + name: Annotated[str, Field(description='Human-readable name for the list')] + description: Annotated[str | None, Field(description="Description of the list's purpose")] = ( + None + ) + account: Annotated[ + account_ref.AccountReference | None, + Field( + description='Account that owns this list. Returned as account_id form (seller-assigned identifier).' + ), + ] = None + base_properties: Annotated[ + list[base_property_source.BasePropertySource] | None, + Field( + description="Array of property sources to evaluate. Each entry is a discriminated union: publisher_tags (publisher_domain + tags), publisher_ids (publisher_domain + property_ids), or identifiers (direct identifiers). If omitted, queries the agent's entire property database." + ), + ] = None + filters: Annotated[ + property_list_filters.PropertyListFilters | None, + Field(description='Dynamic filters applied when resolving the list'), + ] = None + brand: Annotated[ + brand_ref.BrandReference | None, + Field( + description='Brand reference used to automatically apply appropriate rules. Resolved to full brand identity at execution time.' + ), + ] = None + webhook_url: Annotated[ + AnyUrl | None, + Field(description='URL to receive notifications when the resolved list changes'), + ] = None + cache_duration_hours: Annotated[ + SchemaInt | None, + Field( + description='Recommended cache duration for resolved list. Consumers should re-fetch after this period.', + ge=1, + ), + ] = 24 + created_at: Annotated[AwareDatetime | None, Field(description='When the list was created')] = ( + None + ) + updated_at: Annotated[ + AwareDatetime | None, Field(description='When the list was last modified') + ] = None + property_count: Annotated[ + SchemaInt | None, + Field(description='Number of properties in the resolved list (at time of last resolution)'), + ] = None + pricing_options: Annotated[ + list[vendor_pricing_option.VendorPricingOption] | None, + Field( + description='Pricing options for this property list. Present when the requesting account has a billing relationship with the list provider. The buyer passes the selected pricing_option_id in report_usage.', + min_length=1, + ), + ] = None diff --git a/src/adcp/types/domains/property/property_list_changed_webhook.py b/src/adcp/types/domains/property/property_list_changed_webhook.py index 5cc4bf626..307f85d72 100644 --- a/src/adcp/types/domains/property/property_list_changed_webhook.py +++ b/src/adcp/types/domains/property/property_list_changed_webhook.py @@ -1,21 +1,61 @@ -"""Types declared by the AdCP ``property/property_list_changed_webhook`` schema. +# generated by datamodel-codegen: +# filename: property/property_list_changed_webhook.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.property.property_list_changed_webhook import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import ext as ext_1 + + +class ChangeSummary(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + properties_added: Annotated[ + SchemaInt | None, Field(description='Number of properties added since last resolution') + ] = None + properties_removed: Annotated[ + SchemaInt | None, Field(description='Number of properties removed since last resolution') + ] = None + total_properties: Annotated[ + SchemaInt | None, Field(description='Total properties in the resolved list') + ] = None -from adcp.types.generated_poc.property.property_list_changed_webhook import ( - ChangeSummary, - PropertyListChangedWebhook, -) -# Explicit exports -__all__ = ["ChangeSummary", "PropertyListChangedWebhook"] +class PropertyListChangedWebhook(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + idempotency_key: Annotated[ + str, + Field( + description='Sender-generated key stable across retries of the same webhook event. Governance agents MUST generate a cryptographically random value (UUID v4 recommended) per distinct list-change event and reuse the same key on every retry. Recipients MUST dedupe by this key, scoped to the authenticated sender identity established by the RFC 9421 signing key — keys from different governance agents are independent.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + event: Annotated[Literal['property_list_changed'], Field(description='The event type')] = 'property_list_changed' + list_id: Annotated[str, Field(description='ID of the property list that changed')] + list_name: Annotated[str | None, Field(description='Name of the property list')] = None + change_summary: Annotated[ + ChangeSummary | None, Field(description='Summary of changes to the resolved list') + ] = None + resolved_at: Annotated[AwareDatetime, Field(description='When the list was re-resolved')] + cache_valid_until: Annotated[ + AwareDatetime | None, + Field(description='When the consumer should refresh from the governance agent'), + ] = None + signature: Annotated[ + str, + Field( + deprecated=True, + description='Deprecated 3.x compatibility marker. Property-list webhooks previously required this field but did not define an implementable signature algorithm, signed bytes, or key-discovery contract. RFC 9421 signatures are carried in the Signature and Signature-Input HTTP headers and bind the complete payload through Content-Digest. New 3.2 senders MUST set this field to the literal value `rfc9421`; receivers MUST accept any string from an older 3.x sender and MUST ignore it when authenticating a delivery. The field remains required through 3.x for schema compatibility and is removed in 4.0.', + ), + ] + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/property/property_list_filters.py b/src/adcp/types/domains/property/property_list_filters.py index 2baec3d6e..011cdfa59 100644 --- a/src/adcp/types/domains/property/property_list_filters.py +++ b/src/adcp/types/domains/property/property_list_filters.py @@ -1,21 +1,54 @@ -"""Types declared by the AdCP ``property/property_list_filters`` schema. +# generated by datamodel-codegen: +# filename: property/property_list_filters.json +# timestamp: 2026-10-02T13:37:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.property.property_list_filters import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import feature_requirement, identifier +from ..enums import channels, property_type + + +class CountriesAllItem(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{2}$'} -from adcp.types.generated_poc.property.property_list_filters import ( - CountriesAllItem, - PropertyListFilters, -) -# Explicit exports -__all__ = ["CountriesAllItem", "PropertyListFilters"] +class PropertyListFilters(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + countries_all: Annotated[ + list[CountriesAllItem] | None, + Field( + description='Property must have feature data for ALL listed countries (ISO codes). When omitted, no country restriction is applied.', + min_length=1, + ), + ] = None + channels_any: Annotated[ + list[channels.MediaChannel] | None, + Field( + description='Property must support ANY of the listed channels. When omitted, no channel restriction is applied.', + min_length=1, + ), + ] = None + property_types: Annotated[ + list[property_type.PropertyType] | None, + Field(description='Filter to these property types', min_length=1), + ] = None + feature_requirements: Annotated[ + list[feature_requirement.FeatureRequirement] | None, + Field( + description='Feature-based requirements. Property must pass ALL requirements (AND logic).', + min_length=1, + ), + ] = None + exclude_identifiers: Annotated[ + list[identifier.Identifier] | None, + Field(description='Identifiers to always exclude from results', min_length=1), + ] = None diff --git a/src/adcp/types/domains/property/update_property_list_request.py b/src/adcp/types/domains/property/update_property_list_request.py index d47f5ab04..ddd86196a 100644 --- a/src/adcp/types/domains/property/update_property_list_request.py +++ b/src/adcp/types/domains/property/update_property_list_request.py @@ -1,18 +1,65 @@ -"""Types declared by the AdCP ``property/update_property_list_request`` schema. +# generated by datamodel-codegen: +# filename: property/update_property_list_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.property.update_property_list_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import AnyUrl, ConfigDict, Field + +from ..core import account_ref, brand_ref +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope +from . import base_property_source, property_list_filters -from adcp.types.generated_poc.property.update_property_list_request import UpdatePropertyListRequest -# Explicit exports -__all__ = ["UpdatePropertyListRequest"] +class UpdatePropertyListRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + list_id: Annotated[str, Field(description='ID of the property list to update')] + account: Annotated[ + account_ref.AccountReference | None, + Field( + description='Account that owns the list. Required when the authenticated agent has access to multiple accounts; optional otherwise.' + ), + ] = None + name: Annotated[str | None, Field(description='New name for the list')] = None + description: Annotated[str | None, Field(description='New description')] = None + base_properties: Annotated[ + list[base_property_source.BasePropertySource] | None, + Field( + description='Complete replacement for the base properties list (not a patch). Each entry is a discriminated union: publisher_tags (publisher_domain + tags), publisher_ids (publisher_domain + property_ids), or identifiers (direct identifiers).' + ), + ] = None + filters: Annotated[ + property_list_filters.PropertyListFilters | None, + Field(description='Complete replacement for the filters (not a patch)'), + ] = None + brand: Annotated[ + brand_ref.BrandReference | None, + Field( + description='Update brand reference. Resolved to full brand identity at execution time.' + ), + ] = None + webhook_url: Annotated[ + AnyUrl | None, + Field( + description='Update the webhook URL for list change notifications (set to empty string to remove)' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + idempotency_key: Annotated[ + str, + Field( + description='Client-generated unique key for at-most-once execution. If a request with the same key has already been processed, the server returns the original response without re-processing. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] diff --git a/src/adcp/types/domains/property/update_property_list_response.py b/src/adcp/types/domains/property/update_property_list_response.py index cfd002895..27aca96d7 100644 --- a/src/adcp/types/domains/property/update_property_list_response.py +++ b/src/adcp/types/domains/property/update_property_list_response.py @@ -1,20 +1,32 @@ -"""Types declared by the AdCP ``property/update_property_list_response`` schema. +# generated by datamodel-codegen: +# filename: property/update_property_list_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.property.update_property_list_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field, StrictBool + +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from . import property_list -from adcp.types.generated_poc.property.update_property_list_response import ( - UpdatePropertyListResponse, -) -# Explicit exports -__all__ = ["UpdatePropertyListResponse"] +class UpdatePropertyListResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + list: Annotated[property_list.PropertyList, Field(description='The updated property list')] + replayed: Annotated[ + StrictBool | None, + Field( + description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. `replayed` appears only when the request actually resolved through the idempotency cache. Pure reads may ignore an optional `idempotency_key`; when a seller voluntarily caches keyed reads, those responses use the same replay indicator and full cache contract." + ), + ] = False + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/property/validate_property_delivery_request.py b/src/adcp/types/domains/property/validate_property_delivery_request.py index 33ee7c07e..e4afe9e45 100644 --- a/src/adcp/types/domains/property/validate_property_delivery_request.py +++ b/src/adcp/types/domains/property/validate_property_delivery_request.py @@ -1,20 +1,44 @@ -"""Types declared by the AdCP ``property/validate_property_delivery_request`` schema. +# generated by datamodel-codegen: +# filename: property/validate_property_delivery_request.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.property.validate_property_delivery_request import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import ConfigDict, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import account_ref +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope +from . import delivery_record -from adcp.types.generated_poc.property.validate_property_delivery_request import ( - ValidatePropertyDeliveryRequest, -) -# Explicit exports -__all__ = ["ValidatePropertyDeliveryRequest"] +class ValidatePropertyDeliveryRequest(AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + list_id: Annotated[str, Field(description='ID of the property list to validate against')] + account: Annotated[ + account_ref.AccountReference | None, + Field( + description='Account that owns the list. Required when the authenticated agent has access to multiple accounts; optional otherwise.' + ), + ] = None + records: Annotated[ + list[delivery_record.DeliveryRecord], + Field( + description='Delivery records to validate. Each record represents impressions delivered to a property identifier.', + max_length=10000, + min_length=1, + ), + ] + include_compliant: Annotated[ + StrictBool | None, + Field( + description='Include compliant records in results (default: only return non_compliant, unmodeled, and unidentified)' + ), + ] = False + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/property/validate_property_delivery_response.py b/src/adcp/types/domains/property/validate_property_delivery_response.py index 7c687369e..ccc3fd0f0 100644 --- a/src/adcp/types/domains/property/validate_property_delivery_response.py +++ b/src/adcp/types/domains/property/validate_property_delivery_response.py @@ -1,23 +1,149 @@ -"""Types declared by the AdCP ``property/validate_property_delivery_response`` schema. +# generated by datamodel-codegen: +# filename: property/validate_property_delivery_response.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.property.validate_property_delivery_response import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from . import validation_result + + +class Summary(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + total_records: Annotated[SchemaInt, Field(description='Total number of records validated')] + total_impressions: Annotated[ + SchemaInt, Field(description='Total impressions across all records') + ] + compliant_records: Annotated[ + SchemaInt, Field(description='Number of records with compliant status') + ] + compliant_impressions: Annotated[ + SchemaInt, Field(description='Impressions from compliant records') + ] + non_compliant_records: Annotated[ + SchemaInt, Field(description='Number of records with non_compliant status') + ] + non_compliant_impressions: Annotated[ + SchemaInt, Field(description='Impressions from non_compliant records') + ] + not_covered_records: Annotated[ + SchemaInt, + Field( + description='Number of records where identifier was recognized but no data available' + ), + ] + not_covered_impressions: Annotated[ + SchemaInt, Field(description='Impressions from not_covered records') + ] + unidentified_records: Annotated[ + SchemaInt, Field(description='Number of records where identifier type was not resolvable') + ] + unidentified_impressions: Annotated[ + SchemaInt, Field(description='Impressions from unidentified records') + ] + + +class Aggregate(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + score: Annotated[ + StrictFloat | None, + Field(description='Numeric score (0-100 scale typical, but agent-defined)'), + ] = None + grade: Annotated[ + str | None, + Field(description="Letter grade or category (e.g., 'A+', 'B-', 'Gold', 'Compliant')"), + ] = None + label: Annotated[ + str | None, + Field(description="Human-readable summary (e.g., '85% compliant', 'High quality')"), + ] = None + methodology_url: Annotated[ + AnyUrl | None, Field(description='URL explaining how this aggregate was calculated') + ] = None + + +class AuthorizationSummary(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + records_checked: Annotated[ + SchemaInt, Field(description='Number of records with sales_agent_url provided') + ] + impressions_checked: Annotated[ + SchemaInt, Field(description='Total impressions from records with sales_agent_url') + ] + authorized_records: Annotated[ + SchemaInt, Field(description='Number of records where sales agent was authorized') + ] + authorized_impressions: Annotated[ + SchemaInt, Field(description='Impressions from authorized records') + ] + unauthorized_records: Annotated[ + SchemaInt, Field(description='Number of records where sales agent was NOT authorized') + ] + unauthorized_impressions: Annotated[ + SchemaInt, Field(description='Impressions from unauthorized records') + ] + unknown_records: Annotated[ + SchemaInt, + Field( + description='Number of records where authorization could not be determined (adagents.json unavailable)' + ), + ] + unknown_impressions: Annotated[ + SchemaInt, + Field(description='Impressions from records where authorization could not be determined'), + ] -from adcp.types.generated_poc.property.validate_property_delivery_response import ( - Aggregate, - AuthorizationSummary, - Summary, - ValidatePropertyDeliveryResponse, -) -# Explicit exports -__all__ = ["Aggregate", "AuthorizationSummary", "Summary", "ValidatePropertyDeliveryResponse"] +class ValidatePropertyDeliveryResponse(AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + compliant: Annotated[ + StrictBool | None, + Field( + description='Overall compliance flag for the submitted delivery — true when every record is compliant, false when any record is non_compliant. Derived from summary.non_compliant_records === 0 but surfaced at the root as a convenience signal for buyers. Agents MAY omit this field when the response represents a partial validation (e.g., when include_compliant is false and results only contains non_compliant records); consumers SHOULD fall back to summary counts if compliant is absent.' + ), + ] = None + list_id: Annotated[str, Field(description='ID of the property list validated against')] + summary: Annotated[Summary, Field(description='Aggregate validation statistics')] + aggregate: Annotated[ + Aggregate | None, + Field( + description='Optional aggregate measurements computed by the governance agent. Format and meaning are agent-specific.' + ), + ] = None + authorization_summary: Annotated[ + AuthorizationSummary | None, + Field( + description='Aggregate authorization statistics. Only present if any records included sales_agent_url.' + ), + ] = None + results: Annotated[ + list[validation_result.ValidationResult], + Field( + description='Per-record validation results. By default only includes non_compliant and unknown records. Set include_compliant=true to include all records.' + ), + ] + validated_at: Annotated[ + AwareDatetime, Field(description='Timestamp when validation was performed') + ] + list_resolved_at: Annotated[ + AwareDatetime | None, + Field(description='Timestamp of the property list resolution used for validation'), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/property/validation_result.py b/src/adcp/types/domains/property/validation_result.py index c765c1ae5..416fdff27 100644 --- a/src/adcp/types/domains/property/validation_result.py +++ b/src/adcp/types/domains/property/validation_result.py @@ -1,23 +1,108 @@ -"""Types declared by the AdCP ``property/validation_result`` schema. +# generated by datamodel-codegen: +# filename: property/validation_result.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.property.validation_result import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import ext as ext_1 +from ..core import identifier as identifier_1 +from ..enums import feature_check_status +from . import authorization_result + + +class Status(StrEnum): + compliant = 'compliant' + non_compliant = 'non_compliant' + not_covered = 'not_covered' + unidentified = 'unidentified' + + +class Requirement(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + min_value: Annotated[ + StrictFloat | None, Field(description='Minimum value that was required') + ] = None + max_value: Annotated[ + StrictFloat | None, Field(description='Maximum value that was allowed') + ] = None + allowed_values: Annotated[ + list[Any] | None, Field(description='Values that would have been acceptable') + ] = None + + +class Feature(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + feature_id: Annotated[ + str, + Field( + description="Which feature was evaluated. Data features come from the governance agent's feature catalog (e.g., 'mfa_score', 'carbon_score'). Record-level structural checks use reserved namespaces: 'record:list_membership', 'record:excluded', 'delivery:seller_authorization', 'delivery:click_url_presence'. Reserved prefixes: 'record:', 'delivery:'." + ), + ] + status: feature_check_status.FeatureCheckStatus + policy_id: Annotated[ + str | None, + Field( + description='Optional attribution — when this feature was evaluated for the purpose of a specific policy, policy_id references the authorizing PolicyEntry. Property-list agents populate when the validation was motivated by a specific policy. See /docs/governance/policy-attribution.' + ), + ] = None + explanation: Annotated[ + str | None, Field(description='Directional human-readable explanation of the result.') + ] = None + requirement: Annotated[ + Requirement | None, + Field( + description='The feature requirement that was not met. MAY be present on failed features when the caller authored the requirement (e.g., feature_requirements on a property list). The buyer set these thresholds — echoing them back enables fix-and-retry loops without looking up the list definition.' + ), + ] = None + confidence: Annotated[ + StrictFloat | None, + Field( + description='Optional evaluator confidence in this result (0-1). Distinguishes certain verdicts from ambiguous ones.', + ge=0.0, + le=1.0, + ), + ] = None -from adcp.types.generated_poc.property.validation_result import ( - Feature, - Requirement, - Status, - ValidationResult, -) -# Explicit exports -__all__ = ["Feature", "Requirement", "Status", "ValidationResult"] +class ValidationResult(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + identifier: Annotated[ + identifier_1.Identifier, Field(description='The identifier that was validated') + ] + record_id: Annotated[ + str | None, Field(description='Client-provided ID from the delivery record (if provided)') + ] = None + status: Annotated[ + Status, + Field( + description='Validation status: compliant (in list), non_compliant (not in list), not_covered (identifier recognized but no data available), unidentified (identifier type not resolvable by this governance agent)' + ), + ] + impressions: Annotated[ + SchemaInt, Field(description='Number of impressions from this record', ge=0) + ] + features: Annotated[ + list[Feature] | None, + Field( + description='Per-feature breakdown for this record. SHOULD include all failed and warning features. MAY include passed features. For property validation the buyer authored the requirement, so the `requirement` that was not met MAY be echoed back on failures — this is contract data, not evaluator IP.' + ), + ] = None + authorization: Annotated[ + authorization_result.AuthorizationResult | None, + Field( + description='Authorization validation result (only present if sales_agent_url was provided in the delivery record)' + ), + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/protocol/__init__.py b/src/adcp/types/domains/protocol/__init__.py index 1df5dbae6..4a8bf2913 100644 --- a/src/adcp/types/domains/protocol/__init__.py +++ b/src/adcp/types/domains/protocol/__init__.py @@ -9,18 +9,18 @@ it from its own schema's module, ``adcp.types.domains.protocol.``. Nothing here is renamed. -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-04 01:57:50 UTC +Auto-generated from the generated domain tree. DO NOT EDIT MANUALLY. +Generation date: 2026-10-04 18:45:11 UTC """ # ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.protocol.get_adcp_capabilities_request import ( +from adcp.types.domains.protocol.get_adcp_capabilities_request import ( GetAdcpCapabilitiesRequest, Protocol, ) -from adcp.types.generated_poc.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.protocol.get_adcp_capabilities_response import ( AcceptancePolicyDiscovery, AccessMode, Account, @@ -157,8 +157,8 @@ WholesaleFeedVersioning, WholesaleFeedWebhooks, ) -from adcp.types.generated_poc.protocol.get_principal_request import GetPrincipalRequest -from adcp.types.generated_poc.protocol.get_principal_response import ( +from adcp.types.domains.protocol.get_principal_request import GetPrincipalRequest +from adcp.types.domains.protocol.get_principal_response import ( GetPrincipalResponse, PrincipalCurrentResult, PrincipalReadFailedResult, @@ -168,21 +168,16 @@ Result7, Result9, ) -from adcp.types.generated_poc.protocol.get_task_status_request import GetTaskStatusRequest -from adcp.types.generated_poc.protocol.get_task_status_response import ( +from adcp.types.domains.protocol.get_task_status_request import GetTaskStatusRequest +from adcp.types.domains.protocol.get_task_status_response import ( Details, Error, GetTaskStatusResponse, HistoryItem, Progress, ) -from adcp.types.generated_poc.protocol.list_tasks_request import ( - Field1, - Filters, - ListTasksRequest, - Sort, -) -from adcp.types.generated_poc.protocol.list_tasks_response import ( +from adcp.types.domains.protocol.list_tasks_request import Field1, Filters, ListTasksRequest, Sort +from adcp.types.domains.protocol.list_tasks_response import ( Direction, Domain, DomainBreakdown, @@ -191,17 +186,14 @@ SortApplied, Task, ) -from adcp.types.generated_poc.protocol.sync_agent_notification_configs_request import ( +from adcp.types.domains.protocol.sync_agent_notification_configs_request import ( SyncAgentNotificationConfigsRequest, ) -from adcp.types.generated_poc.protocol.sync_agent_notification_configs_response import ( +from adcp.types.domains.protocol.sync_agent_notification_configs_response import ( SyncAgentNotificationConfigsResponse, ) -from adcp.types.generated_poc.protocol.sync_principal_request import ( - Configuration, - SyncPrincipalRequest, -) -from adcp.types.generated_poc.protocol.sync_principal_response import ( +from adcp.types.domains.protocol.sync_principal_request import Configuration, SyncPrincipalRequest +from adcp.types.domains.protocol.sync_principal_response import ( Action33, PrincipalAppliedResult, PrincipalSyncFailedResult, diff --git a/src/adcp/types/domains/protocol/get_adcp_capabilities_request.py b/src/adcp/types/domains/protocol/get_adcp_capabilities_request.py index 5bcfa32b3..dbc8b70fc 100644 --- a/src/adcp/types/domains/protocol/get_adcp_capabilities_request.py +++ b/src/adcp/types/domains/protocol/get_adcp_capabilities_request.py @@ -1,21 +1,39 @@ -"""Types declared by the AdCP ``protocol/get_adcp_capabilities_request`` schema. +# generated by datamodel-codegen: +# filename: protocol/get_adcp_capabilities_request.json +# timestamp: 2026-10-04T02:05:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.protocol.get_adcp_capabilities_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field + +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope + + +class Protocol(StrEnum): + media_buy = 'media_buy' + signals = 'signals' + governance = 'governance' + sponsored_intelligence = 'sponsored_intelligence' + creative = 'creative' -from adcp.types.generated_poc.protocol.get_adcp_capabilities_request import ( - GetAdcpCapabilitiesRequest, - Protocol, -) -# Explicit exports -__all__ = ["GetAdcpCapabilitiesRequest", "Protocol"] +class GetAdcpCapabilitiesRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + protocols: Annotated[ + list[Protocol] | None, + Field( + description='Specific protocols to query capabilities for. If omitted, returns capabilities for all supported protocols.', + min_length=1, + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/protocol/get_adcp_capabilities_response.py b/src/adcp/types/domains/protocol/get_adcp_capabilities_response.py index c146edb54..1d70e429e 100644 --- a/src/adcp/types/domains/protocol/get_adcp_capabilities_response.py +++ b/src/adcp/types/domains/protocol/get_adcp_capabilities_response.py @@ -1,292 +1,2747 @@ -"""Types declared by the AdCP ``protocol/get_adcp_capabilities_response`` schema. +# generated by datamodel-codegen: +# filename: protocol/get_adcp_capabilities_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.protocol.get_adcp_capabilities_response import +from adcp.types.base import AdcpResponse, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-04 01:57:57 UTC -""" +from datetime import date +from adcp.types._str_enum import StrEnum +from typing import Any, Annotated, Dict, Literal -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarInt, ScalarStr +from pydantic import ( + AnyUrl, + AwareDatetime, + ConfigDict, + Field, + RootModel, + StrictBool, + StrictFloat, +) -from adcp.types.generated_poc.protocol.get_adcp_capabilities_response import ( - AcceptancePolicyDiscovery, - AccessMode, - Account, - Accreditation, - Adcp, - AgeRestriction, - Algorithm, - AttributionWindow, - AudienceEvidence, - AudienceTargeting, - Brand, - BudgetCapping, - BudgetCappingTimezoneBasis, - BuyingMode, - CapabilityChanges, - CapabilityReportingDeliveryMethod, - ChangeFeed, - ChangeFeed1, - ComplianceTesting, - CompromiseNotification, - ContentStandards, - ConversionTracking, - CoversContentDigest, - Creative, - CreativeApprovalMode, - CreativeFeature, - CreativeSpecs, - DataProviderDomain, - DefaultProfileId, - Demographics, - DiscoveryMode, - Endpoint, - EventType, - EventType3, - Execution, - ExtensionsSupportedItem, - Features, - Format, - FrequencyCapping, - GeoMetros, - GeoProximity, - GetAdcpCapabilitiesResponse, - Governance, - GovernanceEnforcement, - Idempotency, - Idempotency1, - Identity, - IdentityUpdates, - IdentityUpdates1, - KeyOrigins, - KeywordTargets, - Language, - LifecycleTool, - Localization, - MajorVersion, - MatchingLatencyHours, - Measurement, - MeasurementGateway, - MediaBuy, - Metric, - Mode, - MraidVersion, - Multiplicity, - NegativeKeywords, - Notifications, - Notifications1, - Notifications2, - Notifications3, - Oauth, - Operation, - Pattern, - PerformanceFeedback, - Portfolio, - Preview, - PrimaryCountry, - Principal, - PropagationSurface, - PropertyFeature, - ProposalRefinement, - ProtocolMethodsRequiredForItem, - ProtocolMethodsSupportedForItem, - ProtocolMethodsWarnForItem, - PublisherDomain, - Range, - RegistrationTask, - RelationshipNotifications, - RenderingOrigin, - ReportingDestinationOffering, - RepresentationResolution, - RequestSigning, - RequiredForItem, - Requirement, - ResourceType, - RightsAttestations, - Route, - RuntimeAttestations, - SignalActivation, - Signals, - SponsoredIntelligence, - SupportedChange, - SupportedDimension, - SupportedForItem, - SupportedFormat, - SupportedIdentifierType, - SupportedOptimizationMetric, - SupportedPresenceMode, - SupportedProtocol, - SupportedRequirementMode, - SupportedScope, - SupportedSection, - SupportedTarget, - SupportedTarget1, - SupportedVersion, - Targeting, - Tasks, - Tasks1, - Tasks2, - Tasks3, - Tasks4, - Tasks5, - Tasks6, - Tasks7, - Tasks8, - Tasks9, - Transport, - Transport1, - TrustedMatch, - Type, - Type6, - VariantDimension, - VastValidation, - VendorMetricOptimization, - WarnForItem, - WebhookSigning, - WholesaleFeedVersioning, - WholesaleFeedWebhooks, +from ..core import account_timezone_capability, attestation_capabilities, audience_activation_method +from ..core import context as context_1 +from ..core import creative_operation_format_declaration, duration, error, experimental_feature_id +from ..core import ext as ext_1 +from ..core import ( + geo_place_support, + geo_place_system, + geo_region_support, + locale_tag, + macro_resolution_capability, + media_buy_features, + media_buy_frequency_cap_capability, + postal_area_support, + reporting_delivery_capabilities, + reporting_verification_profile_set, + vendor_metric_id, +) +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import ( + account_currency_mode, + action_source, + age_verification_method, + billing_party, + channels, + cloud_storage_protocol, + creative_selection_strategy, +) +from ..enums import event_type as event_type_1 +from ..enums import ( + indicator_type, + match_type, + notification_type, + pricing_model, + property_type, + reach_unit, + representation_selection_strategy, + right_type, + right_use, + specialism, + transport_mode, + uid_type, + vast_version, ) +from ..governance import accepted_governance_agents as accepted_governance_agents_1 +from ..sponsored_intelligence import si_capabilities + + +class MajorVersion(ScalarInt): + __slots__ = () + _constraints = {'ge': 1} + + +class SupportedVersion(ScalarStr): + __slots__ = () + _constraints = { + 'pattern': '^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?)?$', + } + + +class Idempotency(AdCPBaseModel): + supported: Annotated[ + Literal[True], + Field( + description='Discriminator. True means the seller deduplicates replays — a repeat of the same idempotency_key within replay_ttl_seconds returns the cached response without re-executing side effects.' + ), + ] + replay_ttl_seconds: Annotated[ + SchemaInt, + Field( + description="How long the seller retains a canonical response or committed-outcome tombstone for an idempotency_key, measured from the successful mutation's durable commit time. The completed key record and canonical request hash MUST remain available until at least committed_at + this interval, independently of the lifecycle of any resource the request created; a UNIQUE idempotency_key column on the resource row alone does not satisfy this contract. Within this window, a replay with the same key + equivalent canonical payload returns the cached response, or COMMITTED_RESOURCE_PURGED when the mutation committed but the affected resource was independently deleted or purged before the canonical success response could be durably recorded; a replay with a different canonical payload returns IDEMPOTENCY_CONFLICT. An unresolved in-flight or reconciliation-required claim is retained regardless of this completed-entry clock. A replay past a completed entry's window returns IDEMPOTENCY_EXPIRED when the seller can still distinguish 'seen and evicted' from 'never seen'. Minimum 3600 (1h); recommended 86400 (24h). Maximum 604800 (7 days) — longer windows force buyers to retain secret keys at rest for extended periods and grow the seller's cache table without bounded benefit.", + ge=3600, + le=604800, + ), + ] + in_flight_max_seconds: Annotated[ + SchemaInt | None, + Field( + description="Maximum active execution lease in seconds before the seller stops or replaces the original handler and transitions the durable idempotency claim to reconciliation-required per L1/security.mdx rule 9. Expiry does not release or evict an unresolved claim and never permits reinvocation while commit state is ambiguous. Buyer SDKs use this value to cap an individual retry wait when they see `IDEMPOTENCY_IN_FLIGHT`, rather than using the much wider `replay_ttl_seconds` ceiling; it is not a deadline after which they may mint a fresh key. Optional in 3.1 (additive declaration); SDKs that don't see the field fall back to rule 9's order-of-magnitude SHOULD heuristic. Required when `supported: true` in 4.0. MUST be no greater than `replay_ttl_seconds`; validators MUST enforce this cross-field constraint at the test layer since JSON Schema cannot express field-relative bounds. A buyer that observes top-level `error.retry_after` exceeding this value MAY treat that as a seller bug for an actively executing attempt.", + ge=1, + le=604800, + ), + ] = None + account_id_is_opaque: Annotated[ + StrictBool | None, + Field( + description="When true, the seller derives `account_id` via an HKDF-based one-way transform of the buyer's natural account key rather than echoing the natural key on the wire. Buyers MUST NOT attempt to invert the opaque id and MUST treat it as a blind handle scoped to this seller. Absent or false, callers should assume `account_id` is the natural key (or a server-assigned but non-opaque id). This flag does not change the wire shape, but it DOES change buyer behavior — buyers MUST NOT cache, log, or treat `account_id` as a natural-key analog when this flag is true. Migration note for sellers already returning an opaque id without this flag: set it to true at the next capabilities refresh so buyers stop inferring natural-key semantics; until set, new-buyer replay/retry logic will misclassify these ids as natural keys." + ), + ] = False + + +class Idempotency1(AdCPBaseModel): + supported: Annotated[ + Literal[False], + Field(description='Discriminator. False means the seller does not deduplicate retries.'), + ] + + +class SupportedSection(StrEnum): + notification_configs = 'notification_configs' + reporting_destinations = 'reporting_destinations' + declarations = 'declarations' + + +class Pattern(StrEnum): + file_transfer = 'file_transfer' + warehouse_materialization = 'warehouse_materialization' + dataset_share = 'dataset_share' + + +class Transport(ScalarStr): + __slots__ = () + _constraints = {'max_length': 64, 'min_length': 1, 'pattern': '^[a-z][a-z0-9_.-]*$'} + + +class Format(StrEnum): + jsonl = 'jsonl' + csv = 'csv' + parquet = 'parquet' + avro = 'avro' + orc = 'orc' + + +class AccessMode(Transport): + pass + + +class RegistrationTask(StrEnum): + sync_agent_notification_configs = 'sync_agent_notification_configs' + sync_principal = 'sync_principal' + + +class Notifications(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + supported: Annotated[ + Literal[True], + Field( + description='Discriminator. True means the seller accepts the declared registration_task for capabilities.changed subscriptions.' + ), + ] + registration_task: Annotated[ + RegistrationTask, + Field( + description='Task consumers call to manage their caller-scoped agent-level subscriber set.' + ), + ] + event_types: Annotated[ + list[Literal['capabilities.changed']], + Field( + description='Agent-level notification types this seller can emit for capability changes. Currently only `capabilities.changed` is defined.', + min_length=1, + ), + ] + coalescence_window_seconds: Annotated[ + SchemaInt | None, + Field( + description='Maximum coalescence window the seller applies before firing a `capabilities.changed` webhook after a burst of related capability edits. The default recommendation is at most 300 seconds; latency-sensitive deactivations SHOULD fire sooner.', + ge=0, + le=86400, + ), + ] = None + + +class Notifications1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + supported: Annotated[ + Literal[False], + Field( + description='Discriminator. False means consumers must rely on TTL refresh, manual refresh, or registry polling for capability changes.' + ), + ] + + +class CapabilityChanges(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + capabilities_version: Annotated[ + str | None, + Field( + description='Opaque revision token for the full capability document. Required when `notifications.supported: true` because `capabilities.changed` webhooks use this as the read-after-notify fence. Sellers SHOULD change this value whenever any material field in `get_adcp_capabilities` changes. Buyers MUST treat it as opaque and compare it only for equality.', + max_length=255, + min_length=1, + ), + ] = None + last_modified: Annotated[ + AwareDatetime | None, + Field( + description='ISO 8601 timestamp when the seller last changed the advertised capability document. This is the scoped replacement for legacy top-level `last_updated` when buyers need cache validation semantics.' + ), + ] = None + cache_ttl_seconds: Annotated[ + SchemaInt | None, + Field( + description='Maximum time a buyer or registry SHOULD reuse this capability response before refreshing when it has no fresher invalidation signal. Sellers that emit `capabilities.changed` webhooks SHOULD still set a bounded TTL so consumers can recover from missed notifications. This is a cache hint, not an authorization lifetime.', + ge=60, + le=604800, + ), + ] = None + notifications: Annotated[ + Notifications | Notifications1 | None, + Field( + description='Whether the seller supports agent-level capability-change webhooks. When supported, interested consumers register endpoint subscribers with the declared registration_task; sync_principal is preferred when the broader connection surface is available, while sync_agent_notification_configs remains the specialized compatibility task. Each capabilities.changed fire is a small invalidation payload, and consumers repair by re-reading get_adcp_capabilities.' + ), + ] = None + + +class Mode(StrEnum): + signed_context = 'signed_context' + online_execution_check = 'online_execution_check' + + +class Tasks1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + task: Literal['create_media_buy'] = 'create_media_buy' + modes: Annotated[list[Mode], Field(min_length=1)] + + +class Tasks2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + task: Literal['update_media_buy'] = 'update_media_buy' + modes: Annotated[list[Mode], Field(min_length=1)] + + +class Tasks3(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + task: Literal['buy_products'] = 'buy_products' + modes: Annotated[list[Mode], Field(min_length=1)] + + +class Tasks4(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + task: Literal['accept_proposal'] = 'accept_proposal' + modes: Annotated[list[Mode], Field(min_length=1)] + + +class Tasks5(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + task: Literal['control_media_buy'] = 'control_media_buy' + modes: Annotated[list[Mode], Field(min_length=1)] + + +class Tasks6(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + task: Literal['build_creative'] = 'build_creative' + modes: Annotated[list[Literal['signed_context']], Field(max_length=1, min_length=1)] + + +class Tasks7(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + task: Literal['activate_signal'] = 'activate_signal' + modes: Annotated[list[Literal['signed_context']], Field(max_length=1, min_length=1)] + + +class Tasks8(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + task: Literal['acquire_rights'] = 'acquire_rights' + modes: Annotated[list[Literal['signed_context']], Field(max_length=1, min_length=1)] + + +class Tasks9(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + task: Literal['update_rights'] = 'update_rights' + modes: Annotated[list[Literal['signed_context']], Field(max_length=1, min_length=1)] + + +class Tasks( + RootModel[Tasks1 | Tasks2 | Tasks3 | Tasks4 | Tasks5 | Tasks6 | Tasks7 | Tasks8 | Tasks9] +): + root: Annotated[ + Tasks1 | Tasks2 | Tasks3 | Tasks4 | Tasks5 | Tasks6 | Tasks7 | Tasks8 | Tasks9, + Field(discriminator='task'), + ] + def __getattr__(self, name: str) -> Any: + """Proxy attribute access to the wrapped type.""" + if name.startswith('_'): + raise AttributeError(name) + return getattr(self.root, name) + + + +class GovernanceEnforcement(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + tasks: Annotated[ + list[Tasks], + Field( + description='Task-scoped enforcement claims. The task field is a semantic uniqueness key: an agent MUST emit at most one entry per task and combine all supported modes in that entry. JSON Schema uniqueItems only rejects structurally identical objects, so producers and capability validators MUST enforce task-key uniqueness separately. Values correspond to request schemas annotated with x-governed-commitment. Online execution checks are currently defined only for media-buy tasks, whose prepared result has the PlannedDelivery contract; other roles can enforce signed intent authorization without inventing media-buy fields.', + min_length=1, + ), + ] + accepted_governance_agents: Annotated[ + accepted_governance_agents_1.AcceptedGovernanceAgents | None, + Field( + description='Seller-wide advisory default for governance agents this enforcing service accepts as binding counterparties. A candidate satisfying any matcher is accepted by this declaration. The per-account sync_governance response is authoritative and may apply stricter account-specific criteria. Omission means accept any, preserving legacy behavior.' + ), + ] = None + + +class SupportedProtocol(StrEnum): + media_buy = 'media_buy' + signals = 'signals' + governance = 'governance' + sponsored_intelligence = 'sponsored_intelligence' + creative = 'creative' + brand = 'brand' + measurement = 'measurement' + + +class Notifications2(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + supported: Annotated[ + Literal[True], + Field( + description='Discriminator. True means the seller accepts account-level `notification_configs[]` registrations for account lifecycle events.' + ), + ] + registration_task: Annotated[ + Literal['sync_accounts'], + Field(description='Task buyers call to manage account-level subscriber sets.'), + ] = 'sync_accounts' + read_task: Annotated[ + Literal['list_accounts'], + Field( + description='Authoritative repair read after receiving an account lifecycle invalidation.' + ), + ] = 'list_accounts' + event_types: Annotated[ + list[Literal['account.status_changed']], + Field( + description='Account lifecycle notification types this seller can emit. Currently only `account.status_changed` is defined.', + min_length=1, + ), + ] + supports_webhook_activity: Annotated[ + StrictBool | None, + Field( + description='Whether `list_accounts` can return recent delivery attempts in `accounts[].webhook_activity[]` when the buyer requests `include_webhook_activity: true`.' + ), + ] = False + + +class Notifications3(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + supported: Annotated[ + Literal[False], + Field( + description='Discriminator. False means buyers must rely on `sync_accounts` results, `push_notification_config` for one-shot provisioning callbacks, and polling `list_accounts` for later status changes.' + ), + ] + + +class ResourceType(ScalarStr): + __slots__ = () + _constraints = {'max_length': 100, 'min_length': 1, 'pattern': '^[a-z][a-z0-9_.-]{0,99}$'} + + +class ChangeFeed(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + supported: Literal[True] + read_task: Literal['list_account_changes'] = 'list_account_changes' + registration_task: Literal['sync_accounts'] = 'sync_accounts' + event_type: Literal['account.change_recorded'] = 'account.change_recorded' + retention_days: Annotated[ + SchemaInt, + Field( + description='Minimum number of days committed change records remain readable.', ge=90 + ), + ] + resource_types: Annotated[ + list[ResourceType], + Field( + description='Resource families for which this seller can produce change records. Account-specific connected-source status and freshness are returned by list_account_changes.', + min_length=1, + ), + ] + + +class ChangeFeed1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + supported: Literal[False] + + +class SupportedChange(StrEnum): + operator_unit_name = 'operator_unit_name' + operator_unit = 'operator_unit' + operator = 'operator' + + +class IdentityUpdates(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + supported: Annotated[ + Literal[True], + Field( + description='Discriminator. True means the seller accepts operator_identity in sync_accounts settings-update mode.' + ), + ] + supported_changes: Annotated[ + list[SupportedChange], + Field( + description='Identity changes the seller accepts. operator_unit_name covers display-name-only reconciliation within the current operator. operator_unit covers adding, removing, or changing the stable unit id within the current operator and also implies name updates. operator covers an inter-entity operator-domain handoff and encompasses the entire replacement operator identity, including any simultaneous unit addition, removal, or change; a seller need not also advertise operator_unit for that cross-operator replacement.', + min_length=1, + ), + ] + + +class IdentityUpdates1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + supported: Annotated[ + Literal[False], + Field( + description='Discriminator. False means buyers must not submit operator_identity settings updates.' + ), + ] + + +class Account(AdCPBaseModel): + require_operator_auth: Annotated[ + StrictBool | None, + Field( + description="Whether the seller requires operator-level credentials. This declares who must authenticate; it does not by itself declare whether OAuth is used, whether list_accounts is exposed, or which sync_accounts modes are supported. When true, operators authenticate independently with the seller and account-scoped calls use seller/storefront-assigned account_id values because the seller or upstream platform owns the canonical account namespace. If a credential may access more than one account, the seller MUST expose list_accounts and buyers MUST resolve an explicit account_id before the first account-scoped request. If a credential is bound to exactly one account, the seller SHOULD expose list_accounts returning that singleton; a seller MAY omit list_accounts only when it provides the same explicit account_id through another declared path or out-of-band onboarding. When false (default, buyer-declared accounts), the seller trusts the agent's identity claims and account-scoped calls use the advertiser natural key: brand + operator + optional operator_unit, fixed currency, optional buyer-selected account timezone, and sandbox. operator_unit.id is owned by the operator and is distinct from the seller's account_id. The seller normally provisions through sync_accounts, but MAY lazily provision on the first account-scoped request when billing and other required settings are unambiguous from capabilities or onboarding defaults. A lazy-provisioning seller MUST keep accepting the natural key and MUST expose list_accounts for recovery; if buyer input is needed before use, the seller MUST expose sync_accounts." + ), + ] = False + authorization_endpoint: Annotated[ + AnyUrl | None, + Field( + description='OAuth authorization endpoint for obtaining operator-level credentials. Present when the seller supports OAuth for operator authentication. The agent directs the operator to this URL to authenticate and obtain a bearer token. If absent and require_operator_auth is true, operators obtain credentials out-of-band (e.g., seller portal, API key).' + ), + ] = None + supported_billing: Annotated[ + list[billing_party.BillingParty], + Field( + description="Billing models this seller supports. operator: seller invoices the operator (agency or brand buying direct). agent: agent consolidates billing. advertiser: seller invoices the advertiser directly, even when a different operator places orders on their behalf. When the buyer calls sync_accounts, it must pass one of these values. A lazy-provisioning seller may omit sync_accounts only when billing can be resolved unambiguously from this capability or the authenticated agent's onboarding defaults.", + min_length=1, + ), + ] + supported_account_currency_modes: Annotated[ + list[account_currency_mode.AccountCurrencyMode] | None, + Field( + description='Required for sellers implementing AdCP 3.2 advertiser-account provisioning, but optional in this shared 3.x response schema so existing 3.0 and 3.1 capability responses remain valid. Declares whether advertiser accounts are bound to one immutable currency (`fixed`), select currency independently per proposal or media buy (`per_media_buy`), or support both models. When only `fixed` is advertised, buyer-declared provisioning entries MUST include `currency`. When only `per_media_buy` is advertised, they MUST omit it. When both are advertised, presence of `currency` selects a fixed-currency account and omission selects per-media-buy currency. Buyers MUST treat absence as an older seller whose currency mode is not discoverable, not as support for either mode.', + min_length=1, + ), + ] = None + timezone: Annotated[ + account_timezone_capability.AccountTimezoneCapability | None, + Field( + description='Required for sellers implementing AdCP 3.2 advertiser-account provisioning, but optional in the shared 3.x response schema for compatibility. Declares whether the account timezone is seller-wide or fixed per account and whether a buyer must select it during sync_accounts provisioning. Account timezone is the default for account-scoped calendar semantics; feature-specific capability fields explicitly declare exceptions.' + ), + ] = None + required_for_products: Annotated[ + StrictBool | None, + Field( + description='Whether an account reference is required for get_products. When true, the buyer must establish an account before browsing products. When false (default), the buyer can browse products without an account — useful for price comparison and discovery before committing to a seller.' + ), + ] = False + account_financials: Annotated[ + StrictBool | None, + Field( + description='Whether this seller exposes the `get_account_financials` task for querying account-level financial status (spend, credit, invoices). Acts as a **pre-call discriminator** — buyers MUST consult this field before issuing `get_account_financials`; when `false` (or absent), sellers MAY reject the call with an `UNSUPPORTED_FEATURE` / `OPERATION_NOT_SUPPORTED` error. Companion pattern to `creative.bills_through_adcp` (issue #2881) — both fields let buyers gate optional capability calls on a single declared boolean rather than probing for support. Only applicable to operator-billed accounts; sellers using buyer-billed flows omit or set to `false`.' + ), + ] = False + notifications: Annotated[ + Notifications2 | Notifications3 | None, + Field( + description='Whether the seller supports durable account-lifecycle webhooks through account-level `notification_configs[]`. This capability is specifically for account status changes such as approval, rejection, payment-required, suspension, recovery, and closure. When supported, buyers register subscribers with `sync_accounts.accounts[].notification_configs[]`; each `account.status_changed` fire is an invalidation payload, and buyers repair by re-reading `list_accounts` for the account_id.' + ), + ] = None + change_feed: Annotated[ + ChangeFeed | ChangeFeed1 | None, + Field( + description='Whether the seller exposes a durable, ordered feed of material changes to authoritative account-scoped state. This is distinct from webhook_activity transport diagnostics and from current-state reads. Sellers claiming support MUST retain changes for at least 90 days after recording and MUST produce records regardless of whether a mutation originated through AdCP, a seller surface, another authorized principal, seller automation, or a connected platform within declared coverage. Experimental in 3.2 (RFC #6810): sellers advertising supported: true MUST list account.change_feed in experimental_features.' + ), + ] = None + identity_updates: Annotated[ + IdentityUpdates | IdentityUpdates1 | None, + Field( + description='Whether the seller accepts buyer-desired operator identity reconciliation through sync_accounts settings-update entries. Sellers declaring support expose the exact identity transitions they implement, MUST return account revisions from sync_accounts and list_accounts, and MUST return identity_change_preview for dry-run identity updates.' + ), + ] = None + sandbox: Annotated[ + StrictBool | None, + Field( + description='Whether this seller supports sandbox accounts for testing. Buyer-declared accounts use sandbox: true in sync_accounts or, for an unambiguous lazy-provisioning seller, in the natural-key account reference. Sellers with account_id namespaces expose sandbox accounts as pre-existing test accounts through list_accounts or supply them out-of-band. Requests using a sandbox account perform no real platform calls or spend.' + ), + ] = False + + +class DefaultProfileId(ScalarStr): + __slots__ = () + _constraints = {'min_length': 1} + + +class AcceptancePolicyDiscovery(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + catalog_url: Annotated[ + AnyUrl, + Field(description='HTTPS document that validates against acceptance-policy-catalog.json.'), + ] + catalog_digest: Annotated[ + str, + Field( + description='SHA-256 digest of the exact catalog representation fetched from catalog_url.', + pattern='^sha256:[a-f0-9]{64}$', + ), + ] + default_profile_ids: Annotated[ + list[DefaultProfileId] | None, + Field( + description='Local or registry-referenced catalog profiles that apply seller-wide unless a product adds further profiles. IDs MUST resolve uniquely across profiles and registry_profiles; all referenced profiles compose restrictively.', + min_length=1, + ), + ] = None + + +class BuyingMode(StrEnum): + brief = 'brief' + wholesale = 'wholesale' + refine = 'refine' + + +class LifecycleTool(StrEnum): + list_products = 'list_products' + request_proposals = 'request_proposals' + refine_proposals = 'refine_proposals' + decline_proposals = 'decline_proposals' + buy_products = 'buy_products' + accept_proposal = 'accept_proposal' + control_media_buy = 'control_media_buy' + + +class SupportedDimension(StrEnum): + total_budget = 'total_budget' + cpm = 'cpm' + impressions = 'impressions' + flight = 'flight' + product_changes = 'product_changes' + alternatives = 'alternatives' + criteria = 'criteria' + + +class ProposalRefinement(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + supported_dimensions: Annotated[ + list[SupportedDimension], + Field( + description='Typed revision dimensions the seller can parse and validate. An empty list authoritatively means ask-only refinement with no typed dimensions. total_budget, cpm, impressions, and flight cover the same-named constraints keys; product_changes covers product_changes; alternatives covers alternatives.count; criteria covers structured discovery criteria. Free-text ask interpretation is competence, not a capability declared here.' + ), + ] + max_alternatives: Annotated[ + SchemaInt | None, + Field( + description='Optional maximum alternatives.count the seller accepts, up to the protocol maximum of 10. Valid only when supported_dimensions includes alternatives. Requests above this ceiling fail at task level with VALIDATION_ERROR identifying refinements[i].alternatives.count; sellers MUST NOT silently clamp the request or return alternatives_unavailable for the declared ceiling violation.', + ge=2, + le=10, + ), + ] = None + + +class PerformanceFeedback(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + reports_application_status: Annotated[ + StrictBool | None, + Field( + description='Whether provide_performance_feedback responses include an honest application_status of accepted, applied, or not_applied. accepted is explicitly not an application claim. Sellers that cannot determine optimizer disposition omit this claim or set it false.' + ), + ] = False + + +class PropagationSurface(StrEnum): + snapshot = 'snapshot' + webhook = 'webhook' + out_of_band = 'out_of_band' + + +class CreativeApprovalMode(StrEnum): + auto_approve = 'auto_approve' + require_human = 'require_human' + + +class EventType(StrEnum): + indicators_changed = 'indicators.changed' + creative_assignment_changed = 'creative.assignment_changed' + + +class RelationshipNotifications(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + supported: Annotated[ + Literal[True], + Field( + description='Discriminator. True means the declared invalidation event types are supported.' + ), + ] + registration_task: Annotated[ + Literal['sync_accounts'], + Field(description='Task buyers call to register account-level subscribers.'), + ] = 'sync_accounts' + event_types: Annotated[ + list[EventType], + Field( + description='Relationship invalidation events supported by this seller. indicators.changed requires supported_indicator_types but is not required merely because polling readback is available. creative.assignment_changed is independently available when the seller can detect assignment or assignment-approval changes; it does not require indicator support or list_creatives.', + max_length=2, + min_length=1, + ), + ] + repair_tasks: Annotated[ + list[Literal['get_media_buys']], + Field( + description='Complete authoritative snapshot reads used after an invalidation. get_media_buys is mandatory and is the only 3.2 repair task for this contract.', + max_length=1, + min_length=1, + ), + ] + projection_tasks: Annotated[ + list[Literal['list_creatives']] | None, + Field( + description='Optional bounded reverse projections of relationship-scoped indicator and approval state. list_creatives is a discovery/read convenience for creative-library sellers, not a complete repair path; declaring it also requires creative in supported_protocols. Buyers fall back to get_media_buys whenever nested assignments are truncated or a relationship must be proven absent.', + max_length=1, + min_length=1, + ), + ] = None + supports_webhook_activity: Annotated[ + StrictBool | None, + Field( + description='Whether the declared repair tasks can return recent delivery attempts for these account-level invalidations when include_webhook_activity is true.' + ), + ] = False + + +class MraidVersion(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[0-9]+\\.[0-9]+$'} + + +class VastValidation(StrEnum): + structural = 'structural' + document = 'document' + wrapper = 'wrapper' + + +class GeoMetros(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + nielsen_dma: StrictBool | None = None + uk_itl1: StrictBool | None = None + uk_itl2: StrictBool | None = None + eurostat_nuts2: StrictBool | None = None + + +class AgeRestriction(AdCPBaseModel): + supported: Annotated[ + StrictBool | None, Field(description='Whether seller supports age restrictions') + ] = None + verification_methods: Annotated[ + list[age_verification_method.AgeVerificationMethod] | None, + Field(description='Age verification methods this seller supports'), + ] = None + + +class Demographics(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + supported: Annotated[ + StrictBool, + Field( + description='Whether at least one seller product supports canonical demographic targeting.' + ), + ] + + +class SupportedRequirementMode(StrEnum): + required = 'required' + preferred = 'preferred' + + +class SupportedPresenceMode(StrEnum): + required = 'required' + when_available = 'when_available' + + +class AudienceEvidence(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + supported_requirement_modes: Annotated[ + list[SupportedRequirementMode], + Field( + description='Buyer policy modes the seller evaluates. A seller MUST NOT silently ignore a mode it does not list.', + min_length=1, + ), + ] + supported_presence_modes: Annotated[ + list[SupportedPresenceMode], + Field(description='Evidence-presence semantics the seller supports.', min_length=1), + ] + supports_attestation_evaluation: Annotated[ + StrictBool, + Field( + description='Whether the seller evaluates audience-evidence attestation_refs under its core adcp.attestations policy and returns the exact reference and AttestationEvaluation in package readback.' + ), + ] + + +class Requirement(StrEnum): + optional = 'optional' + required = 'required' + + +class RightsAttestations(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + requirement: Annotated[ + Requirement, + Field( + description='required means every applicable rights constraint needs at least one current verified attestation evaluation before the creative is eligible to serve. optional permits independent legacy contractual policy, but unattested constraints remain machine-unverified and verification_url is never a downgrade path.' + ), + ] + + +class SupportedIdentifierType(StrEnum): + hashed_email = 'hashed_email' + hashed_phone = 'hashed_phone' + + +class MatchingLatencyHours(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + min: Annotated[SchemaInt | None, Field(ge=0)] = None + max: Annotated[SchemaInt | None, Field(ge=0)] = None + + +class SupportedOptimizationMetric(StrEnum): + clicks = 'clicks' + views = 'views' + completed_views = 'completed_views' + viewed_seconds = 'viewed_seconds' + viewable_rate = 'viewable_rate' + attention_seconds = 'attention_seconds' + attention_score = 'attention_score' + engagements = 'engagements' + follows = 'follows' + saves = 'saves' + profile_visits = 'profile_visits' + reach = 'reach' + + +class SupportedTarget(StrEnum): + cost_per = 'cost_per' + threshold_rate = 'threshold_rate' + + +class VendorMetricOptimization(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + supported_targets: Annotated[ + list[SupportedTarget] | None, + Field( + description='Target kinds this seller can support for vendor_metric optimization goals on at least one product. Values match optimization_goals[].target.kind for kind: vendor_metric. A target-less vendor_metric goal maximizes the metric within budget and does not require a target-kind declaration.', + min_length=1, + ), + ] = None + + +class SupportedTarget1(StrEnum): + cost_per = 'cost_per' + per_ad_spend = 'per_ad_spend' + maximize_value = 'maximize_value' + + +class AttributionWindow(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + event_type: Annotated[ + event_type_1.EventType | None, + Field(description='Event type this window applies to, or omit for default window'), + ] = None + post_click: Annotated[ + list[duration.Duration], + Field( + description='Available post-click attribution windows (e.g. [{"interval": 7, "unit": "days"}])', + min_length=1, + ), + ] + post_view: Annotated[ + list[duration.Duration] | None, + Field( + description='Available post-view attribution windows (e.g. [{"interval": 1, "unit": "days"}])', + min_length=1, + ), + ] = None + + +class SupportedScope(StrEnum): + media_buy = 'media_buy' + package = 'package' + + +class ContentStandards(AdCPBaseModel): + supports_local_evaluation: Annotated[ + StrictBool | None, + Field( + description="Whether the seller runs a local evaluation model. When false, all artifacts will have local_verdict: 'unevaluated' and the failures_only filter on get_media_buy_artifacts is not useful." + ), + ] = None + supported_channels: Annotated[ + list[channels.MediaChannel] | None, + Field( + description='Channels for which the seller can provide content artifacts. Helps buyers understand which parts of a mixed-channel buy will have content standards coverage.', + min_length=1, + ), + ] = None + supports_webhook_delivery: Annotated[ + StrictBool | None, + Field( + description='Whether the seller supports push-based artifact delivery via artifact_webhook configured at buy creation time.' + ), + ] = None + + +class PublisherDomain(ScalarStr): + __slots__ = () + _constraints = { + 'pattern': '^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', + } + + +class PrimaryCountry(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{2}$'} + + +class Portfolio(AdCPBaseModel): + publisher_domains: Annotated[ + list[PublisherDomain], + Field( + description="Publisher domains this seller is authorized to represent. Buyers should fetch each publisher's adagents.json for property definitions.", + min_length=1, + ), + ] + primary_channels: Annotated[ + list[channels.MediaChannel] | None, + Field( + description="Complete list of AdCP media channels for which this sales agent accepts and can meaningfully answer product-discovery briefs. When present, this is an exhaustive brief-routing allowlist: buyers MAY skip the agent when a brief's requested channels do not intersect it. Omission means channel scope is unknown and MUST NOT be interpreted as support for every channel. This is a routing pre-filter, not a promise of current product availability." + ), + ] = None + primary_countries: Annotated[ + list[PrimaryCountry] | None, + Field( + description="Complete list of ISO 3166-1 alpha-2 countries for which this sales agent accepts and can meaningfully answer product-discovery briefs. When present, this is an exhaustive brief-routing allowlist: buyers MAY skip the agent when a brief's requested countries do not intersect it. Omission means country scope is unknown and MUST NOT be interpreted as global coverage. This is a routing pre-filter, not a promise of current product availability and not an executable geo-targeting declaration; media_buy.execution.targeting geo capabilities and each product's overlay_support remain authoritative for targeting execution." + ), + ] = None + description: Annotated[ + str | None, + Field( + description='Markdown-formatted description of the inventory portfolio', max_length=5000 + ), + ] = None + advertising_policies: Annotated[ + str | None, + Field( + description='Advertising content policies, restrictions, and guidelines', + max_length=10000, + ), + ] = None + + +class DataProviderDomain(PublisherDomain): + pass + + +class DiscoveryMode(StrEnum): + brief = 'brief' + wholesale = 'wholesale' + + +class Features(AdCPBaseModel): + __pydantic_extra__: Dict[str, StrictBool] + model_config = ConfigDict( + extra='allow', + ) + catalog_signals: Annotated[ + StrictBool | None, + Field( + deprecated=True, + description='DEPRECATED. Legacy wire flag for structured signal_ref references to provider-published signal definitions. New agents SHOULD omit this flag; callers MUST NOT require it before using signal_ref with the Signals protocol.', + ), + ] = None + + + + +class Signals(AdCPBaseModel): + anonymous_discovery: Annotated[ + StrictBool | None, + Field( + description='Whether this agent accepts get_signals discovery without caller credentials in brief or wholesale mode. true means an anonymous discovery request can produce a successful response, but the response may be a public subset and may differ from results for an authenticated principal or selected account. false means get_signals requires an authenticated principal. Absence means unspecified legacy behavior, so callers probe and handle AUTH_MISSING. A valid authenticated request is never rejected merely because credentials were supplied.' + ), + ] = None + data_provider_domains: Annotated[ + list[DataProviderDomain] | None, + Field( + description="Data provider domains this signals agent is authorized to resell. Buyers should fetch each data provider's adagents.json for published signal definitions and to verify authorization.", + min_length=1, + ), + ] = None + discovery_modes: Annotated[ + list[DiscoveryMode] | None, + Field( + description="Discovery modes this signals agent supports on get_signals. 'brief' (default — every signals agent supports this): semantic discovery driven by signal_spec or signal_refs, with deprecated signal_ids accepted for older clients. 'wholesale': raw wholesale signals feed enumeration — caller omits signal_spec, signal_refs, and signal_ids and the agent returns its full priced signals feed, paginated, scoped by filters/account/destinations/countries. Agents that do not declare 'wholesale' MAY return INVALID_REQUEST for wholesale calls. Absent declaration is treated as ['brief'].", + min_length=1, + ), + ] = [DiscoveryMode.brief] + features: Annotated[ + Features | None, Field(description='Optional signals features supported') + ] = None + + +class SignalActivation(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + requirement: Annotated[ + Requirement, + Field( + description='Whether an activate_signal activation check may omit runtime_attestations. When required, missing evidence fails an activation check. Deactivation and privacy-removal checks never require signal-quality evidence and remain available without it.' + ), + ] + claim_types: Annotated[ + list[AnyUrl], + Field( + description='Signal-quality claim identifiers accepted for this governed action. Values are open absolute URIs and MUST be a subset of adcp.attestations.accepted_claim_types.', + min_length=1, + ), + ] + + +class RuntimeAttestations(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + signal_activation: SignalActivation + + +class Type(StrEnum): + binary = 'binary' + quantitative = 'quantitative' + categorical = 'categorical' + + +class Range(AdCPBaseModel): + min: Annotated[StrictFloat, Field(description='Minimum value')] + max: Annotated[StrictFloat, Field(description='Maximum value')] + + +class PropertyFeature(AdCPBaseModel): + feature_id: Annotated[ + str, + Field( + description="Unique identifier for this feature (e.g., 'consent_quality', 'coppa_certified', 'carbon_score')" + ), + ] + type: Annotated[ + Type, + Field( + description="Data type: 'binary' for yes/no, 'quantitative' for numeric scores, 'categorical' for enum values" + ), + ] + range: Annotated[ + Range | None, Field(description='For quantitative features, the valid range') + ] = None + categories: Annotated[ + list[str] | None, Field(description='For categorical features, the valid values') + ] = None + description: Annotated[ + str | None, Field(description='Human-readable description of what this feature measures') + ] = None + methodology_url: Annotated[ + AnyUrl | None, + Field( + description='URL to documentation explaining how this feature is calculated or measured. Helps buyers understand and compare methodologies across vendors.' + ), + ] = None + + +class CreativeFeature(AdCPBaseModel): + feature_id: Annotated[ + str, + Field( + description="Unique identifier for this feature (e.g., 'auto_redirect', 'brand_consistency', 'iab_casinos_gambling')" + ), + ] + type: Annotated[ + Type, + Field( + description="Data type: 'binary' for yes/no, 'quantitative' for numeric scores, 'categorical' for enum values" + ), + ] + range: Annotated[ + Range | None, Field(description='For quantitative features, the valid range') + ] = None + categories: Annotated[ + list[str] | None, Field(description='For categorical features, the valid values') + ] = None + description: Annotated[ + str | None, Field(description='Human-readable description of what this feature measures') + ] = None + methodology_url: Annotated[ + AnyUrl | None, + Field( + description='URL to documentation explaining how this feature is calculated or measured.' + ), + ] = None + + +class Governance(AdCPBaseModel): + runtime_attestations: Annotated[ + RuntimeAttestations | None, + Field( + description='Signal-activation policy for the portable attestation support declared in adcp.attestations. Presence means check_governance accepts runtime_attestations[] for purchase_type signal_activation. The claim_types list MUST be a subset of adcp.attestations.accepted_claim_types; issuer, resolver, verifier, proof-format, and delivery allowlists remain authoritative in the shared adcp.attestations block rather than being duplicated here.' + ), + ] = None + aggregation_window_days: Annotated[ + SchemaInt | None, + Field( + description='Trailing window (in days) over which this governance agent aggregates committed spend when evaluating dollar-valued thresholds (reallocation_threshold, human_review triggers, registry-policy floors). Required for fragmentation defense: without aggregation, a buyer can split a single large spend into many sub-threshold commits across plans / task surfaces / time and bypass every dollar-gated escalation. Aggregation is keyed on (buyer_agent, seller_agent, account_id) and spans all spend-commit task types. Upper bound 365 represents a one-year trailing window (fiscal-year alignment with grace); governance agents needing longer scopes negotiate via operator sign-off, not this capability. No schema default: absence of this field indicates the governance agent has not committed to any aggregation window and buyers MUST assume per-commit evaluation only (the fragmentation attack surface is open). A declared value of 30 is a common starting point but is not implied by omission. Buyers depending on a specific window for compliance MUST check this capability before relying on aggregation semantics — an agent declaring 7 days does not defend against fragmentation spread across a 30-day quarter-end push.', + ge=1, + le=365, + ), + ] = None + property_features: Annotated[ + list[PropertyFeature] | None, + Field( + description='Property features this governance agent can evaluate. Each feature describes a score, rating, or certification the agent can provide for properties.' + ), + ] = None + creative_features: Annotated[ + list[CreativeFeature] | None, + Field( + description='Creative features this governance agent can evaluate. Each feature describes a score, rating, or assessment the agent can provide for creatives (e.g., security scanning, creative quality, content categorization).' + ), + ] = None + + +class Type6(StrEnum): + mcp = 'mcp' + a2a = 'a2a' + + +class Transport1(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + type: Annotated[Type6, Field(description='Protocol transport type')] + url: Annotated[AnyUrl, Field(description='Agent endpoint URL for this transport')] + + +class Endpoint(AdCPBaseModel): + transports: Annotated[ + list[Transport1], + Field( + description='Available protocol transports. Hosts select based on their capabilities.', + min_length=1, + ), + ] + preferred: Annotated[ + Type6 | None, Field(description='Preferred transport when host supports multiple') + ] = None + + +class VariantDimension(StrEnum): + voice = 'voice' + theme = 'theme' + best_of_n = 'best_of_n' + transformer_config = 'transformer_config' + custom = 'custom' + + +class Multiplicity(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + supports_catalog_fanout: Annotated[ + StrictBool | None, + Field( + description='When true, build_creative accepts max_creatives (one distinct creative per catalog item).' + ), + ] = False + max_creatives_limit: Annotated[ + SchemaInt | None, + Field(description='Ceiling on max_creatives. Omitted means no advertised ceiling.', ge=1), + ] = None + supports_signal_fanout: Annotated[ + StrictBool | None, + Field( + description='Experimental (x-status: experimental) — agents setting this true MUST also list `creative.signal_fanout` in `experimental_features`; the surface MAY change between 3.x releases with notice (see docs/reference/experimental-status). When true, build_creative accepts signal_conditions[] (one distinct creative group per signal condition, keep-all). Mirrors supports_catalog_fanout.' + ), + ] = False + max_signal_conditions_limit: Annotated[ + SchemaInt | None, + Field( + description='Ceiling on len(signal_conditions). Over-limit requests are CLAMPED (not rejected), like max_creatives_limit. Omitted means no advertised ceiling.', + ge=1, + ), + ] = None + supports_variants: Annotated[ + StrictBool | None, + Field( + description='When true, build_creative accepts max_variants > 1 / variant_axis (alternatives per creative).' + ), + ] = False + max_variants_limit: Annotated[ + SchemaInt | None, + Field(description='Ceiling on max_variants. Omitted means no advertised ceiling.', ge=1), + ] = None + variant_dimensions: Annotated[ + list[VariantDimension] | None, + Field(description='Which variant_axis.dimension values this agent supports.'), + ] = None + selection_strategies: Annotated[ + list[creative_selection_strategy.CreativeSelectionStrategy] | None, + Field( + description='Which selection_strategy values this agent supports when sampling max_creatives < items_total. Sibling to variant_dimensions. Part of the experimental signal-fanout surface (feature id `creative.signal_fanout`).' + ), + ] = None + + +class Operation(StrEnum): + build = 'build' + validate = 'validate' + preview = 'preview' + + +class RenderingOrigin(StrEnum): + platform_native = 'platform_native' + agent_approximation = 'agent_approximation' + + +class Route(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + capability_id: Annotated[ + str, + Field( + description='Agent-local creative.supported_formats[].capability_id accepted by preview_creative.', + pattern='^[a-zA-Z0-9_-]+$', + ), + ] + rendering_origin: Annotated[ + RenderingOrigin, + Field( + description="Informational implementation origin. platform_native means the route uses the serving platform's preview machinery; agent_approximation means the agent renders an approximation. Neither value grants authority without a publisher preview_provider delegation." + ), + ] + + +class Preview(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + routes: Annotated[ + list[Route], + Field( + description='Agent-local preview routes and their implementation origin. Authority is resolved from publisher placement delegation, never from this self-description.', + min_length=1, + ), + ] + + +class Localization(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + max_target_variants: Annotated[ + SchemaInt | None, + Field( + description='Maximum target locale variants accepted on one creative. Zero advertises source-only monolingual locale topology. Omit to inherit the protocol ceiling of 50.', + ge=0, + le=50, + ), + ] = None + locale_matching: Annotated[ + Literal['rfc4647_lookup'], + Field(description='The required deterministic locale-matching algorithm.'), + ] = 'rfc4647_lookup' + + +class Oauth(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + supported: Annotated[ + StrictBool, + Field( + description='Whether this agent accepts OAuth access tokens and publishes the discovery metadata needed to obtain and validate them. A true value is an explicit conformance claim: the RFC 9728 protected-resource document and every RFC 8414 authorization-server document it references MUST be reachable, internally consistent, and safe for clients to follow. False or absent means the OAuth metadata storyboard is not applicable; it does not weaken the universal requirement to implement at least one authentication mechanism.' + ), + ] + + +class CoversContentDigest(StrEnum): + required = 'required' + forbidden = 'forbidden' + either = 'either' + + +class RequiredForItem(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[a-z][a-z0-9_]*$'} + + +class WarnForItem(RequiredForItem): + pass + + +class SupportedForItem(RequiredForItem): + pass + + +class ProtocolMethodsSupportedForItem(ScalarStr): + __slots__ = () + _constraints = { + 'max_length': 256, + 'pattern': '^(?:[a-z][A-Za-z0-9_]*(?:/[A-Za-z][A-Za-z0-9_]*)+|[A-Z][A-Za-z0-9_]*)$', + } + + +class ProtocolMethodsWarnForItem(ProtocolMethodsSupportedForItem): + pass + + +class ProtocolMethodsRequiredForItem(ProtocolMethodsSupportedForItem): + pass + + +class RequestSigning(AdCPBaseModel): + supported: Annotated[ + StrictBool, + Field( + description='Whether this agent verifies RFC 9421 signatures on incoming requests. When true, signatures present on requests are validated per the AdCP request-signing profile. When false or absent, signatures are ignored (requests are bearer-authenticated only).' + ), + ] + covers_content_digest: Annotated[ + CoversContentDigest | None, + Field( + description="Policy for content-digest coverage in request signatures. In AdCP 3.2 and later, an agent with request_signing.supported=true MUST explicitly emit 'required': every accepted signature on a request with a body covers content-digest, and a body-unbound signature is rejected with request_signature_components_incomplete. Omission retains the legacy effective default of 'either' only for a 3.0/3.1 response; 3.2 responses require this field explicitly. 'either' and 'forbidden' are deprecated legacy 3.0/3.1 postures retained only for version negotiation with pre-3.2 peers; they MUST NOT be advertised as a 3.2 signing posture and are removed in 4.0. A shared endpoint MUST select the verifier policy from trusted endpoint configuration and negotiated capabilities before dispatch, never from an unbound request-body version field." + ), + ] = None + required_for: Annotated[ + list[Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')]] | None, + Field( + description="AdCP protocol operation names (e.g., 'create_media_buy') for which this agent rejects an unsigned request with request_signature_required unless an independently valid configured fallback authenticator succeeds. Not MCP tool names, A2A skill names, or any transport-specific rename — verifiers MUST NOT accept operation names that are not defined by the AdCP protocol spec. JSON-RPC protocol method names like `tasks/cancel` belong in `protocol_methods_required_for`, not here. Empty in 3.0 by default; sellers populate selectively during per-counterparty pilots. In 4.0 this list MUST include all spend-committing operations the agent supports (create_media_buy, acquire_*, etc.). Every operation listed MUST also appear in `supported_for`; see `x-adcp-validation`.", + validate_default=True, + ), + ] = [] + warn_for: Annotated[ + list[Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')]] | None, + Field( + description='AdCP protocol operation names for shadow-mode verification. The verifier records missing signatures and well-formed signatures that fail verification or body binding, but MUST NOT establish verified-signer identity from a failed signature; processing continues only when an independent bearer, API-key, or mTLS authenticator succeeds. A partial or malformed Signature / Signature-Input pair always hard-rejects. Used as a bridge between supported_for and required_for. Precedence: required_for > warn_for > supported_for. An operation MUST NOT appear in both warn_for and required_for; see x-adcp-validation.', + validate_default=True, + ), + ] = [] + supported_for: Annotated[ + list[Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')]] | None, + Field( + description='AdCP protocol operation names for which this agent verifies signatures when present but does not require them. Under the 3.2 profile, a presented signature on a body-bearing request without content-digest coverage rejects even though an unsigned request may use configured fallback authentication. Typically a superset of required_for and warn_for.' + ), + ] = None + protocol_methods_supported_for: Annotated[ + list[ProtocolMethodsSupportedForItem] | None, + Field( + description="JSON-RPC protocol method names for which this agent verifies signatures when present. Values MUST use exact, case-sensitive equality after JSON decoding: slash-path names such as 'tasks/cancel' and 'tasks/pushNotificationConfig/set' for A2A 0.3, or PascalCase names such as 'CancelTask' and 'CreateTaskPushNotificationConfig' for A2A 1.0. A dual-stack agent lists each supported wire name independently; implementations MUST NOT translate or normalize between protocol versions. The reserved MCP envelope method 'tools/call' is forbidden because its AdCP operation identity is params.name and belongs in supported_for. Under the 3.2 profile, a presented signature on a body-bearing request without content-digest coverage rejects. Disjoint from supported_for, which carries lower_snake_case AdCP operation names only.", + validate_default=True, + ), + ] = [] + protocol_methods_warn_for: Annotated[ + list[ProtocolMethodsWarnForItem] | None, + Field( + description='Exact JSON-RPC protocol method names for shadow-mode verification, mirroring warn_for in the AdCP-operation namespace. Wire-name grammar and exact, case-sensitive matching semantics are identical to protocol_methods_supported_for. Missing signatures and well-formed signatures that fail verification or body binding are recorded but MUST NOT establish verified-signer identity; processing continues only when an independent authenticator succeeds. A partial or malformed Signature / Signature-Input pair always hard-rejects. An item MUST NOT appear in both protocol_methods_warn_for and protocol_methods_required_for; see x-adcp-validation.', + validate_default=True, + ), + ] = [] + protocol_methods_required_for: Annotated[ + list[ProtocolMethodsRequiredForItem] | None, + Field( + description='Exact JSON-RPC protocol method names for which this agent rejects an unsigned request with request_signature_required unless an independently valid configured fallback authenticator succeeds. Wire-name grammar and exact, case-sensitive matching semantics are identical to protocol_methods_supported_for. Separate namespace from required_for: this bucket binds against the JSON-RPC method field, not tools/call params.name. Every listed method MUST also appear in protocol_methods_supported_for; see x-adcp-validation.', + validate_default=True, + ), + ] = [] + + +class Algorithm(StrEnum): + ed25519 = 'ed25519' + ecdsa_p256_sha256 = 'ecdsa-p256-sha256' + + +class WebhookSigning(AdCPBaseModel): + supported: Annotated[ + StrictBool, + Field( + description='Whether this agent signs outbound webhooks with the AdCP RFC 9421 webhook profile. When false or absent, webhooks are delivered with legacy Bearer or HMAC-SHA256 auth only and receivers MUST NOT expect a Signature header. When the seller advertises mutating-webhook emission (i.e., `media_buy.reporting_delivery_methods` includes `webhook`, `media_buy.content_standards.supports_webhook_delivery` is true, `media_buy.relationship_notifications.supported` is true, `wholesale_feed_webhooks.supported` is true, `adcp.capability_changes.notifications.supported` is true, or `account.notifications.supported` is true), this MUST be `true` — emitting state-changing webhooks unsigned is a downgrade vector that lets an on-path attacker forge delivery callbacks. See `x-adcp-validation`.' + ), + ] + profile: Annotated[ + Literal['adcp/webhook-signing/v1'] | None, + Field( + description='Identifier of the webhook-signing profile version the agent emits. Value MUST match the `tag=` parameter emitted in the RFC 9421 `Signature-Input` header (see docs/building/implementation/webhooks.mdx) so receivers can statically validate the declared profile against the on-wire tag. Closed enum; future profile revisions will extend this enum in a follow-up schema bump.' + ), + ] = None + algorithms: Annotated[ + list[Algorithm] | None, + Field( + description="Signature algorithms this agent uses on outbound webhooks. 3.0 profile permits 'ed25519' and 'ecdsa-p256-sha256' only; other values are reserved for future profile versions and MUST NOT be emitted under adcp/webhook-signing/v1.", + min_length=1, + ), + ] = None + legacy_hmac_fallback: Annotated[ + StrictBool | None, + Field( + deprecated=True, + description='Whether this agent will fall back to HMAC-SHA256 on the legacy push_notification_config.authentication, accounts[].notification_configs[].authentication, sync_principal.configuration.notification_configs[].authentication, or sync_agent_notification_configs.notification_configs[].authentication paths for receivers that have not adopted RFC 9421. Deprecated; removed in AdCP 4.0.', + ), + ] = False + delivery_retry_horizon_seconds: Annotated[ + SchemaInt | None, + Field( + description='Maximum elapsed time from the first delivery attempt during which this agent may retry the same webhook delivery. The publisher retains the immutable delivery-key-to-RFC-8785-JCS-payload binding and sufficient delivery state for at least this interval, and MUST NOT retry that key afterward. Receivers retain the matching payload binding and terminal publication proof for at least max(86400, this value) seconds in AdCP 3.x. Retries do not extend the horizon. A webhook-emitting AdCP 3.2 agent MUST populate this additive field; it remains schema-optional so existing 3.x capability documents stay valid. Minimum 86400 (24h), maximum 604800 (7d).', + ge=86400, + le=604800, + ), + ] = None + + +class KeyOrigins(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + governance_signing: Annotated[ + AnyUrl | None, + Field(description='Origin (scheme + host) serving the governance-signing JWKS.'), + ] = None + request_signing: Annotated[ + AnyUrl | None, Field(description='Origin (scheme + host) serving the request-signing JWKS.') + ] = None + webhook_signing: Annotated[ + AnyUrl | None, + Field( + description='Origin (scheme + host) serving the JWKS used for webhook delivery. Webhooks are signed with `adcp_use: "request-signing"` keys; the deprecated `adcp_use: "webhook-signing"` value remains accepted during the backward-compatibility window.' + ), + ] = None + tmp_signing: Annotated[ + AnyUrl | None, + Field( + description='Origin (scheme + host) serving the TMP-signing JWKS, when this operator participates in TMP.' + ), + ] = None + + +class CompromiseNotification(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + emits: Annotated[ + StrictBool | None, + Field(description='Whether this agent emits `identity.compromise_notification` events.'), + ] = False + accepts: Annotated[ + StrictBool | None, + Field( + description='Whether this agent subscribes to `identity.compromise_notification` events from counterparties it verifies signatures from.' + ), + ] = False + + +class Identity(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + brand_json_url: Annotated[ + AnyUrl | None, + Field( + description="HTTPS URL of the operator's brand.json (typically `https://{operator-domain}/.well-known/brand.json`). Trust-root pointer for this agent's signing keys. See [security.mdx §Discovering an agent's signing keys via `brand_json_url`](https://adcontextprotocol.org/docs/building/by-layer/L1/security#discovering-an-agents-signing-keys-via-brand_json_url) for the verifier algorithm and `x-adcp-validation` for structured constraints. Distinct from `sponsored_intelligence.brand_url`, which is a rendering pointer for SI agent visuals — verifiers MUST use this field for key discovery and MUST NOT fall back to `sponsored_intelligence.brand_url` as a trust-root pointer." + ), + ] = None + per_principal_key_isolation: Annotated[ + StrictBool | None, + Field( + description="When true, this multi-principal operator scopes signing keys per-principal so a single principal's key compromise does not silently re-scope across principals served by the same operator. `kid` values remain opaque to verifiers per RFC 7517; any operator-side naming convention (e.g., `{operator}:{principal}:{key_version}`) is internal bookkeeping and MUST NOT be parsed by verifiers. See docs/building/understanding/security-model.mdx." + ), + ] = False + key_origins: Annotated[ + KeyOrigins | None, + Field( + description='Map of signing-key surface/purpose → publishing origin, so counterparties can verify origin separation (e.g., governance keys served from a separate origin than transport/webhook keys) at onboarding. Absent means the operator has not declared a separation scheme; receivers SHOULD assume shared-origin. Every entry listed MUST have a corresponding signing posture declared elsewhere — `request_signing` requires non-empty `request_signing.supported_for`/`required_for`/`protocol_methods_supported_for`/`protocol_methods_required_for`; `webhook_signing` requires `webhook_signing.supported === true` and names the webhook delivery surface, not a required live `adcp_use: "webhook-signing"` key purpose — otherwise the consistency check at signature-verification time has nothing to anchor against. See `x-adcp-validation` and docs/building/implementation/security.mdx §Origin separation.' + ), + ] = None + compromise_notification: Annotated[ + CompromiseNotification | None, + Field( + description='Whether this agent emits the `identity.compromise_notification` webhook event on key revocation due to known or suspected compromise (as opposed to scheduled rotation). Subscribers use this to bound the window between compromise detected and verifiers converging on revocation. See docs/building/implementation/webhooks.mdx §identity.compromise_notification.' + ), + ] = None + + +class MeasurementGateway(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + delivery_task: Annotated[ + Literal['get_media_buy_delivery'], + Field( + description='Task providers call on the orchestrator gateway to read buyer-approved delivery.' + ), + ] = 'get_media_buy_delivery' + feedback_task: Annotated[ + Literal['provide_performance_feedback'], + Field( + description='Task providers call on the orchestrator gateway to return one compact assertion.' + ), + ] = 'provide_performance_feedback' + + +class Accreditation(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + accrediting_body: Annotated[ + str, + Field( + description='Accrediting organization — open string (the global landscape includes MRC, ARF, ABC, BARB, JICWEBS, AGOF, JIC bodies in many markets). Use the canonical short name where one exists.', + examples=['MRC', 'ARF', 'ABC', 'BARB', 'JICWEBS', 'AGOF'], + ), + ] + certification_id: Annotated[ + str | None, + Field( + description="Optional identifier for the certification in the accrediting body's records (when one exists; many bodies do not issue stable IDs)." + ), + ] = None + valid_until: Annotated[ + date | None, + Field( + description="Optional ISO 8601 date when the current accreditation expires. Buyers MAY treat post-expiry data as un-accredited. Absence means the vendor does not assert an expiry — buyers SHOULD verify currency at the accrediting body's directory." + ), + ] = None + evidence_url: Annotated[ + AnyUrl | None, + Field( + description="Optional URL pointing at the accrediting body's public listing for this certification (the buyer's path to verify the claim independently)." + ), + ] = None + + +class ComplianceTesting(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + scenarios: Annotated[ + list[str], + Field( + description="Compliance testing scenarios this agent supports. Must be non-empty — at least one scenario. Values SHOULD include every canonical controller scenario the agent implements, excluding list_scenarios because that value is a discovery operation rather than a test capability. Values MAY also include implementation-specific scenarios. Callers can use comply_test_controller with scenario: 'list_scenarios' to discover supported scenarios at runtime.", + min_length=1, + ), + ] + + +class ExtensionsSupportedItem(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[a-z][a-z0-9_]*$'} + _json_schema_extra = { + 'description': "Extension namespace (lowercase alphanumeric with underscores, e.g., 'iab_tcf', 'iab_gpp')", + } + + +class WholesaleFeedVersioning(AdCPBaseModel): + supported: Annotated[ + StrictBool, + Field( + description='Whether the agent returns wholesale_feed_version on responses and honors if_wholesale_feed_version on requests. When absent, treated as false; buyers MAY still probe (the field-presence detection path) but cannot pre-flight-decide.' + ), + ] + pricing_version_separate: Annotated[ + StrictBool | None, + Field( + description="Whether the agent tracks pricing_version independently of wholesale_feed_version. When true, the agent returns both tokens and honors if_pricing_version separately — useful for rate-card sweeps that don't change product and signal metadata. When false or absent, the agent collapses both into wholesale_feed_version; callers SHOULD NOT send if_pricing_version (it will be ignored and may produce INVALID_REQUEST when sent without if_wholesale_feed_version per the dependencies rule)." + ), + ] = None + cache_scope_account: Annotated[ + StrictBool | None, + Field( + description="Whether the agent ever returns cache_scope: 'account' (i.e., publishes per-account overlays distinct from the public rate card). When true, buyers MUST be prepared to maintain account-overlay caches alongside the public layer. When false or absent, all responses are cache_scope: 'public' regardless of whether account was provided — the agent's rate card is universal. Confidentiality note: declaring true advertises that the agent runs custom-pricing deals (low-grade market-posture signal); agents preferring not to disclose this MAY omit the field and let consumers detect-on-call via cache_scope on response." + ), + ] = None + + +class EventType3(StrEnum): + product_created = 'product.created' + product_updated = 'product.updated' + product_priced = 'product.priced' + product_removed = 'product.removed' + signal_created = 'signal.created' + signal_updated = 'signal.updated' + signal_priced = 'signal.priced' + signal_removed = 'signal.removed' + wholesale_feed_bulk_change = 'wholesale_feed.bulk_change' + + +class WholesaleFeedWebhooks(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + supported: Annotated[ + StrictBool, + Field( + description='Whether this agent can push wholesale feed change payloads through account-level sync_accounts.accounts[].notification_configs[]. When false or absent, consumers fall back to wholesale polling, optionally with if_wholesale_feed_version probes.' + ), + ] + event_types: Annotated[ + list[EventType3] | None, + Field( + description='Wholesale feed webhook event types this agent can emit. Sales agents emit product.* events and MUST expose list_products or the deprecated 3.x wholesale get_products compatibility path. Signals agents emit signal.* events and MUST support wholesale get_signals. wholesale_feed.bulk_change requires at least one corresponding repair path.', + min_length=1, + ), + ] = None + + +class CapabilityReportingDeliveryMethod(StrEnum): + webhook = 'webhook' + offline = 'offline' + + +class BudgetCappingTimezoneBasis(StrEnum): + account = 'account' + fixed = 'fixed' + + +class ReportingDestinationOffering(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + pattern: Pattern + transports: Annotated[ + list[Transport], + Field( + description='Provider transports the seller can deliver through for this pattern.', + min_length=1, + ), + ] + formats: Annotated[ + list[Format] | None, + Field( + description='Physical file formats the seller can produce. Only meaningful for file_transfer.', + min_length=1, + ), + ] = None + access_modes: Annotated[ + list[AccessMode] | None, + Field( + description='Dataset-share access families the seller can provision. Only meaningful for dataset_share.', + min_length=1, + ), + ] = None + verification_profiles: Annotated[ + reporting_verification_profile_set.ReportingVerificationProfileSet, + Field( + description='Verification profiles the seller can produce for this pattern. A destination whose accepted_verification_profiles does not intersect this set is out of offering.' + ), + ] + + +class Principal(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + supported: Literal[True] + sync_task: Literal['sync_principal'] = 'sync_principal' + read_task: Annotated[ + Literal['get_principal'], + Field( + description="Side-effect-free task returning the caller's current connection configuration and version." + ), + ] = 'get_principal' + supported_sections: Annotated[ + list[SupportedSection], + Field( + description='Connection configuration sections this seller accepts. Unsupported sections are rejected rather than silently ignored.', + min_length=1, + ), + ] + max_reporting_destinations: Annotated[ + SchemaInt | None, + Field( + description='Maximum caller-scoped destination bindings when reporting_destinations is supported. The task schema has a portable maximum of 64; sellers may advertise a lower operational limit.', + ge=1, + le=64, + ), + ] = None + reporting_destination_offerings: Annotated[ + list[ReportingDestinationOffering] | None, + Field( + description='Objective per-pattern delivery offering, so buyers can select a compatible destination without submit-and-reject probing. A destination whose pattern, transport, access mode, format set, or verification-profile set falls outside this offering fails the sync request atomically with UNSUPPORTED_FEATURE; provider-side setup outcomes for in-offering destinations are expressed only through destination states.', + max_length=3, + min_length=1, + ), + ] = None + suspension_interval_seconds: Annotated[ + SchemaInt | None, + Field( + description='Advertised upper bound, in seconds, between a committed destination suspension or revocation — or a subscriber deactivation — and the halt of new deliveries and fires.', + ge=1, + le=86400, + ), + ] = None + caller_event_types: Annotated[ + list[notification_type.NotificationType] | None, + Field( + description='Notification types this seller can emit against caller-level subscribers, so buyers select from the offering instead of probing by rejection. Account-anchored entries fire with fire-time authorization scoping. When absent, only capabilities.changed is assumed. A caller-level subscriber requesting a type outside this set fails the sync request with UNSUPPORTED_FEATURE.', + min_length=1, + ), + ] = None + optimistic_concurrency: Annotated[ + StrictBool, + Field( + description='Whether expected_configuration_version is enforced. Sellers SHOULD support it when several services may authenticate as the same stable principal. When false, a request carrying expected_configuration_version is rejected with UNSUPPORTED_FEATURE rather than silently ignored.' + ), + ] + + +class TrustedMatch(AdCPBaseModel): + surfaces: Annotated[ + list[property_type.PropertyType] | None, + Field(description='Surface types this seller supports via TMP.'), + ] = None + + +class Language(AdCPBaseModel): + supported: Annotated[ + StrictBool | None, + Field(description='Whether seller supports language-preference targeting'), + ] = None + supported_languages: Annotated[ + list[locale_tag.LanguageTag] | None, + Field( + description='Exact canonical BCP 47 language ranges accepted in targeting.language. Omission means the seller makes no exhaustive language-list declaration.' + ), + ] = None + + +class KeywordTargets(AdCPBaseModel): + supported_match_types: Annotated[ + list[match_type.MatchType], + Field( + description='Match types this seller supports for keyword targets. Sellers must reject goals with unsupported match types.', + min_length=1, + ), + ] + + +class NegativeKeywords(AdCPBaseModel): + supported_match_types: Annotated[ + list[match_type.MatchType], + Field( + description='Match types this seller supports for negative keywords. Sellers must reject goals with unsupported match types.', + min_length=1, + ), + ] + + +class GeoProximity(AdCPBaseModel): + radius: Annotated[ + StrictBool | None, + Field( + description='Whether seller supports simple radius targeting (distance circle from a point)' + ), + ] = None + travel_time: Annotated[ + StrictBool | None, + Field( + description='Whether seller supports travel time isochrone targeting (requires a routing engine)' + ), + ] = None + geometry: Annotated[ + StrictBool | None, + Field( + description='Whether seller supports pre-computed GeoJSON geometry (buyer provides the polygon)' + ), + ] = None + transport_modes: Annotated[ + list[transport_mode.TransportMode] | None, + Field( + description='Transport modes supported for travel_time isochrones. Only relevant when travel_time is true.', + min_length=1, + ), + ] = None + + +class Targeting(AdCPBaseModel): + geo_countries: Annotated[ + StrictBool | None, + Field(description='Country-level targeting using ISO 3166-1 alpha-2 codes'), + ] = None + geo_regions: Annotated[ + StrictBool | geo_region_support.GeographicRegionSupport | None, + Field( + description='ISO 3166-2 subdivision inclusion targeting. A legacy boolean is a coarse seller-wide declaration. Structured country/value entries are individually supported within the response scope, but do not promise joint composability or availability through the same execution route or account. Only Product.overlay_support supplies the binding set of executable targeting permissions for a configured Product.' + ), + ] = None + geo_regions_exclude: Annotated[ + StrictBool | geo_region_support.GeographicRegionSupport | None, + Field( + description='ISO 3166-2 subdivision exclusion targeting, declared independently from inclusion. Structured country/value entries are individually supported within the response scope, but do not promise joint composability or availability through the same execution route or account. Only Product.overlay_support supplies the binding set of executable targeting permissions for a configured Product; absence means buyers cannot infer exclusion support from geo_regions alone.' + ), + ] = None + geo_metros: Annotated[ + GeoMetros | None, + Field( + description='Metro area targeting. Properties indicate which classification systems are supported.' + ), + ] = None + geo_postal_areas: Annotated[ + postal_area_support.PostalAreaSupport | None, + Field( + description='Postal area targeting. Prefer the native country-keyed map where each ISO 3166-1 alpha-2 country lists supported country-local postal systems. Deprecated legacy country-fused postal-system boolean aliases may be emitted alongside native country keys during migration.' + ), + ] = None + geo_places: Annotated[ + dict[ + geo_place_system.GeographicPlaceIdentifierSystem, + geo_place_support.GeographicPlaceSystemSupport, + ] + | None, + Field( + description='Place targeting support keyed by collision-safe identifier system. Each system declares exact country-to-place-type combinations, accepted catalog versions, and a machine-readable resolver. Sellers MUST reject unsupported systems, country/type pairs, versions, and identifiers rather than silently dropping them.', + min_length=1, + ), + ] = None + age_restriction: Annotated[ + AgeRestriction | None, + Field(description='Age restriction capabilities for compliance (alcohol, gambling)'), + ] = None + demographics: Annotated[ + Demographics | None, + Field( + description='Seller-wide discovery rollup for canonical demographic targeting. supported=true means at least one product implements the demographic targeting contract; it does not authorize demographic targeting on every product. Buyers MUST inspect Product.demographic_targeting for exact execution modes, bounds, intervals, and unknown-age behavior.' + ), + ] = None + language: Annotated[ + StrictBool | Language | None, + Field( + description="Language-preference targeting support. A legacy boolean is a coarse declaration. The structured form can enumerate the exact canonical BCP 47 targeting ranges the seller accepts. Buyers MUST treat supported_languages as exact selectable values: a declared 'fr' does not by itself authorize a request for 'fr-CA'. Sellers MUST reject unsupported requested values rather than silently widening or dropping them." + ), + ] = None + keyword_targets: Annotated[ + KeywordTargets | None, + Field( + description='Keyword targeting capabilities. Presence indicates support for targeting_overlay.keyword_targets and keyword_targets_add/remove in update_media_buy.' + ), + ] = None + negative_keywords: Annotated[ + NegativeKeywords | None, + Field( + description='Negative keyword capabilities. Presence indicates support for targeting_overlay.negative_keywords and negative_keywords_add/remove in update_media_buy.' + ), + ] = None + placement_selection: Annotated[ + StrictBool | None, + Field( + description='When true, seller-wide rollup indicating at least one product supports targeting_overlay.placement_selection. False or absence makes no product-level promise. Product.overlay_support and Product.placements remain authoritative for a selected product.' + ), + ] = None + property_list: Annotated[ + StrictBool | None, + Field( + description='When true, seller-wide rollup indicating at least one product supports targeting_overlay.property_list inclusion targeting. False or absence makes no product-level promise. Product.overlay_support is authoritative.' + ), + ] = None + property_list_exclude: Annotated[ + StrictBool | None, + Field( + description='When true, seller-wide rollup indicating at least one product supports targeting_overlay.property_list_exclude targeting. False or absence makes no product-level promise. Product.overlay_support is authoritative.' + ), + ] = None + collection_list: Annotated[ + StrictBool | None, + Field( + description='When true, seller-wide rollup indicating at least one product supports targeting_overlay.collection_list inclusion targeting. False or absence makes no product-level promise. Product.overlay_support is authoritative.' + ), + ] = None + collection_list_exclude: Annotated[ + StrictBool | None, + Field( + description='When true, seller-wide rollup indicating at least one product supports targeting_overlay.collection_list_exclude targeting. False or absence makes no product-level promise. Product.overlay_support is authoritative.' + ), + ] = None + geo_proximity: Annotated[ + GeoProximity | None, + Field( + description='Proximity targeting capabilities from arbitrary coordinates via targeting_overlay.geo_proximity.' + ), + ] = None + + +class ConversionTracking(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + multi_source_event_dedup: Annotated[ + StrictBool | None, + Field( + description='Whether this seller can deduplicate conversion events across multiple event sources within a single goal. When true, the seller honors the deduplication semantics in optimization_goals event_sources arrays — the same event_id from multiple sources counts once. When false or absent, buyers should use a single event source per goal; multi-source arrays will be treated as first-source-wins. Most social platforms cannot deduplicate across independently-managed pixel and CAPI sources.' + ), + ] = None + per_creative_attribution: Annotated[ + StrictBool | None, + Field( + description='Whether the seller can attribute conversions to specific creatives within a package and surface that breakdown via media_buy_deliveries[].by_package[].by_creative[].conversions in get_media_buy_delivery. Defaults to false when omitted. Sellers that report conversions only at the line / package / placement / campaign granularity (retail-media, MMP-mediated mobile, CTV performance) declare false (or omit) and the per-creative scenario grades not_applicable for them. Sellers that surface ad-level conversion attribution (most social platforms) declare true and the scenario asserts the breakdown is populated end-to-end. Defaults to false to preserve backward compatibility.' + ), + ] = None + supported_event_types: Annotated[ + list[event_type_1.EventType] | None, + Field( + description='Event types this seller can track and attribute. If omitted, all standard event types are supported.', + min_length=1, + ), + ] = None + supported_targets: Annotated[ + list[SupportedTarget1] | None, + Field( + description='Event-goal target kinds this seller can compute against. Buyers should only submit event-kind optimization goals whose target.kind is listed here — sellers MUST reject goals with unlisted target kinds. When omitted, only target-less event goals (maximize conversion count within budget) are guaranteed; sellers MAY accept specific target kinds but buyers should not rely on it. Named to parallel `metric_optimization.supported_targets` at the product level — same concept (which target kinds are supported), one at seller-capability granularity and one at product granularity.', + min_length=1, + ), + ] = None + supported_uid_types: Annotated[ + list[uid_type.UidType] | None, + Field(description='Universal ID types accepted for user matching', min_length=1), + ] = None + supported_hashed_identifiers: Annotated[ + list[SupportedIdentifierType] | None, + Field( + description='Hashed PII types accepted for user matching. Buyers must hash before sending (SHA-256, normalized).', + min_length=1, + ), + ] = None + supported_action_sources: Annotated[ + list[action_source.ActionSource] | None, + Field(description='Action sources this seller accepts events from', min_length=1), + ] = None + attribution_windows: Annotated[ + list[AttributionWindow] | None, + Field( + description='Attribution windows available from this seller. Single-element arrays indicate fixed windows; multi-element arrays indicate configurable options the buyer can choose from via attribution_window on optimization goals.' + ), + ] = None + + +class FrequencyCapping(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + supported_per_units: Annotated[ + list[reach_unit.ReachUnit] | None, + Field( + description='Entity granularities the seller can enforce caps against. Values from the reach-unit enum. Omit to indicate all reach-unit values are supported.', + min_length=1, + ), + ] = None + supported_window_units: Annotated[ + list[str] | None, + Field( + description="Duration units the seller supports for frequency cap windows. Values must match the duration.json unit enum (e.g., 'hours', 'days', 'campaign'). Omit to indicate all duration units are supported.", + min_length=1, + ), + ] = None + + +class BudgetCapping(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + supported_scopes: Annotated[ + list[SupportedScope], + Field( + description='Scopes where the seller enforces hard daily caps. media_buy bounds aggregate spend across all packages without allocating it. package bounds one package and remains subordinate to any aggregate cap.', + min_length=1, + ), + ] + supported_periods: Annotated[ + list[Literal['day']], + Field( + description='Cap reset periods the seller enforces. AdCP 3.2 defines day only; the array form reserves room for future periods as an additive change.', + min_length=1, + ), + ] + timezone_basis: Annotated[ + BudgetCappingTimezoneBasis, + Field( + description='Source of the default cap-day boundary. account uses the selected Account.timezone and therefore supports different boundaries for different accounts. fixed uses fixed_timezone for every media buy regardless of account timezone.' + ), + ] + fixed_timezone: Annotated[ + str | None, + Field( + description='Feature-specific default cap-day timezone used when timezone_basis is fixed. Use UTC or an IANA timezone identifier.', + min_length=1, + ), + ] = None + buyer_timezone_override: Annotated[ + StrictBool | None, + Field( + description='When true, buyers MAY override the default cap-day boundary via media-buy-level budget_cap_timezone. The accepted timezone applies to aggregate and package caps alike. When false or absent, a submitted override is rejected with UNSUPPORTED_FEATURE.' + ), + ] = None + + +class SponsoredIntelligence(AdCPBaseModel): + endpoint: Annotated[Endpoint, Field(description='SI agent endpoint configuration')] + capabilities: Annotated[ + si_capabilities.SiCapabilities, + Field(description='Modalities, components, and commerce capabilities'), + ] + brand_url: Annotated[ + AnyUrl | None, Field(description='URL to brand.json with colors, fonts, logos, tone') + ] = None + + +class Brand(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + rights: Annotated[ + StrictBool | None, + Field( + description='Supports get_rights and acquire_rights for rights discovery and clearance' + ), + ] = False + right_types: Annotated[ + list[right_type.RightType] | None, + Field(description='Types of rights available through this agent'), + ] = None + available_uses: Annotated[ + list[right_use.RightUse] | None, + Field(description="Rights uses available across this agent's roster"), + ] = None + generation_providers: Annotated[ + list[str] | None, + Field(description='LLM/generation providers this agent can issue credentials for'), + ] = None + description: Annotated[ + str | None, + Field( + description="Description of the agent's brand protocol capabilities", max_length=5000 + ), + ] = None + + +class RepresentationResolution(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + supported: Annotated[ + Literal[True], + Field( + description='True means the agent accepts CreativeRepresentationSet input and returns `representation_selection` or per-candidate structured rejections.' + ), + ] + strategies: Annotated[ + list[representation_selection_strategy.RepresentationSelectionStrategy], + Field( + description='Deterministic selection strategies the seller implements after compatibility filtering. `representation_order` selects the first compatible source entry. `highest_compatible_vast` selects the highest exact VAST version from the already-intersected candidate set, with equal-version ties resolved by source array order.', + min_length=1, + ), + ] + + +class Metric(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + metric_id: Annotated[ + vendor_metric_id.VendorMetricId, + Field( + description="Identifier for the metric within the vendor's vocabulary. Combined with the agent's BrandRef, forms the canonical tuple `(vendor.domain, vendor.brand_id, metric_id)`. Each metric_id MUST be unique within a single agent's catalog." + ), + ] + standard_reference: Annotated[ + AnyUrl | None, + Field( + description='Optional URI pointing at the published standard this metric IMPLEMENTS (e.g., IAB Attention Measurement Guidelines, MRC Viewable Impression Measurement, GARM emissions framework). Distinct from `accreditations[]` — `standard_reference` is what the metric is built against; `accreditations[]` is third-party certification that the implementation actually conforms. Buyer agents normalizing across vendors SHOULD apply the AdCP URL canonicalization rules before comparing — vendors implementing the same standard MAY use different URL forms for the same canonical document.' + ), + ] = None + accreditations: Annotated[ + list[Accreditation] | None, + Field( + description="Third-party accreditations this metric holds (MRC, ARF, JIC, ABC, BARB, AGOF, etc.). Distinct from `standard_reference`: a metric can implement a standard without being independently accredited. Buyers asking 'is this MRC-accredited?' SHOULD check this array, not just `standard_reference`. Each entry names the accrediting body and optionally pins a certification ID, validity date, and evidence URL." + ), + ] = None + unit: Annotated[ + str | None, + Field( + description='Unit of the metric value when reported via `vendor_metric_values.value` (e.g., `score`, `seconds`, `persons`, `gCO2e`, `lift_percent`, `USD`). Buyers SHOULD render the unit alongside the value rather than computing units locally; sellers populating `vendor_metric_values.unit` MUST match this declaration when present.', + examples=['score', 'seconds', 'persons', 'gCO2e', 'lift_percent', 'index', 'USD'], + ), + ] = None + description: Annotated[ + str | None, + Field( + description='Human-readable description of what this metric measures and any relevant methodology notes. Surfaced in buyer-agent UX when explaining the metric to humans.' + ), + ] = None + methodology_url: Annotated[ + AnyUrl | None, + Field( + description="URL to the vendor's full methodology documentation for this metric. Buyers SHOULD link or fetch this when human review of the methodology is in scope (compliance, RFP review, accreditation audit). Field name mirrors `governance.property_features[].methodology_url`." + ), + ] = None + methodology_version: Annotated[ + str | None, + Field( + description='Optional version identifier (semver, ISO date, or vendor-defined version string) for the methodology this metric currently implements. When present, buyer agents pin the contracted version via `committed_metrics[].methodology_version` (vendor scope) so silent vendor methodology changes are detectable; absence means the vendor does not version their methodology and buyers MUST treat any change as untracked.', + examples=['v2.1', '2026-Q1', '1.0'], + ), + ] = None + ext: ext_1.ExtensionObject | None = None + + +class Measurement(AdCPBaseModel): + produces_performance_feedback: Annotated[ + StrictBool | None, + Field( + description="Whether this measurement agent produces compact provide_performance_feedback assertions for a buyer-controlled orchestrator gateway. In the first experimental tier the provider reads delivery through the gateway's get_media_buy_delivery task and returns assertions through its provide_performance_feedback task. The gateway decides what to forward to each seller." + ), + ] = False + metrics: Annotated[ + list[Metric], + Field( + description="Metrics this agent computes. Each entry is identified by `metric_id` within the vendor's vocabulary; the canonical reference everywhere a measurement value appears (`committed_metrics`, `vendor_metric_values`, `missing_metrics`) is the tuple `(vendor.domain, vendor.brand_id, metric_id)`.", + min_length=1, + ), + ] + + +class CreativeSpecs(AdCPBaseModel): + vast_versions: Annotated[ + list[vast_version.VastVersion] | None, + Field( + description='Seller-wide VAST execution ceiling. Each product format option declares its binding accepted subset in `params.vast_versions`.', + min_length=1, + ), + ] = None + macro_resolution_capabilities: Annotated[ + list[macro_resolution_capability.MacroProcessingCapability] | None, + Field( + description='Seller-wide ceiling for exact macro processing tuples (dialect identity/revision, semantic mapping, operation, actor, context, and encoding). It never proves a product execution path supports the same tuple and does not claim tracker firing; inspect the selected format option and, when standardized, product tracker capabilities.', + min_length=1, + ), + ] = None + mraid_versions: Annotated[ + list[MraidVersion] | None, + Field(description='MRAID versions supported for rich media mobile creatives'), + ] = None + vpaid: Annotated[ + StrictBool | None, Field(description='VPAID support for interactive video ads') + ] = None + simid: Annotated[ + StrictBool | None, Field(description='SIMID support for interactive video ads') + ] = None + vast_validation: Annotated[ + VastValidation | None, + Field( + description="Level of VAST asset validation the seller performs at sync_creatives (including dry_run): 'structural' checks manifest shape and format requirements only and never inspects the VAST document; 'document' additionally parses the VAST document and can return VAST_PARSE_FAILED / VAST_VERSION_MISMATCH; 'wrapper' additionally resolves the wrapper chain and can return VAST_WRAPPER_DEPTH_EXCEEDED. Absent means 'structural'. See the VAST Validation section of the video channel documentation for the normative checks at each level." + ), + ] = VastValidation.structural + + +class Execution(AdCPBaseModel): + trusted_match: Annotated[ + TrustedMatch | None, + Field( + description='Trusted Match Protocol (TMP) support. Presence of this object indicates the seller has TMP infrastructure deployed. Check individual products via get_products for per-product TMP capabilities.' + ), + ] = None + axe_integrations: Annotated[ + list[AnyUrl] | None, + Field( + deprecated=True, + description='Deprecated. Legacy AXE integrations. Use trusted_match for new integrations.', + ), + ] = None + creative_specs: Annotated[ + CreativeSpecs | None, Field(description='Creative specification support') + ] = None + targeting: Annotated[ + Targeting | None, + Field( + description='Targeting capabilities. If declared true/supported, buyer can use these targeting parameters and seller MUST honor them.' + ), + ] = None + + +class SupportedFormat(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + capability_id: Annotated[ + str | None, + Field( + description="Stable identifier for this format capability within the creative agent (e.g., 'audio_narration_30s'). The value MUST be unique across this agent's supported_formats catalog. Route the advertised operation with this ID: build_creative.target_capability_id(s) for build, preview_creative.target_capability_id for preview, or validate_input targets[] kind capability for validate. Capability IDs are agent-local routes and do not appear on portable manifests or products.", + pattern='^[a-zA-Z0-9_-]+$', + ), + ] = None + format: Annotated[ + creative_operation_format_declaration.CreativeOperationFormatDeclaration, + Field( + description='Format declaration on which this agent can perform the listed operations. Creative-agent capability self-description has no seller production authority, so tracker_execution_contract and tracker_execution_contract_digest are forbidden.' + ), + ] + operations: Annotated[ + list[Operation] | None, + Field( + description='Creative operations this capability supports. `build` means the agent can produce a conforming manifest via build_creative; `validate` means it can evaluate inputs against the declaration; `preview` means it can render a preview. New 3.2 producers MUST emit this field so buyers and registries can distinguish producers, validators, and renderers without probing tasks. Consumers interpret omission from a legacy 3.x entry as `["build"]`.', + min_length=1, + ), + ] = [Operation.build] + + +class Creative(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + supports_compliance: Annotated[ + StrictBool | None, + Field( + description='When true, this creative agent can process briefs with compliance requirements (required_disclosures, prohibited_claims) and will validate that disclosures can be satisfied by the target format.' + ), + ] = None + has_creative_library: Annotated[ + StrictBool | None, + Field( + description='When true, this agent hosts a creative library and supports list_creatives and creative_id references in build_creative. Creative agents with a library should also implement the accounts protocol (sync_accounts / list_accounts) so buyers can establish access.' + ), + ] = False + supports_revisions: Annotated[ + StrictBool | None, + Field( + description='When true, this agent accepts buyer-assigned revision_id on sync_creatives, enforces immutable revision content, echoes accepted revision identity, returns it from list_creatives, and attributes delivered executions to it. Revision support does not imply revision history, rollback, or staged activation.' + ), + ] = False + supports_generation: Annotated[ + StrictBool | None, + Field( + description='When true, this agent can generate creatives from natural language briefs via build_creative. The buyer provides a message with creative direction, and the agent produces a manifest with generated assets. When false, build_creative only supports transformation or library retrieval.' + ), + ] = False + supports_transformation: Annotated[ + StrictBool | None, + Field( + description='When true, this agent can transform or resize existing canonical manifests via build_creative. The buyer supplies a creative_manifest and an advertised target_capability_id.' + ), + ] = False + representation_resolution: Annotated[ + RepresentationResolution | None, + Field( + description='Explicit opt-in for deterministic seller-bound selection from `build_creative.creative_representation_set`. Only the destination sales agent may advertise and exercise this capability because resolution requires its current product, placement/publisher narrowings, and seller-wide execution ceilings. A standalone creative agent may help a buyer select locally but MUST NOT advertise this capability or claim seller deliverability. Absence means the caller selects a representation before sending a seller-bound manifest; the agent MUST NOT guess silently.' + ), + ] = None + supports_transformers: Annotated[ + StrictBool | None, + Field( + description='When true, this agent exposes account-scoped creative transformers via list_transformers (the creative analog of media-buy products) and accepts transformer_id + config on build_creative. Buyers SHOULD call list_transformers to discover available transformers, their typed config params (and account-scoped enumerable option values via expand_params), and pricing. When false or absent, the agent does not offer the transformer surface.' + ), + ] = False + supports_refinement: Annotated[ + StrictBool | None, + Field( + description="When true, this agent retains produced build_variant leaves for an agent-defined retention window and can re-build from one via build_creative's refine_from_build_variant_id — applying a natural-language instruction in message plus an optional config delta, returning new lineage-linked variants. A build-time agent capability independent of generation/transformation. When false or absent, refine_from_build_variant_id is rejected with UNSUPPORTED_FEATURE; buyers refine instead via the transform path (creative_manifest + message)." + ), + ] = False + supports_spend_controls: Annotated[ + StrictBool | None, + Field( + description='When true, build_creative honors a per-call `max_spend` ceiling (producing partial paid results and returning budget_status:"capped" + a BUDGET_CAP_REACHED advisory rather than overspending) AND supports mode:"estimate" dry-runs (a projected cost band, producing/billing nothing). When false or absent, max_spend / mode:estimate are rejected with UNSUPPORTED_FEATURE. Out-of-band billers (bills_through_adcp:false) have no AdCP cost truth to cap against, so this is meaningful only alongside bills_through_adcp:true.' + ), + ] = False + supports_evaluator: Annotated[ + StrictBool | None, + Field( + description="Experimental (x-status: experimental) — agents setting this true MUST also list `creative.evaluator` in `experimental_features`; the surface MAY change between 3.x releases with notice (see docs/reference/experimental-status). When true, build_creative accepts an advisory `evaluator` input (exemplars / account-arranged evaluator_id / agent_url, plus an optional `feature_requirement[]` gate, a `rank_by` ordering, and an allowlisted `feature_agent` pointer). Feature discovery uses this response's governance.creative_features catalog: rank_by, feature_requirement, and eval.features[] all share the same creative-feature vocabulary as get_creative_features. evaluator_id is not discovered from this catalog; it is a pre-provisioned account preset whose emitted feature_ids still come from it. The evaluator populates a per-leaf `eval` block of creative-feature values (creative-feature-result[], the same shape get_creative_features returns) on BuildCreativeVariantSuccess leaves, which is what the recommended/rank it sets on the best_of_n axis are computed over. The agent runs a gate-then-rank pipeline over its best_of_n exploration: it evaluates each leaf, DROPS leaves failing `feature_requirement[]` from its recommended survivors, then orders survivors by `rank_by`. The gate is internal pruning of which leaves the agent recommends/returns from its own exploration — it never blocks an already-produced billable leaf: what is produced and billed is governed by max_variants/max_creatives/max_spend, not the evaluator. When the evaluator names an external agent, it MUST appear in `creative_policy.accepted_verifiers[]` (off-list → EVALUATOR_AGENT_NOT_ACCEPTED), and the producing agent authenticates the outbound evaluator call on the transport. Evaluator credentials and caller-supplied trust material MUST NOT be passed in the build_creative payload; credential- or trust-material payload keys should be rejected with CREDENTIAL_IN_ARGS. When false or absent, the `evaluator` input is ignored and no `eval` block is emitted." + ), + ] = False + refinable_retention_seconds: Annotated[ + SchemaInt | None, + Field( + description='When supports_refinement is true, the GUARANTEED-MINIMUM window (a floor, not a ceiling) during which a produced build_variant_id remains refinable via refine_from_build_variant_id: a ref within this window from production SHOULD resolve; the agent MAY retain longer. Omit when the retention window is agent-defined and not advertised — buyers then treat refinability as best-effort and handle REFERENCE_NOT_FOUND.', + ge=0, + ), + ] = None + multiplicity: Annotated[ + Multiplicity | None, + Field( + description="Pre-call discriminators for build_creative fan-out, so a buyer knows BEFORE sending max_creatives / max_variants whether this agent supports them and the ceilings. Over-limit requests are CLAMPED to these ceilings (the agent produces up to the limit and signals the shortfall via items_returned < items_total on BuildCreativeVariantSuccess), not rejected — consistent with item_limit's 'use the lesser' rule. Absent means no fan-out: build_creative produces a single creative and max_creatives/max_variants>1 are not supported." + ), + ] = None + supported_formats: Annotated[ + list[SupportedFormat] | None, + Field( + description='Canonical-format capability catalog for this creative agent. This is the 3.2 source of truth for discovering which format contracts the agent can build, validate, or preview; it replaces the deprecated `list_creative_formats` task. Each entry uses the authority-free `CreativeOperationFormatDeclaration` projection of a product format declaration: canonical shape and creative-route macro processing are preserved, while seller production commitments are excluded. New 3.2 producers MUST publish a stable agent-local `capability_id` and explicit `operations` for task routing. Every emitted `capability_id` MUST be unique within this catalog so a route selects exactly one entry. During the 3.x compatibility window, consumers MUST also accept legacy entries that omit either field; absent `operations` means `["build"]`, while an absent `capability_id` means the entry is discoverable by canonical contract but cannot be selected through a capability-ID route.\n\n**Publisher-specific support.** To claim exact support for a publisher declaration, `format` carries the declaration\'s `{publisher_domain, format_option_id}` pair plus its canonical `format_kind` and narrowed `params`. Generic creative agents MAY instead advertise a canonical parameter envelope without publisher identity. A generic capability matches a target declaration only when the capability can satisfy every target constraint; matching canonical names alone is insufficient. Registries MAY reverse-index these entries by `format.format_kind`, `format.publisher_domain`, and `format.format_option_id`.\n\nThis catalog describes creative operations, not sales-agent inventory deliverability. Sales agents publish the purchasable closed set on each `Product.format_options[]`; publisher acceptance lives in `adagents.json.formats[]`.' + ), + ] = None + preview: Annotated[ + Preview | None, + Field( + description='Per-route preview_creative capability metadata. New 3.2 producers whose supported_formats[] explicitly advertises a routable preview operation MUST emit this block. rendering_origin describes how each route is implemented but is informational and never grants presentation authority: only a matching publisher-origin placement preview_provider delegation can do that. routes[].capability_id MUST equal the set of capability IDs on supported_formats[] entries whose operations contains preview.' + ), + ] = None + localization: Annotated[ + Localization | None, + Field( + description='Materialized creative-localization support for sync_creatives/list_creatives, including source-only monolingual topology. Presence opts the agent into exact locale-variant round-trip, strict RFC 4647 Lookup, optional buyer-declared language-family fallback rules, explicit final default/unmatched behavior, creative-wide review, transactional replacement, seller product-format locale-policy enforcement, and delivery attribution. This is a coarse structural capability, not a promise that every locale/format/account combination is accepted; sellers publish accepted ranges on product format declarations and validate each write before mutation. Omit this object when localization is unsupported.' + ), + ] = None + bills_through_adcp: Annotated[ + StrictBool | None, + Field( + description='When true, this creative agent bills through the AdCP rate-card surface: list_creatives returns pricing_options when include_pricing=true with an authenticated account, build_creative populates pricing_option_id and vendor_cost on the response, and report_usage accepts records against the rate card. When false or absent, the agent bills out of band (flat license, SaaS contract, bundled enterprise agreement) and buyers should skip pricing fields and tolerate report_usage returning accepted: 0 with errors carrying BILLING_OUT_OF_BAND. A pre-call discriminator so buyer agents can route across many creative agents without first establishing an account to probe pricing.' + ), + ] = False + canonical_catalog_version: Annotated[ + str | None, + Field( + description="Optional. The AdCP canonical-formats catalog version this agent's runtime is built against (e.g., `3.1`, `3.2.0`). Lets buyer SDKs detect canonical-catalog skew between their generated types and the seller's actual support. SDKs MAY declare the version they were generated against (typically the AdCP version they ship for); when seller and SDK versions disagree, SDKs SHOULD soft-warn rather than fail (the open-enum semantics on `canonical-format-kind.json` make unknown canonicals safe to retain, so skew is not a hard error — it just means the older side might not understand newer canonical values). Omitted by sellers who haven't yet generated against a versioned catalog; absence is interpreted as the AdCP version advertised by the broader capabilities response.", + pattern='^\\d+\\.\\d+(\\.\\d+)?$', + ), + ] = None + + +class AudienceTargeting(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + supported_identifier_types: Annotated[ + list[SupportedIdentifierType], + Field( + description='PII-derived identifier types accepted for audience matching. Buyers should only send identifiers the seller supports.', + min_length=1, + ), + ] + supports_platform_customer_id: Annotated[ + StrictBool | None, + Field( + description="Whether the seller accepts the buyer's CRM/loyalty ID as a matchable identifier. Only applicable when the seller operates a closed ecosystem with a shared ID namespace (e.g., a retailer matching against their loyalty program). When true, buyers can include platform_customer_id values in AudienceMember.identifiers for matching against the seller's identity graph. Reporting on matched platform_customer_ids typically requires a clean room or the seller's own reporting surface." + ), + ] = None + supported_uid_types: Annotated[ + list[uid_type.UidType] | None, + Field( + description='Universal ID types accepted for audience matching (MAIDs, RampID, UID2, etc.). MAID support varies significantly by platform — check this field before sending uids with type: maid.', + min_length=1, + ), + ] = None + minimum_audience_size: Annotated[ + SchemaInt, + Field( + description='Minimum matched audience size required for targeting. Audiences below this threshold will have status: too_small. Varies by platform (100–1000 is typical).', + ge=1, + ), + ] + supported_activation_methods: Annotated[ + list[audience_activation_method.AudienceActivationMethod] | None, + Field( + description="Union of audience_activation.methods across the seller's products. Fast-fail discovery: a buyer reads this once and skips the catalog walk when nothing overlaps its pipeline. Per-product declarations are the source of truth; sellers MUST keep this consistent with the catalog. Operational coordinates are account-scoped: the union MAY omit consumer_identities and destination_ref until bilateral account setup establishes them, and MUST NOT expose another account's coordinates. Absence of this field with media_buy.audience_activation listed in experimental_features means walk the catalog; only a present, non-overlapping union is a fast-fail signal. Experimental (x-status: experimental): sellers implementing audience activation declarations MUST list media_buy.audience_activation in experimental_features. Per docs/reference/experimental-status, this surface MAY change between 3.x releases with notice.", + min_length=1, + ), + ] = None + matching_latency_hours: Annotated[ + MatchingLatencyHours | None, + Field( + description='Expected matching latency range in hours after upload. Use to calibrate polling cadence and set appropriate expectations before configuring push_notification_config.' + ), + ] = None + + +class MediaBuy(AdCPBaseModel): + anonymous_discovery: Annotated[ + StrictBool | None, + Field( + description='Whether this seller accepts product discovery without caller credentials. This applies to list_products and to get_products in brief or wholesale mode; it does not apply to proposal refinement or finalization, purchasing, account-scoped reads, or mutations. true means an anonymous discovery request can produce a successful response, but the response may be a public subset and may differ from results for an authenticated principal or selected account. false means these discovery calls require an authenticated principal. Absence means unspecified legacy behavior, so callers probe and handle AUTH_MISSING. A valid authenticated request is never rejected merely because credentials were supplied. true is inconsistent with account.required_for_products=true because an anonymous caller cannot select protected account context.' + ), + ] = None + acceptance_policy_discovery: Annotated[ + AcceptancePolicyDiscovery | None, + Field( + description='Registry-backed seller acceptance-policy discovery. Presence means the seller publishes a versioned catalog; it does not claim that the seller evaluates acceptance_context during discovery. Discovery is advisory, exact task responses remain authoritative, and absent capability means support is unknown rather than unrestricted acceptance.' + ), + ] = None + supported_pricing_models: Annotated[ + list[pricing_model.PricingModel] | None, + Field( + description='Pricing models this seller supports across its product portfolio. Buyers can use this for pre-flight filtering before querying individual products. Individual products may support a subset of these models.', + min_length=1, + ), + ] = None + buying_modes: Annotated[ + list[BuyingMode] | None, + Field( + description="Buying modes this seller supports on get_products. 'brief' (semantic discovery driven by the brief) is universally supported and implicit. 'wholesale' (raw wholesale product feed enumeration — caller omits brief and the seller returns the full priced product feed, paginated) is opt-in and SHOULD be declared explicitly so buyers can probe before issuing wholesale calls. 'refine' lets buyers iterate on prior products/proposals and is also the vehicle for finalizing draft proposals when the seller returns them. Sellers MAY declare ['brief', 'wholesale'] to signal wholesale support; absent declaration is treated as ['brief'] for wholesale-feed probing purposes and sellers MAY return INVALID_REQUEST for wholesale calls they do not support. Symmetric with signals.discovery_modes.", + min_length=1, + ), + ] = [BuyingMode.brief] + measurement_terms_acceptance: Annotated[ + StrictBool | None, + Field( + description="Whether this seller can accept the default measurement_terms it advertises on a product. A value of true means the seller can return a product carrying measurement_terms for a measurement-specific brief and accept those terms unchanged on a package for that product. This opts the seller into conformance scenarios that discover and replay the product's own terms. False or absent means acceptance is outside the seller's advertised scope, but the seller must still reject unsupported terms with TERMS_REJECTED rather than being graded on an acceptance path it did not claim." + ), + ] = False + availability_horizon: Annotated[ + StrictBool | None, + Field( + description='Whether this seller supports flexible-window availability discovery: parsing offer_filters.availability_horizon and answering with time-dimensioned forecast points that carry availability_status. Sellers declaring true MUST apply the full window contract — half-open non-overlapping windows that partition the requested horizon (or signal gaps via incomplete[]), with availability_status computed from all booking eligibility constraints, not only competing holds. false or absent means flexible-window support is unknown: buyers SHOULD use exact start_date/end_date filtering, and sellers MAY ignore the field or reject it. Conformance storyboards gate flexible-window checks on this declaration.' + ), + ] = False + lifecycle_tools: Annotated[ + list[LifecycleTool] | None, + Field( + description='Compact product and MediaBuy lifecycle operation names this seller supports. Added in AdCP 3.2 as task-specific contracts that form the 4.0 lifecycle foundation. Sellers may advertise any supported subset while retaining the deprecated get_products/create_media_buy/update_media_buy facades throughout 3.x. Each stateful split task has its own idempotency identity; callers MUST retry with the same tool name.', + min_length=1, + ), + ] = None + proposal_refinement: Annotated[ + ProposalRefinement | None, + Field( + description='Pre-flight support for typed refine_proposals revision dimensions. These declarations mean the seller can parse and mechanically validate a dimension; they never promise that the seller will commercially concede it. Absence means typed-dimension support is unknown and buyers must handle per-result partial or unable outcomes. A dimension omitted from an explicit supported_dimensions list MUST be rejected at task level with UNSUPPORTED_FEATURE before any proposal is created.' + ), + ] = None + reporting_delivery_methods: Annotated[ + list[CapabilityReportingDeliveryMethod] | None, + Field( + description="How this seller delivers reporting data to buyers. Polling via get_media_buy_delivery is always available as a baseline regardless of this field. This array declares additional push-based delivery methods the seller supports. 'webhook': seller pushes to buyer-provided URL (configured per buy via reporting_webhook). 'offline': seller pushes batch files to a cloud storage bucket (seller-provisioned per account via reporting_bucket on the account object). When absent, only polling is available.", + min_length=1, + ), + ] = None + performance_feedback: Annotated[ + PerformanceFeedback | None, + Field( + description='Structured seller performance-feedback support beyond the legacy scalar contract. Presence means the seller accepts compact baseline/metric/provenance fields from a buyer orchestrator and returns a feedback_id.' + ), + ] = None + offline_delivery_protocols: Annotated[ + list[cloud_storage_protocol.CloudStorageProtocol] | None, + Field( + description="Cloud storage protocols this seller supports for offline file delivery. Only meaningful when reporting_delivery_methods includes 'offline'. Buyers express a protocol preference in sync_accounts; the seller provisions the account's reporting_bucket using a supported protocol.", + min_length=1, + ), + ] = None + reporting_delivery: Annotated[ + reporting_delivery_capabilities.ReportingDeliveryCapabilities | None, + Field( + description='AdCP 3.2 Reliable Reporting capability. The affirmative machine answer to ‘Do you support Reliable Reporting?’ requires this block with supported: true and reliable_reporting_version: 1.0 plus media_buy.reporting_delivery in experimental_features. Core exposes seller get_reporting_status; during the published migration window, consumer_status_task separately advertises opt-in buyer-to-seller sync_reporting_status and becomes required Core only in the next eligible minor. managed_delivery and reconciled_billing identify optional tiers. This generalizes, but does not remove, the legacy reporting_delivery_methods/offline_delivery_protocols surface; those legacy fields and declarations from other protocols do not imply Reliable Reporting support.' + ), + ] = None + supports_proposals: Annotated[ + StrictBool | None, + Field( + description='Conformance declaration that this seller supports proposals through either the compact request/refine/finalize lifecycle or the legacy get_products facade. accept_proposal, or the create_media_buy compatibility facade, consumes a finalized committed proposal_id before expires_at.' + ), + ] = False + outcome_target: Annotated[ + StrictBool | None, + Field( + description="Whether this seller supports reverse-forecast planning: parsing criteria.outcome_target (a compact goal — a forecastable-metric delivery metric or an event-type conversion event — plus a desired volume, a cost_per target, or both) and planning against it, answering with total_budget_guidance and forecasts whose points carry the goal's key in metrics; see outcome-target.json for cost_per answers and rejections. This flag does not distinguish sellers that plan cost_per; buyers rely on the negotiated adcp_version together with features.bidding_policy. false or absent means support is unknown: buyers SHOULD express outcome goals in brief prose instead, and sellers reject a structured outcome_target on proposal requests with UNSUPPORTED_FEATURE rather than silently ignoring it (list_products ignores it for every seller)." + ), + ] = False + governance_aware: Annotated[ + StrictBool | None, + Field( + description='Compatibility claim used by existing media-buy conformance runners. A value of true corresponds only to online governance consultation for create_media_buy, the historically graded surface. Agents use adcp.governance_enforcement for explicit task-scoped claims, including update_media_buy and cross-role signed-context enforcement.' + ), + ] = False + propagation_surfaces: Annotated[ + list[PropagationSurface] | None, + Field( + description='Where this seller surfaces dependency-resource impairments (creative suspended/rejected post-approval, audience suspended, catalog item withdrawn, event source insufficient, property depublished) to buyers. Non-exclusive: a seller mirroring impairments on both the buy snapshot AND firing webhooks declares `["snapshot", "webhook"]` (the common case for premium guaranteed sellers). Each value names one surface where buyers can observe an impairment:\n\n- **`snapshot`** — seller propagates resource transitions into `media_buy.health` and `media_buy.impairments[]` on the next `get_media_buys` read. The `impairment.coherence` compliance assertion grades this surface; storyboards that exercise it (`media_buy_seller/dependency_impairment`, `media_buy_seller/dependency_impairment_cardinality`) require `"snapshot"` to be declared, else they grade `not_applicable`.\n- **`webhook`** — seller fires `notification-type: impairment` webhooks (configured via `push_notification_config`). Sellers declaring `"webhook"` MUST satisfy the persistent-channel webhook contract for the impairment event type. A seller declaring `["webhook"]` without `"snapshot"` is webhook-only — buyers reconcile state from the push channel alone, and snapshot-coherence storyboards grade `not_applicable`.\n- **`out_of_band`** — seller propagates via channels outside the AdCP protocol surface entirely (email to trafficker, separate dashboard, partner-specific notification feed). Long-tail and enterprise-bundled platforms commonly use this when impairment workflows are managed in human channels. Sellers declaring only `["out_of_band"]` are not graded by snapshot or webhook compliance — their bar is the offline agreement, not a protocol assertion. If a seller has impairment data in their API under a non-AdCP field name (a mapping gap, not truly out-of-band), they SHOULD document the mapping rather than declare `out_of_band` — the spec\'s gap, not the seller\'s posture, is what `out_of_band` legitimately covers.\n\nDefault: `["snapshot"]` when absent (preserves the existing snapshot-coherence contract for sellers that don\'t declare). Empty array `[]` is invalid (`minItems: 1`) — omit the field to inherit the default rather than declaring no surfaces. Pick the surfaces that honestly describe where buyers will see impairments on this agent. Mixing is normative — `["snapshot", "webhook"]` is the documented common case; `["snapshot", "webhook", "out_of_band"]` is valid for sellers that ship all three surfaces (rare but legal). See lifecycle.mdx § Compliance for the per-surface contract.', + min_length=1, + ), + ] = [PropagationSurface.snapshot] + creative_approval_mode: Annotated[ + CreativeApprovalMode | None, + Field( + description="Tenant-wide applicability signal for media-buy creative approval behavior. This is not a notification or new approval workflow. `auto_approve` means human review does not block serving eligibility after creatives are assigned and automated validation passes. `require_human` means one or more products/accounts may require manual review before creatives become eligible to serve; buyers and compliance runners MUST treat this as a worst-case ceiling across this seller's portfolio unless a future product-level override says otherwise. Compliance runners use this mainly to decide whether auto-approval-dependent storyboards apply. When absent, approval behavior is legacy-unspecified; runners SHOULD NOT treat omission as an affirmative auto-approval claim. `ai_assisted` is intentionally not part of the enum until a behavioral contract is defined." + ), + ] = None + supported_indicator_types: Annotated[ + list[indicator_type.IndicatorType] | None, + Field( + description="Indicator types this seller can expose on get_media_buys media-buy/package/creative-assignment snapshots. Each type's meaning is defined by the negotiated AdCP release; indicator types do not carry independent sub-versions. This is availability, not complete upstream coverage. Poll-only sellers may declare this field without relationship_notifications. If relationship_notifications includes indicators.changed, this field is required so receivers know which durable indicator types can be repaired.", + min_length=1, + ), + ] = None + relationship_notifications: Annotated[ + RelationshipNotifications | None, + Field( + description='Optional durable account-level invalidations for indicator, creative-assignment, and assignment-approval changes. A seller may expose indicators only through polling and omit this block. A seller without an indicator catalog may declare creative.assignment_changed alone. get_media_buys is the complete authoritative repair read. creative.assignment_changed is independent of the optional bounded list_creatives reverse projection, so inline-only sellers can advertise approval and assignment invalidations. Presence means the seller accepts the declared subscriptions through sync_accounts notification_configs. Timestamp-only reevaluation does not fire. Poll-based upstream integrations fire when they detect a change; this declaration does not promise upstream detection latency.' + ), + ] = None + features: media_buy_features.MediaBuyFeatures | None = None + execution: Annotated[ + Execution | None, Field(description='Technical execution capabilities for media buying') + ] = None + audience_evidence: Annotated[ + AudienceEvidence | None, + Field( + description='Support for structured product audience evidence and buyer-authored evidence policy. Presence means the seller can publish Product.audience_evidence, preserve immutable snapshots through package readback, and evaluate the declared policy modes. It does not declare audience-targeting capability.' + ), + ] = None + rights_attestations: Annotated[ + RightsAttestations | None, + Field( + description='Seller evaluation policy for portable rights-grant attestations carried on creative rights constraints. Presence requires adcp.attestations and the rights-grant claim URI in accepted_claim_types. The seller remains verifier-of-record and never treats verification_url or a buyer-authored evaluation as authorization.' + ), + ] = None + audience_targeting: Annotated[ + AudienceTargeting | None, + Field( + description='Audience targeting capabilities. Presence of this object indicates the seller supports audience targeting, including sync_audiences and audience_include/audience_exclude in targeting overlays.' + ), + ] = None + supported_optimization_metrics: Annotated[ + list[SupportedOptimizationMetric] | None, + Field( + description='Optimization metrics this seller can support on at least one of their products. Seller-level rollup of product-level metric_optimization.supported_metrics declarations (core/product.json). Buyers SHOULD filter their requested optimization goals against this list before submitting briefs. Sellers MUST keep this in sync with their product catalog — if no products support a metric, it must not appear here. Omitting this field means the seller declares no specific guarantees about which metrics they support; buyers should fall back to per-product inspection of metric_optimization.supported_metrics.', + min_length=1, + ), + ] = None + vendor_metric_optimization: Annotated[ + VendorMetricOptimization | None, + Field( + description='Seller-level rollup of vendor-metric optimization capabilities supported by at least one product. Product-level vendor_metric_optimization.supported_metrics[] remains authoritative for the specific (vendor, metric_id) pairs and target kinds a buyer may bind on a package; this seller-level object exists so buyers and compliance runners can discover whether vendor_metric goals are in scope before walking the catalog. Sellers MUST keep this in sync with product-level vendor_metric_optimization declarations.' + ), + ] = None + conversion_tracking: Annotated[ + ConversionTracking | None, + Field( + description='Seller-level conversion tracking capabilities. Presence of this object indicates the seller supports sync_event_sources and log_event for conversion event tracking.' + ), + ] = None + frequency_capping: Annotated[ + FrequencyCapping | None, + Field( + description='Seller-wide package frequency-capping infrastructure. Presence means the seller honors targeting_overlay.frequency_cap on packages, with an independent counter per package, and MUST reject caps it cannot enforce rather than silently dropping them. Product.overlay_support is the binding per-product declaration. A cap whose single counter spans every package in a MediaBuy is advertised separately through aggregate_frequency_capping.' + ), + ] = None + aggregate_frequency_capping: Annotated[ + media_buy_frequency_cap_capability.MediaBuyFrequencyCapCapability | None, + Field( + description='Seller-wide support for a maximum-impression cap whose single counter is shared across every package in a MediaBuy. Presence is required before a buyer sends a root frequency_cap. Products additionally declare media_buy_support.frequency_cap and any narrower constraints.' + ), + ] = None + budget_capping: Annotated[ + BudgetCapping | None, + Field( + description='Hard daily budget-cap capabilities. Presence declares only the scopes listed in supported_scopes; sellers MUST reject a daily_budget_cap at an undeclared scope with UNSUPPORTED_FEATURE before mutation and MUST NOT silently drop or soften it. A cap is always a hard ceiling. Sellers that offer a best-effort pacing target must expose that under a separately named feature rather than interpreting daily_budget_cap as soft. Daily caps are orthogonal to pacing.' + ), + ] = None + content_standards: Annotated[ + ContentStandards | None, + Field( + description='Content standards implementation details. Presence of this object indicates the seller supports content_standards configuration including sampling rates and category filtering. Gives buyers pre-buy visibility into local evaluation and artifact delivery capabilities. This is a seller-side media-buy capability; governance agents providing content standards services declare `specialisms: ["content-standards"]` instead.' + ), + ] = None + portfolio: Annotated[ + Portfolio | None, + Field( + description="Information about the seller's media inventory portfolio. Media-buy sellers SHOULD publish primary_channels and primary_countries as their complete brief-routing scope. Buyers use publisher_domains to verify authorization via adagents.json. Omitted routing arrays mean unknown scope and MUST NOT be interpreted as global coverage." + ), + ] = None + + +class Adcp(AdCPBaseModel): + major_versions: Annotated[ + list[MajorVersion], + Field( + deprecated=True, + description='DEPRECATED in favor of `supported_versions` (release-precision strings). Servers MUST continue to emit this field through 3.x for backwards compatibility. Removed in 4.0. Original semantics: AdCP major versions supported by this seller. Major versions indicate breaking changes.', + min_length=1, + ), + ] + supported_versions: Annotated[ + list[SupportedVersion] | None, + Field( + description='Release-precision (VERSION.RELEASE) AdCP versions this seller speaks. Authoritative for buyer-side release pinning — buyers SHOULD declare `adcp_version` (release-precision string) on each request. Sellers downshift to the highest supported release ≤ the buyer\'s pin within the same major; cross-major mismatch returns VERSION_UNSUPPORTED. Pre-release tags (e.g. `"3.1-beta"`) hang off release.', + examples=[['3.0'], ['3.0', '3.1'], ['3.0', '3.1-beta']], + min_length=1, + ), + ] = None + build_version: Annotated[ + str | None, + Field( + description="Optional advisory metadata: full semver build identifier of the seller's deployment — MAJOR.MINOR.PATCH plus optional pre-release and build-metadata segments per semver §9–§10. Patches are not part of the wire contract — semver patch by definition introduces no contract change — but surfacing the build helps buyers triage incidents and bug reports against a specific seller deployment lineage. Buyers MUST NOT use this field for negotiation; use `supported_versions` (release-precision) instead.", + examples=[ + '3.0.1', + '3.1.2', + '3.1.0-beta.3', + '3.1.2+scope3.deploy.4821', + '3.1.0-beta.3+sha.a1b2c3d', + ], + pattern='^\\d+\\.\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?(\\+[a-zA-Z0-9.-]+)?$', + ), + ] = None + idempotency: Annotated[ + Idempotency | Idempotency1, + Field( + description='Idempotency semantics for mutating requests. Sellers MUST declare whether they honor idempotency_key replay protection so buyers can reason about safe retry behavior. Modeled as a discriminated union on the supported boolean so that code generators produce two named types (IdempotencySupported, IdempotencyUnsupported) with the replay_ttl_seconds invariant enforced at the type level — draft-07 if/then would be dropped by most generators (openapi-typescript, zod-to-json-schema, datamodel-code-generator pre-0.25, quicktype). Clients MUST NOT assume a default — a seller without this declaration is non-compliant and should be treated as unsafe for retry-sensitive operations.' + ), + ] + principal: Annotated[ + Principal | None, + Field( + description='Caller-scoped durable connection configuration accepted by this agent. This is the buyer-to-seller configuration half of negotiation, not a second seller capability document: the seller advertises its objective offering here, while each authenticated caller submits its desired webhooks and reusable destinations through sync_principal and reads them back through get_principal. Per-account authority and feed selection remain in account/reporting configuration. Sellers exposing this block MUST list protocol.principal in experimental_features.' + ), + ] = None + capability_changes: Annotated[ + CapabilityChanges | None, + Field( + description='Freshness metadata and optional invalidation webhooks for this `get_adcp_capabilities` document. Buyers and registries MAY cache capabilities for up to `cache_ttl_seconds` when present, SHOULD compare `capabilities_version` across refreshes when present, and SHOULD re-run `get_adcp_capabilities` after receiving a `capabilities.changed` webhook. This block describes the agent-wide capability document, not per-caller authorization or account-scoped settings. A material capability change is any externally advertised contract change that can affect routing, validation, conformance coverage, task availability, auth/account handling, sandbox support, billing support, reporting delivery methods, creative-library support, targeting support, protocol versions, or other buyer-visible feature gates. Non-contract operational changes that do not alter the response body do not require a revision or webhook fire.' + ), + ] = None + governance_enforcement: Annotated[ + GovernanceEnforcement | None, + Field( + description='Cross-role declaration that this agent enforces buyer-provided governance authorization when performing consequential tasks. This is distinct from the top-level `governance` capability block, which describes an agent that provides governance services. An agent can enforce governance without implementing the governance protocol itself. Absence means the agent makes no governance-enforcement conformance claim.' + ), + ] = None + attestations: Annotated[ + attestation_capabilities.AttestationCapabilities | None, + Field( + description='Portable-attestation trust and delivery capabilities for this evaluator. Present only when the agent accepts AttestationReference inputs on one or more domain task surfaces. This block is an allowlist: presenters cannot expand accepted issuers, resolver endpoints, verifier agents, claim types, or proof formats by supplying values in a request.' + ), + ] = None + -# Explicit exports -__all__ = [ - "AcceptancePolicyDiscovery", - "AccessMode", - "Account", - "Accreditation", - "Adcp", - "AgeRestriction", - "Algorithm", - "AttributionWindow", - "AudienceEvidence", - "AudienceTargeting", - "Brand", - "BudgetCapping", - "BudgetCappingTimezoneBasis", - "BuyingMode", - "CapabilityChanges", - "CapabilityReportingDeliveryMethod", - "ChangeFeed", - "ChangeFeed1", - "ComplianceTesting", - "CompromiseNotification", - "ContentStandards", - "ConversionTracking", - "CoversContentDigest", - "Creative", - "CreativeApprovalMode", - "CreativeFeature", - "CreativeSpecs", - "DataProviderDomain", - "DefaultProfileId", - "Demographics", - "DiscoveryMode", - "Endpoint", - "EventType", - "EventType3", - "Execution", - "ExtensionsSupportedItem", - "Features", - "Format", - "FrequencyCapping", - "GeoMetros", - "GeoProximity", - "GetAdcpCapabilitiesResponse", - "Governance", - "GovernanceEnforcement", - "Idempotency", - "Idempotency1", - "Identity", - "IdentityUpdates", - "IdentityUpdates1", - "KeyOrigins", - "KeywordTargets", - "Language", - "LifecycleTool", - "Localization", - "MajorVersion", - "MatchingLatencyHours", - "Measurement", - "MeasurementGateway", - "MediaBuy", - "Metric", - "Mode", - "MraidVersion", - "Multiplicity", - "NegativeKeywords", - "Notifications", - "Notifications1", - "Notifications2", - "Notifications3", - "Oauth", - "Operation", - "Pattern", - "PerformanceFeedback", - "Portfolio", - "Preview", - "PrimaryCountry", - "Principal", - "PropagationSurface", - "PropertyFeature", - "ProposalRefinement", - "ProtocolMethodsRequiredForItem", - "ProtocolMethodsSupportedForItem", - "ProtocolMethodsWarnForItem", - "PublisherDomain", - "Range", - "RegistrationTask", - "RelationshipNotifications", - "RenderingOrigin", - "ReportingDestinationOffering", - "RepresentationResolution", - "RequestSigning", - "RequiredForItem", - "Requirement", - "ResourceType", - "RightsAttestations", - "Route", - "RuntimeAttestations", - "SignalActivation", - "Signals", - "SponsoredIntelligence", - "SupportedChange", - "SupportedDimension", - "SupportedForItem", - "SupportedFormat", - "SupportedIdentifierType", - "SupportedOptimizationMetric", - "SupportedPresenceMode", - "SupportedProtocol", - "SupportedRequirementMode", - "SupportedScope", - "SupportedSection", - "SupportedTarget", - "SupportedTarget1", - "SupportedVersion", - "Targeting", - "Tasks", - "Tasks1", - "Tasks2", - "Tasks3", - "Tasks4", - "Tasks5", - "Tasks6", - "Tasks7", - "Tasks8", - "Tasks9", - "Transport", - "Transport1", - "TrustedMatch", - "Type", - "Type6", - "VariantDimension", - "VastValidation", - "VendorMetricOptimization", - "WarnForItem", - "WebhookSigning", - "WholesaleFeedVersioning", - "WholesaleFeedWebhooks", -] +class GetAdcpCapabilitiesResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + adcp: Annotated[Adcp, Field(description='Core AdCP protocol information')] + supported_protocols: Annotated[ + list[SupportedProtocol], + Field( + description='AdCP protocols this agent supports. Stable values both (a) declare which tools the agent implements and (b) commit the agent to pass the baseline compliance storyboard at /compliance/{version}/protocols/{protocol}/ (with snake_case → kebab-case path mapping, e.g. media_buy → /compliance/.../protocols/media-buy/). The `measurement` protocol is experimental and currently covers provider catalog/output declaration (`measurement.core`) and buyer-orchestrator interchange gateways (`measurement.gateway`). Measurement agents exchange delivery and feedback with the gateway rather than receiving direct seller access. Additional provider tasks and a baseline storyboard land only when concrete workflows require them. Compliance testing support is declared separately via the `compliance_testing` capability block (below), not as a protocol claim.', + min_length=1, + ), + ] + account: Annotated[ + Account | None, + Field( + description='Account management capabilities. Describes how accounts are established, what billing models are supported, and whether an account is required before browsing products.' + ), + ] = None + media_buy: Annotated[ + MediaBuy | None, + Field( + description='Media-buy protocol capabilities. Expected when media_buy is in supported_protocols. Sellers declaring media_buy should also include account with supported_billing.' + ), + ] = None + signals: Annotated[ + Signals | None, + Field( + description='Signals protocol capabilities. Only present if signals is in supported_protocols.' + ), + ] = None + governance: Annotated[ + Governance | None, + Field( + description='Governance protocol capabilities. Only present if governance is in supported_protocols. Governance agents provide property and creative data like compliance scores, brand safety ratings, sustainability metrics, and creative quality assessments.' + ), + ] = None + sponsored_intelligence: Annotated[ + SponsoredIntelligence | None, + Field( + description='Sponsored Intelligence protocol capabilities. Only present if sponsored_intelligence is in supported_protocols. SI agents handle conversational brand experiences.' + ), + ] = None + brand: Annotated[ + Brand | None, + Field( + description='Brand protocol capabilities. Only present if brand is in supported_protocols. Brand agents provide identity data (logos, colors, tone, assets) and optionally rights clearance for licensable content (talent, music, stock media).' + ), + ] = None + creative: Annotated[ + Creative | None, + Field( + description='Creative protocol capabilities. Only present if creative is in supported_protocols.' + ), + ] = None + oauth: Annotated[ + Oauth | None, + Field( + description='Introduced in AdCP 3.2. OAuth 2.0 protected-resource support for inbound transport authentication. This is a capability claim, not a requirement that every caller use OAuth: `supported: true` means the agent publishes RFC 9728 protected-resource metadata for its advertised endpoint and RFC 8414 metadata for every referenced authorization server, and opts into the universal `oauth_setup` conformance storyboard. Agents that use only static Bearer, Basic, mTLS, or RFC 9421 authentication omit this block or declare `supported: false`. Operator credential acquisition remains separately described by `account.authorization_endpoint` when applicable.' + ), + ] = None + request_signing: Annotated[ + RequestSigning | None, + Field( + description='RFC 9421 HTTP Signatures support for incoming requests. Signing remains optional through 3.2, but every accepted 3.2 signature on a request with a body MUST cover content-digest. Request signing becomes required for spend-committing operations in 4.0. The full profile is defined in docs/building/by-layer/L1/security.mdx (Signed Requests (Transport Layer)).' + ), + ] = None + webhook_signing: Annotated[ + WebhookSigning | None, + Field( + description='RFC 9421 webhook-signature and delivery-retry support for outbound webhook callbacks (top-level peer of request_signing). Declares which AdCP webhook-signing profile version and algorithms this agent produces on delivery, whether it supports the legacy HMAC-SHA256 fallback for receivers that have not yet adopted RFC 9421, and the maximum retry horizon receivers use to retain immutable delivery evidence. See docs/building/by-layer/L3/webhooks.mdx.' + ), + ] = None + identity: Annotated[ + Identity | None, + Field( + description='Operator identity posture — trust-root pointer (`brand_json_url`) plus key-scoping and compromise-response controls the agent operates. `brand_json_url` is **load-bearing** for signature verification: when the agent declares any signing posture (`request_signing.supported_for`/`required_for` non-empty, `webhook_signing.supported === true`, or any `key_origins` subfield), `brand_json_url` MUST be present (storyboard-enforced in 3.x; schema-required in 4.0). Verifiers use it to bootstrap from the agent URL to the operator\'s brand.json (and from there to signing keys); see [security.mdx §Discovering an agent\'s signing keys](https://adcontextprotocol.org/docs/building/by-layer/L1/security#discovering-an-agents-signing-keys-via-brand_json_url). The remaining fields (`per_principal_key_isolation`, `key_origins`, `compromise_notification`) are advisory and receivers use them to reason about blast radius and revocation latency at onboarding. Empty-object semantics: `identity: {}` means "posture block present but no posture claimed" — schema-valid but advisory-neutral and receivers MUST treat it as equivalent to omitting the block, **except** that an agent declaring a signing posture elsewhere in the response with an empty `identity` MUST be rejected by storyboard runners as missing `brand_json_url`.' + ), + ] = None + measurement_gateway: Annotated[ + MeasurementGateway | None, + Field( + description='Buyer-controlled task gateway between an orchestrator and measurement providers. In the first experimental tier, providers read buyer-approved cross-seller delivery through get_media_buy_delivery and return compact assertions through provide_performance_feedback, without receiving seller credentials. Orchestrators implementing this block MUST include measurement in supported_protocols and measurement.gateway in experimental_features; this gateway role does not claim the media_buy seller protocol.' + ), + ] = None + measurement: Annotated[ + Measurement | None, + Field( + description="Experimental measurement capability block. Presence indicates this agent computes one or more quantitative metrics about ad delivery, exposure, or effect, and is willing to be discovered as a measurement vendor. Agents implementing this block MUST list `measurement.core` in experimental_features. Returns metric definitions and whether the provider produces compact performance feedback, not pricing/coverage (negotiated via `measurement_terms` on `create_media_buy`) or raw/live datasets. Per-buy vendor values remain on delivery reports; optimizer-ready projections use provide_performance_feedback. AgenticAdvertising.org crawls each measurement agent's `metrics[]` on a TTL to populate the federated cross-vendor index." + ), + ] = None + compliance_testing: Annotated[ + ComplianceTesting | None, + Field( + description="Compliance testing capabilities. The presence of this block declares that the agent supports deterministic testing via comply_test_controller for lifecycle state machine validation. Omit the block entirely if the agent does not support compliance testing. Sellers SHOULD list every canonical controller scenario they implement so buyers and runners can distinguish full deterministic coverage from partial coverage without probing each scenario one by one; the runtime source of truth remains comply_test_controller with scenario: 'list_scenarios'." + ), + ] = None + specialisms: Annotated[ + list[specialism.AdcpSpecialism] | None, + Field( + description="Optional — specialized compliance claims this agent supports. Values MUST be kebab-case enum IDs (e.g., 'creative-generative', 'sales-non-guaranteed'). An agent that implements a specialism's tools but omits its ID from this array will receive 'No applicable tracks found' from the compliance runner — tracks for that specialism are not evaluated even if every tool works. Omitting the field means the agent declares no specialism claims (it still passes the universal + domain-baseline storyboards implied by supported_protocols). Each specialism maps to a storyboard bundle at /compliance/{version}/specialisms/{id}/ that the AAO compliance runner executes to verify the claim. Each specialism rolls up to one of the protocols in supported_protocols — the runner rejects a specialism claim whose parent protocol is missing. Only list specialisms your agent actually implements — the AAO Verified badge enumerates which specialisms were demonstrably passed." + ), + ] = None + extensions_supported: Annotated[ + list[ExtensionsSupportedItem] | None, + Field( + description='Extension namespaces this agent supports. Buyers can expect meaningful data in ext.{namespace} fields on responses from this agent. Extension schemas are published in the AdCP extension registry.' + ), + ] = None + experimental_features: Annotated[ + list[experimental_feature_id.ExperimentalFeatureId] | None, + Field( + description='Experimental AdCP surfaces this agent implements. A surface is experimental when its schema carries x-status: experimental and the working group has not yet frozen it. Sellers that implement any experimental surface MUST list its feature id here. Buyers inspect this array before relying on experimental surfaces — a seller that does not list a surface is asserting it does not implement it. Experimental surfaces MAY break between any two 3.x releases with at least 6 weeks notice; the full contract is in docs/reference/experimental-status.' + ), + ] = None + wholesale_feed_versioning: Annotated[ + WholesaleFeedVersioning | None, + Field( + description="Conditional-fetch token capabilities for get_products and get_signals. Independent of wholesale feed webhooks: an agent MAY support cheap version probes via if_wholesale_feed_version without pushing change payloads (and vice versa). When supported is true, the agent returns wholesale_feed_version on every get_products / get_signals response and honors if_wholesale_feed_version on subsequent requests. When absent or supported is false, callers MAY still send if_wholesale_feed_version — pre-3.1 agents that ignore it just return the full payload (correct, just inefficient). Pre-flight declaration here lets buyers fast-path which agents to bother caching versions for. See get_products / get_signals 'Wholesale feed versioning' sections." + ), + ] = None + last_updated: Annotated[ + AwareDatetime | None, + Field( + description='ISO 8601 timestamp of when capabilities were last updated. Buyers can use this for cache invalidation.' + ), + ] = None + errors: Annotated[ + list[error.Error] | None, Field(description='Task-specific errors and warnings') + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + wholesale_feed_webhooks: Annotated[ + WholesaleFeedWebhooks | None, + Field( + description='Per-agent wholesale product-feed and wholesale signals-feed webhook capabilities. Consumers register durable sync_accounts notification subscribers and receive actual product.*, signal.*, or wholesale_feed.bulk_change payloads without polling. Product mirrors bootstrap and repair through list_products(if_feed_version); signal mirrors use get_signals(if_wholesale_feed_version). Deprecated wholesale get_products remains the 3.x product compatibility path. Webhook emission MUST apply the same caller/account authorization and cache-scope predicate as the corresponding read.' + ), + ] = None diff --git a/src/adcp/types/domains/protocol/get_principal_request.py b/src/adcp/types/domains/protocol/get_principal_request.py index d83f5ddb1..68750f185 100644 --- a/src/adcp/types/domains/protocol/get_principal_request.py +++ b/src/adcp/types/domains/protocol/get_principal_request.py @@ -1,18 +1,21 @@ -"""Types declared by the AdCP ``protocol/get_principal_request`` schema. +# generated by datamodel-codegen: +# filename: protocol/get_principal_request.json +# timestamp: 2026-10-04T02:05:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.protocol.get_principal_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from pydantic import ConfigDict -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope -from adcp.types.generated_poc.protocol.get_principal_request import GetPrincipalRequest -# Explicit exports -__all__ = ["GetPrincipalRequest"] +class GetPrincipalRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/protocol/get_principal_response.py b/src/adcp/types/domains/protocol/get_principal_response.py index f204a623f..df22a70a3 100644 --- a/src/adcp/types/domains/protocol/get_principal_response.py +++ b/src/adcp/types/domains/protocol/get_principal_response.py @@ -1,38 +1,102 @@ -"""Types declared by the AdCP ``protocol/get_principal_response`` schema. +# generated by datamodel-codegen: +# filename: protocol/get_principal_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.protocol.get_principal_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-04 01:57:57 UTC -""" +from typing import Annotated, Literal + +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field + +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 +from ..core import principal_state +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import principal_kind as principal_kind_1 + + +class Result(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['unconfigured'] = 'unconfigured' + + +class Result9(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['failed'] = 'failed' + errors: Annotated[list[error.Error], Field(max_length=16, min_length=1)] + + +class Result7(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['recognized'] = 'recognized' + principal_id: Annotated[ + str, + Field( + description='Seller-issued opaque identifier for the existing durable principal record. The read returns the same identifier after credential renewal or rotation when the new credential maps to this principal. It is not a credential and does not grant authority over an advertiser account.', + max_length=255, + min_length=1, + ), + ] + principal_kind: Annotated[ + principal_kind_1.PrincipalKind, + Field( + description='Seller-resolved party kind of the authenticated principal. This is resolved from authenticated transport and authorization state, never request content.' + ), + ] + + +class Result6(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['current'] = 'current' + principal_id: Annotated[ + str, + Field( + description='Seller-issued opaque identifier for this authenticated principal record. It is response-only, not a credential, not caller identity, and not advertiser-account authority.', + max_length=255, + min_length=1, + ), + ] + principal_kind: Annotated[ + principal_kind_1.PrincipalKind, + Field( + description="Seller-resolved party kind of the authenticated principal: a buyer-agent workload, or an operator-side identity such as a person at the operator. Resolved solely from the seller's authorization system, never from request content, so per-party policy such as billing gates can rely on it." + ), + ] + configuration_version: Annotated[ + str, + Field( + description='Opaque version of the persisted configuration. Compare only for equality and pass it as expected_configuration_version on a later guarded sync_principal replacement.', + max_length=255, + min_length=1, + ), + ] + configuration: principal_state.PrincipalState + + +class GetPrincipalResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + result: Result6 | Result7 | Result | Result9 + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.protocol.get_principal_response import ( - GetPrincipalResponse, - PrincipalCurrentResult, - PrincipalReadFailedResult, - PrincipalRecognizedResult, - PrincipalUnconfiguredResult, - Result, - Result6, - Result7, - Result9, -) - -# Explicit exports -__all__ = [ - "GetPrincipalResponse", - "PrincipalCurrentResult", - "PrincipalReadFailedResult", - "PrincipalRecognizedResult", - "PrincipalUnconfiguredResult", - "Result", - "Result6", - "Result7", - "Result9", -] +# Stable aliases for anonymous result arms (selected by discriminator). +PrincipalUnconfiguredResult = Result +PrincipalCurrentResult = Result6 +PrincipalRecognizedResult = Result7 +PrincipalReadFailedResult = Result9 diff --git a/src/adcp/types/domains/protocol/get_task_status_request.py b/src/adcp/types/domains/protocol/get_task_status_request.py index a3bd7de1c..4ff18133b 100644 --- a/src/adcp/types/domains/protocol/get_task_status_request.py +++ b/src/adcp/types/domains/protocol/get_task_status_request.py @@ -1,18 +1,43 @@ -"""Types declared by the AdCP ``protocol/get_task_status_request`` schema. +# generated by datamodel-codegen: +# filename: protocol/get_task_status_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.protocol.get_task_status_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field, StrictBool + +from ..core import account_ref +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope -from adcp.types.generated_poc.protocol.get_task_status_request import GetTaskStatusRequest -# Explicit exports -__all__ = ["GetTaskStatusRequest"] +class GetTaskStatusRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + task_id: Annotated[str, Field(description='Unique identifier of the task to retrieve')] + account: Annotated[ + account_ref.AccountReference | None, + Field( + description='Account scope for the task lookup. Sellers MUST return REFERENCE_NOT_FOUND for a task_id that exists only under a different account or principal. When omitted, the seller MAY use the credential-bound singleton account, but multi-account credentials SHOULD require an explicit account.' + ), + ] = None + include_history: Annotated[ + StrictBool | None, + Field( + description='Include full conversation history for this task (may increase response size)' + ), + ] = False + include_result: Annotated[ + StrictBool | None, + Field( + description="Include the task's canonical terminal result payload when one exists. Defaults to false for lightweight status-only polls. When true, sellers MUST include result for completed, failed, or rejected terminal tasks when that task produced a terminal artifact; canceled tasks may have no result. The legacy singular error field remains a convenience for failed tasks but does not replace the canonical terminal result." + ), + ] = False + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/protocol/get_task_status_response.py b/src/adcp/types/domains/protocol/get_task_status_response.py index f2d36739c..596218463 100644 --- a/src/adcp/types/domains/protocol/get_task_status_response.py +++ b/src/adcp/types/domains/protocol/get_task_status_response.py @@ -1,25 +1,124 @@ -"""Types declared by the AdCP ``protocol/get_task_status_response`` schema. +# generated by datamodel-codegen: +# filename: protocol/get_task_status_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.protocol.get_task_status_response import +from adcp.types.base import AdcpResponse, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any + +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat + +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import adcp_protocol, task_status +from ..enums import task_type as task_type_1 + + +class Progress(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + percentage: Annotated[ + StrictFloat | None, Field(description='Completion percentage (0-100)', ge=0.0, le=100.0) + ] = None + current_step: Annotated[ + str | None, Field(description='Current step or phase of the operation') + ] = None + total_steps: Annotated[ + SchemaInt | None, Field(description='Total number of steps in the operation', ge=1) + ] = None + step_number: Annotated[SchemaInt | None, Field(description='Current step number', ge=1)] = None + + +class Details(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + protocol: Annotated[ + adcp_protocol.AdcpProtocol | None, Field(description='AdCP protocol where error occurred') + ] = None + operation: Annotated[str | None, Field(description='Specific operation that failed')] = None + specific_context: Annotated[ + dict[str, Any] | None, Field(description='Domain-specific error context') + ] = None + + +class Error(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + code: Annotated[str, Field(description='Error code for programmatic handling')] + message: Annotated[str, Field(description='Detailed error message')] + details: Annotated[Details | None, Field(description='Additional error context')] = None + + +class Type(StrEnum): + request = 'request' + response = 'response' + + +class HistoryItem(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + timestamp: Annotated[AwareDatetime, Field(description='When this exchange occurred (ISO 8601)')] + type: Annotated[ + Type, Field(description='Whether this was a request from client or response from server') + ] + data: Annotated[dict[str, Any], Field(description='The full request or response payload')] -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.protocol.get_task_status_response import ( - Details, - Error, - GetTaskStatusResponse, - HistoryItem, - Progress, - Type, -) - -# Explicit exports -__all__ = ["Details", "Error", "GetTaskStatusResponse", "HistoryItem", "Progress", "Type"] +class GetTaskStatusResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + task_id: Annotated[str, Field(description='Unique identifier for this task')] + task_type: Annotated[task_type_1.TaskType, Field(description='Type of AdCP operation')] + protocol: Annotated[ + adcp_protocol.AdcpProtocol, Field(description='AdCP protocol this task belongs to') + ] + status: Annotated[task_status.TaskStatus, Field(description='Current task status')] + created_at: Annotated[ + AwareDatetime, Field(description='When the task was initially created (ISO 8601)') + ] + updated_at: Annotated[ + AwareDatetime, Field(description='When the task was last updated (ISO 8601)') + ] + completed_at: Annotated[ + AwareDatetime | None, + Field( + description='When the task completed (ISO 8601, only for completed/failed/canceled tasks)' + ), + ] = None + has_webhook: Annotated[ + StrictBool | None, Field(description='Whether this task has webhook configuration') + ] = None + progress: Annotated[ + Progress | None, Field(description='Progress information for long-running tasks') + ] = None + error: Annotated[ + Error | None, + Field( + description='Convenience summary for failed tasks. When include_result was true and the canonical terminal result is also present, this error MUST agree with the canonical fatal error in result. A legacy poll carrying only this singular summary proves failure status but not equivalence to a richer terminal webhook artifact.' + ), + ] = None + history: Annotated[ + list[HistoryItem] | None, + Field( + description='Complete conversation history for this task (only included if include_history was true in request)' + ), + ] = None + result: Annotated[ + dict[str, Any] | None, + Field( + description='Canonical task-specific terminal payload. Present when include_result was true and a completed, failed, or rejected task produced a terminal artifact; canceled tasks may omit it. For failed tasks, the singular error field is a convenience summary and MUST agree with the canonical fatal error represented here. Consumers and sellers MUST resolve and validate the exact schema through manifest.task_result_resolution: use terminal_schema_overrides[task_type] when present, otherwise tools[task_type].response_schema. The polling envelope keeps this field generic so get_task_status does not embed every task response schema.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/protocol/list_tasks_request.py b/src/adcp/types/domains/protocol/list_tasks_request.py index 688535a56..b152c9939 100644 --- a/src/adcp/types/domains/protocol/list_tasks_request.py +++ b/src/adcp/types/domains/protocol/list_tasks_request.py @@ -1,23 +1,119 @@ -"""Types declared by the AdCP ``protocol/list_tasks_request`` schema. +# generated by datamodel-codegen: +# filename: protocol/list_tasks_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.protocol.list_tasks_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field, StrictBool + +from ..core import account_ref +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core import pagination_request +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import adcp_protocol, sort_direction, task_status +from ..enums import task_type as task_type_1 + + +class Field1(StrEnum): + created_at = 'created_at' + updated_at = 'updated_at' + status = 'status' + task_type = 'task_type' + protocol = 'protocol' + + +class Filters(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + protocol: Annotated[ + adcp_protocol.AdcpProtocol | None, Field(description='Filter by single AdCP protocol') + ] = None + protocols: Annotated[ + list[adcp_protocol.AdcpProtocol] | None, + Field(description='Filter by multiple AdCP protocols', min_length=1), + ] = None + status: Annotated[ + task_status.TaskStatus | None, Field(description='Filter by single task status') + ] = None + statuses: Annotated[ + list[task_status.TaskStatus] | None, + Field(description='Filter by multiple task statuses', min_length=1), + ] = None + task_type: Annotated[ + task_type_1.TaskType | None, Field(description='Filter by single task type') + ] = None + task_types: Annotated[ + list[task_type_1.TaskType] | None, + Field(description='Filter by multiple task types', min_length=1), + ] = None + created_after: Annotated[ + AwareDatetime | None, Field(description='Filter tasks created after this date (ISO 8601)') + ] = None + created_before: Annotated[ + AwareDatetime | None, Field(description='Filter tasks created before this date (ISO 8601)') + ] = None + updated_after: Annotated[ + AwareDatetime | None, + Field(description='Filter tasks last updated after this date (ISO 8601)'), + ] = None + updated_before: Annotated[ + AwareDatetime | None, + Field(description='Filter tasks last updated before this date (ISO 8601)'), + ] = None + task_ids: Annotated[ + list[str] | None, + Field(description='Filter by specific task IDs', max_length=100, min_length=1), + ] = None + context_contains: Annotated[ + str | None, + Field( + description='Filter tasks where context contains this text (searches media_buy_id, signal_id, etc.)' + ), + ] = None + has_webhook: Annotated[ + StrictBool | None, + Field(description='Filter tasks that have webhook configuration when true'), + ] = None + + +class Sort(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + field: Annotated[Field1 | None, Field(description='Field to sort by')] = Field1.created_at + direction: Annotated[ + sort_direction.SortDirection | None, Field(description='Sort direction') + ] = sort_direction.SortDirection.desc -from adcp.types.generated_poc.protocol.list_tasks_request import ( - Field1, - Filters, - ListTasksRequest, - Sort, -) -# Explicit exports -__all__ = ["Field1", "Filters", "ListTasksRequest", "Sort"] +class ListTasksRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + account: Annotated[ + account_ref.AccountReference | None, + Field( + description="Account scope for task reconciliation. Sellers MUST only return tasks created for the caller's authenticated account + principal pair. When omitted, the seller MAY use the credential-bound singleton account, but multi-account credentials SHOULD require an explicit account." + ), + ] = None + filters: Annotated[Filters | None, Field(description='Filter criteria for querying tasks')] = ( + None + ) + sort: Annotated[Sort | None, Field(description='Sorting parameters')] = None + pagination: pagination_request.PaginationRequest | None = None + include_history: Annotated[ + StrictBool | None, + Field( + description='Include full conversation history for each task (may significantly increase response size)' + ), + ] = False + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/protocol/list_tasks_response.py b/src/adcp/types/domains/protocol/list_tasks_response.py index 8ae40f009..53bcf2b51 100644 --- a/src/adcp/types/domains/protocol/list_tasks_response.py +++ b/src/adcp/types/domains/protocol/list_tasks_response.py @@ -1,34 +1,119 @@ -"""Types declared by the AdCP ``protocol/list_tasks_response`` schema. +# generated by datamodel-codegen: +# filename: protocol/list_tasks_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.protocol.list_tasks_response import +from adcp.types.base import AdcpResponse, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated + +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field, StrictBool + +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core import pagination_response +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import task_status +from ..enums import task_type as task_type_1 + + +class DomainBreakdown(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + media_buy: Annotated[ + SchemaInt | None, + Field(alias='media-buy', description='Number of media-buy tasks in results', ge=0), + ] = None + signals: Annotated[ + SchemaInt | None, Field(description='Number of signals tasks in results', ge=0) + ] = None + creative: Annotated[ + SchemaInt | None, Field(description='Number of creative tasks in results', ge=0) + ] = None + + +class Direction(StrEnum): + asc = 'asc' + desc = 'desc' + + +class SortApplied(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + field: str + direction: Direction + + +class QuerySummary(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + total_matching: Annotated[ + SchemaInt | None, + Field(description='Total number of tasks matching filters (across all pages)', ge=0), + ] = None + returned: Annotated[ + SchemaInt | None, Field(description='Number of tasks returned in this response', ge=0) + ] = None + domain_breakdown: Annotated[ + DomainBreakdown | None, Field(description='Count of tasks by domain') + ] = None + status_breakdown: Annotated[ + dict[str, SchemaInt] | None, Field(description='Count of tasks by status') + ] = None + filters_applied: Annotated[ + list[str] | None, Field(description='List of filters that were applied to the query') + ] = None + sort_applied: Annotated[ + SortApplied | None, Field(description='Sort order that was applied') + ] = None + + +class Domain(StrEnum): + media_buy = 'media-buy' + signals = 'signals' + creative = 'creative' + + +class Task(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + task_id: Annotated[str, Field(description='Unique identifier for this task')] + task_type: Annotated[task_type_1.TaskType, Field(description='Type of AdCP operation')] + domain: Annotated[Domain, Field(description='AdCP domain this task belongs to')] + status: Annotated[task_status.TaskStatus, Field(description='Current task status')] + created_at: Annotated[ + AwareDatetime, Field(description='When the task was initially created (ISO 8601)') + ] + updated_at: Annotated[ + AwareDatetime, Field(description='When the task was last updated (ISO 8601)') + ] + completed_at: Annotated[ + AwareDatetime | None, + Field( + description='When the task completed (ISO 8601, only for completed/failed/canceled tasks)' + ), + ] = None + has_webhook: Annotated[ + StrictBool | None, Field(description='Whether this task has webhook configuration') + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.protocol.list_tasks_response import ( - Direction, - Domain, - DomainBreakdown, - ListTasksResponse, - QuerySummary, - SortApplied, - Task, -) - -# Explicit exports -__all__ = [ - "Direction", - "Domain", - "DomainBreakdown", - "ListTasksResponse", - "QuerySummary", - "SortApplied", - "Task", -] +class ListTasksResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + query_summary: Annotated[ + QuerySummary, Field(description='Summary of the query that was executed') + ] + tasks: Annotated[list[Task], Field(description='Array of tasks matching the query criteria')] + pagination: pagination_response.PaginationResponse + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/protocol/sync_agent_notification_configs_request.py b/src/adcp/types/domains/protocol/sync_agent_notification_configs_request.py index 7d56ed9a1..cd9186358 100644 --- a/src/adcp/types/domains/protocol/sync_agent_notification_configs_request.py +++ b/src/adcp/types/domains/protocol/sync_agent_notification_configs_request.py @@ -1,20 +1,46 @@ -"""Types declared by the AdCP ``protocol/sync_agent_notification_configs_request`` schema. +# generated by datamodel-codegen: +# filename: protocol/sync_agent_notification_configs_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.protocol.sync_agent_notification_configs_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field, StrictBool + +from ..core import agent_notification_config +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope -from adcp.types.generated_poc.protocol.sync_agent_notification_configs_request import ( - SyncAgentNotificationConfigsRequest, -) -# Explicit exports -__all__ = ["SyncAgentNotificationConfigsRequest"] +class SyncAgentNotificationConfigsRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + idempotency_key: Annotated[ + str, + Field( + description="Client-generated unique key for at-most-once execution. The task mutates the authenticated caller's persistent agent-level subscriber set and may trigger endpoint proof-of-control challenges, so retries MUST reuse the same key and same payload. Use a fresh UUID v4 for each logical replacement.", + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + notification_configs: Annotated[ + list[agent_notification_config.AgentNotificationConfig], + Field( + description="Complete desired set of agent-level notification subscribers for the authenticated caller or registry identity. Omit is invalid; send an empty array to remove every subscriber owned by this caller. Each entry registers a URL, the agent-level event types the subscriber wants, and optional legacy auth. Duplicate `subscriber_id` values are rejected within this caller-scoped array. If any entry fails validation or activation proof, the seller rejects the replacement and leaves this caller's previous set unchanged.", + max_length=16, + ), + ] + dry_run: Annotated[ + StrictBool | None, + Field( + description='When true, validate the replacement and report what would be persisted without applying it or sending endpoint proof-of-control challenges.' + ), + ] = False + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/protocol/sync_agent_notification_configs_response.py b/src/adcp/types/domains/protocol/sync_agent_notification_configs_response.py index 79b3fcae8..f29354949 100644 --- a/src/adcp/types/domains/protocol/sync_agent_notification_configs_response.py +++ b/src/adcp/types/domains/protocol/sync_agent_notification_configs_response.py @@ -1,21 +1,57 @@ -"""Types declared by the AdCP ``protocol/sync_agent_notification_configs_response`` schema. +# generated by datamodel-codegen: +# filename: protocol/sync_agent_notification_configs_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.protocol.sync_agent_notification_configs_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field, StrictBool + +from ..core import agent_notification_config +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope + + +class Action(StrEnum): + updated = 'updated' + unchanged = 'unchanged' + cleared = 'cleared' + failed = 'failed' -from adcp.types.generated_poc.protocol.sync_agent_notification_configs_response import ( - Action, - SyncAgentNotificationConfigsResponse, -) -# Explicit exports -__all__ = ["Action", "SyncAgentNotificationConfigsResponse"] +class SyncAgentNotificationConfigsResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + dry_run: Annotated[ + StrictBool | None, + Field(description='Whether this was a dry run and no persisted subscriber set changed.'), + ] = None + action: Annotated[ + Action, + Field( + description="Outcome for the caller-scoped replacement. `updated`: a new subscriber set was persisted for this caller. `unchanged`: the submitted set matched this caller's current set. `cleared`: the submitted empty array removed all subscribers owned by this caller. `failed`: validation, authorization, or endpoint proof failed; the prior caller-scoped set remains unchanged and `errors[]` explains why." + ), + ] + notification_configs: Annotated[ + list[agent_notification_config.AgentNotificationConfig] | None, + Field( + description='Current persisted agent-level notification subscribers owned by the authenticated caller after the request. Entries are keyed by `subscriber_id`; `authentication.credentials` is omitted on every entry because credentials are write-only. Present on successful actions and MAY be present on `failed` responses to show the unchanged prior caller-scoped set.', + max_length=16, + ), + ] = None + errors: Annotated[ + list[error.Error] | None, + Field( + description='Operation-level errors and warnings. Present when action is `failed`, and MAY include non-fatal warnings on successful responses.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/protocol/sync_principal_request.py b/src/adcp/types/domains/protocol/sync_principal_request.py index 0ddafffcb..dd4d850c0 100644 --- a/src/adcp/types/domains/protocol/sync_principal_request.py +++ b/src/adcp/types/domains/protocol/sync_principal_request.py @@ -1,21 +1,88 @@ -"""Types declared by the AdCP ``protocol/sync_principal_request`` schema. +# generated by datamodel-codegen: +# filename: protocol/sync_principal_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.protocol.sync_principal_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictBool + +from ..core import agent_notification_config, agent_reporting_destination +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core import principal_declarations +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import principal_kind + + +class Configuration(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + notification_configs: Annotated[ + list[agent_notification_config.AgentNotificationConfig] | None, + Field( + description='Complete desired agent-level subscriber set. The same caller-scoping, proof-of-control, secret handling, and replacement rules as sync_agent_notification_configs apply.', + max_length=16, + ), + ] = None + reporting_destinations: Annotated[ + list[agent_reporting_destination.AgentReportingDestination] | None, + Field( + description='Complete desired reusable reporting destination set. Omitting a previously present destination_id revokes it, and [] revokes every destination: the seller halts new deliveries to all of its generations within the advertised suspension_interval_seconds and retains it as a retired generation for reporting history. Revocation does not delete caller-owned data already delivered. destination_id values MUST be unique.', + max_length=64, + ), + ] = None + declarations: Annotated[ + principal_declarations.AgentDeclarations | None, + Field( + description='Complete declared consumption facts for this principal record. A present object replaces the declared set wholesale; {} clears it; omission leaves it unchanged. The seller computes and returns the accepted intersection in state.' + ), + ] = None -from adcp.types.generated_poc.protocol.sync_principal_request import ( - Configuration, - SyncPrincipalRequest, -) -# Explicit exports -__all__ = ["Configuration", "SyncPrincipalRequest"] +class SyncPrincipalRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + idempotency_key: Annotated[ + str, + Field( + description='Client-generated key for at-most-once execution, at least 16 characters; a fresh UUID v4 per logical operation is recommended. Retries MUST reuse the same key with the same body.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + expected_configuration_version: Annotated[ + str | None, + Field( + description='Optional optimistic-concurrency fence returned by a previous successful sync. When present and stale, the seller rejects the whole request without mutation. Compare only for equality.', + max_length=255, + min_length=1, + ), + ] = None + expected_principal_kind: Annotated[ + principal_kind.PrincipalKind | None, + Field( + description='Optional assertion fence, not identity input: the caller states which party kind it believes it is authenticating as. When present and different from the seller-resolved principal_kind, the seller rejects the whole request with CONFLICT before mutation. The field never influences resolution.' + ), + ] = None + configuration: Annotated[ + Configuration, + Field( + description='Sections to replace atomically. At least one section is required. A present array is complete desired state for that section; [] clears it; omission leaves it unchanged.' + ), + ] + dry_run: Annotated[ + StrictBool | None, + Field( + description='Validate the proposed replacements and report the would-be action without persisting them, issuing durable identifiers or grants, or sending endpoint proof challenges.' + ), + ] = False + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/protocol/sync_principal_response.py b/src/adcp/types/domains/protocol/sync_principal_response.py index 0735cc1ce..8f069dd8f 100644 --- a/src/adcp/types/domains/protocol/sync_principal_response.py +++ b/src/adcp/types/domains/protocol/sync_principal_response.py @@ -1,38 +1,104 @@ -"""Types declared by the AdCP ``protocol/sync_principal_response`` schema. +# generated by datamodel-codegen: +# filename: protocol/sync_principal_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.protocol.sync_principal_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-04 01:57:57 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal + +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field + +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 +from ..core import principal_state +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import principal_kind as principal_kind_1 + + +class Action(StrEnum): + updated = 'updated' + unchanged = 'unchanged' + cleared = 'cleared' + + +class Action33(StrEnum): + would_update = 'would_update' + would_be_unchanged = 'would_be_unchanged' + would_clear = 'would_clear' + + +class Result(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['validated'] = 'validated' + action: Action33 + dry_run: Literal[True] + warnings: Annotated[list[error.Error] | None, Field(max_length=16)] = None + + +class Result19(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['failed'] = 'failed' + errors: Annotated[list[error.Error], Field(max_length=16, min_length=1)] + + +class Result17(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['applied'] = 'applied' + action: Annotated[ + Action, + Field( + description="Persisted outcome for the submitted sections, computed solely against the caller's own prior state. cleared applies only when every submitted section was []; any other change is updated; unchanged means no submitted section differed." + ), + ] + dry_run: Literal[False] + principal_id: Annotated[ + str, + Field( + description='Seller-issued opaque identifier for this authenticated principal record. It is response-only, not a credential, not caller identity, and not advertiser-account authority.', + max_length=255, + min_length=1, + ), + ] + principal_kind: Annotated[ + principal_kind_1.PrincipalKind, + Field( + description="Seller-resolved party kind of the authenticated principal: a buyer-agent workload, or an operator-side identity such as a person at the operator. Resolved solely from the seller's authorization system, never from request content, so per-party policy such as billing gates can rely on it." + ), + ] + configuration_version: Annotated[ + str, + Field( + description='Opaque version of the persisted configuration. Compare only for equality and return it as expected_configuration_version on a later guarded replacement.', + max_length=255, + min_length=1, + ), + ] + configuration: principal_state.PrincipalState + warnings: Annotated[list[error.Error] | None, Field(max_length=16)] = None + + +class SyncPrincipalResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + result: Result17 | Result | Result19 + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.protocol.sync_principal_response import ( - Action, - Action33, - PrincipalAppliedResult, - PrincipalSyncFailedResult, - PrincipalValidatedResult, - Result, - Result17, - Result19, - SyncPrincipalResponse, -) - -# Explicit exports -__all__ = [ - "Action", - "Action33", - "PrincipalAppliedResult", - "PrincipalSyncFailedResult", - "PrincipalValidatedResult", - "Result", - "Result17", - "Result19", - "SyncPrincipalResponse", -] +# Stable aliases for anonymous result arms (selected by discriminator). +PrincipalValidatedResult = Result +PrincipalAppliedResult = Result17 +PrincipalSyncFailedResult = Result19 diff --git a/src/adcp/types/domains/registries/__init__.py b/src/adcp/types/domains/registries/__init__.py index 005e8830a..133424d84 100644 --- a/src/adcp/types/domains/registries/__init__.py +++ b/src/adcp/types/domains/registries/__init__.py @@ -9,14 +9,14 @@ it from its own schema's module, ``adcp.types.domains.registries.``. Nothing here is renamed. -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:33:59 UTC +Auto-generated from the generated domain tree. DO NOT EDIT MANUALLY. +Generation date: 2026-10-04 18:45:11 UTC """ # ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.registries.v1_canonical_mapping import ( +from adcp.types.domains.registries.v1_canonical_mapping import ( Dimensions, Mapping, ParameterMapping, diff --git a/src/adcp/types/domains/registries/v1_canonical_mapping.py b/src/adcp/types/domains/registries/v1_canonical_mapping.py index 6bea29b6c..3aeeb020f 100644 --- a/src/adcp/types/domains/registries/v1_canonical_mapping.py +++ b/src/adcp/types/domains/registries/v1_canonical_mapping.py @@ -1,38 +1,126 @@ -"""Types declared by the AdCP ``registries/v1_canonical_mapping`` schema. +# generated by datamodel-codegen: +# filename: registries/v1_canonical_mapping.json +# timestamp: 2026-10-05T13:49:50+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.registries.v1_canonical_mapping import +from datetime import date +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictBool + + +class V1Pattern(AdCPBaseModel): + format_id_glob: Annotated[ + str, + Field( + description="Glob pattern matched against v1 format_id.id. Examples: 'iab_mrec_300x250', 'iab_leaderboard_*', 'meta_*_reels'." + ), + ] + + +class Dimensions(AdCPBaseModel): + width: SchemaInt | None = None + height: SchemaInt | None = None + + +class Structural(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + asset_types: Annotated[ + list[str] | None, + Field( + description="Set of asset_type values that must appear in the format's slots (in any order, any count)." + ), + ] = None + vast_versions: Annotated[ + list[str] | None, + Field(description="VAST version constraints. Strings like '>=4.0', '4.x', '4.2'."), + ] = None + daast_versions: list[str] | None = None + dimensions: Dimensions | None = None + + +class V1Pattern1(AdCPBaseModel): + structural: Annotated[ + Structural, + Field( + description="Structural match against the format's slot shape, asset types, and version constraints." + ), + ] + + +class Transform(StrEnum): + identity = 'identity' + singleton_array = 'singleton_array' + + +class ParameterMapping(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + source_field: Annotated[ + str, Field(description='Field read from the matched v1 format_id object.', min_length=1) + ] + target_parameter: Annotated[ + str, Field(description='Field written into the projected v2 params object.', min_length=1) + ] + transform: Annotated[ + Transform | None, + Field( + description='Value transform applied during forwarding. identity copies the value; singleton_array wraps it in a one-element array.' + ), + ] = Transform.identity + + +class V2(AdCPBaseModel): + canonical: Annotated[str, Field(description='v2 canonical format the v1 pattern projects to.')] + parameters: Annotated[ + dict[str, Any] | None, + Field( + description='Optional parameters that narrow the canonical (e.g., width/height, vast_version). When present, become the params on the projected v2 ProductFormatDeclaration. The shape MUST be valid params for the named canonical.' + ), + ] = None + parameter_mappings: Annotated[ + list[ParameterMapping] | None, + Field( + description='Machine-readable forwarding rules for parameters carried by a structured v1 format_id. SDKs copy each present source_field to the target_parameter after applying transform; absent source fields leave the target parameter omitted. Static v2.parameters are applied before these forwarded values.', + min_length=1, + ), + ] = None + + +class Mapping(AdCPBaseModel): + v1_pattern: Annotated[ + V1Pattern | V1Pattern1, + Field(description='Match pattern. Carries either format_id_glob OR structural, not both.'), + ] + v2: V2 + deprecated: Annotated[ + StrictBool | None, + Field( + description='When true, this mapping is retained for backward-compatibility but should not be used for new mappings. SDKs SHOULD emit lint warnings when matching a deprecated entry.' + ), + ] = False + notes: Annotated[ + str | None, + Field(description='Optional human-readable explanation, examples, or rationale.'), + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.registries.v1_canonical_mapping import ( - Dimensions, - Mapping, - ParameterMapping, - Structural, - Transform, - V1Pattern, - V1Pattern1, - V1V2CanonicalFormatMappingRegistry, - V2, -) - -# Explicit exports -__all__ = [ - "Dimensions", - "Mapping", - "ParameterMapping", - "Structural", - "Transform", - "V1Pattern", - "V1Pattern1", - "V1V2CanonicalFormatMappingRegistry", - "V2", -] +class V1V2CanonicalFormatMappingRegistry(AdCPBaseModel): + version: Annotated[ + str, Field(description='Semver of this registry. Bumped on every published change.') + ] + last_updated: Annotated[ + date | None, Field(description='ISO date of the last published change.') + ] = None + mappings: Annotated[ + list[Mapping], + Field( + description='Ordered list of v1 → v2 mappings. SDKs apply mappings in order and use the first match.' + ), + ] diff --git a/src/adcp/types/domains/signals/__init__.py b/src/adcp/types/domains/signals/__init__.py index 962262360..6d256e076 100644 --- a/src/adcp/types/domains/signals/__init__.py +++ b/src/adcp/types/domains/signals/__init__.py @@ -9,29 +9,23 @@ it from its own schema's module, ``adcp.types.domains.signals.``. Nothing here is renamed. -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:33:59 UTC +Auto-generated from the generated domain tree. DO NOT EDIT MANUALLY. +Generation date: 2026-10-04 18:45:11 UTC """ # ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.signals.activate_signal_request import Action, ActivateSignalRequest -from adcp.types.generated_poc.signals.activate_signal_response import ( +from adcp.types.domains.signals.activate_signal_request import Action, ActivateSignalRequest +from adcp.types.domains.signals.activate_signal_response import ( ActivateSignalResponse, ActivateSignalResponse1, ActivateSignalResponse2, ) -from adcp.types.generated_poc.signals.get_signals_async_response_submitted import ( - GetSignalsSubmitted, -) -from adcp.types.generated_poc.signals.get_signals_async_response_working import GetSignalsWorking -from adcp.types.generated_poc.signals.get_signals_request import ( - DiscoveryMode, - Field1, - GetSignalsRequest, -) -from adcp.types.generated_poc.signals.get_signals_response import ( +from adcp.types.domains.signals.get_signals_async_response_submitted import GetSignalsSubmitted +from adcp.types.domains.signals.get_signals_async_response_working import GetSignalsWorking +from adcp.types.domains.signals.get_signals_request import DiscoveryMode, Field1, GetSignalsRequest +from adcp.types.domains.signals.get_signals_response import ( AiActRiskClass, Art9Basis, CacheScope, diff --git a/src/adcp/types/domains/signals/activate_signal_request.py b/src/adcp/types/domains/signals/activate_signal_request.py index 73e487605..b8478e04e 100644 --- a/src/adcp/types/domains/signals/activate_signal_request.py +++ b/src/adcp/types/domains/signals/activate_signal_request.py @@ -1,18 +1,80 @@ -"""Types declared by the AdCP ``signals/activate_signal_request`` schema. +# generated by datamodel-codegen: +# filename: signals/activate_signal_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.signals.activate_signal_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field + +from ..core import account_ref +from ..core import context as context_1 +from ..core import destination +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope + + +class Action(StrEnum): + activate = 'activate' + deactivate = 'deactivate' -from adcp.types.generated_poc.signals.activate_signal_request import Action, ActivateSignalRequest -# Explicit exports -__all__ = ["Action", "ActivateSignalRequest"] +class ActivateSignalRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + action: Annotated[ + Action | None, + Field( + description="Whether to activate or deactivate the signal. Deactivating removes the segment from downstream platforms, required when campaigns end to comply with data governance policies (GDPR, CCPA). Defaults to 'activate' when omitted." + ), + ] = Action.activate + signal_agent_segment_id: Annotated[ + str, + Field( + description='Opaque activation handle returned in the signal_agent_segment_id field of each get_signals response entry. Pass this string verbatim — do not pass the signal_id object.' + ), + ] + destinations: Annotated[ + list[destination.Destination], + Field( + description='Target destination(s) for activation. If the authenticated caller matches one of these destinations, activation keys will be included in the response.', + min_length=1, + ), + ] + pricing_option_id: Annotated[ + str | None, + Field( + description="The pricing option selected from the signal's pricing_options in the get_signals response. Required when the signal has pricing options. Records the buyer's pricing commitment at activation time; pass this same value in report_usage for billing verification." + ), + ] = None + governance_context: Annotated[ + str | None, + Field( + description='Opaque authorization context returned by an approved check_governance decision for this signal activation. Required when the account has a registered governance agent; signal agents MUST reject governed activations that omit a valid context. Conditions and denied decisions never produce this value.', + max_length=4096, + min_length=1, + pattern='^[\\x20-\\x7E]+$', + ), + ] = None + account: Annotated[ + account_ref.AccountReference | None, + Field( + description='Account for this activation. Associates with a commercial relationship established via sync_accounts.' + ), + ] = None + idempotency_key: Annotated[ + str, + Field( + description='Client-generated unique key for this request. Prevents duplicate activations on retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/signals/activate_signal_response.py b/src/adcp/types/domains/signals/activate_signal_response.py index e72a1d50f..cf0505ef9 100644 --- a/src/adcp/types/domains/signals/activate_signal_response.py +++ b/src/adcp/types/domains/signals/activate_signal_response.py @@ -1,22 +1,43 @@ -"""Types declared by the AdCP ``signals/activate_signal_response`` schema. +# generated by datamodel-codegen: +# filename: signals/activate_signal_response.json +# timestamp: preserved-by-post-generate-fixes -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.signals.activate_signal_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, TypeAlias -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field + +from ..core.version_envelope import AdcpVersionEnvelope +from ..core import context as context_1 +from ..core import deployment as deployment_1 +from ..core import error as error_1 +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope + + +class ActivateSignalResponse1(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + deployments: list[deployment_1.Deployment] + sandbox: bool | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +class ActivateSignalResponse2(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict(extra='allow') + errors: Annotated[list[error_1.Error], Field(min_length=1)] + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None + + +ActivateSignalResponse: TypeAlias = ActivateSignalResponse1 | ActivateSignalResponse2 -from adcp.types.generated_poc.signals.activate_signal_response import ( - ActivateSignalResponse, - ActivateSignalResponse1, - ActivateSignalResponse2, -) -# Explicit exports -__all__ = ["ActivateSignalResponse", "ActivateSignalResponse1", "ActivateSignalResponse2"] +__all__ = [ + 'ActivateSignalResponse', + 'ActivateSignalResponse1', + 'ActivateSignalResponse2', +] diff --git a/src/adcp/types/domains/signals/get_signals_async_response_submitted.py b/src/adcp/types/domains/signals/get_signals_async_response_submitted.py index a81c274bb..22488e68a 100644 --- a/src/adcp/types/domains/signals/get_signals_async_response_submitted.py +++ b/src/adcp/types/domains/signals/get_signals_async_response_submitted.py @@ -1,20 +1,51 @@ -"""Types declared by the AdCP ``signals/get_signals_async_response_submitted`` schema. +# generated by datamodel-codegen: +# filename: signals/get_signals_async_response_submitted.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.signals.get_signals_async_response_submitted import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 -from adcp.types.generated_poc.signals.get_signals_async_response_submitted import ( - GetSignalsSubmitted, -) -# Explicit exports -__all__ = ["GetSignalsSubmitted"] +class GetSignalsSubmitted(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + status: Annotated[ + Literal['submitted'], + Field( + description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, whose signals array is issued in-line. See task-status.json for the full task-status enum.' + ), + ] = 'submitted' + task_id: Annotated[ + str, + Field( + description='Task handle the caller uses with get_task_status (or the legacy AdCP tasks/get alias), and that the agent references on push-notification callbacks. The signals array is issued on the completion artifact, not here. This AdCP application-layer handle remains the snake_case task_id in every transport payload and is distinct from any transport-native A2A Task id.' + ), + ] + message: Annotated[ + str | None, + Field( + description="Optional human-readable explanation of why the task is submitted — e.g., 'Provider discovery queued; typical turnaround 10-30 minutes.' Plain text only. Callers MUST treat this as untrusted agent input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile agent may inject prompt-injection payloads aimed at the caller's agent.", + max_length=2000, + ), + ] = None + estimated_completion: Annotated[ + AwareDatetime | None, + Field(description='Estimated completion time for the signal discovery task.'), + ] = None + errors: Annotated[ + list[error.Error] | None, + Field( + description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories or partial provider unavailability). Terminal failures belong in the error branch, not here.' + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/signals/get_signals_async_response_working.py b/src/adcp/types/domains/signals/get_signals_async_response_working.py index 05d35a349..54a4a68da 100644 --- a/src/adcp/types/domains/signals/get_signals_async_response_working.py +++ b/src/adcp/types/domains/signals/get_signals_async_response_working.py @@ -1,18 +1,40 @@ -"""Types declared by the AdCP ``signals/get_signals_async_response_working`` schema. +# generated by datamodel-codegen: +# filename: signals/get_signals_async_response_working.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.signals.get_signals_async_response_working import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import context as context_1 +from ..core import ext as ext_1 -from adcp.types.generated_poc.signals.get_signals_async_response_working import GetSignalsWorking -# Explicit exports -__all__ = ["GetSignalsWorking"] +class GetSignalsWorking(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + percentage: Annotated[ + StrictFloat | None, + Field( + description='Progress percentage of the signal discovery operation.', ge=0.0, le=100.0 + ), + ] = None + current_step: Annotated[ + str | None, + Field( + description='Current step in the signal discovery process, such as `querying_providers`, `ranking_signals`, or `checking_deployments`.' + ), + ] = None + total_steps: Annotated[ + SchemaInt | None, + Field(description='Total number of steps in the signal discovery process.'), + ] = None + step_number: Annotated[ + SchemaInt | None, Field(description='Current step number (1-indexed).') + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/signals/get_signals_request.py b/src/adcp/types/domains/signals/get_signals_request.py index a5e779f85..cc807a636 100644 --- a/src/adcp/types/domains/signals/get_signals_request.py +++ b/src/adcp/types/domains/signals/get_signals_request.py @@ -1,23 +1,165 @@ -"""Types declared by the AdCP ``signals/get_signals_request`` schema. +# generated by datamodel-codegen: +# filename: signals/get_signals_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.signals.get_signals_request import +from adcp.types.base import AdcpRequest, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._scalar import ScalarStr -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types._str_enum import StrEnum +from typing import Annotated + +from pydantic import ConfigDict, Field + +from ..core import account_ref +from ..core import context as context_1 +from ..core import destination +from ..core import ext as ext_1 +from ..core import pagination_request +from ..core import push_notification_config as push_notification_config_1 +from ..core import signal_filters +from ..core import signal_id as signal_id_1 +from ..core import signal_ref as signal_ref_1 +from ..core.version_envelope import AdcpVersionEnvelope + + +class DiscoveryMode(StrEnum): + brief = 'brief' + wholesale = 'wholesale' + + +class Country(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{2}$'} + + +class Field1(StrEnum): + signal_ref = 'signal_ref' + signal_id = 'signal_id' + signal_agent_segment_id = 'signal_agent_segment_id' + name = 'name' + description = 'description' + value_type = 'value_type' + categories = 'categories' + range = 'range' + demographic_predicate = 'demographic_predicate' + signal_type = 'signal_type' + data_provider = 'data_provider' + coverage_percentage = 'coverage_percentage' + deployments = 'deployments' + pricing_options = 'pricing_options' + taxonomy = 'taxonomy' + data_sources = 'data_sources' + methodology = 'methodology' + segmentation_criteria = 'segmentation_criteria' + criteria_url = 'criteria_url' + refresh_cadence = 'refresh_cadence' + lookback_window = 'lookback_window' + onboarder = 'onboarder' + modeling = 'modeling' + audience_expansion = 'audience_expansion' + device_expansion = 'device_expansion' + countries = 'countries' + consent_basis = 'consent_basis' + restricted_attributes = 'restricted_attributes' + policy_categories = 'policy_categories' + art9_basis = 'art9_basis' + data_subject_rights = 'data_subject_rights' + last_updated = 'last_updated' -from adcp.types.generated_poc.signals.get_signals_request import ( - Country, - DiscoveryMode, - Field1, - GetSignalsRequest, -) -# Explicit exports -__all__ = ["Country", "DiscoveryMode", "Field1", "GetSignalsRequest"] +class GetSignalsRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + discovery_mode: Annotated[ + DiscoveryMode | None, + Field( + description="Declares caller intent for this request. 'brief' (default): semantic discovery — signal_spec, signal_refs, or legacy signal_ids is required and the agent performs inference/RAG. 'wholesale': raw wholesale signals feed enumeration — signal_spec, signal_refs, and signal_ids MUST NOT be provided and the agent returns its full priced signals feed, paginated, scoped by filters/account/destinations/countries when present. Sellers receiving requests from pre-v3.1 clients without discovery_mode MUST default to 'brief'. Timing semantics: 'wholesale' is a wholesale signals feed read — agents SHOULD respond synchronously and MUST NOT route a 'wholesale' request through the async/Submitted arm; partial completion is signalled via the response's incomplete[] field, not via a task-handoff envelope. Agents that do not implement wholesale enumeration MAY return INVALID_REQUEST for wholesale calls; callers SHOULD probe via get_adcp_capabilities (signals.discovery_modes) first." + ), + ] = DiscoveryMode.brief + account: Annotated[ + account_ref.AccountReference | None, + Field( + description="Account for this request. When provided, the signals agent returns per-account pricing options if configured. In 'wholesale' mode, this is the rate-card scope: when omitted in wholesale mode, agents return their default rate-card pricing or omit pricing_options entirely." + ), + ] = None + signal_spec: Annotated[ + str | None, + Field( + description="Natural language description of the desired signals. When used alone, enables semantic discovery. When combined with signal_refs, provides context for the agent but signal_ref matches are returned first. MUST NOT be provided when discovery_mode is 'wholesale'." + ), + ] = None + signal_refs: Annotated[ + list[signal_ref_1.SignalRef] | None, + Field( + description="Specific signals to look up by reference. Returns exact matches for the requested SignalRef values. When combined with signal_spec, these signals anchor the starting set and signal_spec guides adjustments. MUST NOT be provided when discovery_mode is 'wholesale'.", + min_length=1, + ), + ] = None + signal_ids: Annotated[ + list[signal_id_1.SignalId] | None, + Field( + deprecated=True, + description="DEPRECATED. Use signal_refs instead. Legacy exact lookup field using SignalId objects. MUST NOT be provided when discovery_mode is 'wholesale'.", + min_length=1, + ), + ] = None + destinations: Annotated[ + list[destination.Destination] | None, + Field( + description='Filter signals to those activatable on specific agents/platforms. When omitted, returns all signals available on the current agent. If the authenticated caller matches one of these destinations, activation keys will be included in the response.', + min_length=1, + ), + ] = None + countries: Annotated[ + list[Country] | None, + Field( + description='Countries where signals will be used (ISO 3166-1 alpha-2 codes). When omitted, no geographic filter is applied.', + min_length=1, + ), + ] = None + filters: signal_filters.SignalFilters | None = None + fields: Annotated[ + list[Field1] | None, + Field( + description="Specific signal fields to include in the response, aligned with get_products.fields. Required identity and activation fields such as signal_ref or signal_id, signal_agent_segment_id, name, description, signal_type, coverage_percentage, and deployments are always included when required by the response schema. Use for progressive disclosure of rich signal-definition metadata: request fields such as demographic_predicate, taxonomy, data_sources, methodology, segmentation_criteria, criteria_url, refresh_cadence, lookback_window, onboarder, modeling, audience_expansion, device_expansion, countries, consent_basis, restricted_attributes, policy_categories, art9_basis, data_subject_rights, and last_updated when the buyer needs them inline. Omit for the agent's default discovery projection. Agents SHOULD honor requested fields for exact lookup, refinement, small custom-signal result sets, and private/source-native signals when available. fields is a projection request, not an entitlement grant; agents MAY redact requested definition fields unless the caller is authorized for the underlying lineage, methodology, and rights-routing metadata. When demographic_predicate, consent_basis, or art9_basis is projected for another provider's signal, the value remains provider-declared signal-definition posture; sellers and federating agents MUST NOT substitute their own semantics or processing basis. For broad discovery and wholesale pages, agents MAY return compact pointers instead of inlining large resources, especially when provider-published definitions can be resolved from signal_ref, taxonomy.ref, criteria_url, disclosure_url, and validators such as resolved URL plus catalog_etag, HTTP ETag/Last-Modified, or taxonomy.etag.", + min_length=1, + ), + ] = None + max_results: Annotated[ + SchemaInt | None, + Field( + deprecated=True, + description='DEPRECATED: Use pagination.max_results instead. When both fields are present, agents MUST honor pagination.max_results. When only this field is present without a pagination envelope, agents SHOULD treat it as the page size subject to a maximum of 100 results. This field will be removed in AdCP 4.0.', + ge=1, + ), + ] = None + pagination: Annotated[ + pagination_request.PaginationRequest | None, + Field( + description='Pagination parameters. Use pagination.max_results (max: 100, default: 50) and pagination.cursor for cursor-based page walks. When the deprecated top-level max_results field is also present, pagination.max_results takes precedence.' + ), + ] = None + push_notification_config: Annotated[ + push_notification_config_1.PushNotificationConfig | None, + Field( + description='Optional webhook configuration for async terminal completion/failure notifications on semantic signal discovery. Meaningful only for `discovery_mode: "brief"` requests that enter the async lifecycle. Submitted envelopes with `task_id` remain pollable through `get_task_status` (legacy `tasks/get`) whether or not this field is present. If a brief request includes this field and the agent returns a Submitted envelope, the agent MUST deliver at least the terminal completion/failure notification to the configured URL; intermediate progress notifications are MAY. If the agent cannot honor the webhook channel, it MUST reject the request with a structured error instead of silently accepting. This field does not change wholesale timing semantics: agents MUST NOT route `discovery_mode: "wholesale"` requests through the async/Submitted arm or emit async delivery solely because `push_notification_config` is present; partial wholesale completion is reported via `incomplete[]`.' + ), + ] = None + if_wholesale_feed_version: Annotated[ + str | None, + Field( + description="Opaque wholesale_feed_version token returned by a prior wholesale-mode get_signals response from this agent. Only valid when discovery_mode is wholesale. When provided, the agent compares against its current wholesale signals feed version for the caller's cache_scope and MAY return an unchanged: true response (with signals omitted) if nothing has changed. The token is scope-keyed: callers cache `(cache_scope, wholesale_feed_version)` pairs. Scoping dimensions: (agent, discovery_mode, filters, destinations, countries) for cache_scope: 'public'; that tuple plus account_id for cache_scope: 'account'. pagination.cursor is NOT part of the scoping tuple. See specs/wholesale-feed-webhooks.md for the full sync pattern." + ), + ] = None + if_pricing_version: Annotated[ + str | None, + Field( + description="Opaque pricing_version token from a prior get_signals response. MUST only be sent together with if_wholesale_feed_version — pricing version has no structural baseline to compare against on its own. Evaluation order: (1) if_wholesale_feed_version mismatch → agent returns the full payload; (2) if_wholesale_feed_version matches but if_pricing_version mismatches → agent returns the full payload so the caller sees updated pricing_options; (3) both match → agent MAY return unchanged: true. Agents that don't track pricing separately ignore this and fall back to if_wholesale_feed_version semantics." + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/signals/get_signals_response.py b/src/adcp/types/domains/signals/get_signals_response.py index 4c6749976..9b8e1ff2d 100644 --- a/src/adcp/types/domains/signals/get_signals_response.py +++ b/src/adcp/types/domains/signals/get_signals_response.py @@ -1,74 +1,475 @@ -"""Types declared by the AdCP ``signals/get_signals_response`` schema. +# generated by datamodel-codegen: +# filename: signals/get_signals_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.signals.get_signals_response import +from adcp.types.base import AdcpResponse, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from collections.abc import Sequence +from typing import Annotated, Literal + +from adcp.types.base import AdCPBaseModel +from adcp.types._scalar import ScalarStr +from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, StrictBool, StrictFloat + +from ..core import context as context_1 +from ..core import demographic_predicate as demographic_predicate_1 +from ..core import deployment, duration, error +from ..core import ext as ext_1 +from ..core import pagination_response, signal_coverage_forecast +from ..core import signal_id as signal_id_1 +from ..core import signal_modeling_disclosure +from ..core import signal_ref as signal_ref_1 +from ..core import vendor_pricing_option +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import consent_basis as consent_basis_1 +from ..enums import restricted_attribute, signal_catalog_type, signal_value_type + + +class Range(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + min: Annotated[StrictFloat, Field(description='Minimum value, inclusive.')] + max: Annotated[StrictFloat, Field(description='Maximum value, inclusive.')] + + +class Value(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + id: Annotated[str, Field(min_length=1)] + path: str | None = None + modifiers: list[str] | None = None + + +class ValueMapping(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + value: str + taxonomy_value_id: str + path: str | None = None + modifiers: list[str] | None = None + + +class ParentMatchBehavior(StrEnum): + exact_only = 'exact_only' + descendants_supported = 'descendants_supported' + unknown = 'unknown' + + +class Taxonomy(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + ref: AnyUrl + version: str | None = None + segtax: Annotated[SchemaInt | None, Field(ge=1)] = None + etag: str | None = None + values: Annotated[list[Value], Field(min_length=1)] + value_mappings: Annotated[list[ValueMapping] | None, Field(min_length=1)] = None + parent_match_behavior: ParentMatchBehavior | None = None + + +class DataSource(StrEnum): + app_behavior = 'app_behavior' + app_usage = 'app_usage' + web_usage = 'web_usage' + geo_location = 'geo_location' + email = 'email' + tv_ott_or_stb_device = 'tv_ott_or_stb_device' + panel = 'panel' + online_ecommerce = 'online_ecommerce' + credit_data = 'credit_data' + loyalty_card = 'loyalty_card' + transaction = 'transaction' + online_survey = 'online_survey' + offline_survey = 'offline_survey' + public_record_census = 'public_record_census' + public_record_voter_file = 'public_record_voter_file' + public_record_other = 'public_record_other' + offline_transaction = 'offline_transaction' + + +class Methodology(StrEnum): + observed = 'observed' + declared = 'declared' + derived = 'derived' + inferred = 'inferred' + modeled = 'modeled' + + +class RefreshCadence(StrEnum): + intra_day = 'intra_day' + daily = 'daily' + weekly = 'weekly' + monthly = 'monthly' + bi_monthly = 'bi_monthly' + quarterly = 'quarterly' + bi_annually = 'bi_annually' + annually = 'annually' + + +class MatchKey(StrEnum): + name = 'name' + address = 'address' + email = 'email' + postal = 'postal' + lat_long = 'lat_long' + mobile_id = 'mobile_id' + cookie_id = 'cookie_id' + ip = 'ip' + customer_id = 'customer_id' + phone = 'phone' + + +class PreOnboardingPrecisionLevel(StrEnum): + individual = 'individual' + household = 'household' + business = 'business' + geography = 'geography' + + +class Onboarder(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + match_keys: Annotated[list[MatchKey], Field(min_length=1)] + pre_onboarding_audience_expansion: StrictBool | None = None + pre_onboarding_device_expansion: StrictBool | None = None + pre_onboarding_precision_level: PreOnboardingPrecisionLevel | None = None + + +class Country(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{2}$'} + + +class Art9Basis(StrEnum): + explicit_consent = 'explicit_consent' + manifestly_made_public = 'manifestly_made_public' + substantial_public_interest = 'substantial_public_interest' + vital_interests = 'vital_interests' + + +class Method(StrEnum): + lookalike = 'lookalike' + supervised = 'supervised' + embedding = 'embedding' + rules = 'rules' + + +class Type(StrEnum): + first_party_crm = 'first_party_crm' + panel = 'panel' + declared_survey = 'declared_survey' + transactional = 'transactional' + behavioral = 'behavioral' + + +class SeedSource(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Type + provider_signed: Annotated[ + StrictBool, + Field( + description='Provider assertion that the seed source carries a signed attestation. Consumers MUST NOT treat this boolean alone as cryptographic proof.' + ), + ] + + +class TrainingDataJurisdiction(Country): + pass + + +class AiActRiskClass(StrEnum): + minimal = 'minimal' + limited = 'limited' + high_risk = 'high_risk' + + +class Right(StrEnum): + access = 'access' + rectification = 'rectification' + erasure = 'erasure' + portability = 'portability' + objection = 'objection' + + +class Channel(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + rights: Annotated[list[Right], Field(min_length=1)] + url: AnyUrl | None = None + email: EmailStr | None = None + languages: list[str] | None = None + countries: list[Country] | None = None + + +class DataSubjectRights(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + upstream_source_domain: Annotated[ + str | None, + Field( + max_length=253, + pattern='^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]{0,61}[A-Za-z0-9])$', + ), + ] = None + channels: Annotated[list[Channel], Field(min_length=1)] + response_sla_days: Annotated[SchemaInt | None, Field(ge=1, le=90)] = None + ccpa_opt_out_url: AnyUrl | None = None + + +class Scope(StrEnum): + signals = 'signals' + pricing = 'pricing' + wholesale_feed = 'wholesale_feed' + + +class IncompleteItem(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + scope: Annotated[ + Scope, + Field( + description="'signals': not all matching signals were returned. 'pricing': signals returned but pricing is absent or unconfirmed. 'wholesale_feed': in wholesale mode, full feed enumeration could not complete in the time budget." + ), + ] + description: Annotated[ + str, Field(description='Human-readable explanation of what is missing and why.') + ] + estimated_wait: Annotated[ + duration.Duration | None, + Field( + description='How much additional time would resolve this scope. Allows the caller to decide whether to retry with a larger time_budget.' + ), + ] = None + + +class CacheScope(StrEnum): + public = 'public' + account = 'account' + + +class Modeling(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + method: Method + seed_source: SeedSource + training_data_jurisdictions: Annotated[list[TrainingDataJurisdiction], Field(min_length=1)] + ai_act_risk_class: AiActRiskClass + disclosure: signal_modeling_disclosure.SignalModelingDisclosure | None = None + + +class Signal(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + signal_id: Annotated[ + signal_id_1.SignalId | None, + Field( + deprecated=True, + description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older Signals Protocol clients.', + ), + ] = None + signal_ref: Annotated[ + signal_ref_1.SignalRef | None, + Field( + description="Canonical signal reference. Use scope 'product' for a product-local signal defined by this listing; use scope 'data_provider' with data_provider_domain for a signal defined in a data provider's published adagents.json signals[]; use scope 'signal_source' with signal_source_url for a source-native signal." + ), + ] = None + signal_agent_segment_id: Annotated[ + str, + Field( + description='Opaque resolved-segment handle issued by this signal source. Pass this string verbatim to activate_signal.signal_agent_segment_id, and echo it in package signal targeting when the selected product option exposes the same handle. Treat the value as provider-scoped and opaque: providers MAY namespace it so two providers can expose similarly named signals without relying on a shared taxonomy. Do not pass the signal_id object as this handle, and do not reconstruct a segment handle from categorical values when get_signals returned a resolved segment.' + ), + ] + name: Annotated[ + str, + Field( + description="Human-readable signal name. Required when signal_ref_1.scope is 'product'. For data_provider and signal_source refs, this is optional contextual display text; the referenced definition or source remains authoritative." + ), + ] + description: Annotated[ + str, + Field( + description='Detailed signal description. For data_provider and signal_source refs, this is optional contextual display text and MUST NOT replace the referenced definition.' + ), + ] + value_type: Annotated[ + signal_value_type.SignalValueType | None, + Field( + description="The data type of this signal's values. Required when signal_ref_1.scope is 'product'." + ), + ] = None + categories: Annotated[ + list[str] | None, + Field( + description="Valid values for categorical signals. Present when value_type is 'categorical'.", + min_length=1, + ), + ] = None + range: Annotated[ + Range | None, + Field(description="Valid range for numeric signals. Present when value_type is 'numeric'."), + ] = None + signal_type: Annotated[ + signal_catalog_type.SignalAvailabilityType, + Field(description='Commercial/provenance type of signal (marketplace, custom, owned)'), + ] + data_provider: Annotated[ + str | None, + Field( + description='Human-readable source name for the signal, when applicable. For data_provider-scoped signals this is the data provider name; for signal_source-scoped signals it may identify the signal source or proprietary origin.' + ), + ] = None + coverage_percentage: Annotated[ + StrictFloat | None, + Field( + deprecated=True, + description='DEPRECATED for detailed planning. Optional legacy scalar percentage of audience coverage retained only as a fallback for clients that do not consume coverage_forecast. When coverage_forecast is present, coverage_forecast is authoritative for signal-level discovery and coverage_percentage is fallback-only. If coverage_forecast includes an absent bucket over the same denominator, coverage_percentage SHOULD align with 100 * (1 - absent coverage_rate.mid).', + ge=0.0, + le=100.0, + ), + ] = None + coverage_forecast: Annotated[ + signal_coverage_forecast.SignalCoverageForecast | None, + Field( + description='Optional forecast-shaped signal availability guidance. When present, this is authoritative for signal-level discovery coverage. Use this to disclose the denominator, bucket semantics, not-present bucket, aggregate present bucket, and per-value coverage distribution for the signal.' + ), + ] = None + deployments: Annotated[ + Sequence[deployment.Deployment], Field(description='Array of deployment targets') + ] + pricing_options: Annotated[ + list[vendor_pricing_option.VendorPricingOption] | None, + Field( + description='Pricing options available for this signal when it has an incremental price. The buyer selects one and passes its pricing_option_id in report_usage or package-level signal_targeting_groups for billing verification. Omit when pricing is unavailable to the caller, bundled into the destination product, or has no incremental cost.', + min_length=1, + ), + ] = None + methodology_url: Annotated[ + AnyUrl | None, + Field( + description='Optional link to published methodology, media-kit, or data documentation. For data_provider and signal_source refs, this SHOULD match or supplement the referenced definition.' + ), + ] = None + last_updated: Annotated[ + AwareDatetime | None, + Field( + description='When this definition record was last updated. This indicates freshness of the definition record, not an attestation that the underlying data or model was refreshed at that time.' + ), + ] = None + restricted_attributes: Annotated[ + list[restricted_attribute.RestrictedAttribute] | None, + Field(description='Restricted attribute categories this signal touches.', min_length=1), + ] = None + demographic_predicate: Annotated[ + demographic_predicate_1.DemographicPredicate | None, + Field( + description="Projected authoritative demographic meaning for the signal. When projected from another provider, this MUST match the provider's definition exactly. Signal names alone never establish demographic semantics." + ), + ] = None + policy_categories: Annotated[ + list[str] | None, + Field(description='Policy categories this signal is sensitive for.', min_length=1), + ] = None + taxonomy: Annotated[ + Taxonomy | None, + Field( + description='Optional taxonomy metadata describing what this signal means in an external audience, content, retail-media, or provider-owned taxonomy.' + ), + ] = None + segmentation_criteria: Annotated[str | None, Field(max_length=500)] = None + criteria_url: AnyUrl | None = None + data_sources: Annotated[list[DataSource] | None, Field(min_length=1)] = None + methodology: Methodology | None = None + audience_expansion: StrictBool | None = None + device_expansion: StrictBool | None = None + refresh_cadence: RefreshCadence | None = None + lookback_window: RefreshCadence | None = None + onboarder: Onboarder | None = None + countries: Annotated[list[Country] | None, Field(min_length=1)] = None + consent_basis: Annotated[ + list[consent_basis_1.ConsentBasis] | None, + Field( + description="Data provider's declared GDPR Article 6 lawful basis or consent basis for the underlying signal definition, projected into this get_signals response row when requested. Sellers and federating agents that pass through another provider's signal MUST NOT substitute their own processing basis for the provider-declared basis.", + min_length=1, + ), + ] = None + art9_basis: Annotated[ + Art9Basis | None, + Field( + description="Data provider's declared GDPR Article 9 basis for the underlying signal definition when special-category data is involved and Article 9 applies, projected into this get_signals response row when requested. Sellers and federating agents that pass through another provider's signal MUST NOT substitute their own Article 9 basis for the provider-declared basis." + ), + ] = None + modeling: Modeling | None = None + data_subject_rights: Annotated[ + DataSubjectRights | None, + Field( + description='Per-signal data-subject-rights routing. This is a contact/routing reference, not a machine-callable AdCP API.' + ), + ] = None + dts_compliant_version: str | None = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.signals.get_signals_response import ( - AiActRiskClass, - Art9Basis, - CacheScope, - Channel, - Country, - DataSource, - DataSubjectRights, - GetSignalsResponse, - IncompleteItem, - MatchKey, - Method, - Methodology, - Modeling, - Onboarder, - ParentMatchBehavior, - PreOnboardingPrecisionLevel, - Range, - RefreshCadence, - Right, - Scope, - SeedSource, - Signal, - Taxonomy, - TrainingDataJurisdiction, - Type, - Value, - ValueMapping, -) - -# Explicit exports -__all__ = [ - "AiActRiskClass", - "Art9Basis", - "CacheScope", - "Channel", - "Country", - "DataSource", - "DataSubjectRights", - "GetSignalsResponse", - "IncompleteItem", - "MatchKey", - "Method", - "Methodology", - "Modeling", - "Onboarder", - "ParentMatchBehavior", - "PreOnboardingPrecisionLevel", - "Range", - "RefreshCadence", - "Right", - "Scope", - "SeedSource", - "Signal", - "Taxonomy", - "TrainingDataJurisdiction", - "Type", - "Value", - "ValueMapping", -] +class GetSignalsResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + signals: Annotated[Sequence[Signal] | None, Field(description='Array of matching signals')] = None + errors: Annotated[ + list[error.Error] | None, + Field( + description='Task-specific errors and warnings (e.g., signal discovery or pricing issues)' + ), + ] = None + incomplete: Annotated[ + list[IncompleteItem] | None, + Field( + description="Declares what the agent could not finish within the caller's time_budget or due to internal limits. Each entry identifies a scope that is missing or partial. Absent when the response is fully complete.", + min_length=1, + ), + ] = None + wholesale_feed_version: Annotated[ + str | None, + Field( + description="Opaque token representing the version of the wholesale signals feed state used to compose this response. Agents that implement conditional-fetch (if_wholesale_feed_version) MUST return this on every wholesale-mode response so callers can cache and probe later. Callers MUST treat the value as opaque — no format, no ordering, no inspection. The token is scope-keyed: it describes a version for the cache_scope declared on this response, NOT a global agent version. A caller caches `(cache_scope, wholesale_feed_version)` pairs and presents the matching token on the next request. Scoping dimensions: (agent, discovery_mode, filters, destinations, countries) for cache_scope: 'public'; that tuple plus account_id for cache_scope: 'account'. pagination.cursor is NOT part of the scoping tuple. See specs/wholesale-feed-webhooks.md for the full cache layering model." + ), + ] = None + pricing_version: Annotated[ + str | None, + Field( + description='Opaque token representing the version of the pricing layer. When the agent supports independent pricing versioning, pricing_version changes when prices move but wholesale_feed_version changes only when structure/metadata moves. Same cache_scope keying as wholesale_feed_version. Agents not separating these MAY omit pricing_version and use wholesale_feed_version for both.' + ), + ] = None + cache_scope: Annotated[ + CacheScope | None, + Field( + description="Declares whether the wholesale_feed_version and pricing_version on this response describe a universal layer or an account-specific overlay. REQUIRED on every 3.1+ response (the 3.1 schema enforces this — the safety property of the two-layer cache model depends on it). 'public': this response describes the agent's published rate card; the caller MAY dedupe under (agent, discovery_mode, filters, destinations, countries) without scoping by account. 'account': this response includes account-specific overrides; the caller MUST cache the version under that tuple plus account_id. When the request did NOT include `account`, the agent MUST return `cache_scope: 'public'`. When the request included `account`, the agent MUST return either 'public' (this account prices off the public rate card — caller dedupes) or 'account' (account-specific overrides exist — caller caches under the account key). Agents MAY return 'public' on an account-scoped request that previously had overrides — callers SHOULD interpret this as a downgrade. Without schema-required cache_scope, an agent silently omitting the field on an account-scoped response would cause callers to mis-key the cache and serve account-overlay payloads to other accounts — the canonical safety invariant of the entire cache layering model. **Backward-compatibility note for 3.1 validators:** SDKs validating strictly against the 3.1 schema MUST select the validator based on the server-declared `adcp_version`. For responses with `adcp_version` starting `3.0`, the 3.1 cache_scope-required constraint MUST be relaxed — pre-3.1 agents correctly emit no cache_scope and remain conformant to their declared version. This is a tightening within 3.1, not a 3.0 break." + ), + ] = CacheScope.public + unchanged: Annotated[ + Literal[True] | None, + Field( + description="Present and `true` ONLY on wholesale-mode responses when the request carried if_wholesale_feed_version (and/or if_pricing_version) matching the agent's current version for the caller's cache_scope, in which case signals[] MUST be omitted; wholesale_feed_version (echoed), cache_scope (echoed), and pricing_version (echoed when used) MUST still be present. Callers receiving unchanged: true MUST NOT mutate their local wholesale signals mirror. **One shape per state:** agents MUST NOT emit `unchanged: false` — the absence of the field IS the signal that the response carries signals. **Cross-scope isolation:** the comparator that decides `unchanged` MUST be keyed on `(cache_scope, wholesale_feed_version)`, not on the token value alone. An agent MUST NOT emit `unchanged: true` when it resolves the request to a different `cache_scope` than the one whose token the caller echoed in `if_wholesale_feed_version` (and/or `if_pricing_version`): because the token is scope-keyed, a value minted for `cache_scope: 'public'` cannot match the agent's current token for `cache_scope: 'account'` (or vice-versa), so such a request MUST return the full feed for the resolved scope with that scope's own token." + ), + ] = None + pagination: pagination_response.PaginationResponse | None = None + sandbox: Annotated[ + StrictBool | None, + Field(description='When true, this response contains simulated data from sandbox mode.'), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/sponsored_intelligence/__init__.py b/src/adcp/types/domains/sponsored_intelligence/__init__.py index d54cb4654..4e8d57292 100644 --- a/src/adcp/types/domains/sponsored_intelligence/__init__.py +++ b/src/adcp/types/domains/sponsored_intelligence/__init__.py @@ -9,14 +9,14 @@ it from its own schema's module, ``adcp.types.domains.sponsored_intelligence.``. Nothing here is renamed. -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:33:59 UTC +Auto-generated from the generated domain tree. DO NOT EDIT MANUALLY. +Generation date: 2026-10-04 18:45:11 UTC """ # ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.sponsored_intelligence.si_capabilities import ( +from adcp.types.domains.sponsored_intelligence.si_capabilities import ( A2ui, Avatar, Commerce, @@ -27,40 +27,38 @@ Video, Voice, ) -from adcp.types.generated_poc.sponsored_intelligence.si_context_use import SiContextUse -from adcp.types.generated_poc.sponsored_intelligence.si_get_offering_request import ( - SiGetOfferingRequest, -) -from adcp.types.generated_poc.sponsored_intelligence.si_get_offering_response import ( +from adcp.types.domains.sponsored_intelligence.si_context_use import SiContextUse +from adcp.types.domains.sponsored_intelligence.si_get_offering_request import SiGetOfferingRequest +from adcp.types.domains.sponsored_intelligence.si_get_offering_response import ( MatchingProduct, Offering, SiGetOfferingResponse, ) -from adcp.types.generated_poc.sponsored_intelligence.si_identity import ( +from adcp.types.domains.sponsored_intelligence.si_identity import ( ConsentScopeEnum, PrivacyPolicyAcknowledged, ShippingAddress, SiIdentity, User, ) -from adcp.types.generated_poc.sponsored_intelligence.si_initiate_session_request import ( +from adcp.types.domains.sponsored_intelligence.si_initiate_session_request import ( SiInitiateSessionRequest, ) -from adcp.types.generated_poc.sponsored_intelligence.si_initiate_session_response import ( +from adcp.types.domains.sponsored_intelligence.si_initiate_session_response import ( SiInitiateSessionResponse, ) -from adcp.types.generated_poc.sponsored_intelligence.si_send_message_request import ( +from adcp.types.domains.sponsored_intelligence.si_send_message_request import ( ActionResponse, SiSendMessageRequest, ) -from adcp.types.generated_poc.sponsored_intelligence.si_send_message_response import ( +from adcp.types.domains.sponsored_intelligence.si_send_message_response import ( ContextForCheckout, Handoff, Intent, Price, SiSendMessageResponse, ) -from adcp.types.generated_poc.sponsored_intelligence.si_sponsored_context import ( +from adcp.types.domains.sponsored_intelligence.si_sponsored_context import ( Account, DeclaredBy, DisclosureObligation, @@ -71,25 +69,25 @@ SiSponsoredContext, Timing, ) -from adcp.types.generated_poc.sponsored_intelligence.si_sponsored_context_receipt import ( +from adcp.types.domains.sponsored_intelligence.si_sponsored_context_receipt import ( DisclosureCommitment, HostReceipt, SiSponsoredContextReceipt, Status, Status46, ) -from adcp.types.generated_poc.sponsored_intelligence.si_terminate_session_request import ( +from adcp.types.domains.sponsored_intelligence.si_terminate_session_request import ( Reason, SiTerminateSessionRequest, TerminationContext, TransactionIntent, ) -from adcp.types.generated_poc.sponsored_intelligence.si_terminate_session_response import ( +from adcp.types.domains.sponsored_intelligence.si_terminate_session_response import ( AcpHandoff, FollowUp, SiTerminateSessionResponse, ) -from adcp.types.generated_poc.sponsored_intelligence.si_ui_element import SiUiElement +from adcp.types.domains.sponsored_intelligence.si_ui_element import SiUiElement # Explicit exports __all__ = [ diff --git a/src/adcp/types/domains/sponsored_intelligence/si_capabilities.py b/src/adcp/types/domains/sponsored_intelligence/si_capabilities.py index 643f43c0c..bf86e4c49 100644 --- a/src/adcp/types/domains/sponsored_intelligence/si_capabilities.py +++ b/src/adcp/types/domains/sponsored_intelligence/si_capabilities.py @@ -1,38 +1,125 @@ -"""Types declared by the AdCP ``sponsored_intelligence/si_capabilities`` schema. +# generated by datamodel-codegen: +# filename: sponsored_intelligence/si_capabilities.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.sponsored_intelligence.si_capabilities import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StrictBool + + +class Voice(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + provider: Annotated[ + str | None, Field(description='TTS provider (elevenlabs, openai, etc.)') + ] = None + voice_id: Annotated[str | None, Field(description='Brand voice identifier')] = None + + +class Video(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + formats: Annotated[ + list[str] | None, Field(description='Supported video formats (mp4, webm, etc.)') + ] = None + max_duration_seconds: Annotated[ + SchemaInt | None, Field(description='Maximum video duration') + ] = None + + +class Avatar(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + provider: Annotated[ + str | None, Field(description='Avatar provider (d-id, heygen, synthesia, etc.)') + ] = None + avatar_id: Annotated[str | None, Field(description='Brand avatar identifier')] = None + + +class Modalities(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + conversational: Annotated[ + StrictBool | None, Field(description='Pure text exchange - the baseline modality') + ] = True + voice: Annotated[ + StrictBool | Voice | None, Field(description='Audio-based interaction using brand voice') + ] = None + video: Annotated[ + StrictBool | Video | None, Field(description='Brand video content playback') + ] = None + avatar: Annotated[ + StrictBool | Avatar | None, Field(description='Animated video presence with brand avatar') + ] = None + + +class StandardEnum(StrEnum): + text = 'text' + link = 'link' + image = 'image' + product_card = 'product_card' + carousel = 'carousel' + action_button = 'action_button' + + +class Components(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + standard: Annotated[ + list[StandardEnum] | None, + Field(description='Standard components that all SI hosts must render'), + ] = None + extensions: Annotated[ + dict[str, Any] | None, + Field(description='Platform-specific extensions (chatgpt_apps_sdk, maps, forms, etc.)'), + ] = None + + +class Commerce(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + acp_checkout: Annotated[ + StrictBool | None, + Field(description='Supports ACP (Agentic Commerce Protocol) checkout handoff'), + ] = None + + +class A2ui(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + supported: Annotated[ + StrictBool | None, Field(description='Supports A2UI surface rendering') + ] = False + catalogs: Annotated[ + list[str] | None, + Field(description="Supported A2UI component catalogs (e.g., 'si-standard', 'standard')"), + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.sponsored_intelligence.si_capabilities import ( - A2ui, - Avatar, - Commerce, - Components, - Modalities, - SiCapabilities, - StandardEnum, - Video, - Voice, -) - -# Explicit exports -__all__ = [ - "A2ui", - "Avatar", - "Commerce", - "Components", - "Modalities", - "SiCapabilities", - "StandardEnum", - "Video", - "Voice", -] +class SiCapabilities(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + modalities: Annotated[ + Modalities | None, Field(description='Interaction modalities supported') + ] = None + components: Annotated[Components | None, Field(description='Visual components supported')] = ( + None + ) + commerce: Annotated[Commerce | None, Field(description='Commerce capabilities')] = None + a2ui: Annotated[A2ui | None, Field(description='A2UI (Agent-to-UI) capabilities')] = None + mcp_apps: Annotated[ + StrictBool | None, + Field(description='Supports MCP Apps for rendering A2UI surfaces in iframes'), + ] = False diff --git a/src/adcp/types/domains/sponsored_intelligence/si_context_use.py b/src/adcp/types/domains/sponsored_intelligence/si_context_use.py index ed7dfdf0a..0bdccd1fe 100644 --- a/src/adcp/types/domains/sponsored_intelligence/si_context_use.py +++ b/src/adcp/types/domains/sponsored_intelligence/si_context_use.py @@ -1,18 +1,13 @@ -"""Types declared by the AdCP ``sponsored_intelligence/si_context_use`` schema. +# generated by datamodel-codegen: +# filename: sponsored_intelligence/si_context_use.json +# timestamp: 2026-06-18T11:28:05+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.sponsored_intelligence.si_context_use import - -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" - -# ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.sponsored_intelligence.si_context_use import SiContextUse +from adcp.types._str_enum import StrEnum + -# Explicit exports -__all__ = ["SiContextUse"] +class SiContextUse(StrEnum): + presentation_only = 'presentation_only' + comparison_set = 'comparison_set' + reasoning_context = 'reasoning_context' diff --git a/src/adcp/types/domains/sponsored_intelligence/si_get_offering_request.py b/src/adcp/types/domains/sponsored_intelligence/si_get_offering_request.py index 8fcc17e15..d7728d5fc 100644 --- a/src/adcp/types/domains/sponsored_intelligence/si_get_offering_request.py +++ b/src/adcp/types/domains/sponsored_intelligence/si_get_offering_request.py @@ -1,20 +1,39 @@ -"""Types declared by the AdCP ``sponsored_intelligence/si_get_offering_request`` schema. +# generated by datamodel-codegen: +# filename: sponsored_intelligence/si_get_offering_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.sponsored_intelligence.si_get_offering_request import +from adcp.types.base import AdcpRequest, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field, StrictBool + +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope -from adcp.types.generated_poc.sponsored_intelligence.si_get_offering_request import ( - SiGetOfferingRequest, -) -# Explicit exports -__all__ = ["SiGetOfferingRequest"] +class SiGetOfferingRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + offering_id: Annotated[ + str, Field(description='Offering identifier from the catalog to get details for') + ] + intent: Annotated[ + str | None, + Field( + description="Optional natural language description of user intent for personalized results (e.g., 'mens size 14 near Cincinnati'). Must be anonymous - no PII." + ), + ] = None + context: context_1.ContextObject | None = None + include_products: Annotated[ + StrictBool | None, Field(description='Whether to include matching products in the response') + ] = False + product_limit: Annotated[ + SchemaInt | None, + Field(description='Maximum number of matching products to return', ge=1, le=50), + ] = 5 + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/sponsored_intelligence/si_get_offering_response.py b/src/adcp/types/domains/sponsored_intelligence/si_get_offering_response.py index ebb772e7f..991dbce26 100644 --- a/src/adcp/types/domains/sponsored_intelligence/si_get_offering_response.py +++ b/src/adcp/types/domains/sponsored_intelligence/si_get_offering_response.py @@ -1,22 +1,128 @@ -"""Types declared by the AdCP ``sponsored_intelligence/si_get_offering_response`` schema. +# generated by datamodel-codegen: +# filename: sponsored_intelligence/si_get_offering_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.sponsored_intelligence.si_get_offering_response import +from adcp.types.base import AdcpResponse, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictBool + +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import offering_availability_status +from . import si_sponsored_context + + +class Offering(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + offering_id: Annotated[str | None, Field(description='Offering identifier')] = None + title: Annotated[str | None, Field(description='Offering title')] = None + summary: Annotated[str | None, Field(description='Brief summary of the offering')] = None + tagline: Annotated[str | None, Field(description='Short promotional tagline')] = None + expires_at: Annotated[AwareDatetime | None, Field(description='When this offering expires')] = ( + None + ) + availability_status: Annotated[ + offering_availability_status.OfferingAvailabilityStatus | None, + Field( + description="Machine-readable availability state for the offering. Optional; when omitted, derive availability from the top-level 'available' boolean and 'unavailable_reason'." + ), + ] = None + price_hint: Annotated[ + str | None, Field(description="Price indication (e.g., 'from $199', '50% off')") + ] = None + image_url: Annotated[AnyUrl | None, Field(description='Hero image for the offering')] = None + landing_url: Annotated[AnyUrl | None, Field(description='Landing page URL')] = None + + +class MatchingProduct(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + product_id: Annotated[str, Field(description='Product identifier')] + name: Annotated[str, Field(description='Product name')] + price: Annotated[str | None, Field(description="Display price (e.g., '$129', '$89.99')")] = None + original_price: Annotated[str | None, Field(description='Original price if on sale')] = None + image_url: Annotated[AnyUrl | None, Field(description='Product image')] = None + availability_summary: Annotated[ + str | None, + Field( + description="Brief availability info (e.g., 'In stock', 'Size 14 available', '3 left')" + ), + ] = None + availability_status: Annotated[ + offering_availability_status.OfferingAvailabilityStatus | None, + Field( + description='Machine-readable availability state for this product. Structured counterpart to availability_summary.' + ), + ] = None + url: Annotated[AnyUrl | None, Field(description='Product detail page URL')] = None -from adcp.types.generated_poc.sponsored_intelligence.si_get_offering_response import ( - MatchingProduct, - Offering, - SiGetOfferingResponse, -) -# Explicit exports -__all__ = ["MatchingProduct", "Offering", "SiGetOfferingResponse"] +class SiGetOfferingResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + available: Annotated[ + StrictBool, Field(description='Whether the offering is currently available') + ] + offering_token: Annotated[ + str | None, + Field( + description="Token to pass to si_initiate_session for session continuity. Brand stores the full query context server-side (products shown, order, context) so they can resolve references like 'the second one' when the session starts." + ), + ] = None + ttl_seconds: Annotated[ + SchemaInt | None, + Field( + description='How long this offering information is valid (seconds). Host should re-fetch after TTL expires.', + ge=0, + ), + ] = None + checked_at: Annotated[ + AwareDatetime | None, Field(description='When this offering information was retrieved') + ] = None + offering: Annotated[Offering | None, Field(description='Offering details')] = None + matching_products: Annotated[ + list[MatchingProduct] | None, + Field( + description='Products matching the request context. Only included if include_products was true.' + ), + ] = None + sponsored_context: Annotated[ + si_sponsored_context.SiSponsoredContext | None, + Field( + description='Declaration for the sponsored context carried by this offering response. When present, it applies to the returned offering and matching_products package as a whole unless a future extension narrows the declaration to individual items. Hosts MUST either honor the declared context_use and disclosure_obligation or reject the context before using it.' + ), + ] = None + total_matching: Annotated[ + SchemaInt | None, + Field( + description='Total number of products matching the context (may be more than returned in matching_products)', + ge=0, + ), + ] = None + unavailable_reason: Annotated[ + str | None, + Field( + description="If not available, why (e.g., 'expired', 'sold_out', 'region_restricted')" + ), + ] = None + alternative_offering_ids: Annotated[ + list[str] | None, + Field(description='Alternative offerings to consider if this one is unavailable'), + ] = None + errors: Annotated[ + list[error.Error] | None, Field(description='Errors during offering lookup') + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/sponsored_intelligence/si_identity.py b/src/adcp/types/domains/sponsored_intelligence/si_identity.py index 62ca33732..0dc750eac 100644 --- a/src/adcp/types/domains/sponsored_intelligence/si_identity.py +++ b/src/adcp/types/domains/sponsored_intelligence/si_identity.py @@ -1,24 +1,80 @@ -"""Types declared by the AdCP ``sponsored_intelligence/si_identity`` schema. +# generated by datamodel-codegen: +# filename: sponsored_intelligence/si_identity.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.sponsored_intelligence.si_identity import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, StrictBool -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.sponsored_intelligence.si_identity import ( - ConsentScopeEnum, - PrivacyPolicyAcknowledged, - ShippingAddress, - SiIdentity, - User, -) +class ConsentScopeEnum(StrEnum): + name = 'name' + email = 'email' + shipping_address = 'shipping_address' + phone = 'phone' + locale = 'locale' + + +class PrivacyPolicyAcknowledged(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + brand_policy_url: Annotated[ + AnyUrl | None, Field(description="URL to brand's privacy policy") + ] = None + brand_policy_version: Annotated[ + str | None, Field(description='Version of policy acknowledged') + ] = None + + +class ShippingAddress(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + street: str | None = None + city: str | None = None + state: str | None = None + postal_code: str | None = None + country: str | None = None + + +class User(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + email: Annotated[EmailStr | None, Field(description="User's email address")] = None + name: Annotated[str | None, Field(description="User's display name")] = None + locale: Annotated[str | None, Field(description="User's locale (e.g., en-US)")] = None + phone: Annotated[str | None, Field(description="User's phone number")] = None + shipping_address: Annotated[ + ShippingAddress | None, Field(description="User's shipping address for accurate pricing") + ] = None + -# Explicit exports -__all__ = ["ConsentScopeEnum", "PrivacyPolicyAcknowledged", "ShippingAddress", "SiIdentity", "User"] +class SiIdentity(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + consent_granted: Annotated[ + StrictBool, Field(description='Whether user consented to share identity') + ] + consent_timestamp: Annotated[ + AwareDatetime | None, Field(description='When consent was granted (ISO 8601)') + ] = None + consent_scope: Annotated[ + list[ConsentScopeEnum] | None, Field(description='What data was consented to share') + ] = None + privacy_policy_acknowledged: Annotated[ + PrivacyPolicyAcknowledged | None, Field(description='Brand privacy policy acknowledgment') + ] = None + user: Annotated[ + User | None, Field(description='User data (only present if consent_granted is true)') + ] = None + anonymous_session_id: Annotated[ + str | None, + Field(description='Session ID for anonymous users (when consent_granted is false)'), + ] = None diff --git a/src/adcp/types/domains/sponsored_intelligence/si_initiate_session_request.py b/src/adcp/types/domains/sponsored_intelligence/si_initiate_session_request.py index bebdeb273..4a42ae51f 100644 --- a/src/adcp/types/domains/sponsored_intelligence/si_initiate_session_request.py +++ b/src/adcp/types/domains/sponsored_intelligence/si_initiate_session_request.py @@ -1,20 +1,68 @@ -"""Types declared by the AdCP ``sponsored_intelligence/si_initiate_session_request`` schema. +# generated by datamodel-codegen: +# filename: sponsored_intelligence/si_initiate_session_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.sponsored_intelligence.si_initiate_session_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from pydantic import ConfigDict, Field + +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope +from . import si_capabilities, si_identity, si_sponsored_context_receipt -from adcp.types.generated_poc.sponsored_intelligence.si_initiate_session_request import ( - SiInitiateSessionRequest, -) -# Explicit exports -__all__ = ["SiInitiateSessionRequest"] +class SiInitiateSessionRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + intent: Annotated[ + str, + Field( + description='Natural language description of user intent — the conversation handoff from the host describing what the user needs from the brand agent' + ), + ] + context: context_1.ContextObject | None = None + identity: si_identity.SiIdentity + media_buy_id: Annotated[ + str | None, Field(description='AdCP media buy ID if session was triggered by advertising') + ] = None + placement: Annotated[ + str | None, + Field( + description="Where this session was triggered (e.g., 'chatgpt_search', 'claude_chat')" + ), + ] = None + offering_id: Annotated[ + str | None, Field(description='Brand-specific offering identifier to apply') + ] = None + supported_capabilities: Annotated[ + si_capabilities.SiCapabilities | None, + Field(description='What capabilities the host supports'), + ] = None + offering_token: Annotated[ + str | None, + Field( + description="Token from si_get_offering response for session continuity. Brand uses this to recall what products were shown to the user, enabling natural references like 'the second one' or 'that blue shoe'." + ), + ] = None + sponsored_context_receipt: Annotated[ + si_sponsored_context_receipt.SiSponsoredContextReceipt | None, + Field( + description='Host receipt for sponsored context accepted from a prior si_get_offering response or other pre-session context package. This records the accepted context_use, disclosure commitment, paying_principal, and host receipt for audit.' + ), + ] = None + idempotency_key: Annotated[ + str, + Field( + description='Client-generated unique key for this request. Prevents duplicate session creation on retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/sponsored_intelligence/si_initiate_session_response.py b/src/adcp/types/domains/sponsored_intelligence/si_initiate_session_response.py index 857cb2d43..aca8ffc4b 100644 --- a/src/adcp/types/domains/sponsored_intelligence/si_initiate_session_response.py +++ b/src/adcp/types/domains/sponsored_intelligence/si_initiate_session_response.py @@ -1,21 +1,70 @@ -"""Types declared by the AdCP ``sponsored_intelligence/si_initiate_session_response`` schema. +# generated by datamodel-codegen: +# filename: sponsored_intelligence/si_initiate_session_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.sponsored_intelligence.si_initiate_session_response import +from adcp.types.base import AdcpResponse, SchemaInt -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field + +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import si_session_status +from . import si_capabilities, si_sponsored_context, si_ui_element + + +class Response(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + message: Annotated[str | None, Field(description='Conversational message from brand agent')] = ( + None + ) + ui_elements: Annotated[ + list[si_ui_element.SiUiElement] | None, Field(description='Visual components to render') + ] = None -from adcp.types.generated_poc.sponsored_intelligence.si_initiate_session_response import ( - Response, - SiInitiateSessionResponse, -) -# Explicit exports -__all__ = ["Response", "SiInitiateSessionResponse"] +class SiInitiateSessionResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + session_id: Annotated[ + str, Field(description='Unique session identifier for subsequent messages') + ] + response: Annotated[Response | None, Field(description="Brand agent's initial response")] = None + negotiated_capabilities: Annotated[ + si_capabilities.SiCapabilities | None, + Field(description='Intersection of brand and host capabilities for this session'), + ] = None + sponsored_context: Annotated[ + si_sponsored_context.SiSponsoredContext | None, + Field( + description='Declaration for sponsored context carried by the initial brand-agent response. Hosts MUST either honor the declared context_use and disclosure_obligation or reject the context before presenting, comparing, or otherwise using it.' + ), + ] = None + session_status: Annotated[ + si_session_status.SiSessionStatus, + Field( + description='Current session lifecycle state. Returned in initiation, message, and termination responses.' + ), + ] + session_ttl_seconds: Annotated[ + SchemaInt | None, + Field( + description='Session inactivity timeout in seconds. After this duration without a message, the brand agent may terminate the session. Hosts SHOULD warn users before timeout when possible.', + ge=1, + ), + ] = None + errors: Annotated[ + list[error.Error] | None, Field(description='Errors during session initiation') + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/sponsored_intelligence/si_send_message_request.py b/src/adcp/types/domains/sponsored_intelligence/si_send_message_request.py index 881f2a1b7..2a8e28ea4 100644 --- a/src/adcp/types/domains/sponsored_intelligence/si_send_message_request.py +++ b/src/adcp/types/domains/sponsored_intelligence/si_send_message_request.py @@ -1,21 +1,69 @@ -"""Types declared by the AdCP ``sponsored_intelligence/si_send_message_request`` schema. +# generated by datamodel-codegen: +# filename: sponsored_intelligence/si_send_message_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.sponsored_intelligence.si_send_message_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Any + +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, model_validator + +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope +from . import si_sponsored_context_receipt + + +class ActionResponse(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + action: Annotated[str | None, Field(description='The action that was triggered')] = None + payload: Annotated[ + dict[str, Any] | None, Field(description='Action-specific response data') + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.sponsored_intelligence.si_send_message_request import ( - ActionResponse, - SiSendMessageRequest, -) +class SiSendMessageRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + idempotency_key: Annotated[ + str, + Field( + description='Client-generated unique key for at-most-once execution. Each conversational turn is a distinct mutation of session transcript — without this key, a timeout-and-retry produces a duplicate turn and a duplicate model response. MUST be unique per (seller, request) pair. Use a fresh UUID v4 for each user turn.', + max_length=255, + min_length=16, + pattern='^[A-Za-z0-9_.:-]{16,255}$', + ), + ] + session_id: Annotated[str, Field(description='Active session identifier')] + message: Annotated[str | None, Field(description="User's message to the brand agent")] = None + action_response: Annotated[ + ActionResponse | None, + Field(description='Response to a previous action_button (e.g., user clicked checkout)'), + ] = None + sponsored_context_receipt: Annotated[ + si_sponsored_context_receipt.SiSponsoredContextReceipt | None, + Field( + description="Host receipt for sponsored context accepted from a prior SI response in this session. This gives the brand/seller an audit-visible record of the host's accepted use mode and disclosure commitment for that context." + ), + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None -# Explicit exports -__all__ = ["ActionResponse", "SiSendMessageRequest"] + @model_validator(mode='after') + def _require_schema_required_group(self) -> SiSendMessageRequest: + # ``required`` asks whether the caller supplied the field, which is what + # model_fields_set answers. An explicit null is a supplied value — on a + # mutation input it is the command to clear — and a default the caller + # never sent is not. + for group in (('message',), ('action_response',),): + if all(name in self.model_fields_set for name in group): + return self + raise ValueError( + 'SiSendMessageRequest requires at least one of these field groups: message | action_response' + ) diff --git a/src/adcp/types/domains/sponsored_intelligence/si_send_message_response.py b/src/adcp/types/domains/sponsored_intelligence/si_send_message_response.py index e29ed5ed2..809393763 100644 --- a/src/adcp/types/domains/sponsored_intelligence/si_send_message_response.py +++ b/src/adcp/types/domains/sponsored_intelligence/si_send_message_response.py @@ -1,34 +1,127 @@ -"""Types declared by the AdCP ``sponsored_intelligence/si_send_message_response`` schema. +# generated by datamodel-codegen: +# filename: sponsored_intelligence/si_send_message_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.sponsored_intelligence.si_send_message_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any + +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictFloat + +from ..a2ui import surface as surface_1 +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import si_session_status +from . import si_sponsored_context, si_ui_element + + +class Type(StrEnum): + transaction = 'transaction' + complete = 'complete' + + +class Price(AdCPBaseModel): + amount: StrictFloat | None = None + currency: str | None = None + + +class Intent(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + action: Annotated[str | None, Field(description="The commerce action (e.g., 'purchase')")] = ( + None + ) + product: Annotated[dict[str, Any] | None, Field(description='Product details for checkout')] = ( + None + ) + price: Annotated[Price | None, Field(description='Price information')] = None + + +class ContextForCheckout(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + conversation_summary: Annotated[ + str | None, Field(description='Summary of the conversation leading to purchase') + ] = None + applied_offers: Annotated[ + list[str] | None, Field(description='Offer IDs that were applied during the conversation') + ] = None + + +class Handoff(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + type: Annotated[ + Type | None, + Field( + description='Type of handoff: transaction (ready for ACP checkout) or complete (conversation done)' + ), + ] = None + intent: Annotated[ + Intent | None, + Field(description='For transaction handoffs: what the user wants to purchase'), + ] = None + context_for_checkout: Annotated[ + ContextForCheckout | None, Field(description='Context to pass to ACP for seamless checkout') + ] = None + + +class Response(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + message: Annotated[str | None, Field(description='Conversational message from brand agent')] = ( + None + ) + surface: Annotated[ + surface_1.A2UiSurface | None, Field(description='A2UI surface with interactive components') + ] = None + ui_elements: Annotated[ + list[si_ui_element.SiUiElement] | None, + Field( + deprecated=True, + description='Visual components to render (DEPRECATED: use surface instead)', + ), + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.sponsored_intelligence.si_send_message_response import ( - ContextForCheckout, - Handoff, - Intent, - Price, - Response, - SiSendMessageResponse, - Type, -) - -# Explicit exports -__all__ = [ - "ContextForCheckout", - "Handoff", - "Intent", - "Price", - "Response", - "SiSendMessageResponse", - "Type", -] +class SiSendMessageResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + session_id: Annotated[str, Field(description='Session identifier')] + response: Annotated[Response | None, Field(description="Brand agent's response")] = None + mcp_resource_uri: Annotated[ + str | None, + Field( + description='MCP resource URI for hosts with MCP Apps support (e.g., ui://si/session-abc123)' + ), + ] = None + sponsored_context: Annotated[ + si_sponsored_context.SiSponsoredContext | None, + Field( + description='Declaration for sponsored context carried by this brand-agent response. Hosts MUST either honor the declared context_use and disclosure_obligation or reject the context before presenting, comparing, or otherwise using it.' + ), + ] = None + session_status: Annotated[ + si_session_status.SiSessionStatus, + Field( + description='Current session status. On a successful response, one of: active, pending_handoff, or complete. Terminated sessions return error codes (SESSION_NOT_FOUND or SESSION_TERMINATED) instead of a success response.' + ), + ] + handoff: Annotated[ + Handoff | None, Field(description='Handoff request when session_status is pending_handoff') + ] = None + errors: list[error.Error] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/sponsored_intelligence/si_sponsored_context.py b/src/adcp/types/domains/sponsored_intelligence/si_sponsored_context.py index 424fd21e6..7264f9df4 100644 --- a/src/adcp/types/domains/sponsored_intelligence/si_sponsored_context.py +++ b/src/adcp/types/domains/sponsored_intelligence/si_sponsored_context.py @@ -1,38 +1,155 @@ -"""Types declared by the AdCP ``sponsored_intelligence/si_sponsored_context`` schema. +# generated by datamodel-codegen: +# filename: sponsored_intelligence/si_sponsored_context.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.sponsored_intelligence.si_sponsored_context import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictBool + +from ..core import brand_ref +from ..core import ext as ext_1 +from . import si_context_use + + +class Account(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + account_id: Annotated[ + str, Field(description='Seller-assigned account identifier for the paying principal.') + ] + + +class Timing(StrEnum): + before_use = 'before_use' + at_first_influenced_output = 'at_first_influenced_output' + near_each_influenced_output = 'near_each_influenced_output' + + +class Proximity(StrEnum): + session_level = 'session_level' + near_rendered_unit = 'near_rendered_unit' + near_influenced_output = 'near_influenced_output' + + +class Jurisdiction(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + country: Annotated[str, Field(description='ISO 3166-1 alpha-2 country code.')] + region: Annotated[str | None, Field(description='Optional sub-national region code.')] = None + regulation: Annotated[str, Field(description='Regulation or policy identifier.')] + + +class DisclosureObligation(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + required: Annotated[ + StrictBool, + Field( + description='Whether the declaring party requires disclosure for this sponsored context.' + ), + ] + label_text: Annotated[ + str | None, + Field( + description='Disclosure label text the host should render when disclosure is required.' + ), + ] = None + timing: Annotated[ + Timing | None, + Field( + description="When the disclosure must be presented relative to the sponsored context's influence." + ), + ] = None + proximity: Annotated[ + Proximity | None, + Field(description='Where the disclosure should appear relative to the affected output.'), + ] = None + jurisdictions: Annotated[ + list[Jurisdiction] | None, + Field( + description='Jurisdictions where this declared disclosure obligation applies.', + min_length=1, + ), + ] = None + + +class Role(StrEnum): + brand_agent = 'brand_agent' + seller = 'seller' + network = 'network' + platform = 'platform' + + +class DeclaredBy(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + agent_url: Annotated[ + AnyUrl | None, Field(description='HTTPS URL of the declaring agent or service.') + ] = None + role: Annotated[Role, Field(description='Role of the declaring party.')] + + +class PayingPrincipal(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + brand: Annotated[ + brand_ref.BrandReference, + Field(description='Brand economically accountable for the sponsored context.'), + ] + account: Annotated[ + Account | None, + Field( + description='Optional seller-assigned account context for the paying principal. This intentionally carries only an account_id so the canonical economic principal remains paying_principal.brand.' + ), + ] = None + operator: Annotated[ + str | None, + Field( + description='Domain of the operator acting for the paying principal, when different from the brand domain.', + 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='Human-readable label for disclosure rendering. The canonical identity remains the brand/account reference.' + ), + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.sponsored_intelligence.si_sponsored_context import ( - Account, - DeclaredBy, - DisclosureObligation, - Jurisdiction, - PayingPrincipal, - Proximity, - Role, - SiSponsoredContext, - Timing, -) - -# Explicit exports -__all__ = [ - "Account", - "DeclaredBy", - "DisclosureObligation", - "Jurisdiction", - "PayingPrincipal", - "Proximity", - "Role", - "SiSponsoredContext", - "Timing", -] +class SiSponsoredContext(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + paying_principal: Annotated[ + PayingPrincipal, + Field( + description="Economic accountability fact: the brand that funded or sponsored this context, with optional account/operator context. This identifies who paid for the sponsored context; it is distinct from the host's later receipt and use commitment." + ), + ] + context_use: Annotated[ + si_context_use.SiContextUse, + Field(description='Declared host-side use mode for this sponsored context.'), + ] + disclosure_obligation: Annotated[ + DisclosureObligation, + Field( + description='Disclosure obligation the receiving host must either accept and satisfy or reject before using this sponsored context. This is a declared obligation and audit input, not a protocol-level legal determination.' + ), + ] + declared_at: Annotated[ + AwareDatetime | None, Field(description='When this sponsored-context declaration was made.') + ] = None + declared_by: Annotated[ + DeclaredBy | None, Field(description='Agent or service that attached the declaration.') + ] = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/sponsored_intelligence/si_sponsored_context_receipt.py b/src/adcp/types/domains/sponsored_intelligence/si_sponsored_context_receipt.py index e066f14a7..4246a0e5f 100644 --- a/src/adcp/types/domains/sponsored_intelligence/si_sponsored_context_receipt.py +++ b/src/adcp/types/domains/sponsored_intelligence/si_sponsored_context_receipt.py @@ -1,24 +1,101 @@ -"""Types declared by the AdCP ``sponsored_intelligence/si_sponsored_context_receipt`` schema. +# generated by datamodel-codegen: +# filename: sponsored_intelligence/si_sponsored_context_receipt.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.sponsored_intelligence.si_sponsored_context_receipt import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AwareDatetime, ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import ext as ext_1 +from . import si_context_use, si_sponsored_context + + +class Status(StrEnum): + accepted = 'accepted' + rejected = 'rejected' + + +class Status46(StrEnum): + accepted = 'accepted' + not_required = 'not_required' + + +class DisclosureCommitment(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + status: Annotated[ + Status46, + Field( + description="Host commitment status for the disclosure obligation. Use accepted when the declaration requires disclosure and the host will satisfy it; use not_required only when the declaration's disclosure_obligation.required is false. A host that will not satisfy a required disclosure rejects the sponsored context." + ), + ] + label_text: Annotated[ + str | None, + Field( + description='Disclosure label text the host committed to render, if different from or copied from the declaration.' + ), + ] = None + notes: Annotated[ + str | None, Field(description='Optional host explanation for the disclosure commitment.') + ] = None + + +class HostReceipt(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + status: Annotated[ + Status, + Field(description='Whether the host accepted the declared sponsored context for use.'), + ] + accepted_context_use: Annotated[ + si_context_use.SiContextUse | None, + Field( + description="Use mode the host accepted. When host_receipt.status is accepted, this MUST match the declaration's context_use; a host that cannot honor the declared use mode rejects the sponsored context instead of down-scoping it." + ), + ] = None + received_at: Annotated[ + AwareDatetime, Field(description='When the host received the sponsored context.') + ] + host_surface: Annotated[ + str | None, + Field( + description='Host-defined surface or placement where the context was accepted, such as an assistant session, search result page, or comparison module.' + ), + ] = None + disclosure_commitment: Annotated[ + DisclosureCommitment | None, + Field( + description='How the host committed to handle the declared disclosure obligation. Required when host_receipt.status is accepted.' + ), + ] = None + rejection_reason: Annotated[ + str | None, + Field( + description='Optional explanation when status is rejected, for example unsupported context_use or inability to satisfy the disclosure obligation.' + ), + ] = None -from adcp.types.generated_poc.sponsored_intelligence.si_sponsored_context_receipt import ( - DisclosureCommitment, - HostReceipt, - SiSponsoredContextReceipt, - Status, - Status46, -) -# Explicit exports -__all__ = ["DisclosureCommitment", "HostReceipt", "SiSponsoredContextReceipt", "Status", "Status46"] +class SiSponsoredContextReceipt(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + sponsored_context: Annotated[ + si_sponsored_context.SiSponsoredContext, + Field( + description='The sponsored-context declaration the host received and is accepting or rejecting.' + ), + ] + host_receipt: Annotated[ + HostReceipt, + Field( + description='Receiving-surface accountability fact: the use mode the host accepted and committed to honor for this sponsored context.' + ), + ] + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/sponsored_intelligence/si_terminate_session_request.py b/src/adcp/types/domains/sponsored_intelligence/si_terminate_session_request.py index 04de717b5..42e4630d1 100644 --- a/src/adcp/types/domains/sponsored_intelligence/si_terminate_session_request.py +++ b/src/adcp/types/domains/sponsored_intelligence/si_terminate_session_request.py @@ -1,30 +1,65 @@ -"""Types declared by the AdCP ``sponsored_intelligence/si_terminate_session_request`` schema. +# generated by datamodel-codegen: +# filename: sponsored_intelligence/si_terminate_session_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.sponsored_intelligence.si_terminate_session_request import +from adcp.types.base import AdcpRequest -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any + +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field + +from ..core import context as context_1 +from ..core import ext as ext_1 +from ..core.version_envelope import AdcpVersionEnvelope + + +class Reason(StrEnum): + handoff_transaction = 'handoff_transaction' + handoff_complete = 'handoff_complete' + user_exit = 'user_exit' + session_timeout = 'session_timeout' + host_terminated = 'host_terminated' + + +class Action(StrEnum): + purchase = 'purchase' + subscribe = 'subscribe' + + +class TransactionIntent(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + action: Action | None = None + product: Annotated[dict[str, Any] | None, Field(description='Product/service details')] = None + + +class TerminationContext(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + summary: Annotated[str | None, Field(description='Summary of the conversation')] = None + transaction_intent: Annotated[ + TransactionIntent | None, + Field(description='For handoff_transaction - what user wants to buy'), + ] = None + cause: Annotated[ + str | None, Field(description='For host_terminated - why host ended session') + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.sponsored_intelligence.si_terminate_session_request import ( - Action, - Reason, - SiTerminateSessionRequest, - TerminationContext, - TransactionIntent, -) - -# Explicit exports -__all__ = [ - "Action", - "Reason", - "SiTerminateSessionRequest", - "TerminationContext", - "TransactionIntent", -] +class SiTerminateSessionRequest(AdcpRequest, AdcpVersionEnvelope): + model_config = ConfigDict( + extra='allow', + ) + session_id: Annotated[str, Field(description='Session identifier to terminate')] + reason: Annotated[Reason, Field(description='Reason for termination')] + termination_context: Annotated[ + TerminationContext | None, Field(description='Context for the termination') + ] = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/sponsored_intelligence/si_terminate_session_response.py b/src/adcp/types/domains/sponsored_intelligence/si_terminate_session_response.py index 041005e27..5c79336e8 100644 --- a/src/adcp/types/domains/sponsored_intelligence/si_terminate_session_response.py +++ b/src/adcp/types/domains/sponsored_intelligence/si_terminate_session_response.py @@ -1,23 +1,89 @@ -"""Types declared by the AdCP ``sponsored_intelligence/si_terminate_session_response`` schema. +# generated by datamodel-codegen: +# filename: sponsored_intelligence/si_terminate_session_response.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.sponsored_intelligence.si_terminate_session_response import +from adcp.types.base import AdcpResponse -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictBool + +from ..core import context as context_1 +from ..core import error +from ..core import ext as ext_1 +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import si_session_status + + +class AcpHandoff(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + checkout_url: Annotated[ + AnyUrl | None, + Field( + description="Brand's ACP checkout endpoint. Hosts MUST validate this is HTTPS before opening." + ), + ] = None + checkout_token: Annotated[ + str | None, + Field( + description='Opaque token for the checkout flow. The host passes this to the checkout endpoint to correlate the SI session with the transaction.' + ), + ] = None + payload: Annotated[ + dict[str, Any] | None, + Field( + description='Rich checkout context to pass to the ACP endpoint (product details, applied offers, pricing). Alternative to checkout_token for integrations that need structured data.' + ), + ] = None + expires_at: Annotated[ + AwareDatetime | None, + Field( + description='When this handoff data expires. Hosts should initiate checkout before this time.' + ), + ] = None + + +class Action(StrEnum): + save_for_later = 'save_for_later' + set_reminder = 'set_reminder' + subscribe_updates = 'subscribe_updates' + none = 'none' + + +class FollowUp(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + action: Action | None = None + data: Annotated[dict[str, Any] | None, Field(description='Data for follow-up action')] = None -from adcp.types.generated_poc.sponsored_intelligence.si_terminate_session_response import ( - AcpHandoff, - Action, - FollowUp, - SiTerminateSessionResponse, -) -# Explicit exports -__all__ = ["AcpHandoff", "Action", "FollowUp", "SiTerminateSessionResponse"] +class SiTerminateSessionResponse(AdcpResponse, AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + session_id: Annotated[str, Field(description='Terminated session identifier')] + terminated: Annotated[ + StrictBool, Field(description='Whether session was successfully terminated') + ] + session_status: Annotated[ + si_session_status.SiSessionStatus | None, + Field( + description="Resulting session state. 'complete' for handoff_transaction/handoff_complete, 'terminated' for user_exit/session_timeout/host_terminated." + ), + ] = None + acp_handoff: Annotated[ + AcpHandoff | None, + Field(description='ACP checkout handoff data. Present when reason is handoff_transaction.'), + ] = None + follow_up: Annotated[FollowUp | None, Field(description='Suggested follow-up actions')] = None + errors: list[error.Error] | None = None + context: context_1.ContextObject | None = None + ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/domains/sponsored_intelligence/si_ui_element.py b/src/adcp/types/domains/sponsored_intelligence/si_ui_element.py index 4db2a2c3d..92556de6d 100644 --- a/src/adcp/types/domains/sponsored_intelligence/si_ui_element.py +++ b/src/adcp/types/domains/sponsored_intelligence/si_ui_element.py @@ -1,18 +1,30 @@ -"""Types declared by the AdCP ``sponsored_intelligence/si_ui_element`` schema. +# generated by datamodel-codegen: +# filename: sponsored_intelligence/si_ui_element.json +# timestamp: 2026-05-02T19:36:29+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.sponsored_intelligence.si_ui_element import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.sponsored_intelligence.si_ui_element import SiUiElement, Type +class Type(StrEnum): + text = 'text' + link = 'link' + image = 'image' + product_card = 'product_card' + carousel = 'carousel' + action_button = 'action_button' + app_handoff = 'app_handoff' + integration_actions = 'integration_actions' + -# Explicit exports -__all__ = ["SiUiElement", "Type"] +class SiUiElement(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + type: Annotated[Type, Field(description='Component type')] + data: Annotated[dict[str, Any] | None, Field(description='Component-specific data')] = None diff --git a/src/adcp/types/domains/trusted_match/__init__.py b/src/adcp/types/domains/trusted_match/__init__.py index 818fa01ac..86d06402b 100644 --- a/src/adcp/types/domains/trusted_match/__init__.py +++ b/src/adcp/types/domains/trusted_match/__init__.py @@ -9,15 +9,15 @@ it from its own schema's module, ``adcp.types.domains.trusted_match.``. Nothing here is renamed. -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:33:59 UTC +Auto-generated from the generated domain tree. DO NOT EDIT MANUALLY. +Generation date: 2026-10-04 18:45:11 UTC """ # ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.trusted_match.available_package import AvailablePackage -from adcp.types.generated_poc.trusted_match.context_match_request import ( +from adcp.types.domains.trusted_match.available_package import AvailablePackage +from adcp.types.domains.trusted_match.context_match_request import ( ArtifactRef, ContextMatchRequest, ContextSignals, @@ -27,12 +27,12 @@ Sentiment, Type, ) -from adcp.types.generated_poc.trusted_match.context_match_response import ( +from adcp.types.domains.trusted_match.context_match_response import ( ContextMatchResponseRouterPublisher, SignalsByProvider, ) -from adcp.types.generated_poc.trusted_match.error import Code, TmpError -from adcp.types.generated_poc.trusted_match.identity_match_request import ( +from adcp.types.domains.trusted_match.error import Code, TmpError +from adcp.types.domains.trusted_match.identity_match_request import ( Attestation, Consent, Identity, @@ -40,20 +40,20 @@ SealedCredential, VerificationLevel, ) -from adcp.types.generated_poc.trusted_match.identity_match_response import ( +from adcp.types.domains.trusted_match.identity_match_response import ( IdentityMatchResponse, IdentityMatchResponseRouterPublisher, TmpxProviders, ) -from adcp.types.generated_poc.trusted_match.offer import Offer -from adcp.types.generated_poc.trusted_match.offer_price import Model, OfferPrice -from adcp.types.generated_poc.trusted_match.provider_context_match_response import ( +from adcp.types.domains.trusted_match.offer import Offer +from adcp.types.domains.trusted_match.offer_price import Model, OfferPrice +from adcp.types.domains.trusted_match.provider_context_match_response import ( ContextMatchResponseProviderRouter, ) -from adcp.types.generated_poc.trusted_match.provider_identity_match_response import ( +from adcp.types.domains.trusted_match.provider_identity_match_response import ( IdentityMatchResponseProviderRouter, ) -from adcp.types.generated_poc.trusted_match.provider_registration import ( +from adcp.types.domains.trusted_match.provider_registration import ( Country, Status, TmpProviderRegistration, @@ -61,11 +61,11 @@ TmpProviderRegistration2, TmpxSlot, ) -from adcp.types.generated_poc.trusted_match.publisher_targeting_kv_config import ( +from adcp.types.domains.trusted_match.publisher_targeting_kv_config import ( PublisherTargetingKvMapping, ) -from adcp.types.generated_poc.trusted_match.publisher_tmpx_config import PublisherTmpxMacroMapping -from adcp.types.generated_poc.trusted_match.tmpx_chunk import TmpxChunk +from adcp.types.domains.trusted_match.publisher_tmpx_config import PublisherTmpxMacroMapping +from adcp.types.domains.trusted_match.tmpx_chunk import TmpxChunk # Explicit exports __all__ = [ diff --git a/src/adcp/types/domains/trusted_match/available_package.py b/src/adcp/types/domains/trusted_match/available_package.py index aebdce0b1..a7c3c7ab9 100644 --- a/src/adcp/types/domains/trusted_match/available_package.py +++ b/src/adcp/types/domains/trusted_match/available_package.py @@ -1,18 +1,46 @@ -"""Types declared by the AdCP ``trusted_match/available_package`` schema. +# generated by datamodel-codegen: +# filename: trusted_match/available_package.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.trusted_match.available_package import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import catalog, format_id, package_format_snapshot, seller_agent_ref -from adcp.types.generated_poc.trusted_match.available_package import AvailablePackage -# Explicit exports -__all__ = ["AvailablePackage"] +class AvailablePackage(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + package_id: Annotated[str, Field(description='Unique identifier for the package')] + media_buy_id: Annotated[str, Field(description='Media buy that this package belongs to')] + seller_agent: Annotated[ + seller_agent_ref.SellerAgentReference, + Field( + description="The seller agent that owns this package. `agent_url` MUST match one of `authorized_agents[].url` in the publisher's adagents.json authoritative for every property this package may serve. Providers SHOULD validate at sync time and reject mismatches with `seller_not_authorized`. Cached alongside the package and used for offer attribution, per-seller observability, and dispute resolution — not for request-time filtering." + ), + ] + format_ids: Annotated[ + list[format_id.FormatReferenceStructuredObject] | None, + Field( + deprecated=True, + description='Deprecated in AdCP 3.2; removed in AdCP 4.0. Legacy named-format identifiers eligible for this package. Use canonical `format_options`.', + ), + ] = None + format_options: Annotated[ + list[package_format_snapshot.PackageFormatSnapshot] | None, + Field( + description='Immutable seller-resolved PackageFormatSnapshots eligible for this package. A sender projects this field only when the Trusted Match transport profile supports the highest AdCP release version of every field in every snapshot. It MUST NOT strip an unsupported selector or digest field from a snapshot; for an older or version-unknown provider, omit the entire contract-bearing snapshot and do not claim production-path evidence from a legacy projection.', + min_length=1, + ), + ] = None + catalogs: Annotated[ + list[catalog.Catalog] | None, + Field( + description="The buyer's catalogs attached to this package, with selectors (ids, gtins, tags, category, query) scoping which items are in play. References synced catalogs by catalog_id. The provider resolves items from its cached copy." + ), + ] = None diff --git a/src/adcp/types/domains/trusted_match/context_match_request.py b/src/adcp/types/domains/trusted_match/context_match_request.py index fb739e821..64205bfca 100644 --- a/src/adcp/types/domains/trusted_match/context_match_request.py +++ b/src/adcp/types/domains/trusted_match/context_match_request.py @@ -1,36 +1,263 @@ -"""Types declared by the AdCP ``trusted_match/context_match_request`` schema. +# generated by datamodel-codegen: +# filename: trusted_match/context_match_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.trusted_match.context_match_request import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal +from uuid import UUID -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from adcp.types._scalar import ScalarStr +from pydantic import AnyUrl, ConfigDict, Field + +from ..content_standards import artifact as artifact_1 +from ..core import property_id as property_id_1 +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import metro_system +from ..enums import property_type as property_type_1 + + +class Type(StrEnum): + url = 'url' + url_hash = 'url_hash' + eidr = 'eidr' + gracenote = 'gracenote' + isrc = 'isrc' + gtin = 'gtin' + rss_guid = 'rss_guid' + isbn = 'isbn' + custom = 'custom' + + +class ArtifactRef(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + type: Annotated[ + Type, + Field( + description="Identifier type. 'url' for web pages, 'url_hash' for URL-addressable content the publisher prefers not to share directly (buyer matches against pre-crawled index), 'eidr' for film/TV (EIDR DOI), 'gracenote' for music/TV (Gracenote TMS ID), 'isrc' for music recordings (International Standard Recording Code), 'gtin' for products (Global Trade Item Number — UPC, EAN, ISBN-13), 'rss_guid' for podcast episodes (RSS GUID), 'isbn' for books, 'custom' for publisher-defined identifiers." + ), + ] + value: Annotated[ + str, + Field( + description="The identifier value. For 'url': the canonical content URL — MUST NOT contain user-specific path segments, query parameters, or fragments; use 'url_hash' when the publisher prefers not to reveal the URL. For 'url_hash': Blake3 hash of the canonicalized URL, base64-encoded (canonicalization: strip scheme, strip www./m./amp. prefixes, lowercase, strip trailing slash, strip query params and fragments). For 'eidr': the EIDR DOI (e.g., '10.5240/xxxx'). For 'gracenote': the Gracenote TMS ID (e.g., 'SH032541890000'). For 'isrc': the ISRC code (e.g., 'USRC17607839'). For 'gtin': the GTIN (e.g., '00012345678905'). For 'rss_guid': the episode GUID from the RSS feed. For 'isbn': the ISBN (e.g., '978-0-123456-78-9'). For 'custom': a publisher-defined identifier." + ), + ] + + +class Sentiment(StrEnum): + positive = 'positive' + negative = 'negative' + neutral = 'neutral' + mixed = 'mixed' + + +class Keyword(ScalarStr): + __slots__ = () + _constraints = {'max_length': 100} + + +class ContextSignals(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + topics: Annotated[ + list[str] | None, + Field( + description="Content topic identifiers. Use IAB Content Taxonomy 3.0 IDs (e.g., '632' for Food & Drink) when taxonomy_id is 7, or bounded human-readable category labels (e.g., 'cooking.pasta') for custom taxonomies. For non-public content attributable to a single user or session, publishers MUST use standardized taxonomy identifiers or bounded custom category labels; custom topic strings MUST NOT reproduce distinctive verbatim phrasing and MUST NOT include PII or uniquely identifying details.", + max_length=50, + ), + ] = None + taxonomy_source: Annotated[ + str | None, + Field( + description="Organization that defines the topic taxonomy. Use 'iab' for IAB Content Taxonomy. Publishers may use other values for custom taxonomies." + ), + ] = 'iab' + taxonomy_id: Annotated[ + SchemaInt | None, + Field( + description='Taxonomy version within the source. For IAB, follows the AdCOM cattax enum: 7 = Content Taxonomy 3.0. Default: 7.' + ), + ] = 7 + sentiment: Annotated[ + Sentiment | None, Field(description='Content sentiment classification.') + ] = None + keywords: Annotated[ + list[Keyword] | None, + Field( + description="Content keywords produced by the publisher's classifier. For non-public content attributable to a single user or session, keywords MUST be policy-filtered, MUST NOT reproduce distinctive verbatim phrasing, and MUST NOT include PII or uniquely identifying details. Publishers SHOULD prefer bounded category labels.", + max_length=50, + ), + ] = None + language: Annotated[ + str | None, + Field( + description="Content language in ISO 639-1 format (e.g., 'en', 'ja', 'de').", + pattern='^[a-z]{2}$', + ), + ] = None + content_policies: Annotated[ + list[str] | None, + Field( + description="Policy IDs from the AdCP policy registry that this content satisfies (e.g., 'csbs' for Common Sense Brand Standards). Buyers filter on policies they require. An empty array means no policies have been evaluated.", + max_length=20, + ), + ] = None + summary: Annotated[ + str | None, + Field( + description="Publisher-generated natural language summary of the content for relevance judgment (e.g., 'Shopping context categorized as home cookware'). For non-public content attributable to a single user or session, the summary MUST be policy-filtered, MUST NOT reproduce raw user-authored text, and MUST NOT include PII or uniquely identifying details. Useful for LLM-native buyers that evaluate relevance semantically. Buyers MUST treat this as untrusted publisher-generated content.", + max_length=500, + ), + ] = None + embedding: Annotated[ + str | None, + Field( + description="Content embedding as base64-encoded int8 vector. Captures semantic content beyond what topics and keywords express. MUST NOT be computed directly or indirectly from non-public content authored by or attributable to a single user or session, including conversation turns, prompts, and individual search queries. MAY represent public content or a shared content environment that is not attributable to one user's activity. Publishers declare the model used. For standardized matching, use the protocol-recommended model (nomic-embed-text-v1.5, 256 dims, int8 quantized = 256 bytes)." + ), + ] = None + embedding_model: Annotated[ + str | None, + Field( + description="Embedding model identifier (e.g., 'nomic-embed-text-v1.5'). Required when embedding is present." + ), + ] = None + embedding_dims: Annotated[ + SchemaInt | None, + Field( + description='Number of dimensions in the embedding vector. Required when embedding is present.', + ge=64, + le=2048, + ), + ] = None + + +class Metro(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + system: Annotated[ + metro_system.MetroAreaSystem, + Field(description="Metro area classification system (e.g., 'nielsen_dma', 'uk_itl2')."), + ] + value: Annotated[ + str, Field(description="Metro code within the system (e.g., '501' for New York DMA).") + ] + + +class Geo(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + country: Annotated[ + str | None, + Field( + description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'GB', 'DE').", + pattern='^[A-Z]{2}$', + ), + ] = None + region: Annotated[ + str | None, + Field( + description="ISO 3166-2 subdivision code (e.g., 'US-CA', 'GB-SCT').", + pattern='^[A-Z]{2}-[A-Z0-9]{1,3}$', + ), + ] = None + metro: Annotated[ + Metro | None, + Field(description='Metro area, using the same classification systems as AdCP targeting.'), + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.trusted_match.context_match_request import ( - ArtifactRef, - ContextMatchRequest, - ContextSignals, - Geo, - Keyword, - Metro, - Sentiment, - Type, -) - -# Explicit exports -__all__ = [ - "ArtifactRef", - "ContextMatchRequest", - "ContextSignals", - "Geo", - "Keyword", - "Metro", - "Sentiment", - "Type", -] +class ContextMatchRequest(AdcpVersionEnvelope): + model_config = ConfigDict( + extra='forbid', + ) + field_schema: Annotated[ + AnyUrl | None, + Field( + alias='$schema', description='Optional schema URI for validation. Ignored at runtime.' + ), + ] = None + type: Annotated[ + Literal['context_match_request'], + Field(description='Message type discriminator for deserialization.'), + ] = 'context_match_request' + protocol_version: Annotated[ + str | None, + Field( + description='TMP protocol version. Allows receivers to handle semantic differences across versions.' + ), + ] = '1.0' + request_id: Annotated[ + str, + Field( + description='Unique request identifier. MUST NOT correlate with any identity match request_id.' + ), + ] + property_rid: Annotated[ + UUID, + Field( + description='Property catalog UUID (UUID v7). Globally unique, stable identifier assigned by the property catalog. The primary key for TMP matching and property list targeting.' + ), + ] + property_id: Annotated[ + property_id_1.PropertyId | None, + Field( + description="Publisher's human-readable property slug (e.g., 'cnn_homepage'). Optional when property_rid is present. Useful for logging and debugging." + ), + ] = None + property_type: Annotated[ + property_type_1.PropertyType, Field(description='Type of the publisher property') + ] + placement_id: Annotated[ + str, + Field( + description="Placement identifier from the publisher's placement registry in adagents.json. Identifies where on the property this ad opportunity exists. One placement per request." + ), + ] + seller_agent_url: Annotated[ + AnyUrl, + Field( + description="API endpoint URL of the seller agent issuing this request. The provider uses this to resolve the active package set it has synced for this seller; when `package_ids` is omitted, evaluation occurs against that full set. If `seller_agent_url` does not match any seller the provider has synced packages for, the provider MUST return an empty offer set — it MUST NOT fall back to another seller's active set. The value identifies the asking seller, is identical for every user on a given placement, and carries no user identity, so it neither varies the request per user nor weakens the context/identity decorrelation boundary. Compared using the AdCP URL canonicalization rules, not byte-equality — see docs/reference/url-canonicalization. Consistent with `seller_agent_url` on the identity match request, `seller_agent.agent_url` on `AvailablePackage`, and `agent_url` in `adagents.json`." + ), + ] + artifact: Annotated[ + artifact_1.Artifact | None, + Field( + description='Full content artifact adjacent to this ad opportunity. Same schema used for content standards evaluation. The publisher sends the artifact when they want the buyer to evaluate the full content. Contractual protections govern buyer use. TEE deployment upgrades contractual trust to cryptographic verification. Because the router fans out to multiple buyer agents, publishers MUST NOT include bearer tokens, service-account credentials, or signed URLs in this artifact. Routers MUST remove every asset `access` object and remove or replace every credential-bearing asset `url` before forwarding; only public asset URLs that recipients can resolve independently may remain.' + ), + ] = None + artifact_refs: Annotated[ + list[ArtifactRef] | None, + Field( + description='Public content references adjacent to this ad opportunity. Each artifact identifies content via a public identifier the buyer can resolve independently — no private registry sync required.', + max_length=20, + min_length=1, + ), + ] = None + geo: Annotated[ + Geo | None, + Field( + description='Coarse geographic location of the viewer. Publisher controls granularity — country is sufficient for regulatory compliance and volume filtering, region or metro helps with campaign targeting and valuation. Coarsened to prevent user identification: no postcode, no coordinates. All fields optional.' + ), + ] = None + context_signals: Annotated[ + ContextSignals | None, + Field( + description="Pre-computed classifier outputs for the content environment. Use when the publisher wants to provide privacy-reduced context without sharing content or public references. Can supplement artifact_refs or replace them entirely. Ephemeral content that many users encounter (a trending query, a syndicated segment) is shared content; one user's turn or query is not. For non-public content attributable to a single user or session, only the field-specific privacy-reduced outputs permitted below may be sent. Raw content MUST NOT be included. The publisher is the classifier and privacy boundary." + ), + ] = None + package_ids: Annotated[ + list[str] | None, + Field( + description='Restrict evaluation to specific packages. When omitted, the provider evaluates all eligible packages for this placement (the common case). MUST NOT vary by user — the same package_ids must be sent for every user on a given placement. User-dependent filtering leaks identity into the context path.', + max_length=500, + min_length=1, + ), + ] = None diff --git a/src/adcp/types/domains/trusted_match/context_match_response.py b/src/adcp/types/domains/trusted_match/context_match_response.py index 353c201ee..f9e878915 100644 --- a/src/adcp/types/domains/trusted_match/context_match_response.py +++ b/src/adcp/types/domains/trusted_match/context_match_response.py @@ -1,30 +1,86 @@ -"""Types declared by the AdCP ``trusted_match/context_match_response`` schema. +# generated by datamodel-codegen: +# filename: trusted_match/context_match_response.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.trusted_match.context_match_response import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, RootModel, StringConstraints + +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from . import offer + + +class Signals(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + segments: Annotated[ + list[str] | None, + Field(description='Contextual segment identifiers combined across provider responses.'), + ] = None + + +class TargetingKv(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + key: Annotated[ + str, + Field( + description='Provider-local targeting key. Publishers resolve this together with provider_id; it is not a globally unique or publisher-owned ad-server key.' + ), + ] + value: Annotated[str, Field(description='Targeting value preserved unchanged by the router.')] + + +class TargetingKvs(RootModel[list[TargetingKv]]): + root: Annotated[ + list[TargetingKv], + Field( + description='Provider-local key-value pairs for ad-server targeting. The router preserves each pair unchanged and attributes it by provider_id; the publisher maps the tuple (provider_id, key) to a local ad-server destination.' + ), + ] + + +class SignalsByProvider(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + targeting_kvs: TargetingKvs -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.trusted_match.context_match_response import ( - ContextMatchResponseRouterPublisher, - Signals, - SignalsByProvider, - TargetingKv, - TargetingKvs, -) - -# Explicit exports -__all__ = [ - "ContextMatchResponseRouterPublisher", - "Signals", - "SignalsByProvider", - "TargetingKv", - "TargetingKvs", -] +class ContextMatchResponseRouterPublisher(AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + type: Annotated[ + Literal['context_match_response'], + Field(description='Message type discriminator for deserialization.'), + ] = 'context_match_response' + request_id: Annotated[ + str, Field(description='Echoed request identifier from the context match request.') + ] + offers: Annotated[ + list[offer.Offer], + Field( + description='Offers collected across the provider fan-out, one per activated package. An empty array means no packages matched. For simple activation, each offer has just package_id. For richer responses, offers include brand, price, summary, and creative manifest.' + ), + ] + signals: Annotated[ + Signals | None, + Field( + description='Merged non-keyed response-level signals. Provider-local targeting pairs do not pass through this object; the router emits them only in signals_by_provider.' + ), + ] = None + signals_by_provider: Annotated[ + dict[Annotated[str, StringConstraints(pattern=r'^[A-Za-z0-9_]+$', min_length=1, max_length=64)], SignalsByProvider] + | None, + Field( + description="Router-authored map of provider targeting pairs, keyed by the publisher-assigned provider_id from provider registration. For every provider response containing a non-empty signals.targeting_kvs list, the router copies the complete list unchanged into that provider's bucket. The router derives the map key from its registration and MUST ignore or reject provider-supplied signals_by_provider data. A provider with no targeting pairs is omitted. Publishers resolve each (provider_id, key) tuple to a local ad-server destination and drop tuples that have no local mapping.", + min_length=1, + ), + ] = None diff --git a/src/adcp/types/domains/trusted_match/error.py b/src/adcp/types/domains/trusted_match/error.py index 64f8e423d..987255417 100644 --- a/src/adcp/types/domains/trusted_match/error.py +++ b/src/adcp/types/domains/trusted_match/error.py @@ -1,18 +1,42 @@ -"""Types declared by the AdCP ``trusted_match/error`` schema. +# generated by datamodel-codegen: +# filename: trusted_match/error.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.trusted_match.error import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.trusted_match.error import Code, TmpError +class Code(StrEnum): + invalid_request = 'invalid_request' + unknown_package = 'unknown_package' + seller_not_authorized = 'seller_not_authorized' + rate_limited = 'rate_limited' + timeout = 'timeout' + internal_error = 'internal_error' + provider_unavailable = 'provider_unavailable' + -# Explicit exports -__all__ = ["Code", "TmpError"] +class TmpError(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + type: Annotated[ + Literal['error'], Field(description='Message type discriminator for deserialization.') + ] = 'error' + request_id: Annotated[ + str, Field(description='Echoed request identifier from the original request') + ] + code: Annotated[ + Code, + Field( + description="Machine-readable error code. `seller_not_authorized` is returned by providers at sync time when an AvailablePackage declares a `seller_agent.agent_url` that is not present in the `authorized_agents` list of the publisher's adagents.json for a property the package claims to serve." + ), + ] + message: Annotated[ + str | None, Field(description='Human-readable error description for debugging') + ] = None diff --git a/src/adcp/types/domains/trusted_match/identity_match_request.py b/src/adcp/types/domains/trusted_match/identity_match_request.py index e871be506..7b6c559e1 100644 --- a/src/adcp/types/domains/trusted_match/identity_match_request.py +++ b/src/adcp/types/domains/trusted_match/identity_match_request.py @@ -1,32 +1,215 @@ -"""Types declared by the AdCP ``trusted_match/identity_match_request`` schema. +# generated by datamodel-codegen: +# filename: trusted_match/identity_match_request.json +# timestamp: 2026-10-04T15:01:24+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.trusted_match.identity_match_request import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Any, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictBool + +from ..core import brand_ref +from ..core.version_envelope import AdcpVersionEnvelope +from ..enums import attestation_claim +from ..enums import uid_type as uid_type_1 + + +class VerificationLevel(StrEnum): + orb = 'orb' + device = 'device' + document = 'document' + + +class Consent(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + gdpr: Annotated[ + StrictBool | None, Field(description='Whether GDPR applies to this request.') + ] = None + tcf_consent: Annotated[ + str | None, Field(description='IAB TCF v2.2 consent string. Present when gdpr is true.') + ] = None + gpp: Annotated[str | None, Field(description='IAB Global Privacy Platform string.')] = None + us_privacy: Annotated[ + str | None, + Field( + deprecated=True, + description='US Privacy string (CCPA). Deprecated in favor of GPP but still widely used.', + ), + ] = None + + +class SealedCredential(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + audience_kid: Annotated[ + str, + Field( + description='Key id identifying the recipient (network / relying party) whose HPKE private key opens `payload`.', + max_length=128, + ), + ] + payload: Annotated[ + str, + Field( + description='HPKE-sealed attestation in the TMPX envelope format: `kid.base64url_nopad(ciphertext)` — unpadded base64url per RFC 4648 §5. Opaque pass-through for the publisher.', + max_length=8192, + ), + ] + + +class Attestation(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + issuer: Annotated[ + brand_ref.BrandReference, + Field( + description='Attestation authority / identity issuer, referenced as a vendor BrandRef (e.g. {"domain": "world.org"}) — the same vendor-reference shape AdCP uses for measurement and signals vendors. The issuer\'s canonical domain is the anchor; brand.json hosting is optional. `scheme` selects the verifier version within the issuer.' + ), + ] + scheme: Annotated[ + str, + Field( + description='Proof scheme and version, e.g. "world_id_v4". Selects how `proof` is verified.' + ), + ] + relying_party_id: Annotated[ + str | None, + Field( + description="Relying-party id the proof was minted for. The receiver checks this against the relying_party_id's published owner (brand.json `identity_relying_parties[]`) so a forwarded proof cannot be replayed under a different owner." + ), + ] = None + action: Annotated[ + str | None, + Field( + description='Issuer action/scope the proof was bound to (e.g. "humanity-check-for-ads").' + ), + ] = None + claims: Annotated[ + list[attestation_claim.AttestationClaim], + Field( + description='Claims this attestation establishes. Closed, issuer-agnostic set.', + max_length=16, + min_length=1, + ), + ] + verification_level: Annotated[ + VerificationLevel | None, + Field( + description='Credential strength (e.g. World ID Orb = biometric unique-human; Device = weaker; Document = passport/NFC).' + ), + ] = None + signal_binding: Annotated[ + str | None, + Field( + description='Hash of the signal the proof commits to. The receiver MUST verify it matches the context the receiver expects (e.g. a buyer-issued nonce or the request_id) and that the attestation is within its freshness window; this, plus nullifier-reuse tracking, is the replay defense.' + ), + ] = None + proof: Annotated[ + dict[str, Any], + Field( + description="Scheme-specific verifiable proof material. Opaque to this schema; validated by the scheme's verifier. MUST carry only scheme-defined verification material — never page context or additional user identifiers." + ), + ] + expires_at: Annotated[ + AwareDatetime | None, + Field(description='Attestation validity horizon. Receivers MUST reject when past.'), + ] = None + + +class Identity(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + user_token: Annotated[ + str, + Field( + description='Opaque token from an identity provider (ID5, LiveRamp, UID2) or publisher-generated. Buyer may map to internal identity graph but cannot reverse to PII.' + ), + ] + uid_type: Annotated[ + uid_type_1.UidType, + Field( + description='Type of the user identifier. Tells the buyer which identity graph to resolve against, avoiding trial-and-error matching.' + ), + ] + attestation: Annotated[ + Attestation | None, + Field( + description="Optional verifiable proof ABOUT this identity (e.g. World ID proof-of-personhood and/or age). The receiver MUST verify it (see conformance) and MUST treat an absent-or-unverifiable attestation as 'no attestation' — never as an asserted-true claim. Issuer-agnostic: World ID is the first scheme; mDL / VC-style issuers use the same shape. Receivers MUST bound attestation size to prevent DoS." + ), + ] = None -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.trusted_match.identity_match_request import ( - Attestation, - Consent, - Identity, - IdentityMatchRequest, - SealedCredential, - VerificationLevel, -) - -# Explicit exports -__all__ = [ - "Attestation", - "Consent", - "Identity", - "IdentityMatchRequest", - "SealedCredential", - "VerificationLevel", -] +class IdentityMatchRequest(AdcpVersionEnvelope): + model_config = ConfigDict( + extra='forbid', + ) + field_schema: Annotated[ + AnyUrl | None, + Field( + alias='$schema', description='Optional schema URI for validation. Ignored at runtime.' + ), + ] = None + type: Annotated[ + Literal['identity_match_request'], + Field(description='Message type discriminator for deserialization.'), + ] = 'identity_match_request' + protocol_version: Annotated[ + str | None, + Field( + description='TMP protocol version. Allows receivers to handle semantic differences across versions.' + ), + ] = '1.0' + request_id: Annotated[ + str, + Field( + description='Unique request identifier. MUST NOT correlate with any context match request_id.' + ), + ] + seller_agent_url: Annotated[ + AnyUrl, + Field( + description="API endpoint URL of the seller agent issuing this request. The buyer's identity-match service uses this to resolve the active package set it has registered for this seller; when `package_ids` is omitted, evaluation occurs against that full set. If `seller_agent_url` does not match any seller for which the buyer has registered active packages, the buyer MUST return an empty `eligible_package_ids` set — it MUST NOT fall back to evaluating against another seller's active set. Compared using the AdCP URL canonicalization rules, not byte-equality — see docs/reference/url-canonicalization. Consistent with `seller_agent.agent_url` on `AvailablePackage` and `agent_url` in `adagents.json`." + ), + ] + identities: Annotated[ + list[Identity], + Field( + description='Identity tokens for the user, each tagged with its type. Publishers SHOULD include every token they have available — the buyer resolves on whichever graph matches. Entry order is not semantically significant; buyers use their own preference order when multiple entries resolve. Duplicate `(uid_type, user_token)` pairs MUST NOT appear; routers MAY reject or dedupe. `maxItems: 3` matches the TMPX plaintext budget (~120 bytes after HPKE overhead fits three 32-byte tokens); exceeding it forces buyer-side truncation.', + max_length=3, + min_length=1, + ), + ] + consent: Annotated[ + Consent | None, + Field( + description='Privacy consent signals. Buyers in regulated jurisdictions MUST NOT process the user token without consent information.' + ), + ] = None + package_ids: Annotated[ + list[str] | None, + Field( + description="Optional. When omitted, the buyer evaluates eligibility against the full set of active packages it has registered for `seller_agent_url`. When provided, the composition of `package_ids` MUST be statistically independent of the current placement — sending only the page-specific subset would let the buyer correlate Identity Match with Context Match by comparing package sets. Two acceptable modes: (a) **all-active** — include every active package this buyer has at this publisher; (b) **fuzzed** — include a random sample of active packages, optionally padded with synthetic non-existent IDs, drawn from a distribution that does not depend on the current placement. The buyer's silent-drop behavior on unknown IDs (specified below) is what makes synthetic-ID padding safe — they do not affect the response shape and cannot leak registry membership. When both `seller_agent_url` and `package_ids` are present, the buyer evaluates against the intersection of its registered active set and `package_ids`; IDs in `package_ids` that the buyer has not registered for this seller MUST be silently ignored (not surfaced as errors) to avoid leaking registry membership back to the publisher.", + min_length=1, + ), + ] = None + country: Annotated[ + str | None, + Field( + description='ISO 3166-1 alpha-2 country code. Routing directive for the TMP Router — used to select the correct regional provider. The router MUST strip this field before forwarding the request to the buyer agent. Not an identity signal.', + pattern='^[A-Z]{2}$', + ), + ] = None + sealed_credentials: Annotated[ + list[SealedCredential] | None, + Field( + description='Optional HPKE-sealed credentials addressed to specific audiences — the network-as-RP ("issuer-as-RP"/Mechanism B) carrier. Each payload is opaque to the publisher, who relays it untouched; the inner plaintext is an `attestation` (see identities[].attestation) scoped to the audience\'s relying party. Reuses the TMPX envelope format. Router handling (normative — see docs/trusted-match/specification.mdx): the router forwards each entry only to the provider that owns its `audience_kid` (not broadcast), folds `sealed_credentials` into the per-provider re-signature canonical bytes so an injected/swapped blob breaks the signature, and includes a `sealed_credentials_hash` in the dedup cache key. Receivers decrypt only entries whose `audience_kid` they hold a key for and ignore the rest. Receivers MUST bound count and size to prevent DoS amplification.', + max_length=8, + ), + ] = None diff --git a/src/adcp/types/domains/trusted_match/identity_match_response.py b/src/adcp/types/domains/trusted_match/identity_match_response.py index bd38a00f8..bd5eb5e1a 100644 --- a/src/adcp/types/domains/trusted_match/identity_match_response.py +++ b/src/adcp/types/domains/trusted_match/identity_match_response.py @@ -1,28 +1,102 @@ -"""Types declared by the AdCP ``trusted_match/identity_match_response`` schema. +# generated by datamodel-codegen: +# filename: trusted_match/identity_match_response.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.trusted_match.identity_match_response import +import re -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated, Literal + +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, StringConstraints, model_validator + +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope + +_PROVIDER_ID_PATTERN = re.compile(r'^[A-Za-z0-9_]{1,64}$') +from . import tmpx_chunk + + + +class TmpxMacro(AdCPBaseModel): + """Deprecated 3.1.8 TMPX macro/value compatibility model.""" + + model_config = ConfigDict( + extra='forbid', + ) + name: Annotated[ + str, + Field(max_length=64, min_length=1, pattern='^[A-Z][A-Z0-9_]*$'), + ] + value: Annotated[str, Field(max_length=1024, min_length=1)] + + +class TmpxProviders(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + chunks: Annotated[ + list[tmpx_chunk.TmpxChunk], + Field( + description="Ordered TMPX chunks for this provider. Each entry is a `{slot_id, value}` pair copied verbatim from the provider's `tmpx_chunks`. Ordered-prefix invariant: the sequence of `slot_id`s MUST equal an ordered prefix of the provider's registered `tmpx_slots` — publishers MAY reject responses whose slot_ids or ordering diverge. Cap of 2 chunks in v1; the cap MAY rise without a shape change.", + max_length=2, + min_length=1, + ), + ] + + +class IdentityMatchResponseRouterPublisher(AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + type: Annotated[ + Literal['identity_match_response'], + Field(description='Message type discriminator for deserialization.'), + ] = 'identity_match_response' + request_id: Annotated[ + str, Field(description='Echoed request identifier from the identity match request') + ] + eligible_package_ids: Annotated[ + list[str], + Field( + description='Package IDs the user is eligible for. Packages not listed are ineligible.' + ), + ] + serve_window_sec: Annotated[ + SchemaInt, + Field( + description="Per-package single-shot fcap window, in seconds. After serving the user one impression on each eligible package within this window, the publisher MUST re-query Identity Match before serving from those packages again. This is NOT a router response cache TTL — it is a buyer-asserted serve throttle. Multi-impression frequency caps are handled separately by the buyer's impression tracker, which writes cap-fire events to the IdentityMatch cap-state store at the boundary regardless of this window. Maximum 300 — longer windows reduce IdentityMatch load but coarsen fcap granularity below what most campaigns require.", + ge=1, + le=300, + ), + ] + tmpx: Annotated[ + str | None, + Field( + deprecated=True, + description='DEPRECATED in favor of tmpx_providers. Routers MAY continue to populate this field for back-compat with consumers that only know the single-token shape; when both fields are present, tmpx_providers is authoritative. Single HPKE-encrypted exposure token containing the resolved user identity tokens. Wire format: kid.base64url_nopad(ciphertext) — unpadded base64url per RFC 4648 section 5 (no = characters). Publishers MUST treat this value as opaque pass-through data. Removed in 4.0.', + ), + ] = None + tmpx_providers: Annotated[ + dict[Annotated[str, StringConstraints(pattern=r'^[A-Za-z0-9_]+$', min_length=1, max_length=64)], TmpxProviders] | None, + Field( + description="Router-populated: ordered TMPX chunk/value pairs grouped by the originating identity provider's `provider_id`. Each entry's `chunks[]` is a copy of the provider's emitted `tmpx_chunks` list, in the same order. Each chunk carries a provider-local `slot_id` (from the provider's registered `tmpx_slots`) and an opaque URL-safe `value`; the publisher's deployment configuration (see publisher-tmpx-config.json) resolves each `(provider_id, slot_id)` pair to the ad-server macro name, targeting key, VAST substitution, or play-log field for that surface. The protocol carries values and attribution only. Required by router conformance when any identity provider emitted TMPX in this request; collapsing per-provider tokens into a single string loses attribution and breaks per-provider impression accounting. Map keys MUST match the provider_id charset registered in provider-registration.json (enforced by `propertyNames`). Publishers MUST NOT parse, decode, or transform any chunk's `value` — each is an opaque URL-safe wire string substituted verbatim into the mapped destination." + ), + ] = None + + @model_validator(mode='after') + def _validate_tmpx_provider_ids(self) -> IdentityMatchResponseRouterPublisher: + if self.tmpx_providers is None: + return self + invalid = [ + provider_id + for provider_id in self.tmpx_providers + if not _PROVIDER_ID_PATTERN.fullmatch(provider_id) + ] + if invalid: + raise ValueError('tmpx_providers keys must be valid provider_id values') + return self -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.trusted_match.identity_match_response import ( - IdentityMatchResponse, - IdentityMatchResponseRouterPublisher, - TmpxMacro, - TmpxProviders, -) - -# Explicit exports -__all__ = [ - "IdentityMatchResponse", - "IdentityMatchResponseRouterPublisher", - "TmpxMacro", - "TmpxProviders", -] +IdentityMatchResponse = IdentityMatchResponseRouterPublisher diff --git a/src/adcp/types/domains/trusted_match/offer.py b/src/adcp/types/domains/trusted_match/offer.py index e7b8907ce..0cd6257cb 100644 --- a/src/adcp/types/domains/trusted_match/offer.py +++ b/src/adcp/types/domains/trusted_match/offer.py @@ -1,18 +1,58 @@ -"""Types declared by the AdCP ``trusted_match/offer`` schema. +# generated by datamodel-codegen: +# filename: trusted_match/offer.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.trusted_match.offer import +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core import brand_ref +from ..core import creative_manifest as creative_manifest_1 +from ..core import seller_agent_ref +from . import offer_price -from adcp.types.generated_poc.trusted_match.offer import Offer -# Explicit exports -__all__ = ["Offer"] +class Offer(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + package_id: Annotated[str, Field(description='Package identifier from the media buy.')] + seller_agent: Annotated[ + seller_agent_ref.SellerAgentReference | None, + Field( + description="Optional echo of the package's seller agent from sync-time metadata. Provided for publisher-side observability so log pipelines can attribute offers to sellers without round-tripping to the media-buy store. Non-authoritative: the binding on the cached AvailablePackage is source of truth. When omitted, the router MAY stamp this field from its cached package→seller map." + ), + ] = None + brand: Annotated[ + brand_ref.BrandReference | None, + Field( + description='Brand for this offer. Required when the product allows dynamic brands (brand selected at match time rather than fixed on the package). For single-brand packages, the brand is already known from the media buy.' + ), + ] = None + price: Annotated[ + offer_price.OfferPrice | None, + Field( + description='Price for this offer. Only present when the product supports variable pricing. For fixed-price packages, price is already set on the media buy.' + ), + ] = None + summary: Annotated[ + str | None, + Field( + description="Buyer-generated description of the offer, for the publisher to judge relevance. E.g., '50% off Goldenfield mayo — recipe integration'. The publisher (or their AI assistant) uses this to decide whether the offer fits the context." + ), + ] = None + creative_manifest: Annotated[ + creative_manifest_1.CreativeManifest | None, + Field( + description='Full creative details, inline. When present, the publisher has everything needed to render. Inline for small creatives (markdown, product card). For large creatives (VAST, video), the manifest references external assets via URLs.' + ), + ] = None + creative_data: Annotated[ + dict[str, str] | None, + Field( + description="Optional free-form string enhancements for dynamic creative rendering, such as sponsor labels and promotion codes. The map has no protocol-level key registry or required keys: meanings are package-local, receivers MUST ignore unknown keys, and a missing key MUST NOT make an otherwise renderable offer fail. Required renderable content belongs in creative_manifest assets, so creative_data may complement but never replace a manifest's required assets. This is not ad-server macro substitution or attribution tracking; tracker URLs belong in creative_manifest assets and per-user exposure tracking uses TMPX." + ), + ] = None diff --git a/src/adcp/types/domains/trusted_match/offer_price.py b/src/adcp/types/domains/trusted_match/offer_price.py index 3e032614c..174fc0e17 100644 --- a/src/adcp/types/domains/trusted_match/offer_price.py +++ b/src/adcp/types/domains/trusted_match/offer_price.py @@ -1,18 +1,32 @@ -"""Types declared by the AdCP ``trusted_match/offer_price`` schema. +# generated by datamodel-codegen: +# filename: trusted_match/offer_price.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.trusted_match.offer_price import +from adcp.types._str_enum import StrEnum +from typing import Annotated -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StrictFloat -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.trusted_match.offer_price import Model, OfferPrice +class Model(StrEnum): + cpm = 'cpm' + cpc = 'cpc' + cpcv = 'cpcv' + cpa = 'cpa' + flat = 'flat' + -# Explicit exports -__all__ = ["Model", "OfferPrice"] +class OfferPrice(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + amount: Annotated[ + StrictFloat, Field(description='Price amount in the specified currency', ge=0.0) + ] + currency: Annotated[ + str | None, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$') + ] = 'USD' + model: Annotated[Model, Field(description='Pricing model for this offer')] diff --git a/src/adcp/types/domains/trusted_match/provider_context_match_response.py b/src/adcp/types/domains/trusted_match/provider_context_match_response.py index beaff4dd6..b120d6b25 100644 --- a/src/adcp/types/domains/trusted_match/provider_context_match_response.py +++ b/src/adcp/types/domains/trusted_match/provider_context_match_response.py @@ -1,23 +1,81 @@ -"""Types declared by the AdCP ``trusted_match/provider_context_match_response`` schema. +# generated by datamodel-codegen: +# filename: trusted_match/provider_context_match_response.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.trusted_match.provider_context_match_response import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from pydantic import ConfigDict, Field, RootModel -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from . import offer + + +class TargetingKv(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + key: Annotated[ + str, + Field( + description='Provider-local targeting key. It is not a globally unique or publisher-owned ad-server destination.' + ), + ] + value: Annotated[str, Field(description='Targeting value that the router preserves unchanged.')] + + +class TargetingKvs(RootModel[list[TargetingKv]]): + root: Annotated[ + list[TargetingKv], + Field( + description="Provider-local key-value pairs for ad-server targeting. The router preserves each pair unchanged under the emitting provider's registered provider_id." + ), + ] + + +class Signals(AdCPBaseModel): + model_config = ConfigDict( + extra='allow', + ) + segments: Annotated[list[str] | None, Field(description='Contextual segment identifiers.')] = ( + None + ) + targeting_kvs: TargetingKvs | None = None -from adcp.types.generated_poc.trusted_match.provider_context_match_response import ( - ContextMatchResponseProviderRouter, - Signals, - TargetingKv, - TargetingKvs, -) -# Explicit exports -__all__ = ["ContextMatchResponseProviderRouter", "Signals", "TargetingKv", "TargetingKvs"] +class ContextMatchResponseProviderRouter(AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + type: Annotated[ + Literal['context_match_response'], + Field( + description='Message type discriminator for deserialization. Same const as the router→publisher variant so decoders can key off type before dispatching on hop shape.' + ), + ] = 'context_match_response' + request_id: Annotated[ + str, Field(description='Echoed request identifier from the context match request.') + ] + offers: Annotated[ + list[offer.Offer], + Field( + description='Offers from this provider, one per activated package. An empty array means no packages matched. For simple activation, each offer has just package_id. For richer responses, offers include brand, price, summary, and creative manifest.' + ), + ] + cache_ttl: Annotated[ + SchemaInt | None, + Field( + description="Optional override for the router's default 5-minute response cache TTL, in seconds. When present, the router MUST use this value instead of its default. Set to 0 to disable caching.", + ge=0, + le=86400, + ), + ] = None + signals: Annotated[ + Signals | None, + Field( + description="Provider-origin response-level signals. Targeting pairs use the provider's local key vocabulary; the router preserves and attributes them rather than passing them through as flattened publisher targeting." + ), + ] = None diff --git a/src/adcp/types/domains/trusted_match/provider_identity_match_response.py b/src/adcp/types/domains/trusted_match/provider_identity_match_response.py index 9008e5826..91d88bb28 100644 --- a/src/adcp/types/domains/trusted_match/provider_identity_match_response.py +++ b/src/adcp/types/domains/trusted_match/provider_identity_match_response.py @@ -1,20 +1,51 @@ -"""Types declared by the AdCP ``trusted_match/provider_identity_match_response`` schema. +# generated by datamodel-codegen: +# filename: trusted_match/provider_identity_match_response.json +# timestamp: 2026-10-03T16:00:16+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.trusted_match.provider_identity_match_response import +from typing import Annotated, Literal -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import SchemaInt +from pydantic import ConfigDict, Field -# ruff: noqa: E501, I001 -from __future__ import annotations +from ..core.protocol_envelope import ProtocolEnvelope +from ..core.version_envelope import AdcpVersionEnvelope +from . import tmpx_chunk -from adcp.types.generated_poc.trusted_match.provider_identity_match_response import ( - IdentityMatchResponseProviderRouter, -) -# Explicit exports -__all__ = ["IdentityMatchResponseProviderRouter"] +class IdentityMatchResponseProviderRouter(AdcpVersionEnvelope, ProtocolEnvelope): + model_config = ConfigDict( + extra='allow', + ) + type: Annotated[ + Literal['identity_match_response'], + Field( + description='Message type discriminator for deserialization. Same const as the router→publisher variant so publisher-facing decoders can key off type before dispatching on shape.' + ), + ] = 'identity_match_response' + request_id: Annotated[ + str, Field(description='Echoed request identifier from the identity match request.') + ] + eligible_package_ids: Annotated[ + list[str], + Field( + description='Package IDs the user is eligible for. Packages not listed are ineligible.' + ), + ] + serve_window_sec: Annotated[ + SchemaInt, + Field( + description="Per-package single-shot fcap window, in seconds. After serving the user one impression on each eligible package within this window, the publisher MUST re-query Identity Match before serving from those packages again. Not a router response cache TTL — it is a buyer-asserted serve throttle. Multi-impression frequency caps are handled separately by the buyer's impression tracker. Maximum 300.", + ge=1, + le=300, + ), + ] + tmpx_chunks: Annotated[ + list[tmpx_chunk.TmpxChunk] | None, + Field( + description="Ordered TMPX chunk/value pairs this identity agent mints. Each entry names the provider-local `slot_id` the value fills (from the provider's registered `tmpx_slots` in provider-registration.json). Ordered-prefix invariant: a response that carries fewer chunks than the provider registered MUST emit an ordered prefix of the registered `tmpx_slots` — routers MUST NOT reorder or fill gaps. The router MUST validate this contract before forwarding: if the provider has no `tmpx_slots` registration, or if the chunk `slot_id` sequence is not an exact non-empty ordered prefix of the registered list (duplicate, reordered, sparse, or unregistered slot IDs), the router MUST drop that provider's chunks atomically and MUST NOT forward them into `tmpx_providers`. Cap of 2 chunks in v1 aligned with the GAM macro-slot budget; the cap MAY rise without a shape change. Omitted when this provider mints no TMPX (e.g. no eligible packages).", + max_length=2, + min_length=1, + ), + ] = None diff --git a/src/adcp/types/domains/trusted_match/provider_registration.py b/src/adcp/types/domains/trusted_match/provider_registration.py index 16eb0568b..67eafbe5f 100644 --- a/src/adcp/types/domains/trusted_match/provider_registration.py +++ b/src/adcp/types/domains/trusted_match/provider_registration.py @@ -1,34 +1,239 @@ -"""Types declared by the AdCP ``trusted_match/provider_registration`` schema. +# generated by datamodel-codegen: +# filename: trusted_match/provider_registration.json +# timestamp: 2026-10-04T00:59:59+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: +from __future__ import annotations - from adcp.types.domains.trusted_match.provider_registration import +from adcp.types._str_enum import StrEnum +from typing import Annotated, Literal +from uuid import UUID -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from adcp.types.base import AdCPBaseModel, SchemaInt +from adcp.types._scalar import ScalarStr +from pydantic import AnyUrl, ConfigDict, Field, StrictBool, field_validator, model_validator + +from ..enums import uid_type + + +class Country(ScalarStr): + __slots__ = () + _constraints = {'pattern': '^[A-Z]{2}$'} + + + +class TmpxMacro(ScalarStr): + """Deprecated 3.1.8 registered macro-name compatibility model.""" + + __slots__ = () + _constraints = {'max_length': 64, 'min_length': 1, 'pattern': '^[A-Z][A-Z0-9_]*$'} + + +class TmpxSlot(ScalarStr): + __slots__ = () + _constraints = {'max_length': 64, 'min_length': 1, 'pattern': '^[a-zA-Z][a-zA-Z0-9_]*$'} + + +class Status(StrEnum): + active = 'active' + inactive = 'inactive' + draining = 'draining' + + +class TmpProviderRegistration1(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + provider_id: Annotated[ + str, + Field( + description="Stable identifier for this provider registration. Used in logs, metrics, cache keys, as the key in `signals_by_provider` on Context Match responses and `targeting_kv_mapping` in publisher configuration, and as the key in `tmpx_providers` on Identity Match responses and `tmpx_macro_mapping` in publisher configuration. Publishers assign this — it is not the provider's agent_url. Charset is constrained to a safe alphanumeric/underscore set so the value can appear in operational surfaces (logs, metrics, dashboards) without quoting. See publisher-targeting-kv-config.json and publisher-tmpx-config.json.", + max_length=64, + min_length=1, + pattern='^[A-Za-z0-9_]+$', + ), + ] + endpoint: Annotated[ + AnyUrl, + Field( + description='Base URL the router calls. The router appends /context for Context Match and /identity for Identity Match. MUST be HTTPS in production, validated against the canonical reserved IPv4 and IPv6 ranges, with the TCP connection pinned to the validated IP (DNS re-resolution alone is insufficient against rebinding). Publishers comparing two provider registrations for the same `endpoint` MUST canonicalize both per the AdCP URL canonicalization rules; two registrations differing only in case, default port, or path-slash collapsing are the same provider. See docs/trusted-match/specification#provider-registration-security, docs/building/implementation/security#webhook-url-validation-ssrf, and docs/reference/url-canonicalization.' + ), + ] + context_match: Annotated[ + Literal[True], Field(description='Provider handles Context Match requests (POST /context).') + ] + identity_match: Annotated[ + StrictBool | None, + Field(description='Provider handles Identity Match requests (POST /identity).'), + ] = None + countries: Annotated[ + list[Country] | None, + Field( + description="ISO 3166-1 alpha-2 country codes this provider serves. The router filters Identity Match providers by the request's country field. MUST be present and non-empty when identity_match is true.", + min_length=1, + ), + ] = None + uid_types: Annotated[ + list[uid_type.UidType] | None, + Field( + description="Identity types this provider can resolve. The router selects Identity Match providers whose uid_types overlaps with any uid_type in the request's identities array. MUST be present and non-empty when identity_match is true.", + min_length=1, + ), + ] = None + properties: Annotated[ + list[UUID] | None, + Field( + description='Property RIDs (UUID v7) this provider serves. When present, the router only sends requests from these properties to this provider. When absent, the provider serves all properties.', + min_length=1, + ), + ] = None + timeout_ms: Annotated[ + SchemaInt | None, + Field( + description="Per-provider timeout in milliseconds. The router skips this provider if it does not respond within this budget. Must be less than or equal to the router's overall latency_budget_ms. The router may further reduce this based on adaptive timeout allocation.", + ge=5, + le=5000, + ), + ] = 50 + priority: Annotated[ + SchemaInt | None, + Field( + description='Provider ordering for Context Match offer conflict resolution. Lower values have higher priority. When two providers return offers for the same package_id (a configuration error), the router keeps the offer from the higher-priority provider; equal priorities are broken by first response received. Identity Match eligibility remains a responder-scoped union because silent omission is not a negative vote. Also used for adaptive timeout allocation — higher-priority providers receive a larger share of the latency budget.', + ge=0, + ), + ] = 0 + tmpx_slots: Annotated[ + list[TmpxSlot] | None, + Field( + description='Stable provider-local slot identifiers for the ordered TMPX chunks this provider mints. Slot IDs are opaque provider-namespaced tokens (e.g. `["primary","secondary"]`), NOT ad-server macro names — publishers map `(provider_id, slot_id)` → local destination via `tmpx_macro_mapping` in publisher-tmpx-config.json, so the destination namespace stays publisher-owned and the router never accepts a destination name from an untrusted provider. Distinct providers MAY reuse the same slot_id without collision because publisher lookup is keyed on `(provider_id, slot_id)`. Publishers use this list at startup to validate `tmpx_macro_mapping` covers every slot the provider mints and to detect config drift when the provider\'s slot contract changes. Ordering carries the ordered-prefix invariant: a provider that emits fewer chunks than it registered MUST emit an ordered prefix of this list — chunks map to slots in registration order and MUST NOT be shifted, sparse, or reordered. Cap of 2 slots in v1 aligned with the GAM macro-slot budget; the cap MAY rise without a shape change. A provider that emits TMPX (populates `tmpx_chunks` on its identity-match response) MUST register this list; a provider that does not emit TMPX omits it. Schema cannot enforce that predicate because "emits TMPX" is not schema-visible.', + max_length=2, + min_length=1, + ), + ] = None + status: Annotated[ + Status | None, + Field( + description='Provider lifecycle status. Active providers receive requests. Inactive providers are skipped entirely. Draining providers stop receiving new requests but in-flight requests complete normally.' + ), + ] = Status.active + + + @field_validator('endpoint') + @classmethod + def _require_https_endpoint(cls, value: AnyUrl) -> AnyUrl: + if value.scheme != 'https': + raise ValueError('endpoint must use https') + return value + + @model_validator(mode='after') + def _require_identity_match_dimensions(self) -> TmpProviderRegistration1: + if self.identity_match is True: + if not self.countries: + raise ValueError('countries is required when identity_match is true') + if not self.uid_types: + raise ValueError('uid_types is required when identity_match is true') + return self + + +class TmpProviderRegistration2(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + provider_id: Annotated[ + str, + Field( + description="Stable identifier for this provider registration. Used in logs, metrics, cache keys, as the key in `signals_by_provider` on Context Match responses and `targeting_kv_mapping` in publisher configuration, and as the key in `tmpx_providers` on Identity Match responses and `tmpx_macro_mapping` in publisher configuration. Publishers assign this — it is not the provider's agent_url. Charset is constrained to a safe alphanumeric/underscore set so the value can appear in operational surfaces (logs, metrics, dashboards) without quoting. See publisher-targeting-kv-config.json and publisher-tmpx-config.json.", + max_length=64, + min_length=1, + pattern='^[A-Za-z0-9_]+$', + ), + ] + endpoint: Annotated[ + AnyUrl, + Field( + description='Base URL the router calls. The router appends /context for Context Match and /identity for Identity Match. MUST be HTTPS in production, validated against the canonical reserved IPv4 and IPv6 ranges, with the TCP connection pinned to the validated IP (DNS re-resolution alone is insufficient against rebinding). Publishers comparing two provider registrations for the same `endpoint` MUST canonicalize both per the AdCP URL canonicalization rules; two registrations differing only in case, default port, or path-slash collapsing are the same provider. See docs/trusted-match/specification#provider-registration-security, docs/building/implementation/security#webhook-url-validation-ssrf, and docs/reference/url-canonicalization.' + ), + ] + context_match: Annotated[ + StrictBool | None, + Field(description='Provider handles Context Match requests (POST /context).'), + ] = None + identity_match: Annotated[ + Literal[True], + Field(description='Provider handles Identity Match requests (POST /identity).'), + ] + countries: Annotated[ + list[Country] | None, + Field( + description="ISO 3166-1 alpha-2 country codes this provider serves. The router filters Identity Match providers by the request's country field. MUST be present and non-empty when identity_match is true.", + min_length=1, + ), + ] = None + uid_types: Annotated[ + list[uid_type.UidType] | None, + Field( + description="Identity types this provider can resolve. The router selects Identity Match providers whose uid_types overlaps with any uid_type in the request's identities array. MUST be present and non-empty when identity_match is true.", + min_length=1, + ), + ] = None + properties: Annotated[ + list[UUID] | None, + Field( + description='Property RIDs (UUID v7) this provider serves. When present, the router only sends requests from these properties to this provider. When absent, the provider serves all properties.', + min_length=1, + ), + ] = None + timeout_ms: Annotated[ + SchemaInt | None, + Field( + description="Per-provider timeout in milliseconds. The router skips this provider if it does not respond within this budget. Must be less than or equal to the router's overall latency_budget_ms. The router may further reduce this based on adaptive timeout allocation.", + ge=5, + le=5000, + ), + ] = 50 + priority: Annotated[ + SchemaInt | None, + Field( + description='Provider ordering for Context Match offer conflict resolution. Lower values have higher priority. When two providers return offers for the same package_id (a configuration error), the router keeps the offer from the higher-priority provider; equal priorities are broken by first response received. Identity Match eligibility remains a responder-scoped union because silent omission is not a negative vote. Also used for adaptive timeout allocation — higher-priority providers receive a larger share of the latency budget.', + ge=0, + ), + ] = 0 + tmpx_slots: Annotated[ + list[TmpxSlot] | None, + Field( + description='Stable provider-local slot identifiers for the ordered TMPX chunks this provider mints. Slot IDs are opaque provider-namespaced tokens (e.g. `["primary","secondary"]`), NOT ad-server macro names — publishers map `(provider_id, slot_id)` → local destination via `tmpx_macro_mapping` in publisher-tmpx-config.json, so the destination namespace stays publisher-owned and the router never accepts a destination name from an untrusted provider. Distinct providers MAY reuse the same slot_id without collision because publisher lookup is keyed on `(provider_id, slot_id)`. Publishers use this list at startup to validate `tmpx_macro_mapping` covers every slot the provider mints and to detect config drift when the provider\'s slot contract changes. Ordering carries the ordered-prefix invariant: a provider that emits fewer chunks than it registered MUST emit an ordered prefix of this list — chunks map to slots in registration order and MUST NOT be shifted, sparse, or reordered. Cap of 2 slots in v1 aligned with the GAM macro-slot budget; the cap MAY rise without a shape change. A provider that emits TMPX (populates `tmpx_chunks` on its identity-match response) MUST register this list; a provider that does not emit TMPX omits it. Schema cannot enforce that predicate because "emits TMPX" is not schema-visible.', + max_length=2, + min_length=1, + ), + ] = None + status: Annotated[ + Status | None, + Field( + description='Provider lifecycle status. Active providers receive requests. Inactive providers are skipped entirely. Draining providers stop receiving new requests but in-flight requests complete normally.' + ), + ] = Status.active + + + @field_validator('endpoint') + @classmethod + def _require_https_endpoint(cls, value: AnyUrl) -> AnyUrl: + if value.scheme != 'https': + raise ValueError('endpoint must use https') + return value + + @model_validator(mode='after') + def _require_identity_match_dimensions(self) -> TmpProviderRegistration2: + if self.identity_match is True: + if not self.countries: + raise ValueError('countries is required when identity_match is true') + if not self.uid_types: + raise ValueError('uid_types is required when identity_match is true') + return self -# ruff: noqa: E501, I001 -from __future__ import annotations -from adcp.types.generated_poc.trusted_match.provider_registration import ( - Country, - Status, - TmpProviderRegistration, - TmpProviderRegistration1, - TmpProviderRegistration2, - TmpxMacro, - TmpxSlot, -) - -# Explicit exports -__all__ = [ - "Country", - "Status", - "TmpProviderRegistration", - "TmpProviderRegistration1", - "TmpProviderRegistration2", - "TmpxMacro", - "TmpxSlot", +TmpProviderRegistration = Annotated[ + TmpProviderRegistration1 | TmpProviderRegistration2, + Field( + description="Declares a TMP provider's endpoint, capabilities, and operational parameters. Used in router configuration (static YAML or dynamic API) and referenced by product-level provider entries. The publisher controls which providers participate in their ad decisioning. Endpoint URLs MUST be validated against SSRF, and dynamic registration endpoints MUST authenticate callers — see docs/trusted-match/specification#provider-registration-security.", + title='TMP Provider Registration', + ), ] diff --git a/src/adcp/types/domains/trusted_match/publisher_targeting_kv_config.py b/src/adcp/types/domains/trusted_match/publisher_targeting_kv_config.py index 26f0ba516..50b801336 100644 --- a/src/adcp/types/domains/trusted_match/publisher_targeting_kv_config.py +++ b/src/adcp/types/domains/trusted_match/publisher_targeting_kv_config.py @@ -1,20 +1,22 @@ -"""Types declared by the AdCP ``trusted_match/publisher_targeting_kv_config`` schema. +# generated by datamodel-codegen: +# filename: trusted_match/publisher_targeting_kv_config.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.trusted_match.publisher_targeting_kv_config import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StringConstraints -from adcp.types.generated_poc.trusted_match.publisher_targeting_kv_config import ( - PublisherTargetingKvMapping, -) -# Explicit exports -__all__ = ["PublisherTargetingKvMapping"] +class PublisherTargetingKvMapping(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + targeting_kv_mapping: Annotated[ + dict[Annotated[str, StringConstraints(pattern=r'^[A-Za-z0-9_]+$', min_length=1, max_length=64)], dict[str, str]], + Field( + description="Map from the publisher-assigned `provider_id` to that provider's key-to-destination mapping for this surface. Outer keys use the `provider_id` charset from provider-registration.json. Inner keys are exact provider-local targeting keys from `signals_by_provider[provider_id].targeting_kvs`; no separator, prefix, or case normalization is applied. Values are publisher-local ad-server targeting destinations. At serve time, the publisher applies a mapping only when both the provider and key are own properties of their respective maps. Missing providers and missing keys are dropped independently, without falling back to the provider-local key. Multiple tuples may intentionally map to one destination; each mapped tuple contributes its value and MUST NOT overwrite another value. Publishers SHOULD warn at startup when this map names a provider that is not registered." + ), + ] diff --git a/src/adcp/types/domains/trusted_match/publisher_tmpx_config.py b/src/adcp/types/domains/trusted_match/publisher_tmpx_config.py index 21c83cb02..c20c39517 100644 --- a/src/adcp/types/domains/trusted_match/publisher_tmpx_config.py +++ b/src/adcp/types/domains/trusted_match/publisher_tmpx_config.py @@ -1,18 +1,25 @@ -"""Types declared by the AdCP ``trusted_match/publisher_tmpx_config`` schema. +# generated by datamodel-codegen: +# filename: trusted_match/publisher_tmpx_config.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.trusted_match.publisher_tmpx_config import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field, StringConstraints -from adcp.types.generated_poc.trusted_match.publisher_tmpx_config import PublisherTmpxMacroMapping -# Explicit exports -__all__ = ["PublisherTmpxMacroMapping"] +class PublisherTmpxMacroMapping(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + tmpx_macro_mapping: Annotated[ + dict[ + Annotated[str, StringConstraints(pattern=r'^[A-Za-z0-9_]+$', min_length=1, max_length=64)], + dict[Annotated[str, StringConstraints(pattern=r'^[a-zA-Z][a-zA-Z0-9_]*$', min_length=1, max_length=64)], str], + ], + Field( + description="Map from `provider_id` to a slot-keyed map of ad-server destinations (macro names, GAM key-values, VAST URL macro tokens, DOOH play-log field identifiers) for that provider on this surface. The inner map's keys are the provider-local `slot_id` values the provider registered in `tmpx_slots` (see provider-registration.json). At serve time, the publisher's adapter reads `tmpx_providers[provider_id].chunks[]` from the identity-match response and, for each chunk, substitutes the chunk's `value` into `tmpx_macro_mapping[provider_id][chunk.slot_id]`. Publishers use the outer map's keys plus the inner map's keys to validate the mapping at startup — every `slot_id` a provider registered in `tmpx_slots` SHOULD have a corresponding entry in that provider's inner map, and publishers SHOULD surface any missing entry as a startup warning so the operator can add it before responses begin carrying the unmapped slot. The exact destination string is publisher-local: GAM adopters typically use uppercase snake-case macro names, VAST surfaces use URL macro tokens, DOOH surfaces use play-log field identifiers. Outer-map key charset matches `provider_id` in provider-registration.json (enforced by `propertyNames`). Inner-map key charset matches `slot_id` in tmpx-chunk.json. When a response carries a `slot_id` for a provider that this mapping has no entry for — the mapping is missing the whole provider, OR the provider is present but the specific `slot_id` is unknown to this surface — the publisher's adapter MUST fail closed for that provider on that impression: none of that provider's chunks are fired into the ad-serving path and the adapter logs a configuration error. Other providers on the same response are unaffected. A `provider_id` absent from this mapping entirely (e.g. a newly onboarded provider not yet trafficked on this surface) is the same case — the adapter drops that provider's chunks on this surface rather than firing them into an unconfigured destination. Startup validation and serve-time fail-closed are two stages of a single rule: the warning at startup is the operator's chance to fix the mapping before the serve-time rule catches the missing slot." + ), + ] diff --git a/src/adcp/types/domains/trusted_match/tmpx_chunk.py b/src/adcp/types/domains/trusted_match/tmpx_chunk.py index 8dff555f0..0100d1b92 100644 --- a/src/adcp/types/domains/trusted_match/tmpx_chunk.py +++ b/src/adcp/types/domains/trusted_match/tmpx_chunk.py @@ -1,18 +1,33 @@ -"""Types declared by the AdCP ``trusted_match/tmpx_chunk`` schema. +# generated by datamodel-codegen: +# filename: trusted_match/tmpx_chunk.json +# timestamp: 2026-10-03T15:15:43+00:00 -One public module per schema, so a type name its own domain declares -more than once is still unambiguous: - - from adcp.types.domains.trusted_match.tmpx_chunk import +from __future__ import annotations -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-03 13:36:32 UTC -""" +from typing import Annotated -# ruff: noqa: E501, I001 -from __future__ import annotations +from adcp.types.base import AdCPBaseModel +from pydantic import ConfigDict, Field -from adcp.types.generated_poc.trusted_match.tmpx_chunk import TmpxChunk -# Explicit exports -__all__ = ["TmpxChunk"] +class TmpxChunk(AdCPBaseModel): + model_config = ConfigDict( + extra='forbid', + ) + slot_id: Annotated[ + str, + Field( + description="Provider-local slot identifier from the emitting provider's registered `tmpx_slots` (provider-registration.json). Opaque provider-namespaced token; publishers map `(provider_id, slot_id)` → local destination via `tmpx_macro_mapping`. NOT an ad-server macro name.", + max_length=64, + min_length=1, + pattern='^[a-zA-Z][a-zA-Z0-9_]*$', + ), + ] + value: Annotated[ + str, + Field( + description="Opaque, URL-safe wire string the publisher substitutes verbatim into the destination the publisher's mapping resolves for this `(provider_id, slot_id)` pair. Publishers MUST NOT parse, decode, or transform this value.", + max_length=1024, + min_length=1, + ), + ] diff --git a/src/adcp/types/error_details.py b/src/adcp/types/error_details.py index 9e4f13947..dfb69d42a 100644 --- a/src/adcp/types/error_details.py +++ b/src/adcp/types/error_details.py @@ -16,128 +16,117 @@ A nested name that two error-details schemas both define carries the defining file in its name (``ScopeFromRateLimited``) instead of a bare one. -Auto-generated from the generated_poc module tree. DO NOT EDIT MANUALLY. -Generation date: 2026-10-04 01:19:10 UTC +Auto-generated from the generated domain tree. DO NOT EDIT MANUALLY. +Generation date: 2026-10-04 18:45:15 UTC """ # ruff: noqa: E501, I001 from __future__ import annotations -from adcp.types.generated_poc.core.brand_key import BrandKey -from adcp.types.generated_poc.core.canonical_account_ref import ( +from adcp.types.domains.core.brand_key import BrandKey +from adcp.types.domains.core.canonical_account_ref import ( CanonicalAccountReference1, CanonicalAccountReference2, ) -from adcp.types.generated_poc.core.downstream_connection_requirement import ( +from adcp.types.domains.core.downstream_connection_requirement import ( ConnectionType, DownstreamConnectionRequirement, ResourceRef, Scope as ScopeFromDownstreamConnectionRequirement, Status as StatusFromDownstreamConnectionRequirement, ) -from adcp.types.generated_poc.core.ext import ExtensionObject -from adcp.types.generated_poc.core.format_option_ref import ( - FormatOptionReference1, - FormatOptionReference2, -) -from adcp.types.generated_poc.core.macro_encoding import Kind, MacroEncoding -from adcp.types.generated_poc.core.macro_resolution_result import ( +from adcp.types.domains.core.ext import ExtensionObject +from adcp.types.domains.core.format_option_ref import FormatOptionReference1, FormatOptionReference2 +from adcp.types.domains.core.macro_encoding import Kind, MacroEncoding +from adcp.types.domains.core.macro_resolution_result import ( MacroResolutionResult, Status as StatusFromMacroResolutionResult, UnavailableBehavior, ) -from adcp.types.generated_poc.core.media_buy_available_action import MediaBuyAvailableAction, Task -from adcp.types.generated_poc.core.operator_unit import OperatorUnit -from adcp.types.generated_poc.core.product_execution_requirement import ( +from adcp.types.domains.core.media_buy_available_action import MediaBuyAvailableAction, Task +from adcp.types.domains.core.operator_unit import OperatorUnit +from adcp.types.domains.core.product_execution_requirement import ( Connection, ProductExecutionRequirement1, ProductExecutionRequirement2, ProductExecutionRequirement3, ) -from adcp.types.generated_poc.core.representation_rejection import Code, RepresentationRejection -from adcp.types.generated_poc.core.sla_window import SlaWindow -from adcp.types.generated_poc.enums.action_not_allowed_reason import ActionNotAllowedReason -from adcp.types.generated_poc.enums.billing_party import BillingParty -from adcp.types.generated_poc.enums.catalog_type import CatalogType -from adcp.types.generated_poc.enums.event_type import EventType -from adcp.types.generated_poc.enums.macro_dialect import MacroDialectFamily -from adcp.types.generated_poc.enums.macro_mapping_status import MacroMappingStatus -from adcp.types.generated_poc.enums.macro_processing_operation import MacroProcessingOperation -from adcp.types.generated_poc.enums.macro_resolution_reason import MacroResolutionReason -from adcp.types.generated_poc.enums.macro_resolver import MacroProcessingActor -from adcp.types.generated_poc.enums.media_buy_action_mode import MediaBuyActionMode -from adcp.types.generated_poc.enums.media_buy_valid_action import MediaBuyValidAction -from adcp.types.generated_poc.enums.seller_policy_decline_reason import SellerPolicyDeclineReason -from adcp.types.generated_poc.enums.universal_macro import UniversalMacro -from adcp.types.generated_poc.enums.vast_version import VastVersion -from adcp.types.generated_poc.error_details.accessibility_violation import ( +from adcp.types.domains.core.representation_rejection import Code, RepresentationRejection +from adcp.types.domains.core.sla_window import SlaWindow +from adcp.types.domains.enums.action_not_allowed_reason import ActionNotAllowedReason +from adcp.types.domains.enums.billing_party import BillingParty +from adcp.types.domains.enums.catalog_type import CatalogType +from adcp.types.domains.enums.event_type import EventType +from adcp.types.domains.enums.macro_dialect import MacroDialectFamily +from adcp.types.domains.enums.macro_mapping_status import MacroMappingStatus +from adcp.types.domains.enums.macro_processing_operation import MacroProcessingOperation +from adcp.types.domains.enums.macro_resolution_reason import MacroResolutionReason +from adcp.types.domains.enums.macro_resolver import MacroProcessingActor +from adcp.types.domains.enums.media_buy_action_mode import MediaBuyActionMode +from adcp.types.domains.enums.media_buy_valid_action import MediaBuyValidAction +from adcp.types.domains.enums.seller_policy_decline_reason import SellerPolicyDeclineReason +from adcp.types.domains.enums.universal_macro import UniversalMacro +from adcp.types.domains.enums.vast_version import VastVersion +from adcp.types.domains.error_details.accessibility_violation import ( AccessibilityViolationDetails, FailureKind, FailureKind1, Violation, ) -from adcp.types.generated_poc.error_details.account_moved import AccountMovedDetails -from adcp.types.generated_poc.error_details.account_setup_required import ( - AccountSetupRequiredDetails, -) -from adcp.types.generated_poc.error_details.action_not_allowed import ActionNotAllowedDetails -from adcp.types.generated_poc.error_details.agent_permission_denied import ( - AgentPermissionDeniedDetails, -) -from adcp.types.generated_poc.error_details.audience_too_small import AudienceTooSmallDetails -from adcp.types.generated_poc.error_details.authorization_required import ( - AuthorizationRequiredDetails, -) -from adcp.types.generated_poc.error_details.billing_not_permitted_for_agent import ( +from adcp.types.domains.error_details.account_moved import AccountMovedDetails +from adcp.types.domains.error_details.account_setup_required import AccountSetupRequiredDetails +from adcp.types.domains.error_details.action_not_allowed import ActionNotAllowedDetails +from adcp.types.domains.error_details.agent_permission_denied import AgentPermissionDeniedDetails +from adcp.types.domains.error_details.audience_too_small import AudienceTooSmallDetails +from adcp.types.domains.error_details.authorization_required import AuthorizationRequiredDetails +from adcp.types.domains.error_details.billing_not_permitted_for_agent import ( BillingNotPermittedForAgentDetails, ) -from adcp.types.generated_poc.error_details.billing_not_supported import ( +from adcp.types.domains.error_details.billing_not_supported import ( BillingNotSupportedDetails, Scope as ScopeFromBillingNotSupported, ) -from adcp.types.generated_poc.error_details.budget_too_low import BudgetTooLowDetails -from adcp.types.generated_poc.error_details.conflict import ConflictDetails -from adcp.types.generated_poc.error_details.creative_rejected import CreativeRejectedDetails -from adcp.types.generated_poc.error_details.creative_representation_unresolved import ( +from adcp.types.domains.error_details.budget_too_low import BudgetTooLowDetails +from adcp.types.domains.error_details.conflict import ConflictDetails +from adcp.types.domains.error_details.creative_rejected import CreativeRejectedDetails +from adcp.types.domains.error_details.creative_representation_unresolved import ( CreativeRepresentationUnresolvedDetails, ) -from adcp.types.generated_poc.error_details.creative_revision_content_mismatch import ( +from adcp.types.domains.error_details.creative_revision_content_mismatch import ( CreativeRevisionContentMismatchDetails, ) -from adcp.types.generated_poc.error_details.execution_requirement_unmet import ( +from adcp.types.domains.error_details.execution_requirement_unmet import ( ExecutionRequirementUnmetDetails, Reason, UnmetRequirement, ) -from adcp.types.generated_poc.error_details.governance_agent_not_accepted import ( +from adcp.types.domains.error_details.governance_agent_not_accepted import ( GovernanceAgentNotAcceptedDetails, GovernanceAgentNotAcceptedDetails1, GovernanceAgentNotAcceptedDetails2, ) -from adcp.types.generated_poc.error_details.macro_resolution_failed import ( - MacroResolutionFailedDetails, -) -from adcp.types.generated_poc.error_details.policy_violation import Origin, PolicyViolationDetails -from adcp.types.generated_poc.error_details.rate_limited import ( +from adcp.types.domains.error_details.macro_resolution_failed import MacroResolutionFailedDetails +from adcp.types.domains.error_details.policy_violation import Origin, PolicyViolationDetails +from adcp.types.domains.error_details.rate_limited import ( RateLimitedDetails, Scope as ScopeFromRateLimited, ) -from adcp.types.generated_poc.error_details.requote_required import ( +from adcp.types.domains.error_details.requote_required import ( EnvelopeField, EnvelopeField1, EnvelopeField1Item, RequoteRequiredDetails, ) -from adcp.types.generated_poc.error_details.stale_response import ( +from adcp.types.domains.error_details.stale_response import ( OriginalError, StaleResponseDetails, Upstream, ) -from adcp.types.generated_poc.error_details.unsupported_refinement_dimension import ( +from adcp.types.domains.error_details.unsupported_refinement_dimension import ( SupportedDimension, UnsupportedRefinementDimensionDetails, ) -from adcp.types.generated_poc.error_details.vast_version_mismatch import ( +from adcp.types.domains.error_details.vast_version_mismatch import ( DocumentRole, MismatchReason, VastVersionMismatchDetails, @@ -145,18 +134,18 @@ VastVersionMismatchDetails2, VastVersionMismatchDetails3, ) -from adcp.types.generated_poc.error_details.vendor_error_codes import ( +from adcp.types.domains.error_details.vendor_error_codes import ( Codes, Recovery, VendorErrorCodeRegistry, Vendors, ) -from adcp.types.generated_poc.error_details.version_unsupported import ( +from adcp.types.domains.error_details.version_unsupported import ( SupportedMajor, SupportedVersion, VersionUnsupportedDetails, ) -from adcp.types.generated_poc.governance.accepted_governance_agents import ( +from adcp.types.domains.governance.accepted_governance_agents import ( AcceptedGovernanceAgents, AnyOf, AnyOf1, diff --git a/src/adcp/types/generated_poc/__init__.py b/src/adcp/types/generated_poc/__init__.py deleted file mode 100644 index 6d1f2364d..000000000 --- a/src/adcp/types/generated_poc/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# generated by datamodel-codegen: -# filename: .schema_temp -# timestamp: 2025-11-18T04:34:42+00:00 diff --git a/src/adcp/types/generated_poc/a2ui/__init__.py b/src/adcp/types/generated_poc/a2ui/__init__.py deleted file mode 100644 index e7f4c72a0..000000000 --- a/src/adcp/types/generated_poc/a2ui/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# generated by datamodel-codegen: -# filename: .schema_temp -# timestamp: 2026-02-02T17:45:21+00:00 diff --git a/src/adcp/types/generated_poc/a2ui/bound_value.py b/src/adcp/types/generated_poc/a2ui/bound_value.py deleted file mode 100644 index 2afc76ba3..000000000 --- a/src/adcp/types/generated_poc/a2ui/bound_value.py +++ /dev/null @@ -1,58 +0,0 @@ -# generated by datamodel-codegen: -# filename: a2ui/bound_value.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictBool, StrictFloat - - -class A2UiBoundValue1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - literalString: Annotated[str, Field(description='Static string value')] - - -class A2UiBoundValue2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - literalNumber: Annotated[StrictFloat, Field(description='Static number value')] - - -class A2UiBoundValue3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - literalBoolean: Annotated[StrictBool, Field(description='Static boolean value')] - - -class A2UiBoundValue4(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - path: Annotated[ - str, - Field(description="JSON pointer path to value in data model (e.g., '/products/0/title')"), - ] - - -class A2UiBoundValue5(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - literalString: str - path: str - - -A2UiBoundValue = Annotated[ - A2UiBoundValue1 | A2UiBoundValue2 | A2UiBoundValue3 | A2UiBoundValue4 | A2UiBoundValue5, - Field( - description='A value that can be a literal or bound to a path in the data model', - title='A2UI Bound Value', - ), -] diff --git a/src/adcp/types/generated_poc/a2ui/component.py b/src/adcp/types/generated_poc/a2ui/component.py deleted file mode 100644 index 6b4065cd3..000000000 --- a/src/adcp/types/generated_poc/a2ui/component.py +++ /dev/null @@ -1,26 +0,0 @@ -# generated by datamodel-codegen: -# filename: a2ui/component.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - - -class A2UiComponent(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - id: Annotated[str, Field(description='Unique identifier for this component within the surface')] - parentId: Annotated[ - str | None, Field(description='ID of the parent component (null for root)') - ] = None - component: Annotated[ - dict[str, dict[str, Any]], - Field( - description='Component definition (keyed by component type)', max_length=1, min_length=1 - ), - ] diff --git a/src/adcp/types/generated_poc/a2ui/si_catalog.py b/src/adcp/types/generated_poc/a2ui/si_catalog.py deleted file mode 100644 index 096662c7e..000000000 --- a/src/adcp/types/generated_poc/a2ui/si_catalog.py +++ /dev/null @@ -1,191 +0,0 @@ -# generated by datamodel-codegen: -# filename: a2ui/si_catalog.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, Field, StrictBool - -from . import bound_value - - -class Component(StrEnum): - Text = 'Text' - Button = 'Button' - Link = 'Link' - Image = 'Image' - Card = 'Card' - ProductCard = 'ProductCard' - List = 'List' - Row = 'Row' - Column = 'Column' - IntegrationAction = 'IntegrationAction' - AppHandoff = 'AppHandoff' - - -class SiComponentCatalog(AdCPBaseModel): - catalogId: Annotated[ - Literal['si-standard'], Field(description='SI standard component catalog') - ] = 'si-standard' - components: Annotated[ - list[Component] | None, Field(description='Available component types') - ] = None - - -class Variant(StrEnum): - body = 'body' - heading = 'heading' - caption = 'caption' - label = 'label' - - -class Text(AdCPBaseModel): - text: Annotated[bound_value.A2UiBoundValue, Field(description='Text content to display')] - variant: Variant | None = Variant.body - - -class Action(AdCPBaseModel): - name: Annotated[str, Field(description='Action identifier')] - context: Annotated[ - dict[str, bound_value.A2UiBoundValue] | None, - Field(description='Context data to include with action'), - ] = None - - -class Variant4(StrEnum): - primary = 'primary' - secondary = 'secondary' - text = 'text' - - -class Button(AdCPBaseModel): - label: Annotated[bound_value.A2UiBoundValue, Field(description='Button label text')] - action: Annotated[Action, Field(description='Action to trigger on click')] - variant: Variant4 | None = Variant4.primary - disabled: bound_value.A2UiBoundValue | None = None - - -class Link(AdCPBaseModel): - label: Annotated[bound_value.A2UiBoundValue, Field(description='Link text')] - url: Annotated[bound_value.A2UiBoundValue, Field(description='URL to navigate to')] - external: Annotated[StrictBool | None, Field(description='Opens in new tab if true')] = True - - -class Image(AdCPBaseModel): - src: Annotated[bound_value.A2UiBoundValue, Field(description='Image source URL')] - alt: Annotated[bound_value.A2UiBoundValue, Field(description='Alt text for accessibility')] - width: Annotated[SchemaInt | None, Field(description='Image width in pixels')] = None - height: Annotated[SchemaInt | None, Field(description='Image height in pixels')] = None - - -class Action23(AdCPBaseModel): - name: str - context: dict[str, bound_value.A2UiBoundValue] | None = None - - -class Card(AdCPBaseModel): - title: Annotated[bound_value.A2UiBoundValue, Field(description='Card title')] - subtitle: Annotated[bound_value.A2UiBoundValue | None, Field(description='Card subtitle')] = ( - None - ) - image: Annotated[bound_value.A2UiBoundValue | None, Field(description='Card image URL')] = None - badge: Annotated[ - bound_value.A2UiBoundValue | None, Field(description="Badge text (e.g., 'New', 'Sale')") - ] = None - action: Annotated[Action23 | None, Field(description='Action to trigger on card click')] = None - children: Annotated[list[str] | None, Field(description='Child component IDs')] = None - - -class ProductCard(AdCPBaseModel): - title: Annotated[bound_value.A2UiBoundValue, Field(description='Product name')] - price: Annotated[bound_value.A2UiBoundValue, Field(description='Price display string')] - image: Annotated[bound_value.A2UiBoundValue | None, Field(description='Product image URL')] = ( - None - ) - description: Annotated[ - bound_value.A2UiBoundValue | None, Field(description='Product description') - ] = None - badge: Annotated[ - bound_value.A2UiBoundValue | None, Field(description="Badge text (e.g., 'Best Seller')") - ] = None - ctaLabel: Annotated[ - bound_value.A2UiBoundValue | None, Field(description='CTA button label') - ] = None - action: Action23 | None = None - - -class Template(AdCPBaseModel): - componentId: Annotated[str, Field(description='ID of component to use as template')] - - -class Layout(StrEnum): - vertical = 'vertical' - horizontal = 'horizontal' - grid = 'grid' - - -class List(AdCPBaseModel): - items: Annotated[bound_value.A2UiBoundValue, Field(description='Path to array in data model')] - template: Annotated[Template, Field(description='Template for list items')] - emptyMessage: Annotated[ - bound_value.A2UiBoundValue | None, Field(description='Message to show when list is empty') - ] = None - layout: Layout | None = Layout.vertical - - -class Align(StrEnum): - start = 'start' - center = 'center' # type: ignore[assignment] - end = 'end' - stretch = 'stretch' - - -class Justify(StrEnum): - start = 'start' - center = 'center' # type: ignore[assignment] - end = 'end' - between = 'between' - around = 'around' - - -class Row(AdCPBaseModel): - children: Annotated[list[str], Field(description='Child component IDs')] - gap: Annotated[str | None, Field(description='Gap between children')] = '8px' - align: Align | None = Align.center - justify: Justify | None = Justify.start - - -class Column(AdCPBaseModel): - children: Annotated[list[str], Field(description='Child component IDs')] - gap: Annotated[str | None, Field(description='Gap between children')] = '8px' - align: Align | None = Align.stretch - - -class Type(StrEnum): - mcp = 'mcp' - a2a = 'a2a' - - -class IntegrationAction(AdCPBaseModel): - type: Annotated[Type, Field(description='Integration type')] - label: Annotated[bound_value.A2UiBoundValue, Field(description='Button label')] - url: Annotated[ - bound_value.A2UiBoundValue | None, Field(description='Integration endpoint URL') - ] = None - highlighted: Annotated[ - StrictBool | None, Field(description='Visually emphasize this action') - ] = False - - -class Apps(AdCPBaseModel): - appId: str | None = None - deepLink: str | None = None - url: AnyUrl | None = None - - -class AppHandoff(AdCPBaseModel): - apps: Annotated[dict[str, Apps], Field(description='Platform-specific app configurations')] diff --git a/src/adcp/types/generated_poc/a2ui/surface.py b/src/adcp/types/generated_poc/a2ui/surface.py deleted file mode 100644 index 2bac4e883..000000000 --- a/src/adcp/types/generated_poc/a2ui/surface.py +++ /dev/null @@ -1,33 +0,0 @@ -# generated by datamodel-codegen: -# filename: a2ui/surface.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from . import component - - -class A2UiSurface(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - surfaceId: Annotated[str, Field(description='Unique identifier for this surface')] - catalogId: Annotated[ - str | None, Field(description='Component catalog to use for rendering') - ] = 'standard' - components: Annotated[ - list[component.A2UiComponent], - Field(description='Flat list of components (adjacency list structure)'), - ] - rootId: Annotated[ - str | None, - Field(description='ID of the root component (if not specified, first component is root)'), - ] = None - dataModel: Annotated[ - dict[str, Any] | None, Field(description='Application data that components can bind to') - ] = None diff --git a/src/adcp/types/generated_poc/a2ui/user_action.py b/src/adcp/types/generated_poc/a2ui/user_action.py deleted file mode 100644 index 44c5cf991..000000000 --- a/src/adcp/types/generated_poc/a2ui/user_action.py +++ /dev/null @@ -1,32 +0,0 @@ -# generated by datamodel-codegen: -# filename: a2ui/user_action.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - - -class Action(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, Field(description="Action identifier (e.g., 'view_product', 'add_to_cart')") - ] - context: Annotated[ - dict[str, Any] | None, Field(description='Context data resolved from data model bindings') - ] = None - - -class A2UiUserAction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - surfaceId: Annotated[str, Field(description='ID of the surface where the action occurred')] - componentId: Annotated[str, Field(description='ID of the component that triggered the action')] - action: Annotated[Action, Field(description='The action that was triggered')] - timestamp: Annotated[AwareDatetime | None, Field(description='When the action occurred')] = None diff --git a/src/adcp/types/generated_poc/aao/__init__.py b/src/adcp/types/generated_poc/aao/__init__.py deleted file mode 100644 index 1c1295b28..000000000 --- a/src/adcp/types/generated_poc/aao/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# generated by datamodel-codegen: -# filename: .schema_temp -# timestamp: 2026-06-05T16:54:33+00:00 diff --git a/src/adcp/types/generated_poc/aao/agent_publishers.py b/src/adcp/types/generated_poc/aao/agent_publishers.py deleted file mode 100644 index da5d89b10..000000000 --- a/src/adcp/types/generated_poc/aao/agent_publishers.py +++ /dev/null @@ -1,117 +0,0 @@ -# generated by datamodel-codegen: -# filename: aao/agent_publishers.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictBool - - -class DiscoveryMethod(StrEnum): - direct = 'direct' - authoritative_location = 'authoritative_location' - adagents_authoritative = 'adagents_authoritative' - ads_txt_managerdomain = 'ads_txt_managerdomain' - - -class Status(StrEnum): - authorized = 'authorized' - revoked = 'revoked' - - -class PublisherEntry(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - publisher_domain: Annotated[ - str, - Field( - description='Publisher whose adagents.json authorizes the agent.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - discovery_method: Annotated[ - DiscoveryMethod, - Field( - description="How the directory discovered this authorization. `direct`: agent listed in publisher's own /.well-known/adagents.json. `authoritative_location`: publisher's /.well-known/adagents.json declared `authoritative_location` pointing to a manager file that lists the agent. `adagents_authoritative`: discovered via the manager file (publisher-declared in the manager's own properties[]). `ads_txt_managerdomain`: discovered via the publisher's ads.txt `MANAGERDOMAIN=` directive pointing to the manager file. The last three paths converge on the same manager file but have different trust profiles — the `managerdomain` path is the weakest because the manager file's `publisher_domain` anchor (the `managerdomain` fallback safety rule) is the only positive cross-check." - ), - ] - manager_domain: Annotated[ - str | None, - Field( - description='Domain of the manager file that authorizes this publisher → agent edge. Required when `discovery_method` is `authoritative_location`, `adagents_authoritative`, or `ads_txt_managerdomain`. Null or absent when `discovery_method` is `direct`.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - properties_authorized: Annotated[ - SchemaInt, - Field( - description="Count of properties under THIS `publisher_domain` only that the agent's selectors resolve to. Never a network-wide count. The directory computes this by applying the publisher's `adagents.json` selector predicates against the publisher's own properties (federated) OR against the parent file's inline properties carrying matching `publisher_domain` (inline, per adcp#4825 resolution rule).", - ge=0, - ), - ] - properties_total: Annotated[ - SchemaInt, - Field( - description="Count of properties under THIS `publisher_domain` only — total inventory the publisher's file declares. Never a network-wide count. On managed-network-shape parent files (per adcp#4825 inline resolution), this is the count of inline `properties[]` entries whose `publisher_domain` field matches this row's domain.", - ge=0, - ), - ] - property_ids: Annotated[ - list[str] | None, - Field( - description="Canonical list of `property_id`s under THIS `publisher_domain` that the agent's selectors resolve to. Present iff the request included `?include=properties`; absent otherwise. The set is the same population `properties_authorized` counts, surfaced as IDs so consumers can run full set-diff against a federated fetch (count-equality is not set-equality — a publisher rotating N properties leaves the count unchanged but the set entirely different). Per-publisher scope; never network-wide. Order is unspecified; consumers should treat as a set." - ), - ] = None - signing_keys_pinned: Annotated[ - StrictBool | None, - Field( - description="Whether the publisher's adagents.json entry for this agent pins `signing_keys[]`. When true, the agent's signed responses MUST verify against the pinned key set regardless of the agent's own JWKS. Operators should treat true as a signal that their published JWKS must match the publisher's pin." - ), - ] = None - status: Annotated[ - Status, - Field( - description="Lifecycle state for this publisher → agent edge. v1: `authorized` (selector resolves to ≥ 1 property under this publisher) and `revoked` (this publisher_domain newly appears in a parent file's `revoked_publisher_domains[]`; emitted as a tombstone on the next sync after revocation lands, then dropped). Future states (`unbound`, `pending`) deferred — the directory does not have the crawler state to emit them honestly." - ), - ] - last_verified_at: Annotated[ - AwareDatetime, - Field( - description="When the directory last fetched and validated this publisher's adagents.json. Distinct from the envelope's `directory_indexed_at` — `last_verified_at` is per-publisher freshness, the envelope value is the most recent refresh in the result set." - ), - ] - - -class AaoDirectoryAgentPublishersInverseLookupResponse(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description='Canonicalized echo of the agent_url that was looked up. Lowercase host, default port stripped, trailing slash on path component normalized — matches the canonicalization the SDK applies in `verify_agent_authorization`.' - ), - ] - directory_indexed_at: Annotated[ - AwareDatetime | None, - Field( - description="When the directory last completed a refresh of any publisher in this result. Provenance anchor for the consumer's own cache. NULL on empty pages — there's no per-publisher anchor to report; consumers SHOULD treat a null value as 'no freshness assertion for this response' and not advance their own cache freshness." - ), - ] - publishers: Annotated[ - list[PublisherEntry], - Field( - description='Publishers whose adagents.json authorizes this agent. Empty array is a valid response (the directory has indexed this agent but no current authorizations resolve).' - ), - ] - next_cursor: Annotated[ - str | None, - Field( - description="Opaque pagination cursor. Absent or null on the terminal page. Stable across the directory's refresh cycle for the lifetime of the cursor." - ), - ] = None diff --git a/src/adcp/types/generated_poc/account/__init__.py b/src/adcp/types/generated_poc/account/__init__.py deleted file mode 100644 index e7f4c72a0..000000000 --- a/src/adcp/types/generated_poc/account/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# generated by datamodel-codegen: -# filename: .schema_temp -# timestamp: 2026-02-02T17:45:21+00:00 diff --git a/src/adcp/types/generated_poc/account/get_account_financials_request.py b/src/adcp/types/generated_poc/account/get_account_financials_request.py deleted file mode 100644 index b1fd73023..000000000 --- a/src/adcp/types/generated_poc/account/get_account_financials_request.py +++ /dev/null @@ -1,33 +0,0 @@ -# generated by datamodel-codegen: -# filename: account/get_account_financials_request.json -# timestamp: 2026-10-02T23:10:32+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import ConfigDict, Field - -from ..core import account_ref -from ..core import context as context_1 -from ..core import date_range -from ..core import ext as ext_1 -from ..core.version_envelope import AdcpVersionEnvelope - - -class GetAccountFinancialsRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - account: Annotated[ - account_ref.AccountReference, - Field(description='Account to query financials for. Must be an operator-billed account.'), - ] - period: Annotated[ - date_range.DateRange | None, - Field( - description='Date range for the spend summary. Defaults to the current billing cycle if omitted.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/account/get_account_financials_response.py b/src/adcp/types/generated_poc/account/get_account_financials_response.py deleted file mode 100644 index f000dd0e3..000000000 --- a/src/adcp/types/generated_poc/account/get_account_financials_response.py +++ /dev/null @@ -1,93 +0,0 @@ -# generated by datamodel-codegen: -# filename: account/get_account_financials_response.json -# timestamp: preserved-by-post-generate-fixes - -from __future__ import annotations - -from datetime import date - -from typing import Annotated, Literal, TypeAlias - -from pydantic import ConfigDict, Field, StringConstraints - -from ..core.version_envelope import AdcpVersionEnvelope -from ..core import account_ref as account_ref_1 -from ..core import context as context_1 -from ..core import date_range as date_range_1 -from ..core import error as error_1 -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..enums import payment_terms as payment_terms_1 - - -class Spend(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - total_spend: Annotated[float, Field(ge=0)] - media_buy_count: Annotated[int, Field(ge=0)] | None = None - - -class Credit(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - credit_limit: Annotated[float, Field(ge=0)] - available_credit: float - utilization_percent: Annotated[float, Field(ge=0, le=100)] | None = None - - -class LastTopUp(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - amount: Annotated[float, Field(ge=0)] - date: date - - -class Balance(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - available: Annotated[float, Field(ge=0)] - last_top_up: LastTopUp | None = None - - -class Invoice(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - invoice_id: str - period: date_range_1.DateRange | None = None - amount: Annotated[float, Field(ge=0)] - status: Literal['draft', 'issued', 'paid', 'past_due', 'void'] - due_date: date | None = None - paid_date: date | None = None - - -class GetAccountFinancialsResponse1(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - account: account_ref_1.AccountReference - currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] - period: date_range_1.DateRange - timezone: str - spend: Spend | None = None - credit: Credit | None = None - balance: Balance | None = None - payment_status: Literal['current', 'past_due', 'suspended'] | None = None - payment_terms: payment_terms_1.PaymentTerms | None = None - invoices: list[Invoice] | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class GetAccountFinancialsResponse2(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - errors: Annotated[list[error_1.Error], Field(min_length=1)] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -GetAccountFinancialsResponse: TypeAlias = GetAccountFinancialsResponse1 | GetAccountFinancialsResponse2 - - -__all__ = [ - 'GetAccountFinancialsResponse', - 'GetAccountFinancialsResponse1', - 'GetAccountFinancialsResponse2', - 'Balance', - 'Credit', - 'Invoice', - 'LastTopUp', - 'Spend', -] diff --git a/src/adcp/types/generated_poc/account/list_account_changes_request.py b/src/adcp/types/generated_poc/account/list_account_changes_request.py deleted file mode 100644 index 23461c7e9..000000000 --- a/src/adcp/types/generated_poc/account/list_account_changes_request.py +++ /dev/null @@ -1,86 +0,0 @@ -# generated by datamodel-codegen: -# filename: account/list_account_changes_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field - -from ..core import account_ref -from ..core import context as context_1 -from ..core import ext as ext_1 - - -class StartingPosition(StrEnum): - earliest = 'earliest' - latest = 'latest' - - -class ResourceType(ScalarStr): - __slots__ = () - _constraints = {'max_length': 100, 'min_length': 1, 'pattern': '^[a-z][a-z0-9_.-]{0,99}$'} - - -class ListAccountChangesRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version. Flattened here so emitted MCP tool input schemas remain plain root objects for clients that reject top-level combinators.', - pattern='^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?)?$', - ), - ] = None - adcp_major_version: Annotated[ - SchemaInt | None, - Field( - deprecated=True, - description='Deprecated major-only version pin retained through 3.x.', - ge=1, - le=99, - ), - ] = None - account: Annotated[ - account_ref.AccountReference, - Field( - description='Account whose change feed to read. The resolved account and returned cursor are bound to the authenticated principal.' - ), - ] - cursor: Annotated[ - str | None, - Field( - description='Opaque checkpoint returned by a prior call using the same authenticated principal, authorization scope epoch, account, and normalized filters. Returns changes strictly after the scanned high-water represented by this value. Sellers MUST reject reuse under a different principal, account, or filter set with INVALID_REQUEST at field cursor; an authorization-scope epoch change returns CURSOR_EXPIRED and requires snapshot rebootstrap.', - max_length=4096, - min_length=1, - ), - ] = None - starting_position: Annotated[ - StartingPosition | None, - Field( - description='Initial position when cursor is absent. Mutually exclusive with cursor; sellers enforce this semantic rule at runtime so the emitted MCP input schema can remain a plain root object. earliest intentionally begins at the oldest retained change. latest returns a checkpoint at the current seller-ingestion high-water and is used before a race-free snapshot bootstrap.' - ), - ] = StartingPosition.earliest - resource_types: Annotated[ - list[ResourceType] | None, - Field( - description='Optional exact resource-type filter. The cursor is bound to the normalized filter. Unknown resource types are allowed for forward compatibility.', - max_length=50, - min_length=1, - ), - ] = None - max_results: Annotated[ - SchemaInt | None, - Field( - description='Maximum changes to return. Sellers still scan through nonmatching records and advance the returned cursor.', - ge=1, - le=100, - ), - ] = 50 - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/account/list_account_changes_response.py b/src/adcp/types/generated_poc/account/list_account_changes_response.py deleted file mode 100644 index 6c700af41..000000000 --- a/src/adcp/types/generated_poc/account/list_account_changes_response.py +++ /dev/null @@ -1,140 +0,0 @@ -# generated by datamodel-codegen: -# filename: account/list_account_changes_response.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarStr -from pydantic import AwareDatetime, ConfigDict, Field, StrictBool, model_validator - -from ..core import account_change -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope - - -class Kind(StrEnum): - seller = 'seller' - connected_platform = 'connected_platform' - - -class Status(StrEnum): - current = 'current' - delayed = 'delayed' - unavailable = 'unavailable' - not_configured = 'not_configured' - - -class ResourceType(ScalarStr): - __slots__ = () - _constraints = {'max_length': 100, 'min_length': 1, 'pattern': '^[a-z][a-z0-9_.-]{0,99}$'} - - -class SourceCoverageItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - source_id: Annotated[ - str, Field(description='Opaque seller or connection reference.', max_length=255) - ] - kind: Kind - status: Annotated[ - Status, - Field( - description='Connector freshness classification. For connected_platform, current means last_successful_sync_at is no older than stale_after_seconds at generated_at and the seller knows of no ingestion gap; delayed means that threshold is exceeded or a gap is known.' - ), - ] - coverage_start: Annotated[ - AwareDatetime | None, - Field( - description='Earliest upstream time covered by this source after connection or retention limits.' - ), - ] = None - observed_through: Annotated[ - AwareDatetime | None, - Field(description='Latest upstream point the seller has successfully observed.'), - ] = None - last_successful_sync_at: AwareDatetime | None = None - stale_after_seconds: Annotated[ - SchemaInt | None, - Field( - description='Maximum age of last_successful_sync_at at generated_at for this connected source to self-classify as current. Required by the protocol contract whenever a connected source reports current.', - ge=1, - le=2592000, - ), - ] = None - resource_types: Annotated[list[ResourceType], Field(max_length=50)] - - -class Status22(StrEnum): - completed = 'completed' - failed = 'failed' - - -class ListAccountChangesResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - changes: Annotated[ - list[account_change.AccountChange] | None, - Field( - description='Matching changes in oldest-first total account order. Timestamps are descriptive and do not define this order.', - max_length=100, - ), - ] = None - cursor: Annotated[ - str | None, - Field( - description='Opaque checkpoint strictly after the high-water scanned by this page. Always persist this value, including when changes is empty or has_more is false. A filtered empty page still advances past scanned nonmatching records.', - max_length=4096, - min_length=1, - ), - ] = None - has_more: Annotated[ - StrictBool | None, - Field( - description='True when more retained matching changes were available at generation time. False means caught up to seller ingestion, not necessarily to an unavailable or delayed connected source.' - ), - ] = None - available_since: Annotated[ - AwareDatetime | None, - Field( - description='Oldest time for which the seller currently retains change records for this account and caller. The capability guarantees at least 90 days after adoption; pre-adoption history is not fabricated.' - ), - ] = None - generated_at: Annotated[ - AwareDatetime | None, - Field( - description='Seller time when this page and its source-coverage watermarks were generated.' - ), - ] = None - source_coverage: Annotated[ - list[SourceCoverageItem] | None, - Field( - description='Account-specific feed and connector coverage. Buyers use this to distinguish feed catch-up from upstream freshness. Omission means no additional connected-source coverage is declared.', - max_length=50, - ), - ] = None - errors: list[error.Error] | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - status: Status22 - - @model_validator(mode='after') - def _require_schema_required_group(self) -> ListAccountChangesResponse: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('status', 'changes', 'cursor', 'has_more', 'available_since', 'generated_at'), ('status', 'adcp_error', 'errors'),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'ListAccountChangesResponse requires at least one of these field groups: status+changes+cursor+has_more+available_since+generated_at | status+adcp_error+errors' - ) diff --git a/src/adcp/types/generated_poc/account/list_accounts_request.py b/src/adcp/types/generated_poc/account/list_accounts_request.py deleted file mode 100644 index b31049f71..000000000 --- a/src/adcp/types/generated_poc/account/list_accounts_request.py +++ /dev/null @@ -1,65 +0,0 @@ -# generated by datamodel-codegen: -# filename: account/list_accounts_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import SchemaInt -from pydantic import ConfigDict, Field, StrictBool - -from ..core import account_ref -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core import pagination_request -from ..core.version_envelope import AdcpVersionEnvelope - - -class Status(StrEnum): - active = 'active' - pending_approval = 'pending_approval' - rejected = 'rejected' - payment_required = 'payment_required' - suspended = 'suspended' - closed = 'closed' - - -class ListAccountsRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - account: Annotated[ - account_ref.AccountReference | None, - Field( - description='Optional exact account filter. Use `account_id` to retrieve one known seller/storefront account, or the complete natural key (`brand` + `operator` + optional `operator_unit`, fixed `currency`, buyer-selected account `timezone`, and `sandbox`) for buyer-declared accounts. When present, the seller returns only matching accounts visible to the authenticated caller.' - ), - ] = None - status: Annotated[ - Status | None, - Field(description='Filter accounts by status. Omit to return accounts in all statuses.'), - ] = None - pagination: pagination_request.PaginationRequest | None = None - sandbox: Annotated[ - StrictBool | None, - Field( - description='Filter by sandbox status. true returns only sandbox accounts, false returns only production accounts. Omit to return all accounts. Primarily used with account-id namespaces where sandbox accounts are pre-existing test accounts on the platform.' - ), - ] = None - include_webhook_activity: Annotated[ - StrictBool | None, - Field( - description='When true, request recent webhook delivery attempts for each returned account in account.webhook_activity[]. Sellers MAY omit webhook_activity if they do not expose this debug log; when present, three-state semantics match the shared webhook_activity[] contract.' - ), - ] = False - webhook_activity_limit: Annotated[ - SchemaInt | None, - Field( - description='Maximum number of webhook_activity[] records to return per account when include_webhook_activity is true.', - ge=1, - le=200, - ), - ] = 50 - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/account/list_accounts_response.py b/src/adcp/types/generated_poc/account/list_accounts_response.py deleted file mode 100644 index fae82b58e..000000000 --- a/src/adcp/types/generated_poc/account/list_accounts_response.py +++ /dev/null @@ -1,35 +0,0 @@ -# generated by datamodel-codegen: -# filename: account/list_accounts_response.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import ConfigDict, Field - -from ..core import account_with_authorization -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 -from ..core import pagination_response -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope - - -class ListAccountsResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - accounts: Annotated[ - list[account_with_authorization.AccountWithAuthorization], - Field( - description='Array of accounts accessible to the authenticated agent. Each entry is the full Account object plus optional authorization. Buyer-declared entries include brand, operator, and any operator_unit, fixed currency, buyer-selected account timezone, and sandbox qualifiers so the natural AccountRef round-trips after a cold start.' - ), - ] - errors: Annotated[ - list[error.Error] | None, Field(description='Task-specific errors and warnings') - ] = None - pagination: pagination_response.PaginationResponse | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/account/report_usage_request.py b/src/adcp/types/generated_poc/account/report_usage_request.py deleted file mode 100644 index 43042ef2a..000000000 --- a/src/adcp/types/generated_poc/account/report_usage_request.py +++ /dev/null @@ -1,158 +0,0 @@ -# generated by datamodel-codegen: -# filename: account/report_usage_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat - -from ..core import account_ref -from ..core import context as context_1 -from ..core import datetime_range -from ..core import ext as ext_1 -from ..core.version_envelope import AdcpVersionEnvelope - - -class UsageItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - account: Annotated[ - account_ref.AccountReference, Field(description='Account for this usage record.') - ] - media_buy_id: Annotated[ - str | None, - Field( - description='Seller-assigned media buy identifier. Links this usage record to a specific media buy.' - ), - ] = None - vendor_cost: Annotated[ - StrictFloat, - Field( - description='Amount owed to the vendor for this record, denominated in currency.', - ge=0.0, - ), - ] - currency: Annotated[str, Field(description='ISO 4217 currency code.', pattern='^[A-Z]{3}$')] - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option identifier from the vendor's discovery response (e.g., get_signals, list_content_standards). The vendor uses this to verify the correct rate was applied." - ), - ] = None - impressions: Annotated[ - SchemaInt | None, - Field(description='Impressions delivered using this vendor service.', ge=0), - ] = None - media_spend: Annotated[ - StrictFloat | None, - Field( - description='Media spend in currency for the period. Required when a percent_of_media pricing model was used, so the vendor can verify the applied rate.', - ge=0.0, - ), - ] = None - conversions: Annotated[ - StrictFloat | None, - Field( - description='Number of attributed conversion events for the reporting period. Optional analytics context for revenue_share reconciliation.', - ge=0.0, - ), - ] = None - conversion_value: Annotated[ - StrictFloat | None, - Field( - description='Total monetary value of attributed conversions for the reporting period, in currency. Optional analytics context for revenue_share reconciliation; it is not the billing basis.', - ge=0.0, - ), - ] = None - commissionable_value: Annotated[ - StrictFloat | None, - Field( - description='Settled attributed value eligible for commission, in currency. Required when pricing_option_id selects a revenue_share option. The receiver verifies vendor_cost = round_currency(commissionable_value × the selected commission_rate).', - ge=0.0, - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field(description='Signal identifier from get_signals. Required for signals agents.'), - ] = None - standards_id: Annotated[ - str | None, - Field( - description='Content standards configuration identifier. Required for governance agents.' - ), - ] = None - rights_id: Annotated[ - str | None, - Field( - description='Rights grant identifier from acquire_rights. Required for brand/rights agents. Links usage records to specific rights grants for cap tracking, billing verification, and overage calculation.' - ), - ] = None - creative_id: Annotated[ - str | None, - Field( - description='Creative identifier from build_creative or list_creatives. Required for creative agents. Links usage records to specific creatives for billing verification.' - ), - ] = None - build_variant_id: Annotated[ - str | None, - Field( - description='Optional. When the reported creative_id was promoted from a specific build_creative variant leaf but the creative_id differs from the source build_variant_id, carry that source build_variant_id so billing reconciliation can link this usage record back to the exact produced leaf for audit (pricing_option_id alone is not unique across leaves). On the canonical path where creative_id is the build_variant_id, omit this field and use creative_id as the join key. Omit for creatives with no build-variant lineage.' - ), - ] = None - property_list_id: Annotated[ - str | None, - Field( - description='Property list identifier from list_property_lists. Required for property list agents. Links usage records to specific property lists for billing verification.' - ), - ] = None - final: Annotated[ - StrictBool | None, - Field( - description="Whether this usage record represents the reporter's final, billing-authoritative numbers for the reporting period. **Absent means unknown** — the reporter has not declared finality on this record. Set `true` only when the reporter has actually settled the numbers (e.g., 3PAS month-end close after SIVT scrubbing, conversion dedup, and view-through windows have closed; vendor file post-C7 for broadcast). Set `false` when pushing preliminary measurements (daily pacing pushes, intra-period progress) that are still settling. Receivers MUST NOT invoice on `final: false` records, and MUST NOT invoice on records where `final` is absent for buys whose `measurement_terms.billing_measurement` names this reporter as authoritative — request a final record first. Receivers MAY invoice on absent for buys with no `measurement_terms.billing_measurement` (3.0-style usage where the receiver treats reports as authoritative on receipt) and for non-media-buy variants (signals, governance, creative, brand — domains with no provisional state concept). When the same `(account, media_buy_id, reporting_period)` is later reported with `final: true`, that record supersedes any prior records for the period." - ), - ] = None - finalized_at: Annotated[ - AwareDatetime | None, - Field( - description="ISO 8601 timestamp at which the reporter considered these numbers final. Present only when `final: true`. Anchors any deadline declared in the buy's `measurement_terms.billing_measurement.finalization_deadline_hours`." - ), - ] = None - measurement_window: Annotated[ - str | None, - Field( - description="Which measurement window this record represents, referencing a window_id from the product's reporting_capabilities.measurement_windows or from `measurement_terms.billing_measurement.measurement_window`. Examples: 'c7' for broadcast TV, 'post_sivt' for digital post-IVT, 'downloads_30d' for podcast. When absent, the record is not windowed (standard digital reporting). When the buy's `measurement_terms.billing_measurement.measurement_window` is set, reporters SHOULD include `measurement_window` so the receiver can reconcile against the correct stage.", - examples=['live', 'c3', 'c7', 'post_ivt', 'post_sivt', 'downloads_30d'], - max_length=50, - ), - ] = None - - -class ReportUsageRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for this request. If a request with the same key has already been accepted, the server returns the original response without re-processing. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request. Prevents duplicate billing on retries.' - ), - ] - reporting_period: Annotated[ - datetime_range.DatetimeRange, - Field( - description='The time range covered by this usage report. Applies to all records in the request.' - ), - ] - usage: Annotated[ - list[UsageItem], - Field( - description='One or more usage records. Each record is self-contained: it carries its own account, allowing a single request to span multiple accounts.', - min_length=1, - ), - ] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/account/report_usage_response.py b/src/adcp/types/generated_poc/account/report_usage_response.py deleted file mode 100644 index 3655a7baf..000000000 --- a/src/adcp/types/generated_poc/account/report_usage_response.py +++ /dev/null @@ -1,37 +0,0 @@ -# generated by datamodel-codegen: -# filename: account/report_usage_response.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import SchemaInt -from pydantic import ConfigDict, Field, StrictBool - -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope - - -class ReportUsageResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - accepted: Annotated[ - SchemaInt, Field(description='Number of usage records successfully stored.', ge=0) - ] - errors: Annotated[ - list[error.Error] | None, - Field( - description="Validation errors for individual records. The field property identifies which record failed (e.g., 'usage[1].pricing_option_id')." - ), - ] = None - sandbox: Annotated[ - StrictBool | None, - Field(description='When true, the account is a sandbox account and no billing occurred.'), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/account/sync_accounts_request.py b/src/adcp/types/generated_poc/account/sync_accounts_request.py deleted file mode 100644 index de8d673ee..000000000 --- a/src/adcp/types/generated_poc/account/sync_accounts_request.py +++ /dev/null @@ -1,280 +0,0 @@ -# generated by datamodel-codegen: -# filename: account/sync_accounts_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictBool - -from ..core import account_ref, brand_ref, business_entity -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core import notification_config -from ..core import operator_identity as operator_identity_1 -from ..core import operator_unit as operator_unit_1 -from ..core import push_notification_config as push_notification_config_1 -from ..core import reporting_delivery_config -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import billing_party, cloud_storage_protocol -from ..enums import payment_terms as payment_terms_1 - - -class Accounts(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - account: Annotated[ - account_ref.AccountReference | None, - Field( - description='Settings-update key. When present, this entry targets an existing account by `account_id` (seller-owned account namespace) or natural key (buyer-declared account settings-update against a previously-provisioned account). Mutually exclusive with the flat `brand` + `operator` + `billing` provisioning trio. When `account` is present, the seller MUST NOT create a new account — entries that would otherwise trigger provisioning are rejected with `UNSUPPORTED_PROVISIONING`.' - ), - ] = None - revision: Annotated[ - SchemaInt | None, - Field( - description='Expected current account revision for optimistic concurrency in settings-update mode. Required whenever operator_identity is present; optional for existing non-identity settings updates. The seller MUST compare it atomically with the write, reject a mismatch with CONFLICT, and leave the account unchanged. Obtain it from list_accounts or the most recent sync_accounts result. Reads, dry runs, validation failures, and exact idempotency replays do not increment revision; every persisted settings or identity-change state transition does. MUST be absent in provisioning mode.', - ge=1, - ), - ] = None - operator_identity: Annotated[ - operator_identity_1.OperatorIdentity | None, - Field( - description='Complete desired operator identity for settings-update mode. Omit this field to leave operator identity unchanged. When present, omission of operator_unit within the object removes the existing unit. Changing only operator_unit_1.name updates display metadata; changing operator_unit_1.id or adding/removing a unit rekeys the same account within the current operator. Changing operator requests an inter-entity handoff and MUST enter pending_approval until the seller verifies the current account authority, verified brand authorization, destination-operator acceptance, and any operator-scoped billing and grant transition. The seller MUST preserve account_id and account-scoped historical resources, MUST reject collisions without merging, and MUST apply no identity change if continuity cannot be preserved. MUST be accompanied by revision and MUST be absent in provisioning mode.' - ), - ] = None - destination_billing_entity: Annotated[ - business_entity.BusinessEntity | None, - Field( - description="Complete staged billing identity for the requested destination operator during an operator-domain handoff on an account whose billing party is operator. This value is write-only while approval is pending and MUST NOT replace or be echoed as the account's canonical billing_entity until the handoff applies atomically. Required by the protocol when an operator-billed account changes operator; otherwise MUST be absent. Requires operator_identity and revision and MUST be absent in provisioning mode." - ), - ] = None - brand: Annotated[ - brand_ref.BrandReference, - Field( - description='Brand reference identifying the advertiser. Required for **provisioning mode**; MUST be absent in settings-update mode. Only the BrandKey projection — `domain`, `brand_id`, and the canonicalized `countries[]` set — participates in account identity. Mutable or per-call BrandRef fields such as `industries`, `data_subject_contestation`, and `brand_kit_override` MUST NOT affect lookup, idempotency, or account creation. New 3.2 producers SHOULD send only the BrandKey fields; the broader BrandRef remains accepted on this existing 3.x task for compatibility.' - ), - ] - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf (e.g., 'pinnacle-media.com'). When the brand operates directly, this is the brand's domain. Verified against the brand's authorized_operators in brand.json. Required for **provisioning mode**; MUST be absent in settings-update mode.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - operator_unit: Annotated[ - operator_unit_1.OperatorUnit | None, - Field( - description='Optional operator-owned business unit, agency seat, or platform account for provisioning mode. operator_unit_1.id participates in the natural key; name is mapping/display metadata. MUST be absent in settings-update mode.' - ), - ] = None - currency: Annotated[ - str | None, - Field( - description='Optional immutable ISO 4217 transaction currency for a currency-bound advertiser object. Consult `account.supported_account_currency_modes` before provisioning. When supplied, it participates in the natural key and all media buys on the account use it. Omit for per-media-buy currency selection. MUST be absent in settings-update mode.', - pattern='^[A-Z]{3}$', - ), - ] = None - timezone: Annotated[ - str | None, - Field( - description='Immutable operational timezone selected for an account_fixed advertiser object. Required in provisioning mode when get_adcp_capabilities.account.timezone declares account_selection: buyer_selected, and the value MUST be one of supported_timezones. Omit for seller_fixed or seller_assigned modes. When supplied, it participates in the natural key. MUST be absent in settings-update mode.', - min_length=1, - ), - ] = None - billing: Annotated[ - billing_party.BillingParty, - Field( - description='Who the seller invoices for this buyer–storefront account relationship. Required for **provisioning mode**; MUST be absent in settings-update mode (the invoiced party is fixed at provisioning time and cannot be changed via settings-update). This field does not select a payment rail, clearing intermediary, or per-media-buy settlement route.' - ), - ] - billing_entity: Annotated[ - business_entity.BusinessEntity | None, - Field( - description='Business entity details for the party responsible for payment. The agent provides this so the seller has the legal name, tax IDs, address, and bank details needed for formal B2B invoicing. Permitted in both modes — sellers MAY accept refinements in settings-update mode (e.g., updated bank details).' - ), - ] = None - payment_terms: Annotated[ - payment_terms_1.PaymentTerms | None, - Field( - description='Payment terms for this account. The seller must either accept these terms or reject the account — terms are never silently remapped. When omitted, the seller applies its default terms. Permitted in both modes.' - ), - ] = None - sandbox: Annotated[ - StrictBool | None, - Field( - description='When true, provision this as a sandbox account with no real platform calls or billing. Only applicable to buyer-declared accounts (require_operator_auth: false) in provisioning mode. For account-id namespaces, sandbox accounts are pre-existing test accounts discovered via list_accounts or supplied out-of-band.' - ), - ] = None - preferred_reporting_protocol: Annotated[ - cloud_storage_protocol.CloudStorageProtocol | None, - Field( - description="Buyer's preferred cloud storage protocol for offline reporting delivery. The seller provisions the account's reporting_bucket using this protocol if supported. When omitted, the seller chooses from its supported offline_delivery_protocols. Only meaningful when the seller's reporting_delivery_methods includes 'offline'." - ), - ] = None - reporting_delivery_configs: Annotated[ - list[reporting_delivery_config.ReportingDeliveryConfiguration] | None, - Field( - description="Caller-owned desired state for durable reporting delivery on this account. Declarative replacement is scoped to (authenticated caller, resolved account): omission leaves that caller's set unchanged; [] deactivates that caller's set and starts grant revocation; another caller's entries MUST NOT be read, replaced, or deleted. Entries are keyed by immutable (delivery_config_id, delivery_config_version); duplicate tuples MUST reject the entire account entry, and reusing a tuple with changed content MUST be rejected. Each generation binds the exact report_definition_id advertised by its offering. destination.mode provision asks the seller to verify caller disclosure authority and destination/recipient control from non-secret provider coordinates; destination.mode existing reuses a caller-scoped immutable destination-generation reference, including one registered through sync_agent_configuration. The account configuration independently authorizes disclosure for this feed and scope, so possession of a reusable reference is never account authority. Unknown, unauthorized, and cross-caller refs MUST be indistinguishable. Credentials never transit AdCP, including nested extension fields. Permitted in both provisioning and settings-update modes. Sellers accepting this field MUST advertise media_buy.reporting_delivery in experimental_features and echo resolved secret-free state on sync_accounts and list_accounts.", - max_length=16, - ), - ] = None - notification_configs: Annotated[ - list[notification_config.NotificationConfig] | None, - Field( - description='Account-level webhook subscriptions for notifications whose lifecycle outlives any single media buy (`creative.status_changed`, optional `creative.assignment_changed`, `indicators.changed`, `creative.purged`, `account.status_changed`, wholesale feed change payloads, and future account-anchored resource events after those event types are added to `notification-config.json`). Indicator and assignment registrations are prospective: activation does not replay current conditions, so buyers establish a complete baseline through `get_media_buys` by enumerating known IDs or requesting every status and exhausting pagination, without an indicator filter. Durable account lifecycle transitions such as later `payment_required`, `suspended`, `closed`, or recovery to `active` use `account.status_changed` on this surface; the one-shot `sync_accounts.push_notification_config` channel remains scoped to the async result of the original provisioning task. Declarative replace semantics: when this field is present, the buyer sends the full desired array and the seller replaces the account\'s current set with that array, keyed by account-scoped `subscriber_id`. Omit this field to leave existing subscribers unchanged; send `[]` to remove all subscribers. Re-sending an existing `subscriber_id` for the account replaces that subscriber\'s config rather than creating a duplicate; persisted entries whose `subscriber_id` does not appear in the sent array are removed, so the seller MUST NOT merge the new array with persisted state. Paused entries (`active: false`) use the same replacement semantics; a buyer that wants to preserve a paused subscriber MUST re-include it with `active: false`. Duplicate `subscriber_id` values within one submitted array are rejected. Permitted in both provisioning and settings-update modes. Each entry registers a URL, the event types the subscriber wants, and optional legacy auth — see [`notification-config.json`](/schemas/core/notification-config.json). The seller MUST echo applied state on the response and on `list_accounts` reads, with `authentication.credentials` omitted (write-only). Sellers MUST reject entries whose `event_types` include any type whose contract anchors at a media buy or below (today: `scheduled`, `final`, `delayed`, `adjusted`, `window_update`, `impairment`) or at the agent (today: `capabilities.changed`) as per-account validation failures with `INVALID_REQUEST` or `VALIDATION_ERROR` and `error.field` pointing at the invalid `event_types` entry — those events do not belong on this surface. Wholesale feed webhook registrations carry the actual change payload in `/schemas/core/wholesale-feed-webhook.json`; canonical product subscribers repair through `list_products(if_feed_version)`, legacy product subscribers through `get_products(if_wholesale_feed_version)`, and signal subscribers through `get_signals(if_wholesale_feed_version)`. Account status change registrations carry the invalidation payload in `/schemas/core/account-status-changed-webhook.json`; receivers use `list_accounts` to repair or reconcile. This is distinct from sync_catalogs, which manages buyer-provided campaign input feeds on a seller account.\n\nActivation proof: before activating a new or changed active subscriber, the seller MUST validate the URL, complete the account-level webhook proof-of-control challenge, and only then persist or expose the subscriber as `active: true`. For `account.status_changed`, sellers MUST assign `account_id` before completing proof so subsequent status transitions can identify the account and be repaired through `list_accounts`, even when external approval remains pending. A valid existing proof for the same `(account_id, subscriber_id, normalized url, authentication mode/credential binding, normalized event_types)` tuple MAY be reused; changing any element of that tuple requires fresh proof. The challenge POST itself MUST be signed with the seller\'s RFC 9421 webhook profile key and MUST include seller_agent_url, delivery_auth, and event_types so the receiver can verify the pending registration before echoing the challenge. New signers use `adcp_use: "request-signing"`; deprecated `webhook-signing` keys remain accepted during the compatibility window. Entries sent with `active: false` may skip only the outbound proof challenge while inactive; sellers MUST still enforce URL parsing, HTTPS, hostname normalization, and reserved-range rejection at write time, and those entries MUST NOT receive fires until reactivated. If proof fails or times out, the seller rejects the account entry with `action: "failed"`, leaves the prior notification_configs[] set unchanged, and reports `VALIDATION_ERROR` (or `INVALID_REQUEST` for malformed URLs) at the failing `notification_configs[j].url` field.\n\n**Cap rationale:** `maxItems: 16` is a practical fan-out cap (governance + buyer ingestion + audit bus + dx team + a few partner hooks). The cap exists to prevent unbounded subscriber arrays in storage and to bound the seller\'s per-event fan-out work. Sellers that hit the cap with legitimate subscribers should surface this on the protocol roadmap rather than work around it.', - max_length=16, - ), - ] = None - - -class Accounts1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - account: Annotated[ - account_ref.AccountReference, - Field( - description='Settings-update key. When present, this entry targets an existing account by `account_id` (seller-owned account namespace) or natural key (buyer-declared account settings-update against a previously-provisioned account). Mutually exclusive with the flat `brand` + `operator` + `billing` provisioning trio. When `account` is present, the seller MUST NOT create a new account — entries that would otherwise trigger provisioning are rejected with `UNSUPPORTED_PROVISIONING`.' - ), - ] - revision: Annotated[ - SchemaInt | None, - Field( - description='Expected current account revision for optimistic concurrency in settings-update mode. Required whenever operator_identity is present; optional for existing non-identity settings updates. The seller MUST compare it atomically with the write, reject a mismatch with CONFLICT, and leave the account unchanged. Obtain it from list_accounts or the most recent sync_accounts result. Reads, dry runs, validation failures, and exact idempotency replays do not increment revision; every persisted settings or identity-change state transition does. MUST be absent in provisioning mode.', - ge=1, - ), - ] = None - operator_identity: Annotated[ - operator_identity_1.OperatorIdentity | None, - Field( - description='Complete desired operator identity for settings-update mode. Omit this field to leave operator identity unchanged. When present, omission of operator_unit within the object removes the existing unit. Changing only operator_unit_1.name updates display metadata; changing operator_unit_1.id or adding/removing a unit rekeys the same account within the current operator. Changing operator requests an inter-entity handoff and MUST enter pending_approval until the seller verifies the current account authority, verified brand authorization, destination-operator acceptance, and any operator-scoped billing and grant transition. The seller MUST preserve account_id and account-scoped historical resources, MUST reject collisions without merging, and MUST apply no identity change if continuity cannot be preserved. MUST be accompanied by revision and MUST be absent in provisioning mode.' - ), - ] = None - destination_billing_entity: Annotated[ - business_entity.BusinessEntity | None, - Field( - description="Complete staged billing identity for the requested destination operator during an operator-domain handoff on an account whose billing party is operator. This value is write-only while approval is pending and MUST NOT replace or be echoed as the account's canonical billing_entity until the handoff applies atomically. Required by the protocol when an operator-billed account changes operator; otherwise MUST be absent. Requires operator_identity and revision and MUST be absent in provisioning mode." - ), - ] = None - brand: Annotated[ - brand_ref.BrandReference | None, - Field( - description='Brand reference identifying the advertiser. Required for **provisioning mode**; MUST be absent in settings-update mode. Only the BrandKey projection — `domain`, `brand_id`, and the canonicalized `countries[]` set — participates in account identity. Mutable or per-call BrandRef fields such as `industries`, `data_subject_contestation`, and `brand_kit_override` MUST NOT affect lookup, idempotency, or account creation. New 3.2 producers SHOULD send only the BrandKey fields; the broader BrandRef remains accepted on this existing 3.x task for compatibility.' - ), - ] = None - operator: Annotated[ - str | None, - Field( - description="Domain of the entity operating on the brand's behalf (e.g., 'pinnacle-media.com'). When the brand operates directly, this is the brand's domain. Verified against the brand's authorized_operators in brand.json. Required for **provisioning mode**; MUST be absent in settings-update mode.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - operator_unit: Annotated[ - operator_unit_1.OperatorUnit | None, - Field( - description='Optional operator-owned business unit, agency seat, or platform account for provisioning mode. operator_unit_1.id participates in the natural key; name is mapping/display metadata. MUST be absent in settings-update mode.' - ), - ] = None - currency: Annotated[ - str | None, - Field( - description='Optional immutable ISO 4217 transaction currency for a currency-bound advertiser object. Consult `account.supported_account_currency_modes` before provisioning. When supplied, it participates in the natural key and all media buys on the account use it. Omit for per-media-buy currency selection. MUST be absent in settings-update mode.', - pattern='^[A-Z]{3}$', - ), - ] = None - timezone: Annotated[ - str | None, - Field( - description='Immutable operational timezone selected for an account_fixed advertiser object. Required in provisioning mode when get_adcp_capabilities.account.timezone declares account_selection: buyer_selected, and the value MUST be one of supported_timezones. Omit for seller_fixed or seller_assigned modes. When supplied, it participates in the natural key. MUST be absent in settings-update mode.', - min_length=1, - ), - ] = None - billing: Annotated[ - billing_party.BillingParty | None, - Field( - description='Who the seller invoices for this buyer–storefront account relationship. Required for **provisioning mode**; MUST be absent in settings-update mode (the invoiced party is fixed at provisioning time and cannot be changed via settings-update). This field does not select a payment rail, clearing intermediary, or per-media-buy settlement route.' - ), - ] = None - billing_entity: Annotated[ - business_entity.BusinessEntity | None, - Field( - description='Business entity details for the party responsible for payment. The agent provides this so the seller has the legal name, tax IDs, address, and bank details needed for formal B2B invoicing. Permitted in both modes — sellers MAY accept refinements in settings-update mode (e.g., updated bank details).' - ), - ] = None - payment_terms: Annotated[ - payment_terms_1.PaymentTerms | None, - Field( - description='Payment terms for this account. The seller must either accept these terms or reject the account — terms are never silently remapped. When omitted, the seller applies its default terms. Permitted in both modes.' - ), - ] = None - sandbox: Annotated[ - StrictBool | None, - Field( - description='When true, provision this as a sandbox account with no real platform calls or billing. Only applicable to buyer-declared accounts (require_operator_auth: false) in provisioning mode. For account-id namespaces, sandbox accounts are pre-existing test accounts discovered via list_accounts or supplied out-of-band.' - ), - ] = None - preferred_reporting_protocol: Annotated[ - cloud_storage_protocol.CloudStorageProtocol | None, - Field( - description="Buyer's preferred cloud storage protocol for offline reporting delivery. The seller provisions the account's reporting_bucket using this protocol if supported. When omitted, the seller chooses from its supported offline_delivery_protocols. Only meaningful when the seller's reporting_delivery_methods includes 'offline'." - ), - ] = None - reporting_delivery_configs: Annotated[ - list[reporting_delivery_config.ReportingDeliveryConfiguration] | None, - Field( - description="Caller-owned desired state for durable reporting delivery on this account. Declarative replacement is scoped to (authenticated caller, resolved account): omission leaves that caller's set unchanged; [] deactivates that caller's set and starts grant revocation; another caller's entries MUST NOT be read, replaced, or deleted. Entries are keyed by immutable (delivery_config_id, delivery_config_version); duplicate tuples MUST reject the entire account entry, and reusing a tuple with changed content MUST be rejected. Each generation binds the exact report_definition_id advertised by its offering. destination.mode provision asks the seller to verify caller disclosure authority and destination/recipient control from non-secret provider coordinates; destination.mode existing reuses a caller-scoped immutable destination-generation reference, including one registered through sync_agent_configuration. The account configuration independently authorizes disclosure for this feed and scope, so possession of a reusable reference is never account authority. Unknown, unauthorized, and cross-caller refs MUST be indistinguishable. Credentials never transit AdCP, including nested extension fields. Permitted in both provisioning and settings-update modes. Sellers accepting this field MUST advertise media_buy.reporting_delivery in experimental_features and echo resolved secret-free state on sync_accounts and list_accounts.", - max_length=16, - ), - ] = None - notification_configs: Annotated[ - list[notification_config.NotificationConfig] | None, - Field( - description='Account-level webhook subscriptions for notifications whose lifecycle outlives any single media buy (`creative.status_changed`, optional `creative.assignment_changed`, `indicators.changed`, `creative.purged`, `account.status_changed`, wholesale feed change payloads, and future account-anchored resource events after those event types are added to `notification-config.json`). Indicator and assignment registrations are prospective: activation does not replay current conditions, so buyers establish a complete baseline through `get_media_buys` by enumerating known IDs or requesting every status and exhausting pagination, without an indicator filter. Durable account lifecycle transitions such as later `payment_required`, `suspended`, `closed`, or recovery to `active` use `account.status_changed` on this surface; the one-shot `sync_accounts.push_notification_config` channel remains scoped to the async result of the original provisioning task. Declarative replace semantics: when this field is present, the buyer sends the full desired array and the seller replaces the account\'s current set with that array, keyed by account-scoped `subscriber_id`. Omit this field to leave existing subscribers unchanged; send `[]` to remove all subscribers. Re-sending an existing `subscriber_id` for the account replaces that subscriber\'s config rather than creating a duplicate; persisted entries whose `subscriber_id` does not appear in the sent array are removed, so the seller MUST NOT merge the new array with persisted state. Paused entries (`active: false`) use the same replacement semantics; a buyer that wants to preserve a paused subscriber MUST re-include it with `active: false`. Duplicate `subscriber_id` values within one submitted array are rejected. Permitted in both provisioning and settings-update modes. Each entry registers a URL, the event types the subscriber wants, and optional legacy auth — see [`notification-config.json`](/schemas/core/notification-config.json). The seller MUST echo applied state on the response and on `list_accounts` reads, with `authentication.credentials` omitted (write-only). Sellers MUST reject entries whose `event_types` include any type whose contract anchors at a media buy or below (today: `scheduled`, `final`, `delayed`, `adjusted`, `window_update`, `impairment`) or at the agent (today: `capabilities.changed`) as per-account validation failures with `INVALID_REQUEST` or `VALIDATION_ERROR` and `error.field` pointing at the invalid `event_types` entry — those events do not belong on this surface. Wholesale feed webhook registrations carry the actual change payload in `/schemas/core/wholesale-feed-webhook.json`; canonical product subscribers repair through `list_products(if_feed_version)`, legacy product subscribers through `get_products(if_wholesale_feed_version)`, and signal subscribers through `get_signals(if_wholesale_feed_version)`. Account status change registrations carry the invalidation payload in `/schemas/core/account-status-changed-webhook.json`; receivers use `list_accounts` to repair or reconcile. This is distinct from sync_catalogs, which manages buyer-provided campaign input feeds on a seller account.\n\nActivation proof: before activating a new or changed active subscriber, the seller MUST validate the URL, complete the account-level webhook proof-of-control challenge, and only then persist or expose the subscriber as `active: true`. For `account.status_changed`, sellers MUST assign `account_id` before completing proof so subsequent status transitions can identify the account and be repaired through `list_accounts`, even when external approval remains pending. A valid existing proof for the same `(account_id, subscriber_id, normalized url, authentication mode/credential binding, normalized event_types)` tuple MAY be reused; changing any element of that tuple requires fresh proof. The challenge POST itself MUST be signed with the seller\'s RFC 9421 webhook profile key and MUST include seller_agent_url, delivery_auth, and event_types so the receiver can verify the pending registration before echoing the challenge. New signers use `adcp_use: "request-signing"`; deprecated `webhook-signing` keys remain accepted during the compatibility window. Entries sent with `active: false` may skip only the outbound proof challenge while inactive; sellers MUST still enforce URL parsing, HTTPS, hostname normalization, and reserved-range rejection at write time, and those entries MUST NOT receive fires until reactivated. If proof fails or times out, the seller rejects the account entry with `action: "failed"`, leaves the prior notification_configs[] set unchanged, and reports `VALIDATION_ERROR` (or `INVALID_REQUEST` for malformed URLs) at the failing `notification_configs[j].url` field.\n\n**Cap rationale:** `maxItems: 16` is a practical fan-out cap (governance + buyer ingestion + audit bus + dx team + a few partner hooks). The cap exists to prevent unbounded subscriber arrays in storage and to bound the seller\'s per-event fan-out work. Sellers that hit the cap with legitimate subscribers should surface this on the protocol roadmap rather than work around it.', - max_length=16, - ), - ] = None - - -class SyncAccountsRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for at-most-once execution. Natural per-account upsert keys handle resource-level dedup, but the envelope triggers onboarding webhooks, billing setup, and audit events — this key prevents those side effects from firing twice on retry. MUST be unique per (seller, request) pair. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - accounts: Annotated[ - list[Accounts | Accounts1], - Field( - description='Per-account sync entries. Each entry uses one of two key shapes: the `account` field (AccountRef) for settings-update mode, or the flat `brand` + `operator` + `billing` trio for provisioning mode. An operator_identity settings update MUST carry the latest account revision.', - max_length=1000, - ), - ] - delete_missing: Annotated[ - StrictBool | None, - Field( - description='When true, accounts previously synced by this agent but not included in this request will be deactivated. Scoped to the authenticated agent — does not affect accounts managed by other agents. Use with caution.' - ), - ] = False - dry_run: Annotated[ - StrictBool | None, - Field( - description='When true, preview what would change without applying. Returns what would be created/updated/deactivated.' - ), - ] = False - push_notification_config: Annotated[ - push_notification_config_1.PushNotificationConfig | None, - Field( - description='Webhook for async notifications when account status changes (e.g., pending_approval transitions to active).' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/account/sync_accounts_response.py b/src/adcp/types/generated_poc/account/sync_accounts_response.py deleted file mode 100644 index 9801bbb61..000000000 --- a/src/adcp/types/generated_poc/account/sync_accounts_response.py +++ /dev/null @@ -1,99 +0,0 @@ -# generated by datamodel-codegen: -# filename: account/sync_accounts_response.json -# timestamp: preserved-by-post-generate-fixes - -from __future__ import annotations - -from typing import Annotated, Any, Literal, TypeAlias - -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StringConstraints - -from ..core.version_envelope import AdcpVersionEnvelope -from ..core import account_authorization as account_authorization_1 -from ..core import account_identity_change as account_identity_change_1 -from ..core import account_identity_change_preview as account_identity_change_preview_1 -from ..core import account_ref as account_ref_1 -from ..core import brand_ref as brand_ref_1 -from ..core import business_entity as business_entity_1 -from ..core import context as context_1 -from ..core import error as error_1 -from ..core import ext as ext_1 -from ..core import notification_config as notification_config_1 -from ..core import operator_unit as operator_unit_1 -from ..core import reporting_delivery_config_state as reporting_delivery_config_state_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..enums import account_scope as account_scope_1 -from ..enums import billing_party as billing_party_1 -from ..enums import payment_terms as payment_terms_1 - - -class Setup(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - url: AnyUrl | None = None - message: str - expires_at: AwareDatetime | None = None - - -class CreditLimit(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - amount: Annotated[float, Field(ge=0)] - currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] - - -class Account(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - account_id: str | None = None - account: account_ref_1.AccountReference | None = None - brand: brand_ref_1.BrandReference | None = None - operator: str | None = None - operator_unit: operator_unit_1.OperatorUnit | None = None - revision: Annotated[int, Field(ge=1)] | None = None - identity_change: account_identity_change_1.AccountIdentityChange | None = None - identity_change_preview: account_identity_change_preview_1.AccountIdentityChangePreview | None = None - currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] | None = None - timezone: Annotated[str, StringConstraints(min_length=1)] | None = None - name: str | None = None - action: Literal['created', 'updated', 'unchanged', 'failed'] - status: Literal['active', 'pending_approval', 'rejected', 'payment_required', 'suspended', 'closed'] | None = None - billing: billing_party_1.BillingParty | None = None - billing_entity: business_entity_1.BusinessEntity | None = None - destination_billing_entity: Any | None = None - account_scope: account_scope_1.AccountScope | None = None - setup: Setup | None = None - rate_card: str | None = None - payment_terms: payment_terms_1.PaymentTerms | None = None - credit_limit: CreditLimit | None = None - errors: Annotated[list[error_1.Error], Field(min_length=1)] | None = None - warnings: list[str] | None = None - sandbox: bool | None = None - notification_configs: Annotated[list[notification_config_1.NotificationConfig], Field(max_length=16)] | None = None - reporting_delivery_configs: Annotated[list[reporting_delivery_config_state_1.ReportingDeliveryConfigurationState], Field(max_length=16)] | None = None - authorization: account_authorization_1.AccountAuthorization | None = None - - -class SyncAccountsResponse1(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - dry_run: bool | None = None - accounts: list[Account] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class SyncAccountsResponse2(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - errors: Annotated[list[error_1.Error], Field(min_length=1)] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -SyncAccountsResponse: TypeAlias = SyncAccountsResponse1 | SyncAccountsResponse2 - - -__all__ = [ - 'SyncAccountsResponse', - 'SyncAccountsResponse1', - 'SyncAccountsResponse2', - 'Account', - 'CreditLimit', - 'Setup', -] diff --git a/src/adcp/types/generated_poc/account/sync_governance_request.py b/src/adcp/types/generated_poc/account/sync_governance_request.py deleted file mode 100644 index 0d12a1069..000000000 --- a/src/adcp/types/generated_poc/account/sync_governance_request.py +++ /dev/null @@ -1,88 +0,0 @@ -# generated by datamodel-codegen: -# filename: account/sync_governance_request.json -# timestamp: 2026-10-02T23:10:32+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - -from ..core import account_ref -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.version_envelope import AdcpVersionEnvelope - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Literal['Bearer']], - Field( - description='The seller authenticates outbound check_governance calls with the registered Bearer credential. Other shared webhook authentication schemes are not valid for this agent-to-agent call.', - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, Field(description='Authentication credential (e.g., Bearer token).', min_length=32) - ] - - -class GovernanceAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: Annotated[AnyUrl, Field(description='Governance agent endpoint URL. Must use HTTPS.')] - authentication: Annotated[ - Authentication, - Field(description='Authentication the seller presents when calling this governance agent.'), - ] - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account: Annotated[ - account_ref.AccountReference, - Field( - description='Account to sync governance agents for. Use account_id for account-id namespaces or brand + operator for buyer-declared accounts.' - ), - ] - governance_agents: Annotated[ - list[GovernanceAgent], - Field( - description="Governance agent endpoint for this account. Exactly one entry — the single agent that owns the account's full governance lifecycle. The seller calls this agent via check_governance during media buy lifecycle events. The array shape is preserved for wire compatibility with 3.0 senders; `maxItems: 1` is load-bearing and mirrors the singular `governance_context` on the protocol envelope.", - max_length=1, - min_length=1, - ), - ] - - -class SyncGovernanceRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for at-most-once execution. `account` gives resource-level dedup, but governance changes emit audit events and can trigger reapproval flows — this key prevents those side effects from firing twice on retry. MUST be unique per (seller, request) pair. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - accounts: Annotated[ - list[Account], - Field( - description='Per-account governance agent configuration. Each entry pairs an account reference with the governance agents for that account.', - max_length=100, - min_length=1, - ), - ] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/account/sync_governance_response.py b/src/adcp/types/generated_poc/account/sync_governance_response.py deleted file mode 100644 index 8ad40d311..000000000 --- a/src/adcp/types/generated_poc/account/sync_governance_response.py +++ /dev/null @@ -1,96 +0,0 @@ -# generated by datamodel-codegen: -# filename: account/sync_governance_response.json -# timestamp: 2026-10-02T23:10:32+00:00 - -from __future__ import annotations - -from adcp.types.response_dispatch import ResponseArmDispatchMixin - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - -from ..core import account_ref -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope - - -class Status(StrEnum): - synced = 'synced' - failed = 'failed' - - -class GovernanceAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: Annotated[AnyUrl, Field(description='Governance agent endpoint URL.')] - - -class SyncGovernanceResponse(ResponseArmDispatchMixin, AdcpVersionEnvelope, ProtocolEnvelope): - """Constructible compatibility base for generated response arms.""" - - @classmethod - def _response_arm_models(cls) -> tuple[type[SyncGovernanceResponse], ...]: - return ( - SyncGovernanceResponse1, - SyncGovernanceResponse2, - ) - - -class SyncGovernanceResponse2(SyncGovernanceResponse): - model_config = ConfigDict( - extra='allow', - ) - errors: Annotated[ - list[error.Error], - Field( - description='Operation-level errors (e.g., authentication failure, service unavailable)', - min_length=1, - ), - ] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - account: Annotated[ - account_ref.AccountReference, Field(description='Account reference, echoed from request') - ] - status: Annotated[ - Status, - Field( - description='Sync result. synced: governance agents persisted. failed: could not complete (see errors).' - ), - ] - governance_agents: Annotated[ - list[GovernanceAgent] | None, - Field( - description='Governance agent now synced on this account. Reflects the persisted state after sync. Exactly one entry; the array shape mirrors the request schema and the one-agent-per-account invariant. See sync_governance request schema.', - max_length=1, - min_length=1, - ), - ] = None - errors: Annotated[ - list[error.Error] | None, - Field( - description="Per-account errors (only present when status is 'failed')", min_length=1 - ), - ] = None - - -class SyncGovernanceResponse1(SyncGovernanceResponse): - model_config = ConfigDict( - extra='allow', - ) - accounts: Annotated[list[Account], Field(description='Per-account sync results')] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/brand/__init__.py b/src/adcp/types/generated_poc/brand/__init__.py deleted file mode 100644 index 23c7f178d..000000000 --- a/src/adcp/types/generated_poc/brand/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# generated by datamodel-codegen: -# filename: .schema_temp -# timestamp: 2026-08-17T23:02:13+00:00 diff --git a/src/adcp/types/generated_poc/brand/acquire_rights_request.py b/src/adcp/types/generated_poc/brand/acquire_rights_request.py deleted file mode 100644 index 813ca0e48..000000000 --- a/src/adcp/types/generated_poc/brand/acquire_rights_request.py +++ /dev/null @@ -1,127 +0,0 @@ -# generated by datamodel-codegen: -# filename: brand/acquire_rights_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from datetime import date -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field - -from ..core import account_ref, brand_ref, canonical_format_kind -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core import format_id, format_option_ref -from ..core import push_notification_config as push_notification_config_1 -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import right_use - - -class Country(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{2}$'} - - -class Campaign(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - description: Annotated[str, Field(description='Description of how the rights will be used')] - uses: Annotated[ - list[right_use.RightUse], - Field(description='Specific rights uses for this campaign', min_length=1), - ] - countries: Annotated[ - list[Country] | None, - Field(description='Countries where the campaign will run (ISO 3166-1 alpha-2)'), - ] = None - format_ids: Annotated[ - list[format_id.FormatReferenceStructuredObject] | None, - Field( - deprecated=True, - description='Deprecated in AdCP 3.2; removed in AdCP 4.0. Legacy named formats that will be produced. Use `format_kinds` and optional `format_option_refs`.', - ), - ] = None - format_kinds: Annotated[ - list[canonical_format_kind.CanonicalFormatKind] | None, - Field( - description='Canonical creative format kinds that will be produced under the rights grant.', - min_length=1, - ), - ] = None - format_option_refs: Annotated[ - list[format_option_ref.FormatOptionReference] | None, - Field( - description='Exact canonical publisher or product format options covered by the planned rights use, when kind alone is insufficient.', - min_length=1, - ), - ] = None - estimated_impressions: Annotated[ - SchemaInt | None, - Field( - description="Estimated total impressions for the campaign. Required when the brand agent will project commitment against a governance plan AND the selected pricing_option has model: 'cpm' — projection equals (pricing_option.price / 1000) × estimated_impressions evaluated in pricing_option.currency. The brand agent will project commitment whenever the request is governance-aware via either (a) an intent-phase governance_context token on the protocol envelope, or (b) `account` resolving to an account that has a governance agent previously bound via sync_governance. Brand agents MUST reject with INVALID_REQUEST (field: campaign.estimated_impressions) in either path when CPM-priced rights are requested and this field is omitted or zero; implementer-chosen defaults are non-conformant. See the acquire_rights task reference for the full validation contract including currency-mismatch handling.", - ge=0, - ), - ] = None - start_date: Annotated[date | None, Field(description='Campaign start date (ISO 8601)')] = None - end_date: Annotated[ - date | None, - Field( - description='Campaign end date (ISO 8601). Brand agents MUST reject with INVALID_REQUEST (field: campaign.end_date) when end_date is in the past at the time of the request — acquiring rights for an elapsed window produces a zero-duration grant and is almost always a buyer-side bug.' - ), - ] = None - - -class AcquireRightsRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - governance_context: Annotated[ - str | None, - Field( - description='Opaque intent authorization for this rights commitment. Required when governance applies to the resolved account.', - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - rights_id: Annotated[ - str, Field(description='Rights offering identifier from get_rights response') - ] - pricing_option_id: Annotated[ - str, Field(description='Selected pricing option from the rights offering') - ] - buyer: Annotated[brand_ref.BrandReference, Field(description="The buyer's brand identity")] - account: Annotated[ - account_ref.AccountReference | None, - Field( - description='Account context for this acquisition. Used by the brand agent to resolve any governance agent previously bound for this brand+operator pair via sync_governance. When both an inline governance_context token and a bound governance agent are present, the token is verified against that resolved relationship. An agent advertising adcp.governance_enforcement for acquire_rights MUST resolve an account and returns ACCOUNT_REQUIRED when neither the request nor an existing resource supplies one; it MUST NOT infer that a missing token means ungoverned. Legacy non-claiming agents may continue to treat omission of both fields as ungoverned during 3.x. Pass a natural key (brand, operator, optional sandbox) or a seller-assigned account_id from list_accounts.' - ), - ] = None - campaign: Annotated[Campaign, Field(description='Campaign details for rights clearance')] - revocation_webhook: Annotated[ - push_notification_config_1.PushNotificationConfig, - Field( - description='Webhook for rights revocation notifications. If the rights holder needs to revoke rights (talent scandal, contract violation, etc.), they POST a revocation-notification to this URL. The buyer is responsible for stopping creative delivery upon receipt.' - ), - ] - push_notification_config: Annotated[ - push_notification_config_1.PushNotificationConfig | None, - Field( - description='Webhook for async status updates if the acquisition requires approval. The rights agent sends a webhook notification when the status transitions to acquired or rejected.' - ), - ] = None - idempotency_key: Annotated[ - str, - Field( - description='Client-generated key for safe retries. Resubmitting with the same key returns the original response rather than creating a duplicate acquisition. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/brand/acquire_rights_response.py b/src/adcp/types/generated_poc/brand/acquire_rights_response.py deleted file mode 100644 index 825371fd7..000000000 --- a/src/adcp/types/generated_poc/brand/acquire_rights_response.py +++ /dev/null @@ -1,82 +0,0 @@ -# generated by datamodel-codegen: -# filename: brand/acquire_rights_response.json -# timestamp: preserved-by-post-generate-fixes - -from __future__ import annotations - -from typing import Annotated, Any, Literal, TypeAlias - -from pydantic import AnyUrl, ConfigDict, Field - -from ..core.version_envelope import AdcpVersionEnvelope -from . import rights_terms as rights_terms_1 -from ..core import context as context_1 -from ..core import error as error_1 -from ..core import ext as ext_1 -from ..core import generation_credential as generation_credential_1 -from ..core import push_notification_config as push_notification_config_1 -from ..core.protocol_envelope import ProtocolEnvelope - - -class Disclosure(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - required: bool - text: str | None = None - - -class AcquireRightsResponse1(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - rights_id: str - rights_status: Literal['acquired'] = 'acquired' - brand_id: str - terms: rights_terms_1.RightsTerms - generation_credentials: list[generation_credential_1.GenerationCredential] - restrictions: list[str] | None = None - disclosure: Disclosure | None = None - approval_webhook: push_notification_config_1.PushNotificationConfig | None = None - usage_reporting_url: AnyUrl | None = None - rights_constraint: Any - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class AcquireRightsResponse2(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - rights_id: str - rights_status: Literal['pending_approval'] = 'pending_approval' - brand_id: str - detail: str | None = None - estimated_response_time: str | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class AcquireRightsResponse3(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - rights_id: str - rights_status: Literal['rejected'] = 'rejected' - brand_id: str - reason: str - suggestions: list[str] | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class AcquireRightsResponse4(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - errors: Annotated[list[error_1.Error], Field(min_length=1)] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -AcquireRightsResponse: TypeAlias = AcquireRightsResponse1 | AcquireRightsResponse2 | AcquireRightsResponse3 | AcquireRightsResponse4 - - -__all__ = [ - 'AcquireRightsResponse', - 'AcquireRightsResponse1', - 'AcquireRightsResponse2', - 'AcquireRightsResponse3', - 'AcquireRightsResponse4', - 'Disclosure', -] diff --git a/src/adcp/types/generated_poc/brand/creative_approval_request.py b/src/adcp/types/generated_poc/brand/creative_approval_request.py deleted file mode 100644 index 1b5e5c17e..000000000 --- a/src/adcp/types/generated_poc/brand/creative_approval_request.py +++ /dev/null @@ -1,54 +0,0 @@ -# generated by datamodel-codegen: -# filename: brand/creative_approval_request.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from pydantic import AnyUrl, ConfigDict, Field - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core import format_id -from ..core.version_envelope import AdcpVersionEnvelope - - -class CreativeApprovalRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - rights_id: Annotated[str, Field(description='Rights grant this creative was produced under')] - creative_id: Annotated[ - str | None, - Field( - description='Buyer-assigned creative identifier. Equivalent to OpenRTB crid. Used to track approval status across resubmissions.' - ), - ] = None - creative_url: Annotated[ - AnyUrl, Field(description='URL where the creative asset can be retrieved for review') - ] - creative_format: Annotated[ - format_id.FormatReferenceStructuredObject | None, - Field(description='Format of the creative being submitted'), - ] = None - description: Annotated[ - str | None, Field(description='Description of the creative for reviewer context') - ] = None - metadata: Annotated[ - dict[str, Any] | None, - Field( - description='Additional creative metadata (duration, dimensions, target audience, etc.)' - ), - ] = None - idempotency_key: Annotated[ - str, - Field( - description='Client-generated key for safe retries. Resubmitting with the same key returns the original response. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/brand/creative_approval_response.py b/src/adcp/types/generated_poc/brand/creative_approval_response.py deleted file mode 100644 index 7593c775e..000000000 --- a/src/adcp/types/generated_poc/brand/creative_approval_response.py +++ /dev/null @@ -1,106 +0,0 @@ -# generated by datamodel-codegen: -# filename: brand/creative_approval_response.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope - - -class CreativeApprovalResponse1(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - approval_status: Annotated[ - Literal['approved'], - Field( - description='Creative has been approved for distribution. Renamed from `status` in 3.1 to free the top-level `status` key for the envelope task-status (TaskStatus) under MCP flat-on-the-wire serialization (#4878).' - ), - ] = 'approved' - rights_id: str - creative_id: Annotated[ - str | None, Field(description="Echo of the buyer's creative identifier") - ] = None - creative_url: AnyUrl | None = None - approved_at: AwareDatetime | None = None - conditions: Annotated[ - list[str] | None, - Field(description="Conditions on the approval (e.g., 'approved for NL market only')"), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class CreativeApprovalResponse2(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - approval_status: Annotated[Literal['rejected'], Field(description='Creative was rejected')] = 'rejected' - rights_id: str - creative_id: Annotated[ - str | None, Field(description="Echo of the buyer's creative identifier") - ] = None - creative_url: AnyUrl | None = None - reason: Annotated[str, Field(description='Why the creative was rejected')] - suggestions: Annotated[ - list[str] | None, - Field( - description='Actionable feedback for revision. If present, the buyer can revise and resubmit the creative. If absent, the rejection is final for this creative concept.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class CreativeApprovalResponse3(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - approval_status: Annotated[ - Literal['pending_review'], Field(description='Creative is queued for review') - ] = 'pending_review' - rights_id: str - creative_id: Annotated[ - str | None, Field(description="Echo of the buyer's creative identifier") - ] = None - creative_url: AnyUrl | None = None - estimated_response_time: Annotated[ - str | None, Field(description="Expected time for review (e.g., '24h', '2 business days')") - ] = None - status_url: Annotated[ - AnyUrl | None, - Field( - description='URL to poll for updated approval status. GET this URL to receive a creative-approval-response. Poll at reasonable intervals (suggested: every 5 minutes, back off after 1 hour to every 30 minutes). Stop polling after estimated_response_time has elapsed and the approval_status is still pending_review.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class CreativeApprovalResponse4(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - errors: Annotated[list[error.Error], Field(min_length=1)] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -CreativeApprovalResponse = Annotated[ - CreativeApprovalResponse1 - | CreativeApprovalResponse2 - | CreativeApprovalResponse3 - | CreativeApprovalResponse4, - Field( - description='Response from the approval_webhook after reviewing a submitted creative. Uses discriminated union on `approval_status` field: approved, rejected, or pending_review.', - title='Creative Approval Response', - ), -] diff --git a/src/adcp/types/generated_poc/brand/get_brand_identity_request.py b/src/adcp/types/generated_poc/brand/get_brand_identity_request.py deleted file mode 100644 index 518f9f036..000000000 --- a/src/adcp/types/generated_poc/brand/get_brand_identity_request.py +++ /dev/null @@ -1,51 +0,0 @@ -# generated by datamodel-codegen: -# filename: brand/get_brand_identity_request.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from pydantic import ConfigDict, Field - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.version_envelope import AdcpVersionEnvelope - - -class Field1(StrEnum): - description = 'description' - industries = 'industries' - keller_type = 'keller_type' - logos = 'logos' - colors = 'colors' - fonts = 'fonts' - visual_guidelines = 'visual_guidelines' - tone = 'tone' - tagline = 'tagline' - voice_synthesis = 'voice_synthesis' - assets = 'assets' - rights = 'rights' - - -class GetBrandIdentityRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - brand_id: Annotated[str, Field(description='Brand identifier from brand.json brands array')] - fields: Annotated[ - list[Field1] | None, - Field( - description='Optional identity sections to include in the response. When omitted, all sections the caller is authorized to see are returned. Core fields (brand_id, house, names) are always returned and do not need to be requested.', - min_length=1, - ), - ] = None - use_case: Annotated[ - str | None, - Field( - description="Intended use case, so the agent can tailor the response. A 'voice_synthesis' use case returns voice configs; a 'likeness' use case returns high-res photos and appearance guidelines." - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/brand/get_brand_identity_response.py b/src/adcp/types/generated_poc/brand/get_brand_identity_response.py deleted file mode 100644 index 69ccf26d0..000000000 --- a/src/adcp/types/generated_poc/brand/get_brand_identity_response.py +++ /dev/null @@ -1,155 +0,0 @@ -# generated by datamodel-codegen: -# filename: brand/get_brand_identity_response.json -# timestamp: preserved-by-post-generate-fixes - -from __future__ import annotations - -from typing import Annotated, Any, Literal, TypeAlias - -from pydantic import AnyUrl, ConfigDict, Field, StringConstraints - -from ..core.version_envelope import AdcpVersionEnvelope -from ..core import context as context_1 -from ..core import error as error_1 -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..enums import asset_content_type as asset_content_type_1 -from ..enums import right_use as right_use_1 - - -class House(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - domain: str - name: str - - -class Logo(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - url: AnyUrl - orientation: Literal['square', 'horizontal', 'vertical', 'stacked'] | None = None - background: Literal['dark-bg', 'light-bg', 'transparent-bg'] | None = None - variant: Literal['primary', 'secondary', 'icon', 'wordmark', 'full-lockup'] | None = None - tags: list[str] | None = None - usage: str | None = None - width: int | None = None - height: int | None = None - - -class Colors(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - primary: Annotated[str, StringConstraints(pattern='^#[0-9A-Fa-f]{6}$')] | Annotated[list[Annotated[str, StringConstraints(pattern='^#[0-9A-Fa-f]{6}$')]], Field(min_length=1)] | None = None - secondary: Annotated[str, StringConstraints(pattern='^#[0-9A-Fa-f]{6}$')] | Annotated[list[Annotated[str, StringConstraints(pattern='^#[0-9A-Fa-f]{6}$')]], Field(min_length=1)] | None = None - accent: Annotated[str, StringConstraints(pattern='^#[0-9A-Fa-f]{6}$')] | Annotated[list[Annotated[str, StringConstraints(pattern='^#[0-9A-Fa-f]{6}$')]], Field(min_length=1)] | None = None - background: Annotated[str, StringConstraints(pattern='^#[0-9A-Fa-f]{6}$')] | Annotated[list[Annotated[str, StringConstraints(pattern='^#[0-9A-Fa-f]{6}$')]], Field(min_length=1)] | None = None - text: Annotated[str, StringConstraints(pattern='^#[0-9A-Fa-f]{6}$')] | Annotated[list[Annotated[str, StringConstraints(pattern='^#[0-9A-Fa-f]{6}$')]], Field(min_length=1)] | None = None - - -class File(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - url: Annotated[str, StringConstraints(pattern='^https://')] - weight: Annotated[int, Field(ge=100, le=900)] | None = None - weight_range: Annotated[list[Annotated[int, Field(ge=100, le=900)]], Field(min_length=2, max_length=2)] | None = None - style: Literal['normal', 'italic', 'oblique'] | None = None - - -class FontRole2(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - family: str - files: Annotated[list[File], Field(max_length=36)] | None = None - opentype_features: Annotated[list[Annotated[str, StringConstraints(pattern='^[a-z0-9]{4}$')]], Field(max_length=20)] | None = None - fallbacks: Annotated[list[Annotated[str, StringConstraints(max_length=100)]], Field(max_length=10)] | None = None - - -class Fonts(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - primary: str | FontRole2 | None = None - secondary: str | FontRole2 | None = None - - -class Tone(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - voice: str | None = None - attributes: list[str] | None = None - dos: list[str] | None = None - donts: list[str] | None = None - - -class VoiceSynthesis(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - provider: str | None = None - voice_id: str | None = None - rights_offering_id: str | None = None - settings: dict[str, Any] | None = None - - -class Asset(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - asset_id: str - asset_type: asset_content_type_1.AssetContentType - url: AnyUrl - tags: list[str] | None = None - name: str | None = None - description: str | None = None - width: int | None = None - height: int | None = None - duration_seconds: float | None = None - file_size_bytes: int | None = None - format: str | None = None - - -class Rights(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - available_uses: list[right_use_1.RightUse] | None = None - countries: list[Annotated[str, StringConstraints(pattern='^[A-Z]{2}$')]] | None = None - excluded_countries: list[Annotated[str, StringConstraints(pattern='^[A-Z]{2}$')]] | None = None - exclusivity_model: str | None = None - content_restrictions: list[str] | None = None - - -class GetBrandIdentityResponse1(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - brand_id: str - house: House - names: list[dict[str, str]] - description: str | None = None - industries: Annotated[list[str], Field(min_length=1)] | None = None - keller_type: Literal['master', 'sub_brand', 'endorsed', 'independent'] | None = None - logos: list[Logo] | None = None - colors: Colors | None = None - fonts: Fonts | None = None - visual_guidelines: dict[str, Any] | None = None - tone: Tone | None = None - tagline: str | Annotated[list[dict[str, Annotated[str, StringConstraints(min_length=1)]]], Field(min_length=1)] | None = None - voice_synthesis: VoiceSynthesis | None = None - assets: list[Asset] | None = None - rights: Rights | None = None - available_fields: list[Literal['description', 'industries', 'keller_type', 'logos', 'colors', 'fonts', 'visual_guidelines', 'tone', 'tagline', 'voice_synthesis', 'assets', 'rights']] | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class GetBrandIdentityResponse2(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - errors: Annotated[list[error_1.Error], Field(min_length=1)] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -GetBrandIdentityResponse: TypeAlias = GetBrandIdentityResponse1 | GetBrandIdentityResponse2 - - -__all__ = [ - 'GetBrandIdentityResponse', - 'GetBrandIdentityResponse1', - 'GetBrandIdentityResponse2', - 'Asset', - 'Colors', - 'File', - 'FontRole2', - 'Fonts', - 'House', - 'Logo', - 'Rights', - 'Tone', - 'VoiceSynthesis', -] diff --git a/src/adcp/types/generated_poc/brand/get_rights_request.py b/src/adcp/types/generated_poc/brand/get_rights_request.py deleted file mode 100644 index de22904fc..000000000 --- a/src/adcp/types/generated_poc/brand/get_rights_request.py +++ /dev/null @@ -1,78 +0,0 @@ -# generated by datamodel-codegen: -# filename: brand/get_rights_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._scalar import ScalarStr - -from typing import Annotated - -from pydantic import ConfigDict, Field, StrictBool - -from ..core import brand_ref -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core import pagination_request -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import right_type as right_type_1 -from ..enums import right_use - - -class Country(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{2}$'} - - -class GetRightsRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - query: Annotated[ - str, - Field( - description='Natural language description of desired rights. The agent interprets intent, budget signals, and compatibility from this text.', - max_length=2000, - ), - ] - uses: Annotated[ - list[right_use.RightUse], - Field( - description='Rights uses being requested. The agent returns options covering these uses, potentially bundled into composite pricing.', - min_length=1, - ), - ] - buyer_brand: Annotated[ - brand_ref.BrandReference | None, - Field( - description="The buyer's brand. The agent fetches the buyer's brand.json for compatibility filtering (e.g., dietary conflicts, competitor exclusions)." - ), - ] = None - countries: Annotated[ - list[Country] | None, - Field( - description='Countries where rights are needed (ISO 3166-1 alpha-2). Filters to rights available in these markets.' - ), - ] = None - brand_id: Annotated[ - str | None, - Field( - description="Search within a specific brand's rights. If omitted, searches across the agent's full roster." - ), - ] = None - right_type: Annotated[ - right_type_1.RightType | None, - Field(description='Filter by type of rights (talent, music, stock_media, etc.)'), - ] = None - include_excluded: Annotated[ - StrictBool | None, - Field( - description='Include filtered-out results in the excluded array with reasons. Defaults to false.' - ), - ] = False - pagination: Annotated[ - pagination_request.PaginationRequest | None, - Field(description='Pagination parameters for large result sets'), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/brand/get_rights_response.py b/src/adcp/types/generated_poc/brand/get_rights_response.py deleted file mode 100644 index f949927e9..000000000 --- a/src/adcp/types/generated_poc/brand/get_rights_response.py +++ /dev/null @@ -1,85 +0,0 @@ -# generated by datamodel-codegen: -# filename: brand/get_rights_response.json -# timestamp: preserved-by-post-generate-fixes - -from __future__ import annotations - -from typing import Annotated, TypeAlias - -from pydantic import AnyUrl, ConfigDict, Field, StringConstraints - -from ..core.version_envelope import AdcpVersionEnvelope -from . import rights_pricing_option as rights_pricing_option_1 -from ..core import context as context_1 -from ..core import error as error_1 -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..enums import right_type as right_type_1 -from ..enums import right_use as right_use_1 - - -class ExclusivityStatus(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - available: bool | None = None - existing_exclusives: list[str] | None = None - - -class PreviewAsset(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - url: AnyUrl - usage: str | None = None - - -class Right(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - rights_id: str - brand_id: str - name: str - description: str | None = None - right_type: right_type_1.RightType | None = None - match_score: Annotated[float, Field(ge=0, le=1)] | None = None - match_reasons: list[str] | None = None - available_uses: list[right_use_1.RightUse] - countries: list[Annotated[str, StringConstraints(pattern='^[A-Z]{2}$')]] | None = None - excluded_countries: list[Annotated[str, StringConstraints(pattern='^[A-Z]{2}$')]] | None = None - exclusivity_status: ExclusivityStatus | None = None - pricing_options: Annotated[list[rights_pricing_option_1.RightsPricingOption], Field(min_length=1)] - content_restrictions: list[str] | None = None - preview_assets: list[PreviewAsset] | None = None - - -class Excluded(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - brand_id: str - name: str | None = None - reason: str - suggestions: list[str] | None = None - - -class GetRightsResponse1(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - rights: list[Right] - excluded: list[Excluded] | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class GetRightsResponse2(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - errors: Annotated[list[error_1.Error], Field(min_length=1)] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -GetRightsResponse: TypeAlias = GetRightsResponse1 | GetRightsResponse2 - - -__all__ = [ - 'GetRightsResponse', - 'GetRightsResponse1', - 'GetRightsResponse2', - 'Excluded', - 'ExclusivityStatus', - 'PreviewAsset', - 'Right', -] diff --git a/src/adcp/types/generated_poc/brand/revocation_notification.py b/src/adcp/types/generated_poc/brand/revocation_notification.py deleted file mode 100644 index e6424cfac..000000000 --- a/src/adcp/types/generated_poc/brand/revocation_notification.py +++ /dev/null @@ -1,47 +0,0 @@ -# generated by datamodel-codegen: -# filename: brand/revocation_notification.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..enums import right_use - - -class RevocationNotification(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - idempotency_key: Annotated[ - str, - Field( - description='Sender-generated key stable across retries of the same revocation notification. Rights holders MUST generate a cryptographically random value (UUID v4 recommended) per distinct revocation event and reuse the same key when retrying delivery. Buyers MUST dedupe by this key, scoped to the authenticated sender identity (HMAC secret or Bearer credential); keys from different senders are independent.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - rights_id: Annotated[str, Field(description='The revoked rights grant identifier')] - brand_id: Annotated[str, Field(description='Brand identifier of the rights subject')] - reason: Annotated[str, Field(description='Human-readable reason for revocation')] - effective_at: Annotated[ - AwareDatetime, - Field( - description='When the revocation takes effect. Immediate revocations use current time. Grace periods use a future time. The buyer must stop serving creative using these rights by this time.' - ), - ] - revoked_uses: Annotated[ - list[right_use.RightUse] | None, - Field( - description='If present, only these uses are revoked (partial revocation). If absent, all uses under the grant are revoked.', - min_length=1, - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/brand/rights_pricing_option.py b/src/adcp/types/generated_poc/brand/rights_pricing_option.py deleted file mode 100644 index 13e7695ff..000000000 --- a/src/adcp/types/generated_poc/brand/rights_pricing_option.py +++ /dev/null @@ -1,59 +0,0 @@ -# generated by datamodel-codegen: -# filename: brand/rights_pricing_option.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictFloat - -from ..core import ext as ext_1 -from ..enums import pricing_model, right_use, rights_billing_period - - -class RightsPricingOption(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, - Field( - description='Unique identifier for this pricing option. Referenced in acquire_rights and report_usage.' - ), - ] - model: Annotated[ - pricing_model.PricingModel, Field(description='Pricing model (cpm, flat_rate, etc.)') - ] - price: Annotated[ - StrictFloat, - Field( - description='Price amount. Interpretation depends on model: CPM = cost per 1,000 impressions, flat_rate = fixed cost per period.', - ge=0.0, - ), - ] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - uses: Annotated[ - list[right_use.RightUse], - Field( - description='Which rights uses this pricing option covers. A single option can bundle multiple uses (e.g., likeness + voice).', - min_length=1, - ), - ] - period: Annotated[ - rights_billing_period.RightsBillingPeriod | None, - Field(description='Billing period for flat_rate and time-based models'), - ] = None - impression_cap: Annotated[ - SchemaInt | None, - Field(description='Maximum impressions included in this pricing option per period', ge=1), - ] = None - overage_cpm: Annotated[ - StrictFloat | None, - Field(description='CPM rate applied to impressions exceeding the impression_cap', ge=0.0), - ] = None - description: Annotated[ - str | None, Field(description='Human-readable description of this pricing option') - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/brand/rights_terms.py b/src/adcp/types/generated_poc/brand/rights_terms.py deleted file mode 100644 index 7e9bc7637..000000000 --- a/src/adcp/types/generated_poc/brand/rights_terms.py +++ /dev/null @@ -1,45 +0,0 @@ -# generated by datamodel-codegen: -# filename: brand/rights_terms.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from datetime import date -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, StrictFloat - -from ..enums import right_use, rights_billing_period - - -class Country(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{2}$'} - - -class Exclusivity(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: str | None = None - countries: list[Country] | None = None - - -class RightsTerms(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: str - amount: Annotated[StrictFloat, Field(ge=0.0)] - currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] - period: rights_billing_period.RightsBillingPeriod | None = None - uses: list[right_use.RightUse] - impression_cap: Annotated[SchemaInt | None, Field(ge=1)] = None - overage_cpm: Annotated[StrictFloat | None, Field(ge=0.0)] = None - start_date: date | None = None - end_date: date | None = None - exclusivity: Annotated[ - Exclusivity | None, Field(description='Exclusivity terms if applicable') - ] = None diff --git a/src/adcp/types/generated_poc/brand/search_brands_request.py b/src/adcp/types/generated_poc/brand/search_brands_request.py deleted file mode 100644 index c3440c221..000000000 --- a/src/adcp/types/generated_poc/brand/search_brands_request.py +++ /dev/null @@ -1,69 +0,0 @@ -# generated by datamodel-codegen: -# filename: brand/search_brands_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field - -from ..core import brand_ref -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core import pagination_request -from ..enums import advertiser_industry - - -class Country(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{2}$'} - - -class SearchBrandsRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_major_version: Annotated[ - SchemaInt | None, - Field( - description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - query: Annotated[ - str, - Field( - description='Natural language description of the brands to find. The agent interprets intent, category, and roster coverage from this text.', - max_length=2000, - ), - ] - industries: Annotated[ - list[advertiser_industry.AdvertiserIndustry] | None, - Field( - description='Optional industry filter. The agent restricts results to brands operating in these industries. Values follow the advertiser-industry taxonomy; the agent SHOULD accept unknown values gracefully per the advertiser-industry extension policy.', - min_length=1, - ), - ] = None - countries: Annotated[ - list[Country] | None, - Field( - description='Optional market filter. Returns brands with rights availability in at least one of the specified countries (ISO 3166-1 alpha-2).', - min_length=1, - ), - ] = None - buyer_brand: Annotated[ - brand_ref.BrandReference | None, - Field( - description="The buyer's brand. When provided, the agent SHOULD apply the same compatibility filtering as get_rights — filtering out brands with category conflicts, competitor exclusions, or dietary restrictions that would prevent licensing. When omitted, results are returned without compatibility filtering." - ), - ] = None - pagination: Annotated[ - pagination_request.PaginationRequest | None, - Field(description='Pagination parameters for large result sets'), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/brand/search_brands_response.py b/src/adcp/types/generated_poc/brand/search_brands_response.py deleted file mode 100644 index 763918ffe..000000000 --- a/src/adcp/types/generated_poc/brand/search_brands_response.py +++ /dev/null @@ -1,204 +0,0 @@ -# generated by datamodel-codegen: -# filename: brand/search_brands_response.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, model_validator - -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 -from ..core import pagination_response -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import advertiser_industry, right_use - - -class House(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - domain: Annotated[ - str, - Field( - description='House domain (e.g., acme-corp.com). For mutually confirmed relationships (relationship_trust = mutual or inline) this domain is trust-extending. For one-sided or unconfirmed claims see also claimed_house_domain.' - ), - ] - name: Annotated[str, Field(description='House display name')] - - -class RelationshipTrust(StrEnum): - inline = 'inline' - mutual = 'mutual' - leaf_only = 'leaf_only' - house_only = 'house_only' - standalone = 'standalone' - unverifiable = 'unverifiable' - - -class KellerType(StrEnum): - master = 'master' - sub_brand = 'sub_brand' - endorsed = 'endorsed' - independent = 'independent' - - -class Orientation(StrEnum): - square = 'square' - horizontal = 'horizontal' - vertical = 'vertical' - stacked = 'stacked' - - -class Background(StrEnum): - dark_bg = 'dark-bg' - light_bg = 'light-bg' - transparent_bg = 'transparent-bg' - - -class Variant(StrEnum): - primary = 'primary' - secondary = 'secondary' - icon = 'icon' - wordmark = 'wordmark' - full_lockup = 'full-lockup' - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - url: Annotated[AnyUrl, Field(description='URL to the logo asset')] - orientation: Annotated[ - Orientation | None, Field(description='Logo aspect ratio orientation') - ] = None - background: Annotated[Background | None, Field(description='Background compatibility')] = None - variant: Annotated[Variant | None, Field(description='Logo variant type')] = None - - -class Country(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{2}$'} - - -class ExcludedCountry(Country): - pass - - -class Rights(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - available_uses: list[right_use.RightUse] | None = None - countries: Annotated[ - list[Country] | None, - Field( - description='Countries where rights are available (ISO 3166-1 alpha-2). If omitted, rights are available worldwide.' - ), - ] = None - excluded_countries: Annotated[ - list[ExcludedCountry] | None, - Field(description='Countries excluded from availability (ISO 3166-1 alpha-2)'), - ] = None - - -class SearchBrandResult(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - brand_id: Annotated[ - str, - Field( - description='Brand identifier. Pass to get_brand_identity or get_rights for the next step.' - ), - ] - house: Annotated[ - House | None, - Field( - description='The house (corporate entity) associated with this brand. Present when a house affiliation is on record. Use relationship_trust to determine whether this association has been reciprocally confirmed by the house.' - ), - ] = None - relationship_trust: Annotated[ - RelationshipTrust | None, - Field( - description="Trust verdict for the brand-to-house relationship, computed from the mutual-assertion model. inline = brand defined inside the house's own document; mutual = both sides publish the relationship (trust-extending); leaf_only = brand claims house but house has not reciprocated or declaration is expired; house_only = house claims brand, brand has not reciprocated; standalone = brand claims no house; unverifiable = claimed house could not be checked. When absent, trust has not yet been computed — absent MUST NOT be interpreted as standalone. Use get_brand_identity or verify_brand_claim for authoritative trust resolution." - ), - ] = None - relationship_verified_at: Annotated[ - AwareDatetime | None, - Field( - description='When the mutual-assertion edge was last confirmed by both sides. Present only when relationship_trust is mutual.' - ), - ] = None - claimed_house_domain: Annotated[ - str | None, - Field( - description='Canonical domain of the house this brand self-declares as its parent. Not trust-extending on its own. Present for leaf_only and unverifiable trust states where the brand has made a claim that could not be or has not been reciprocated.' - ), - ] = None - names: Annotated[ - list[dict[str, str]], - Field( - description="Localized brand names with BCP 47 locale code keys (e.g., 'en_US', 'fr_CA')." - ), - ] - description: Annotated[str | None, Field(description='Brand description')] = None - industries: Annotated[ - list[advertiser_industry.AdvertiserIndustry] | None, - Field( - description='Brand industries, following the advertiser-industry taxonomy', min_length=1 - ), - ] = None - keller_type: Annotated[ - KellerType | None, - Field( - description='Brand architecture type: master (primary brand of house), sub_brand (carries parent name), endorsed (independent identity backed by parent), independent (operates separately)' - ), - ] = None - logos: Annotated[ - list[Logo] | None, - Field( - description='Standard-resolution logos for display during discovery. High-res variants are gated behind get_brand_identity with account authorization.' - ), - ] = None - rights: Annotated[ - Rights | None, - Field( - description='Rights availability summary. Signals whether this brand has licensable rights and in which markets. For detailed pricing and rights records, use get_rights.' - ), - ] = None - - -class SearchBrandsResponse(AdcpVersionEnvelope, ProtocolEnvelope): - brands: Annotated[ - list[SearchBrandResult] | None, - Field(description='Brand stubs matching the query, ranked by relevance'), - ] = None - pagination: Annotated[ - pagination_response.PaginationResponse | None, - Field( - description='Pagination metadata. Present when has_more is true or total_count is known.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - errors: Annotated[list[error.Error] | None, Field(min_length=1)] = None - - @model_validator(mode='after') - def _require_schema_required_group(self) -> SearchBrandsResponse: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('brands',), ('errors',),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'SearchBrandsResponse requires at least one of these field groups: brands | errors' - ) diff --git a/src/adcp/types/generated_poc/brand/update_rights_request.py b/src/adcp/types/generated_poc/brand/update_rights_request.py deleted file mode 100644 index fdbaf6133..000000000 --- a/src/adcp/types/generated_poc/brand/update_rights_request.py +++ /dev/null @@ -1,81 +0,0 @@ -# generated by datamodel-codegen: -# filename: brand/update_rights_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from datetime import date -from typing import Annotated - -from adcp.types.base import SchemaInt -from pydantic import ConfigDict, Field, StrictBool - -from ..core import account_ref -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core import push_notification_config as push_notification_config_1 -from ..core.version_envelope import AdcpVersionEnvelope - - -class UpdateRightsRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - governance_context: Annotated[ - str | None, - Field( - description='Opaque intent authorization for a commitment-increasing rights update.', - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - rights_id: Annotated[ - str, Field(description='Rights grant identifier from acquire_rights response') - ] - account: Annotated[ - account_ref.AccountReference | None, - Field( - description='Account context for this update. Used by the brand agent to resolve any governance agent previously bound for this brand+operator pair via sync_governance — update_rights is a modification-phase governance trigger (per `/docs/governance/campaign/specification#spend-commit-invocation`) and the brand agent consults the bound agent when computing the incremental commit delta. When both an inline governance_context token (on the protocol envelope) and a bound governance agent are present, the inline token wins. Pass a natural key (brand, operator, optional sandbox) or a seller-assigned account_id from list_accounts. The estimated_impressions / commit-delta projection rule for governance-aware updates is tracked separately and not yet normative on this task.' - ), - ] = None - end_date: Annotated[ - date | None, - Field( - description='New end date for the rights grant (must be >= current end_date). Extending the grant may re-issue generation credentials with updated expiration.' - ), - ] = None - impression_cap: Annotated[ - SchemaInt | None, - Field( - description='New impression cap for the grant. Must be >= impressions already delivered.', - ge=1, - ), - ] = None - pricing_option_id: Annotated[ - str | None, - Field( - description="Switch to a different pricing option from the original get_rights offering. The new option must be compatible with the existing grant's uses and countries." - ), - ] = None - paused: Annotated[ - StrictBool | None, - Field( - description='Pause or resume the rights grant. When paused, generation credentials are suspended and creative delivery should stop. When resumed, credentials are re-activated.' - ), - ] = None - push_notification_config: Annotated[ - push_notification_config_1.PushNotificationConfig | None, - Field(description='Webhook for async update notifications if the update requires approval'), - ] = None - idempotency_key: Annotated[ - str, - Field( - description='Client-generated idempotency key for safe retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/brand/update_rights_response.py b/src/adcp/types/generated_poc/brand/update_rights_response.py deleted file mode 100644 index 7678cdef9..000000000 --- a/src/adcp/types/generated_poc/brand/update_rights_response.py +++ /dev/null @@ -1,48 +0,0 @@ -# generated by datamodel-codegen: -# filename: brand/update_rights_response.json -# timestamp: preserved-by-post-generate-fixes - -from __future__ import annotations - -from typing import Annotated, Any, TypeAlias - -from pydantic import AwareDatetime, ConfigDict, Field - -from ..core.version_envelope import AdcpVersionEnvelope -from . import rights_terms as rights_terms_1 -from ..core import context as context_1 -from ..core import error as error_1 -from ..core import ext as ext_1 -from ..core import generation_credential as generation_credential_1 -from ..core.protocol_envelope import ProtocolEnvelope - - -class UpdateRightsResponse1(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - rights_id: str - terms: rights_terms_1.RightsTerms - generation_credentials: list[generation_credential_1.GenerationCredential] = Field( - default_factory=list - ) - rights_constraint: Any | None = None - paused: bool | None = None - implementation_date: AwareDatetime | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class UpdateRightsResponse2(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - errors: Annotated[list[error_1.Error], Field(min_length=1)] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -UpdateRightsResponse: TypeAlias = UpdateRightsResponse1 | UpdateRightsResponse2 - - -__all__ = [ - 'UpdateRightsResponse', - 'UpdateRightsResponse1', - 'UpdateRightsResponse2', -] diff --git a/src/adcp/types/generated_poc/brand/verification_status.py b/src/adcp/types/generated_poc/brand/verification_status.py deleted file mode 100644 index 0317fa409..000000000 --- a/src/adcp/types/generated_poc/brand/verification_status.py +++ /dev/null @@ -1,19 +0,0 @@ -# generated by datamodel-codegen: -# filename: brand/verification_status.json -# timestamp: 2026-05-22T13:15:12+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class VerificationStatus(StrEnum): - owned = 'owned' - pending_review = 'pending_review' - transferring = 'transferring' - disputed = 'disputed' - not_ours = 'not_ours' - archived = 'archived' - licensed_in = 'licensed_in' - licensed_out = 'licensed_out' - unknown = 'unknown' diff --git a/src/adcp/types/generated_poc/brand/verify_brand_claim_request.py b/src/adcp/types/generated_poc/brand/verify_brand_claim_request.py deleted file mode 100644 index 1ec6e9604..000000000 --- a/src/adcp/types/generated_poc/brand/verify_brand_claim_request.py +++ /dev/null @@ -1,38 +0,0 @@ -# generated by datamodel-codegen: -# filename: brand/verify_brand_claim_request.json -# timestamp: 2026-10-02T23:28:30+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Any - -from ..core.version_envelope import AdcpVersionEnvelope - - -from pydantic import model_validator - - -class ClaimType(StrEnum): - subsidiary = 'subsidiary' - parent = 'parent' - property = 'property' - trademark = 'trademark' - - -class VerifyBrandClaimRequest(AdcpVersionEnvelope): - claim_type: ClaimType - claim: dict[str, Any] - - @model_validator(mode='after') - def _require_schema_required_group(self) -> VerifyBrandClaimRequest: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('claim_type', 'claim'),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'VerifyBrandClaimRequest requires at least one of these field groups: claim_type+claim' - ) diff --git a/src/adcp/types/generated_poc/brand/verify_brand_claim_response.py b/src/adcp/types/generated_poc/brand/verify_brand_claim_response.py deleted file mode 100644 index 50f8da09f..000000000 --- a/src/adcp/types/generated_poc/brand/verify_brand_claim_response.py +++ /dev/null @@ -1,151 +0,0 @@ -# generated by datamodel-codegen: -# filename: brand/verify_brand_claim_response.json - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Any, Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - -from ..core import context as context_1 -from ..core import error as error_1 -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from . import verification_status - - -class ClaimType(StrEnum): - subsidiary = 'subsidiary' - parent = 'parent' - property = 'property' - trademark = 'trademark' - - -class VerifyBrandClaimSignedSuccessPayload(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - claim_type: ClaimType - verification_status: verification_status.VerificationStatus - details: dict[str, Any] | None = None - context_note: Annotated[str | None, Field(max_length=500)] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class VerifyBrandClaimPayload(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - typ: Annotated[ - Literal['adcp-response-payload+jws'], - Field(description='Type discriminator preventing cross-profile replay.'), - ] - task: Annotated[ - Literal['verify_brand_claim'], - Field(description='Designated task whose response payload is signed.'), - ] - brand_domain: Annotated[ - str, - Field( - description='Brand tenant whose policy store produced the answer. The signer MUST derive this from server-side tenant resolution, not caller-supplied request fields.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - agent_url: Annotated[ - AnyUrl, - Field( - description='Canonical URL of the responding brand agent entry whose response-signing key verifies this envelope.' - ), - ] - request_hash: Annotated[ - str, - Field( - description='sha256: prefix plus unpadded base64url SHA-256 of the canonical request-binding object for this call.', - pattern='^sha256:[A-Za-z0-9_-]{43}$', - ), - ] - iat: Annotated[int, Field(description='Issued-at time as Unix epoch seconds.', ge=0)] - exp: Annotated[ - int, - Field( - description='Expiration time as Unix epoch seconds. Online verifiers reject envelopes after this time, allowing only implementation-defined clock skew.', - ge=0, - ), - ] - response: VerifyBrandClaimSignedSuccessPayload - - -class VerifyBrandClaimSignedResponse(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - protected: Annotated[ - str, - Field( - description='Base64url-encoded JWS protected header. The decoded header MUST include alg, kid, and typ: adcp-response-payload+jws, and MUST NOT include the RFC 7797 b64 header. Verifiers enforce the key purpose by resolving kid to a JWK with adcp_use: response-signing.', - pattern='^[A-Za-z0-9_-]+$', - ), - ] - payload: Annotated[ - VerifyBrandClaimPayload, - Field( - description='Decoded signed payload. Signers compute the JWS payload bytes from the RFC 8785/JCS canonicalization of this object.' - ), - ] - signature: Annotated[ - str, - Field( - description='Base64url-encoded JWS signature over the protected header and canonicalized payload.', - pattern='^[A-Za-z0-9_-]+$', - ), - ] - - -class VerifyBrandClaimSuccessResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - claim_type: Annotated[ - ClaimType, - Field(description="Echoes the request's claim_type for caller-side routing."), - ] - verification_status: Annotated[ - verification_status.VerificationStatus, - Field( - description='Verification status. Not every status applies to every claim_type - see the task page for the applicable subset. Renamed from `status` in 3.1 to free the top-level `status` key for the envelope task-status (TaskStatus) under MCP flat-on-the-wire serialization (#4878).' - ), - ] - signed_response: Annotated[ - VerifyBrandClaimSignedResponse, - Field( - description='Payload-envelope JWS attesting the canonical success response for verify_brand_claim. The signed payload response MUST match the unsigned task-body fields on this response, excluding signed_response and protocol/version envelope fields.' - ), - ] - details: Annotated[ - dict[str, Any] | None, - Field( - description="Per-claim-type response fields. Shape varies - see the task page for each claim_type's expected fields." - ), - ] = None - context_note: Annotated[ - str | None, - Field(description='Public - free-text context the brand chooses to surface.', max_length=500), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class VerifyBrandClaimErrorResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - errors: Annotated[list[error_1.Error], Field(min_length=1)] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -VerifyBrandClaimResponse = VerifyBrandClaimSuccessResponse | VerifyBrandClaimErrorResponse diff --git a/src/adcp/types/generated_poc/brand/verify_brand_claims_request.py b/src/adcp/types/generated_poc/brand/verify_brand_claims_request.py deleted file mode 100644 index 2f1805d63..000000000 --- a/src/adcp/types/generated_poc/brand/verify_brand_claims_request.py +++ /dev/null @@ -1,162 +0,0 @@ -# generated by datamodel-codegen: -# filename: brand/verify_brand_claims_request.json -# timestamp: 2026-10-04T00:53:27+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import AwareDatetime, ConfigDict, Field - -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import property_type - - -class Claim(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - subsidiary_domain: Annotated[ - str, - Field( - description='Domain of the leaf brand whose `house_domain` claim is being verified.', - pattern='^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]{0,61}[A-Za-z0-9])$', - ), - ] - subsidiary_brand_id: Annotated[ - str | None, - Field( - description='Stable brand identifier the leaf uses for itself. Optional but recommended.', - pattern='^[a-z0-9_]+$', - ), - ] = None - observed_at: Annotated[ - AwareDatetime | None, Field(description="When the caller observed the leaf's claim.") - ] = None - - -class ClaimEntry1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - claim_type: Literal['subsidiary'] = 'subsidiary' - claim: Claim - - -class Claim1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - parent_domain: Annotated[ - str, - Field( - description="Domain of the house claimed as this brand's parent.", - pattern='^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]{0,61}[A-Za-z0-9])$', - ), - ] - claimant_says: Annotated[ - str | None, Field(description='Optional context — what the claimant published or said.') - ] = None - observed_at: AwareDatetime | None = None - - -class ClaimEntry2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - claim_type: Literal['parent'] = 'parent' - claim: Claim1 - - -class Store(StrEnum): - apple = 'apple' - google = 'google' - amazon = 'amazon' - roku = 'roku' - fire_tv = 'fire_tv' - samsung = 'samsung' - lg = 'lg' - vizio = 'vizio' - other = 'other' - - -class Property(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: property_type.PropertyType - identifier: str - store: Store | None = None - region: str | None = None - - -class Claim2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - property: Annotated[Property, Field(description='Shape matches brand.json properties[] entry.')] - use_case: Annotated[ - str | None, Field(description='Optional caller-declared use case.', max_length=100) - ] = None - - -class ClaimEntry3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - claim_type: Literal['property'] = 'property' - claim: Claim2 - - -class Country(ScalarStr): - __slots__ = () - _constraints = {'max_length': 2, 'min_length': 2} - - -class Claim3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - mark: Annotated[str, Field(max_length=200, min_length=1)] - registry: Annotated[str | None, Field(max_length=50)] = None - number: Annotated[str | None, Field(max_length=100)] = None - countries: list[Country] | None = None - use_case: Annotated[str | None, Field(max_length=100)] = None - - -class ClaimEntry4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - claim_type: Literal['trademark'] = 'trademark' - claim: Claim3 - - -ClaimEntry = Annotated[ - ClaimEntry1 | ClaimEntry2 | ClaimEntry3 | ClaimEntry4, - Field( - description='One verification claim. Shape mirrors the single-target `verify_brand_claim` request body (minus the version envelope, which lives on the batch).', - discriminator='claim_type', - ), -] - - -class VerifyBrandClaimsRequestBulk(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - claims: Annotated[ - list[ClaimEntry], - Field( - description='Ordered list of verification claims. The agent MUST return `results[]` in the same order (positional zip-by-index). Maximum batch size is 100 per call; agents MAY enforce a lower limit and SHOULD advertise it via `get_adcp_capabilities` (see the task page).', - max_length=100, - min_length=1, - ), - ] - - -class VerifyBrandClaimsRequest(VerifyBrandClaimsRequestBulk): - pass diff --git a/src/adcp/types/generated_poc/brand/verify_brand_claims_response.py b/src/adcp/types/generated_poc/brand/verify_brand_claims_response.py deleted file mode 100644 index 97a8ce42b..000000000 --- a/src/adcp/types/generated_poc/brand/verify_brand_claims_response.py +++ /dev/null @@ -1,183 +0,0 @@ -# generated by datamodel-codegen: -# filename: brand/verify_brand_claims_response.json - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, RootModel - -from ..core import context as context_1 -from ..core import error as error_1 -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from . import verification_status - - -class ClaimType(StrEnum): - subsidiary = 'subsidiary' - parent = 'parent' - property = 'property' - trademark = 'trademark' - - -class ResultEntry1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - claim_type: Annotated[ - ClaimType, - Field(description="Echoes the request item's claim_type for caller-side routing."), - ] - status: Annotated[ - verification_status.VerificationStatus, - Field( - description='Verification status for this claim. Not every status applies to every claim_type - see the single-target task page for the applicable subset.' - ), - ] - details: Annotated[ - dict[str, Any] | None, - Field( - description="Per-claim-type response fields. Shape varies - see the single-target task page for each claim_type's expected fields." - ), - ] = None - context_note: Annotated[ - str | None, - Field(description='Public - free-text context the brand chooses to surface.', max_length=500), - ] = None - - -class ResultEntry2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - error: error_1.Error - - -class ResultEntry(RootModel[ResultEntry1 | ResultEntry2]): - root: Annotated[ - ResultEntry1 | ResultEntry2, - Field( - description='One entry in `results[]`. Either a per-claim success (claim_type + status + optional details/context_note) or a per-claim error (error field only). Mirrors the single-target `verify_brand_claim` response success arm shape.' - ), - ] - - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - - -class VerifyBrandClaimsSignedSuccessPayload(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - results: Annotated[list[ResultEntry], Field(min_length=1)] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class VerifyBrandClaimsPayload(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - typ: Annotated[ - Literal['adcp-response-payload+jws'], - Field(description='Type discriminator preventing cross-profile replay.'), - ] - task: Annotated[ - Literal['verify_brand_claims'], - Field(description='Designated task whose response payload is signed.'), - ] - brand_domain: Annotated[ - str, - Field( - description='Brand tenant whose policy store produced the answer. The signer MUST derive this from server-side tenant resolution, not caller-supplied request fields.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - agent_url: Annotated[ - AnyUrl, - Field( - description='Canonical URL of the responding brand agent entry whose response-signing key verifies this envelope.' - ), - ] - request_hash: Annotated[ - str, - Field( - description='sha256: prefix plus unpadded base64url SHA-256 of the canonical request-binding object for this call.', - pattern='^sha256:[A-Za-z0-9_-]{43}$', - ), - ] - iat: Annotated[int, Field(description='Issued-at time as Unix epoch seconds.', ge=0)] - exp: Annotated[ - int, - Field( - description='Expiration time as Unix epoch seconds. Online verifiers reject envelopes after this time, allowing only implementation-defined clock skew.', - ge=0, - ), - ] - response: VerifyBrandClaimsSignedSuccessPayload - - -class VerifyBrandClaimsSignedResponse(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - protected: Annotated[ - str, - Field( - description='Base64url-encoded JWS protected header. The decoded header MUST include alg, kid, and typ: adcp-response-payload+jws, and MUST NOT include the RFC 7797 b64 header. Verifiers enforce the key purpose by resolving kid to a JWK with adcp_use: response-signing.', - pattern='^[A-Za-z0-9_-]+$', - ), - ] - payload: Annotated[ - VerifyBrandClaimsPayload, - Field( - description='Decoded signed payload. Signers compute the JWS payload bytes from the RFC 8785/JCS canonicalization of this object.' - ), - ] - signature: Annotated[ - str, - Field( - description='Base64url-encoded JWS signature over the protected header and canonicalized payload.', - pattern='^[A-Za-z0-9_-]+$', - ), - ] - - -class VerifyBrandClaimsResponseBulk(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - results: Annotated[ - list[ResultEntry], - Field( - description="Per-claim results, positionally aligned with the request's claims.", - min_length=1, - ), - ] - signed_response: Annotated[ - VerifyBrandClaimsSignedResponse, - Field( - description='Payload-envelope JWS attesting the canonical bulk success response for verify_brand_claims. The signed payload response MUST match the unsigned task-body fields on this response, excluding signed_response and protocol/version envelope fields.' - ), - ] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class VerifyBrandClaimsErrorResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - errors: Annotated[list[error_1.Error], Field(min_length=1)] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -VerifyBrandClaimsResponse = VerifyBrandClaimsResponseBulk | VerifyBrandClaimsErrorResponse diff --git a/src/adcp/types/generated_poc/collection/__init__.py b/src/adcp/types/generated_poc/collection/__init__.py deleted file mode 100644 index 862adb7d8..000000000 --- a/src/adcp/types/generated_poc/collection/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# generated by datamodel-codegen: -# filename: .schema_temp -# timestamp: 2026-04-16T02:55:59+00:00 diff --git a/src/adcp/types/generated_poc/collection/base_collection_source.py b/src/adcp/types/generated_poc/collection/base_collection_source.py deleted file mode 100644 index fbc4c3806..000000000 --- a/src/adcp/types/generated_poc/collection/base_collection_source.py +++ /dev/null @@ -1,107 +0,0 @@ -# generated by datamodel-codegen: -# filename: collection/base_collection_source.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..enums import distribution_identifier_type -from ..enums import genre_taxonomy as genre_taxonomy_1 - - -class BaseCollectionSource2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - selection_type: Annotated[ - Literal['publisher_collections'], - Field( - description='Discriminator indicating selection by specific collection IDs within a publisher' - ), - ] = 'publisher_collections' - publisher_domain: Annotated[ - str, - Field( - description="Domain where publisher's adagents.json is hosted", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - collection_ids: Annotated[ - list[str], - Field( - description="Specific collection IDs from the publisher's adagents.json", min_length=1 - ), - ] - - -class Identifier(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Annotated[ - distribution_identifier_type.DistributionIdentifierType, - Field(description='Type of distribution identifier'), - ] - value: Annotated[str, Field(description='The identifier value')] - - -class BaseCollectionSource1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - selection_type: Annotated[ - Literal['distribution_ids'], - Field( - description='Discriminator indicating selection by platform-independent distribution identifiers' - ), - ] = 'distribution_ids' - identifiers: Annotated[ - list[Identifier], - Field( - description='Platform-independent identifiers (imdb_id, gracenote_id, eidr_id, etc.). Each identifier uniquely identifies a collection across all publishers. platform_channel_id is not allowed here: its identity is the tuple (publisher_domain, value), which this bare {type, value} shape cannot carry.', - min_length=1, - ), - ] - - -class BaseCollectionSource3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - selection_type: Annotated[ - Literal['publisher_genres'], - Field(description='Discriminator indicating selection by genre within a publisher'), - ] = 'publisher_genres' - publisher_domain: Annotated[ - str, - Field( - description="Domain where publisher's adagents.json is hosted", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - genres: Annotated[ - list[str], - Field( - description="Genre values to match against the publisher's collections", min_length=1 - ), - ] - genre_taxonomy: Annotated[ - genre_taxonomy_1.GenreTaxonomy, - Field( - description="Taxonomy for the genre values. Required so sellers can interpret genre strings unambiguously. Use 'custom' for free-form values negotiated out of band." - ), - ] - - -BaseCollectionSource = Annotated[ - BaseCollectionSource1 | BaseCollectionSource2 | BaseCollectionSource3, - Field( - description='A source of collections for a collection list. Supports three selection patterns: distribution identifiers (cross-publisher), publisher-specific collection IDs, or publisher-specific genres.', - discriminator='selection_type', - title='Base Collection Source', - ), -] diff --git a/src/adcp/types/generated_poc/collection/collection_list.py b/src/adcp/types/generated_poc/collection/collection_list.py deleted file mode 100644 index cc082bc3b..000000000 --- a/src/adcp/types/generated_poc/collection/collection_list.py +++ /dev/null @@ -1,69 +0,0 @@ -# generated by datamodel-codegen: -# filename: collection/collection_list.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - -from ..core import account_ref, brand_ref -from . import base_collection_source, collection_list_filters - - -class CollectionList(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - list_id: Annotated[str, Field(description='Unique identifier for this collection list')] - name: Annotated[str, Field(description='Human-readable name for the list')] - description: Annotated[str | None, Field(description="Description of the list's purpose")] = ( - None - ) - account: Annotated[ - account_ref.AccountReference | None, - Field( - description='Account that owns this list. Returned as account_id form (seller-assigned identifier).' - ), - ] = None - base_collections: Annotated[ - list[base_collection_source.BaseCollectionSource] | None, - Field( - description="Array of collection sources to evaluate. Each entry is a discriminated union: distribution_ids (platform-independent identifiers), publisher_collections (publisher_domain + collection_ids), or publisher_genres (publisher_domain + genres). If omitted, queries the agent's entire collection database." - ), - ] = None - filters: Annotated[ - collection_list_filters.CollectionListFilters | None, - Field(description='Dynamic filters applied when resolving the list'), - ] = None - brand: Annotated[ - brand_ref.BrandReference | None, - Field( - description='Brand reference used to automatically apply appropriate rules. Resolved to full brand identity at execution time.' - ), - ] = None - webhook_url: Annotated[ - AnyUrl | None, - Field(description='URL to receive notifications when the resolved list changes'), - ] = None - cache_duration_hours: Annotated[ - SchemaInt | None, - Field( - description='Recommended cache duration for resolved list. Consumers should re-fetch after this period. Defaults to 168 (one week) because collection metadata changes less frequently than property metadata.', - ge=1, - ), - ] = 168 - created_at: Annotated[AwareDatetime | None, Field(description='When the list was created')] = ( - None - ) - updated_at: Annotated[ - AwareDatetime | None, Field(description='When the list was last modified') - ] = None - collection_count: Annotated[ - SchemaInt | None, - Field( - description='Number of collections in the resolved list (at time of last resolution)' - ), - ] = None diff --git a/src/adcp/types/generated_poc/collection/collection_list_changed_webhook.py b/src/adcp/types/generated_poc/collection/collection_list_changed_webhook.py deleted file mode 100644 index 763b93045..000000000 --- a/src/adcp/types/generated_poc/collection/collection_list_changed_webhook.py +++ /dev/null @@ -1,60 +0,0 @@ -# generated by datamodel-codegen: -# filename: collection/collection_list_changed_webhook.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field - -from ..core import ext as ext_1 - - -class ChangeSummary(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - collections_added: Annotated[ - SchemaInt | None, Field(description='Number of collections added since last resolution') - ] = None - collections_removed: Annotated[ - SchemaInt | None, Field(description='Number of collections removed since last resolution') - ] = None - total_collections: Annotated[ - SchemaInt | None, Field(description='Total collections in the resolved list') - ] = None - - -class CollectionListChangedWebhook(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - idempotency_key: Annotated[ - str, - Field( - description='Sender-generated key stable across retries of the same webhook event. Governance agents MUST generate a cryptographically random value (UUID v4 recommended) per distinct list-change event and reuse the same key on every retry. Recipients MUST dedupe by this key, scoped to the authenticated sender identity (HMAC secret or Bearer credential) — keys from different governance agents are independent.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - event: Annotated[Literal['collection_list_changed'], Field(description='The event type')] = 'collection_list_changed' - list_id: Annotated[str, Field(description='ID of the collection list that changed')] - list_name: Annotated[str | None, Field(description='Name of the collection list')] = None - change_summary: Annotated[ - ChangeSummary | None, Field(description='Summary of changes to the resolved list') - ] = None - resolved_at: Annotated[AwareDatetime, Field(description='When the list was re-resolved')] - cache_valid_until: Annotated[ - AwareDatetime | None, - Field(description='When the consumer should refresh from the governance agent'), - ] = None - signature: Annotated[ - str, - Field( - description='HMAC-SHA256 webhook signature over {unix_timestamp}.{raw_http_body_bytes} using the secret exchanged out-of-band when the seller registered with the governance agent. Recipients MUST verify against the X-ADCP-Signature and X-ADCP-Timestamp headers using timing-safe comparison and MUST reject requests where |now - timestamp| > 300 seconds. The body copy of this field is a convenience only — the headers are authoritative. See docs/building/implementation/security#webhook-security.' - ), - ] - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/collection/collection_list_filters.py b/src/adcp/types/generated_poc/collection/collection_list_filters.py deleted file mode 100644 index a56006edd..000000000 --- a/src/adcp/types/generated_poc/collection/collection_list_filters.py +++ /dev/null @@ -1,81 +0,0 @@ -# generated by datamodel-codegen: -# filename: collection/collection_list_filters.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..core import content_rating -from ..enums import collection_kind, distribution_identifier_type -from ..enums import genre_taxonomy as genre_taxonomy_1 -from ..enums import production_quality as production_quality_1 - - -class ExcludeDistributionId(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Annotated[ - distribution_identifier_type.DistributionIdentifierType, - Field(description='Type of distribution identifier'), - ] - value: Annotated[str, Field(description='The identifier value')] - - -class CollectionListFilters(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - content_ratings_exclude: Annotated[ - list[content_rating.ContentRating] | None, - Field( - description="Exclude collections with any of these content ratings (OR logic). This is a metadata filter on the collection's declared content_rating field — it does not evaluate episode content.", - min_length=1, - ), - ] = None - content_ratings_include: Annotated[ - list[content_rating.ContentRating] | None, - Field( - description='Include only collections with any of these content ratings (OR logic). Collections without a declared content_rating are excluded.', - min_length=1, - ), - ] = None - genres_exclude: Annotated[ - list[str] | None, - Field( - description='Exclude collections tagged with any of these genres (OR logic). Values are interpreted against genre_taxonomy when present.', - min_length=1, - ), - ] = None - genres_include: Annotated[ - list[str] | None, - Field( - description='Include only collections with any of these genres (OR logic). Collections without genre metadata are excluded. Values are interpreted against genre_taxonomy when present.', - min_length=1, - ), - ] = None - genre_taxonomy: Annotated[ - genre_taxonomy_1.GenreTaxonomy | None, - Field( - description='Taxonomy for genre filter values. When present, genres_include and genres_exclude values are interpreted as taxonomy IDs.' - ), - ] = None - kinds: Annotated[ - list[collection_kind.CollectionKind] | None, - Field(description='Filter to these collection kinds', min_length=1), - ] = None - exclude_distribution_ids: Annotated[ - list[ExcludeDistributionId] | None, - Field( - description='Always exclude collections with these distribution identifiers', - min_length=1, - ), - ] = None - production_quality: Annotated[ - list[production_quality_1.ProductionQuality] | None, - Field(description='Filter by production quality tier', min_length=1), - ] = None diff --git a/src/adcp/types/generated_poc/collection/create_collection_list_request.py b/src/adcp/types/generated_poc/collection/create_collection_list_request.py deleted file mode 100644 index 7b3411883..000000000 --- a/src/adcp/types/generated_poc/collection/create_collection_list_request.py +++ /dev/null @@ -1,59 +0,0 @@ -# generated by datamodel-codegen: -# filename: collection/create_collection_list_request.json -# timestamp: 2026-10-02T23:10:32+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import ConfigDict, Field - -from ..core import account_ref, brand_ref -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.version_envelope import AdcpVersionEnvelope -from . import base_collection_source, collection_list_filters - - -class CreateCollectionListRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - account: Annotated[ - account_ref.AccountReference | None, - Field( - description='Account that will own the list. Pass a natural key (brand, operator, optional sandbox) or a seller-assigned account_id from list_accounts. When omitted, this task applies its task-local single-account shortcut: if exactly one account is accessible to the authenticated caller, the seller may assign the list to that account; otherwise it MUST return an account-required or ambiguous-account error. Omission MUST NOT mean an undocumented credential-local default account.' - ), - ] = None - name: Annotated[str, Field(description='Human-readable name for the list')] - description: Annotated[str | None, Field(description="Description of the list's purpose")] = ( - None - ) - base_collections: Annotated[ - list[base_collection_source.BaseCollectionSource] | None, - Field( - description="Array of collection sources to evaluate. Each entry is a discriminated union: distribution_ids (platform-independent identifiers), publisher_collections (publisher_domain + collection_ids), or publisher_genres (publisher_domain + genres). If omitted, queries the agent's entire collection database.", - min_length=1, - ), - ] = None - filters: Annotated[ - collection_list_filters.CollectionListFilters | None, - Field(description='Dynamic filters to apply when resolving the list'), - ] = None - brand: Annotated[ - brand_ref.BrandReference | None, - Field( - description='Brand reference. When provided, the agent automatically applies appropriate rules based on brand characteristics (industry, target_audience, etc.). Resolved at execution time.' - ), - ] = None - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for this request. Prevents duplicate collection list creation on retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/collection/create_collection_list_response.py b/src/adcp/types/generated_poc/collection/create_collection_list_response.py deleted file mode 100644 index cf29cfe8f..000000000 --- a/src/adcp/types/generated_poc/collection/create_collection_list_response.py +++ /dev/null @@ -1,38 +0,0 @@ -# generated by datamodel-codegen: -# filename: collection/create_collection_list_response.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import ConfigDict, Field, StrictBool - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from . import collection_list - - -class CreateCollectionListResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - list: Annotated[ - collection_list.CollectionList, Field(description='The created collection list') - ] - auth_token: Annotated[ - str, - Field( - description='Token that authorizes sellers to fetch this list via get_collection_list. Only returned at creation time — buyers MUST store it in a secret manager. Scoped to this one list_id; MUST NOT be reused across lists. Governance agents MUST issue a distinct token per seller so per-relationship revocation is possible. Tokens MUST NOT be logged, appear in cache keys, or echo in error responses. delete_collection_list MUST revoke the token immediately; compromise-driven revocation MUST also signal cache invalidation to sellers (reduced cache_valid_until or a list-changed webhook). See Security considerations in docs/governance/collection/tasks/collection_lists.' - ), - ] - replayed: Annotated[ - StrictBool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. `replayed` appears only when the request actually resolved through the idempotency cache. Pure reads may ignore an optional `idempotency_key`; when a seller voluntarily caches keyed reads, those responses use the same replay indicator and full cache contract." - ), - ] = False - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/collection/delete_collection_list_request.py b/src/adcp/types/generated_poc/collection/delete_collection_list_request.py deleted file mode 100644 index 4c4f726bf..000000000 --- a/src/adcp/types/generated_poc/collection/delete_collection_list_request.py +++ /dev/null @@ -1,38 +0,0 @@ -# generated by datamodel-codegen: -# filename: collection/delete_collection_list_request.json -# timestamp: 2026-10-02T23:10:32+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import ConfigDict, Field - -from ..core import account_ref -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.version_envelope import AdcpVersionEnvelope - - -class DeleteCollectionListRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - list_id: Annotated[str, Field(description='ID of the collection list to delete')] - account: Annotated[ - account_ref.AccountReference | None, - Field( - description='Account that owns the list. Required when the authenticated agent has access to multiple accounts; optional otherwise.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for at-most-once execution. If a request with the same key has already been processed, the server returns the original response without re-processing. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] diff --git a/src/adcp/types/generated_poc/collection/delete_collection_list_response.py b/src/adcp/types/generated_poc/collection/delete_collection_list_response.py deleted file mode 100644 index 72c556d11..000000000 --- a/src/adcp/types/generated_poc/collection/delete_collection_list_response.py +++ /dev/null @@ -1,30 +0,0 @@ -# generated by datamodel-codegen: -# filename: collection/delete_collection_list_response.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import ConfigDict, Field, StrictBool - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope - - -class DeleteCollectionListResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - deleted: Annotated[StrictBool, Field(description='Whether the list was successfully deleted')] - list_id: Annotated[str, Field(description='ID of the deleted list')] - replayed: Annotated[ - StrictBool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. `replayed` appears only when the request actually resolved through the idempotency cache. Pure reads may ignore an optional `idempotency_key`; when a seller voluntarily caches keyed reads, those responses use the same replay indicator and full cache contract." - ), - ] = False - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/collection/get_collection_list_request.py b/src/adcp/types/generated_poc/collection/get_collection_list_request.py deleted file mode 100644 index eb1beddd0..000000000 --- a/src/adcp/types/generated_poc/collection/get_collection_list_request.py +++ /dev/null @@ -1,56 +0,0 @@ -# generated by datamodel-codegen: -# filename: collection/get_collection_list_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictBool - -from ..core import account_ref -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.version_envelope import AdcpVersionEnvelope - - -class Pagination(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - max_results: Annotated[ - SchemaInt | None, - Field(description='Maximum number of collections to return per page', ge=1, le=10000), - ] = 1000 - cursor: Annotated[ - str | None, - Field(description='Opaque cursor from a previous response to fetch the next page'), - ] = None - - -class GetCollectionListRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - list_id: Annotated[str, Field(description='ID of the collection list to retrieve')] - account: Annotated[ - account_ref.AccountReference | None, - Field( - description='Account that owns the list. Required when the authenticated agent has access to multiple accounts and the list_id is not globally unique within that scope; optional otherwise.' - ), - ] = None - resolve: Annotated[ - StrictBool | None, - Field( - description='Whether to apply filters and return resolved collections (default: true)' - ), - ] = True - pagination: Annotated[ - Pagination | None, - Field( - description='Pagination parameters. Uses higher limits than standard pagination because collection lists can contain thousands of entries.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/collection/get_collection_list_response.py b/src/adcp/types/generated_poc/collection/get_collection_list_response.py deleted file mode 100644 index 582015adf..000000000 --- a/src/adcp/types/generated_poc/collection/get_collection_list_response.py +++ /dev/null @@ -1,107 +0,0 @@ -# generated by datamodel-codegen: -# filename: collection/get_collection_list_response.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - -from ..core import content_rating as content_rating_1 -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core import pagination_response -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import collection_kind, distribution_identifier_type -from ..enums import genre_taxonomy as genre_taxonomy_1 -from . import collection_list - - -class DistributionId(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Annotated[ - distribution_identifier_type.DistributionIdentifierType, - Field(description='Type of distribution identifier'), - ] - value: Annotated[str, Field(description='The identifier value')] - - -class CoverageGap(DistributionId): - pass - - -class Collection(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - collection_rid: Annotated[ - str | None, - Field( - description='Registry-assigned stable identifier for this collection. Present when the collection has been registered in the collection registry.' - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description='Domain of the adagents.json that declares this collection — the canonical namespace for collection_id. SHOULD be returned whenever known: together with collection_id it is the domain-qualified identity that makes a resolved row matchable when no platform-independent distribution identifier exists (e.g. a channel known only by a platform_channel_id).' - ), - ] = None - collection_id: Annotated[ - str | None, - Field( - description="Publisher-assigned collection ID within publisher_domain's adagents.json. SHOULD be returned whenever known; identity is the tuple (publisher_domain, collection_id)." - ), - ] = None - name: Annotated[str, Field(description='Human-readable collection name')] - distribution_ids: Annotated[ - list[DistributionId] | None, - Field(description='Platform-independent identifiers for cross-publisher matching'), - ] = None - content_rating: Annotated[ - content_rating_1.ContentRating | None, - Field(description='Baseline content rating for this collection'), - ] = None - genre: Annotated[list[str] | None, Field(description='Genre tags for this collection')] = None - genre_taxonomy: Annotated[ - genre_taxonomy_1.GenreTaxonomy | None, Field(description='Taxonomy system for genre values') - ] = None - kind: collection_kind.CollectionKind | None = None - - -class GetCollectionListResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - list: Annotated[ - collection_list.CollectionList, - Field(description='The collection list metadata (always returned)'), - ] - collections: Annotated[ - list[Collection] | None, - Field( - description='Resolved collections that passed filters (if resolve=true). Each entry contains identification and key metadata for seller matching.' - ), - ] = None - pagination: pagination_response.PaginationResponse | None = None - resolved_at: Annotated[ - AwareDatetime | None, Field(description='When the list was resolved') - ] = None - cache_valid_until: Annotated[ - AwareDatetime | None, - Field( - description='Cache expiration timestamp. Re-fetch the list after this time to get updated collections.' - ), - ] = None - coverage_gaps: Annotated[ - dict[str, list[CoverageGap]] | None, - Field( - description="Collections included in the list despite missing metadata for a filtered dimension. Maps dimension name (e.g., 'genre', 'content_rating') to arrays of distribution identifiers for collections not covered. Only present when filters are applied and some collections lack the filtered metadata." - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/collection/list_collection_lists_request.py b/src/adcp/types/generated_poc/collection/list_collection_lists_request.py deleted file mode 100644 index 864877576..000000000 --- a/src/adcp/types/generated_poc/collection/list_collection_lists_request.py +++ /dev/null @@ -1,33 +0,0 @@ -# generated by datamodel-codegen: -# filename: collection/list_collection_lists_request.json -# timestamp: 2026-10-02T23:10:32+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import ConfigDict, Field - -from ..core import account_ref -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core import pagination_request -from ..core.version_envelope import AdcpVersionEnvelope - - -class ListCollectionListsRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - account: Annotated[ - account_ref.AccountReference | None, - Field( - description='Filter to lists owned by this account. When omitted, returns lists across all accounts accessible to the authenticated agent.' - ), - ] = None - name_contains: Annotated[ - str | None, Field(description='Filter to lists whose name contains this string') - ] = None - pagination: pagination_request.PaginationRequest | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/collection/list_collection_lists_response.py b/src/adcp/types/generated_poc/collection/list_collection_lists_response.py deleted file mode 100644 index 679b2c021..000000000 --- a/src/adcp/types/generated_poc/collection/list_collection_lists_response.py +++ /dev/null @@ -1,29 +0,0 @@ -# generated by datamodel-codegen: -# filename: collection/list_collection_lists_response.json -# timestamp: 2026-05-22T13:15:12+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import ConfigDict, Field - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core import pagination_response -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from . import collection_list - - -class ListCollectionListsResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - lists: Annotated[ - list[collection_list.CollectionList], - Field(description='Array of collection lists (metadata only, not resolved collections)'), - ] - pagination: pagination_response.PaginationResponse | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/collection/update_collection_list_request.py b/src/adcp/types/generated_poc/collection/update_collection_list_request.py deleted file mode 100644 index fe0561e62..000000000 --- a/src/adcp/types/generated_poc/collection/update_collection_list_request.py +++ /dev/null @@ -1,63 +0,0 @@ -# generated by datamodel-codegen: -# filename: collection/update_collection_list_request.json -# timestamp: 2026-10-02T23:10:32+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import AnyUrl, ConfigDict, Field - -from ..core import account_ref, brand_ref -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.version_envelope import AdcpVersionEnvelope -from . import base_collection_source, collection_list_filters - - -class UpdateCollectionListRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - list_id: Annotated[str, Field(description='ID of the collection list to update')] - account: Annotated[ - account_ref.AccountReference | None, - Field( - description='Account that owns the list. Required when the authenticated agent has access to multiple accounts; optional otherwise.' - ), - ] = None - name: Annotated[str | None, Field(description='New name for the list')] = None - description: Annotated[str | None, Field(description='New description')] = None - base_collections: Annotated[ - list[base_collection_source.BaseCollectionSource] | None, - Field( - description='Complete replacement for the base collections list (not a patch). Each entry is a discriminated union: distribution_ids (platform-independent identifiers), publisher_collections (publisher_domain + collection_ids), or publisher_genres (publisher_domain + genres).' - ), - ] = None - filters: Annotated[ - collection_list_filters.CollectionListFilters | None, - Field(description='Complete replacement for the filters (not a patch)'), - ] = None - brand: Annotated[ - brand_ref.BrandReference | None, - Field( - description='Update brand reference. Resolved to full brand identity at execution time.' - ), - ] = None - webhook_url: Annotated[ - AnyUrl | None, - Field( - description='Update the webhook URL for list change notifications (set to empty string to remove). Governance agents MUST validate this URL against SSRF per docs/building/implementation/security#webhook-url-validation-ssrf.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for at-most-once execution. If a request with the same key has already been processed, the server returns the original response without re-processing. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] diff --git a/src/adcp/types/generated_poc/collection/update_collection_list_response.py b/src/adcp/types/generated_poc/collection/update_collection_list_response.py deleted file mode 100644 index 72bc15fad..000000000 --- a/src/adcp/types/generated_poc/collection/update_collection_list_response.py +++ /dev/null @@ -1,32 +0,0 @@ -# generated by datamodel-codegen: -# filename: collection/update_collection_list_response.json -# timestamp: 2026-10-03T15:09:44+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import ConfigDict, Field, StrictBool - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from . import collection_list - - -class UpdateCollectionListResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - list: Annotated[ - collection_list.CollectionList, Field(description='The updated collection list') - ] - replayed: Annotated[ - StrictBool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. `replayed` appears only when the request actually resolved through the idempotency cache. Pure reads may ignore an optional `idempotency_key`; when a seller voluntarily caches keyed reads, those responses use the same replay indicator and full cache contract." - ), - ] = False - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/compliance/__init__.py b/src/adcp/types/generated_poc/compliance/__init__.py deleted file mode 100644 index b6f833879..000000000 --- a/src/adcp/types/generated_poc/compliance/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# generated by datamodel-codegen: -# filename: .schema_temp -# timestamp: 2026-04-02T22:34:46+00:00 diff --git a/src/adcp/types/generated_poc/compliance/comply_test_controller_request.py b/src/adcp/types/generated_poc/compliance/comply_test_controller_request.py deleted file mode 100644 index 1e06cf4a3..000000000 --- a/src/adcp/types/generated_poc/compliance/comply_test_controller_request.py +++ /dev/null @@ -1,516 +0,0 @@ -# generated by datamodel-codegen: -# filename: compliance/comply_test_controller_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from datetime import date -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarStr -from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat - -from ..core import brand_ref, committed_metric -from ..core import context as context_1 -from ..core import delivery_metrics, duration -from ..core import ext as ext_1 -from ..core import vendor_metric_id, vendor_metric_value -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import creative_event_reason_code -from ..enums import reach_unit as reach_unit_1 -from . import task_completion_data - - -class Operation(StrEnum): - seed_inaccessible_item = 'seed_inaccessible_item' - query_eligibility = 'query_eligibility' - advance_time = 'advance_time' - recreate_catalog = 'recreate_catalog' - prepare = 'prepare' - expire_proposal = 'expire_proposal' - publish_zero_row = 'publish_zero_row' - publish_nonempty = 'publish_nonempty' - restate_snapshot = 'restate_snapshot' - restate_after_received = 'restate_after_received' - omit_obligation = 'omit_obligation' - advance_past_status_deadline = 'advance_past_status_deadline' - advance_past_escalation = 'advance_past_escalation' - publish_official_adjustment = 'publish_official_adjustment' - probe_scheduler_dst = 'probe_scheduler_dst' - suppress_readiness = 'suppress_readiness' - advance_within_retention = 'advance_within_retention' - revoke_access = 'revoke_access' - publish_adjustment = 'publish_adjustment' - - -class TargetHealth(StrEnum): - delayed = 'delayed' - action_required = 'action_required' - - -class AdvanceTo(StrEnum): - within_grace = 'within_grace' - past_grace = 'past_grace' - - -class PurgeKind(StrEnum): - soft = 'soft' - hard = 'hard' - - -class ReportedSpend(AdCPBaseModel): - amount: Annotated[StrictFloat, Field(ge=0.0)] - currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] - - -class Kind(StrEnum): - cumulative = 'cumulative' - period = 'period' - rolling = 'rolling' - - -class Arm(StrEnum): - submitted = 'submitted' - completed = 'completed' - input_required = 'input-required' - rejected = 'rejected' - - -class Suggestion(ScalarStr): - __slots__ = () - _constraints = {'max_length': 1000, 'min_length': 1} - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - sandbox: Annotated[ - Literal[True], - Field( - description='MUST be true. The seller MUST verify the targeted account is sandbox by looking up the persisted account record, not by trusting this field. A request asserting sandbox: false schema-rejects before reaching the seller — defense-in-depth on top of the per-request gate.' - ), - ] - - -class ReachWindow(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[ - Kind, - Field( - description='Window kind. `cumulative` — no period field required. `period` and `rolling` — period field REQUIRED.' - ), - ] - period: Annotated[ - duration.Duration | None, - Field( - description="Duration of the measurement window. REQUIRED when kind is `period` or `rolling`. Matches the `period` field in delivery-metrics.json's reach_window block." - ), - ] = None - - -class Metric(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - metric_id: vendor_metric_id.VendorMetricId - - -class NotYetMeasurableVendorMetric(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: brand_ref.BrandReference - metric_id: vendor_metric_id.VendorMetricId - qualifier: Annotated[ - committed_metric.Qualifier | None, - Field( - description='Qualifier for the exact committed vendor-metric row whose measurement is deferred.' - ), - ] = None - - -class NotYetMeasurableVendorMetricsByPackageItem(NotYetMeasurableVendorMetric): - pass - - -class Params(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - creative_id: Annotated[ - str | None, - Field( - description='Creative to transition (force_creative_status) or seed (seed_creative).' - ), - ] = None - account_id: Annotated[ - str | None, - Field( - description='Account to transition, simulate, or seed. Used by force_account_status, simulate_budget_spend, and seed_account.' - ), - ] = None - media_buy_id: Annotated[ - str | None, - Field( - description='Media buy to transition or purge (force_media_buy_status, force_media_buy_purge), simulate (simulate_delivery, simulate_budget_spend), or seed (seed_media_buy).' - ), - ] = None - session_id: Annotated[ - str | None, Field(description='Session to transition. Used by force_session_status.') - ] = None - product_id: Annotated[ - str | None, - Field( - description='Product to seed or prepare for deterministic compact lifecycle testing. Used by seed_product, seed_pricing_option, compact_product_lifecycle_probe prepare, and compact_direct_buy_lifecycle_probe prepare.' - ), - ] = None - proposal_id: Annotated[ - str | None, - Field( - description='Committed proposal whose hold the compact_product_lifecycle_probe expires.', - min_length=1, - ), - ] = None - pricing_option_id: Annotated[ - str | None, - Field( - description='Pricing option to seed, scoped to a product. Used by seed_pricing_option.' - ), - ] = None - plan_id: Annotated[str | None, Field(description='Plan to seed. Used by seed_plan.')] = None - rights_id: Annotated[ - str | None, Field(description='Rights grant to seed. Used by seed_rights_grant.') - ] = None - fixture: Annotated[ - dict[str, Any] | None, - Field( - description='Arbitrary fixture payload carried by seed_* scenarios. Shape matches the domain object the seed scenario creates (account, product, creative, plan, media buy, pricing option, rights grant). Seller MAY reject malformed fixtures with INVALID_PARAMS. Kept permissive so storyboard authors can declare the minimum shape each test needs without the spec locking down every field.' - ), - ] = None - operation: Annotated[ - Operation | None, - Field( - description="Scenario-specific probe operation. catalog_item_availability_probe uses seed_inaccessible_item, query_eligibility, advance_time, and recreate_catalog. compact_product_lifecycle_probe uses prepare to make one seeded product's compact proposal, acceptance, operational-control, and MediaBuy readback path deterministic and expire_proposal to advance strictly beyond a committed proposal's stored expires_at and process the hold lapse. compact_direct_buy_lifecycle_probe uses prepare to make one seeded product's list, direct-purchase, operational-control, and readback path deterministic. reporting_core_lifecycle_probe uses prepare, advance_time, publish_zero_row, publish_nonempty, restate_snapshot, restate_after_received, omit_obligation, advance_past_status_deadline, and advance_past_escalation to exercise obligation availability, health deadlines, explicit reporting, provisional restatement, stale-received grace, buyer-side missing-obligation detection, counted consumer-status silence, and consumer-mismatch escalation. advance_past_status_deadline moves the clock strictly past expected_at plus the advertised automated_recovery_window_seconds without recording any consumer status, so obligation_counts.consumer_status_pending is gradable; advance_past_escalation moves it strictly past the open CONSUMER_STATUS_MISMATCH issue opened_at plus the advertised consumer_mismatch_escalation_seconds and returns both that boundary and any still-future stale_received_grace_deadline, so the escalation precedence rule is gradable. restate_snapshot publishes a new snapshot revision that immediately supersedes the current snapshot for the same logical slice and is invalid when the current revision is official. restate_after_received does the same restatement but only against the revision named in received_reporting_revision_id, so the stale-received grace projection can be graded live; it returns the grace deadline and advance_to positions the virtual clock inside or past it. Reliable Reporting tier probes use prepare plus publish_official_adjustment, probe_scheduler_dst, suppress_readiness, advance_within_retention, revoke_access, or publish_adjustment to seed deterministic Core-integrity, Managed Delivery, and Reconciled Billing lifecycle evidence." - ), - ] = None - target_health: Annotated[ - TargetHealth | None, - Field( - description='Clock-derived health boundary selected by reporting_core_lifecycle_probe advance_time. The controller chooses the corresponding deterministic target time; callers do not supply timestamps.' - ), - ] = None - received_reporting_revision_id: Annotated[ - str | None, - Field( - description="Revision the caller currently reports as received, required by reporting_core_lifecycle_probe restate_after_received. The seller MUST reject the operation unless this names the caller's current required revision for the fixture obligation, and a seller advertising consumer_status_task MUST additionally require a current received consumer status from this caller naming it. This binds the restatement to a real prior read instead of restating into a vacuum.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - advance_to: Annotated[ - AdvanceTo | None, - Field( - description='Where reporting_core_lifecycle_probe restate_after_received leaves the virtual clock relative to the returned stale_received_grace_deadline. within_grace grades the delayed projection; past_grace grades the escalation to action_required. Repeating the operation is convergent: it reuses the committed restatement and only moves the clock.' - ), - ] = AdvanceTo.within_grace - catalog_id: Annotated[ - str | None, - Field( - description='Catalog operated on by catalog_item_availability_probe.', - max_length=255, - min_length=1, - ), - ] = None - catalog_generation: Annotated[ - str | None, - Field( - description='Catalog incarnation operated on by catalog_item_availability_probe.', - max_length=255, - min_length=1, - ), - ] = None - item_id: Annotated[ - str | None, - Field( - description='Catalog item operated on by catalog_item_availability_probe.', - max_length=255, - min_length=1, - ), - ] = None - target_time: Annotated[ - AwareDatetime | None, - Field( - description='Deterministic sandbox clock target used by catalog_item_availability_probe advance_time.' - ), - ] = None - status: Annotated[ - str | None, - Field( - description="Target status for the resource. Type depends on scenario: creative-status for force_creative_status, account-status for force_account_status, media-buy-status for force_media_buy_status. For force_session_status, must be 'complete' or 'terminated'." - ), - ] = None - rejection_reason: Annotated[ - str | None, - Field( - description='Reason for rejection. Used by force_creative_status and force_media_buy_status when status = rejected.' - ), - ] = None - purge_kind: Annotated[ - PurgeKind | None, - Field( - description='Purge mode for force_creative_purge. soft retains a list_creatives tombstone; hard removes the creative entirely.' - ), - ] = None - reason_code: Annotated[ - creative_event_reason_code.CreativeEventReasonCode | None, - Field( - description='Creative lifecycle reason code for force_creative_status and force_creative_purge.' - ), - ] = None - reason_detail: Annotated[ - str | None, - Field( - description='Human-readable detail for force_creative_status and force_creative_purge.' - ), - ] = None - termination_reason: Annotated[ - str | None, - Field( - description='Reason for termination (e.g., session_timeout, host_terminated, policy_violation). Used by force_session_status when status = terminated.' - ), - ] = None - impressions: Annotated[ - SchemaInt | None, - Field(description='Impressions to simulate. Used by simulate_delivery.', ge=0), - ] = None - clicks: Annotated[ - SchemaInt | None, Field(description='Clicks to simulate. Used by simulate_delivery.', ge=0) - ] = None - plays: Annotated[ - SchemaInt | None, - Field( - description='Raw DOOH or broadcast plays to add to delivery. Used by simulate_delivery for single-package buys; sellers MUST surface the cumulative value at totals.plays and by_package[0].plays in the next get_media_buy_delivery response. Multi-package simulations require a future package-scoped form and MUST reject this media-buy-scoped field.', - ge=0, - ), - ] = None - dooh_metrics: Annotated[ - delivery_metrics.DoohMetrics | None, - Field( - description='DOOH delivery detail to inject. Used by simulate_delivery for single-package buys; the latest injected block replaces the previous simulated DOOH detail and MUST surface at totals.dooh_metrics and by_package[0].dooh_metrics. Multi-package simulations require a future package-scoped form and MUST reject this media-buy-scoped field.' - ), - ] = None - conversions: Annotated[ - SchemaInt | None, - Field(description='Conversions to simulate. Used by simulate_delivery.', ge=0), - ] = None - delivery_date: Annotated[ - date | None, - Field( - description="Calendar date, in the product's reporting_capabilities.timezone, attributable to this simulated delivery batch. Used by simulate_delivery to seed deterministic date-range tests. When get_media_buy_delivery supplies start_date or end_date, dated batches are included when delivery_date is greater than or equal to start_date and strictly less than end_date. Omit to preserve cumulative, unfiltered simulation behavior." - ), - ] = None - conversion_value: Annotated[ - StrictFloat | None, - Field( - description='Total attributed conversion value to simulate in the delivery reporting currency. Used by simulate_delivery.', - ge=0.0, - ), - ] = None - commissionable_value: Annotated[ - StrictFloat | None, - Field( - description='Settled attributed value eligible for revenue-share commission to simulate in the delivery reporting currency. Used by simulate_delivery.', - ge=0.0, - ), - ] = None - reported_spend: Annotated[ - ReportedSpend | None, - Field( - description='Spend as reported in delivery data. Does not affect budget. Used by simulate_delivery.' - ), - ] = None - reach: Annotated[ - StrictFloat | None, - Field( - description="Unique reach count to inject into the simulated delivery row. Used by simulate_delivery. The unit of measurement matches the reach_unit declared on the media buy's optimization goal. When supplied, sellers MUST surface this value at `totals.reach` in the next get_media_buy_delivery response.", - ge=0.0, - ), - ] = None - frequency: Annotated[ - StrictFloat | None, - Field( - description='Average frequency per reach unit to inject. Used by simulate_delivery. When supplied, sellers MUST surface this value at `totals.frequency`, including frequency-only delivery. The measurement window for this frequency value is declared in `reach_window` when also present.', - ge=0.0, - ), - ] = None - reach_unit: Annotated[ - reach_unit_1.ReachUnit | None, - Field( - description='Unit for an injected reach or frequency value. Used by simulate_delivery. When supplied with reach or frequency, sellers MUST surface it on the corresponding delivery row.' - ), - ] = None - reach_window: Annotated[ - ReachWindow | None, - Field( - description='Measurement window semantics to attach to the simulated reach and frequency values. Used by simulate_delivery. When present, sellers MUST populate `reach_window` on the delivery row so buyers can determine the window semantics (cumulative vs. period vs. rolling). Omitting this param produces a row with no `reach_window` — valid per schema but sum-unsafe. Mirrors the `reach_window` shape in delivery-metrics.json.' - ), - ] = None - viewability: Annotated[ - delivery_metrics.Viewability | None, - Field( - description='Viewability metrics to inject into simulated delivery. Uses the canonical delivery viewability shape, including viewed-seconds distributions. This media-buy-scoped form is valid only for a single-package buy, where the reference seller surfaces the same values at package and media-buy totals grain.' - ), - ] = None - vendor_metric_values: Annotated[ - list[vendor_metric_value.VendorMetricValue] | None, - Field( - description="Vendor-defined metric values to inject into the next delivery report. Used by simulate_delivery. The reference seller reconciles these rows against each package's seller-stamped committed_metrics contract." - ), - ] = None - vendor_metric_values_by_package: Annotated[ - dict[str, list[vendor_metric_value.VendorMetricValue]] | None, - Field( - description='Package-scoped vendor-defined values to inject, keyed by package_id. Used by simulate_delivery for multi-package buys. Sellers MUST use this form whenever more than one package could carry the same vendor/metric_id key; the legacy media-buy-scoped vendor_metric_values form is unambiguous only for a single-package buy.' - ), - ] = None - not_yet_measurable_vendor_metrics: Annotated[ - list[NotYetMeasurableVendorMetric] | None, - Field( - description='Committed vendor metrics that are not yet measurable for the simulated measurement window. Used by simulate_delivery to prove that a future metric is omitted from missing_metrics until its measurement window matures.' - ), - ] = None - not_yet_measurable_vendor_metrics_by_package: Annotated[ - dict[str, list[NotYetMeasurableVendorMetricsByPackageItem]] | None, - Field( - description="Package-scoped committed vendor metrics that are not yet measurable for the simulated window, keyed by package_id. Used by simulate_delivery for multi-package buys so a deferral on one package never suppresses another package's overdue gap." - ), - ] = None - spend_percentage: Annotated[ - StrictFloat | None, - Field( - description='Spend to this percentage of budget (0–100). Used by simulate_budget_spend.', - ge=0.0, - le=100.0, - ), - ] = None - arm: Annotated[ - Arm | None, - Field( - description="Response arm for the next forced operation call. Used by force_create_media_buy_arm, force_get_products_arm, force_get_signals_arm, and force_get_creative_features_arm. 'submitted' is supported for all four operations; get_creative_features also supports 'completed' with a deterministic result; create_media_buy also supports 'input-required'; get_products also supports 'rejected'. Async completion after a submitted task is covered by force_task_completion; 'working' is an out-of-band progress signal, not an initial response arm." - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description="Deterministic task handle the seller MUST emit verbatim on the next forced operation response when arm is 'submitted'. The seller MUST accept this exact value on subsequent tasks/get or get_task_status calls within the same authenticated sandbox account + principal pair and MUST return REFERENCE_NOT_FOUND for the same task_id under any other account or principal. Sandbox task_ids are caller-opaque strings - the seller's production task-id format rules do not apply.", - max_length=128, - ), - ] = None - evaluation_id: Annotated[ - str | None, - Field( - description='Deterministic provider evaluation identity the seller MUST emit on the next forced get_creative_features submitted acknowledgement and preserve on its terminal completion result.', - max_length=255, - min_length=1, - ), - ] = None - message: Annotated[ - str | None, - Field( - description='Optional human-readable explanation surfaced on the next forced operation response. Used by force_create_media_buy_arm, force_get_products_arm, force_get_signals_arm, and force_get_creative_features_arm for the submitted arm. Plain text only.', - max_length=2000, - ), - ] = None - reason: Annotated[ - str | None, - Field( - description="Deterministic buyer-facing reason the seller MUST emit on the next get_products response when force_get_products_arm uses arm 'rejected'. Plain text only; MUST NOT expose confidential internal rules, inventory identifiers, credentials, or stack traces.", - max_length=2000, - min_length=1, - ), - ] = None - suggestions: Annotated[ - list[Suggestion] | None, - Field( - description="Optional deterministic alternatives the seller MUST emit on the next get_products rejection. Used only by force_get_products_arm with arm 'rejected'.", - max_length=20, - min_length=1, - ), - ] = None - format_id: Annotated[ - str | None, - Field( - deprecated=True, - description='**DEPRECATED 3.x compatibility fixture.** Legacy named-format ID to seed for seed_creative_format scenarios. The seller MUST expose it through the deprecated list_creative_formats projection for the duration of the compliance session. Canonical 3.2 scenarios seed creative.supported_formats capabilities instead.', - ), - ] = None - vendor: Annotated[ - brand_ref.BrandReference | None, - Field( - description="Measurement vendor whose catalog is being seeded. Used by seed_measurement_catalog. The seller MUST treat `metrics[]` as this vendor's `get_adcp_capabilities.measurement.metrics[]` snapshot for the duration of the compliance session." - ), - ] = None - metrics: Annotated[ - list[Metric] | None, - Field( - description='Measurement catalog entries to seed for the vendor. Used by seed_measurement_catalog. Shape mirrors `get_adcp_capabilities.measurement.metrics[]`; each metric_id is unique within the seeded vendor catalog.', - min_length=1, - ), - ] = None - tool: Annotated[ - str | None, - Field( - description="Tool name whose upstream dependency to affect. Used by force_upstream_unavailable. The seller marks the named upstream as unreachable for subsequent calls to this tool within the compliance session. Sellers MAY accept a wildcard ('*') to affect all tools." - ), - ] = None - upstream_name: Annotated[ - str | None, - Field( - description="Human-readable identifier for the upstream dependency to force unavailable (e.g., 'inventory-service', 'creative-agent'). Used by force_upstream_unavailable. When omitted, the seller marks its default upstream for the specified tool as unavailable. Sellers MUST include the same name in STALE_RESPONSE error.details.upstream.name on the affected response." - ), - ] = None - cache_age_seconds: Annotated[ - SchemaInt | None, - Field( - description='Deterministic age of the stale cache entry used by force_upstream_unavailable. When supplied, the seller MUST emit this exact value in the matching STALE_RESPONSE error.details.cache_age_seconds on the next affected response. This lets the conformance runner bind the advisory code and its required detail fields to the same errors[] entry without assuming array order.', - ge=0, - ), - ] = None - result: Annotated[ - task_completion_data.ComplianceTaskCompletionData | None, - Field( - description="Deterministic result payload. force_get_creative_features_arm uses it when arm is completed and returns it on the next matching operation call. force_task_completion records it against an existing task and supports the bounded get_products, get_signals, create_media_buy, and get_creative_features completion union; polling and production SDKs resolve all task results through the originating task's manifest response mapping instead of embedding a global result union. The seller MUST preserve caller-supplied fields. Sellers MUST emit INVALID_PARAMS when the payload does not match the required response branch and MAY reject payloads exceeding 256 KB." - ), - ] = None - - -class ComplyTestControllerRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - scenario: Annotated[ - str, - Field( - description="Test scenario to execute. 'list_scenarios' discovers supported scenarios. 'force_*' and 'simulate_*' trigger state transitions. 'reporting_core_lifecycle_probe' installs a caller/account-scoped Core fixture whose first elapsed obligation is visible before any report, advances its virtual clock into delayed or action_required, and can publish deterministic zero-row or non-empty revisions, restate a provisional revision, restate one the caller already received, or cross the consumer-status deadline and consumer-mismatch escalation boundaries, without waiting for wall-clock boundaries. Other scenarios provide deterministic sandbox probes for their documented lifecycle checks. Runners and sellers MUST accept unknown scenario strings - new scenarios may be added in additive releases." - ), - ] - params: Annotated[ - Params | None, - Field( - description='Scenario-specific parameters. Required for all scenarios except list_scenarios.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - account: Annotated[ - Account | None, - Field( - description="Sandbox account assertion. The runner MUST set sandbox: true on every comply_test_controller request. The seller MUST refuse the request (returning a structured error) if the targeted account is not a sandbox account in the seller's persisted records. This field is a caller-side declaration of intent — it does not grant sandbox status; sellers verify against their own account state. The (Sandbox) verification tier is defined by this gate: real production endpoints accept sandbox-flagged traffic and process it without real-world side effects, no separate test-mode endpoint required. See spec issue #3755 and the (Sandbox) framing in #4379." - ), - ] = None diff --git a/src/adcp/types/generated_poc/compliance/comply_test_controller_response.py b/src/adcp/types/generated_poc/compliance/comply_test_controller_response.py deleted file mode 100644 index e167b41fa..000000000 --- a/src/adcp/types/generated_poc/compliance/comply_test_controller_response.py +++ /dev/null @@ -1,469 +0,0 @@ -# generated by datamodel-codegen: -# filename: compliance/comply_test_controller_response.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types.response_dispatch import ResponseArmDispatchMixin - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarStr -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictBool - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from ..creative import audit_observation - - -class ComplyResponseArm(StrEnum): - submitted = 'submitted' - completed = 'completed' - input_required = 'input-required' - rejected = 'rejected' - - -class Suggestion(ScalarStr): - __slots__ = () - _constraints = {'max_length': 1000, 'min_length': 1} - - -class Forced(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - arm: Annotated[ - ComplyResponseArm, Field(description='ComplyResponseArm the seller will emit on the next forced operation response.') - ] - task_id: Annotated[ - str | None, - Field( - description="Echo of the registered task_id. Present only when arm is 'submitted' (the arm that emits a task envelope).", - max_length=128, - ), - ] = None - evaluation_id: Annotated[ - str | None, - Field( - description='Echo of the provider evaluation identity registered by force_get_creative_features_arm.', - max_length=255, - min_length=1, - ), - ] = None - reason: Annotated[ - str | None, - Field( - description="Echo of the deterministic buyer-facing rejection reason. Required when arm is 'rejected'.", - max_length=2000, - min_length=1, - ), - ] = None - suggestions: Annotated[ - list[Suggestion] | None, - Field( - description='Echo of the optional deterministic alternatives registered for a rejected get_products response.', - max_length=20, - min_length=1, - ), - ] = None - - -class Method(StrEnum): - GET = 'GET' - POST = 'POST' - PUT = 'PUT' - PATCH = 'PATCH' - DELETE = 'DELETE' - HEAD = 'HEAD' - OPTIONS = 'OPTIONS' - - -class AttestationMode(StrEnum): - raw = 'raw' - digest = 'digest' - - -class Purpose(StrEnum): - platform_primary = 'platform_primary' - measurement = 'measurement' - attribution = 'attribution' - creative_serving = 'creative_serving' - identity = 'identity' - other = 'other' - - -class IdentifierMatchProof(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - identifier_value_sha256: Annotated[ - str, - Field( - description='Echo of one digest from `params.identifier_value_digests` so the runner can pair this proof with the identifier it queried.', - pattern='^[a-f0-9]{64}$', - ), - ] - found: Annotated[ - StrictBool, - Field( - description='True if any string token in the recorded payload hashes to the queried digest. False otherwise.' - ), - ] - - -class RecordedCalls1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - method: Annotated[Method, Field(description='HTTP method of the outbound call.')] - endpoint: Annotated[ - str, - Field( - description="Composed ` ` string used for `endpoint_pattern` matching, e.g. 'POST https://api.tiktok.com/v2/audience/upload'. Convenience field — the runner can also reconstruct from `method` + URL components." - ), - ] - url: Annotated[ - AnyUrl, - Field( - description='Full URL of the outbound call (scheme + host + path + query). Treated as untrusted agent-controlled input by report renderers — see runner-output-contract.yaml > security.rendered_output_fencing.' - ), - ] - host: Annotated[ - str | None, - Field( - description='Host portion of the URL, useful for grouping calls by upstream platform.' - ), - ] = None - path: Annotated[ - str | None, Field(description='Path portion of the URL (without query string).') - ] = None - content_type: Annotated[ - str, - Field( - description="Media type of the outbound request body, mirroring the agent's outbound `Content-Type` header (e.g., `application/json`, `application/x-www-form-urlencoded`, `multipart/form-data`, `text/plain`). In raw mode this describes the returned `payload`; in digest mode it describes the body the digest was computed over. Storyboard `payload_must_contain` JSONPath-lite assertions are valid only when content_type is `application/json` or has a `+json` suffix AND attestation_mode is `raw` — digest mode and non-JSON content types grade `payload_must_contain` as not_applicable. Required so the runner can choose the right matcher deterministically." - ), - ] - attestation_mode: Annotated[ - Literal['raw'], - Field( - description="Per-call attestation mode echoing the request's `params.attestation_mode`. Required on every recorded_call so the `oneOf` discriminator always has an explicit value to dispatch on — no implicit defaults inside oneOf branches. Adopters MAY unilaterally downgrade a `raw` request to `digest` for a specific call when their policy requires it (e.g., the call carried regulated PII the adopter can't return raw). The runner reads this field to know which assertions to apply." - ), - ] = 'raw' - purpose: Annotated[ - Purpose | None, - Field( - description="Optional adopter-supplied semantic tag for the call's role. Values: `platform_primary` for the primary upstream platform the adapter is integrating with (e.g., a TikTok audience-upload call from a sales-social adapter); `measurement` for ancillary calls to measurement vendors (DV, IAS, Nielsen, MOAT); `attribution` for server-side conversion APIs (TTD Trans-API, Meta CAPI, AppsFlyer/Branch postbacks) that flow alongside primary platform calls in a buy-step; `creative_serving` for ad-server / CDN / tag-build calls (GAM tag generation, VAST/CDN fetches, creative trafficking); `identity` for ID-graph / hashing-service calls (LiveRamp, ID5, UID2); `other` for everything else (config fetches, internal telemetry, consent signal exchange). Lets storyboards scope `upstream_traffic` assertions via `purpose_filter` so a buyer-agent adapter that legitimately calls measurement vendors during a single buy step doesn't muddy the platform-primary assertion. Calls without a `purpose` field are treated as `purpose: other` for `purpose_filter` matching — adopters who haven't classified are matched only by storyboards filtering on `other` (or by storyboards with no `purpose_filter`). Self-reported, not adversarially trustworthy — same trust model as the rest of recorded_calls; misclassification by a façade is bounded by the runner's reporting of unclassified-call counts in `actual` when filters match zero." - ), - ] = None - payload: Annotated[ - Any, - Field( - description="Request body the agent sent. Required when `attestation_mode` is `raw` (or omitted); MUST be absent when `attestation_mode` is `digest`. Object when content_type is JSON-shaped and the controller decoded it; string otherwise. The `x-adcp-open-payload: true` annotation applies to the decoded JSON/object arm of this mixed field; non-JSON string payloads remain scalar values governed by the surrounding schema. Adopters MUST apply the recursive secret-key redaction described in this branch's top-level description before emission — secrets at any depth (Authorization values inlined into JSON bodies, embedded JWTs, presigned-URL tokens, OAuth refresh tokens) MUST be replaced with the literal string `[redacted]`. Storyboards that assert `payload_must_contain` or `identifier_paths` are matching against THIS field — secrets MUST be redacted but storyboard-supplied identifiers (hashed PII, request correlation values) MUST NOT be redacted. Adopters SHOULD cap individual payload size at 64 KiB; payloads exceeding that SHOULD be truncated with a trailing `[…truncated]` marker — large payloads bloat compliance reports and the LLM-rendered context windows that consume them.", - max_length=65536, - ), - ] - payload_digest_sha256: Annotated[ - str | None, - Field( - description='SHA-256 digest of the canonicalized outbound request body, lowercase hex (64 chars). Required when `attestation_mode` is `digest`; MUST be absent when `attestation_mode` is `raw`. Canonicalization order is normative: (1) controllers MUST apply the recursive secret-key redaction (same pattern as the raw-mode payload field) BEFORE computing the digest; (2) for `application/json` and `*+json` content types, controllers MUST then serialize the redacted body to RFC 8785 (JCS) canonical form — sorted keys, no extraneous whitespace — and digest the resulting bytes. Storyboard-supplied identifiers (hashed PII, request correlation values) MUST NOT be redacted before digest computation; they are the load-bearing match target for `identifier_match_proofs` and digesting them away makes echo verification impossible. Both `payload_digest_sha256` AND `identifier_match_proofs` MUST be computed against the same post-redaction canonical bytes — diverging the two surfaces breaks coherence between digest replay and identifier echo. JCS edge cases: when a digest-mode `query_upstream_traffic` response cannot be produced because the parsed JSON-like value tree contains a non-finite numeric value (`NaN`, `+Infinity`, or `-Infinity`), controllers MUST NOT coerce that value to `null`, a string, or any other placeholder for digest computation; they MUST return the typed ControllerError code `JCS_NON_FINITE_NUMBER`. Runners MUST grade the affected upstream_traffic digest validation `not_applicable` because RFC 8785 forbids non-finite numbers. Payloads carrying numeric identifiers MUST serialize them as JSON strings before digest computation (adtech bid payloads regularly carry IDs outside ±2^53 where I-JSON / RFC 7493 number round-tripping diverges across implementations). For non-JSON content types the digest is computed over the post-redaction raw body bytes.', - pattern='^[a-f0-9]{64}$', - ), - ] = None - payload_length: Annotated[ - SchemaInt, - Field( - description='Byte length of the post-redaction body bytes represented by this recorded_call. Required in both `raw` and `digest` modes — symmetric across modes so runners can detect adopter-side truncation regardless of attestation choice. In `raw` mode this MUST equal the UTF-8 byte length of the emitted `payload` value after the same recursive secret-key redaction the controller applied before returning it. In `digest` mode this MUST equal the exact number of bytes fed into SHA-256 for `payload_digest_sha256`: RFC 8785 (JCS) canonical bytes for JSON-shaped content after redaction, or the post-redaction raw body bytes for non-JSON content. Digest-mode `payload_length` is therefore NOT the original outbound body length before JSON parsing, redaction, or canonicalization. Mismatch between observed payload length and reported `payload_length` is a controller-side bug worth surfacing in the report.', - ge=0, - ), - ] - identifier_match_proofs: Annotated[ - list[IdentifierMatchProof] | None, - Field( - description='Per-identifier echo proofs for digest-mode calls. Required when `attestation_mode` is `digest` AND the request supplied `params.identifier_value_digests`; MUST be absent or empty otherwise. Each entry corresponds to one digest from the request. Capped at 64 to match the request-side `params.identifier_value_digests` cap. Lets storyboards verify `identifier_paths` echo in digest mode without ever transmitting plaintext identifiers to the controller. SHA-256 is a privacy mechanism here, not a trust mechanism — controllers self-report `found` and a determined façade can return any boolean; consumers MUST NOT treat digest-mode passing as cryptographically more trustworthy than raw mode. Tokenization is normative: for `application/json` and `*+json` content types, controllers MUST scan exactly the JSON string-typed leaf values of the post-redaction canonicalized body — no substring matching, no word splitting, no case folding, no Unicode normalization. A token matches when its SHA-256 hash equals one of the requested digests byte-for-byte. For non-JSON content types (form-urlencoded, multipart, plain text), `identifier_match_proofs` MUST be empty and runner-side `identifier_paths` assertions targeting those calls grade `not_applicable` — token boundaries are not portably defined across non-JSON shapes.', - max_length=64, - ), - ] = None - timestamp: Annotated[ - AwareDatetime, - Field( - description="ISO 8601 timestamp the adopter recorded the outbound call. MUST reflect the adopter's wall clock at the moment the outbound request was sent (not log-flush time), and MUST be monotonically non-decreasing across recorded_calls of a single response. Used by runners to scope assertions to a specific storyboard step's window — see runner-output-contract.yaml > validation_result for the timestamp boundary semantics." - ), - ] - status_code: Annotated[ - SchemaInt | None, - Field( - description='HTTP status code returned by the upstream. Optional — adopters MAY omit when the call was instrumented before the response arrived.', - ge=100, - le=599, - ), - ] = None - - -class RecordedCalls2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - method: Annotated[Method, Field(description='HTTP method of the outbound call.')] - endpoint: Annotated[ - str, - Field( - description="Composed ` ` string used for `endpoint_pattern` matching, e.g. 'POST https://api.tiktok.com/v2/audience/upload'. Convenience field — the runner can also reconstruct from `method` + URL components." - ), - ] - url: Annotated[ - AnyUrl, - Field( - description='Full URL of the outbound call (scheme + host + path + query). Treated as untrusted agent-controlled input by report renderers — see runner-output-contract.yaml > security.rendered_output_fencing.' - ), - ] - host: Annotated[ - str | None, - Field( - description='Host portion of the URL, useful for grouping calls by upstream platform.' - ), - ] = None - path: Annotated[ - str | None, Field(description='Path portion of the URL (without query string).') - ] = None - content_type: Annotated[ - str, - Field( - description="Media type of the outbound request body, mirroring the agent's outbound `Content-Type` header (e.g., `application/json`, `application/x-www-form-urlencoded`, `multipart/form-data`, `text/plain`). In raw mode this describes the returned `payload`; in digest mode it describes the body the digest was computed over. Storyboard `payload_must_contain` JSONPath-lite assertions are valid only when content_type is `application/json` or has a `+json` suffix AND attestation_mode is `raw` — digest mode and non-JSON content types grade `payload_must_contain` as not_applicable. Required so the runner can choose the right matcher deterministically." - ), - ] - attestation_mode: Annotated[ - Literal['digest'], - Field( - description="Per-call attestation mode echoing the request's `params.attestation_mode`. Required on every recorded_call so the `oneOf` discriminator always has an explicit value to dispatch on — no implicit defaults inside oneOf branches. Adopters MAY unilaterally downgrade a `raw` request to `digest` for a specific call when their policy requires it (e.g., the call carried regulated PII the adopter can't return raw). The runner reads this field to know which assertions to apply." - ), - ] = 'digest' - purpose: Annotated[ - Purpose | None, - Field( - description="Optional adopter-supplied semantic tag for the call's role. Values: `platform_primary` for the primary upstream platform the adapter is integrating with (e.g., a TikTok audience-upload call from a sales-social adapter); `measurement` for ancillary calls to measurement vendors (DV, IAS, Nielsen, MOAT); `attribution` for server-side conversion APIs (TTD Trans-API, Meta CAPI, AppsFlyer/Branch postbacks) that flow alongside primary platform calls in a buy-step; `creative_serving` for ad-server / CDN / tag-build calls (GAM tag generation, VAST/CDN fetches, creative trafficking); `identity` for ID-graph / hashing-service calls (LiveRamp, ID5, UID2); `other` for everything else (config fetches, internal telemetry, consent signal exchange). Lets storyboards scope `upstream_traffic` assertions via `purpose_filter` so a buyer-agent adapter that legitimately calls measurement vendors during a single buy step doesn't muddy the platform-primary assertion. Calls without a `purpose` field are treated as `purpose: other` for `purpose_filter` matching — adopters who haven't classified are matched only by storyboards filtering on `other` (or by storyboards with no `purpose_filter`). Self-reported, not adversarially trustworthy — same trust model as the rest of recorded_calls; misclassification by a façade is bounded by the runner's reporting of unclassified-call counts in `actual` when filters match zero." - ), - ] = None - payload: Annotated[ - Any | None, - Field( - description="Request body the agent sent. Required when `attestation_mode` is `raw` (or omitted); MUST be absent when `attestation_mode` is `digest`. Object when content_type is JSON-shaped and the controller decoded it; string otherwise. The `x-adcp-open-payload: true` annotation applies to the decoded JSON/object arm of this mixed field; non-JSON string payloads remain scalar values governed by the surrounding schema. Adopters MUST apply the recursive secret-key redaction described in this branch's top-level description before emission — secrets at any depth (Authorization values inlined into JSON bodies, embedded JWTs, presigned-URL tokens, OAuth refresh tokens) MUST be replaced with the literal string `[redacted]`. Storyboards that assert `payload_must_contain` or `identifier_paths` are matching against THIS field — secrets MUST be redacted but storyboard-supplied identifiers (hashed PII, request correlation values) MUST NOT be redacted. Adopters SHOULD cap individual payload size at 64 KiB; payloads exceeding that SHOULD be truncated with a trailing `[…truncated]` marker — large payloads bloat compliance reports and the LLM-rendered context windows that consume them.", - max_length=65536, - ), - ] = None - payload_digest_sha256: Annotated[ - str, - Field( - description='SHA-256 digest of the canonicalized outbound request body, lowercase hex (64 chars). Required when `attestation_mode` is `digest`; MUST be absent when `attestation_mode` is `raw`. Canonicalization order is normative: (1) controllers MUST apply the recursive secret-key redaction (same pattern as the raw-mode payload field) BEFORE computing the digest; (2) for `application/json` and `*+json` content types, controllers MUST then serialize the redacted body to RFC 8785 (JCS) canonical form — sorted keys, no extraneous whitespace — and digest the resulting bytes. Storyboard-supplied identifiers (hashed PII, request correlation values) MUST NOT be redacted before digest computation; they are the load-bearing match target for `identifier_match_proofs` and digesting them away makes echo verification impossible. Both `payload_digest_sha256` AND `identifier_match_proofs` MUST be computed against the same post-redaction canonical bytes — diverging the two surfaces breaks coherence between digest replay and identifier echo. JCS edge cases: when a digest-mode `query_upstream_traffic` response cannot be produced because the parsed JSON-like value tree contains a non-finite numeric value (`NaN`, `+Infinity`, or `-Infinity`), controllers MUST NOT coerce that value to `null`, a string, or any other placeholder for digest computation; they MUST return the typed ControllerError code `JCS_NON_FINITE_NUMBER`. Runners MUST grade the affected upstream_traffic digest validation `not_applicable` because RFC 8785 forbids non-finite numbers. Payloads carrying numeric identifiers MUST serialize them as JSON strings before digest computation (adtech bid payloads regularly carry IDs outside ±2^53 where I-JSON / RFC 7493 number round-tripping diverges across implementations). For non-JSON content types the digest is computed over the post-redaction raw body bytes.', - pattern='^[a-f0-9]{64}$', - ), - ] - payload_length: Annotated[ - SchemaInt, - Field( - description='Byte length of the post-redaction body bytes represented by this recorded_call. Required in both `raw` and `digest` modes — symmetric across modes so runners can detect adopter-side truncation regardless of attestation choice. In `raw` mode this MUST equal the UTF-8 byte length of the emitted `payload` value after the same recursive secret-key redaction the controller applied before returning it. In `digest` mode this MUST equal the exact number of bytes fed into SHA-256 for `payload_digest_sha256`: RFC 8785 (JCS) canonical bytes for JSON-shaped content after redaction, or the post-redaction raw body bytes for non-JSON content. Digest-mode `payload_length` is therefore NOT the original outbound body length before JSON parsing, redaction, or canonicalization. Mismatch between observed payload length and reported `payload_length` is a controller-side bug worth surfacing in the report.', - ge=0, - ), - ] - identifier_match_proofs: Annotated[ - list[IdentifierMatchProof] | None, - Field( - description='Per-identifier echo proofs for digest-mode calls. Required when `attestation_mode` is `digest` AND the request supplied `params.identifier_value_digests`; MUST be absent or empty otherwise. Each entry corresponds to one digest from the request. Capped at 64 to match the request-side `params.identifier_value_digests` cap. Lets storyboards verify `identifier_paths` echo in digest mode without ever transmitting plaintext identifiers to the controller. SHA-256 is a privacy mechanism here, not a trust mechanism — controllers self-report `found` and a determined façade can return any boolean; consumers MUST NOT treat digest-mode passing as cryptographically more trustworthy than raw mode. Tokenization is normative: for `application/json` and `*+json` content types, controllers MUST scan exactly the JSON string-typed leaf values of the post-redaction canonicalized body — no substring matching, no word splitting, no case folding, no Unicode normalization. A token matches when its SHA-256 hash equals one of the requested digests byte-for-byte. For non-JSON content types (form-urlencoded, multipart, plain text), `identifier_match_proofs` MUST be empty and runner-side `identifier_paths` assertions targeting those calls grade `not_applicable` — token boundaries are not portably defined across non-JSON shapes.', - max_length=64, - ), - ] = None - timestamp: Annotated[ - AwareDatetime, - Field( - description="ISO 8601 timestamp the adopter recorded the outbound call. MUST reflect the adopter's wall clock at the moment the outbound request was sent (not log-flush time), and MUST be monotonically non-decreasing across recorded_calls of a single response. Used by runners to scope assertions to a specific storyboard step's window — see runner-output-contract.yaml > validation_result for the timestamp boundary semantics." - ), - ] - status_code: Annotated[ - SchemaInt | None, - Field( - description='HTTP status code returned by the upstream. Optional — adopters MAY omit when the call was instrumented before the response arrived.', - ge=100, - le=599, - ), - ] = None - - -RecordedCalls = Annotated[ - RecordedCalls1 | RecordedCalls2, - Field(discriminator='attestation_mode'), -] - - -class Error(StrEnum): - INVALID_TRANSITION = 'INVALID_TRANSITION' - INVALID_STATE = 'INVALID_STATE' - NOT_FOUND = 'NOT_FOUND' - UNKNOWN_SCENARIO = 'UNKNOWN_SCENARIO' - INVALID_PARAMS = 'INVALID_PARAMS' - FORBIDDEN = 'FORBIDDEN' - JCS_NON_FINITE_NUMBER = 'JCS_NON_FINITE_NUMBER' - INTERNAL_ERROR = 'INTERNAL_ERROR' - - -class ComplyTestControllerResponse(ResponseArmDispatchMixin, AdcpVersionEnvelope, ProtocolEnvelope): - """Constructible compatibility base for generated response arms.""" - - @classmethod - def _response_arm_models(cls) -> tuple[type[ComplyTestControllerResponse], ...]: - return ( - ComplyTestControllerResponse1, - ComplyTestControllerResponse2, - ComplyTestControllerResponse3, - ComplyTestControllerResponse4, - ComplyTestControllerResponse5, - ComplyTestControllerResponse6, - ComplyTestControllerResponse7, - ComplyTestControllerResponse8, - ) - - -class ComplyTestControllerResponse1(ComplyTestControllerResponse): - model_config = ConfigDict( - extra='allow', - ) - success: Literal[True] - scenarios: Annotated[ - list[str], - Field( - description='Scenarios this seller has implemented. Runners and sellers MUST accept unknown scenario strings (open-for-extension) — new scenarios may be added in additive releases. Adopters who advertise `catalog_item_availability_probe` support deterministic cross-principal reference, eligibility-gate, expiry-clock, and catalog-generation tests for the catalog availability storyboard. Adopters who advertise `compact_product_lifecycle_probe` support deterministic synchronous list/request/finalize/decline/accept/control/readback behavior for a prepared product and strict post-deadline expiry of a committed proposal. Adopters who advertise `compact_direct_buy_lifecycle_probe` support deterministic synchronous list/buy/control/readback behavior for a prepared product. Adopters who advertise `reporting_core_lifecycle_probe` support deterministic obligation-before-report, clock-health, zero-row reporting, provisional-restatement, and post-received restatement-grace tests without wall-clock waits. `reliable_reporting_core_integrity_probe`, `reliable_reporting_managed_delivery_probe`, and `reliable_reporting_reconciled_billing_probe` seed the source-calendar/checkpoint, managed-resource, and receipt/adjustment workflows used by the Reliable Reporting tier storyboards. Adopters who advertise `force_creative_purge` opt in to deterministic creative purge coverage for account-level lifecycle webhooks. Adopters who advertise `force_media_buy_purge` opt in to deterministic deletion-independent idempotency replay coverage. Adopters who advertise `seed_measurement_catalog` opt in to deterministic measurement-catalog fixtures used by vendor_metric precondition storyboards. Adopters who advertise `query_upstream_traffic` opt in to the upstream-traffic conformance contract; storyboards that declare `check: upstream_traffic` grade not_applicable against adopters who do not advertise it. Adopters who advertise `query_provenance_audit_observations` opt in to sandbox-only audit-observation assertions for accepted creatives. Adopters who advertise `force_upstream_unavailable` opt in to stale-cache conformance testing via the `stale_response_advisory` storyboard.' - ), - ] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class ComplyTestControllerResponse2(ComplyTestControllerResponse): - model_config = ConfigDict( - extra='allow', - ) - success: Literal[True] - previous_state: Annotated[str, Field(description='State before this transition')] - current_state: Annotated[str, Field(description='State after this transition')] - message: Annotated[ - str | None, Field(description='Human-readable description of the transition') - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class ComplyTestControllerResponse3(ComplyTestControllerResponse): - model_config = ConfigDict( - extra='allow', - ) - success: Literal[True] - simulated: Annotated[ - dict[str, Any], - Field(description='Values injected or applied by this call. Shape depends on scenario.'), - ] - cumulative: Annotated[ - dict[str, Any] | None, - Field(description='Running totals across all simulation calls (simulate_delivery only)'), - ] = None - message: str | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class ComplyTestControllerResponse4(ComplyTestControllerResponse): - model_config = ConfigDict( - extra='allow', - ) - success: Literal[True] - forced: Annotated[ - Forced, - Field( - description='Echo of the registered directive. The next matching operation call from this sandbox account will return the named arm.' - ), - ] - message: Annotated[str | None, Field(description='Human-readable acknowledgement.')] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class ComplyTestControllerResponse5(ComplyTestControllerResponse): - model_config = ConfigDict( - extra='allow', - ) - success: Literal[True] - message: Annotated[str | None, Field(description='Human-readable acknowledgement.')] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class ComplyTestControllerResponse6(ComplyTestControllerResponse): - model_config = ConfigDict( - extra='allow', - ) - success: Literal[True] - creative_id: Annotated[ - str, Field(description='Creative ID whose audit observations were queried.') - ] - audit_observations: Annotated[ - list[audit_observation.CreativeAuditObservation], - Field(description='Audit observations recorded for the creative in the sandbox session.'), - ] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class ComplyTestControllerResponse7(ComplyTestControllerResponse): - model_config = ConfigDict( - extra='allow', - ) - success: Literal[True] - recorded_calls: Annotated[ - list[RecordedCalls], - Field( - description="Outbound HTTP calls caused by the requesting principal in the requested window, ordered by `timestamp` ascending. Cross-caller calls MUST NOT appear here. Each item declares its `attestation_mode`: `raw` items carry the full `payload`; `digest` items carry `payload_digest_sha256` + `payload_length` + optional `identifier_match_proofs[]` instead, for adopters who can't return raw payloads under their privacy/data-residency policy." - ), - ] - total_count: Annotated[ - SchemaInt, - Field( - description='Total calls in the requested window before any pagination — `recorded_calls.length` may be smaller when `params.limit` truncated the response.', - ge=0, - ), - ] - truncated: Annotated[ - StrictBool | None, - Field( - description='True when `total_count > recorded_calls.length`. Runners MAY raise the `limit` and re-query, but storyboards SHOULD declare assertions that fit within the default 100-call window — a truncated response is a signal that the storyboard step is causing more upstream activity than expected.' - ), - ] = None - since_timestamp: Annotated[ - AwareDatetime, - Field( - description="Echo of the `since_timestamp` the runner requested (or the session-start timestamp the adopter substituted when the runner omitted it). Informational — the runner SHOULD use its own clock-bracket of when it issued the AdCP step request, not this echo, when attributing recorded_calls to a specific step. The controller is part of the adopter's claimed conformance; the echo is not adversarially trustworthy." - ), - ] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class ComplyTestControllerResponse8(ComplyTestControllerResponse): - model_config = ConfigDict( - extra='allow', - ) - success: Literal[False] - error: Annotated[ - Error, - Field( - description='Structured error code. `JCS_NON_FINITE_NUMBER` is reserved for digest-mode upstream_traffic responses that cannot be RFC 8785/JCS-canonicalized because the parsed JSON-like value tree contains a non-finite numeric value (`NaN`, `+Infinity`, or `-Infinity`); controllers MUST NOT coerce those values during digest computation, and runners grade that validation `not_applicable`, not failed.' - ), - ] - error_detail: Annotated[ - str | None, Field(description='Human-readable explanation of the failure') - ] = None - current_state: Annotated[ - str | None, Field(description='Current state of the entity, or null if not found') - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/compliance/get_creative_features_completion.py b/src/adcp/types/generated_poc/compliance/get_creative_features_completion.py deleted file mode 100644 index a7fd506c2..000000000 --- a/src/adcp/types/generated_poc/compliance/get_creative_features_completion.py +++ /dev/null @@ -1,26 +0,0 @@ -# generated by datamodel-codegen: -# filename: compliance/get_creative_features_completion.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictFloat - -from ..core import creative_consumption -from ..creative import creative_feature_result - - -class GetCreativeFeaturesComplianceCompletion(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Literal['completed'] = 'completed' - evaluation_id: Annotated[str, Field(min_length=1)] - results: list[creative_feature_result.CreativeFeatureResult] - pricing_option_id: str | None = None - vendor_cost: Annotated[StrictFloat | None, Field(ge=0.0)] = None - currency: Annotated[str | None, Field(pattern='^[A-Z]{3}$')] = None - consumption: creative_consumption.CreativeConsumption | None = None diff --git a/src/adcp/types/generated_poc/compliance/task_completion_data.py b/src/adcp/types/generated_poc/compliance/task_completion_data.py deleted file mode 100644 index 5690b1089..000000000 --- a/src/adcp/types/generated_poc/compliance/task_completion_data.py +++ /dev/null @@ -1,25 +0,0 @@ -# generated by datamodel-codegen: -# filename: compliance/task_completion_data.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import Field - -from ..media_buy import create_media_buy_response, get_products_response -from ..signals import get_signals_response -from . import get_creative_features_completion - - -ComplianceTaskCompletionData = Annotated[ - get_products_response.GetProductsResponse - | get_signals_response.GetSignalsResponse - | create_media_buy_response.CreateMediaBuyResponse1 - | get_creative_features_completion.GetCreativeFeaturesComplianceCompletion, - Field( - description="Bounded completion union for the async tasks supported by force_task_completion. Polling and production task-result resolution use the originating task's manifest response schema rather than embedding the global async union.", - title='Compliance Task Completion Data', - ), -] diff --git a/src/adcp/types/generated_poc/content_standards/__init__.py b/src/adcp/types/generated_poc/content_standards/__init__.py deleted file mode 100644 index 935753268..000000000 --- a/src/adcp/types/generated_poc/content_standards/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# generated by datamodel-codegen: -# filename: .schema_temp -# timestamp: 2026-01-25T21:17:54+00:00 diff --git a/src/adcp/types/generated_poc/content_standards/artifact.py b/src/adcp/types/generated_poc/content_standards/artifact.py deleted file mode 100644 index 9c5d03ecb..000000000 --- a/src/adcp/types/generated_poc/content_standards/artifact.py +++ /dev/null @@ -1,317 +0,0 @@ -# generated by datamodel-codegen: -# filename: content_standards/artifact.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, RootModel - -from ..core import canonical_format_kind -from ..core import format_id as format_id_1 -from ..core import provenance as provenance_1 - - -class Role(StrEnum): - title = 'title' # type: ignore[assignment] - paragraph = 'paragraph' - heading = 'heading' - caption = 'caption' - quote = 'quote' - list_item = 'list_item' - description = 'description' - - -class ContentFormat(StrEnum): - text_plain = 'text/plain' - text_markdown = 'text/markdown' - text_html = 'text/html' - application_json = 'application/json' - - -class TranscriptFormat(StrEnum): - text_plain = 'text/plain' - text_markdown = 'text/markdown' - application_json = 'application/json' - - -class TranscriptSource(StrEnum): - original_script = 'original_script' - subtitles = 'subtitles' - closed_captions = 'closed_captions' - dub = 'dub' - generated = 'generated' - - -class TranscriptSource1(StrEnum): - original_script = 'original_script' - closed_captions = 'closed_captions' - generated = 'generated' - - -class Metadata(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - canonical: Annotated[AnyUrl | None, Field(description='Canonical URL')] = None - author: Annotated[str | None, Field(description='Artifact author name')] = None - keywords: Annotated[str | None, Field(description='Artifact keywords')] = None - open_graph: Annotated[ - dict[str, Any] | None, Field(description='Open Graph protocol metadata') - ] = None - twitter_card: Annotated[dict[str, Any] | None, Field(description='Twitter Card metadata')] = ( - None - ) - json_ld: Annotated[ - list[dict[str, Any]] | None, Field(description='JSON-LD structured data (schema.org)') - ] = None - - -class Identifiers(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - apple_podcast_id: Annotated[str | None, Field(description='Apple Podcasts ID')] = None - spotify_collection_id: Annotated[str | None, Field(description='Spotify collection ID')] = None - podcast_guid: Annotated[str | None, Field(description='Podcast GUID (from RSS feed)')] = None - youtube_video_id: Annotated[str | None, Field(description='YouTube video ID')] = None - rss_url: Annotated[AnyUrl | None, Field(description='RSS feed URL')] = None - - -class AssetAccess1(AdCPBaseModel): - method: Literal['bearer_token'] = 'bearer_token' - token: Annotated[ - str, - Field( - description="Short-lived, asset-scoped bearer token for the Authorization header. Producers MUST NOT issue a standing or broadly scoped credential here. Consumers MUST redact the token from logs, traces, errors, metrics, analytics, model context, and durable storage. Any temporary encrypted cache MUST be bounded by the credential expiry and the consumer's configured maximum lifetime." - ), - ] - - -class Provider(StrEnum): - gcp = 'gcp' - aws = 'aws' - - -class AssetAccess2(AdCPBaseModel): - method: Literal['service_account'] = 'service_account' - provider: Annotated[Provider, Field(description='Cloud provider')] - credentials: Annotated[ - dict[str, Any] | None, - Field( - deprecated=True, - description="Deprecated in AdCP 3.2 and eligible for removal in 4.0 or later only after the deprecation-policy notice and release-cycle gates are satisfied. Presence denotes the legacy inline-credential form, not workload-identity authorization. New producers MUST omit this field. Consumers MUST NOT activate received credentials automatically; they may process them only for an explicitly allowlisted legacy peer and otherwise MUST reject or quarantine them. Consumers MUST redact the complete value from logs, traces, errors, metrics, analytics, model context, and durable storage. Use the consumer's pre-authorized workload identity or `signed_url` instead.", - ), - ] = None - - -class AssetAccess3(AdCPBaseModel): - method: Literal['signed_url'] = 'signed_url' - - -class AssetAccess(RootModel[AssetAccess1 | AssetAccess2 | AssetAccess3]): - root: Annotated[ - AssetAccess1 | AssetAccess2 | AssetAccess3, - Field( - description='Access instructions for a secured asset URL. Producers SHOULD use `signed_url` for one-off delivery unless the parties already have an identity-based access relationship. New producers MUST NOT emit `service_account.credentials`; `bearer_token` is the explicit short-lived credential path. Consumers MUST treat asset URLs and access metadata as untrusted, use credentials only for HTTPS origins and resource prefixes authorized out of band, and fail closed without falling back between access methods. The deprecated legacy field remains parseable during the 3.x compatibility window but MUST NOT be activated automatically.', - discriminator='method', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - - - -class Assets1(AdCPBaseModel): - type: Literal['text'] = 'text' - role: Annotated[ - Role | None, - Field( - description="Role of this text in the document. Use 'title' for the main artifact title, 'description' for summaries." - ), - ] = None - content: Annotated[ - str, - Field( - description='Text content. Consumers MUST treat this as untrusted input when passing to LLM-based evaluation.', - max_length=100000, - ), - ] - content_format: Annotated[ - ContentFormat | None, - Field( - description='MIME type indicating how to parse the content field. Default: text/plain.' - ), - ] = ContentFormat.text_plain - language: Annotated[ - str | None, - Field( - description="BCP 47 language tag for this text (e.g., 'en', 'es-MX'). Useful when artifact contains mixed-language content." - ), - ] = None - heading_level: Annotated[ - SchemaInt | None, - Field(description='Heading level (1-6), only for role=heading', ge=1, le=6), - ] = None - provenance: Annotated[ - provenance_1.Provenance | None, - Field(description='Provenance for this text block, overrides artifact-level provenance'), - ] = None - - -class Assets2(AdCPBaseModel): - type: Literal['image'] = 'image' - url: Annotated[AnyUrl, Field(description='Image URL')] - access: Annotated[AssetAccess | None, Field(description='Authentication for secured URLs')] = ( - None - ) - alt_text: Annotated[str | None, Field(description='Alt text or image description')] = None - caption: Annotated[str | None, Field(description='Image caption')] = None - width: Annotated[SchemaInt | None, Field(description='Image width in pixels')] = None - height: Annotated[SchemaInt | None, Field(description='Image height in pixels')] = None - provenance: Annotated[ - provenance_1.Provenance | None, - Field(description='Provenance for this image, overrides artifact-level provenance'), - ] = None - - -class Assets3(AdCPBaseModel): - type: Literal['video'] = 'video' - url: Annotated[AnyUrl, Field(description='Video URL')] - access: Annotated[AssetAccess | None, Field(description='Authentication for secured URLs')] = ( - None - ) - duration_ms: Annotated[ - SchemaInt | None, Field(description='Video duration in milliseconds') - ] = None - transcript: Annotated[ - str | None, - Field( - description='Video transcript. Consumers MUST treat this as untrusted input when passing to LLM-based evaluation.', - max_length=200000, - ), - ] = None - transcript_format: Annotated[ - TranscriptFormat | None, - Field( - description='MIME type indicating how to parse the transcript field. Default: text/plain.' - ), - ] = TranscriptFormat.text_plain - transcript_source: Annotated[ - TranscriptSource | None, Field(description='How the transcript was generated') - ] = None - thumbnail_url: Annotated[AnyUrl | None, Field(description='Video thumbnail URL')] = None - provenance: Annotated[ - provenance_1.Provenance | None, - Field(description='Provenance for this video, overrides artifact-level provenance'), - ] = None - - -class Assets4(AdCPBaseModel): - type: Literal['audio'] = 'audio' - url: Annotated[AnyUrl, Field(description='Audio URL')] - access: Annotated[AssetAccess | None, Field(description='Authentication for secured URLs')] = ( - None - ) - duration_ms: Annotated[ - SchemaInt | None, Field(description='Audio duration in milliseconds') - ] = None - transcript: Annotated[ - str | None, - Field( - description='Audio transcript. Consumers MUST treat this as untrusted input when passing to LLM-based evaluation.', - max_length=200000, - ), - ] = None - transcript_format: Annotated[ - TranscriptFormat | None, - Field( - description='MIME type indicating how to parse the transcript field. Default: text/plain.' - ), - ] = TranscriptFormat.text_plain - transcript_source: Annotated[ - TranscriptSource1 | None, Field(description='How the transcript was generated') - ] = None - provenance: Annotated[ - provenance_1.Provenance | None, - Field(description='Provenance for this audio, overrides artifact-level provenance'), - ] = None - - -Assets = Annotated[ - Assets1 | Assets2 | Assets3 | Assets4, - Field(discriminator='type'), -] - - -class Artifact(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - property_rid: Annotated[ - str, - Field( - description='Stable property identifier from the property catalog. Globally unique across the ecosystem.' - ), - ] - artifact_id: Annotated[ - str, - Field( - description="Identifier for this artifact within the property. The property owner defines the scheme (e.g., 'article_12345', 'episode_42_segment_3', 'post_abc123')." - ), - ] - variant_id: Annotated[ - str | None, - Field( - description="Identifies a specific variant of this artifact. Use for A/B tests, translations, or temporal versions. Examples: 'en', 'es-MX', 'v2', 'headline_test_b'. The combination of artifact_id + variant_id must be unique." - ), - ] = None - format_id: Annotated[ - format_id_1.FormatReferenceStructuredObject | None, - Field( - deprecated=True, - description='**DEPRECATED in 3.2.** Legacy named-format reference. Use `format_kind` for canonical artifact media classification.', - ), - ] = None - format_kind: Annotated[ - canonical_format_kind.CanonicalFormatKind | None, - Field(description='Optional canonical media/creative kind associated with this artifact.'), - ] = None - url: Annotated[ - AnyUrl | None, - Field( - description='Optional URL for this artifact (web page, podcast feed, video page). Not all artifacts have URLs (e.g., Instagram content, podcast segments, TV scenes).' - ), - ] = None - published_time: Annotated[ - AwareDatetime | None, Field(description='When the artifact was published (ISO 8601 format)') - ] = None - last_update_time: Annotated[ - AwareDatetime | None, - Field(description='When the artifact was last modified (ISO 8601 format)'), - ] = None - assets: Annotated[ - list[Assets], - Field( - description='Artifact assets in document flow order - text blocks, images, video, audio', - max_length=200, - ), - ] - metadata: Annotated[ - Metadata | None, Field(description='Rich metadata extracted from the artifact') - ] = None - provenance: Annotated[ - provenance_1.Provenance | None, - Field( - description='Provenance metadata for this artifact. Serves as the default provenance for all assets within this artifact — individual assets can override with their own provenance.' - ), - ] = None - identifiers: Annotated[ - Identifiers | None, Field(description='Platform-specific identifiers for this artifact') - ] = None diff --git a/src/adcp/types/generated_poc/content_standards/artifact_webhook_payload.py b/src/adcp/types/generated_poc/content_standards/artifact_webhook_payload.py deleted file mode 100644 index 39c89399d..000000000 --- a/src/adcp/types/generated_poc/content_standards/artifact_webhook_payload.py +++ /dev/null @@ -1,73 +0,0 @@ -# generated by datamodel-codegen: -# filename: content_standards/artifact_webhook_payload.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field - -from ..core import ext as ext_1 -from . import artifact as artifact_1 - - -class Pagination(AdCPBaseModel): - total_artifacts: Annotated[ - SchemaInt | None, Field(description='Total artifacts in the delivery period') - ] = None - batch_number: Annotated[ - SchemaInt | None, Field(description='Current batch number (1-indexed)') - ] = None - total_batches: Annotated[ - SchemaInt | None, Field(description='Total batches for this delivery period') - ] = None - - -class Artifact(AdCPBaseModel): - artifact: Annotated[artifact_1.Artifact, Field(description='The content artifact')] - delivered_at: Annotated[ - AwareDatetime, Field(description='When the impression was delivered (ISO 8601)') - ] - impression_id: Annotated[ - str | None, - Field(description='Optional impression identifier for correlation with delivery reports'), - ] = None - package_id: Annotated[ - str | None, Field(description='Package within the media buy this artifact relates to') - ] = None - - -class ArtifactWebhookPayload(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - idempotency_key: Annotated[ - str, - Field( - description='Sender-generated key stable across retries of the same webhook event. Sales agents MUST generate a cryptographically random value (UUID v4 recommended) per distinct emission of a batch and reuse the same key on every retry. Recipients MUST dedupe by this key, scoped to the authenticated sender identity (HMAC secret or Bearer credential) — keys from different sales agents are independent. Distinct from `batch_id`, which identifies the logical batch: `idempotency_key` identifies this specific emission event, so a re-emission of the same `batch_id` (e.g., after a correction) is a different event and MUST carry a fresh `idempotency_key`.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - media_buy_id: Annotated[ - str, Field(description='Media buy identifier these artifacts belong to') - ] - batch_id: Annotated[ - str, - Field( - description='Unique identifier for this batch of artifacts. Use for deduplication and acknowledgment.' - ), - ] - timestamp: Annotated[ - AwareDatetime, Field(description='When this batch was generated (ISO 8601)') - ] - artifacts: Annotated[ - list[Artifact], Field(description='Content artifacts from delivered impressions') - ] - pagination: Annotated[ - Pagination | None, Field(description='Pagination info when batching large artifact sets') - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/content_standards/calibrate_content_request.py b/src/adcp/types/generated_poc/content_standards/calibrate_content_request.py deleted file mode 100644 index 18c22b93e..000000000 --- a/src/adcp/types/generated_poc/content_standards/calibrate_content_request.py +++ /dev/null @@ -1,30 +0,0 @@ -# generated by datamodel-codegen: -# filename: content_standards/calibrate_content_request.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import Field - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.version_envelope import AdcpVersionEnvelope -from . import artifact as artifact_1 - - -class CalibrateContentRequest(AdcpVersionEnvelope): - standards_id: Annotated[str, Field(description='Standards configuration to calibrate against')] - artifact: Annotated[artifact_1.Artifact, Field(description='Artifact to evaluate')] - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for at-most-once execution. If a request with the same key has already been processed, the server returns the original response without re-processing. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/content_standards/calibrate_content_response.py b/src/adcp/types/generated_poc/content_standards/calibrate_content_response.py deleted file mode 100644 index c0a3c7623..000000000 --- a/src/adcp/types/generated_poc/content_standards/calibrate_content_response.py +++ /dev/null @@ -1,54 +0,0 @@ -# generated by datamodel-codegen: -# filename: content_standards/calibrate_content_response.json -# timestamp: preserved-by-post-generate-fixes - -from __future__ import annotations - -from typing import Annotated, TypeAlias - -from pydantic import ConfigDict, Field - -from ..core.version_envelope import AdcpVersionEnvelope -from ..core import context as context_1 -from ..core import error as error_1 -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..enums import binary_verdict as binary_verdict_1 -from ..enums import feature_check_status as feature_check_status_1 - - -class Feature(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - feature_id: str - status: feature_check_status_1.FeatureCheckStatus - policy_id: str | None = None - explanation: str | None = None - confidence: Annotated[float, Field(ge=0, le=1)] | None = None - - -class CalibrateContentResponse1(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - verdict: binary_verdict_1.BinaryVerdict - confidence: Annotated[float, Field(ge=0, le=1)] | None = None - explanation: str | None = None - features: list[Feature] | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class CalibrateContentResponse2(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - errors: list[error_1.Error] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -CalibrateContentResponse: TypeAlias = CalibrateContentResponse1 | CalibrateContentResponse2 - - -__all__ = [ - 'CalibrateContentResponse', - 'CalibrateContentResponse1', - 'CalibrateContentResponse2', - 'Feature', -] diff --git a/src/adcp/types/generated_poc/content_standards/content_standards.py b/src/adcp/types/generated_poc/content_standards/content_standards.py deleted file mode 100644 index 18137b446..000000000 --- a/src/adcp/types/generated_poc/content_standards/content_standards.py +++ /dev/null @@ -1,78 +0,0 @@ -# generated by datamodel-codegen: -# filename: content_standards/content_standards.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import Field - -from ..core import ext as ext_1 -from ..core import vendor_pricing_option -from ..enums import channels -from ..governance import policy_entry -from . import artifact - - -class CalibrationExemplars(AdCPBaseModel): - pass_: Annotated[ - list[artifact.Artifact] | None, - Field(alias='pass', description='Artifacts that pass the content standards'), - ] = None - fail: Annotated[ - list[artifact.Artifact] | None, - Field(description='Artifacts that fail the content standards'), - ] = None - - -class ContentStandards(AdCPBaseModel): - standards_id: Annotated[ - str, Field(description='Unique identifier for this standards configuration') - ] - name: Annotated[ - str | None, Field(description='Human-readable name for this standards configuration') - ] = None - countries_all: Annotated[ - list[str] | None, - Field( - description='ISO 3166-1 alpha-2 country codes. Standards apply in ALL listed countries (AND logic).', - min_length=1, - ), - ] = None - channels_any: Annotated[ - list[channels.MediaChannel] | None, - Field( - description='Advertising channels. Standards apply to ANY of the listed channels (OR logic).', - min_length=1, - ), - ] = None - languages_any: Annotated[ - list[str] | None, - Field( - description="BCP 47 language tags (e.g., 'en', 'de', 'fr'). Standards apply to content in ANY of these languages (OR logic). Content in unlisted languages is not covered by these standards.", - min_length=1, - ), - ] = None - policies: Annotated[ - list[policy_entry.PolicyEntry] | None, - Field( - description='Bespoke policies for this content-standards configuration, using the same shape as registry entries. Each policy is addressable by policy_id; governance findings reference the policy_id that triggered them.', - min_length=1, - ), - ] = None - calibration_exemplars: Annotated[ - CalibrationExemplars | None, - Field( - description='Training/test set to calibrate policy interpretation. Provides concrete examples of pass/fail decisions.' - ), - ] = None - pricing_options: Annotated[ - list[vendor_pricing_option.VendorPricingOption] | None, - Field( - description='Pricing options for this content standards service. The buyer passes the selected pricing_option_id in report_usage for billing verification.', - min_length=1, - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/content_standards/create_content_standards_request.py b/src/adcp/types/generated_poc/content_standards/create_content_standards_request.py deleted file mode 100644 index cb8fb330c..000000000 --- a/src/adcp/types/generated_poc/content_standards/create_content_standards_request.py +++ /dev/null @@ -1,130 +0,0 @@ -# generated by datamodel-codegen: -# filename: content_standards/create_content_standards_request.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, model_validator - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import channels -from ..governance import policy_entry -from . import artifact - - -class Scope(AdCPBaseModel): - countries_all: Annotated[ - list[str] | None, - Field( - description='ISO 3166-1 alpha-2 country codes. Standards apply in ALL listed countries (AND logic).', - min_length=1, - ), - ] = None - channels_any: Annotated[ - list[channels.MediaChannel] | None, - Field( - description='Advertising channels. Standards apply to ANY of the listed channels (OR logic).', - min_length=1, - ), - ] = None - languages_any: Annotated[ - list[str], - Field( - description="BCP 47 language tags (e.g., 'en', 'de', 'fr'). Standards apply to content in ANY of these languages (OR logic). Content in unlisted languages is not covered by these standards.", - min_length=1, - ), - ] - description: Annotated[ - str | None, Field(description='Human-readable description of this scope') - ] = None - - -class Pass(AdCPBaseModel): - type: Annotated[Literal['url'], Field(description='Indicates this is a URL reference')] = 'url' - value: Annotated[ - AnyUrl, - Field( - description="Full URL to a specific page (e.g., 'https://espn.com/nba/story/_/id/12345/lakers-win')" - ), - ] - language: Annotated[ - str | None, Field(description='BCP 47 language tag for content at this URL') - ] = None - - -class Fail(AdCPBaseModel): - type: Annotated[Literal['url'], Field(description='Indicates this is a URL reference')] = 'url' - value: Annotated[ - AnyUrl, - Field( - description="Full URL to a specific page (e.g., 'https://news.example.com/controversial-article')" - ), - ] - language: Annotated[ - str | None, Field(description='BCP 47 language tag for content at this URL') - ] = None - - -class CalibrationExemplars(AdCPBaseModel): - pass_: Annotated[ - list[Pass | artifact.Artifact] | None, - Field(alias='pass', description='Content that passes the standards'), - ] = None - fail: Annotated[ - list[Fail | artifact.Artifact] | None, Field(description='Content that fails the standards') - ] = None - - -class CreateContentStandardsRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[Scope, Field(description='Where this standards configuration applies')] - registry_policy_ids: Annotated[ - list[str] | None, - Field( - description="Registry policy IDs to use as the evaluation basis for this content standard. When provided, the agent resolves policies from the registry and uses their policy text and exemplars as the evaluation criteria. The 'policy' field becomes optional when registry_policy_ids is provided." - ), - ] = None - policies: Annotated[ - list[policy_entry.PolicyEntry] | None, - Field( - description='Bespoke policies for this content-standards configuration, using the same shape as registry entries. Each policy is addressable by policy_id and carries its own enforcement (must|should); governance findings reference the policy_id that triggered them. Inline bespoke policies can omit version/name/category (defaulted by the server). Combines with registry_policy_ids — registry policies and bespoke policies are both evaluated. Bespoke policy_ids MUST be flat (no colons/slashes) to avoid collision with namespaced registry ids.', - min_length=1, - ), - ] = None - calibration_exemplars: Annotated[ - CalibrationExemplars | None, - Field( - description='Training/test set to calibrate policy interpretation. Use URL references for pages to be fetched and analyzed, or full artifacts for pre-extracted content.' - ), - ] = None - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for this request. Prevents duplicate content standards creation on retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - @model_validator(mode='after') - def _require_schema_required_group(self) -> CreateContentStandardsRequest: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('policies',), ('registry_policy_ids',),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'CreateContentStandardsRequest requires at least one of these field groups: policies | registry_policy_ids' - ) diff --git a/src/adcp/types/generated_poc/content_standards/create_content_standards_response.py b/src/adcp/types/generated_poc/content_standards/create_content_standards_response.py deleted file mode 100644 index 5d9c11865..000000000 --- a/src/adcp/types/generated_poc/content_standards/create_content_standards_response.py +++ /dev/null @@ -1,48 +0,0 @@ -# generated by datamodel-codegen: -# filename: content_standards/create_content_standards_response.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types.response_dispatch import ResponseArmDispatchMixin - -from typing import Annotated - -from pydantic import Field - -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope - - -class CreateContentStandardsResponse(ResponseArmDispatchMixin, AdcpVersionEnvelope, ProtocolEnvelope): - """Constructible compatibility base for generated response arms.""" - - @classmethod - def _response_arm_models(cls) -> tuple[type[CreateContentStandardsResponse], ...]: - return ( - CreateContentStandardsResponse1, - CreateContentStandardsResponse2, - ) - - -class CreateContentStandardsResponse1(CreateContentStandardsResponse): - standards_id: Annotated[ - str, Field(description='Unique identifier for the created standards configuration') - ] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class CreateContentStandardsResponse2(CreateContentStandardsResponse): - errors: list[error.Error] - conflicting_standards_id: Annotated[ - str | None, - Field( - description='If the error is a scope conflict, the ID of the existing standards that conflict' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/content_standards/get_content_standards_request.py b/src/adcp/types/generated_poc/content_standards/get_content_standards_request.py deleted file mode 100644 index ce6b4190a..000000000 --- a/src/adcp/types/generated_poc/content_standards/get_content_standards_request.py +++ /dev/null @@ -1,21 +0,0 @@ -# generated by datamodel-codegen: -# filename: content_standards/get_content_standards_request.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import Field - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.version_envelope import AdcpVersionEnvelope - - -class GetContentStandardsRequest(AdcpVersionEnvelope): - standards_id: Annotated[ - str, Field(description='Identifier for the standards configuration to retrieve') - ] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/content_standards/get_content_standards_response.py b/src/adcp/types/generated_poc/content_standards/get_content_standards_response.py deleted file mode 100644 index 51020f83b..000000000 --- a/src/adcp/types/generated_poc/content_standards/get_content_standards_response.py +++ /dev/null @@ -1,38 +0,0 @@ -# generated by datamodel-codegen: -# filename: content_standards/get_content_standards_response.json -# timestamp: preserved-by-post-generate-fixes - -from __future__ import annotations - -from typing import TypeAlias - -from pydantic import ConfigDict - -from ..core.version_envelope import AdcpVersionEnvelope -from ..core import context as context_1 -from ..core import error as error_1 -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope - - -class GetContentStandardsResponse1(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class GetContentStandardsResponse2(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - errors: list[error_1.Error] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -GetContentStandardsResponse: TypeAlias = GetContentStandardsResponse1 | GetContentStandardsResponse2 - - -__all__ = [ - 'GetContentStandardsResponse', - 'GetContentStandardsResponse1', - 'GetContentStandardsResponse2', -] diff --git a/src/adcp/types/generated_poc/content_standards/get_media_buy_artifacts_request.py b/src/adcp/types/generated_poc/content_standards/get_media_buy_artifacts_request.py deleted file mode 100644 index dd63b64e9..000000000 --- a/src/adcp/types/generated_poc/content_standards/get_media_buy_artifacts_request.py +++ /dev/null @@ -1,67 +0,0 @@ -# generated by datamodel-codegen: -# filename: content_standards/get_media_buy_artifacts_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field, StrictBool - -from ..core import account_ref -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.version_envelope import AdcpVersionEnvelope - - -class TimeRange(AdCPBaseModel): - start: Annotated[AwareDatetime | None, Field(description='Start of time range (inclusive)')] = ( - None - ) - end: Annotated[AwareDatetime | None, Field(description='End of time range (exclusive)')] = None - - -class Pagination(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - max_results: Annotated[ - SchemaInt | None, - Field(description='Maximum number of artifacts to return per page', ge=1, le=10000), - ] = 1000 - cursor: Annotated[ - str | None, - Field(description='Opaque cursor from a previous response to fetch the next page'), - ] = None - - -class GetMediaBuyArtifactsRequest(AdcpVersionEnvelope): - account: Annotated[ - account_ref.AccountReference | None, - Field( - description='Filter artifacts to a specific account. When omitted, returns artifacts across all accessible accounts.' - ), - ] = None - media_buy_id: Annotated[str, Field(description='Media buy to get artifacts from')] - package_ids: Annotated[ - list[str] | None, - Field(description='Filter to specific packages within the media buy', min_length=1), - ] = None - failures_only: Annotated[ - StrictBool | None, - Field( - description="When true, only return artifacts where the seller's local model returned local_verdict: 'fail'. Useful for auditing false positives. Not useful when the seller does not run a local evaluation model (all verdicts are 'unevaluated')." - ), - ] = False - time_range: Annotated[TimeRange | None, Field(description='Filter to specific time period')] = ( - None - ) - pagination: Annotated[ - Pagination | None, - Field( - description='Pagination parameters. Uses higher limits than standard pagination because artifact result sets can be very large.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/content_standards/get_media_buy_artifacts_response.py b/src/adcp/types/generated_poc/content_standards/get_media_buy_artifacts_response.py deleted file mode 100644 index 54d0504e5..000000000 --- a/src/adcp/types/generated_poc/content_standards/get_media_buy_artifacts_response.py +++ /dev/null @@ -1,73 +0,0 @@ -# generated by datamodel-codegen: -# filename: content_standards/get_media_buy_artifacts_response.json -# timestamp: preserved-by-post-generate-fixes - -from __future__ import annotations - -from typing import Literal, TypeAlias - -from pydantic import AwareDatetime, ConfigDict - -from ..core.version_envelope import AdcpVersionEnvelope -from . import artifact as artifact_1 -from ..core import context as context_1 -from ..core import error as error_1 -from ..core import ext as ext_1 -from ..core import pagination_response as pagination_response_1 -from ..core.protocol_envelope import ProtocolEnvelope - - -class BrandContext(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - brand_id: str | None = None - sku_id: str | None = None - - -class Artifact(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - record_id: str - timestamp: AwareDatetime | None = None - package_id: str | None = None - artifact: artifact_1.Artifact - country: str | None = None - channel: str | None = None - brand_context: BrandContext | None = None - local_verdict: Literal['pass', 'fail', 'unevaluated'] | None = None - - -class CollectionInfo(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - total_deliveries: int | None = None - total_collected: int | None = None - returned_count: int | None = None - effective_rate: float | None = None - - -class GetMediaBuyArtifactsResponse1(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - media_buy_id: str - artifacts: list[Artifact] - collection_info: CollectionInfo | None = None - pagination: pagination_response_1.PaginationResponse | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class GetMediaBuyArtifactsResponse2(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - errors: list[error_1.Error] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -GetMediaBuyArtifactsResponse: TypeAlias = GetMediaBuyArtifactsResponse1 | GetMediaBuyArtifactsResponse2 - - -__all__ = [ - 'GetMediaBuyArtifactsResponse', - 'GetMediaBuyArtifactsResponse1', - 'GetMediaBuyArtifactsResponse2', - 'Artifact', - 'BrandContext', - 'CollectionInfo', -] diff --git a/src/adcp/types/generated_poc/content_standards/list_content_standards_request.py b/src/adcp/types/generated_poc/content_standards/list_content_standards_request.py deleted file mode 100644 index b2dd35aa8..000000000 --- a/src/adcp/types/generated_poc/content_standards/list_content_standards_request.py +++ /dev/null @@ -1,34 +0,0 @@ -# generated by datamodel-codegen: -# filename: content_standards/list_content_standards_request.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import ConfigDict, Field - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core import pagination_request -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import channels as channels_1 - - -class ListContentStandardsRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - channels: Annotated[ - list[channels_1.MediaChannel] | None, Field(description='Filter by channel', min_length=1) - ] = None - languages: Annotated[ - list[str] | None, Field(description='Filter by BCP 47 language tags', min_length=1) - ] = None - countries: Annotated[ - list[str] | None, - Field(description='Filter by ISO 3166-1 alpha-2 country codes', min_length=1), - ] = None - pagination: pagination_request.PaginationRequest | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/content_standards/list_content_standards_response.py b/src/adcp/types/generated_poc/content_standards/list_content_standards_response.py deleted file mode 100644 index 3c1b9465f..000000000 --- a/src/adcp/types/generated_poc/content_standards/list_content_standards_response.py +++ /dev/null @@ -1,46 +0,0 @@ -# generated by datamodel-codegen: -# filename: content_standards/list_content_standards_response.json -# timestamp: 2026-09-05T19:08:34+00:00 - -from __future__ import annotations - -from adcp.types.response_dispatch import ResponseArmDispatchMixin - -from typing import Annotated - -from pydantic import Field - -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 -from ..core import pagination_response -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from . import content_standards - - -class ListContentStandardsResponse(ResponseArmDispatchMixin, AdcpVersionEnvelope, ProtocolEnvelope): - """Constructible compatibility base for generated response arms.""" - - @classmethod - def _response_arm_models(cls) -> tuple[type[ListContentStandardsResponse], ...]: - return ( - ListContentStandardsResponse1, - ListContentStandardsResponse2, - ) - - -class ListContentStandardsResponse2(ListContentStandardsResponse): - errors: list[error.Error] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class ListContentStandardsResponse1(ListContentStandardsResponse): - standards: Annotated[ - list[content_standards.ContentStandards], - Field(description='Array of content standards configurations matching the filter criteria'), - ] - pagination: pagination_response.PaginationResponse | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/content_standards/update_content_standards_request.py b/src/adcp/types/generated_poc/content_standards/update_content_standards_request.py deleted file mode 100644 index 04509bb48..000000000 --- a/src/adcp/types/generated_poc/content_standards/update_content_standards_request.py +++ /dev/null @@ -1,121 +0,0 @@ -# generated by datamodel-codegen: -# filename: content_standards/update_content_standards_request.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import channels -from ..governance import policy_entry -from . import artifact - - -class Scope(AdCPBaseModel): - countries_all: Annotated[ - list[str] | None, - Field( - description='ISO 3166-1 alpha-2 country codes. Standards apply in ALL listed countries (AND logic).', - min_length=1, - ), - ] = None - channels_any: Annotated[ - list[channels.MediaChannel] | None, - Field( - description='Advertising channels. Standards apply to ANY of the listed channels (OR logic).', - min_length=1, - ), - ] = None - languages_any: Annotated[ - list[str] | None, - Field( - description="BCP 47 language tags (e.g., 'en', 'de', 'fr'). Standards apply to content in ANY of these languages (OR logic). Content in unlisted languages is not covered by these standards.", - min_length=1, - ), - ] = None - description: Annotated[ - str | None, Field(description='Human-readable description of this scope') - ] = None - - -class Pass(AdCPBaseModel): - type: Annotated[Literal['url'], Field(description='Indicates this is a URL reference')] = 'url' - value: Annotated[ - AnyUrl, - Field( - description="Full URL to a specific page (e.g., 'https://espn.com/nba/story/_/id/12345/lakers-win')" - ), - ] - language: Annotated[ - str | None, Field(description='BCP 47 language tag for content at this URL') - ] = None - - -class Fail(AdCPBaseModel): - type: Annotated[Literal['url'], Field(description='Indicates this is a URL reference')] = 'url' - value: Annotated[ - AnyUrl, - Field( - description="Full URL to a specific page (e.g., 'https://news.example.com/controversial-article')" - ), - ] - language: Annotated[ - str | None, Field(description='BCP 47 language tag for content at this URL') - ] = None - - -class CalibrationExemplars(AdCPBaseModel): - pass_: Annotated[ - list[Pass | artifact.Artifact] | None, - Field(alias='pass', description='Content that passes the standards'), - ] = None - fail: Annotated[ - list[Fail | artifact.Artifact] | None, Field(description='Content that fails the standards') - ] = None - - -class UpdateContentStandardsRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - standards_id: Annotated[str, Field(description='ID of the standards configuration to update')] - scope: Annotated[ - Scope | None, - Field(description='Updated scope for where this standards configuration applies'), - ] = None - registry_policy_ids: Annotated[ - list[str] | None, - Field( - description='Registry policy IDs to use as the evaluation basis. When provided, the agent resolves policies from the registry and uses their policy text and exemplars as the evaluation criteria.' - ), - ] = None - policies: Annotated[ - list[policy_entry.PolicyEntry] | None, - Field( - description='Updated bespoke policies for this content-standards configuration, using the same shape as registry entries. Replaces the existing policies array; use stable policy_ids to track policies across versions. Combines with registry_policy_ids. Bespoke policy_ids MUST be flat (no colons/slashes).', - min_length=1, - ), - ] = None - calibration_exemplars: Annotated[ - CalibrationExemplars | None, - Field( - description='Updated training/test set to calibrate policy interpretation. Use URL references for pages to be fetched and analyzed, or full artifacts for pre-extracted content.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for at-most-once execution. If a request with the same key has already been processed, the server returns the original response without re-processing. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] diff --git a/src/adcp/types/generated_poc/content_standards/update_content_standards_response.py b/src/adcp/types/generated_poc/content_standards/update_content_standards_response.py deleted file mode 100644 index 31c5bb8f5..000000000 --- a/src/adcp/types/generated_poc/content_standards/update_content_standards_response.py +++ /dev/null @@ -1,58 +0,0 @@ -# generated by datamodel-codegen: -# filename: content_standards/update_content_standards_response.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types.response_dispatch import ResponseArmDispatchMixin - -from typing import Annotated, Literal - -from pydantic import ConfigDict, Field - -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope - - -class UpdateContentStandardsResponse(ResponseArmDispatchMixin, AdcpVersionEnvelope, ProtocolEnvelope): - """Constructible compatibility base for generated response arms.""" - - @classmethod - def _response_arm_models(cls) -> tuple[type[UpdateContentStandardsResponse], ...]: - return ( - UpdateContentStandardsResponse1, - UpdateContentStandardsResponse2, - ) - - -class UpdateContentStandardsResponse1(UpdateContentStandardsResponse): - model_config = ConfigDict( - extra='allow', - ) - success: Annotated[ - Literal[True], Field(description='Indicates the update was applied successfully') - ] - standards_id: Annotated[str, Field(description='ID of the updated standards configuration')] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class UpdateContentStandardsResponse2(UpdateContentStandardsResponse): - model_config = ConfigDict( - extra='allow', - ) - success: Annotated[Literal[False], Field(description='Indicates the update failed')] - errors: Annotated[ - list[error.Error], Field(description='Errors that occurred during the update', min_length=1) - ] - conflicting_standards_id: Annotated[ - str | None, - Field( - description='If scope change conflicts with another configuration, the ID of the conflicting standards' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/content_standards/validate_content_delivery_request.py b/src/adcp/types/generated_poc/content_standards/validate_content_delivery_request.py deleted file mode 100644 index a4d69e93b..000000000 --- a/src/adcp/types/generated_poc/content_standards/validate_content_delivery_request.py +++ /dev/null @@ -1,63 +0,0 @@ -# generated by datamodel-codegen: -# filename: content_standards/validate_content_delivery_request.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, Field, StrictBool - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.version_envelope import AdcpVersionEnvelope -from . import artifact as artifact_1 - - -class BrandContext(AdCPBaseModel): - brand_id: Annotated[str | None, Field(description='Brand identifier')] = None - sku_id: Annotated[str | None, Field(description='Product/SKU identifier if applicable')] = None - - -class Record(AdCPBaseModel): - record_id: Annotated[str, Field(description='Unique identifier for this delivery record')] - media_buy_id: Annotated[ - str | None, - Field(description='Media buy this record belongs to (when batching across multiple buys)'), - ] = None - timestamp: Annotated[AwareDatetime | None, Field(description='When the delivery occurred')] = ( - None - ) - artifact: Annotated[artifact_1.Artifact, Field(description='Artifact where ad was delivered')] - country: Annotated[ - str | None, Field(description='ISO 3166-1 alpha-2 country code where delivery occurred') - ] = None - channel: Annotated[ - str | None, Field(description='Channel type (e.g., display, video, audio, social)') - ] = None - brand_context: Annotated[ - BrandContext | None, - Field( - description='Brand information for policy evaluation. Schema TBD - placeholder for brand identifiers.' - ), - ] = None - - -class ValidateContentDeliveryRequest(AdcpVersionEnvelope): - standards_id: Annotated[str, Field(description='Standards configuration to validate against')] - records: Annotated[ - list[Record], - Field( - description='Delivery records to validate (max 10,000)', max_length=10000, min_length=1 - ), - ] - feature_ids: Annotated[ - list[str] | None, - Field(description='Specific features to evaluate (defaults to all)', min_length=1), - ] = None - include_passed: Annotated[ - StrictBool | None, Field(description='Include passed records in results') - ] = True - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/content_standards/validate_content_delivery_response.py b/src/adcp/types/generated_poc/content_standards/validate_content_delivery_response.py deleted file mode 100644 index 0c75fb0d5..000000000 --- a/src/adcp/types/generated_poc/content_standards/validate_content_delivery_response.py +++ /dev/null @@ -1,68 +0,0 @@ -# generated by datamodel-codegen: -# filename: content_standards/validate_content_delivery_response.json -# timestamp: preserved-by-post-generate-fixes - -from __future__ import annotations - -from typing import Annotated, TypeAlias - -from pydantic import ConfigDict, Field - -from ..core.version_envelope import AdcpVersionEnvelope -from ..core import context as context_1 -from ..core import error as error_1 -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..enums import binary_verdict as binary_verdict_1 -from ..enums import feature_check_status as feature_check_status_1 - - -class Summary(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - total_records: int - passed_records: int - failed_records: int - - -class Feature(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - feature_id: str - status: feature_check_status_1.FeatureCheckStatus - policy_id: str | None = None - explanation: str | None = None - confidence: Annotated[float, Field(ge=0, le=1)] | None = None - - -class Result(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - record_id: str - verdict: binary_verdict_1.BinaryVerdict - features: list[Feature] | None = None - - -class ValidateContentDeliveryResponse1(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - summary: Summary - results: list[Result] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class ValidateContentDeliveryResponse2(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - errors: list[error_1.Error] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -ValidateContentDeliveryResponse: TypeAlias = ValidateContentDeliveryResponse1 | ValidateContentDeliveryResponse2 - - -__all__ = [ - 'ValidateContentDeliveryResponse', - 'ValidateContentDeliveryResponse1', - 'ValidateContentDeliveryResponse2', - 'Feature', - 'Result', - 'Summary', -] diff --git a/src/adcp/types/generated_poc/core/__init__.py b/src/adcp/types/generated_poc/core/__init__.py deleted file mode 100644 index 5a3cca36b..000000000 --- a/src/adcp/types/generated_poc/core/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# generated by datamodel-codegen: -# filename: .schema_temp -# timestamp: 2025-11-22T15:23:24+00:00 diff --git a/src/adcp/types/generated_poc/core/acceptance_policy_profile_ids.py b/src/adcp/types/generated_poc/core/acceptance_policy_profile_ids.py deleted file mode 100644 index dfaf4589f..000000000 --- a/src/adcp/types/generated_poc/core/acceptance_policy_profile_ids.py +++ /dev/null @@ -1,27 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/acceptance_policy_profile_ids.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from adcp.types._scalar import ScalarStr - -from typing import Annotated - -from pydantic import Field, RootModel - - -class AcceptancePolicyProfileId(ScalarStr): - __slots__ = () - _constraints = {'min_length': 1} - - -class AcceptancePolicyProfileIds(RootModel[list[AcceptancePolicyProfileId]]): - root: Annotated[ - list[AcceptancePolicyProfileId], - Field( - description='Acceptance-policy profiles from the seller catalog that apply to this product in addition to seller defaults. Profiles compose restrictively; the most restrictive matching disposition wins.', - min_length=1, - title='Acceptance Policy Profile IDs', - ), - ] diff --git a/src/adcp/types/generated_poc/core/account.py b/src/adcp/types/generated_poc/core/account.py deleted file mode 100644 index 0142c4c4c..000000000 --- a/src/adcp/types/generated_poc/core/account.py +++ /dev/null @@ -1,266 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/account.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat - -from ..enums import account_scope as account_scope_1 -from ..enums import account_status, billing_party, cloud_storage_protocol -from ..enums import payment_terms as payment_terms_1 -from . import account_identity_change, brand_ref, business_entity -from . import ext as ext_1 -from . import notification_config -from . import operator_unit as operator_unit_1 -from . import reporting_delivery_config_state, webhook_activity_record - - -class CreditLimit(AdCPBaseModel): - amount: Annotated[StrictFloat, Field(ge=0.0)] - currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] - - -class Setup(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - url: Annotated[ - AnyUrl | None, - Field( - description='URL where the human can complete the required action (credit application, legal agreement, add funds).' - ), - ] = None - message: Annotated[str, Field(description="Human-readable description of what's needed.")] - expires_at: Annotated[ - AwareDatetime | None, Field(description='When this setup link expires.') - ] = None - - -class GovernanceAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: Annotated[AnyUrl, Field(description='Governance agent endpoint URL. Must use HTTPS.')] - - -class Format(StrEnum): - jsonl = 'jsonl' - csv = 'csv' - parquet = 'parquet' - avro = 'avro' - orc = 'orc' - - -class Compression(StrEnum): - gzip = 'gzip' - none = 'none' - - -class ReportingBucket(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - protocol: Annotated[ - cloud_storage_protocol.CloudStorageProtocol, Field(description='Cloud storage protocol') - ] - bucket: Annotated[ - str, - Field( - description='Bucket or container name', - max_length=63, - min_length=3, - pattern='^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$', - ), - ] - prefix: Annotated[ - str | None, - Field( - description='Path prefix within the bucket. Seller appends date-based partitioning beneath this prefix.', - examples=['accounts/pinnacle/adcp', 'reporting/2024'], - max_length=512, - pattern='^[a-zA-Z0-9/_.-]+$', - ), - ] = None - region: Annotated[ - str | None, - Field( - description='Cloud region for the bucket', - examples=['us-east-1', 'europe-west1'], - max_length=64, - pattern='^[a-z0-9-]+$', - ), - ] = None - format: Annotated[ - Format | None, - Field( - description='File format for delivered files. Parquet, Avro, and ORC use internal compression (the top-level compression field is ignored for these formats).' - ), - ] = Format.jsonl - compression: Annotated[ - Compression | None, Field(description='Compression applied to delivered files') - ] = Compression.gzip - file_retention_days: Annotated[ - SchemaInt, - Field( - description='How long reporting files are retained in the bucket before deletion. Buyers must read files within this window. Minimum recommended: 14 days.', - examples=[14, 30, 90], - ge=1, - ), - ] - setup_instructions: Annotated[ - AnyUrl | None, - Field( - description='URL to documentation for configuring buyer read access to this bucket (IAM role, service account, etc.). Operator-facing documentation — buyer agents MUST NOT auto-fetch this URL; surface it to a human operator. If an implementation fetches it (for preview), apply webhook URL SSRF validation and do not pass the fetched content into an LLM context without indirect-prompt-injection guarding. See docs/media-buy/media-buys/optimization-reporting#security-considerations-for-offline-delivery.' - ), - ] = None - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - account_id: Annotated[str, Field(description='Unique identifier for this account')] - name: Annotated[ - str, Field(description="Human-readable account name (e.g., 'Acme', 'Acme c/o Pinnacle')") - ] - advertiser: Annotated[ - str | None, Field(description='The advertiser whose rates apply to this account') - ] = None - billing_proxy: Annotated[ - str | None, - Field( - description='Optional intermediary who receives invoices on behalf of the advertiser (e.g., agency)' - ), - ] = None - status: Annotated[ - account_status.AccountStatus, - Field( - description='Account lifecycle status. See the Accounts Protocol overview for the operations matrix showing which tasks are permitted in each state.' - ), - ] - brand: Annotated[ - brand_ref.BrandReference | None, - Field(description='Brand reference identifying the advertiser'), - ] = None - operator: Annotated[ - str | None, - Field( - description="Domain of the entity operating this account. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - operator_unit: Annotated[ - operator_unit_1.OperatorUnit | None, - Field( - description='Operator-owned business unit, agency seat, or platform account associated with this advertiser account. The id round-trips from the natural key; name is mutable display metadata. This is distinct from account_id, which belongs to the seller/storefront namespace.' - ), - ] = None - revision: Annotated[ - SchemaInt | None, - Field( - description='Monotonically increasing optimistic-concurrency token for this account. Incremented on every persisted settings change, identity-change request, and identity-change disposition; reads, dry runs, validation failures, and exact idempotency replays do not increment it. Pass the latest observed value in a sync_accounts settings-update entry to prevent lost updates.', - ge=1, - ), - ] = None - identity_change: Annotated[ - account_identity_change.AccountIdentityChange | None, - Field( - description='Pending or rejected operator-identity transition. While present, the top-level operator and operator_unit remain the current canonical identity. Re-read list_accounts until the request is applied (canonical fields change and this object disappears) or rejected.' - ), - ] = None - currency: Annotated[ - str | None, - Field( - description="Immutable transaction currency when the seller's advertiser object is currency-bound. Media buys on this account MUST use this currency. Omit when the account selects currency independently per media buy.", - pattern='^[A-Z]{3}$', - ), - ] = None - timezone: Annotated[ - str | None, - Field( - description='Immutable operational timezone for this account, expressed as UTC or an IANA timezone identifier. AdCP 3.2 sellers return it on every account. It is the default calendar-day boundary for account-scoped behavior unless a feature explicitly declares another timezone basis. For buyer-selected account_fixed provisioning it participates in the natural account key.', - min_length=1, - ), - ] = None - billing: Annotated[ - billing_party.BillingParty | None, - Field( - description="Who is invoiced on this account. See billing_entity for the invoiced party's business details." - ), - ] = None - billing_entity: Annotated[ - business_entity.BusinessEntity | None, - Field( - description='Current canonical business entity for the party responsible for payment. Contains the legal name, tax IDs, and address needed for formal B2B invoicing. Corresponds to whoever billing points to (operator, agent, or advertiser). When this account appears in a response, bank details MUST be omitted and the request-only destination_billing_entity MUST NOT be exposed.' - ), - ] = None - destination_billing_entity: Annotated[ - Any | None, - Field(description='Request-only staging field. It MUST NOT appear in account read models.'), - ] = None - rate_card: Annotated[ - str | None, Field(description='Identifier for the rate card applied to this account') - ] = None - payment_terms: Annotated[ - payment_terms_1.PaymentTerms | None, - Field( - description='Payment terms agreed for this account. Binding for all invoices when the account is active.' - ), - ] = None - credit_limit: Annotated[ - CreditLimit | None, Field(description='Maximum outstanding balance allowed') - ] = None - setup: Annotated[ - Setup | None, - Field( - description="Present when status is 'pending_approval'. Contains next steps for completing account activation." - ), - ] = None - account_scope: account_scope_1.AccountScope | None = None - governance_agents: Annotated[ - list[GovernanceAgent] | None, - Field( - description="Governance agent endpoint registered on this account. Exactly one entry per sync_governance's one-agent-per-account invariant. The array shape is preserved for wire compatibility with 3.0; `maxItems: 1` is load-bearing and mirrors the singular `governance_context` on the protocol envelope. Authentication credentials are write-only and not included in responses — use sync_governance to set or update credentials.", - max_length=1, - min_length=1, - ), - ] = None - reporting_bucket: Annotated[ - ReportingBucket | None, - Field( - description="Cloud storage bucket where the seller delivers offline reporting files for this account. Seller provisions a dedicated bucket or a per-account prefix within a shared bucket, and grants the buyer read access out-of-band. Access MUST be scoped at the IAM layer so each account can only read its own prefix — bucket-wide grants are non-compliant even with per-account prefixes. Seller MUST revoke access when the account's status transitions to inactive, suspended, or closed. See security considerations for offline delivery in docs/media-buy/media-buys/optimization-reporting. Only present when the seller supports offline delivery (reporting_delivery_methods includes 'offline' in capabilities)." - ), - ] = None - sandbox: Annotated[ - StrictBool | None, - Field( - description='When true, this is a sandbox account — no real platform calls, no real spend. For account-id namespaces, sandbox accounts are pre-existing test accounts on the platform discovered via list_accounts or supplied out-of-band. For buyer-declared accounts, sandbox is part of the natural key: the same brand/operator pair can have both a production and sandbox account.' - ), - ] = None - notification_configs: Annotated[ - list[notification_config.NotificationConfig] | None, - Field( - description='Account-level webhook subscriptions for creative lifecycle/assignment changes, indicators.changed, account status, durable account-change wake-ups, wholesale feed changes, and reporting.delivery_ready. Buyers manage entries via sync_accounts and verify persisted state on list_accounts. account.change_recorded wakes receivers to drain list_account_changes; reporting.delivery_ready is repaired through get_reporting_status; indicator and assignment payloads are invalidations repaired completely through get_media_buys; list_creatives may provide a bounded reverse projection. Distinct from per-resource push_notification_config. Entries are keyed by account-scoped subscriber_id; credentials are write-only.', - max_length=16, - ), - ] = None - reporting_delivery_configs: Annotated[ - list[reporting_delivery_config_state.ReportingDeliveryConfigurationState] | None, - Field( - description="Resolved durable reporting delivery configurations owned by the authenticated caller for this account. list_accounts MUST expose only the calling principal's set. State and seller-issued destination_ref are returned; credentials and bearer profiles MUST NOT appear. Any setup URL is a secret-free authenticated entry point, not a bearer credential.", - max_length=16, - ), - ] = None - webhook_activity: Annotated[ - list[webhook_activity_record.WebhookActivityRecord] | None, - Field( - description='Recent webhook delivery attempts scoped to this account when the caller requested webhook activity on list_accounts and the seller surfaces the log. Includes account-anchored notifications such as account.status_changed and MAY include other account-level fires relevant to this account. Three-state presence follows the shared webhook_activity[] contract: omitted means unsupported or not requested, [] means supported but no retained fires, non-empty lists recent attempts most-recent-first.', - max_length=200, - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/account_authorization.py b/src/adcp/types/generated_poc/core/account_authorization.py deleted file mode 100644 index f4acc12c1..000000000 --- a/src/adcp/types/generated_poc/core/account_authorization.py +++ /dev/null @@ -1,55 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/account_authorization.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, StrictBool - - -class AllowedTask(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[a-z][a-z0-9_]*$'} - - -class ScopeName(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^custom:[a-z][a-z0-9_]*$'} - _json_schema_extra = { - 'description': 'Agent-defined scope name, prefixed with `custom:`. Any vendor agent (media-buy seller, signals agent, governance agent, creative agent, brand agent) MAY define custom scopes. Callers MUST NOT assume any semantics from a custom-prefixed scope name.', - 'title': 'CustomScope', - } - - -class AccountAuthorization(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - allowed_tasks: Annotated[ - list[AllowedTask], - Field( - description='Canonical snake_case task names the caller may invoke against this account (for example get_media_buys, buy_products, accept_proposal, control_media_buy, or sync_creatives). Absence of a task from this list means not permitted and returns SCOPE_INSUFFICIENT. Compact 3.2 tools are authorized by their own names; grants for deprecated get_products/create_media_buy/update_media_buy do not silently transfer across aliases.' - ), - ] - field_scopes: Annotated[ - dict[str, list[str]] | None, - Field( - description='Optional per-task allowlist of request fields the caller may set. Keys are task names (which MUST also appear in allowed_tasks). Values are arrays of top-level request-field paths permitted for that task. When a task appears in field_scopes, requests to that task with any field outside the allowlist MUST be rejected with FIELD_NOT_PERMITTED. Compact product tools use their own task names and top-level fields such as criteria and refinements. Implicit framing fields are always permitted and do NOT need to appear in the allowlist — they identify the resource or shape the call rather than mutating business state. Tasks absent from field_scopes have no field-level restriction beyond what the task schema already enforces.' - ), - ] = None - scope_name: Annotated[ - Literal['attestation_verifier'] | ScopeName | None, - Field( - description='Optional named scope identifier. When present, callers and the vendor agent can reason about the grant by name rather than by enumerating allowed_tasks and field_scopes. Modeled as a discriminated union so code generators produce a literal type for the standard scope(s) and a distinct type for agent-defined values — this prevents a typo of `attestation_verifier` from being silently accepted as a custom scope. Agent-defined scope names MUST use a `custom:` prefix to avoid collision with future standard scopes. The prefix is protocol-neutral: a signals agent, a governance agent, or a creative agent defines custom scopes the same way a media-buy sales agent does.' - ), - ] = None - read_only: Annotated[ - StrictBool | None, - Field( - description='Convenience flag. When true, the caller is permitted only non-mutating tasks. Sellers MUST reject any mutation from a read-only caller with READ_ONLY_SCOPE. For the AdCP 3.2 product split, read-only permits list_products but rejects request_proposals, refine_proposals, and decline_proposals; a legacy get_products call is permitted only when the seller can guarantee the selected arm is a synchronous side-effect-free read. Sellers MAY omit this field; omission is equivalent to `false`. Callers MUST NOT infer read-only from `allowed_tasks` alone — the seller MUST set this explicitly when it applies.' - ), - ] = False diff --git a/src/adcp/types/generated_poc/core/account_change.py b/src/adcp/types/generated_poc/core/account_change.py deleted file mode 100644 index 2150fedd6..000000000 --- a/src/adcp/types/generated_poc/core/account_change.py +++ /dev/null @@ -1,240 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/account_change.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarStr -from pydantic import AwareDatetime, ConfigDict, Field, StrictBool - -from . import ext as ext_1 - - -class Resource(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - str, - Field( - description='Open resource-type name such as account, media_buy, package, creative, creative_assignment, delivery_report, audience, event_source, catalog, or account_financials.', - max_length=100, - min_length=1, - pattern='^[a-z][a-z0-9_.-]{0,99}$', - ), - ] - account_id: Annotated[ - str, - Field( - description='Seller-assigned account containing the resource.', - max_length=255, - min_length=1, - ), - ] - resource_id: Annotated[ - str, - Field( - description='Canonical identifier for the resource within its type. For an account change this equals account_id.', - max_length=255, - min_length=1, - ), - ] - parent_ids: Annotated[ - dict[str, str] | None, - Field( - description='Additional canonical parent identifiers needed to repair a nested resource, for example media_buy_id and package_id for a creative assignment. Keys and values MUST NOT contain sensitive payload data.', - max_length=12, - ), - ] = None - - -class Kind(StrEnum): - adcp = 'adcp' - seller_operator = 'seller_operator' - seller_system = 'seller_system' - connected_platform = 'connected_platform' - unknown = 'unknown' - - -class Origin(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Kind - connection_id: Annotated[ - str | None, - Field( - description='Opaque, non-secret reference to the connected source when safe for this caller.', - max_length=255, - ), - ] = None - - -class ChangedPath(ScalarStr): - __slots__ = () - _constraints = {'max_length': 500, 'pattern': '^(|/.*)$'} - - -class Task(StrEnum): - list_accounts = 'list_accounts' - get_media_buys = 'get_media_buys' - get_media_buy_delivery = 'get_media_buy_delivery' - list_creatives = 'list_creatives' - sync_audiences = 'sync_audiences' - sync_event_sources = 'sync_event_sources' - sync_catalogs = 'sync_catalogs' - get_account_financials = 'get_account_financials' - list_products = 'list_products' - get_signals = 'get_signals' - - -class UnavailableReason(StrEnum): - deleted = 'deleted' - purged = 'purged' - legal_erasure = 'legal_erasure' - access_revoked = 'access_revoked' - other = 'other' - - -class Repair(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - task: Annotated[ - Task, - Field( - description='Allowlisted authoritative read task. This is a repair hint, never an instruction to dispatch dynamically. The buyer constructs and validates the request locally from the authenticated feed account and resource identity.' - ), - ] - available: Annotated[ - StrictBool | None, - Field( - description='False when deletion or compelled erasure makes the resource unavailable on the repair read.' - ), - ] = True - unavailable_reason: UnavailableReason | None = None - - -class Type(StrEnum): - principal = 'principal' - operator = 'operator' - system = 'system' - platform = 'platform' - unknown = 'unknown' - - -class Actor(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Type - id: Annotated[ - str | None, Field(description='Opaque, redaction-safe actor reference.', max_length=255) - ] = None - - -class AccountChange(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - change_id: Annotated[ - str, - Field( - description='Stable seller-generated identifier for this logical change. Retries and notification re-emissions reuse this identifier.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] - recorded_at: Annotated[ - AwareDatetime, - Field( - description='Time the seller committed or durably observed the change. Feed order is defined by the cursor, not by this timestamp.' - ), - ] - occurred_at: Annotated[ - AwareDatetime | None, - Field( - description='Upstream business time when the change occurred, only when the seller can establish it reliably.' - ), - ] = None - batch_id: Annotated[ - str | None, - Field( - description='Optional stable identifier grouping records produced by one committed operation or one external-source ingestion batch. Each independently repairable authoritative identity still receives its own record; batch_id does not change cursor ordering or notification identity.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - resource: Annotated[ - Resource, - Field( - description='Stable identity of the changed resource. Resource types are open for forward compatibility. account_id is always present; resource_id identifies the changed entity and parent_ids supplies any IDs needed to disambiguate nested resources.' - ), - ] - action: Annotated[ - str, - Field( - description='Material change action. Standard values are created, discovered, updated, status_changed, linked, unlinked, deleted, and purged. Future standard or vendor-namespaced values are allowed; receivers MUST treat unknown values as generic invalidations.', - max_length=100, - min_length=1, - pattern='^[a-z][a-z0-9_.-]{0,99}$', - ), - ] - origin: Annotated[ - Origin, - Field( - description='Server-derived origin classification. The seller MUST NOT trust caller-supplied origin or actor claims.' - ), - ] - resource_revision: Annotated[ - SchemaInt | str | None, - Field( - description='Post-change revision exposed by the repair read, when that resource family defines one.' - ), - ] = None - changed_paths: Annotated[ - list[ChangedPath] | None, - Field( - description='Bounded set of RFC 6901 JSON Pointers naming material fields that changed. Values are intentionally omitted.', - max_length=64, - ), - ] = None - repair: Annotated[ - Repair, - Field( - description='Authoritative AdCP read the receiver uses to reconcile current state. The buyer constructs safe request arguments from the structured resource identity. A deleted or legally purged resource may instead declare unavailable with a categorical reason.' - ), - ] - actor: Annotated[ - Actor | None, - Field( - description='Optional privacy-safe actor classification. Sellers MUST omit direct personal identifiers unless the authenticated caller is authorized for them.' - ), - ] = None - reason: Annotated[ - str | None, - Field( - description='Short machine-readable reason code, when available.', - max_length=100, - pattern='^[a-z][a-z0-9_.-]{0,99}$', - ), - ] = None - summary: Annotated[ - str | None, - Field( - description='Optional brief, untrusted human-readable summary. MUST NOT contain secrets or sensitive payload data.', - max_length=500, - ), - ] = None - ext: Annotated[ - ext_1.ExtensionObject | None, - Field( - description='Bounded vendor extensions. The entire encoded change record, including extensions, MUST NOT exceed 64 KiB and remains subject to the same secret/PII prohibitions.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/account_change_recorded_webhook.py b/src/adcp/types/generated_poc/core/account_change_recorded_webhook.py deleted file mode 100644 index 8cea2f4b8..000000000 --- a/src/adcp/types/generated_poc/core/account_change_recorded_webhook.py +++ /dev/null @@ -1,74 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/account_change_recorded_webhook.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - -from . import ext as ext_1 - - -class Resource(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[str, Field(max_length=100, min_length=1, pattern='^[a-z][a-z0-9_.-]{0,99}$')] - resource_id: Annotated[str, Field(max_length=255, min_length=1)] - parent_ids: Annotated[dict[str, str] | None, Field(max_length=12)] = None - - -class AccountChangeRecordedWebhook(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - idempotency_key: Annotated[ - str, - Field( - description='Sender-generated delivery key stable across retries of one fire. Deliberate re-emission uses a new key.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - notification_id: Annotated[ - str, - Field( - description='Logical notification identifier. Always equals change_id; retries and deliberate re-emissions of the same change retain it.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] - notification_type: Literal['account.change_recorded'] = 'account.change_recorded' - fired_at: AwareDatetime - subscriber_id: Annotated[ - str, Field(max_length=64, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,64}$') - ] - account_id: Annotated[str, Field(max_length=255, min_length=1)] - change_id: Annotated[ - str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') - ] - recorded_at: AwareDatetime - resource: Annotated[ - Resource, - Field(description='Resource identity copied from the corresponding account change record.'), - ] - action: Annotated[str, Field(max_length=100, min_length=1)] - through_cursor: Annotated[ - str | None, - Field( - description='Optional advisory checkpoint at or after this change. It is a drain target, not a cursor the receiver may install without reading every intervening page.', - max_length=4096, - min_length=1, - ), - ] = None - ext: Annotated[ - ext_1.ExtensionObject | None, - Field( - description='Bounded vendor extensions subject to the same secret/PII prohibitions as the base payload.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/account_identity_change.py b/src/adcp/types/generated_poc/core/account_identity_change.py deleted file mode 100644 index dea0f8d79..000000000 --- a/src/adcp/types/generated_poc/core/account_identity_change.py +++ /dev/null @@ -1,60 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/account_identity_change.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - -from . import operator_identity - - -class AccountIdentityChange1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - status: Annotated[ - Literal['pending_approval'], - Field(description='The seller is reviewing the requested transition.'), - ] = 'pending_approval' - requested_operator_identity: Annotated[ - operator_identity.OperatorIdentity, - Field(description='Complete desired operator identity submitted by the buyer.'), - ] - requested_at: Annotated[ - AwareDatetime | None, Field(description='When the seller recorded the request.') - ] = None - - -class AccountIdentityChange2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - status: Annotated[ - Literal['rejected'], - Field( - description='The seller rejected the transition and retained the current canonical identity.' - ), - ] = 'rejected' - requested_operator_identity: Annotated[ - operator_identity.OperatorIdentity, - Field(description='Complete desired operator identity submitted by the buyer.'), - ] - requested_at: Annotated[ - AwareDatetime | None, Field(description='When the seller recorded the request.') - ] = None - reason: Annotated[ - str, Field(description='Human-readable rejection reason.', max_length=1000, min_length=1) - ] - - -AccountIdentityChange = Annotated[ - AccountIdentityChange1 | AccountIdentityChange2, - Field( - description="A requested operator-identity change that has not been applied. The account's top-level operator and operator_unit remain canonical until this object disappears after approval and the canonical fields advance. Rejected changes remain visible until replaced by a later request so buyers polling list_accounts can observe the outcome.", - title='Account Identity Change', - ), -] diff --git a/src/adcp/types/generated_poc/core/account_identity_change_preview.py b/src/adcp/types/generated_poc/core/account_identity_change_preview.py deleted file mode 100644 index e614355c3..000000000 --- a/src/adcp/types/generated_poc/core/account_identity_change_preview.py +++ /dev/null @@ -1,116 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/account_identity_change_preview.json -# timestamp: 2026-10-04T00:53:27+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, RootModel - -from . import operator_identity - - -class Blocker(ScalarStr): - __slots__ = () - _constraints = {'max_length': 1000, 'min_length': 1} - - -class Area(StrEnum): - account_id = 'account_id' - media_buys = 'media_buys' - reporting = 'reporting' - approval = 'approval' - billing = 'billing' - grants = 'grants' - other = 'other' - - -class Effect(StrEnum): - preserved = 'preserved' - revalidation_required = 'revalidation_required' - revoke_and_regrant = 'revoke_and_regrant' - blocked = 'blocked' - - -class Impact(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - area: Area - effect: Effect - reason: Annotated[str | None, Field(max_length=1000, min_length=1)] = None - - -class Effect1(StrEnum): - preserved = 'preserved' - revalidation_required = 'revalidation_required' - revoke_and_regrant = 'revoke_and_regrant' - - -class NonblockingImpact(Impact): - effect: Effect1 | None = None - - -class NonblockingImpacts(RootModel[list[NonblockingImpact]]): - root: Annotated[ - list[NonblockingImpact], - Field( - description='Account areas the seller evaluated for continuity or reauthorization. Non-blocked outcomes cannot carry a blocked effect.', - max_length=16, - min_length=1, - ), - ] - - -class BlockedImpacts(RootModel[list[Impact]]): - root: Annotated[ - list[Impact], - Field( - description='Account areas evaluated for a blocked change. At least one impact identifies the blocking area.', - max_length=16, - min_length=1, - ), - ] - - -class AccountIdentityChangePreview1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - outcome: Literal['would_apply'] = 'would_apply' - requested_operator_identity: operator_identity.OperatorIdentity - impacts: NonblockingImpacts - - -class AccountIdentityChangePreview2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - outcome: Literal['would_require_approval'] = 'would_require_approval' - requested_operator_identity: operator_identity.OperatorIdentity - impacts: NonblockingImpacts - - -class AccountIdentityChangePreview3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - outcome: Literal['blocked'] = 'blocked' - requested_operator_identity: operator_identity.OperatorIdentity - impacts: BlockedImpacts - blockers: Annotated[list[Blocker], Field(min_length=1)] - - -AccountIdentityChangePreview = Annotated[ - AccountIdentityChangePreview1 - | AccountIdentityChangePreview2 - | AccountIdentityChangePreview3, - Field( - description='Non-persisted dry-run result for a requested operator-identity change. Canonical account fields and revision remain unchanged.', - title='Account Identity Change Preview', - ), -] diff --git a/src/adcp/types/generated_poc/core/account_ref.py b/src/adcp/types/generated_poc/core/account_ref.py deleted file mode 100644 index abb742bcc..000000000 --- a/src/adcp/types/generated_poc/core/account_ref.py +++ /dev/null @@ -1,96 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/account_ref.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictBool - -from . import brand_ref -from . import operator_unit as operator_unit_1 - - -class AccountReference1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, - Field( - description="Seller-assigned account identifier. For upstream-managed account namespaces, this value comes from list_accounts; for seller-defined namespaces without a list_accounts surface, it is supplied out-of-band. Buyer-declared account sellers MAY echo account_id from sync_accounts as an internal handle, but they MUST continue accepting the account's current natural-key AccountRef on subsequent calls. A former key tombstoned by identity reconciliation returns ACCOUNT_MOVED to authorized callers." - ), - ] - - -class AccountReference2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brand: Annotated[ - brand_ref.BrandReference, Field(description='Brand reference identifying the advertiser') - ] - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - operator_unit: Annotated[ - operator_unit_1.OperatorUnit | None, - Field( - description='Optional operator-owned business unit, agency seat, or platform account. Only id participates in the natural account key; name is mutable display metadata.' - ), - ] = None - currency: Annotated[ - str | None, - Field( - description="Immutable ISO 4217 transaction currency when the seller's advertiser object is currency-bound. When present, this is part of the natural account key and media buys on the account MUST use it. Omit when currency is selected independently per media buy.", - pattern='^[A-Z]{3}$', - ), - ] = None - timezone: Annotated[ - str | None, - Field( - description='Immutable account timezone. Include it in the natural key when get_adcp_capabilities.account.timezone declares account_fixed with buyer_selected; omit it for seller_fixed or seller_assigned accounts.', - min_length=1, - ), - ] = None - sandbox: Annotated[ - StrictBool | None, - Field( - description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' - ), - ] = False - - -AccountReference = Annotated[ - AccountReference1 | AccountReference2, - Field( - description="Reference to an advertiser account by seller-assigned ID or natural key. account_id is the seller/storefront handle returned by list_accounts. The natural key is brand + operator + optional operator_unit.id + optional fixed currency + optional buyer-selected account timezone + sandbox; it describes the advertiser object the seller resolves or provisions. After a capability-gated identity rekey, the former natural key is tombstoned and returns ACCOUNT_MOVED to authorized callers rather than resolving or provisioning another account. brand.countries qualifies the advertiser's geographic identity. operator_unit identifies the operator-owned buying seat or business unit and is distinct from the seller account. operator_unit.name is display metadata and does not participate in identity. Use account_id when the seller or upstream platform owns the canonical account namespace; use the natural key for buyer-declared accounts (require_operator_auth: false).", - examples=[ - {'account_id': 'acc_acme_001'}, - {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, - { - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - }, - { - 'brand': {'domain': 'nova-athletics.example', 'countries': ['NL']}, - 'operator': 'nova-athletics.example', - 'operator_unit': {'id': '234284238', 'name': 'Nova EMEA'}, - 'currency': 'EUR', - 'timezone': 'Europe/Amsterdam', - }, - { - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'sandbox': True, - }, - ], - title='Account Reference', - ), -] diff --git a/src/adcp/types/generated_poc/core/account_status_changed_webhook.py b/src/adcp/types/generated_poc/core/account_status_changed_webhook.py deleted file mode 100644 index 4e3fbf2e9..000000000 --- a/src/adcp/types/generated_poc/core/account_status_changed_webhook.py +++ /dev/null @@ -1,132 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/account_status_changed_webhook.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - -from ..enums import account_status -from . import ext as ext_1 - - -class ReasonCode(StrEnum): - seller_approved = 'seller_approved' - seller_rejected = 'seller_rejected' - payment_required = 'payment_required' - credit_limit_reached = 'credit_limit_reached' - funds_depleted = 'funds_depleted' - setup_required = 'setup_required' - policy_review = 'policy_review' - policy_violation = 'policy_violation' - compliance_hold = 'compliance_hold' - buyer_requested = 'buyer_requested' - seller_closed = 'seller_closed' - manual_update = 'manual_update' - other = 'other' - - -class Setup(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - message: Annotated[ - str, - Field( - description='Human-readable setup message safe to fan out to every active account notification subscriber.' - ), - ] - expires_at: Annotated[ - AwareDatetime | None, Field(description='When the setup action or hint expires, if known.') - ] = None - - -class AccountStatusChangedWebhook(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - idempotency_key: Annotated[ - str, - Field( - description='Sender-generated key stable across retries of the same fire. Sellers MUST generate a cryptographically random value (UUID v4 recommended) per distinct fire and reuse it on every retry of the same fire. Receivers MUST dedupe by this key, scoped to the authenticated sender identity.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - notification_id: Annotated[ - str, - Field( - description='Stable identifier for this logical account status transition. Stability key is (account_id, previous_status, status, observed_at): retries and re-emissions of the same transition reuse the id under a new idempotency_key, while a later transition cycle receives a new id.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] - notification_type: Annotated[ - Literal['account.status_changed'], - Field( - description="Fixed notification type discriminator. Matches the value registered on the subscriber's event_types." - ), - ] = 'account.status_changed' - fired_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the seller initiated this fire. Distinct from observed_at, which is when the seller recorded the account transition.' - ), - ] - subscriber_id: Annotated[ - str, - Field( - description="Identifies which sync_accounts.accounts[].notification_configs[] entry is receiving this fire. Echoed verbatim from the entry's subscriber_id.", - max_length=64, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,64}$', - ), - ] - account_id: Annotated[ - str, - Field( - description='Seller-assigned account identifier whose status changed. Sellers MUST assign account_id before activating an account.status_changed subscriber, including accounts still pending external approval, so later pending_approval -> rejected or pending_approval -> active transitions can be delivered and repaired through list_accounts.' - ), - ] - previous_status: Annotated[ - account_status.AccountStatus, - Field(description='Account status immediately before this transition.'), - ] - status: Annotated[ - account_status.AccountStatus, - Field( - description='Account status after this transition. Receivers SHOULD treat this as advisory and re-read list_accounts for the authoritative account snapshot.' - ), - ] - observed_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the seller recorded the account status transition. Used in the notification_id stability key; this is seller wall time for the transition, not webhook fire time.' - ), - ] - reason_code: Annotated[ - ReasonCode, - Field( - description='Machine-readable reason for the transition. This is advisory routing/debug metadata; receivers MUST re-read list_accounts rather than relying on the reason code as the source of truth.' - ), - ] - reason_detail: Annotated[ - str | None, - Field( - description='Optional short human-readable detail. Treat as untrusted text. Sellers MUST NOT include secrets, setup tokens, internal stack traces, or regulated financial details.', - max_length=500, - ), - ] = None - setup: Annotated[ - Setup | None, - Field( - description='Reduced setup hint when the new status requires human action. This block intentionally omits setup.url; receivers fetch the current setup URL from list_accounts if the caller is authorized to see it.' - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/account_timezone_capability.py b/src/adcp/types/generated_poc/core/account_timezone_capability.py deleted file mode 100644 index bf7b14481..000000000 --- a/src/adcp/types/generated_poc/core/account_timezone_capability.py +++ /dev/null @@ -1,59 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/account_timezone_capability.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field - - -class Mode(StrEnum): - seller_fixed = 'seller_fixed' - account_fixed = 'account_fixed' - - -class AccountSelection(StrEnum): - seller_assigned = 'seller_assigned' - buyer_selected = 'buyer_selected' - - -class SupportedTimezone(ScalarStr): - __slots__ = () - _constraints = {'min_length': 1} - - -class AccountTimezoneCapability(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - mode: Annotated[ - Mode, - Field( - description='seller_fixed means every account uses fixed_timezone. account_fixed means each account has an immutable timezone returned on Account and selected or assigned during account establishment.' - ), - ] - fixed_timezone: Annotated[ - str | None, - Field( - description='Seller-wide timezone used by every account. Required only for seller_fixed. Use UTC or an IANA timezone identifier.', - min_length=1, - ), - ] = None - account_selection: Annotated[ - AccountSelection | None, - Field( - description='How an account_fixed timezone is established. seller_assigned covers an existing upstream account or seller onboarding choice; buyer_selected requires timezone in buyer-declared sync_accounts provisioning.' - ), - ] = None - supported_timezones: Annotated[ - list[SupportedTimezone] | None, - Field( - description='Exact timezone values accepted during buyer-selected account provisioning. Required when account_selection is buyer_selected so buyers can validate the choice before sync_accounts.', - min_length=1, - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/account_with_authorization.py b/src/adcp/types/generated_poc/core/account_with_authorization.py deleted file mode 100644 index 7a56c8547..000000000 --- a/src/adcp/types/generated_poc/core/account_with_authorization.py +++ /dev/null @@ -1,21 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/account_with_authorization.json -# timestamp: 2026-06-05T16:54:33+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import Field - -from . import account_authorization -from .account import Account - - -class AccountWithAuthorization(Account): - authorization: Annotated[ - account_authorization.AccountAuthorization | None, - Field( - description="Optional. The caller's scope grant against this account. Vendor agents of any type (media-buy, signals, governance, creative, brand) that support scope introspection SHOULD populate this so callers can preempt SCOPE_INSUFFICIENT / FIELD_NOT_PERMITTED errors rather than discovering scope by trial and error. Media-buy sales agents claiming the `attestation_verifier` standard scope MUST populate it. Absence means the vendor agent does not advertise introspectable scope for this account — callers MUST NOT infer access from absence, and fall back to error-driven discovery via the RBAC error codes." - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/activation_key.py b/src/adcp/types/generated_poc/core/activation_key.py deleted file mode 100644 index f1f666888..000000000 --- a/src/adcp/types/generated_poc/core/activation_key.py +++ /dev/null @@ -1,40 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/activation_key.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - - -class ActivationKey1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[Literal['segment_id'], Field(description='Segment ID based targeting')] = 'segment_id' - segment_id: Annotated[ - str, - Field(description='The platform-specific segment identifier to use in campaign targeting'), - ] - - -class ActivationKey2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[Literal['key_value'], Field(description='Key-value pair based targeting')] = 'key_value' - key: Annotated[str, Field(description='The targeting parameter key')] - value: Annotated[str, Field(description='The targeting parameter value')] - - -ActivationKey = Annotated[ - ActivationKey1 | ActivationKey2, - Field( - description="Universal identifier for using a signal on a destination platform. Can be either a segment ID or a key-value pair depending on the platform's targeting mechanism.", - discriminator='type', - title='Activation Key', - ), -] diff --git a/src/adcp/types/generated_poc/core/ad_inventory_config.py b/src/adcp/types/generated_poc/core/ad_inventory_config.py deleted file mode 100644 index 40c8df5df..000000000 --- a/src/adcp/types/generated_poc/core/ad_inventory_config.py +++ /dev/null @@ -1,41 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/ad_inventory_config.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictBool - - -class AdInventoryConfiguration(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - expected_breaks: Annotated[ - SchemaInt, Field(description='Number of planned ad breaks in the installment', ge=0) - ] - total_ad_seconds: Annotated[ - SchemaInt | None, Field(description='Total seconds of ad time across all breaks', ge=0) - ] = None - max_ad_duration_seconds: Annotated[ - SchemaInt | None, - Field( - description='Maximum duration in seconds for a single ad within a break. Buyers need this to know whether their creative fits.', - ge=1, - ), - ] = None - unplanned_breaks: Annotated[ - StrictBool | None, - Field( - description='Whether ad breaks are dynamic and driven by live conditions (sports timeouts, election coverage). When false, all breaks are pre-defined.' - ), - ] = None - supported_formats: Annotated[ - list[str] | None, - Field( - description="Ad format types supported in breaks (e.g., 'video', 'audio', 'display')" - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/agent_encryption_key.py b/src/adcp/types/generated_poc/core/agent_encryption_key.py deleted file mode 100644 index f9dbda1a1..000000000 --- a/src/adcp/types/generated_poc/core/agent_encryption_key.py +++ /dev/null @@ -1,31 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/agent_encryption_key.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - - -class AgentEncryptionKey(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kid: Annotated[ - str, - Field( - description='Key identifier. Opaque — MUST NOT encode geographic or deployment information.', - max_length=8, - ), - ] - kty: Annotated[Literal['OKP'], Field(description='JWK key type. Must be OKP for X25519.')] = 'OKP' - crv: Annotated[ - Literal['X25519'], Field(description='Curve name. Must be X25519 for TMPX encryption.') - ] = 'X25519' - use: Annotated[ - Literal['enc'], Field(description='JWK use value. Must be enc for encryption keys.') - ] = 'enc' - x: Annotated[str, Field(description='Base64url-encoded X25519 public key (32 bytes).')] diff --git a/src/adcp/types/generated_poc/core/agent_notification_config.py b/src/adcp/types/generated_poc/core/agent_notification_config.py deleted file mode 100644 index af2f567ad..000000000 --- a/src/adcp/types/generated_poc/core/agent_notification_config.py +++ /dev/null @@ -1,81 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/agent_notification_config.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, StrictBool - -from ..enums import auth_scheme, notification_type -from . import ext as ext_1 - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[list[auth_scheme.AuthenticationScheme], Field(max_length=1, min_length=1)] - credentials: Annotated[ - str | None, - Field( - description='Credentials for the legacy scheme. Bearer: token. HMAC-SHA256: shared secret. Minimum 32 characters. Exchanged out-of-band during onboarding. Write-only.', - min_length=32, - ), - ] = None - - -class AgentNotificationConfig(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - subscriber_id: Annotated[ - str, - Field( - description="Buyer- or registry-supplied identifier for this agent-level subscription endpoint. This is the stable logical key within the authenticated caller's agent-level notification config set: re-sending the same subscriber_id replaces that caller's subscriber URL, event_types, authentication selector, and active flag rather than creating a duplicate. Echoed on every webhook payload so multi-subscriber consumers can route by endpoint. MUST be unique within the submitted `notification_configs[]` array.", - max_length=64, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,64}$', - ), - ] - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL. Same wire contract as `push-notification-config.url` and account-level `notification-config.url`: `format: "uri"`, no destination-port allowlist enforced by the protocol, SSRF protection via the IP-range check defined in docs/building/by-layer/L1/security.mdx#webhook-url-validation-ssrf. Sellers MUST validate URL syntax, HTTPS usage, hostname normalization, and reserved-range rejection when writing any config, including `active: false` configs. Sellers MUST complete an activation challenge or equivalent proof-of-control before treating a new or changed active subscriber as active.' - ), - ] - event_types: Annotated[ - list[notification_type.NotificationType], - Field( - description="Notification types this subscriber wishes to receive on the registered `url`. Caller-anchored types (`capabilities.changed`, `principal.changed`) always fire principal-wide. Account-anchored types additionally require the explicit all_authorized_accounts acknowledgment. Account-anchored types (such as `creative.status_changed` or `account.change_recorded`) are also accepted here: each fire then covers only accounts the authenticated caller is authorized for at each delivery attempt — the subscription is standing, authorization is evaluated per attempt including retries, and losing account authority both stops new fires and suppresses queued retries carrying that account's data. Media-buy-anchored types are rejected on this surface; their per-buy cadence configuration stays on `push_notification_config`. Caller-level and account-level subscriptions to the same event are independent — both fire, and receivers dedupe by the event's logical `notification_id`.", - min_length=1, - ), - ] - all_authorized_accounts: Annotated[ - StrictBool | None, - Field( - description="Explicit scope acknowledgment required whenever event_types includes any account-anchored type: true states that this subscriber intentionally receives those events for every account the principal is authorized for at each delivery attempt. Subscribing an endpoint to all accounts is never implicit. Authorization is evaluated per delivery attempt, including retries: losing authority for an account suppresses queued retries carrying that account's data." - ), - ] = None - include_future_event_types: Annotated[ - StrictBool | None, - Field( - description='When true, the seller also fires caller-eligible notification types added to the enum by later AdCP versions — but only types classified invalidation-only, whose payloads carry identifiers and a repair pointer rather than domain data. Payload-bearing types always require explicit enumeration in event_types; this flag never silently opts a caller into more-sensitive payloads. There is deliberately no wildcard event type. Receivers setting this MUST tolerate unknown notification_type values.' - ), - ] = False - authentication: Annotated[ - Authentication | None, - Field( - deprecated=True, - description="Legacy authentication selector. Same precedence and semantics as `push-notification-config.authentication` and account-level `notification-config.authentication`: presence opts the seller into Bearer or HMAC-SHA256 signing; absence selects the default RFC 9421 webhook profile keyed off the seller's brand.json `agents[]` JWKS. Deprecated; removed in AdCP 4.0. Credentials are write-only and MUST NOT be echoed on reads.", - ), - ] = None - active: Annotated[ - StrictBool | None, - Field( - description='When false, the seller persists the configuration but suppresses fires. Use to pause a subscriber without losing the registration. Paused configs may skip only the outbound proof challenge while inactive; sellers MUST still enforce URL parsing, HTTPS, hostname normalization, and reserved-range rejection at write time. Reactivation requires full SSRF validation with connect pinning plus proof-of-control for any tuple without current valid proof.' - ), - ] = True - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/agent_notification_config_state.py b/src/adcp/types/generated_poc/core/agent_notification_config_state.py deleted file mode 100644 index 68353b331..000000000 --- a/src/adcp/types/generated_poc/core/agent_notification_config_state.py +++ /dev/null @@ -1,41 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/agent_notification_config_state.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, StrictBool - -from ..enums import auth_scheme, notification_type -from . import ext as ext_1 - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[list[auth_scheme.AuthenticationScheme], Field(max_length=1, min_length=1)] - - -class AgentNotificationConfigState(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - subscriber_id: Annotated[ - str, Field(max_length=64, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,64}$') - ] - url: AnyUrl - event_types: Annotated[list[notification_type.NotificationType], Field(min_length=1)] - all_authorized_accounts: Annotated[ - StrictBool | None, - Field(description='Echoed scope acknowledgment for account-anchored event types.'), - ] = None - include_future_event_types: Annotated[ - StrictBool | None, Field(description='Echoed from the desired configuration when set.') - ] = None - authentication: Annotated[Authentication | None, Field(deprecated=True)] = None - active: StrictBool | None = True - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/agent_reporting_destination.py b/src/adcp/types/generated_poc/core/agent_reporting_destination.py deleted file mode 100644 index d5e09f31d..000000000 --- a/src/adcp/types/generated_poc/core/agent_reporting_destination.py +++ /dev/null @@ -1,211 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/agent_reporting_destination.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictBool - -from . import delivery_provider, delivery_recipient, reporting_verification_profile_set - - -class AcceptedFormat(StrEnum): - jsonl = 'jsonl' - csv = 'csv' - parquet = 'parquet' - avro = 'avro' - orc = 'orc' - - -class AgentReportingDestination1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - pattern: Annotated[ - Literal['file_transfer'], - Field( - description='Discriminator. Producer publishes immutable files plus a manifest to caller-controlled object storage.' - ), - ] = 'file_transfer' - destination_id: Annotated[ - str, - Field( - description='Caller-selected stable logical key, unique within this seller relationship. Reusing it replaces desired configuration; when proof-bound coordinates or the accepted delivery contract change, the seller issues a new immutable destination_ref generation while retaining the old reference for existing account bindings and reporting history.', - max_length=64, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,64}$', - ), - ] - operator_id: Annotated[ - str | None, - Field( - description="Optional agent-scoped label naming the operator this destination serves, for per-operator isolation, audit, and offboarding under an agent principal. It is the principal's own bookkeeping and confers no identity or authority: sellers MUST NOT link, dedupe, or authorize across principals based on matching operator labels or domains.", - max_length=64, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,64}$', - ), - ] = None - active: Annotated[ - StrictBool, - Field( - description='True permits use. False suspends the destination: the seller MUST stop initiating new deliveries to every generation of this destination_id within the advertised suspension interval, and new account-level delivery configurations MUST NOT select it. Suspension does not delete caller-owned data already delivered.' - ), - ] - provider: delivery_provider.DeliveryProvider - transport: Annotated[ - str, - Field( - description='Open provider transport name, such as s3, gcs, or azure_blob.', - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_.-]*$', - ), - ] - location: Annotated[ - str, - Field( - description='Provider-native bucket/prefix locator, such as s3://bucket/prefix/ or abfss://container@account.dfs.core.windows.net/path. Printable ASCII without whitespace, \'?\', \'#\', \'%\', \'"\', \'<\', or \'>\'. Never a credential or signed URL; sellers additionally screen and normalize per the secret_rejection and normalization rules.', - max_length=2048, - min_length=1, - pattern='^[!$&-;=@-~]+$', - ), - ] - accepted_formats: Annotated[ - list[AcceptedFormat], - Field( - description='Physical formats accepted by this file-transfer destination.', min_length=1 - ), - ] - accepted_verification_profiles: ( - reporting_verification_profile_set.ReportingVerificationProfileSet - ) - - -class AgentReportingDestination2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - pattern: Annotated[ - Literal['warehouse_materialization'], - Field( - description='Discriminator. Producer or its connector commits reporting revisions into a caller-owned warehouse relation.' - ), - ] = 'warehouse_materialization' - destination_id: Annotated[ - str, - Field( - description='Caller-selected stable logical key, unique within this seller relationship. Reusing it replaces desired configuration; when proof-bound coordinates or the accepted delivery contract change, the seller issues a new immutable destination_ref generation while retaining the old reference for existing account bindings and reporting history.', - max_length=64, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,64}$', - ), - ] - operator_id: Annotated[ - str | None, - Field( - description="Optional agent-scoped label naming the operator this destination serves, for per-operator isolation, audit, and offboarding under an agent principal. It is the principal's own bookkeeping and confers no identity or authority: sellers MUST NOT link, dedupe, or authorize across principals based on matching operator labels or domains.", - max_length=64, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,64}$', - ), - ] = None - active: Annotated[ - StrictBool, - Field( - description='True permits use. False suspends the destination: the seller MUST stop initiating new deliveries to every generation of this destination_id within the advertised suspension interval, and new account-level delivery configurations MUST NOT select it. Suspension does not delete caller-owned data already delivered.' - ), - ] - provider: delivery_provider.DeliveryProvider - transport: Annotated[ - str, - Field( - description='Open provider transport name, such as bigquery, snowflake, or databricks_sql.', - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_.-]*$', - ), - ] - location: Annotated[ - str, - Field( - description='Provider-native project/dataset, database/schema, or catalog/schema locator. Printable ASCII without whitespace, \'?\', \'#\', \'%\', \'"\', \'<\', or \'>\'. Never a credential or signed URL; sellers additionally screen and normalize per the secret_rejection and normalization rules.', - max_length=2048, - min_length=1, - pattern='^[!$&-;=@-~]+$', - ), - ] - accepted_verification_profiles: ( - reporting_verification_profile_set.ReportingVerificationProfileSet - ) - - -class AgentReportingDestination3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - pattern: Annotated[ - Literal['dataset_share'], - Field( - description='Discriminator. Producer retains and versions the dataset; the named recipient reads provider-hosted share objects.' - ), - ] = 'dataset_share' - destination_id: Annotated[ - str, - Field( - description='Caller-selected stable logical key, unique within this seller relationship. Reusing it replaces desired configuration; when proof-bound coordinates or the accepted delivery contract change, the seller issues a new immutable destination_ref generation while retaining the old reference for existing account bindings and reporting history.', - max_length=64, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,64}$', - ), - ] - operator_id: Annotated[ - str | None, - Field( - description="Optional agent-scoped label naming the operator this destination serves, for per-operator isolation, audit, and offboarding under an agent principal. It is the principal's own bookkeeping and confers no identity or authority: sellers MUST NOT link, dedupe, or authorize across principals based on matching operator labels or domains.", - max_length=64, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,64}$', - ), - ] = None - active: Annotated[ - StrictBool, - Field( - description='True permits use. False suspends the destination: the seller MUST stop publishing new revisions to every generation of this destination_id within the advertised suspension interval, and new account-level delivery configurations MUST NOT select it. Suspension does not delete caller-owned data already delivered or revoke provider-side grants by itself.' - ), - ] - provider: delivery_provider.DeliveryProvider - transport: Annotated[ - str, - Field( - description='Open provider transport name, such as delta_sharing or snowflake_secure_sharing.', - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_.-]*$', - ), - ] - access_mode: Annotated[ - str, - Field( - description='Dataset-share access family, such as databricks_to_databricks, open_sharing, or secure_data_sharing.', - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_.-]*$', - ), - ] - recipient: delivery_recipient.DeliveryRecipient - accepted_verification_profiles: ( - reporting_verification_profile_set.ReportingVerificationProfileSet - ) - - -AgentReportingDestination = Annotated[ - AgentReportingDestination1 | AgentReportingDestination2 | AgentReportingDestination3, - Field( - description="Reusable, non-secret reporting destination owned by the authenticated caller's relationship with one seller. Exactly one delivery pattern per destination; pattern is the discriminator and matches the managed reporting delivery pattern vocabulary. It does not grant account authority: account/reporting configuration separately binds authorized data to the seller-issued destination_ref. Sellers key ownership to the stable transport principal, never a signing key, token, or request-body identity. Credentials, private keys, bearer profiles, signed URLs, and embedded passwords are forbidden. Sellers implementing this schema advertise protocol.principal.", - title='Agent Reporting Destination', - ), -] diff --git a/src/adcp/types/generated_poc/core/agent_reporting_destination_state.py b/src/adcp/types/generated_poc/core/agent_reporting_destination_state.py deleted file mode 100644 index 1cbef19d0..000000000 --- a/src/adcp/types/generated_poc/core/agent_reporting_destination_state.py +++ /dev/null @@ -1,101 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/agent_reporting_destination_state.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - -from ..enums import reporting_destination_setup_state -from . import agent_reporting_destination, error - - -class PriorDestinationRef(ScalarStr): - __slots__ = () - _constraints = {'max_length': 255, 'min_length': 1} - - -class Action(StrEnum): - grant_access = 'grant_access' - accept_share = 'accept_share' - prove_control = 'prove_control' - contact_support = 'contact_support' - - -class Setup(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - action: Action - setup_url: Annotated[ - AnyUrl | None, - Field( - description='HTTPS page for completing provider-native setup. Printable ASCII without whitespace, \'?\', \'#\', \'%\', \'"\', \'<\', or \'>\'. It MUST NOT carry a credential, userinfo authority, or signed component and MUST be rendered as an untrusted link, never executed as agent instructions.' - ), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field( - description='Optional expiry of this setup action. A new sync obtains a fresh action after expiry.' - ), - ] = None - - -class AgentReportingDestinationState(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - destination_id: Annotated[ - str, - Field( - description='Caller-selected key echoed from the desired configuration.', - max_length=64, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,64}$', - ), - ] - destination_ref: Annotated[ - str, - Field( - description='Seller-issued opaque immutable destination-generation reference bound to the stable authenticated principal and destination_id. Exact replays preserve it; a proof-bound coordinate or delivery-contract change creates a new reference. Possession does not authorize account access, and sellers MUST NOT resolve it across callers.', - max_length=255, - min_length=1, - ), - ] - prior_destination_refs: Annotated[ - list[PriorDestinationRef] | None, - Field( - description='Retained superseded generation references for this destination_id, newest first, still resolvable for existing authorized account bindings and retained reporting history. Enumerable only by the owning principal. Suspension and revocation of the destination apply to these generations too.', - max_length=32, - ), - ] = None - state: Annotated[ - reporting_destination_setup_state.ReportingDestinationSetupState, - Field( - description='Validation and setup state; see the enum for the per-pattern ready definition.' - ), - ] - configuration: Annotated[ - agent_reporting_destination.AgentReportingDestination, - Field( - description='Credential-free desired configuration currently associated with this destination reference.' - ), - ] - setup: Annotated[ - Setup | None, - Field( - description='Closed, non-secret setup instruction. Human-readable messages are deliberately excluded; agents dispatch only the typed action and treat setup_url as an untrusted navigation target.' - ), - ] = None - issues: Annotated[ - list[error.Error] | None, - Field( - description='Structured validation or setup issues. Messages and details are untrusted display data and MUST NOT be executed as instructions.', - max_length=16, - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/agent_signing_key.py b/src/adcp/types/generated_poc/core/agent_signing_key.py deleted file mode 100644 index 80caf3d82..000000000 --- a/src/adcp/types/generated_poc/core/agent_signing_key.py +++ /dev/null @@ -1,46 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/agent_signing_key.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - - -class AgentSigningKey(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kid: Annotated[str, Field(description='Key identifier for selecting the correct signing key.')] - kty: Annotated[str, Field(description="JWK key type, such as 'OKP', 'EC', or 'RSA'.")] - alg: Annotated[ - str | None, - Field(description="Expected signing algorithm for this key, such as 'EdDSA' or 'RS256'."), - ] = None - use: Annotated[ - str | None, Field(description="Optional JWK use value. Typically 'sig' for signing keys.") - ] = None - crv: Annotated[ - str | None, - Field(description="Curve name for OKP or EC keys, such as 'Ed25519' or 'P-256'."), - ] = None - x: Annotated[ - str | None, - Field( - description='Base64url-encoded public key x coordinate or public key value for OKP keys.' - ), - ] = None - y: Annotated[ - str | None, Field(description='Base64url-encoded public key y coordinate for EC keys.') - ] = None - n: Annotated[str | None, Field(description='Base64url-encoded RSA modulus.')] = None - e: Annotated[str | None, Field(description='Base64url-encoded RSA public exponent.')] = None - revoked_at: Annotated[ - AwareDatetime | None, - Field( - description='Optional revocation timestamp. When present, verifiers MUST reject any signature produced with this key whose signing epoch (or equivalent time reference) is at or after this timestamp. The key may continue to appear in the trust anchor during a grace period so caches that have not yet refreshed still find the key and can evaluate the revocation marker. Keys past their revocation can be removed once the cache TTL (recommended: 5 minutes) has elapsed across all verifiers.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/agent_webhook_challenge.py b/src/adcp/types/generated_poc/core/agent_webhook_challenge.py deleted file mode 100644 index 00e893df2..000000000 --- a/src/adcp/types/generated_poc/core/agent_webhook_challenge.py +++ /dev/null @@ -1,87 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/agent_webhook_challenge.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - - -class Mode(StrEnum): - rfc9421 = 'rfc9421' - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class DeliveryAuth(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - mode: Annotated[ - Mode, - Field( - description='Future webhook delivery authentication mode. `rfc9421` is used when notification_configs[].authentication is absent; Bearer and HMAC-SHA256 are the deprecated legacy modes selected by notification_configs[].authentication.' - ), - ] - credential_fingerprint: Annotated[ - str | None, - Field( - description='SHA-256 hex fingerprint of the exact legacy credential string supplied in notification_configs[].authentication.credentials. Required when mode is Bearer or HMAC-SHA256; absent for rfc9421.', - pattern='^[a-f0-9]{64}$', - ), - ] = None - - -class AgentWebhookChallenge(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Annotated[ - Literal['webhook.challenge'], - Field(description='Discriminator for endpoint proof-of-control challenges.'), - ] = 'webhook.challenge' - scope: Annotated[ - Literal['agent'], - Field(description='Discriminator for agent-level endpoint proof challenges.'), - ] = 'agent' - challenge: Annotated[ - str, - Field( - description='Opaque, cryptographically random value that the receiver must echo in the response body. Recommended encoding: base64url without padding.', - max_length=255, - min_length=32, - pattern='^[A-Za-z0-9_.:-]{32,255}$', - ), - ] - subscriber_id: Annotated[ - str, - Field( - description='Buyer-supplied subscriber identifier from the caller-scoped notification_configs[] entry being challenged.', - max_length=64, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,64}$', - ), - ] - seller_agent_url: Annotated[ - AnyUrl, - Field( - description='Exact seller agent URL whose RFC 9421 webhook profile key signs this challenge and that will send subsequent webhooks.' - ), - ] - delivery_auth: Annotated[ - DeliveryAuth, - Field( - description='Authentication/signing mode the seller will use for subsequent webhooks delivered to this notification config.' - ), - ] - event_types: Annotated[ - list[Literal['capabilities.changed']], - Field( - description='Normalized agent-level notification types requested by the subscriber at the time of the challenge. Part of the endpoint proof scope; changing event_types[] requires a fresh challenge before the new set can become active. Currently only `capabilities.changed` is valid.', - min_length=1, - ), - ] diff --git a/src/adcp/types/generated_poc/core/app_item.py b/src/adcp/types/generated_poc/core/app_item.py deleted file mode 100644 index ae754a723..000000000 --- a/src/adcp/types/generated_poc/core/app_item.py +++ /dev/null @@ -1,137 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/app_item.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, ConfigDict, Field, StrictFloat - -from . import ext as ext_1 -from . import offering_asset_group -from . import price as price_1 - - -class Platform(StrEnum): - ios = 'ios' - android = 'android' - - -class AppItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - app_id: Annotated[ - str, - Field( - description='Buyer-assigned unique identifier for this app item. Used for catalog deduplication and content_ids matching on install and launch events.' - ), - ] - name: Annotated[ - str, - Field( - description="App display name as shown in the store (e.g., 'Puzzle Quest: Match 3', 'Acme Banking')." - ), - ] - platform: Annotated[ - Platform, - Field( - description='Target platform. iOS and Android are separate items because they have distinct store identifiers and attribution mechanisms.' - ), - ] - bundle_id: Annotated[ - str | None, - Field( - description="Reverse-domain bundle identifier (e.g., 'com.acmegames.puzzlequest'). The universal store identifier: required for Android (Google Play), also used for iOS MMP attribution, SKAN matching, and app-ads.txt verification. Distinct from app_id, which is a buyer-assigned catalog key." - ), - ] = None - apple_id: Annotated[ - str | None, - Field( - description="Numeric Apple App Store ID (e.g., '389801252'). Required for Apple Search Ads and iOS platforms that use the numeric ID rather than bundle_id.", - pattern='^[0-9]+$', - ), - ] = None - description: Annotated[ - str | None, - Field( - description='App description. Platforms typically pull this from the store listing automatically; supply here to override or for platforms that require it in the request.' - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Primary store category (e.g., 'games', 'productivity', 'finance', 'health_fitness', 'social_networking')." - ), - ] = None - genre: Annotated[ - str | None, - Field( - description="Sub-genre within the category. Particularly relevant for games (e.g., 'puzzle', 'strategy', 'rpg', 'casual', 'simulation', 'action')." - ), - ] = None - icon_url: Annotated[ - AnyUrl | None, Field(description='App icon image URL. Typically 1024×1024 px.') - ] = None - screenshots: Annotated[ - list[AnyUrl] | None, - Field( - description='App store screenshot URLs. Used by platforms for creative generation when native store assets are not available.', - min_length=1, - ), - ] = None - preview_video_url: Annotated[ - AnyUrl | None, - Field(description='App preview or gameplay video URL for use in video ad creatives.'), - ] = None - store_url: Annotated[ - AnyUrl | None, - Field( - description="Direct link to the app's store listing (Apple App Store or Google Play)." - ), - ] = None - deep_link_url: Annotated[ - AnyUrl | None, - Field( - description="Deep link URI for re-engagement campaigns targeting existing users. Use Universal Links (iOS) or App Links (Android) where available (e.g., 'https://acmegames.com/app/level/5'). Falls back to URI scheme (e.g., 'acmegames://level/5') when universal links are not configured." - ), - ] = None - price: Annotated[ - price_1.Price | None, - Field(description='App download price. Set amount to 0 for free apps.'), - ] = None - rating: Annotated[ - StrictFloat | None, - Field( - description='Average store rating (0–5). Use 0 to indicate no ratings yet.', - ge=0.0, - le=5.0, - ), - ] = None - rating_count: Annotated[ - SchemaInt | None, Field(description='Total number of store ratings.', ge=0) - ] = None - content_rating: Annotated[ - str | None, - Field( - description="Age or content rating (e.g., '4+', '12+', 'Everyone', 'Teen', 'PEGI 12'). Format depends on store and region." - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description="Tags for filtering and targeting (e.g., 'multiplayer', 'offline', 'no-ads', 'subscription').", - min_length=1, - ), - ] = None - assets: Annotated[ - list[offering_asset_group.OfferingAssetGroup] | None, - Field( - description="Typed creative asset pools for this app. Uses the same OfferingAssetGroup structure as offering-type catalogs. Standard group IDs: 'images_landscape' (promotional hero), 'images_vertical' (9:16 for Snap, Stories), 'images_square' (1:1 for display), 'video' (gameplay or demo video). Supplements icon_url and screenshots for platform-specific format requirements.", - min_length=1, - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/applicable_package_id.py b/src/adcp/types/generated_poc/core/applicable_package_id.py deleted file mode 100644 index 777cc31f7..000000000 --- a/src/adcp/types/generated_poc/core/applicable_package_id.py +++ /dev/null @@ -1,16 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/applicable_package_id.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from adcp.types._scalar import ScalarStr - - -class ApplicablePackageId(ScalarStr): - __slots__ = () - _constraints = {'min_length': 1} - _json_schema_extra = { - 'description': 'A package identifier to which a package-scoped media-buy action currently applies.', - 'title': 'Applicable Package ID', - } diff --git a/src/adcp/types/generated_poc/core/asset_group_vocabulary.py b/src/adcp/types/generated_poc/core/asset_group_vocabulary.py deleted file mode 100644 index 782e1078e..000000000 --- a/src/adcp/types/generated_poc/core/asset_group_vocabulary.py +++ /dev/null @@ -1,19 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/asset_group_vocabulary.json -# timestamp: 2026-06-01T00:32:59+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from pydantic import Field, RootModel - - -class AdcpAssetGroupVocabularyRegistry(RootModel[Any]): - root: Annotated[ - Any, - Field( - description='Canonical registry of asset_group_id values used in offering asset groups (OfferingAssetGroup) and in v2 product format declarations. Non-canonical IDs remain valid for platform-specific extensions; this registry codifies the recommended canonical set so that buyers and sellers share a vocabulary for the most common slot roles. Validators may emit soft warnings on non-canonical IDs to encourage convergence.\n\nThe registry covers everything the buyer ships in the manifest\'s `assets` map — both directly-rendered creative content (image, video, audio) AND content the seller consumes for production (script, creative_brief, video_brief). The seller dispatches per the format\'s slot declaration. There is no separate "inputs" map on the manifest; everything is an asset.\n\n**Two-tier boundary (normative).** This registry is the *canonical, portable* tier — IAB-aligned, platform-agnostic slot roles every adopter shares (`headline`, `body_text`, `main_image`, `cta`, `landing_page_url`, etc.). Platform-specific asset identifiers (e.g., YouTube video IDs, Pinterest pin IDs, TikTok video IDs, Snap attachment IDs, Meta Advantage+ creative IDs) MUST NOT be added here — they live on the canonical\'s `platform_extensions[]` (URI+digest reference to the platform\'s extension schema) so the canonical registry stays portable. Earlier drafts carried `youtube_video_id` and `pin_id` here; they were dropped in 3.1 GA precisely because adding them set precedent for every platform\'s identifier vocabulary to leak into the canonical tier, defeating the registry\'s portability purpose. Adopters needing to reference an existing platform-hosted asset attach a `platform_extensions[]` entry on the format declaration whose extension schema defines the platform-specific ID slot.', - title='AdCP Asset Group Vocabulary Registry', - ), - ] diff --git a/src/adcp/types/generated_poc/core/assets/__init__.py b/src/adcp/types/generated_poc/core/assets/__init__.py deleted file mode 100644 index a01e99719..000000000 --- a/src/adcp/types/generated_poc/core/assets/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# generated by datamodel-codegen: -# filename: .schema_temp -# timestamp: 2026-06-01T00:32:59+00:00 diff --git a/src/adcp/types/generated_poc/core/assets/asset_union.py b/src/adcp/types/generated_poc/core/assets/asset_union.py deleted file mode 100644 index 7c03eab18..000000000 --- a/src/adcp/types/generated_poc/core/assets/asset_union.py +++ /dev/null @@ -1,3274 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/assets/asset_union.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from enum import IntEnum -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarFloat, ScalarStr -from pydantic import ( - AnyUrl, - AwareDatetime, - ConfigDict, - Field, - RootModel, - StrictBool, - StrictFloat, -) - - -class FocalPointItem(ScalarFloat): - __slots__ = () - _constraints = {'ge': 0.0, 'le': 1.0} - - -class AiTool(AdCPBaseModel): - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class VerifyAgent1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - StrictFloat | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class EmbeddedProvenanceMethod(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class WatermarkMediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class C2paWatermarkAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class Ext(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - - -class ColorSpace(StrEnum): - rec709 = 'rec709' - rec2020 = 'rec2020' - rec2100 = 'rec2100' - srgb = 'srgb' - dci_p3 = 'dci_p3' - - -class HdrFormat(StrEnum): - sdr = 'sdr' - hdr10 = 'hdr10' - hdr10_plus = 'hdr10_plus' - hlg = 'hlg' - dolby_vision = 'dolby_vision' - - -class ChromaSubsampling(StrEnum): - field_4_2_0 = '4:2:0' - field_4_2_2 = '4:2:2' - field_4_4_4 = '4:4:4' - - -class VideoBitDepth(IntEnum): - integer_8 = 8 - integer_10 = 10 - integer_12 = 12 - - -class AudioBitDepth(IntEnum): - integer_16 = 16 - integer_24 = 24 - integer_32 = 32 - - -class FrameRateType(StrEnum): - constant = 'constant' - variable = 'variable' - - -class ScanType(StrEnum): - progressive = 'progressive' - interlaced = 'interlaced' - - -class GopType(StrEnum): - closed = 'closed' - open = 'open' - - -class MoovAtomPosition(StrEnum): - start = 'start' - end = 'end' - - -class AudioChannelLayout(StrEnum): - mono = 'mono' - stereo = 'stereo' - field_5_1 = '5.1' - field_7_1 = '7.1' - - -class FieldModel(StrEnum): - url = 'url' - markup = 'markup' - content = 'content' - ad_request_url = 'ad_request_url' - clickthrough_url = 'clickthrough_url' - - -class UnavailableBehavior(StrEnum): - preserve = 'preserve' - omit_parameter = 'omit_parameter' - dialect_sentinel = 'dialect_sentinel' - reject = 'reject' - - -class MacroBearingUrl1(RootModel[AnyUrl]): - root: Annotated[ - AnyUrl, - Field( - description='Backward-compatible URL value that preserves the existing `uri-template` branch (including deep links, protocol-relative references, and RFC 6570 host variables) while additionally accepting absolute HTTP(S) URLs with byte-preserved macro delimiters not legal in a strict URI, including `%%...%%`, `[...]`, and `${...}`. Macro-bearing HTTP(S) URLs require a valid lexical authority and valid percent triplets; an AdCP verifier replaces each recognized token with an unreserved sentinel and validates the resulting absolute URI. Token semantics are established only by `macro_declarations`.', - title='Macro-bearing URL', - ), - ] - - -class MacroBearingUrl2(ScalarStr): - __slots__ = () - _constraints = { - 'pattern': '^https?://(?:[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?|\\[[0-9A-Fa-f:.]+\\])(?::[0-9]{1,5})?(?:[/?#](?:[^%\\s\\u0000-\\u001F\\u007F"<>`\\\\]|%[0-9A-Fa-f]{2}|%%[A-Za-z0-9_.:-]+%%)*)?$', - } - _json_schema_extra = { - 'description': 'Backward-compatible URL value that preserves the existing `uri-template` branch (including deep links, protocol-relative references, and RFC 6570 host variables) while additionally accepting absolute HTTP(S) URLs with byte-preserved macro delimiters not legal in a strict URI, including `%%...%%`, `[...]`, and `${...}`. Macro-bearing HTTP(S) URLs require a valid lexical authority and valid percent triplets; an AdCP verifier replaces each recognized token with an unreserved sentinel and validates the resulting absolute URI. Token semantics are established only by `macro_declarations`.', - 'title': 'Macro-bearing URL', - } - - -MacroBearingUrl = Annotated[ - str | MacroBearingUrl1 | MacroBearingUrl2, - Field( - description='Backward-compatible URL value that preserves the existing `uri-template` branch (including deep links, protocol-relative references, and RFC 6570 host variables) while additionally accepting absolute HTTP(S) URLs with byte-preserved macro delimiters not legal in a strict URI, including `%%...%%`, `[...]`, and `${...}`. Macro-bearing HTTP(S) URLs require a valid lexical authority and valid percent triplets; an AdCP verifier replaces each recognized token with an unreserved sentinel and validates the resulting absolute URI. Token semantics are established only by `macro_declarations`.', - title='Macro-bearing URL', - ), -] - - -class VastVersion(StrEnum): - field_2_0 = '2.0' - field_3_0 = '3.0' - field_4_0 = '4.0' - field_4_1 = '4.1' - field_4_2 = '4.2' - field_4_3 = '4.3' - - -class MacroDialect(StrEnum): - adcp = 'adcp' - iab_vast = 'iab_vast' - iab_daast = 'iab_daast' - vendor = 'vendor' - unknown = 'unknown' - - -class MacroMappingStatus(StrEnum): - verified_universal = 'verified_universal' - dialect_defined = 'dialect_defined' - unresolved = 'unresolved' - - -class UniversalMacro(StrEnum): - MEDIA_BUY_ID = 'MEDIA_BUY_ID' - PACKAGE_ID = 'PACKAGE_ID' - CREATIVE_ID = 'CREATIVE_ID' - CACHEBUSTER = 'CACHEBUSTER' - TIMESTAMP = 'TIMESTAMP' - CLICK_URL = 'CLICK_URL' - GDPR = 'GDPR' - GDPR_CONSENT = 'GDPR_CONSENT' - US_PRIVACY = 'US_PRIVACY' - GPP_STRING = 'GPP_STRING' - GPP_SID = 'GPP_SID' - IP_ADDRESS = 'IP_ADDRESS' - LIMIT_AD_TRACKING = 'LIMIT_AD_TRACKING' - DEVICE_TYPE = 'DEVICE_TYPE' - OS = 'OS' - OS_VERSION = 'OS_VERSION' - DEVICE_MAKE = 'DEVICE_MAKE' - DEVICE_MODEL = 'DEVICE_MODEL' - USER_AGENT = 'USER_AGENT' - APP_BUNDLE = 'APP_BUNDLE' - APP_NAME = 'APP_NAME' - COUNTRY = 'COUNTRY' - REGION = 'REGION' - CITY = 'CITY' - ZIP = 'ZIP' - DMA = 'DMA' - LAT = 'LAT' - LONG = 'LONG' - DEVICE_ID = 'DEVICE_ID' - DEVICE_ID_TYPE = 'DEVICE_ID_TYPE' - DOMAIN = 'DOMAIN' - PAGE_URL = 'PAGE_URL' - REFERRER = 'REFERRER' - KEYWORDS = 'KEYWORDS' - PLACEMENT_ID = 'PLACEMENT_ID' - FOLD_POSITION = 'FOLD_POSITION' - AD_WIDTH = 'AD_WIDTH' - AD_HEIGHT = 'AD_HEIGHT' - VIDEO_ID = 'VIDEO_ID' - VIDEO_TITLE = 'VIDEO_TITLE' - VIDEO_DURATION = 'VIDEO_DURATION' - VIDEO_CATEGORY = 'VIDEO_CATEGORY' - CONTENT_GENRE = 'CONTENT_GENRE' - CONTENT_RATING = 'CONTENT_RATING' - PLAYER_WIDTH = 'PLAYER_WIDTH' - PLAYER_HEIGHT = 'PLAYER_HEIGHT' - POD_POSITION = 'POD_POSITION' - POD_SIZE = 'POD_SIZE' - AD_BREAK_ID = 'AD_BREAK_ID' - STATION_ID = 'STATION_ID' - COLLECTION_NAME = 'COLLECTION_NAME' - INSTALLMENT_ID = 'INSTALLMENT_ID' - AUDIO_DURATION = 'AUDIO_DURATION' - TMPX = 'TMPX' - IMPRESSION_ID = 'IMPRESSION_ID' - AXEM = 'AXEM' - CATALOG_ID = 'CATALOG_ID' - SKU = 'SKU' - GTIN = 'GTIN' - OFFERING_ID = 'OFFERING_ID' - JOB_ID = 'JOB_ID' - HOTEL_ID = 'HOTEL_ID' - FLIGHT_ID = 'FLIGHT_ID' - VEHICLE_ID = 'VEHICLE_ID' - LISTING_ID = 'LISTING_ID' - STORE_ID = 'STORE_ID' - PROGRAM_ID = 'PROGRAM_ID' - DESTINATION_ID = 'DESTINATION_ID' - CREATIVE_VARIANT_ID = 'CREATIVE_VARIANT_ID' - APP_ITEM_ID = 'APP_ITEM_ID' - ITEM_NAME = 'ITEM_NAME' - ITEM_DESCRIPTION = 'ITEM_DESCRIPTION' - ITEM_TAGLINE = 'ITEM_TAGLINE' - ITEM_PRICE = 'ITEM_PRICE' - ITEM_PRICE_CURRENCY = 'ITEM_PRICE_CURRENCY' - - -class MacroProcessingOperation(StrEnum): - translate_to_native = 'translate_to_native' - resolve_value = 'resolve_value' - preserve = 'preserve' - - -class MacroResolver(StrEnum): - buyer = 'buyer' - creative_agent = 'creative_agent' - seller = 'seller' - request_executor = 'request_executor' - source_ad_server = 'source_ad_server' - - -class Kind(StrEnum): - none = 'none' - rfc3986 = 'rfc3986' - iab_vast_uri = 'iab_vast_uri' - - -class MacroEncoding(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Kind - depth: Annotated[SchemaInt, Field(ge=0)] - - -class MacroValueContext(StrEnum): - url_query_value = 'url_query_value' - url_path_segment = 'url_path_segment' - opaque = 'opaque' - - -class VastTrackingEvent(StrEnum): - impression = 'impression' - creativeView = 'creativeView' - loaded = 'loaded' - start = 'start' - firstQuartile = 'firstQuartile' - midpoint = 'midpoint' - thirdQuartile = 'thirdQuartile' - complete = 'complete' - mute = 'mute' - unmute = 'unmute' - pause = 'pause' - resume = 'resume' - rewind = 'rewind' - skip = 'skip' - playerExpand = 'playerExpand' - playerCollapse = 'playerCollapse' - fullscreen = 'fullscreen' - exitFullscreen = 'exitFullscreen' - progress = 'progress' - acceptInvitation = 'acceptInvitation' - adExpand = 'adExpand' - adCollapse = 'adCollapse' - minimize = 'minimize' - overlayViewDuration = 'overlayViewDuration' - otherAdInteraction = 'otherAdInteraction' - interactiveStart = 'interactiveStart' - clickTracking = 'clickTracking' - customClick = 'customClick' - close = 'close' - closeLinear = 'closeLinear' - error = 'error' - viewable = 'viewable' - notViewable = 'notViewable' - viewUndetermined = 'viewUndetermined' - measurableImpression = 'measurableImpression' - viewableImpression = 'viewableImpression' - - -class Location(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - field: Annotated[FieldModel, Field(description='Asset field containing this occurrence.')] - occurrence: Annotated[ - SchemaInt, - Field( - description='Zero-based occurrence of this exact token within the named field.', ge=0 - ), - ] - context: MacroValueContext - - -class Location4(Location): - pass - - -class MarkupType(StrEnum): - iframe_javascript = 'iframe_javascript' - javascript = 'javascript' - standard = 'standard' - - -class Location5(Location): - pass - - -class Location6(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - field: Annotated[Literal['url'], Field(description='Asset field containing this occurrence.')] = 'url' - occurrence: Annotated[ - SchemaInt, - Field( - description='Zero-based occurrence of this exact token within the named field.', ge=0 - ), - ] - context: MacroValueContext - - -class UrlAssetType(StrEnum): - clickthrough = 'clickthrough' - ad_request = 'ad_request' - tracker_pixel = 'tracker_pixel' - tracker_script = 'tracker_script' - - -class Accessibility(AdCPBaseModel): - alt_text: Annotated[ - str | None, Field(description='Text alternative describing the creative content') - ] = None - keyboard_navigable: Annotated[ - StrictBool | None, - Field(description='Whether the creative can be fully operated via keyboard'), - ] = None - motion_control: Annotated[ - StrictBool | None, - Field( - description='Whether the creative respects prefers-reduced-motion or provides pause/stop controls' - ), - ] = None - screen_reader_tested: Annotated[ - StrictBool | None, - Field(description='Whether the creative has been tested with screen readers'), - ] = None - - -class JavascriptModuleType(StrEnum): - esm = 'esm' - commonjs = 'commonjs' - script = 'script' - - -class HttpMethod(StrEnum): - GET = 'GET' - POST = 'POST' - - -class WebhookResponseType(StrEnum): - html = 'html' - json = 'json' - xml = 'xml' - javascript = 'javascript' - - -class WebhookSecurityMethod(StrEnum): - hmac_sha256 = 'hmac_sha256' - api_key = 'api_key' - none = 'none' - - -class Location8(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - field: Annotated[ - Literal['content'], Field(description='Asset field containing this occurrence.') - ] = 'content' - occurrence: Annotated[ - SchemaInt, - Field( - description='Zero-based occurrence of this exact token within the named field.', ge=0 - ), - ] - context: MacroValueContext - - -class DaastVersion(StrEnum): - field_1_0 = '1.0' - field_1_1 = '1.1' - - -class DaastTrackingEvent(StrEnum): - impression = 'impression' - creativeView = 'creativeView' - start = 'start' - firstQuartile = 'firstQuartile' - midpoint = 'midpoint' - thirdQuartile = 'thirdQuartile' - complete = 'complete' - mute = 'mute' - unmute = 'unmute' - pause = 'pause' - resume = 'resume' - rewind = 'rewind' - skip = 'skip' - progress = 'progress' - clickTracking = 'clickTracking' - customClick = 'customClick' - close = 'close' - error = 'error' - viewable = 'viewable' - notViewable = 'notViewable' - viewUndetermined = 'viewUndetermined' - measurableImpression = 'measurableImpression' - viewableImpression = 'viewableImpression' - - -class MarkdownFlavor(StrEnum): - commonmark = 'commonmark' - gfm = 'gfm' - - -class Objective(StrEnum): - awareness = 'awareness' - consideration = 'consideration' - conversion = 'conversion' - retention = 'retention' - engagement = 'engagement' - - -class Messaging(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - headline: Annotated[str | None, Field(description='Primary headline')] = None - tagline: Annotated[str | None, Field(description='Supporting tagline or sub-headline')] = None - cta: Annotated[str | None, Field(description='Call-to-action text')] = None - key_messages: Annotated[ - list[str] | None, Field(description='Key messages to communicate in priority order') - ] = None - - -class Jurisdiction(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{2}(-[A-Z0-9]{1,3})?$'} - - -class RequiredDisclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - text: Annotated[str, Field(description='The disclosure text that must appear in the creative')] - position: Annotated[ - DisclosurePosition | None, - Field( - description='Where the disclosure should appear within the creative. prominent: clearly visible in the main creative area; footer: at the bottom of visual creatives; audio: spoken in audio/video creatives; subtitle: displayed in the subtitle or closed-caption track; overlay: superimposed on video content; end_card: displayed on video end card; pre_roll: spoken or displayed before main content; companion: in companion ad unit alongside primary creative' - ), - ] = None - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field( - description="Jurisdictions where this disclosure is required. ISO 3166-1 alpha-2 country codes or ISO 3166-2 subdivision codes (e.g., 'US', 'GB', 'US-NJ', 'CA-QC'). If omitted, the disclosure applies to all jurisdictions in the campaign.", - min_length=1, - ), - ] = None - regulation: Annotated[ - str | None, - Field( - description="The regulation or legal authority requiring this disclosure (e.g., 'SEC Rule 156', 'FCA COBS 4.5', 'FDA 21 CFR 202')" - ), - ] = None - min_duration_ms: Annotated[ - SchemaInt | None, - Field( - description='Minimum display duration in milliseconds. For video/audio disclosures, how long the disclosure must be visible or audible. For static formats, how long the disclosure must remain on screen before any auto-advance.', - ge=1, - ), - ] = None - language: Annotated[ - str | None, - Field( - description="Language of the disclosure text as a BCP 47 language tag (e.g., 'en', 'fr-CA', 'es'). When omitted, the disclosure is assumed to match the creative's language." - ), - ] = None - persistence: Annotated[ - DisclosurePersistence | None, - Field( - description='How long the disclosure must persist during content playback or display. When omitted, the creative agent should use the most restrictive persistence mode supported by the target format.' - ), - ] = None - - -class Compliance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Role2(StrEnum): - style_reference = 'style_reference' - product_shot = 'product_shot' - mood_board = 'mood_board' - example_creative = 'example_creative' - logo = 'logo' - strategy_doc = 'strategy_doc' - storyboard = 'storyboard' - - -class ReferenceAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - url: Annotated[ - AnyUrl, Field(description='URL to the reference asset (image, video, or document)') - ] - role: Annotated[ - Role2, - Field( - description='How the creative agent should use this asset. style_reference: match the visual style; product_shot: include this product; mood_board: overall look and feel; example_creative: example of a similar execution; logo: logo to use; strategy_doc: strategy or planning document for context; storyboard: sequential visual direction for video or multi-scene creative' - ), - ] - description: Annotated[ - str | None, - Field( - description='Human-readable description of the asset and how it should inform creative generation' - ), - ] = None - - -class Gtin(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[0-9]{8,14}$'} - - -class CatalogType(StrEnum): - offering = 'offering' - product = 'product' - inventory = 'inventory' - store = 'store' - promotion = 'promotion' - hotel = 'hotel' - flight = 'flight' - job = 'job' - vehicle = 'vehicle' - real_estate = 'real_estate' - education = 'education' - destination = 'destination' - app = 'app' - - -class FeedFormat(StrEnum): - google_merchant_center = 'google_merchant_center' - facebook_catalog = 'facebook_catalog' - shopify = 'shopify' - linkedin_jobs = 'linkedin_jobs' - tiktok_shop = 'tiktok_shop' - pinterest_catalog = 'pinterest_catalog' - openai_product_feed = 'openai_product_feed' - custom = 'custom' - - -class UpdateFrequency(StrEnum): - realtime = 'realtime' - hourly = 'hourly' - daily = 'daily' - weekly = 'weekly' - - -class EventType(StrEnum): - page_view = 'page_view' - view_content = 'view_content' - select_content = 'select_content' - select_item = 'select_item' - search = 'search' - share = 'share' - add_to_cart = 'add_to_cart' - remove_from_cart = 'remove_from_cart' - viewed_cart = 'viewed_cart' - add_to_wishlist = 'add_to_wishlist' - initiate_checkout = 'initiate_checkout' - add_payment_info = 'add_payment_info' - purchase = 'purchase' - refund = 'refund' - lead = 'lead' - qualify_lead = 'qualify_lead' - close_convert_lead = 'close_convert_lead' - disqualify_lead = 'disqualify_lead' - complete_registration = 'complete_registration' - subscribe = 'subscribe' - follow = 'follow' - content_view = 'content_view' - watch_milestone = 'watch_milestone' - start_trial = 'start_trial' - app_install = 'app_install' - app_launch = 'app_launch' - contact = 'contact' - schedule = 'schedule' - donate = 'donate' - submit_application = 'submit_application' - custom = 'custom' - - -class ContentIdType(StrEnum): - sku = 'sku' - gtin = 'gtin' - offering_id = 'offering_id' - job_id = 'job_id' - hotel_id = 'hotel_id' - flight_id = 'flight_id' - vehicle_id = 'vehicle_id' - listing_id = 'listing_id' - store_id = 'store_id' - program_id = 'program_id' - destination_id = 'destination_id' - app_id = 'app_id' - - -class Transform(StrEnum): - date = 'date' - divide = 'divide' - boolean = 'boolean' - split = 'split' # type: ignore[assignment] - - -class CatalogFieldMapping(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - feed_field: Annotated[ - str | None, - Field( - description='Field name in the external feed record. Omit when injecting a static literal value (use the value property instead).' - ), - ] = None - catalog_field: Annotated[ - str | None, - Field( - description="Target field on the catalog item schema, using dot notation for nested fields (e.g., 'name', 'price.amount', 'location.city'). Mutually exclusive with asset_group_id." - ), - ] = None - asset_group_id: Annotated[ - str | None, - Field( - description="Places the feed field value (a URL) into a typed asset pool on the catalog item's assets array. The value is wrapped as an image or video asset in a group with this ID. Use standard group IDs: 'images_landscape', 'images_vertical', 'images_square', 'logo', 'video'. Mutually exclusive with catalog_field." - ), - ] = None - value: Annotated[ - Any | None, - Field( - description='Static literal value to inject into catalog_field for every item, regardless of what the feed contains. Mutually exclusive with feed_field. Useful for fields the feed omits (e.g., currency when price is always USD, or a constant category value).' - ), - ] = None - transform: Annotated[ - Transform | None, - Field( - description='Named transform to apply to the feed field value before writing to the catalog schema. See transform-specific parameters (format, timezone, by, separator).' - ), - ] = None - format: Annotated[ - str | None, - Field( - description="For transform 'date': the input date format string (e.g., 'YYYYMMDD', 'MM/DD/YYYY', 'DD-MM-YYYY'). Output is always ISO 8601 (e.g., '2025-03-01'). Uses Unicode date pattern tokens." - ), - ] = None - timezone: Annotated[ - str | None, - Field( - description="For transform 'date': the timezone of the input value. IANA timezone identifier (e.g., 'UTC', 'America/New_York', 'Europe/Amsterdam'). Defaults to UTC when omitted." - ), - ] = None - by: Annotated[ - StrictFloat | None, - Field( - description="For transform 'divide': the divisor to apply (e.g., 100 to convert integer cents to decimal dollars).", - gt=0.0, - ), - ] = None - separator: Annotated[ - str | None, - Field( - description="For transform 'split': the separator character or string to split on. Defaults to ','." - ), - ] = ',' - default: Annotated[ - Any | None, - Field( - description='Fallback value to use when feed_field is absent, null, or empty. Applied after any transform would have been applied. Allows optional feed fields to have a guaranteed baseline value.' - ), - ] = None - ext: Ext | None = None - - -class IdentityRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - handle: Annotated[ - str | None, Field(description='Human-readable handle or username when available.') - ] = None - profile_url: Annotated[ - AnyUrl | None, Field(description='URL for the authoring profile, page, or channel.') - ] = None - platform_identity_id: Annotated[ - str | None, Field(description='Opaque platform-native identity identifier.') - ] = None - - -class Status(StrEnum): - authorized = 'authorized' - pending = 'pending' - expired = 'expired' - revoked = 'revoked' - not_required = 'not_required' - unknown = 'unknown' - - -class ReferenceAuthorization(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Status, - Field( - description='Current seller-observed authorization state. `authorized`: seller can serve the referenced post. `pending`: additional creator/account action is required. `expired` or `revoked`: authorization previously existed but no longer permits serving. `not_required`: the seller can legally and technically serve without a separate identity authorization. `unknown`: seller has not checked or cannot disclose the state.' - ), - ] - checked_at: Annotated[ - AwareDatetime | None, - Field(description='When the seller last checked the authorization state.'), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field(description='When the authorization expires, if the platform exposes an expiry.'), - ] = None - authorization_url: Annotated[ - AnyUrl | None, - Field(description='Optional URL the buyer or creator can use to complete authorization.'), - ] = None - authorization_instructions: Annotated[ - str | None, - Field(description='Human-readable instructions for completing or restoring authorization.'), - ] = None - - -class ReferenceAuthorization1(ReferenceAuthorization): - pass - - -class PlatformExtensionRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - uri: Annotated[ - AnyUrl, - Field( - description="HTTPS URL identifying the extension. `https://` is mandatory — `http://`, `file://`, `data:`, and other schemes are rejected at the schema layer (defense-in-depth on top of the fetch-contract normative rules). The URI base is the owning agent's URL; the path identifies the extension within that agent. Example: 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel'. The full fetch contract — SSRF allowlist, response-size cap, $ref sandbox, schema-compile bounds — is documented on `product-format-declaration.json#format_schema` and applies to ALL fetches of this reference shape regardless of whether the field is named `format_schema` (load-bearing for validation) or `platform_extensions` (informational); the *transport* rules are identical, only the *consumption* semantics differ." - ), - ] - digest: Annotated[ - str, - Field( - description='SHA-256 content digest of the extension definition (sha256:). Used to detect drift — if the agent revises the extension, the digest changes and cached definitions become invalid.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] - - -class Method(StrEnum): - img = 'img' - js = 'js' - - -class Location9(Location6): - pass - - -class PixelTrackingEvent(StrEnum): - impression = 'impression' - viewable_mrc_50 = 'viewable_mrc_50' - viewable_mrc_100 = 'viewable_mrc_100' - viewable_video_50 = 'viewable_video_50' - audible_video_complete = 'audible_video_complete' - click = 'click' - custom = 'custom' - - -class Target(StrEnum): - linear = 'linear' - non_linear = 'non_linear' - companion = 'companion' - - -class Target1(StrEnum): - linear = 'linear' - companion = 'companion' - - -class EmbeddedProvenanceItem(AdCPBaseModel): - method: Annotated[ - EmbeddedProvenanceMethod, - Field(description='How provenance data is carried within the content'), - ] - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark(AdCPBaseModel): - media_type: Annotated[ - WatermarkMediaType, Field(description='Media category of the watermarked content') - ] - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: Annotated[ - C2paWatermarkAction | None, - Field(description='C2PA action classification for this watermark'), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class RenderGuidance(AdCPBaseModel): - persistence: Annotated[ - DisclosurePersistence | None, - Field( - description='How long the disclosure must persist during content playback or display' - ), - ] = None - min_duration_ms: Annotated[ - SchemaInt | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[DisclosurePosition] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Ext | None = None - - -class Jurisdiction2(AdCPBaseModel): - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - required: Annotated[ - StrictBool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction2] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance(AdCPBaseModel): - digital_source_type: Annotated[ - DigitalSourceType | None, - Field( - description='IPTC-aligned classification of AI involvement in producing this content' - ), - ] = None - synthetic_depiction: Annotated[ - StrictBool | None, - Field( - description='Assessed declaration of whether the content synthetically depicts a real or fictional person performing or appearing in a way that was generated or materially manipulated rather than captured as depicted. `true` covers both a fully synthetic performer and material manipulation of a real performer; `false` is an assessed declaration that the content does not contain such a depiction. Absence means the content has not been assessed for synthetic depiction. This field does not claim consent, legality, or independent verification, and receivers MUST NOT derive it solely from `digital_source_type`.' - ), - ] = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Ext | None = None - - -class VideoAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[ - SchemaInt, - Field( - description="Width in pixels — the video file's intrinsic native width. Required: a hosted file always has concrete dimensions. (Tag-delivered video carries no width; see the `vast` asset.)", - ge=1, - ), - ] - height: Annotated[ - SchemaInt, - Field( - description="Height in pixels — the video file's intrinsic native height. Required: a hosted file always has concrete dimensions. (Tag-delivered video carries no height; see the `vast` asset.)", - ge=1, - ), - ] - duration_ms: Annotated[ - SchemaInt | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[SchemaInt | None, Field(description='File size in bytes', ge=1)] = ( - None - ) - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - SchemaInt | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - StrictFloat | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GopType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - StrictBool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - SchemaInt | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: Annotated[ - AudioChannelLayout | None, Field(description='Audio channel configuration') - ] = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - SchemaInt | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - StrictFloat | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[ - StrictFloat | None, Field(description='True peak level in dBFS') - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance' - ), - ] = None - - -class AudioAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - SchemaInt | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[SchemaInt | None, Field(description='File size in bytes', ge=1)] = ( - None - ) - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - SchemaInt | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: Annotated[AudioChannelLayout | None, Field(description='Channel configuration')] = ( - None - ) - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - SchemaInt | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[ - StrictFloat | None, Field(description='Integrated loudness in LUFS') - ] = None - true_peak_dbfs: Annotated[StrictFloat | None, Field(description='True peak level in dBFS')] = ( - None - ) - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance' - ), - ] = None - - -class Location1(Location8): - pass - - -class Location2(Location): - pass - - -class MacroTranslationTarget(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - token: Annotated[str, Field(min_length=1)] - dialect: MacroDialect - dialect_namespace: AnyUrl | None = None - dialect_revision: Annotated[str | None, Field(min_length=1)] = None - dialect_semantic: Annotated[str, Field(min_length=1)] - mapping_status: MacroMappingStatus - universal_semantic: UniversalMacro | None = None - next_operation: Literal['resolve_value'] = 'resolve_value' - performed_by: MacroResolver - encoding: MacroEncoding - - -class TextAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[ - str | None, - Field( - description='Optional language claim for this text. In a materialized creative localization variant, localized-creative-asset.json requires this value to use /schemas/core/locale-tag.json and conformance requires exact equality with the enclosing variant locale. General non-localized assets retain the legacy unconstrained string for compatibility.' - ), - ] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance' - ), - ] = None - - -class HtmlAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance' - ), - ] = None - - -class JavascriptAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: Annotated[ - JavascriptModuleType | None, Field(description='JavaScript module type') - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance' - ), - ] = None - - -class ZipAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - SchemaInt | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance' - ), - ] = None - - -class Security(AdCPBaseModel): - method: Annotated[WebhookSecurityMethod, Field(description='Authentication method')] - hmac_header: Annotated[ - str | None, Field(description="Header name for HMAC signature (e.g., 'X-Signature')") - ] = None - api_key_header: Annotated[ - str | None, Field(description="Header name for API key (e.g., 'X-API-Key')") - ] = None - - -class WebhookAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: Annotated[HttpMethod | None, Field(description='HTTP method')] = HttpMethod.POST - timeout_ms: Annotated[ - SchemaInt | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: Annotated[ - WebhookResponseType, Field(description='Expected content type of webhook response') - ] - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance' - ), - ] = None - - -class CssAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance' - ), - ] = None - - -class MacroDeclaration6(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - declaration_id: Annotated[ - str, - Field( - description='Identifier unique within the enclosing asset, used to correlate validation results.', - min_length=1, - pattern='^[A-Za-z0-9_-]+$', - ), - ] - token: Annotated[ - str, - Field( - description='Exact byte-preserved token expression at this occurrence, including delimiters.', - min_length=1, - ), - ] - dialect: MacroDialect - dialect_namespace: Annotated[ - AnyUrl | None, - Field( - description='Authority-controlled registry or mapping URI. Required for IAB VAST, IAB DAAST, and vendor dialects.' - ), - ] = None - dialect_revision: Annotated[ - str | None, - Field( - description='Immutable registry/mapping revision, release, commit, or digest. Required for IAB VAST, IAB DAAST, and vendor dialects.', - min_length=1, - ), - ] = None - dialect_semantic: Annotated[ - str, - Field( - description='Exact semantic identifier in the cited dialect, such as `CACHEBUSTING`, `PLAYERSTATE`, or a documented vendor name. This is not inferred from token spelling.', - min_length=1, - ), - ] - mapping_status: MacroMappingStatus - universal_semantic: Annotated[ - UniversalMacro | None, - Field( - description='Verified AdCP universal meaning. Present only when mapping_status is `verified_universal`.' - ), - ] = None - operation: MacroProcessingOperation - performed_by: Annotated[ - MacroResolver | None, - Field( - description='Actor authorized to perform the declared translation or value resolution. Omitted for preservation.' - ), - ] = None - translation_target: Annotated[ - MacroTranslationTarget | None, - Field( - description='Required only for `translate_to_native`. The output token contract that replaces this declaration after translation.' - ), - ] = None - location: Location6 - encoding: Annotated[ - MacroEncoding, - Field( - description='Encoding for the concrete value. Translation/preservation uses `none` at depth zero.' - ), - ] - required: Annotated[ - StrictBool, - Field( - description='Whether terminal delivery must fail if the declared operation cannot complete.' - ), - ] - unavailable_behavior: Annotated[ - UnavailableBehavior, - Field( - description='Action when the responsible actor has no value. Omission is legal only for a complete URL query-value occurrence; dialect sentinels require a cited dialect rule.' - ), - ] - - -class DaastAsset1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: Annotated[ - DaastVersion | None, Field(description='DAAST specification version') - ] = None - duration_ms: Annotated[ - SchemaInt | None, - Field(description='Expected audio duration in milliseconds (if known)', ge=0), - ] = None - tracking_events: Annotated[ - list[DaastTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - StrictBool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance' - ), - ] = None - macro_declarations: Annotated[ - list[MacroDeclaration6] | None, - Field( - description='One declaration per occurrence in a field carried by this asset. URL-delivered assets declare only locator-URL occurrences; receivers do not infer declarations for tokens discovered later in a fetched document.', - min_length=1, - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - MacroBearingUrl, - Field( - description='URL endpoint returning DAAST XML. Macro delimiters remain byte-preserved and are processed only under attached occurrence declarations.' - ), - ] - - -class MacroDeclaration7(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - declaration_id: Annotated[ - str, - Field( - description='Identifier unique within the enclosing asset, used to correlate validation results.', - min_length=1, - pattern='^[A-Za-z0-9_-]+$', - ), - ] - token: Annotated[ - str, - Field( - description='Exact byte-preserved token expression at this occurrence, including delimiters.', - min_length=1, - ), - ] - dialect: MacroDialect - dialect_namespace: Annotated[ - AnyUrl | None, - Field( - description='Authority-controlled registry or mapping URI. Required for IAB VAST, IAB DAAST, and vendor dialects.' - ), - ] = None - dialect_revision: Annotated[ - str | None, - Field( - description='Immutable registry/mapping revision, release, commit, or digest. Required for IAB VAST, IAB DAAST, and vendor dialects.', - min_length=1, - ), - ] = None - dialect_semantic: Annotated[ - str, - Field( - description='Exact semantic identifier in the cited dialect, such as `CACHEBUSTING`, `PLAYERSTATE`, or a documented vendor name. This is not inferred from token spelling.', - min_length=1, - ), - ] - mapping_status: MacroMappingStatus - universal_semantic: Annotated[ - UniversalMacro | None, - Field( - description='Verified AdCP universal meaning. Present only when mapping_status is `verified_universal`.' - ), - ] = None - operation: MacroProcessingOperation - performed_by: Annotated[ - MacroResolver | None, - Field( - description='Actor authorized to perform the declared translation or value resolution. Omitted for preservation.' - ), - ] = None - translation_target: Annotated[ - MacroTranslationTarget | None, - Field( - description='Required only for `translate_to_native`. The output token contract that replaces this declaration after translation.' - ), - ] = None - location: Location8 - encoding: Annotated[ - MacroEncoding, - Field( - description='Encoding for the concrete value. Translation/preservation uses `none` at depth zero.' - ), - ] - required: Annotated[ - StrictBool, - Field( - description='Whether terminal delivery must fail if the declared operation cannot complete.' - ), - ] - unavailable_behavior: Annotated[ - UnavailableBehavior, - Field( - description='Action when the responsible actor has no value. Omission is legal only for a complete URL query-value occurrence; dialect sentinels require a cited dialect rule.' - ), - ] - - -class DaastAsset2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: Annotated[ - DaastVersion | None, Field(description='DAAST specification version') - ] = None - duration_ms: Annotated[ - SchemaInt | None, - Field(description='Expected audio duration in milliseconds (if known)', ge=0), - ] = None - tracking_events: Annotated[ - list[DaastTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - StrictBool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance' - ), - ] = None - macro_declarations: Annotated[ - list[MacroDeclaration7] | None, - Field( - description='One declaration per occurrence in a field carried by this asset. URL-delivered assets declare only locator-URL occurrences; receivers do not infer declarations for tokens discovered later in a fetched document.', - min_length=1, - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class DaastAsset(RootModel[DaastAsset1 | DaastAsset2]): - root: Annotated[ - DaastAsset1 | DaastAsset2, - Field( - description='DAAST (Digital Audio Ad Serving Template) tag for third-party audio ad serving', - discriminator='delivery_type', - title='DAAST Asset', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - - - -class MarkdownAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['markdown'], - Field( - description='Discriminator identifying this as a markdown asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'markdown' - content: Annotated[ - str, - Field( - description='Markdown content following CommonMark spec with optional GitHub Flavored Markdown extensions' - ), - ] - language: Annotated[ - str | None, - Field( - description='Optional language claim for this markdown. In a materialized creative localization variant, localized-creative-asset.json requires this value to use /schemas/core/locale-tag.json and conformance requires exact equality with the enclosing variant locale. General non-localized assets retain the legacy unconstrained string for compatibility.' - ), - ] = None - markdown_flavor: Annotated[ - MarkdownFlavor | None, - Field( - description='Markdown flavor used. CommonMark for strict compatibility, GFM for tables/task lists/strikethrough.' - ), - ] = MarkdownFlavor.commonmark - allow_raw_html: Annotated[ - StrictBool | None, - Field( - description='Whether raw HTML blocks are allowed in the markdown. False recommended for security.' - ), - ] = False - - -class CreativeBrief(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - - -class Catalog(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: Annotated[ - CatalogType, - Field( - description="Catalog type. Structural types: 'offering' (AdCP Offering objects), 'product' (ecommerce entries), 'inventory' (stock per location), 'store' (physical locations), 'promotion' (deals and pricing). Vertical types: 'hotel', 'flight', 'job', 'vehicle', 'real_estate', 'education', 'destination', 'app' — each with an industry-specific item schema." - ), - ] - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: Annotated[ - FeedFormat | None, - Field( - description='Format of the external feed at url. Required when url points to a non-AdCP feed (e.g., Google Merchant Center XML, Meta Product Catalog). Omit for offering-type catalogs where the feed is native AdCP JSON.' - ), - ] = None - update_frequency: Annotated[ - UpdateFrequency | None, - Field( - description='How often the platform should re-fetch the feed from url. Only applicable when url is provided. Platforms may use this as a hint for polling schedules.' - ), - ] = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to exact canonical item keys. The key field is offering_id for offering, store_id for store, hotel_id for hotel, flight_id for flight, job_id for job, vehicle_id for vehicle, listing_id for real_estate, program_id for education, destination_id for destination, and app_id for app. Product, inventory, and promotion catalogs use the stable normalized source identifier retained during ingestion (for example a retailer SKU). The same canonical key is used by catalog availability item_id.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: Annotated[ - ContentIdType | None, - Field( - description="Identifier type that the event's content_ids field should be matched against for items in this catalog. For example, 'gtin' means content_ids values are Global Trade Item Numbers, 'sku' means retailer SKUs. Omit when using a custom identifier scheme not listed in the enum." - ), - ] = None - feed_field_mappings: Annotated[ - list[CatalogFieldMapping] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - - -class PublishedPostAsset1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.' - ), - ] = None - - -class PublishedPostAsset2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization1 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.' - ), - ] = None - - -class PublishedPostAsset(RootModel[PublishedPostAsset1 | PublishedPostAsset2]): - root: Annotated[ - PublishedPostAsset1 | PublishedPostAsset2, - Field( - description="Reference to an already-published post on a publisher, creator, or social platform. The buyer supplies a reference to the existing post; the seller resolves, authorizes, reviews, and serves the referenced post according to the target product's format declaration. This asset is for post references, not uploaded bytes and not catalog rows.", - title='Published Post Asset', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - - - -class ImageAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[SchemaInt, Field(description='Width in pixels', ge=1)] - height: Annotated[SchemaInt, Field(description='Height in pixels', ge=1)] - file_size_bytes: Annotated[ - SchemaInt | None, - Field( - description='Image file size in bytes. Required by agents that advertise a max_file_size_kb constraint.', - ge=1, - ), - ] = None - pixel_ratio: Annotated[ - StrictFloat | None, - Field( - description='Intrinsic pixels per logical render pixel (for example `2` for a 600×500 image intended to render at 300×250). Optional because a validator can infer the ratio when the target format declares logical dimensions. When supplied, it MUST agree with both `width / logical_width` and `height / logical_height`; it is never a substitute for the intrinsic `width` and `height` fields.', - gt=0.0, - ), - ] = None - state_id: Annotated[ - str | None, - Field( - description='Binding used only when this image populates a `seller_rendered_stateful_display` `state_canvases` slot. It MUST match one declared `states[].state_id` (semantic validators resolve it). Omit for ordinary image slots.' - ), - ] = None - breakpoint_id: Annotated[ - str | None, - Field( - description='Binding used only when this image populates a `seller_rendered_stateful_display` `state_canvases` slot. It MUST match one breakpoint declared on the selected state (semantic validators resolve it). Omit for ordinary image slots.' - ), - ] = None - focal_point: Annotated[ - list[FocalPointItem] | None, - Field( - description="Normalized `[x, y]` coordinates (0–1 from top-left) of the image's visual anchor. Seller-side renderers crop toward the focal point when deriving renditions across breakpoints and aspect ratios; absent, cropping falls back to center-weighted defaults.", - max_length=2, - min_length=2, - ), - ] = None - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance' - ), - ] = None - - -class MacroDeclaration(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - declaration_id: Annotated[ - str, - Field( - description='Identifier unique within the enclosing asset, used to correlate validation results.', - min_length=1, - pattern='^[A-Za-z0-9_-]+$', - ), - ] - token: Annotated[ - str, - Field( - description='Exact byte-preserved token expression at this occurrence, including delimiters.', - min_length=1, - ), - ] - dialect: MacroDialect - dialect_namespace: Annotated[ - AnyUrl | None, - Field( - description='Authority-controlled registry or mapping URI. Required for IAB VAST, IAB DAAST, and vendor dialects.' - ), - ] = None - dialect_revision: Annotated[ - str | None, - Field( - description='Immutable registry/mapping revision, release, commit, or digest. Required for IAB VAST, IAB DAAST, and vendor dialects.', - min_length=1, - ), - ] = None - dialect_semantic: Annotated[ - str, - Field( - description='Exact semantic identifier in the cited dialect, such as `CACHEBUSTING`, `PLAYERSTATE`, or a documented vendor name. This is not inferred from token spelling.', - min_length=1, - ), - ] - mapping_status: MacroMappingStatus - universal_semantic: Annotated[ - UniversalMacro | None, - Field( - description='Verified AdCP universal meaning. Present only when mapping_status is `verified_universal`.' - ), - ] = None - operation: MacroProcessingOperation - performed_by: Annotated[ - MacroResolver | None, - Field( - description='Actor authorized to perform the declared translation or value resolution. Omitted for preservation.' - ), - ] = None - translation_target: Annotated[ - MacroTranslationTarget | None, - Field( - description='Required only for `translate_to_native`. The output token contract that replaces this declaration after translation.' - ), - ] = None - location: Location9 - encoding: Annotated[ - MacroEncoding, - Field( - description='Encoding for the concrete value. Translation/preservation uses `none` at depth zero.' - ), - ] - required: Annotated[ - StrictBool, - Field( - description='Whether terminal delivery must fail if the declared operation cannot complete.' - ), - ] - unavailable_behavior: Annotated[ - UnavailableBehavior, - Field( - description='Action when the responsible actor has no value. Omission is legal only for a complete URL query-value occurrence; dialect sentinels require a cited dialect rule.' - ), - ] - - -class VastAsset1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: Annotated[ - VastVersion | None, - Field( - description='Exact VAST version declared by the supplied URL response or inline document. Required by the 3.2 canonical `video_vast` and `audio_vast` manifest paths; optional only on the deprecated named-format compatibility path. Receivers MUST NOT relabel or synthesize a newer version merely because the destination accepts it.' - ), - ] = None - macro_declarations: Annotated[ - list[MacroDeclaration] | None, - Field( - description='One declaration per exact occurrence in a field carried by this asset. A URL-delivered asset can declare only occurrences in its locator `url`; tokens discovered later in a fetched VAST response require document validation evidence or an inline/snapshotted asset and MUST NOT be guessed from the locator. IAB tokens cite a registry namespace and revision rather than copying the live registry into AdCP.', - min_length=1, - ), - ] = None - vpaid_enabled: Annotated[ - StrictBool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - SchemaInt | None, - Field(description='Expected media duration in milliseconds (if known)', ge=0), - ] = None - tracking_events: Annotated[ - list[VastTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance' - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - MacroBearingUrl, - Field( - description='URL endpoint returning VAST XML. Macro delimiters remain byte-preserved; declarations distinguish occurrences in this locator URL from occurrences in inline or fetched VAST content.' - ), - ] - - -class MacroDeclaration1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - declaration_id: Annotated[ - str, - Field( - description='Identifier unique within the enclosing asset, used to correlate validation results.', - min_length=1, - pattern='^[A-Za-z0-9_-]+$', - ), - ] - token: Annotated[ - str, - Field( - description='Exact byte-preserved token expression at this occurrence, including delimiters.', - min_length=1, - ), - ] - dialect: MacroDialect - dialect_namespace: Annotated[ - AnyUrl | None, - Field( - description='Authority-controlled registry or mapping URI. Required for IAB VAST, IAB DAAST, and vendor dialects.' - ), - ] = None - dialect_revision: Annotated[ - str | None, - Field( - description='Immutable registry/mapping revision, release, commit, or digest. Required for IAB VAST, IAB DAAST, and vendor dialects.', - min_length=1, - ), - ] = None - dialect_semantic: Annotated[ - str, - Field( - description='Exact semantic identifier in the cited dialect, such as `CACHEBUSTING`, `PLAYERSTATE`, or a documented vendor name. This is not inferred from token spelling.', - min_length=1, - ), - ] - mapping_status: MacroMappingStatus - universal_semantic: Annotated[ - UniversalMacro | None, - Field( - description='Verified AdCP universal meaning. Present only when mapping_status is `verified_universal`.' - ), - ] = None - operation: MacroProcessingOperation - performed_by: Annotated[ - MacroResolver | None, - Field( - description='Actor authorized to perform the declared translation or value resolution. Omitted for preservation.' - ), - ] = None - translation_target: Annotated[ - MacroTranslationTarget | None, - Field( - description='Required only for `translate_to_native`. The output token contract that replaces this declaration after translation.' - ), - ] = None - location: Location1 - encoding: Annotated[ - MacroEncoding, - Field( - description='Encoding for the concrete value. Translation/preservation uses `none` at depth zero.' - ), - ] - required: Annotated[ - StrictBool, - Field( - description='Whether terminal delivery must fail if the declared operation cannot complete.' - ), - ] - unavailable_behavior: Annotated[ - UnavailableBehavior, - Field( - description='Action when the responsible actor has no value. Omission is legal only for a complete URL query-value occurrence; dialect sentinels require a cited dialect rule.' - ), - ] - - -class VastAsset2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: Annotated[ - VastVersion | None, - Field( - description='Exact VAST version declared by the supplied URL response or inline document. Required by the 3.2 canonical `video_vast` and `audio_vast` manifest paths; optional only on the deprecated named-format compatibility path. Receivers MUST NOT relabel or synthesize a newer version merely because the destination accepts it.' - ), - ] = None - macro_declarations: Annotated[ - list[MacroDeclaration1] | None, - Field( - description='One declaration per exact occurrence in a field carried by this asset. A URL-delivered asset can declare only occurrences in its locator `url`; tokens discovered later in a fetched VAST response require document validation evidence or an inline/snapshotted asset and MUST NOT be guessed from the locator. IAB tokens cite a registry namespace and revision rather than copying the live registry into AdCP.', - min_length=1, - ), - ] = None - vpaid_enabled: Annotated[ - StrictBool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - SchemaInt | None, - Field(description='Expected media duration in milliseconds (if known)', ge=0), - ] = None - tracking_events: Annotated[ - list[VastTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance' - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class VastAsset(RootModel[VastAsset1 | VastAsset2]): - root: Annotated[ - VastAsset1 | VastAsset2, - Field( - description='VAST (Video Ad Serving Template) tag for third-party video or audio ad serving. Unlike a hosted media asset, a VAST tag carries no single `width`/`height`: a response can return multiple renditions and the player selects one at serve time. Standardized VAST audio support begins at 4.1 and uses MediaFile width and height values of 0; older audio-in-VAST versions are seller-declared legacy interoperability. Dimensional, duration, MIME-type, and codec constraints live on the format/requirements layer, not on this asset.', - discriminator='delivery_type', - title='VAST Asset', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - - - -class MacroDeclarationModel(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - declaration_id: Annotated[ - str, - Field( - description='Identifier unique within the enclosing asset, used to correlate validation results.', - min_length=1, - pattern='^[A-Za-z0-9_-]+$', - ), - ] - token: Annotated[ - str, - Field( - description='Exact byte-preserved token expression at this occurrence, including delimiters.', - min_length=1, - ), - ] - dialect: MacroDialect - dialect_namespace: Annotated[ - AnyUrl | None, - Field( - description='Authority-controlled registry or mapping URI. Required for IAB VAST, IAB DAAST, and vendor dialects.' - ), - ] = None - dialect_revision: Annotated[ - str | None, - Field( - description='Immutable registry/mapping revision, release, commit, or digest. Required for IAB VAST, IAB DAAST, and vendor dialects.', - min_length=1, - ), - ] = None - dialect_semantic: Annotated[ - str, - Field( - description='Exact semantic identifier in the cited dialect, such as `CACHEBUSTING`, `PLAYERSTATE`, or a documented vendor name. This is not inferred from token spelling.', - min_length=1, - ), - ] - mapping_status: MacroMappingStatus - universal_semantic: Annotated[ - UniversalMacro | None, - Field( - description='Verified AdCP universal meaning. Present only when mapping_status is `verified_universal`.' - ), - ] = None - operation: MacroProcessingOperation - performed_by: Annotated[ - MacroResolver | None, - Field( - description='Actor authorized to perform the declared translation or value resolution. Omitted for preservation.' - ), - ] = None - translation_target: Annotated[ - MacroTranslationTarget | None, - Field( - description='Required only for `translate_to_native`. The output token contract that replaces this declaration after translation.' - ), - ] = None - location: Location2 - encoding: Annotated[ - MacroEncoding, - Field( - description='Encoding for the concrete value. Translation/preservation uses `none` at depth zero.' - ), - ] - required: Annotated[ - StrictBool, - Field( - description='Whether terminal delivery must fail if the declared operation cannot complete.' - ), - ] - unavailable_behavior: Annotated[ - UnavailableBehavior, - Field( - description='Action when the responsible actor has no value. Omission is legal only for a complete URL query-value occurrence; dialect sentinels require a cited dialect rule.' - ), - ] - - -class MacroDeclaration2(MacroDeclarationModel): - model_config = ConfigDict( - extra='forbid', - ) - location: Location | None = None - declaration_id: Annotated[ - str, - Field( - description='Identifier unique within the enclosing asset, used to correlate validation results.', - min_length=1, - pattern='^[A-Za-z0-9_-]+$', - ), - ] - token: Annotated[ - str, - Field( - description='Exact byte-preserved token expression at this occurrence, including delimiters.', - min_length=1, - ), - ] - dialect: MacroDialect - dialect_namespace: Annotated[ - AnyUrl | None, - Field( - description='Authority-controlled registry or mapping URI. Required for IAB VAST, IAB DAAST, and vendor dialects.' - ), - ] = None - dialect_revision: Annotated[ - str | None, - Field( - description='Immutable registry/mapping revision, release, commit, or digest. Required for IAB VAST, IAB DAAST, and vendor dialects.', - min_length=1, - ), - ] = None - dialect_semantic: Annotated[ - str, - Field( - description='Exact semantic identifier in the cited dialect, such as `CACHEBUSTING`, `PLAYERSTATE`, or a documented vendor name. This is not inferred from token spelling.', - min_length=1, - ), - ] - mapping_status: MacroMappingStatus - universal_semantic: Annotated[ - UniversalMacro | None, - Field( - description='Verified AdCP universal meaning. Present only when mapping_status is `verified_universal`.' - ), - ] = None - operation: MacroProcessingOperation - performed_by: Annotated[ - MacroResolver | None, - Field( - description='Actor authorized to perform the declared translation or value resolution. Omitted for preservation.' - ), - ] = None - translation_target: Annotated[ - MacroTranslationTarget | None, - Field( - description='Required only for `translate_to_native`. The output token contract that replaces this declaration after translation.' - ), - ] = None - encoding: Annotated[ - MacroEncoding, - Field( - description='Encoding for the concrete value. Translation/preservation uses `none` at depth zero.' - ), - ] - required: Annotated[ - StrictBool, - Field( - description='Whether terminal delivery must fail if the declared operation cannot complete.' - ), - ] - unavailable_behavior: Annotated[ - UnavailableBehavior, - Field( - description='Action when the responsible actor has no value. Omission is legal only for a complete URL query-value occurrence; dialect sentinels require a cited dialect rule.' - ), - ] - - -class DisplayTagAsset1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['display_tag'], - Field( - description='Discriminator identifying an atomic third-party display-tag representation.' - ), - ] = 'display_tag' - macro_declarations: Annotated[ - list[MacroDeclaration2] | None, - Field( - description='Exact macro tokens present anywhere in this representation. Tokens remain byte-preserved until the resolver named by each declaration substitutes them.', - min_length=1, - ), - ] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overriding manifest-level provenance.' - ), - ] = None - delivery_type: Literal['tag_url'] = 'tag_url' - url: Annotated[ - MacroBearingUrl, - Field( - description='Ad-request URL invoked at impression time. It is equivalent to the backward-compatible `url` asset with `url_type: "ad_request"`.' - ), - ] - - -class MacroDeclaration3(MacroDeclarationModel): - model_config = ConfigDict( - extra='forbid', - ) - location: Location4 | None = None - declaration_id: Annotated[ - str, - Field( - description='Identifier unique within the enclosing asset, used to correlate validation results.', - min_length=1, - pattern='^[A-Za-z0-9_-]+$', - ), - ] - token: Annotated[ - str, - Field( - description='Exact byte-preserved token expression at this occurrence, including delimiters.', - min_length=1, - ), - ] - dialect: MacroDialect - dialect_namespace: Annotated[ - AnyUrl | None, - Field( - description='Authority-controlled registry or mapping URI. Required for IAB VAST, IAB DAAST, and vendor dialects.' - ), - ] = None - dialect_revision: Annotated[ - str | None, - Field( - description='Immutable registry/mapping revision, release, commit, or digest. Required for IAB VAST, IAB DAAST, and vendor dialects.', - min_length=1, - ), - ] = None - dialect_semantic: Annotated[ - str, - Field( - description='Exact semantic identifier in the cited dialect, such as `CACHEBUSTING`, `PLAYERSTATE`, or a documented vendor name. This is not inferred from token spelling.', - min_length=1, - ), - ] - mapping_status: MacroMappingStatus - universal_semantic: Annotated[ - UniversalMacro | None, - Field( - description='Verified AdCP universal meaning. Present only when mapping_status is `verified_universal`.' - ), - ] = None - operation: MacroProcessingOperation - performed_by: Annotated[ - MacroResolver | None, - Field( - description='Actor authorized to perform the declared translation or value resolution. Omitted for preservation.' - ), - ] = None - translation_target: Annotated[ - MacroTranslationTarget | None, - Field( - description='Required only for `translate_to_native`. The output token contract that replaces this declaration after translation.' - ), - ] = None - encoding: Annotated[ - MacroEncoding, - Field( - description='Encoding for the concrete value. Translation/preservation uses `none` at depth zero.' - ), - ] - required: Annotated[ - StrictBool, - Field( - description='Whether terminal delivery must fail if the declared operation cannot complete.' - ), - ] - unavailable_behavior: Annotated[ - UnavailableBehavior, - Field( - description='Action when the responsible actor has no value. Omission is legal only for a complete URL query-value occurrence; dialect sentinels require a cited dialect rule.' - ), - ] - - -class DisplayTagAsset2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['display_tag'], - Field( - description='Discriminator identifying an atomic third-party display-tag representation.' - ), - ] = 'display_tag' - macro_declarations: Annotated[ - list[MacroDeclaration3] | None, - Field( - description='Exact macro tokens present anywhere in this representation. Tokens remain byte-preserved until the resolver named by each declaration substitutes them.', - min_length=1, - ), - ] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overriding manifest-level provenance.' - ), - ] = None - delivery_type: Literal['inline_markup'] = 'inline_markup' - markup_type: Annotated[ - MarkupType, Field(description='How the destination traffics the byte-preserved markup.') - ] - markup: Annotated[ - str, - Field( - description='Exact third-party tag markup. Receivers MUST preserve its bytes and MUST NOT reinterpret it as a seller-hosted HTML5 bundle.', - max_length=1048576, - min_length=1, - ), - ] - - -class MacroDeclaration4(MacroDeclarationModel): - model_config = ConfigDict( - extra='forbid', - ) - location: Location5 | None = None - declaration_id: Annotated[ - str, - Field( - description='Identifier unique within the enclosing asset, used to correlate validation results.', - min_length=1, - pattern='^[A-Za-z0-9_-]+$', - ), - ] - token: Annotated[ - str, - Field( - description='Exact byte-preserved token expression at this occurrence, including delimiters.', - min_length=1, - ), - ] - dialect: MacroDialect - dialect_namespace: Annotated[ - AnyUrl | None, - Field( - description='Authority-controlled registry or mapping URI. Required for IAB VAST, IAB DAAST, and vendor dialects.' - ), - ] = None - dialect_revision: Annotated[ - str | None, - Field( - description='Immutable registry/mapping revision, release, commit, or digest. Required for IAB VAST, IAB DAAST, and vendor dialects.', - min_length=1, - ), - ] = None - dialect_semantic: Annotated[ - str, - Field( - description='Exact semantic identifier in the cited dialect, such as `CACHEBUSTING`, `PLAYERSTATE`, or a documented vendor name. This is not inferred from token spelling.', - min_length=1, - ), - ] - mapping_status: MacroMappingStatus - universal_semantic: Annotated[ - UniversalMacro | None, - Field( - description='Verified AdCP universal meaning. Present only when mapping_status is `verified_universal`.' - ), - ] = None - operation: MacroProcessingOperation - performed_by: Annotated[ - MacroResolver | None, - Field( - description='Actor authorized to perform the declared translation or value resolution. Omitted for preservation.' - ), - ] = None - translation_target: Annotated[ - MacroTranslationTarget | None, - Field( - description='Required only for `translate_to_native`. The output token contract that replaces this declaration after translation.' - ), - ] = None - encoding: Annotated[ - MacroEncoding, - Field( - description='Encoding for the concrete value. Translation/preservation uses `none` at depth zero.' - ), - ] - required: Annotated[ - StrictBool, - Field( - description='Whether terminal delivery must fail if the declared operation cannot complete.' - ), - ] - unavailable_behavior: Annotated[ - UnavailableBehavior, - Field( - description='Action when the responsible actor has no value. Omission is legal only for a complete URL query-value occurrence; dialect sentinels require a cited dialect rule.' - ), - ] - - -class DisplayTagAsset3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['display_tag'], - Field( - description='Discriminator identifying an atomic third-party display-tag representation.' - ), - ] = 'display_tag' - macro_declarations: Annotated[ - list[MacroDeclaration4] | None, - Field( - description='Exact macro tokens present anywhere in this representation. Tokens remain byte-preserved until the resolver named by each declaration substitutes them.', - min_length=1, - ), - ] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overriding manifest-level provenance.' - ), - ] = None - delivery_type: Literal['paired_redirect'] = 'paired_redirect' - ad_request_url: Annotated[ - MacroBearingUrl, - Field( - description='Image or ad-request URL entered into the destination ad server. This field deliberately accepts byte-preserved vendor tokens that are not RFC 6570 URI templates.' - ), - ] - clickthrough_url: Annotated[ - MacroBearingUrl, - Field( - description='Click-through URL paired with `ad_request_url`. This field deliberately accepts byte-preserved vendor tokens that are not RFC 6570 URI templates. The pair MUST NOT be split, mixed, or revised independently.' - ), - ] - - -class DisplayTagAsset(RootModel[DisplayTagAsset1 | DisplayTagAsset2 | DisplayTagAsset3]): - root: Annotated[ - DisplayTagAsset1 | DisplayTagAsset2 | DisplayTagAsset3, - Field( - description='One atomic third-party display delivery representation. `delivery_type` distinguishes a single ad-request URL, byte-preserved inline markup, and a paired redirect whose ad-request and click-through URLs MUST travel together. This is not an HTML5 bundle: seller-hosted HTML5 zip files use `asset_type: "zip"` under `format_kind: "html5"`.', - discriminator='delivery_type', - title='Display Tag Asset', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - - - -class MacroDeclaration5(MacroDeclarationModel): - location: Location6 | None = None - - -class UrlAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - MacroBearingUrl, - Field( - description='URL reference carrying plain, AdCP, IAB, or declared vendor macro syntax. Buyers preserve token delimiters; the authoritative declaration determines the processing operation and encoding.' - ), - ] - url_type: Annotated[ - UrlAssetType | None, - Field( - description='Mechanism a receiver uses to invoke this URL: `clickthrough` for a user destination, `ad_request` for a third-party display creative request, `tracker_pixel` for an event HTTP request, or `tracker_script` for a script include. SHOULD be present on every URL asset.' - ), - ] = None - macro_declarations: Annotated[ - list[MacroDeclaration5] | None, - Field( - description='One declaration per token occurrence in `url`; declaration_id values MUST be unique and every location MUST identify an existing occurrence. Absence retains legacy opaque transport behavior.', - min_length=1, - ), - ] = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - state_id: Annotated[ - str | None, - Field( - description='Binding used only when this URL populates a `seller_rendered_stateful_display` `state_click_urls` slot. It MUST match one declared `states[].state_id` (semantic validators resolve it); at most one entry per state. Omit for ordinary URL slots.' - ), - ] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance' - ), - ] = None - - -class BriefAsset(CreativeBrief): - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class CatalogAsset(Catalog): - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class CardAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - ImageAsset | VideoAsset, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - UrlAsset | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtensionRef] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.' - ), - ] = None - - -class MacroDeclaration8(MacroDeclarationModel): - location: Location9 | None = None - - -class PixelTrackerAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - PixelTrackingEvent, - Field( - description="Which event this tracker fires on. The event enum maps its first four measurement values to IAB OpenRTB Native 1.2 event types 1-4 and adds explicit AdCP events for renderer behavior that Native does not assign a standard event type:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds. Native type 4 does not require audio. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (AdCP-defined) — video reached 100% completion with audio on. Native reserves event types 500+ for exchange-specific use and does not assign this event a standard numeric type. Meaningful on non-VAST video formats where audible-complete is measured but VAST `` is not the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`; it can represent Native's exchange-specific 500+ range or another qualified vendor event without assigning an IAB numeric identity." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - MacroBearingUrl, - Field( - description='Tracker URL fired when `event` occurs. Macro processing and encoding follow an attached occurrence declaration; absent declarations retain the legacy universal-macro path.' - ), - ] - macro_declarations: Annotated[ - list[MacroDeclaration8] | None, - Field( - description='One declaration per token occurrence in `url`; declaration_id values MUST be unique and locations MUST resolve. When omitted, legacy behavior applies.', - min_length=1, - ), - ] = None - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. When tracker execution is undeclared, an unknown custom event is a forward-compatible probe and the seller silently no-ops instead of rejecting the creative. An effective tracker_execution_contract overrides that legacy fallback: with complete:true an unlisted custom selector is unsupported and rejects compatibility with tracker_contract_mismatch; a listed custom selector is an affirmative accept-and-initiate commitment.' - ), - ] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.' - ), - ] = None - - -class MacroDeclaration9(MacroDeclaration8): - pass - - -class VastTrackerAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VastTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - MacroBearingUrl, - Field( - description='Tracker URL fired for the VAST event. Attached declarations identify each macro occurrence, registry revision, processing actor, and exact encoding profile.' - ), - ] - macro_declarations: Annotated[ - list[MacroDeclaration9] | None, - Field( - description='Exact tokens in `url` and their resolver/encoding contracts.', min_length=1 - ), - ] = None - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise for compatibility with existing 3.x manifests. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. Defaults to `linear`. Existing 3.x assets remain structurally permissive; a tracker execution contract applies the standards-valid event/target matrix when matching a creative to a product.' - ), - ] = Target.linear - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.' - ), - ] = None - - -class MacroDeclaration10(MacroDeclaration8): - pass - - -class DaastTrackerAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DaastTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - MacroBearingUrl, - Field( - description='Tracker URL fired for the DAAST event. Attached declarations identify each macro occurrence, processing actor, and exact encoding profile.' - ), - ] - macro_declarations: Annotated[ - list[MacroDeclaration10] | None, - Field( - description='Exact tokens in `url` and their resolver/encoding contracts.', min_length=1 - ), - ] = None - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise for compatibility with existing 3.x manifests. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target1 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7). DAAST has no `` element. Defaults to `linear`. Existing 3.x assets remain structurally permissive; a tracker execution contract applies the standards-valid event/target matrix when matching a creative to a product.' - ), - ] = Target1.linear - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.' - ), - ] = None - - -AssetVariant = Annotated[ - ImageAsset - | VideoAsset - | AudioAsset - | VastAsset - | DisplayTagAsset - | TextAsset - | UrlAsset - | HtmlAsset - | JavascriptAsset - | ZipAsset - | WebhookAsset - | CssAsset - | DaastAsset - | MarkdownAsset - | BriefAsset - | CatalogAsset - | PublishedPostAsset - | CardAsset - | PixelTrackerAsset - | VastTrackerAsset - | DaastTrackerAsset, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), -] diff --git a/src/adcp/types/generated_poc/core/assets/audio_asset.py b/src/adcp/types/generated_poc/core/assets/audio_asset.py deleted file mode 100644 index 8cb913633..000000000 --- a/src/adcp/types/generated_poc/core/assets/audio_asset.py +++ /dev/null @@ -1,74 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/assets/audio_asset.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from enum import IntEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, ConfigDict, Field, StrictFloat - -from ...enums import audio_channel_layout -from .. import provenance as provenance_1 - - -class BitDepth(IntEnum): - integer_16 = 16 - integer_24 = 24 - integer_32 = 32 - - -class AudioAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - SchemaInt | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[SchemaInt | None, Field(description='File size in bytes', ge=1)] = ( - None - ) - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - SchemaInt | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: Annotated[ - audio_channel_layout.AudioChannelLayout | None, Field(description='Channel configuration') - ] = None - bit_depth: Annotated[BitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - SchemaInt | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[ - StrictFloat | None, Field(description='Integrated loudness in LUFS') - ] = None - true_peak_dbfs: Annotated[StrictFloat | None, Field(description='True peak level in dBFS')] = ( - None - ) - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - provenance_1.Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/assets/brief_asset.py b/src/adcp/types/generated_poc/core/assets/brief_asset.py deleted file mode 100644 index cb6f0f962..000000000 --- a/src/adcp/types/generated_poc/core/assets/brief_asset.py +++ /dev/null @@ -1,20 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/assets/brief_asset.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from pydantic import Field - -from ..creative_brief import CreativeBrief - - -class BriefAsset(CreativeBrief): - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' diff --git a/src/adcp/types/generated_poc/core/assets/card_asset.py b/src/adcp/types/generated_poc/core/assets/card_asset.py deleted file mode 100644 index 5d202c99b..000000000 --- a/src/adcp/types/generated_poc/core/assets/card_asset.py +++ /dev/null @@ -1,67 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/assets/card_asset.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from . import asset_union - - -class CardAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - asset_union.ImageAsset | asset_union.VideoAsset, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - asset_union.UrlAsset | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.' - ), - ] = None - platform_extensions: Annotated[ - list[asset_union.PlatformExtensionRef] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - asset_union.Provenance | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/assets/catalog_asset.py b/src/adcp/types/generated_poc/core/assets/catalog_asset.py deleted file mode 100644 index 939cdcad0..000000000 --- a/src/adcp/types/generated_poc/core/assets/catalog_asset.py +++ /dev/null @@ -1,20 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/assets/catalog_asset.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from pydantic import Field - -from ..catalog import Catalog - - -class CatalogAsset(Catalog): - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' diff --git a/src/adcp/types/generated_poc/core/assets/css_asset.py b/src/adcp/types/generated_poc/core/assets/css_asset.py deleted file mode 100644 index 41e41b5ad..000000000 --- a/src/adcp/types/generated_poc/core/assets/css_asset.py +++ /dev/null @@ -1,34 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/assets/css_asset.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from .. import provenance as provenance_1 - - -class CssAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - provenance_1.Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/assets/daast_asset.py b/src/adcp/types/generated_poc/core/assets/daast_asset.py deleted file mode 100644 index cf4a9c9b9..000000000 --- a/src/adcp/types/generated_poc/core/assets/daast_asset.py +++ /dev/null @@ -1,339 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/assets/daast_asset.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Any, Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, ConfigDict, Field, RootModel, StrictBool - -from ...enums import daast_tracking_event -from ...enums import daast_version as daast_version_1 -from .. import macro_bearing_url -from .. import provenance as provenance_1 -from . import asset_union - - -class Field1(StrEnum): - url = 'url' - markup = 'markup' - content = 'content' - ad_request_url = 'ad_request_url' - clickthrough_url = 'clickthrough_url' - - -class Location(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - field: Annotated[Literal['url'], Field(description='Asset field containing this occurrence.')] = 'url' - occurrence: Annotated[ - SchemaInt, - Field( - description='Zero-based occurrence of this exact token within the named field.', ge=0 - ), - ] - context: asset_union.MacroValueContext - - -class UnavailableBehavior(StrEnum): - preserve = 'preserve' - omit_parameter = 'omit_parameter' - dialect_sentinel = 'dialect_sentinel' - reject = 'reject' - - -class Location13(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - field: Annotated[ - Literal['content'], Field(description='Asset field containing this occurrence.') - ] = 'content' - occurrence: Annotated[ - SchemaInt, - Field( - description='Zero-based occurrence of this exact token within the named field.', ge=0 - ), - ] - context: asset_union.MacroValueContext - - -class MacroDeclaration(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - declaration_id: Annotated[ - str, - Field( - description='Identifier unique within the enclosing asset, used to correlate validation results.', - min_length=1, - pattern='^[A-Za-z0-9_-]+$', - ), - ] - token: Annotated[ - str, - Field( - description='Exact byte-preserved token expression at this occurrence, including delimiters.', - min_length=1, - ), - ] - dialect: asset_union.MacroDialect - dialect_namespace: Annotated[ - AnyUrl | None, - Field( - description='Authority-controlled registry or mapping URI. Required for IAB VAST, IAB DAAST, and vendor dialects.' - ), - ] = None - dialect_revision: Annotated[ - str | None, - Field( - description='Immutable registry/mapping revision, release, commit, or digest. Required for IAB VAST, IAB DAAST, and vendor dialects.', - min_length=1, - ), - ] = None - dialect_semantic: Annotated[ - str, - Field( - description='Exact semantic identifier in the cited dialect, such as `CACHEBUSTING`, `PLAYERSTATE`, or a documented vendor name. This is not inferred from token spelling.', - min_length=1, - ), - ] - mapping_status: asset_union.MacroMappingStatus - universal_semantic: Annotated[ - asset_union.UniversalMacro | None, - Field( - description='Verified AdCP universal meaning. Present only when mapping_status is `verified_universal`.' - ), - ] = None - operation: asset_union.MacroProcessingOperation - performed_by: Annotated[ - asset_union.MacroResolver | None, - Field( - description='Actor authorized to perform the declared translation or value resolution. Omitted for preservation.' - ), - ] = None - translation_target: Annotated[ - asset_union.MacroTranslationTarget | None, - Field( - description='Required only for `translate_to_native`. The output token contract that replaces this declaration after translation.' - ), - ] = None - location: Location - encoding: Annotated[ - asset_union.MacroEncoding, - Field( - description='Encoding for the concrete value. Translation/preservation uses `none` at depth zero.' - ), - ] - required: Annotated[ - StrictBool, - Field( - description='Whether terminal delivery must fail if the declared operation cannot complete.' - ), - ] - unavailable_behavior: Annotated[ - UnavailableBehavior, - Field( - description='Action when the responsible actor has no value. Omission is legal only for a complete URL query-value occurrence; dialect sentinels require a cited dialect rule.' - ), - ] - - -class MacroDeclaration12(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - declaration_id: Annotated[ - str, - Field( - description='Identifier unique within the enclosing asset, used to correlate validation results.', - min_length=1, - pattern='^[A-Za-z0-9_-]+$', - ), - ] - token: Annotated[ - str, - Field( - description='Exact byte-preserved token expression at this occurrence, including delimiters.', - min_length=1, - ), - ] - dialect: asset_union.MacroDialect - dialect_namespace: Annotated[ - AnyUrl | None, - Field( - description='Authority-controlled registry or mapping URI. Required for IAB VAST, IAB DAAST, and vendor dialects.' - ), - ] = None - dialect_revision: Annotated[ - str | None, - Field( - description='Immutable registry/mapping revision, release, commit, or digest. Required for IAB VAST, IAB DAAST, and vendor dialects.', - min_length=1, - ), - ] = None - dialect_semantic: Annotated[ - str, - Field( - description='Exact semantic identifier in the cited dialect, such as `CACHEBUSTING`, `PLAYERSTATE`, or a documented vendor name. This is not inferred from token spelling.', - min_length=1, - ), - ] - mapping_status: asset_union.MacroMappingStatus - universal_semantic: Annotated[ - asset_union.UniversalMacro | None, - Field( - description='Verified AdCP universal meaning. Present only when mapping_status is `verified_universal`.' - ), - ] = None - operation: asset_union.MacroProcessingOperation - performed_by: Annotated[ - asset_union.MacroResolver | None, - Field( - description='Actor authorized to perform the declared translation or value resolution. Omitted for preservation.' - ), - ] = None - translation_target: Annotated[ - asset_union.MacroTranslationTarget | None, - Field( - description='Required only for `translate_to_native`. The output token contract that replaces this declaration after translation.' - ), - ] = None - location: Location13 - encoding: Annotated[ - asset_union.MacroEncoding, - Field( - description='Encoding for the concrete value. Translation/preservation uses `none` at depth zero.' - ), - ] - required: Annotated[ - StrictBool, - Field( - description='Whether terminal delivery must fail if the declared operation cannot complete.' - ), - ] - unavailable_behavior: Annotated[ - UnavailableBehavior, - Field( - description='Action when the responsible actor has no value. Omission is legal only for a complete URL query-value occurrence; dialect sentinels require a cited dialect rule.' - ), - ] - - -class DaastAsset3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: Annotated[ - daast_version_1.DaastVersion | None, Field(description='DAAST specification version') - ] = None - duration_ms: Annotated[ - SchemaInt | None, - Field(description='Expected audio duration in milliseconds (if known)', ge=0), - ] = None - tracking_events: Annotated[ - list[daast_tracking_event.DaastTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - StrictBool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - provenance_1.Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance' - ), - ] = None - macro_declarations: Annotated[ - list[MacroDeclaration] | None, - Field( - description='One declaration per occurrence in a field carried by this asset. URL-delivered assets declare only locator-URL occurrences; receivers do not infer declarations for tokens discovered later in a fetched document.', - min_length=1, - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - macro_bearing_url.MacroBearingUrl, - Field( - description='URL endpoint returning DAAST XML. Macro delimiters remain byte-preserved and are processed only under attached occurrence declarations.' - ), - ] - - -class DaastAsset4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: Annotated[ - daast_version_1.DaastVersion | None, Field(description='DAAST specification version') - ] = None - duration_ms: Annotated[ - SchemaInt | None, - Field(description='Expected audio duration in milliseconds (if known)', ge=0), - ] = None - tracking_events: Annotated[ - list[daast_tracking_event.DaastTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - StrictBool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - provenance_1.Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance' - ), - ] = None - macro_declarations: Annotated[ - list[MacroDeclaration12] | None, - Field( - description='One declaration per occurrence in a field carried by this asset. URL-delivered assets declare only locator-URL occurrences; receivers do not infer declarations for tokens discovered later in a fetched document.', - min_length=1, - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class DaastAsset(RootModel[DaastAsset3 | DaastAsset4]): - root: Annotated[ - DaastAsset3 | DaastAsset4, - Field( - description='DAAST (Digital Audio Ad Serving Template) tag for third-party audio ad serving', - discriminator='delivery_type', - title='DAAST Asset', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) diff --git a/src/adcp/types/generated_poc/core/assets/daast_tracker_asset.py b/src/adcp/types/generated_poc/core/assets/daast_tracker_asset.py deleted file mode 100644 index 065ec6771..000000000 --- a/src/adcp/types/generated_poc/core/assets/daast_tracker_asset.py +++ /dev/null @@ -1,97 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/assets/daast_tracker_asset.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field - -from ...enums import daast_tracking_event -from .. import macro_bearing_url -from .. import provenance as provenance_1 -from ..macro_declaration import MacroDeclaration as MacroDeclaration_1 -from . import asset_union - - -class Field1(StrEnum): - url = 'url' - markup = 'markup' - content = 'content' - ad_request_url = 'ad_request_url' - clickthrough_url = 'clickthrough_url' - - -class Location(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - field: Annotated[Literal['url'], Field(description='Asset field containing this occurrence.')] = 'url' - occurrence: Annotated[ - SchemaInt, - Field( - description='Zero-based occurrence of this exact token within the named field.', ge=0 - ), - ] - context: asset_union.MacroValueContext - - -class Target(StrEnum): - linear = 'linear' - companion = 'companion' - - -class MacroDeclaration(MacroDeclaration_1): - location: Location | None = None - - -class DaastTrackerAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - daast_tracking_event.DaastTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - macro_bearing_url.MacroBearingUrl, - Field( - description='Tracker URL fired for the DAAST event. Attached declarations identify each macro occurrence, processing actor, and exact encoding profile.' - ), - ] - macro_declarations: Annotated[ - list[MacroDeclaration] | None, - Field( - description='Exact tokens in `url` and their resolver/encoding contracts.', min_length=1 - ), - ] = None - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise for compatibility with existing 3.x manifests. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7). DAAST has no `` element. Defaults to `linear`. Existing 3.x assets remain structurally permissive; a tracker execution contract applies the standards-valid event/target matrix when matching a creative to a product.' - ), - ] = Target.linear - provenance: Annotated[ - provenance_1.Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/assets/display_tag_asset.py b/src/adcp/types/generated_poc/core/assets/display_tag_asset.py deleted file mode 100644 index d5ceb0365..000000000 --- a/src/adcp/types/generated_poc/core/assets/display_tag_asset.py +++ /dev/null @@ -1,425 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/assets/display_tag_asset.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Any, Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, ConfigDict, Field, RootModel, StrictBool - -from .. import macro_bearing_url -from .. import provenance as provenance_1 -from ..macro_declaration import MacroDeclaration as MacroDeclaration_1 -from . import asset_union - - -class Field1(StrEnum): - url = 'url' - markup = 'markup' - content = 'content' - ad_request_url = 'ad_request_url' - clickthrough_url = 'clickthrough_url' - - -class Location(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - field: Annotated[Field1, Field(description='Asset field containing this occurrence.')] - occurrence: Annotated[ - SchemaInt, - Field( - description='Zero-based occurrence of this exact token within the named field.', ge=0 - ), - ] - context: asset_union.MacroValueContext - - -class UnavailableBehavior(StrEnum): - preserve = 'preserve' - omit_parameter = 'omit_parameter' - dialect_sentinel = 'dialect_sentinel' - reject = 'reject' - - -class Location17(Location): - pass - - -class MarkupType(StrEnum): - iframe_javascript = 'iframe_javascript' - javascript = 'javascript' - standard = 'standard' - - -class Location18(Location): - pass - - -class MacroDeclaration(MacroDeclaration_1): - model_config = ConfigDict( - extra='forbid', - ) - location: Location | None = None - declaration_id: Annotated[ - str, - Field( - description='Identifier unique within the enclosing asset, used to correlate validation results.', - min_length=1, - pattern='^[A-Za-z0-9_-]+$', - ), - ] - token: Annotated[ - str, - Field( - description='Exact byte-preserved token expression at this occurrence, including delimiters.', - min_length=1, - ), - ] - dialect: asset_union.MacroDialect - dialect_namespace: Annotated[ - AnyUrl | None, - Field( - description='Authority-controlled registry or mapping URI. Required for IAB VAST, IAB DAAST, and vendor dialects.' - ), - ] = None - dialect_revision: Annotated[ - str | None, - Field( - description='Immutable registry/mapping revision, release, commit, or digest. Required for IAB VAST, IAB DAAST, and vendor dialects.', - min_length=1, - ), - ] = None - dialect_semantic: Annotated[ - str, - Field( - description='Exact semantic identifier in the cited dialect, such as `CACHEBUSTING`, `PLAYERSTATE`, or a documented vendor name. This is not inferred from token spelling.', - min_length=1, - ), - ] - mapping_status: asset_union.MacroMappingStatus - universal_semantic: Annotated[ - asset_union.UniversalMacro | None, - Field( - description='Verified AdCP universal meaning. Present only when mapping_status is `verified_universal`.' - ), - ] = None - operation: asset_union.MacroProcessingOperation - performed_by: Annotated[ - asset_union.MacroResolver | None, - Field( - description='Actor authorized to perform the declared translation or value resolution. Omitted for preservation.' - ), - ] = None - translation_target: Annotated[ - asset_union.MacroTranslationTarget | None, - Field( - description='Required only for `translate_to_native`. The output token contract that replaces this declaration after translation.' - ), - ] = None - encoding: Annotated[ - asset_union.MacroEncoding, - Field( - description='Encoding for the concrete value. Translation/preservation uses `none` at depth zero.' - ), - ] - required: Annotated[ - StrictBool, - Field( - description='Whether terminal delivery must fail if the declared operation cannot complete.' - ), - ] - unavailable_behavior: Annotated[ - UnavailableBehavior, - Field( - description='Action when the responsible actor has no value. Omission is legal only for a complete URL query-value occurrence; dialect sentinels require a cited dialect rule.' - ), - ] - - -class DisplayTagAsset4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['display_tag'], - Field( - description='Discriminator identifying an atomic third-party display-tag representation.' - ), - ] = 'display_tag' - macro_declarations: Annotated[ - list[MacroDeclaration] | None, - Field( - description='Exact macro tokens present anywhere in this representation. Tokens remain byte-preserved until the resolver named by each declaration substitutes them.', - min_length=1, - ), - ] = None - provenance: Annotated[ - provenance_1.Provenance | None, - Field( - description='Provenance metadata for this asset, overriding manifest-level provenance.' - ), - ] = None - delivery_type: Literal['tag_url'] = 'tag_url' - url: Annotated[ - macro_bearing_url.MacroBearingUrl, - Field( - description='Ad-request URL invoked at impression time. It is equivalent to the backward-compatible `url` asset with `url_type: "ad_request"`.' - ), - ] - - -class MacroDeclaration15(MacroDeclaration_1): - model_config = ConfigDict( - extra='forbid', - ) - location: Location17 | None = None - declaration_id: Annotated[ - str, - Field( - description='Identifier unique within the enclosing asset, used to correlate validation results.', - min_length=1, - pattern='^[A-Za-z0-9_-]+$', - ), - ] - token: Annotated[ - str, - Field( - description='Exact byte-preserved token expression at this occurrence, including delimiters.', - min_length=1, - ), - ] - dialect: asset_union.MacroDialect - dialect_namespace: Annotated[ - AnyUrl | None, - Field( - description='Authority-controlled registry or mapping URI. Required for IAB VAST, IAB DAAST, and vendor dialects.' - ), - ] = None - dialect_revision: Annotated[ - str | None, - Field( - description='Immutable registry/mapping revision, release, commit, or digest. Required for IAB VAST, IAB DAAST, and vendor dialects.', - min_length=1, - ), - ] = None - dialect_semantic: Annotated[ - str, - Field( - description='Exact semantic identifier in the cited dialect, such as `CACHEBUSTING`, `PLAYERSTATE`, or a documented vendor name. This is not inferred from token spelling.', - min_length=1, - ), - ] - mapping_status: asset_union.MacroMappingStatus - universal_semantic: Annotated[ - asset_union.UniversalMacro | None, - Field( - description='Verified AdCP universal meaning. Present only when mapping_status is `verified_universal`.' - ), - ] = None - operation: asset_union.MacroProcessingOperation - performed_by: Annotated[ - asset_union.MacroResolver | None, - Field( - description='Actor authorized to perform the declared translation or value resolution. Omitted for preservation.' - ), - ] = None - translation_target: Annotated[ - asset_union.MacroTranslationTarget | None, - Field( - description='Required only for `translate_to_native`. The output token contract that replaces this declaration after translation.' - ), - ] = None - encoding: Annotated[ - asset_union.MacroEncoding, - Field( - description='Encoding for the concrete value. Translation/preservation uses `none` at depth zero.' - ), - ] - required: Annotated[ - StrictBool, - Field( - description='Whether terminal delivery must fail if the declared operation cannot complete.' - ), - ] - unavailable_behavior: Annotated[ - UnavailableBehavior, - Field( - description='Action when the responsible actor has no value. Omission is legal only for a complete URL query-value occurrence; dialect sentinels require a cited dialect rule.' - ), - ] - - -class DisplayTagAsset5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['display_tag'], - Field( - description='Discriminator identifying an atomic third-party display-tag representation.' - ), - ] = 'display_tag' - macro_declarations: Annotated[ - list[MacroDeclaration15] | None, - Field( - description='Exact macro tokens present anywhere in this representation. Tokens remain byte-preserved until the resolver named by each declaration substitutes them.', - min_length=1, - ), - ] = None - provenance: Annotated[ - provenance_1.Provenance | None, - Field( - description='Provenance metadata for this asset, overriding manifest-level provenance.' - ), - ] = None - delivery_type: Literal['inline_markup'] = 'inline_markup' - markup_type: Annotated[ - MarkupType, Field(description='How the destination traffics the byte-preserved markup.') - ] - markup: Annotated[ - str, - Field( - description='Exact third-party tag markup. Receivers MUST preserve its bytes and MUST NOT reinterpret it as a seller-hosted HTML5 bundle.', - max_length=1048576, - min_length=1, - ), - ] - - -class MacroDeclaration16(MacroDeclaration_1): - model_config = ConfigDict( - extra='forbid', - ) - location: Location18 | None = None - declaration_id: Annotated[ - str, - Field( - description='Identifier unique within the enclosing asset, used to correlate validation results.', - min_length=1, - pattern='^[A-Za-z0-9_-]+$', - ), - ] - token: Annotated[ - str, - Field( - description='Exact byte-preserved token expression at this occurrence, including delimiters.', - min_length=1, - ), - ] - dialect: asset_union.MacroDialect - dialect_namespace: Annotated[ - AnyUrl | None, - Field( - description='Authority-controlled registry or mapping URI. Required for IAB VAST, IAB DAAST, and vendor dialects.' - ), - ] = None - dialect_revision: Annotated[ - str | None, - Field( - description='Immutable registry/mapping revision, release, commit, or digest. Required for IAB VAST, IAB DAAST, and vendor dialects.', - min_length=1, - ), - ] = None - dialect_semantic: Annotated[ - str, - Field( - description='Exact semantic identifier in the cited dialect, such as `CACHEBUSTING`, `PLAYERSTATE`, or a documented vendor name. This is not inferred from token spelling.', - min_length=1, - ), - ] - mapping_status: asset_union.MacroMappingStatus - universal_semantic: Annotated[ - asset_union.UniversalMacro | None, - Field( - description='Verified AdCP universal meaning. Present only when mapping_status is `verified_universal`.' - ), - ] = None - operation: asset_union.MacroProcessingOperation - performed_by: Annotated[ - asset_union.MacroResolver | None, - Field( - description='Actor authorized to perform the declared translation or value resolution. Omitted for preservation.' - ), - ] = None - translation_target: Annotated[ - asset_union.MacroTranslationTarget | None, - Field( - description='Required only for `translate_to_native`. The output token contract that replaces this declaration after translation.' - ), - ] = None - encoding: Annotated[ - asset_union.MacroEncoding, - Field( - description='Encoding for the concrete value. Translation/preservation uses `none` at depth zero.' - ), - ] - required: Annotated[ - StrictBool, - Field( - description='Whether terminal delivery must fail if the declared operation cannot complete.' - ), - ] - unavailable_behavior: Annotated[ - UnavailableBehavior, - Field( - description='Action when the responsible actor has no value. Omission is legal only for a complete URL query-value occurrence; dialect sentinels require a cited dialect rule.' - ), - ] - - -class DisplayTagAsset6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['display_tag'], - Field( - description='Discriminator identifying an atomic third-party display-tag representation.' - ), - ] = 'display_tag' - macro_declarations: Annotated[ - list[MacroDeclaration16] | None, - Field( - description='Exact macro tokens present anywhere in this representation. Tokens remain byte-preserved until the resolver named by each declaration substitutes them.', - min_length=1, - ), - ] = None - provenance: Annotated[ - provenance_1.Provenance | None, - Field( - description='Provenance metadata for this asset, overriding manifest-level provenance.' - ), - ] = None - delivery_type: Literal['paired_redirect'] = 'paired_redirect' - ad_request_url: Annotated[ - macro_bearing_url.MacroBearingUrl, - Field( - description='Image or ad-request URL entered into the destination ad server. This field deliberately accepts byte-preserved vendor tokens that are not RFC 6570 URI templates.' - ), - ] - clickthrough_url: Annotated[ - macro_bearing_url.MacroBearingUrl, - Field( - description='Click-through URL paired with `ad_request_url`. This field deliberately accepts byte-preserved vendor tokens that are not RFC 6570 URI templates. The pair MUST NOT be split, mixed, or revised independently.' - ), - ] - - -class DisplayTagAsset(RootModel[DisplayTagAsset4 | DisplayTagAsset5 | DisplayTagAsset6]): - root: Annotated[ - DisplayTagAsset4 | DisplayTagAsset5 | DisplayTagAsset6, - Field( - description='One atomic third-party display delivery representation. `delivery_type` distinguishes a single ad-request URL, byte-preserved inline markup, and a paired redirect whose ad-request and click-through URLs MUST travel together. This is not an HTML5 bundle: seller-hosted HTML5 zip files use `asset_type: "zip"` under `format_kind: "html5"`.', - discriminator='delivery_type', - title='Display Tag Asset', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) diff --git a/src/adcp/types/generated_poc/core/assets/html_asset.py b/src/adcp/types/generated_poc/core/assets/html_asset.py deleted file mode 100644 index 9ad378fc1..000000000 --- a/src/adcp/types/generated_poc/core/assets/html_asset.py +++ /dev/null @@ -1,56 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/assets/html_asset.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictBool - -from .. import provenance as provenance_1 - - -class Accessibility(AdCPBaseModel): - alt_text: Annotated[ - str | None, Field(description='Text alternative describing the creative content') - ] = None - keyboard_navigable: Annotated[ - StrictBool | None, - Field(description='Whether the creative can be fully operated via keyboard'), - ] = None - motion_control: Annotated[ - StrictBool | None, - Field( - description='Whether the creative respects prefers-reduced-motion or provides pause/stop controls' - ), - ] = None - screen_reader_tested: Annotated[ - StrictBool | None, - Field(description='Whether the creative has been tested with screen readers'), - ] = None - - -class HtmlAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - provenance_1.Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/assets/image_asset.py b/src/adcp/types/generated_poc/core/assets/image_asset.py deleted file mode 100644 index 9a5de6b11..000000000 --- a/src/adcp/types/generated_poc/core/assets/image_asset.py +++ /dev/null @@ -1,77 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/assets/image_asset.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarFloat -from pydantic import AnyUrl, ConfigDict, Field, StrictFloat - -from .. import provenance as provenance_1 - - -class FocalPointItem(ScalarFloat): - __slots__ = () - _constraints = {'ge': 0.0, 'le': 1.0} - - -class ImageAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[SchemaInt, Field(description='Width in pixels', ge=1)] - height: Annotated[SchemaInt, Field(description='Height in pixels', ge=1)] - file_size_bytes: Annotated[ - SchemaInt | None, - Field( - description='Image file size in bytes. Required by agents that advertise a max_file_size_kb constraint.', - ge=1, - ), - ] = None - pixel_ratio: Annotated[ - StrictFloat | None, - Field( - description='Intrinsic pixels per logical render pixel (for example `2` for a 600×500 image intended to render at 300×250). Optional because a validator can infer the ratio when the target format declares logical dimensions. When supplied, it MUST agree with both `width / logical_width` and `height / logical_height`; it is never a substitute for the intrinsic `width` and `height` fields.', - gt=0.0, - ), - ] = None - state_id: Annotated[ - str | None, - Field( - description='Binding used only when this image populates a `seller_rendered_stateful_display` `state_canvases` slot. It MUST match one declared `states[].state_id` (semantic validators resolve it). Omit for ordinary image slots.' - ), - ] = None - breakpoint_id: Annotated[ - str | None, - Field( - description='Binding used only when this image populates a `seller_rendered_stateful_display` `state_canvases` slot. It MUST match one breakpoint declared on the selected state (semantic validators resolve it). Omit for ordinary image slots.' - ), - ] = None - focal_point: Annotated[ - list[FocalPointItem] | None, - Field( - description="Normalized `[x, y]` coordinates (0–1 from top-left) of the image's visual anchor. Seller-side renderers crop toward the focal point when deriving renditions across breakpoints and aspect ratios; absent, cropping falls back to center-weighted defaults.", - max_length=2, - min_length=2, - ), - ] = None - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - provenance_1.Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/assets/javascript_asset.py b/src/adcp/types/generated_poc/core/assets/javascript_asset.py deleted file mode 100644 index 81b2d19fa..000000000 --- a/src/adcp/types/generated_poc/core/assets/javascript_asset.py +++ /dev/null @@ -1,60 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/assets/javascript_asset.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictBool - -from ...enums import javascript_module_type -from .. import provenance as provenance_1 - - -class Accessibility(AdCPBaseModel): - alt_text: Annotated[ - str | None, Field(description='Text alternative describing the creative content') - ] = None - keyboard_navigable: Annotated[ - StrictBool | None, - Field(description='Whether the creative can be fully operated via keyboard'), - ] = None - motion_control: Annotated[ - StrictBool | None, - Field( - description='Whether the creative respects prefers-reduced-motion or provides pause/stop controls' - ), - ] = None - screen_reader_tested: Annotated[ - StrictBool | None, - Field(description='Whether the creative has been tested with screen readers'), - ] = None - - -class JavascriptAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: Annotated[ - javascript_module_type.JavascriptModuleType | None, - Field(description='JavaScript module type'), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - provenance_1.Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/assets/markdown_asset.py b/src/adcp/types/generated_poc/core/assets/markdown_asset.py deleted file mode 100644 index af288a283..000000000 --- a/src/adcp/types/generated_poc/core/assets/markdown_asset.py +++ /dev/null @@ -1,48 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/assets/markdown_asset.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictBool - -from ...enums import markdown_flavor as markdown_flavor_1 - - -class MarkdownAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['markdown'], - Field( - description='Discriminator identifying this as a markdown asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'markdown' - content: Annotated[ - str, - Field( - description='Markdown content following CommonMark spec with optional GitHub Flavored Markdown extensions' - ), - ] - language: Annotated[ - str | None, - Field( - description='Optional language claim for this markdown. In a materialized creative localization variant, localized-creative-asset.json requires this value to use /schemas/core/locale-tag.json and conformance requires exact equality with the enclosing variant locale. General non-localized assets retain the legacy unconstrained string for compatibility.' - ), - ] = None - markdown_flavor: Annotated[ - markdown_flavor_1.MarkdownFlavor | None, - Field( - description='Markdown flavor used. CommonMark for strict compatibility, GFM for tables/task lists/strikethrough.' - ), - ] = markdown_flavor_1.MarkdownFlavor.commonmark - allow_raw_html: Annotated[ - StrictBool | None, - Field( - description='Whether raw HTML blocks are allowed in the markdown. False recommended for security.' - ), - ] = False diff --git a/src/adcp/types/generated_poc/core/assets/pixel_tracker_asset.py b/src/adcp/types/generated_poc/core/assets/pixel_tracker_asset.py deleted file mode 100644 index e7c9ff657..000000000 --- a/src/adcp/types/generated_poc/core/assets/pixel_tracker_asset.py +++ /dev/null @@ -1,97 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/assets/pixel_tracker_asset.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field - -from ...enums import pixel_tracking_event -from .. import macro_bearing_url -from .. import provenance as provenance_1 -from ..macro_declaration import MacroDeclaration as MacroDeclaration_1 -from . import asset_union - - -class Method(StrEnum): - img = 'img' - js = 'js' - - -class Field1(StrEnum): - url = 'url' - markup = 'markup' - content = 'content' - ad_request_url = 'ad_request_url' - clickthrough_url = 'clickthrough_url' - - -class Location(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - field: Annotated[Literal['url'], Field(description='Asset field containing this occurrence.')] = 'url' - occurrence: Annotated[ - SchemaInt, - Field( - description='Zero-based occurrence of this exact token within the named field.', ge=0 - ), - ] - context: asset_union.MacroValueContext - - -class MacroDeclaration(MacroDeclaration_1): - location: Location | None = None - - -class PixelTrackerAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - pixel_tracking_event.PixelTrackingEvent, - Field( - description="Which event this tracker fires on. The event enum maps its first four measurement values to IAB OpenRTB Native 1.2 event types 1-4 and adds explicit AdCP events for renderer behavior that Native does not assign a standard event type:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds. Native type 4 does not require audio. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (AdCP-defined) — video reached 100% completion with audio on. Native reserves event types 500+ for exchange-specific use and does not assign this event a standard numeric type. Meaningful on non-VAST video formats where audible-complete is measured but VAST `` is not the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`; it can represent Native's exchange-specific 500+ range or another qualified vendor event without assigning an IAB numeric identity." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - macro_bearing_url.MacroBearingUrl, - Field( - description='Tracker URL fired when `event` occurs. Macro processing and encoding follow an attached occurrence declaration; absent declarations retain the legacy universal-macro path.' - ), - ] - macro_declarations: Annotated[ - list[MacroDeclaration] | None, - Field( - description='One declaration per token occurrence in `url`; declaration_id values MUST be unique and locations MUST resolve. When omitted, legacy behavior applies.', - min_length=1, - ), - ] = None - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. When tracker execution is undeclared, an unknown custom event is a forward-compatible probe and the seller silently no-ops instead of rejecting the creative. An effective tracker_execution_contract overrides that legacy fallback: with complete:true an unlisted custom selector is unsupported and rejects compatibility with tracker_contract_mismatch; a listed custom selector is an affirmative accept-and-initiate commitment.' - ), - ] = None - provenance: Annotated[ - provenance_1.Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/assets/published_post_asset.py b/src/adcp/types/generated_poc/core/assets/published_post_asset.py deleted file mode 100644 index 204165e61..000000000 --- a/src/adcp/types/generated_poc/core/assets/published_post_asset.py +++ /dev/null @@ -1,130 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/assets/published_post_asset.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, model_validator - -from .. import provenance as provenance_1 - - -class IdentityRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - handle: Annotated[ - str | None, Field(description='Human-readable handle or username when available.') - ] = None - profile_url: Annotated[ - AnyUrl | None, Field(description='URL for the authoring profile, page, or channel.') - ] = None - platform_identity_id: Annotated[ - str | None, Field(description='Opaque platform-native identity identifier.') - ] = None - - -class Status(StrEnum): - authorized = 'authorized' - pending = 'pending' - expired = 'expired' - revoked = 'revoked' - not_required = 'not_required' - unknown = 'unknown' - - -class ReferenceAuthorization(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Status, - Field( - description='Current seller-observed authorization state. `authorized`: seller can serve the referenced post. `pending`: additional creator/account action is required. `expired` or `revoked`: authorization previously existed but no longer permits serving. `not_required`: the seller can legally and technically serve without a separate identity authorization. `unknown`: seller has not checked or cannot disclose the state.' - ), - ] - checked_at: Annotated[ - AwareDatetime | None, - Field(description='When the seller last checked the authorization state.'), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field(description='When the authorization expires, if the platform exposes an expiry.'), - ] = None - authorization_url: Annotated[ - AnyUrl | None, - Field(description='Optional URL the buyer or creator can use to complete authorization.'), - ] = None - authorization_instructions: Annotated[ - str | None, - Field(description='Human-readable instructions for completing or restoring authorization.'), - ] = None - - -class PublishedPostAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - provenance_1.Provenance | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.' - ), - ] = None - - @model_validator(mode='after') - def _require_schema_required_group(self) -> PublishedPostAsset: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('post_url',), ('platform_post_id',),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'PublishedPostAsset requires at least one of these field groups: post_url | platform_post_id' - ) diff --git a/src/adcp/types/generated_poc/core/assets/text_asset.py b/src/adcp/types/generated_poc/core/assets/text_asset.py deleted file mode 100644 index 11f1ea791..000000000 --- a/src/adcp/types/generated_poc/core/assets/text_asset.py +++ /dev/null @@ -1,37 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/assets/text_asset.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from .. import provenance as provenance_1 - - -class TextAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[ - str | None, - Field( - description='Optional language claim for this text. In a materialized creative localization variant, localized-creative-asset.json requires this value to use /schemas/core/locale-tag.json and conformance requires exact equality with the enclosing variant locale. General non-localized assets retain the legacy unconstrained string for compatibility.' - ), - ] = None - provenance: Annotated[ - provenance_1.Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/assets/url_asset.py b/src/adcp/types/generated_poc/core/assets/url_asset.py deleted file mode 100644 index 0e312fafa..000000000 --- a/src/adcp/types/generated_poc/core/assets/url_asset.py +++ /dev/null @@ -1,89 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/assets/url_asset.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field - -from ...enums import url_asset_type -from .. import macro_bearing_url -from .. import provenance as provenance_1 -from ..macro_declaration import MacroDeclaration as MacroDeclaration_1 -from . import asset_union - - -class Field1(StrEnum): - url = 'url' - markup = 'markup' - content = 'content' - ad_request_url = 'ad_request_url' - clickthrough_url = 'clickthrough_url' - - -class Location(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - field: Annotated[Literal['url'], Field(description='Asset field containing this occurrence.')] = 'url' - occurrence: Annotated[ - SchemaInt, - Field( - description='Zero-based occurrence of this exact token within the named field.', ge=0 - ), - ] - context: asset_union.MacroValueContext - - -class MacroDeclaration(MacroDeclaration_1): - location: Location | None = None - - -class UrlAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - macro_bearing_url.MacroBearingUrl, - Field( - description='URL reference carrying plain, AdCP, IAB, or declared vendor macro syntax. Buyers preserve token delimiters; the authoritative declaration determines the processing operation and encoding.' - ), - ] - url_type: Annotated[ - url_asset_type.UrlAssetType | None, - Field( - description='Mechanism a receiver uses to invoke this URL: `clickthrough` for a user destination, `ad_request` for a third-party display creative request, `tracker_pixel` for an event HTTP request, or `tracker_script` for a script include. SHOULD be present on every URL asset.' - ), - ] = None - macro_declarations: Annotated[ - list[MacroDeclaration] | None, - Field( - description='One declaration per token occurrence in `url`; declaration_id values MUST be unique and every location MUST identify an existing occurrence. Absence retains legacy opaque transport behavior.', - min_length=1, - ), - ] = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - state_id: Annotated[ - str | None, - Field( - description='Binding used only when this URL populates a `seller_rendered_stateful_display` `state_click_urls` slot. It MUST match one declared `states[].state_id` (semantic validators resolve it); at most one entry per state. Omit for ordinary URL slots.' - ), - ] = None - provenance: Annotated[ - provenance_1.Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/assets/vast_asset.py b/src/adcp/types/generated_poc/core/assets/vast_asset.py deleted file mode 100644 index 4f9ea6726..000000000 --- a/src/adcp/types/generated_poc/core/assets/vast_asset.py +++ /dev/null @@ -1,355 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/assets/vast_asset.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Any, Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, ConfigDict, Field, RootModel, StrictBool - -from ...enums import vast_tracking_event -from ...enums import vast_version as vast_version_1 -from .. import macro_bearing_url -from .. import provenance as provenance_1 -from . import asset_union - - -class Field1(StrEnum): - url = 'url' - markup = 'markup' - content = 'content' - ad_request_url = 'ad_request_url' - clickthrough_url = 'clickthrough_url' - - -class Location(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - field: Annotated[Literal['url'], Field(description='Asset field containing this occurrence.')] = 'url' - occurrence: Annotated[ - SchemaInt, - Field( - description='Zero-based occurrence of this exact token within the named field.', ge=0 - ), - ] - context: asset_union.MacroValueContext - - -class UnavailableBehavior(StrEnum): - preserve = 'preserve' - omit_parameter = 'omit_parameter' - dialect_sentinel = 'dialect_sentinel' - reject = 'reject' - - -class Location26(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - field: Annotated[ - Literal['content'], Field(description='Asset field containing this occurrence.') - ] = 'content' - occurrence: Annotated[ - SchemaInt, - Field( - description='Zero-based occurrence of this exact token within the named field.', ge=0 - ), - ] - context: asset_union.MacroValueContext - - -class MacroDeclaration(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - declaration_id: Annotated[ - str, - Field( - description='Identifier unique within the enclosing asset, used to correlate validation results.', - min_length=1, - pattern='^[A-Za-z0-9_-]+$', - ), - ] - token: Annotated[ - str, - Field( - description='Exact byte-preserved token expression at this occurrence, including delimiters.', - min_length=1, - ), - ] - dialect: asset_union.MacroDialect - dialect_namespace: Annotated[ - AnyUrl | None, - Field( - description='Authority-controlled registry or mapping URI. Required for IAB VAST, IAB DAAST, and vendor dialects.' - ), - ] = None - dialect_revision: Annotated[ - str | None, - Field( - description='Immutable registry/mapping revision, release, commit, or digest. Required for IAB VAST, IAB DAAST, and vendor dialects.', - min_length=1, - ), - ] = None - dialect_semantic: Annotated[ - str, - Field( - description='Exact semantic identifier in the cited dialect, such as `CACHEBUSTING`, `PLAYERSTATE`, or a documented vendor name. This is not inferred from token spelling.', - min_length=1, - ), - ] - mapping_status: asset_union.MacroMappingStatus - universal_semantic: Annotated[ - asset_union.UniversalMacro | None, - Field( - description='Verified AdCP universal meaning. Present only when mapping_status is `verified_universal`.' - ), - ] = None - operation: asset_union.MacroProcessingOperation - performed_by: Annotated[ - asset_union.MacroResolver | None, - Field( - description='Actor authorized to perform the declared translation or value resolution. Omitted for preservation.' - ), - ] = None - translation_target: Annotated[ - asset_union.MacroTranslationTarget | None, - Field( - description='Required only for `translate_to_native`. The output token contract that replaces this declaration after translation.' - ), - ] = None - location: Location - encoding: Annotated[ - asset_union.MacroEncoding, - Field( - description='Encoding for the concrete value. Translation/preservation uses `none` at depth zero.' - ), - ] - required: Annotated[ - StrictBool, - Field( - description='Whether terminal delivery must fail if the declared operation cannot complete.' - ), - ] - unavailable_behavior: Annotated[ - UnavailableBehavior, - Field( - description='Action when the responsible actor has no value. Omission is legal only for a complete URL query-value occurrence; dialect sentinels require a cited dialect rule.' - ), - ] - - -class VastAsset3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: Annotated[ - vast_version_1.VastVersion | None, - Field( - description='Exact VAST version declared by the supplied URL response or inline document. Required by the 3.2 canonical `video_vast` and `audio_vast` manifest paths; optional only on the deprecated named-format compatibility path. Receivers MUST NOT relabel or synthesize a newer version merely because the destination accepts it.' - ), - ] = None - macro_declarations: Annotated[ - list[MacroDeclaration] | None, - Field( - description='One declaration per exact occurrence in a field carried by this asset. A URL-delivered asset can declare only occurrences in its locator `url`; tokens discovered later in a fetched VAST response require document validation evidence or an inline/snapshotted asset and MUST NOT be guessed from the locator. IAB tokens cite a registry namespace and revision rather than copying the live registry into AdCP.', - min_length=1, - ), - ] = None - vpaid_enabled: Annotated[ - StrictBool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - SchemaInt | None, - Field(description='Expected media duration in milliseconds (if known)', ge=0), - ] = None - tracking_events: Annotated[ - list[vast_tracking_event.VastTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - provenance_1.Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance' - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - macro_bearing_url.MacroBearingUrl, - Field( - description='URL endpoint returning VAST XML. Macro delimiters remain byte-preserved; declarations distinguish occurrences in this locator URL from occurrences in inline or fetched VAST content.' - ), - ] - - -class MacroDeclaration20(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - declaration_id: Annotated[ - str, - Field( - description='Identifier unique within the enclosing asset, used to correlate validation results.', - min_length=1, - pattern='^[A-Za-z0-9_-]+$', - ), - ] - token: Annotated[ - str, - Field( - description='Exact byte-preserved token expression at this occurrence, including delimiters.', - min_length=1, - ), - ] - dialect: asset_union.MacroDialect - dialect_namespace: Annotated[ - AnyUrl | None, - Field( - description='Authority-controlled registry or mapping URI. Required for IAB VAST, IAB DAAST, and vendor dialects.' - ), - ] = None - dialect_revision: Annotated[ - str | None, - Field( - description='Immutable registry/mapping revision, release, commit, or digest. Required for IAB VAST, IAB DAAST, and vendor dialects.', - min_length=1, - ), - ] = None - dialect_semantic: Annotated[ - str, - Field( - description='Exact semantic identifier in the cited dialect, such as `CACHEBUSTING`, `PLAYERSTATE`, or a documented vendor name. This is not inferred from token spelling.', - min_length=1, - ), - ] - mapping_status: asset_union.MacroMappingStatus - universal_semantic: Annotated[ - asset_union.UniversalMacro | None, - Field( - description='Verified AdCP universal meaning. Present only when mapping_status is `verified_universal`.' - ), - ] = None - operation: asset_union.MacroProcessingOperation - performed_by: Annotated[ - asset_union.MacroResolver | None, - Field( - description='Actor authorized to perform the declared translation or value resolution. Omitted for preservation.' - ), - ] = None - translation_target: Annotated[ - asset_union.MacroTranslationTarget | None, - Field( - description='Required only for `translate_to_native`. The output token contract that replaces this declaration after translation.' - ), - ] = None - location: Location26 - encoding: Annotated[ - asset_union.MacroEncoding, - Field( - description='Encoding for the concrete value. Translation/preservation uses `none` at depth zero.' - ), - ] - required: Annotated[ - StrictBool, - Field( - description='Whether terminal delivery must fail if the declared operation cannot complete.' - ), - ] - unavailable_behavior: Annotated[ - UnavailableBehavior, - Field( - description='Action when the responsible actor has no value. Omission is legal only for a complete URL query-value occurrence; dialect sentinels require a cited dialect rule.' - ), - ] - - -class VastAsset4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: Annotated[ - vast_version_1.VastVersion | None, - Field( - description='Exact VAST version declared by the supplied URL response or inline document. Required by the 3.2 canonical `video_vast` and `audio_vast` manifest paths; optional only on the deprecated named-format compatibility path. Receivers MUST NOT relabel or synthesize a newer version merely because the destination accepts it.' - ), - ] = None - macro_declarations: Annotated[ - list[MacroDeclaration20] | None, - Field( - description='One declaration per exact occurrence in a field carried by this asset. A URL-delivered asset can declare only occurrences in its locator `url`; tokens discovered later in a fetched VAST response require document validation evidence or an inline/snapshotted asset and MUST NOT be guessed from the locator. IAB tokens cite a registry namespace and revision rather than copying the live registry into AdCP.', - min_length=1, - ), - ] = None - vpaid_enabled: Annotated[ - StrictBool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - SchemaInt | None, - Field(description='Expected media duration in milliseconds (if known)', ge=0), - ] = None - tracking_events: Annotated[ - list[vast_tracking_event.VastTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - provenance_1.Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance' - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class VastAsset(RootModel[VastAsset3 | VastAsset4]): - root: Annotated[ - VastAsset3 | VastAsset4, - Field( - description='VAST (Video Ad Serving Template) tag for third-party video or audio ad serving. Unlike a hosted media asset, a VAST tag carries no single `width`/`height`: a response can return multiple renditions and the player selects one at serve time. Standardized VAST audio support begins at 4.1 and uses MediaFile width and height values of 0; older audio-in-VAST versions are seller-declared legacy interoperability. Dimensional, duration, MIME-type, and codec constraints live on the format/requirements layer, not on this asset.', - discriminator='delivery_type', - title='VAST Asset', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) diff --git a/src/adcp/types/generated_poc/core/assets/vast_tracker_asset.py b/src/adcp/types/generated_poc/core/assets/vast_tracker_asset.py deleted file mode 100644 index d6395ac3a..000000000 --- a/src/adcp/types/generated_poc/core/assets/vast_tracker_asset.py +++ /dev/null @@ -1,98 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/assets/vast_tracker_asset.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field - -from ...enums import vast_tracking_event -from .. import macro_bearing_url -from .. import provenance as provenance_1 -from ..macro_declaration import MacroDeclaration as MacroDeclaration_1 -from . import asset_union - - -class Field1(StrEnum): - url = 'url' - markup = 'markup' - content = 'content' - ad_request_url = 'ad_request_url' - clickthrough_url = 'clickthrough_url' - - -class Location(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - field: Annotated[Literal['url'], Field(description='Asset field containing this occurrence.')] = 'url' - occurrence: Annotated[ - SchemaInt, - Field( - description='Zero-based occurrence of this exact token within the named field.', ge=0 - ), - ] - context: asset_union.MacroValueContext - - -class Target(StrEnum): - linear = 'linear' - non_linear = 'non_linear' - companion = 'companion' - - -class MacroDeclaration(MacroDeclaration_1): - location: Location | None = None - - -class VastTrackerAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - vast_tracking_event.VastTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - macro_bearing_url.MacroBearingUrl, - Field( - description='Tracker URL fired for the VAST event. Attached declarations identify each macro occurrence, registry revision, processing actor, and exact encoding profile.' - ), - ] - macro_declarations: Annotated[ - list[MacroDeclaration] | None, - Field( - description='Exact tokens in `url` and their resolver/encoding contracts.', min_length=1 - ), - ] = None - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise for compatibility with existing 3.x manifests. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. Defaults to `linear`. Existing 3.x assets remain structurally permissive; a tracker execution contract applies the standards-valid event/target matrix when matching a creative to a product.' - ), - ] = Target.linear - provenance: Annotated[ - provenance_1.Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/assets/video_asset.py b/src/adcp/types/generated_poc/core/assets/video_asset.py deleted file mode 100644 index 0612ddb2d..000000000 --- a/src/adcp/types/generated_poc/core/assets/video_asset.py +++ /dev/null @@ -1,157 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/assets/video_asset.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from enum import IntEnum -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, ConfigDict, Field, StrictBool, StrictFloat - -from ...enums import audio_channel_layout -from ...enums import frame_rate_type as frame_rate_type_1 -from ...enums import gop_type as gop_type_1 -from ...enums import moov_atom_position as moov_atom_position_1 -from ...enums import scan_type as scan_type_1 -from .. import provenance as provenance_1 - - -class ColorSpace(StrEnum): - rec709 = 'rec709' - rec2020 = 'rec2020' - rec2100 = 'rec2100' - srgb = 'srgb' - dci_p3 = 'dci_p3' - - -class HdrFormat(StrEnum): - sdr = 'sdr' - hdr10 = 'hdr10' - hdr10_plus = 'hdr10_plus' - hlg = 'hlg' - dolby_vision = 'dolby_vision' - - -class ChromaSubsampling(StrEnum): - field_4_2_0 = '4:2:0' - field_4_2_2 = '4:2:2' - field_4_4_4 = '4:4:4' - - -class VideoBitDepth(IntEnum): - integer_8 = 8 - integer_10 = 10 - integer_12 = 12 - - -class AudioBitDepth(IntEnum): - integer_16 = 16 - integer_24 = 24 - integer_32 = 32 - - -class VideoAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[ - SchemaInt, - Field( - description="Width in pixels — the video file's intrinsic native width. Required: a hosted file always has concrete dimensions. (Tag-delivered video carries no width; see the `vast` asset.)", - ge=1, - ), - ] - height: Annotated[ - SchemaInt, - Field( - description="Height in pixels — the video file's intrinsic native height. Required: a hosted file always has concrete dimensions. (Tag-delivered video carries no height; see the `vast` asset.)", - ge=1, - ), - ] - duration_ms: Annotated[ - SchemaInt | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[SchemaInt | None, Field(description='File size in bytes', ge=1)] = ( - None - ) - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - SchemaInt | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: frame_rate_type_1.FrameRateType | None = None - scan_type: scan_type_1.ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - StrictFloat | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: gop_type_1.GopType | None = None - moov_atom_position: moov_atom_position_1.MoovAtomPosition | None = None - has_audio: Annotated[ - StrictBool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - SchemaInt | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: Annotated[ - audio_channel_layout.AudioChannelLayout | None, - Field(description='Audio channel configuration'), - ] = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - SchemaInt | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - StrictFloat | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[ - StrictFloat | None, Field(description='True peak level in dBFS') - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - provenance_1.Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/assets/webhook_asset.py b/src/adcp/types/generated_poc/core/assets/webhook_asset.py deleted file mode 100644 index a1dc08d0c..000000000 --- a/src/adcp/types/generated_poc/core/assets/webhook_asset.py +++ /dev/null @@ -1,66 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/assets/webhook_asset.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, ConfigDict, Field - -from ...enums import http_method, universal_macro, webhook_response_type, webhook_security_method -from .. import provenance as provenance_1 - - -class Security(AdCPBaseModel): - method: Annotated[ - webhook_security_method.WebhookSecurityMethod, Field(description='Authentication method') - ] - hmac_header: Annotated[ - str | None, Field(description="Header name for HMAC signature (e.g., 'X-Signature')") - ] = None - api_key_header: Annotated[ - str | None, Field(description="Header name for API key (e.g., 'X-API-Key')") - ] = None - - -class WebhookAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: Annotated[http_method.HttpMethod | None, Field(description='HTTP method')] = ( - http_method.HttpMethod.POST - ) - timeout_ms: Annotated[ - SchemaInt | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[universal_macro.UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[universal_macro.UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: Annotated[ - webhook_response_type.WebhookResponseType, - Field(description='Expected content type of webhook response'), - ] - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - provenance_1.Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/assets/zip_asset.py b/src/adcp/types/generated_poc/core/assets/zip_asset.py deleted file mode 100644 index c6f97d094..000000000 --- a/src/adcp/types/generated_poc/core/assets/zip_asset.py +++ /dev/null @@ -1,87 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/assets/zip_asset.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, ConfigDict, Field, StrictBool - -from .. import provenance as provenance_1 - - -class Accessibility(AdCPBaseModel): - alt_text: Annotated[ - str | None, Field(description='Text alternative describing the creative content') - ] = None - keyboard_navigable: Annotated[ - StrictBool | None, - Field(description='Whether the creative can be fully operated via keyboard'), - ] = None - motion_control: Annotated[ - StrictBool | None, - Field( - description='Whether the creative respects prefers-reduced-motion or provides pause/stop controls' - ), - ] = None - screen_reader_tested: Annotated[ - StrictBool | None, - Field(description='Whether the creative has been tested with screen readers'), - ] = None - - -class ZipAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - SchemaInt | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - provenance_1.Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/async_response_data.py b/src/adcp/types/generated_poc/core/async_response_data.py deleted file mode 100644 index e76ca6936..000000000 --- a/src/adcp/types/generated_poc/core/async_response_data.py +++ /dev/null @@ -1,106 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_data.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import Field - -from ..creative import ( - get_creative_features_async_response_submitted, - get_creative_features_response, - preview_creative_response, - sync_creatives_async_response_input_required, - sync_creatives_async_response_submitted, - sync_creatives_async_response_working, - sync_creatives_response, -) -from ..media_buy import ( - build_creative_async_response_input_required, - build_creative_async_response_submitted, - build_creative_async_response_working, - build_creative_response, - control_media_buy_response, - create_media_buy_async_response_input_required, - create_media_buy_async_response_submitted, - create_media_buy_async_response_working, - create_media_buy_response, - decline_proposals_response, - get_products_async_response_input_required, - get_products_async_response_submitted, - get_products_async_response_working, - get_products_rejected, - get_products_response, - media_buy_commitment_response, - media_buy_delivery_webhook_result, - refine_proposals_async_response_submitted, - refine_proposals_response, - request_proposals_async_response_submitted, - request_proposals_response, - sync_catalogs_async_response_input_required, - sync_catalogs_async_response_submitted, - sync_catalogs_async_response_working, - sync_catalogs_response, - update_media_buy_async_response_input_required, - update_media_buy_async_response_submitted, - update_media_buy_async_response_working, - update_media_buy_response, -) -from ..signals import ( - get_signals_async_response_submitted, - get_signals_async_response_working, - get_signals_response, -) -from . import compact_task_input_required, compact_task_submitted, compact_task_working - - -AdcpAsyncResponseData = Annotated[ - get_products_response.GetProductsResponse - | get_products_rejected.GetProductsRejected - | get_products_async_response_working.GetProductsWorking - | get_products_async_response_input_required.GetProductsInputRequired - | get_products_async_response_submitted.GetProductsSubmitted - | request_proposals_response.RequestProposalsResponse - | request_proposals_async_response_submitted.RequestProposalsSubmitted - | refine_proposals_response.RefineProposalsResponse - | refine_proposals_async_response_submitted.RefineProposalsSubmitted - | decline_proposals_response.DeclineProposalsResponse - | media_buy_commitment_response.MediaBuyCommitmentResponse - | control_media_buy_response.ControlMediaBuyResponse - | compact_task_submitted.CompactTaskSubmitted - | compact_task_working.CompactTaskWorking - | compact_task_input_required.CompactTaskInputRequired - | get_signals_response.GetSignalsResponse - | get_signals_async_response_working.GetSignalsWorking - | get_signals_async_response_submitted.GetSignalsSubmitted - | create_media_buy_response.CreateMediaBuyResponse - | create_media_buy_async_response_working.CreateMediaBuyWorking - | create_media_buy_async_response_input_required.CreateMediaBuyInputRequired - | create_media_buy_async_response_submitted.CreateMediaBuySubmitted - | update_media_buy_response.UpdateMediaBuyResponse - | update_media_buy_async_response_working.UpdateMediaBuyWorking - | update_media_buy_async_response_input_required.UpdateMediaBuyInputRequired - | update_media_buy_async_response_submitted.UpdateMediaBuySubmitted - | media_buy_delivery_webhook_result.MediaBuyDeliveryWebhookResult - | build_creative_response.BuildCreativeResponse - | preview_creative_response.PreviewCreativeResponse - | build_creative_async_response_working.BuildCreativeWorking - | build_creative_async_response_input_required.BuildCreativeInputRequired - | build_creative_async_response_submitted.BuildCreativeSubmitted - | get_creative_features_response.GetCreativeFeaturesResponse - | get_creative_features_async_response_submitted.GetCreativeFeaturesSubmitted - | sync_creatives_response.SyncCreativesResponse - | sync_creatives_async_response_working.SyncCreativesWorking - | sync_creatives_async_response_input_required.SyncCreativesInputRequired - | sync_creatives_async_response_submitted.SyncCreativesSubmitted - | sync_catalogs_response.SyncCatalogsResponse - | sync_catalogs_async_response_working.SyncCatalogsWorking - | sync_catalogs_async_response_input_required.SyncCatalogsInputRequired - | sync_catalogs_async_response_submitted.SyncCatalogsSubmitted, - Field( - description="Validation union of supported async webhook payloads. For completed/failed statuses, use the main task response schema. For rejected get_products outcomes and working/input-required/submitted statuses, use the status-specific schemas. Because this shared webhook union is not tagged with the originating task type, callers MUST also validate a terminal result against that task or event's specific schema. Polling responses use a generic result selected through manifest.task_result_resolution instead of embedding this union.", - title='AdCP Async Response Data', - ), -] diff --git a/src/adcp/types/generated_poc/core/async_response_refs/__init__.py b/src/adcp/types/generated_poc/core/async_response_refs/__init__.py deleted file mode 100644 index 4a4523033..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# generated by datamodel-codegen: -# filename: .schema_temp -# timestamp: 2026-05-29T23:56:01+00:00 diff --git a/src/adcp/types/generated_poc/core/async_response_refs/creative/__init__.py b/src/adcp/types/generated_poc/core/async_response_refs/creative/__init__.py deleted file mode 100644 index 4a4523033..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/creative/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# generated by datamodel-codegen: -# filename: .schema_temp -# timestamp: 2026-05-29T23:56:01+00:00 diff --git a/src/adcp/types/generated_poc/core/async_response_refs/creative/get_creative_features_async_response_submitted.py b/src/adcp/types/generated_poc/core/async_response_refs/creative/get_creative_features_async_response_submitted.py deleted file mode 100644 index 6b0bd0095..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/creative/get_creative_features_async_response_submitted.py +++ /dev/null @@ -1,48 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/creative/get_creative_features_async_response_submitted.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ... import context as context_1 -from ... import ext as ext_1 - - -class GetCreativeFeaturesSubmitted(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Literal['submitted'], - Field( - description='Task-level status literal that discriminates this acknowledgement from terminal success and error responses.' - ), - ] = 'submitted' - task_id: Annotated[ - str, - Field( - description='AdCP task handle used to poll get_task_status or correlate terminal webhook delivery. Distinct from evaluation_id.', - min_length=1, - ), - ] - evaluation_id: Annotated[ - str | None, - Field( - description='Provider-generated identity allocated when the evaluation is accepted. Optional in AdCP 3.x and required in AdCP 4.0. Providers SHOULD emit it in 3.x; when present, exact replays and the terminal result MUST carry this same value. Distinct from task_id and idempotency_key.', - min_length=1, - ), - ] = None - message: Annotated[ - str | None, - Field( - description='Optional human-readable explanation of why the evaluation is submitted. Plain text only; callers treat it as untrusted provider input.', - max_length=2000, - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/async_response_refs/creative/sync_creatives_async_response_input_required.py b/src/adcp/types/generated_poc/core/async_response_refs/creative/sync_creatives_async_response_input_required.py deleted file mode 100644 index abe340e98..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/creative/sync_creatives_async_response_input_required.py +++ /dev/null @@ -1,31 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/creative/sync_creatives_async_response_input_required.json -# timestamp: 2026-05-29T23:56:01+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ... import context as context_1 -from ... import ext as ext_1 - - -class Reason(StrEnum): - APPROVAL_REQUIRED = 'APPROVAL_REQUIRED' - ASSET_CONFIRMATION = 'ASSET_CONFIRMATION' - FORMAT_CLARIFICATION = 'FORMAT_CLARIFICATION' - - -class SyncCreativesInputRequired(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - reason: Annotated[ - Reason | None, Field(description='Reason code indicating why buyer input is needed') - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/async_response_refs/creative/sync_creatives_async_response_submitted.py b/src/adcp/types/generated_poc/core/async_response_refs/creative/sync_creatives_async_response_submitted.py deleted file mode 100644 index 9c87f5d91..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/creative/sync_creatives_async_response_submitted.py +++ /dev/null @@ -1,47 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/creative/sync_creatives_async_response_submitted.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ... import context as context_1 -from ... import error -from ... import ext as ext_1 - - -class SyncCreativesSubmitted(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Literal['submitted'], - Field( - description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, whose creatives array carries per-item approval state via CreativeStatus. See task-status.json for the full task-status enum.' - ), - ] = 'submitted' - task_id: Annotated[ - str, - Field( - description='Task handle the buyer uses with get_task_status (or the legacy AdCP tasks/get alias), and that the seller references on push-notification callbacks. The creatives array is issued on the completion artifact, not here. This AdCP application-layer handle remains the snake_case task_id in every transport payload and is distinct from any transport-native A2A Task id.' - ), - ] - message: Annotated[ - str | None, - Field( - description="Optional human-readable explanation of why the task is submitted — e.g., 'Batch ingestion queued; typical turnaround 15-30 minutes.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", - max_length=2000, - ), - ] = None - errors: Annotated[ - list[error.Error] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/async_response_refs/creative/sync_creatives_async_response_working.py b/src/adcp/types/generated_poc/core/async_response_refs/creative/sync_creatives_async_response_working.py deleted file mode 100644 index e306bbcdf..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/creative/sync_creatives_async_response_working.py +++ /dev/null @@ -1,37 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/creative/sync_creatives_async_response_working.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictFloat - -from ... import context as context_1 -from ... import ext as ext_1 - - -class SyncCreativesWorking(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - percentage: Annotated[ - StrictFloat | None, Field(description='Completion percentage (0-100)', ge=0.0, le=100.0) - ] = None - current_step: Annotated[ - str | None, Field(description='Current step or phase of the operation') - ] = None - total_steps: Annotated[ - SchemaInt | None, Field(description='Total number of steps in the operation', ge=1) - ] = None - step_number: Annotated[SchemaInt | None, Field(description='Current step number', ge=1)] = None - creatives_processed: Annotated[ - SchemaInt | None, Field(description='Number of creatives processed so far', ge=0) - ] = None - creatives_total: Annotated[ - SchemaInt | None, Field(description='Total number of creatives to process', ge=0) - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/__init__.py b/src/adcp/types/generated_poc/core/async_response_refs/media_buy/__init__.py deleted file mode 100644 index 4a4523033..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# generated by datamodel-codegen: -# filename: .schema_temp -# timestamp: 2026-05-29T23:56:01+00:00 diff --git a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/accept_proposal_async_response_input_required.py b/src/adcp/types/generated_poc/core/async_response_refs/media_buy/accept_proposal_async_response_input_required.py deleted file mode 100644 index a06ebe861..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/accept_proposal_async_response_input_required.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/media_buy/accept_proposal_async_response_input_required.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from ...compact_task_input_required import CompactTaskInputRequired - - -class AcceptProposalInputRequired(CompactTaskInputRequired): - pass diff --git a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/accept_proposal_async_response_submitted.py b/src/adcp/types/generated_poc/core/async_response_refs/media_buy/accept_proposal_async_response_submitted.py deleted file mode 100644 index 6d66fd5c6..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/accept_proposal_async_response_submitted.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/media_buy/accept_proposal_async_response_submitted.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from ...compact_task_submitted import CompactTaskSubmitted - - -class AcceptProposalSubmitted(CompactTaskSubmitted): - pass diff --git a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/accept_proposal_async_response_working.py b/src/adcp/types/generated_poc/core/async_response_refs/media_buy/accept_proposal_async_response_working.py deleted file mode 100644 index 5aa6c23f4..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/accept_proposal_async_response_working.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/media_buy/accept_proposal_async_response_working.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from ...compact_task_working import CompactTaskWorking - - -class AcceptProposalWorking(CompactTaskWorking): - pass diff --git a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/build_creative_async_response_input_required.py b/src/adcp/types/generated_poc/core/async_response_refs/media_buy/build_creative_async_response_input_required.py deleted file mode 100644 index a322fbc3e..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/build_creative_async_response_input_required.py +++ /dev/null @@ -1,38 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/media_buy/build_creative_async_response_input_required.json -# timestamp: 2026-05-29T23:56:01+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ... import context as context_1 -from ... import error -from ... import ext as ext_1 - - -class Reason(StrEnum): - APPROVAL_REQUIRED = 'APPROVAL_REQUIRED' - CREATIVE_DIRECTION_NEEDED = 'CREATIVE_DIRECTION_NEEDED' - ASSET_SELECTION_NEEDED = 'ASSET_SELECTION_NEEDED' - - -class BuildCreativeInputRequired(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - reason: Annotated[ - Reason | None, Field(description='Reason code indicating why input is needed') - ] = None - errors: Annotated[ - list[error.Error] | None, - Field( - description='Optional validation errors or warnings explaining why input is required.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/build_creative_async_response_submitted.py b/src/adcp/types/generated_poc/core/async_response_refs/media_buy/build_creative_async_response_submitted.py deleted file mode 100644 index 2fb45a85a..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/build_creative_async_response_submitted.py +++ /dev/null @@ -1,47 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/media_buy/build_creative_async_response_submitted.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ... import context as context_1 -from ... import error -from ... import ext as ext_1 - - -class BuildCreativeSubmitted(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Literal['submitted'], - Field( - description='Task-level status literal. Discriminates this async envelope from the synchronous success shapes, whose creative_manifest or creative_manifests are issued in-line. See task-status.json for the full task-status enum.' - ), - ] = 'submitted' - task_id: Annotated[ - str, - Field( - description='Task handle the buyer uses with get_task_status (or the legacy AdCP tasks/get alias), and that the seller references on push-notification callbacks. This AdCP application-layer handle remains the snake_case task_id in every transport payload and is distinct from any transport-native A2A Task id.' - ), - ] - message: Annotated[ - str | None, - Field( - description="Optional human-readable explanation of why the task is submitted — e.g., 'Generative build queued; typical turnaround 3–5 minutes.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", - max_length=2000, - ), - ] = None - errors: Annotated[ - list[error.Error] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/build_creative_async_response_working.py b/src/adcp/types/generated_poc/core/async_response_refs/media_buy/build_creative_async_response_working.py deleted file mode 100644 index 653e18b6b..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/build_creative_async_response_working.py +++ /dev/null @@ -1,34 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/media_buy/build_creative_async_response_working.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictFloat - -from ... import context as context_1 -from ... import ext as ext_1 - - -class BuildCreativeWorking(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - percentage: Annotated[ - StrictFloat | None, Field(description='Completion percentage (0-100)', ge=0.0, le=100.0) - ] = None - current_step: Annotated[ - str | None, - Field( - description="Current step or phase of the operation (e.g., 'generating_assets', 'resolving_macros', 'rendering_preview')" - ), - ] = None - total_steps: Annotated[ - SchemaInt | None, Field(description='Total number of steps in the operation', ge=1) - ] = None - step_number: Annotated[SchemaInt | None, Field(description='Current step number', ge=1)] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/buy_products_async_response_input_required.py b/src/adcp/types/generated_poc/core/async_response_refs/media_buy/buy_products_async_response_input_required.py deleted file mode 100644 index 29d5786e3..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/buy_products_async_response_input_required.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/media_buy/buy_products_async_response_input_required.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from ...compact_task_input_required import CompactTaskInputRequired - - -class BuyProductsInputRequired(CompactTaskInputRequired): - pass diff --git a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/buy_products_async_response_submitted.py b/src/adcp/types/generated_poc/core/async_response_refs/media_buy/buy_products_async_response_submitted.py deleted file mode 100644 index 440962ec4..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/buy_products_async_response_submitted.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/media_buy/buy_products_async_response_submitted.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from ...compact_task_submitted import CompactTaskSubmitted - - -class BuyProductsSubmitted(CompactTaskSubmitted): - pass diff --git a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/buy_products_async_response_working.py b/src/adcp/types/generated_poc/core/async_response_refs/media_buy/buy_products_async_response_working.py deleted file mode 100644 index 3828a18e8..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/buy_products_async_response_working.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/media_buy/buy_products_async_response_working.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from ...compact_task_working import CompactTaskWorking - - -class BuyProductsWorking(CompactTaskWorking): - pass diff --git a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/control_media_buy_async_response_input_required.py b/src/adcp/types/generated_poc/core/async_response_refs/media_buy/control_media_buy_async_response_input_required.py deleted file mode 100644 index b02ecc6d6..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/control_media_buy_async_response_input_required.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/media_buy/control_media_buy_async_response_input_required.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from ...compact_task_input_required import CompactTaskInputRequired - - -class ControlMediaBuyInputRequired(CompactTaskInputRequired): - pass diff --git a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/control_media_buy_async_response_submitted.py b/src/adcp/types/generated_poc/core/async_response_refs/media_buy/control_media_buy_async_response_submitted.py deleted file mode 100644 index 70f02ccc6..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/control_media_buy_async_response_submitted.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/media_buy/control_media_buy_async_response_submitted.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from ...compact_task_submitted import CompactTaskSubmitted - - -class ControlMediaBuySubmitted(CompactTaskSubmitted): - pass diff --git a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/control_media_buy_async_response_working.py b/src/adcp/types/generated_poc/core/async_response_refs/media_buy/control_media_buy_async_response_working.py deleted file mode 100644 index 11a44fec5..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/control_media_buy_async_response_working.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/media_buy/control_media_buy_async_response_working.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from ...compact_task_working import CompactTaskWorking - - -class ControlMediaBuyWorking(CompactTaskWorking): - pass diff --git a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/create_media_buy_async_response_input_required.py b/src/adcp/types/generated_poc/core/async_response_refs/media_buy/create_media_buy_async_response_input_required.py deleted file mode 100644 index bb7da427a..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/create_media_buy_async_response_input_required.py +++ /dev/null @@ -1,37 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/media_buy/create_media_buy_async_response_input_required.json -# timestamp: 2026-05-29T23:56:01+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ... import context as context_1 -from ... import error -from ... import ext as ext_1 - - -class Reason(StrEnum): - APPROVAL_REQUIRED = 'APPROVAL_REQUIRED' - BUDGET_EXCEEDS_LIMIT = 'BUDGET_EXCEEDS_LIMIT' - - -class CreateMediaBuyInputRequired(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - reason: Annotated[ - Reason | None, Field(description='Reason code indicating why input is needed') - ] = None - errors: Annotated[ - list[error.Error] | None, - Field( - description='Optional validation errors or warnings for debugging purposes. Helps explain why input is required.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/create_media_buy_async_response_submitted.py b/src/adcp/types/generated_poc/core/async_response_refs/media_buy/create_media_buy_async_response_submitted.py deleted file mode 100644 index 19fa10e0a..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/create_media_buy_async_response_submitted.py +++ /dev/null @@ -1,47 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/media_buy/create_media_buy_async_response_submitted.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ... import context as context_1 -from ... import error -from ... import ext as ext_1 - - -class CreateMediaBuySubmitted(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Literal['submitted'], - Field( - description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, which carries media-buy lifecycle state in `media_buy_status`. See task-status.json for the full task-status enum.' - ), - ] = 'submitted' - task_id: Annotated[ - str, - Field( - description='Task handle the buyer uses with get_task_status (or the legacy AdCP tasks/get alias), and that the seller references on push-notification callbacks. The media_buy_id is issued on the completion artifact, not here. This AdCP application-layer handle remains the snake_case task_id in every transport payload and is distinct from any transport-native A2A Task id.' - ), - ] - message: Annotated[ - str | None, - Field( - description="Optional human-readable explanation of why the task is submitted — e.g., 'Awaiting IO signature from sales team; typical turnaround 2–4 hours.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", - max_length=2000, - ), - ] = None - errors: Annotated[ - list[error.Error] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/create_media_buy_async_response_working.py b/src/adcp/types/generated_poc/core/async_response_refs/media_buy/create_media_buy_async_response_working.py deleted file mode 100644 index b09e8be39..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/create_media_buy_async_response_working.py +++ /dev/null @@ -1,31 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/media_buy/create_media_buy_async_response_working.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictFloat - -from ... import context as context_1 -from ... import ext as ext_1 - - -class CreateMediaBuyWorking(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - percentage: Annotated[ - StrictFloat | None, Field(description='Completion percentage (0-100)', ge=0.0, le=100.0) - ] = None - current_step: Annotated[ - str | None, Field(description='Current step or phase of the operation') - ] = None - total_steps: Annotated[ - SchemaInt | None, Field(description='Total number of steps in the operation', ge=1) - ] = None - step_number: Annotated[SchemaInt | None, Field(description='Current step number', ge=1)] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/decline_proposals_async_response_input_required.py b/src/adcp/types/generated_poc/core/async_response_refs/media_buy/decline_proposals_async_response_input_required.py deleted file mode 100644 index 3452c3a47..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/decline_proposals_async_response_input_required.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/media_buy/decline_proposals_async_response_input_required.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from ...compact_task_input_required import CompactTaskInputRequired - - -class DeclineProposalsInputRequired(CompactTaskInputRequired): - pass diff --git a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/decline_proposals_async_response_submitted.py b/src/adcp/types/generated_poc/core/async_response_refs/media_buy/decline_proposals_async_response_submitted.py deleted file mode 100644 index 28872c305..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/decline_proposals_async_response_submitted.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/media_buy/decline_proposals_async_response_submitted.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from ...compact_task_submitted import CompactTaskSubmitted - - -class DeclineProposalsSubmitted(CompactTaskSubmitted): - pass diff --git a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/decline_proposals_async_response_working.py b/src/adcp/types/generated_poc/core/async_response_refs/media_buy/decline_proposals_async_response_working.py deleted file mode 100644 index 0e902ab30..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/decline_proposals_async_response_working.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/media_buy/decline_proposals_async_response_working.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from ...compact_task_working import CompactTaskWorking - - -class DeclineProposalsWorking(CompactTaskWorking): - pass diff --git a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/get_products_async_response_input_required.py b/src/adcp/types/generated_poc/core/async_response_refs/media_buy/get_products_async_response_input_required.py deleted file mode 100644 index 33e346407..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/get_products_async_response_input_required.py +++ /dev/null @@ -1,38 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/media_buy/get_products_async_response_input_required.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ... import context as context_1 -from ... import ext as ext_1 -from ... import product - - -class Reason(StrEnum): - CLARIFICATION_NEEDED = 'CLARIFICATION_NEEDED' - BUDGET_REQUIRED = 'BUDGET_REQUIRED' - - -class GetProductsInputRequired(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - reason: Annotated[ - Reason | None, Field(description='Reason code indicating why input is needed') - ] = None - partial_results: Annotated[ - list[product.Product] | None, - Field(description='Partial product results that may help inform the clarification'), - ] = None - suggestions: Annotated[ - list[str] | None, Field(description='Suggested values or options for the required input') - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/get_products_async_response_submitted.py b/src/adcp/types/generated_poc/core/async_response_refs/media_buy/get_products_async_response_submitted.py deleted file mode 100644 index 5e4be1c86..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/get_products_async_response_submitted.py +++ /dev/null @@ -1,50 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/media_buy/get_products_async_response_submitted.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - -from ... import context as context_1 -from ... import error -from ... import ext as ext_1 - - -class GetProductsSubmitted(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Literal['submitted'], - Field( - description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, whose products array is issued in-line. See task-status.json for the full task-status enum.' - ), - ] = 'submitted' - task_id: Annotated[ - str, - Field( - description='Task handle the buyer uses with get_task_status (or the legacy AdCP tasks/get alias), and that the seller references on push-notification callbacks. The products array is issued on the completion artifact, not here. This AdCP application-layer handle remains the snake_case task_id in every transport payload and is distinct from any transport-native A2A Task id.' - ), - ] - message: Annotated[ - str | None, - Field( - description="Optional human-readable explanation of why the task is submitted — e.g., 'Custom curation queued; typical turnaround 10–30 minutes.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", - max_length=2000, - ), - ] = None - estimated_completion: Annotated[ - AwareDatetime | None, Field(description='Estimated completion time for the search') - ] = None - errors: Annotated[ - list[error.Error] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/get_products_async_response_working.py b/src/adcp/types/generated_poc/core/async_response_refs/media_buy/get_products_async_response_working.py deleted file mode 100644 index 49a7e2e4c..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/get_products_async_response_working.py +++ /dev/null @@ -1,37 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/media_buy/get_products_async_response_working.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictFloat - -from ... import context as context_1 -from ... import ext as ext_1 - - -class GetProductsWorking(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - percentage: Annotated[ - StrictFloat | None, - Field(description='Progress percentage of the search operation', ge=0.0, le=100.0), - ] = None - current_step: Annotated[ - str | None, - Field( - description="Current step in the search process (e.g., 'searching_inventory', 'validating_availability')" - ), - ] = None - total_steps: Annotated[ - SchemaInt | None, Field(description='Total number of steps in the search process') - ] = None - step_number: Annotated[ - SchemaInt | None, Field(description='Current step number (1-indexed)') - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/refine_proposals_async_response_input_required.py b/src/adcp/types/generated_poc/core/async_response_refs/media_buy/refine_proposals_async_response_input_required.py deleted file mode 100644 index 8fc8c2c45..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/refine_proposals_async_response_input_required.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/media_buy/refine_proposals_async_response_input_required.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from ...compact_task_input_required import CompactTaskInputRequired - - -class RefineProposalsInputRequired(CompactTaskInputRequired): - pass diff --git a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/refine_proposals_async_response_submitted.py b/src/adcp/types/generated_poc/core/async_response_refs/media_buy/refine_proposals_async_response_submitted.py deleted file mode 100644 index 86358f468..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/refine_proposals_async_response_submitted.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/media_buy/refine_proposals_async_response_submitted.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from ...compact_task_submitted import CompactTaskSubmitted - - -class RefineProposalsSubmitted(CompactTaskSubmitted): - pass diff --git a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/refine_proposals_async_response_working.py b/src/adcp/types/generated_poc/core/async_response_refs/media_buy/refine_proposals_async_response_working.py deleted file mode 100644 index 7efc83e9f..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/refine_proposals_async_response_working.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/media_buy/refine_proposals_async_response_working.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from ...compact_task_working import CompactTaskWorking - - -class RefineProposalsWorking(CompactTaskWorking): - pass diff --git a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/request_proposals_async_response_input_required.py b/src/adcp/types/generated_poc/core/async_response_refs/media_buy/request_proposals_async_response_input_required.py deleted file mode 100644 index b3c37a0d6..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/request_proposals_async_response_input_required.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/media_buy/request_proposals_async_response_input_required.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from ...compact_task_input_required import CompactTaskInputRequired - - -class RequestProposalsInputRequired(CompactTaskInputRequired): - pass diff --git a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/request_proposals_async_response_submitted.py b/src/adcp/types/generated_poc/core/async_response_refs/media_buy/request_proposals_async_response_submitted.py deleted file mode 100644 index f2f7b507b..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/request_proposals_async_response_submitted.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/media_buy/request_proposals_async_response_submitted.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from ...compact_task_submitted import CompactTaskSubmitted - - -class RequestProposalsSubmitted(CompactTaskSubmitted): - pass diff --git a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/request_proposals_async_response_working.py b/src/adcp/types/generated_poc/core/async_response_refs/media_buy/request_proposals_async_response_working.py deleted file mode 100644 index 3b9f603c0..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/request_proposals_async_response_working.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/media_buy/request_proposals_async_response_working.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from ...compact_task_working import CompactTaskWorking - - -class RequestProposalsWorking(CompactTaskWorking): - pass diff --git a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/sync_catalogs_async_response_input_required.py b/src/adcp/types/generated_poc/core/async_response_refs/media_buy/sync_catalogs_async_response_input_required.py deleted file mode 100644 index 332b83101..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/sync_catalogs_async_response_input_required.py +++ /dev/null @@ -1,35 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/media_buy/sync_catalogs_async_response_input_required.json -# timestamp: 2026-05-29T23:56:01+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ... import context as context_1 -from ... import ext as ext_1 - - -class Reason(StrEnum): - APPROVAL_REQUIRED = 'APPROVAL_REQUIRED' - FEED_VALIDATION = 'FEED_VALIDATION' - ITEM_REVIEW = 'ITEM_REVIEW' - FEED_ACCESS = 'FEED_ACCESS' - - -class SyncCatalogsInputRequired(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - reason: Annotated[ - Reason | None, - Field( - description='Reason code indicating why buyer input is needed. APPROVAL_REQUIRED: platform requires explicit approval before activating the catalog. FEED_VALIDATION: feed URL returned unexpected format or schema errors. ITEM_REVIEW: platform flagged items for manual review. FEED_ACCESS: platform cannot access the feed URL (authentication, CORS, etc.).' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/sync_catalogs_async_response_submitted.py b/src/adcp/types/generated_poc/core/async_response_refs/media_buy/sync_catalogs_async_response_submitted.py deleted file mode 100644 index e50407f29..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/sync_catalogs_async_response_submitted.py +++ /dev/null @@ -1,47 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/media_buy/sync_catalogs_async_response_submitted.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ... import context as context_1 -from ... import error -from ... import ext as ext_1 - - -class SyncCatalogsSubmitted(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Literal['submitted'], - Field( - description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, whose catalogs array is issued in-line. See task-status.json for the full task-status enum.' - ), - ] = 'submitted' - task_id: Annotated[ - str, - Field( - description='Task handle the buyer uses with get_task_status (or the legacy AdCP tasks/get alias), and that the seller references on push-notification callbacks. This AdCP application-layer handle remains the snake_case task_id in every transport payload and is distinct from any transport-native A2A Task id.' - ), - ] - message: Annotated[ - str | None, - Field( - description="Optional human-readable explanation of why the task is submitted — e.g., 'Catalog ingestion queued; typical turnaround 5–15 minutes.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", - max_length=2000, - ), - ] = None - errors: Annotated[ - list[error.Error] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/sync_catalogs_async_response_working.py b/src/adcp/types/generated_poc/core/async_response_refs/media_buy/sync_catalogs_async_response_working.py deleted file mode 100644 index ee29491da..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/sync_catalogs_async_response_working.py +++ /dev/null @@ -1,48 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/media_buy/sync_catalogs_async_response_working.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictFloat - -from ... import context as context_1 -from ... import ext as ext_1 - - -class SyncCatalogsWorking(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - percentage: Annotated[ - StrictFloat | None, Field(description='Completion percentage (0-100)', ge=0.0, le=100.0) - ] = None - current_step: Annotated[ - str | None, - Field( - description="Current step or phase of the operation (e.g., 'Fetching product feed', 'Validating items', 'Platform review')" - ), - ] = None - total_steps: Annotated[ - SchemaInt | None, Field(description='Total number of steps in the operation', ge=1) - ] = None - step_number: Annotated[SchemaInt | None, Field(description='Current step number', ge=1)] = None - catalogs_processed: Annotated[ - SchemaInt | None, Field(description='Number of catalogs processed so far', ge=0) - ] = None - catalogs_total: Annotated[ - SchemaInt | None, Field(description='Total number of catalogs to process', ge=0) - ] = None - items_processed: Annotated[ - SchemaInt | None, - Field(description='Total number of catalog items processed across all catalogs', ge=0), - ] = None - items_total: Annotated[ - SchemaInt | None, - Field(description='Total number of catalog items to process across all catalogs', ge=0), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/update_media_buy_async_response_input_required.py b/src/adcp/types/generated_poc/core/async_response_refs/media_buy/update_media_buy_async_response_input_required.py deleted file mode 100644 index 570fd47d6..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/update_media_buy_async_response_input_required.py +++ /dev/null @@ -1,30 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/media_buy/update_media_buy_async_response_input_required.json -# timestamp: 2026-05-29T23:56:01+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ... import context as context_1 -from ... import ext as ext_1 - - -class Reason(StrEnum): - APPROVAL_REQUIRED = 'APPROVAL_REQUIRED' - CHANGE_CONFIRMATION = 'CHANGE_CONFIRMATION' - - -class UpdateMediaBuyInputRequired(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - reason: Annotated[ - Reason | None, Field(description='Reason code indicating why input is needed') - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/update_media_buy_async_response_submitted.py b/src/adcp/types/generated_poc/core/async_response_refs/media_buy/update_media_buy_async_response_submitted.py deleted file mode 100644 index ef9261393..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/update_media_buy_async_response_submitted.py +++ /dev/null @@ -1,47 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/media_buy/update_media_buy_async_response_submitted.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ... import context as context_1 -from ... import error -from ... import ext as ext_1 - - -class UpdateMediaBuySubmitted(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Literal['submitted'], - Field( - description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, whose media_buy_id is issued in-line. See task-status.json for the full task-status enum.' - ), - ] = 'submitted' - task_id: Annotated[ - str, - Field( - description='Task handle the buyer uses with get_task_status (or the legacy AdCP tasks/get alias), and that the seller references on push-notification callbacks. This AdCP application-layer handle remains the snake_case task_id in every transport payload and is distinct from any transport-native A2A Task id.' - ), - ] - message: Annotated[ - str | None, - Field( - description="Optional human-readable explanation of why the task is submitted — e.g., 'Awaiting operator re-approval; typical turnaround 2–4 hours.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", - max_length=2000, - ), - ] = None - errors: Annotated[ - list[error.Error] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/update_media_buy_async_response_working.py b/src/adcp/types/generated_poc/core/async_response_refs/media_buy/update_media_buy_async_response_working.py deleted file mode 100644 index dbe6e7e2c..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/media_buy/update_media_buy_async_response_working.py +++ /dev/null @@ -1,31 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/media_buy/update_media_buy_async_response_working.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictFloat - -from ... import context as context_1 -from ... import ext as ext_1 - - -class UpdateMediaBuyWorking(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - percentage: Annotated[ - StrictFloat | None, Field(description='Completion percentage (0-100)', ge=0.0, le=100.0) - ] = None - current_step: Annotated[ - str | None, Field(description='Current step or phase of the operation') - ] = None - total_steps: Annotated[ - SchemaInt | None, Field(description='Total number of steps in the operation', ge=1) - ] = None - step_number: Annotated[SchemaInt | None, Field(description='Current step number', ge=1)] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/async_response_refs/signals/__init__.py b/src/adcp/types/generated_poc/core/async_response_refs/signals/__init__.py deleted file mode 100644 index 0bf6028ff..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/signals/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# generated by datamodel-codegen: -# filename: .schema_temp -# timestamp: 2026-06-04T19:44:00+00:00 diff --git a/src/adcp/types/generated_poc/core/async_response_refs/signals/get_signals_async_response_submitted.py b/src/adcp/types/generated_poc/core/async_response_refs/signals/get_signals_async_response_submitted.py deleted file mode 100644 index 35ec0661d..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/signals/get_signals_async_response_submitted.py +++ /dev/null @@ -1,51 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/signals/get_signals_async_response_submitted.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - -from ... import context as context_1 -from ... import error -from ... import ext as ext_1 - - -class GetSignalsSubmitted(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Literal['submitted'], - Field( - description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, whose signals array is issued in-line. See task-status.json for the full task-status enum.' - ), - ] = 'submitted' - task_id: Annotated[ - str, - Field( - description='Task handle the caller uses with get_task_status (or the legacy AdCP tasks/get alias), and that the agent references on push-notification callbacks. The signals array is issued on the completion artifact, not here. This AdCP application-layer handle remains the snake_case task_id in every transport payload and is distinct from any transport-native A2A Task id.' - ), - ] - message: Annotated[ - str | None, - Field( - description="Optional human-readable explanation of why the task is submitted — e.g., 'Provider discovery queued; typical turnaround 10-30 minutes.' Plain text only. Callers MUST treat this as untrusted agent input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile agent may inject prompt-injection payloads aimed at the caller's agent.", - max_length=2000, - ), - ] = None - estimated_completion: Annotated[ - AwareDatetime | None, - Field(description='Estimated completion time for the signal discovery task.'), - ] = None - errors: Annotated[ - list[error.Error] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories or partial provider unavailability). Terminal failures belong in the error branch, not here.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/async_response_refs/signals/get_signals_async_response_working.py b/src/adcp/types/generated_poc/core/async_response_refs/signals/get_signals_async_response_working.py deleted file mode 100644 index 51c7e5023..000000000 --- a/src/adcp/types/generated_poc/core/async_response_refs/signals/get_signals_async_response_working.py +++ /dev/null @@ -1,40 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/async_response_refs/signals/get_signals_async_response_working.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictFloat - -from ... import context as context_1 -from ... import ext as ext_1 - - -class GetSignalsWorking(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - percentage: Annotated[ - StrictFloat | None, - Field( - description='Progress percentage of the signal discovery operation.', ge=0.0, le=100.0 - ), - ] = None - current_step: Annotated[ - str | None, - Field( - description='Current step in the signal discovery process, such as `querying_providers`, `ranking_signals`, or `checking_deployments`.' - ), - ] = None - total_steps: Annotated[ - SchemaInt | None, - Field(description='Total number of steps in the signal discovery process.'), - ] = None - step_number: Annotated[ - SchemaInt | None, Field(description='Current step number (1-indexed).') - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/attestation_capabilities.py b/src/adcp/types/generated_poc/core/attestation_capabilities.py deleted file mode 100644 index 9cd9b6537..000000000 --- a/src/adcp/types/generated_poc/core/attestation_capabilities.py +++ /dev/null @@ -1,144 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/attestation_capabilities.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, ConfigDict, Field, RootModel - -from . import attestation_issuer -from . import ext as ext_1 - - -class SupportedDeliveryMethod(StrEnum): - credential_uri = 'credential_uri' - issuer_credential_id = 'issuer_credential_id' - embedded = 'embedded' - - -class CredentialOrigin(RootModel[AnyUrl]): - root: AnyUrl - - -class Authentication(StrEnum): - none = 'none' - evaluator_managed = 'evaluator_managed' - - -class Resolver(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - resolver_id: Annotated[str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9._:-]+$')] - url: Annotated[ - AnyUrl, - Field( - description='Evaluator-configured HTTPS resolver endpoint. Calls use POST with Content-Type application/json and a body containing only credential_id; query-string and path interpolation are forbidden. The evaluator still applies the attestation fetch contract before every call.' - ), - ] - authentication: Annotated[ - Authentication, - Field( - description='Whether the resolver is public or uses credentials managed outside AdCP task payloads. Presenter-supplied credentials are never accepted.' - ), - ] - - -class AcceptedVerifier(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: AnyUrl - claim_types: Annotated[list[AnyUrl] | None, Field(min_length=1)] = None - proof_formats: Annotated[list[AnyUrl] | None, Field(min_length=1)] = None - ext: ext_1.ExtensionObject | None = None - - -class AcceptedIssuer(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - issuer: attestation_issuer.AttestationIssuer - claim_types: Annotated[ - list[AnyUrl] | None, - Field( - description='Optional subset of accepted_claim_types this issuer may assert. Omit to allow any globally accepted claim type for this issuer.', - min_length=1, - ), - ] = None - proof_formats: Annotated[ - list[AnyUrl] | None, - Field( - description='Optional subset of accepted_proof_formats allowed for this issuer. Omit to allow any globally accepted proof format for this issuer.', - min_length=1, - ), - ] = None - credential_origins: Annotated[ - list[CredentialOrigin] | None, - Field( - description='Canonical HTTPS origins from which credential_uri locators may be fetched for this issuer. Exact origin matching happens after URL canonicalization and before DNS resolution. Paths in the credential URI may vary; userinfo is forbidden.', - min_length=1, - ), - ] = None - resolvers: Annotated[ - list[Resolver] | None, - Field( - description='Evaluator-approved resolver endpoints for issuer_credential_id delivery. Presentations carry only resolver_id; they cannot replace url or authentication policy.', - min_length=1, - ), - ] = None - ext: ext_1.ExtensionObject | None = None - - -class AttestationCapabilities(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - accepted_claim_types: Annotated[ - list[AnyUrl], - Field( - description='Open claim identifiers the evaluator is prepared to evaluate. Each value is an absolute URI. Absence means the evaluator has not advertised portable-attestation support; an empty list is not permitted.', - min_length=1, - ), - ] - accepted_proof_formats: Annotated[ - list[AnyUrl], - Field( - description='Open credential/proof format identifiers the evaluator can verify. Values are absolute URIs rather than a protocol enum so issuers can adopt new formats without AdCP endorsement.', - min_length=1, - ), - ] - supported_delivery_methods: Annotated[ - list[SupportedDeliveryMethod], - Field( - description='Credential delivery paths this evaluator supports. credential_uri resolves an HTTPS credential URI from the presentation; issuer_credential_id combines issuer, credential_id, and an evaluator-published resolver_id; embedded accepts an inline credential.', - min_length=1, - ), - ] - accepted_issuers: Annotated[ - list[AcceptedIssuer], - Field( - description='Issuer allowlist and resolver policy. Matching is on the canonical AttestationIssuer identity. A presenter-supplied issuer or credential URI that does not match this policy is rejected without an outbound request.', - min_length=1, - ), - ] - accepted_verifiers: Annotated[ - list[AcceptedVerifier] | None, - Field( - description="Verifier agents the evaluator may call. A presenter's verify_agent nomination must match one of these canonicalized URLs, but the evaluator remains verifier-of-record and chooses whether to use the nominated agent, another accepted agent, or local verification.", - min_length=1, - ), - ] = None - max_embedded_credential_bytes: Annotated[ - SchemaInt | None, - Field( - description='Maximum UTF-8 byte size accepted for one embedded credential. Evaluators MUST enforce this limit before parsing the credential. The protocol ceiling is 1 MiB.', - ge=1024, - le=1048576, - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/attestation_evaluation.py b/src/adcp/types/generated_poc/core/attestation_evaluation.py deleted file mode 100644 index 9a0536ab7..000000000 --- a/src/adcp/types/generated_poc/core/attestation_evaluation.py +++ /dev/null @@ -1,140 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/attestation_evaluation.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictFloat - -from . import ext as ext_1 - - -class Outcome(StrEnum): - verified = 'verified' - not_found = 'not_found' - expired = 'expired' - revoked = 'revoked' - invalid = 'invalid' - unsupported = 'unsupported' - unverifiable = 'unverifiable' - untrusted_issuer = 'untrusted_issuer' - untrusted_resolver = 'untrusted_resolver' - subject_mismatch = 'subject_mismatch' - digest_mismatch = 'digest_mismatch' - resolution_failed = 'resolution_failed' - - -class ReasonCode(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Za-z0-9._:-]+$'} - - -class ActionBinding(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - action_type: Annotated[ - AnyUrl, - Field( - description='Absolute URI naming the action vocabulary, such as an AdCP governance-check or audience-evidence snapshot type.' - ), - ] - action_id: Annotated[ - str | None, - Field( - description="Stable identifier of the consuming action within the domain consumer's namespace.", - max_length=1024, - min_length=1, - ), - ] = None - action_digest: Annotated[ - str | None, - Field( - description='SHA-256 digest of the domain-defined canonical action preimage.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - - -class AttestationEvaluation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - reference_digest: Annotated[ - str, - Field( - description='SHA-256 of the RFC 8785 JSON Canonicalization Scheme encoding of the complete AttestationReference, including embedded_credential when present. This prevents a result for one presentation from being replayed for another.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] - credential_digest: Annotated[ - str | None, - Field( - description='SHA-256 of the exact resolved or embedded credential bytes according to proof_format. REQUIRED by the normative contract when outcome is verified, expired, revoked, invalid, subject_mismatch, or digest_mismatch; omitted when no credential bytes were obtained. For dual delivery, this is the resolved credential digest; reference_digest independently binds the embedded bytes.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - proof_format: Annotated[ - AnyUrl | None, - Field( - description='Absolute URI identifying the credential/proof format used to interpret and hash obtained credential bytes. REQUIRED whenever credential_digest is present.' - ), - ] = None - outcome: Annotated[ - Outcome, - Field( - description='Stable evaluation outcome. verified means all required issuer, signature, claim type, subject, consuming-action scope, validity, revocation, and digest checks passed under evaluator policy. subject_mismatch covers either a credential/presentation subject mismatch or a presentation/consuming-action subject mismatch. Other values are non-authoritative failures and MUST NOT be treated as verified.' - ), - ] - evaluated_at: AwareDatetime - evaluated_by: Annotated[ - AnyUrl, - Field( - description='Canonical agent URL of the evaluator-of-record that produced this result, even when it delegated proof checking to an accepted verifier.' - ), - ] - verifier_agent_url: Annotated[ - AnyUrl | None, - Field( - description='Accepted verifier agent actually used, when verification was delegated. Omit for local verification or when no verifier was called.' - ), - ] = None - reason_codes: Annotated[ - list[ReasonCode] | None, - Field( - description='Open, machine-readable reason identifiers supplied by the evaluator. Consumers MUST branch on outcome, not on free-form or evaluator-specific reason codes.', - min_length=1, - ), - ] = None - confidence: Annotated[ - StrictFloat | None, - Field( - description='Optional evaluator-produced confidence in the normalized outcome on the AdCP 0–1 scale. This is evaluation output, never copied from an authoritative presenter field. Omit when the proof or evaluator does not support a meaningful confidence score.', - ge=0.0, - le=1.0, - ), - ] = None - revocation_checked_at: Annotated[ - AwareDatetime | None, - Field( - description='When the evaluator last checked authoritative revocation state. Its freshness is governed by proof format and evaluator policy.' - ), - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='Upper bound on reuse of this evaluation, considering credential expiry, revocation-cache TTL, and evaluator policy. Consumers MUST re-evaluate after this time.' - ), - ] = None - action_binding: Annotated[ - ActionBinding | None, - Field( - description='Optional binding to the consuming action or readback. Domain consumers that rely on an evaluation MUST carry either an action id or an action digest so the result cannot be transplanted to an unrelated decision.' - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/attestation_issuer.py b/src/adcp/types/generated_poc/core/attestation_issuer.py deleted file mode 100644 index f1e8bd57b..000000000 --- a/src/adcp/types/generated_poc/core/attestation_issuer.py +++ /dev/null @@ -1,80 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/attestation_issuer.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - -from . import brand_ref -from . import ext as ext_1 - - -class AttestationIssuer2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Annotated[ - Literal['agent'], - Field( - description='The issuer is an AdCP agent identified by its canonical HTTPS endpoint.' - ), - ] = 'agent' - agent_url: Annotated[ - AnyUrl, - Field( - description='Canonical HTTPS endpoint of the issuing agent. Evaluators compare it using AdCP URL canonicalization rules.' - ), - ] - ext: ext_1.ExtensionObject | None = None - - -class AttestationIssuer3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Annotated[ - Literal['origin'], - Field( - description='The issuer is identified by a canonical HTTPS origin because no AdCP brand or agent identity applies.' - ), - ] = 'origin' - origin: Annotated[ - AnyUrl, - Field( - description='Canonical HTTPS origin with no path, query, fragment, or userinfo. This identifies the issuer; it does not authorize a fetch.' - ), - ] - ext: ext_1.ExtensionObject | None = None - - -class AttestationIssuer1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Annotated[ - Literal['brand'], Field(description='The issuer is identified by an AdCP BrandRef.') - ] = 'brand' - brand: brand_ref.BrandReference - ext: ext_1.ExtensionObject | None = None - - -AttestationIssuer = Annotated[ - AttestationIssuer1 | AttestationIssuer2 | AttestationIssuer3, - Field( - description='Canonical identity of the party that issued an attestation credential. The discriminator selects an existing AdCP identity when one exists and falls back to an HTTPS origin for other attestors. This identity is a claim carried by the presentation; evaluators MUST match it against their configured trust policy and verify it from the resolved or embedded credential before relying on it.', - discriminator='type', - examples=[ - { - 'type': 'brand', - 'brand': {'domain': 'nova-brands.example', 'brand_id': 'nova_motors'}, - }, - {'type': 'agent', 'agent_url': 'https://attestor.example/adcp'}, - {'type': 'origin', 'origin': 'https://credentials.example'}, - ], - title='Attestation Issuer', - ), -] diff --git a/src/adcp/types/generated_poc/core/attestation_reference.py b/src/adcp/types/generated_poc/core/attestation_reference.py deleted file mode 100644 index e97dadf65..000000000 --- a/src/adcp/types/generated_poc/core/attestation_reference.py +++ /dev/null @@ -1,155 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/attestation_reference.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, model_validator - -from . import attestation_issuer, attestation_subject -from . import ext as ext_1 - - -class ValidityHint(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - not_before: AwareDatetime | None = None - expires_at: AwareDatetime | None = None - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: AnyUrl - - -class Locator(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Literal['credential_uri'] = 'credential_uri' - credential_uri: Annotated[ - AnyUrl, - Field( - description='HTTPS URI of the credential. Before any fetch, evaluators MUST match its canonical origin to credential_origins on the accepted issuer capability and then apply the attestation fetch contract.' - ), - ] - ext: ext_1.ExtensionObject | None = None - - -class Locator1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Literal['issuer_credential_id'] = 'issuer_credential_id' - credential_id: Annotated[ - str, - Field( - description="Stable credential identifier in the issuer's namespace. It is meaningful only together with issuer and resolver_id.", - max_length=1024, - min_length=1, - ), - ] - resolver_id: Annotated[ - str, - Field( - description='Identifier of a resolver already published in the matched accepted_issuers[].resolvers[] capability. A presenter cannot supply or override its URL.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9._:-]+$', - ), - ] - ext: ext_1.ExtensionObject | None = None - - -class EmbeddedCredential(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - format: Annotated[ - AnyUrl, - Field( - description='Open, absolute URI identifying the credential/proof format. AdCP does not impose a universal issuer payload schema.' - ), - ] - value: Annotated[ - dict[str, Any] | str, - Field( - description='Credential encoded as a JSON object or a compact string, according to format.', - min_length=1, - ), - ] - ext: ext_1.ExtensionObject | None = None - - -class AttestationReference(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - issuer: attestation_issuer.AttestationIssuer - claim_type: Annotated[ - AnyUrl, - Field( - description='Open, absolute URI identifying the claim vocabulary. The URI is an identifier and need not be dereferenceable. AdCP does not maintain an enum of approved claims.' - ), - ] - subject: attestation_subject.AttestationSubject - locator: Annotated[ - Locator | Locator1 | None, - Field( - description='Stable locator for resolving the credential. Credential URLs are permitted only when their canonical origin is allowlisted for the matched issuer. Issuer-scoped IDs name a resolver_id already published by the evaluator; the presentation cannot introduce a resolver URL.', - discriminator='type', - ), - ] = None - embedded_credential: Annotated[ - EmbeddedCredential | None, - Field( - description='Optional inline credential for private, authenticated, or offline delivery. It may accompany locator or be the only delivery path. Its format must be supported by the evaluator, and the evaluator MUST verify the credential exactly as it would a resolved credential.' - ), - ] = None - content_digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 digest pin for the exact credential bytes, formatted as sha256:. The credential format defines its canonical byte representation. A mismatch is invalid and MUST NOT fall back to the unpinned credential. REQUIRED when both locator and embedded_credential are present; both byte representations MUST match this digest.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - credential_version: Annotated[ - str | None, - Field( - description='Optional issuer-defined credential version hint. It is advisory; the resolved credential is authoritative.', - max_length=255, - min_length=1, - ), - ] = None - validity_hint: Annotated[ - ValidityHint | None, - Field( - description="Optional planning-time validity hint copied from issuer metadata. Evaluators MUST use the resolved or embedded credential's signed validity and revocation state as authoritative." - ), - ] = None - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description='Optional presenter nomination of a verifier already published by the evaluator. This is a representation, not routing authority: agent_url MUST match an accepted_verifiers[] entry after canonicalization, and the evaluator may choose another accepted verifier or verify locally.' - ), - ] = None - ext: ext_1.ExtensionObject | None = None - - @model_validator(mode='after') - def _require_schema_required_group(self) -> AttestationReference: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('locator',), ('embedded_credential',),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'AttestationReference requires at least one of these field groups: locator | embedded_credential' - ) diff --git a/src/adcp/types/generated_poc/core/attestation_subject.py b/src/adcp/types/generated_poc/core/attestation_subject.py deleted file mode 100644 index ff4620b51..000000000 --- a/src/adcp/types/generated_poc/core/attestation_subject.py +++ /dev/null @@ -1,90 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/attestation_subject.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - -from . import brand_ref -from . import ext as ext_1 - - -class AttestationSubject2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Literal['agent'] = 'agent' - agent_url: Annotated[ - AnyUrl, Field(description='Canonical HTTPS endpoint of the agent the claim concerns.') - ] - ext: ext_1.ExtensionObject | None = None - - -class AttestationSubject3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Literal['resource'] = 'resource' - resource_type: Annotated[ - AnyUrl, - Field( - description='Open, absolute URI naming the subject vocabulary, such as https://adcontextprotocol.org/claims/subjects/signal. AdCP does not maintain an exhaustive enum.' - ), - ] - namespace: Annotated[ - AnyUrl, - Field( - description='Absolute URI identifying the namespace in which id is unique. This may be an AdCP agent endpoint, a catalog origin, or a domain-specific namespace URI.' - ), - ] - id: Annotated[ - str, - Field( - description='Stable identifier for the subject within namespace. It MUST NOT be compared without resource_type and namespace.', - max_length=1024, - min_length=1, - ), - ] - content_digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 pin for the exact content or immutable snapshot identified by this resource subject. This is part of the complete typed subject identity and is distinct from AttestationReference.content_digest, which pins credential bytes.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - ext: ext_1.ExtensionObject | None = None - - -class AttestationSubject1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Literal['brand'] = 'brand' - brand: brand_ref.BrandReference - ext: ext_1.ExtensionObject | None = None - - -AttestationSubject = Annotated[ - AttestationSubject1 | AttestationSubject2 | AttestationSubject3, - Field( - description='Typed identity of the entity or object an attestation credential is about. Brand and agent subjects reuse canonical AdCP identities. Other resources use an open, URI-namespaced resource_type plus an identifier whose namespace is explicit. Evaluators MUST compare the resolved credential subject to this complete typed identity, not to id alone.', - discriminator='type', - examples=[ - { - 'type': 'brand', - 'brand': {'domain': 'nova-brands.example', 'brand_id': 'nova_motors'}, - }, - { - 'type': 'resource', - 'resource_type': 'https://adcontextprotocol.org/claims/subjects/signal', - 'namespace': 'https://signals.meridian.example/adcp', - 'id': 'signal_urban_commuters', - }, - ], - title='Attestation Subject', - ), -] diff --git a/src/adcp/types/generated_poc/core/attribution_window.py b/src/adcp/types/generated_poc/core/attribution_window.py deleted file mode 100644 index a78e4ecbf..000000000 --- a/src/adcp/types/generated_poc/core/attribution_window.py +++ /dev/null @@ -1,37 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/attribution_window.json -# timestamp: 2026-05-22T13:15:12+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..enums import attribution_model -from . import duration - - -class AttributionWindow(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - post_click: Annotated[ - duration.Duration | None, - Field( - description='Post-click attribution window. Conversions occurring within this duration after a click are attributed to the ad.' - ), - ] = None - post_view: Annotated[ - duration.Duration | None, - Field( - description='Post-view attribution window. Conversions occurring within this duration after an ad impression (without click) are attributed to the ad.' - ), - ] = None - model: Annotated[ - attribution_model.AttributionModel | None, - Field( - description="Attribution model used to assign credit when multiple touchpoints exist. SHOULD be populated when committing to a specific model; when absent, the seller's default applies." - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/audience_activation_method.py b/src/adcp/types/generated_poc/core/audience_activation_method.py deleted file mode 100644 index 8b1aae52c..000000000 --- a/src/adcp/types/generated_poc/core/audience_activation_method.py +++ /dev/null @@ -1,197 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/audience_activation_method.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, ConfigDict, Field - -from ..enums import cloud_storage_protocol -from . import brand_ref - - -class AudienceActivationMethod1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - pattern: Literal['sync_audiences'] = 'sync_audiences' - - -class BuyerAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="Buyer agent's registered agent URL. Canonical identifier, mirroring trusted_match.providers[].agent_url." - ), - ] - - -class AudienceActivationMethod2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - pattern: Literal['tmp_identity_match'] = 'tmp_identity_match' - buyer_agent: BuyerAgent - - -class Direction(StrEnum): - buyer_to_seller = 'buyer_to_seller' - seller_to_buyer = 'seller_to_buyer' - - -class Cloud(StrEnum): - aws = 'aws' - azure = 'azure' - gcp = 'gcp' - - -class ConsumerIdentity(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - cloud: Annotated[ - Cloud | None, Field(description="Cloud family hosting this identity's account.") - ] = None - region: Annotated[ - str | None, - Field( - description="Vendor/cloud region identifier. Format follows the cloud family's region naming; not constrained by this schema." - ), - ] = None - identity: Annotated[ - str, - Field( - description="Principal to grant, interpreted relative to the enclosing entry's vendor.domain. Format is vendor-specific and opaque — Snowflake: orgname.accountname; Databricks: sharing recipient identifier; BigQuery: IAM principal.", - min_length=1, - ), - ] - - -class AudienceActivationMethod3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - pattern: Literal['file_transfer'] = 'file_transfer' - transport: Annotated[ - cloud_storage_protocol.CloudStorageProtocol, - Field(description='Storage protocol for the exchange.'), - ] - directions: Annotated[ - list[Direction] | None, - Field( - description='Supported transfer directions. buyer_to_seller: buyer writes to a seller-hosted bucket. seller_to_buyer: seller reads from a buyer-hosted bucket. Absent means unspecified (resolve during account setup), not neither.', - min_length=1, - ), - ] = None - vendor: Annotated[ - brand_ref.BrandReference, - Field( - description="Platform providing the storage primitive (e.g., the cloud provider's domain)." - ), - ] - - -class AudienceActivationMethod4(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - pattern: Literal['dataset_query'] = 'dataset_query' - vendor: Annotated[ - brand_ref.BrandReference, - Field(description='Data-sharing platform the seller can consume from.'), - ] - consumer_identities: Annotated[ - list[ConsumerIdentity] | None, - Field( - description="Principals the buyer grants access to in the vendor's system. identity is always required. cloud and region are optional, paired deployment metadata: omit both for global principals (for example, an IAM principal or federated identity). Their operational meaning is vendor-specific — they may constrain direct-share reachability or select a fulfillment route, or may be routing and cost hints only. They are not compliance boundaries; data-transfer assessments key on the recipient entity's jurisdiction, not the grantee account's region. Optional: sellers MAY instead communicate identities during account setup.", - min_length=1, - ), - ] = None - - -class AudienceActivationMethod5(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - pattern: Literal['clean_room'] = 'clean_room' - vendor: Annotated[ - brand_ref.BrandReference, Field(description="Clean-room product's operating domain.") - ] - - -class AudienceActivationMethod6(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - pattern: Literal['platform_distribution'] = 'platform_distribution' - vendor: Annotated[ - brand_ref.BrandReference, Field(description="Distribution platform's operating domain.") - ] - destination_ref: Annotated[ - str | None, - Field( - description="Opaque, account-scoped destination or seat reference in the vendor's system, as the buyer needs it to initiate distribution. A seller-declared configuration reference, not a buyer-invoked key or secret. Sellers MUST NOT publish one global reference when the vendor configuration is buyer- or account-specific. Omit until account setup has established the destination.", - max_length=256, - min_length=1, - ), - ] = None - bind_expiry_days: Annotated[ - SchemaInt | None, - Field( - description="Window after which the seller MAY expire an unfulfilled platform_segment bind (per-audience action: failed on sync_audiences). Initial vendor distribution is days-scale; windows shorter than the vendor's documented distribution latency are non-conformant.", - ge=1, - ), - ] = None - - -AudienceActivationMethod = Annotated[ - AudienceActivationMethod1 - | AudienceActivationMethod2 - | AudienceActivationMethod3 - | AudienceActivationMethod4 - | AudienceActivationMethod5 - | AudienceActivationMethod6, - Field( - description='One way buyer audience data can reach a seller for targeting. The pattern field discriminates the entry; each pattern carries only its own fields. Two patterns are AdCP-canonical (sync_audiences, tmp_identity_match); the rest describe integration paths that move data outside the protocol while remaining discoverable through it. Vendor identity is a BrandRef domain so new platforms declare a domain rather than waiting on an enum change. Experimental (x-status: experimental): the audience-activation surface — this schema, product.audience_activation, audience_targeting.supported_activation_methods, and the audience_activation_methods product filter — is new and not yet field-tested across parties. Sellers that implement it MUST list media_buy.audience_activation in experimental_features. Per docs/reference/experimental-status, it MAY change between 3.x releases with notice.', - examples=[ - {'pattern': 'sync_audiences'}, - { - 'pattern': 'tmp_identity_match', - 'buyer_agent': {'agent_url': 'https://buyer.example-agent.com'}, - }, - { - 'pattern': 'file_transfer', - 'transport': 's3', - 'directions': ['buyer_to_seller'], - 'vendor': {'domain': 'object-store.example'}, - }, - { - 'pattern': 'dataset_query', - 'vendor': {'domain': 'data-cloud.example'}, - 'consumer_identities': [ - { - 'cloud': 'aws', - 'region': 'us-east-1', - 'identity': 'SELLERORG.ADCP_INGEST', - }, - {'identity': 'serviceAccount:adcp-ingest@publisher.example'}, - ], - }, - {'pattern': 'clean_room', 'vendor': {'domain': 'collaboration-room.example'}}, - { - 'pattern': 'platform_distribution', - 'vendor': {'domain': 'activation-hub.example'}, - 'destination_ref': 'dest_12345', - 'bind_expiry_days': 14, - }, - ], - title='Audience Activation Method', - ), -] diff --git a/src/adcp/types/generated_poc/core/audience_characteristic.py b/src/adcp/types/generated_poc/core/audience_characteristic.py deleted file mode 100644 index 43bb30e53..000000000 --- a/src/adcp/types/generated_poc/core/audience_characteristic.py +++ /dev/null @@ -1,93 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/audience_characteristic.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from typing import Any, Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, RootModel, StrictBool, StrictFloat, model_validator - - -class Dimension(RootModel[AnyUrl]): - root: Annotated[ - AnyUrl, - Field( - description='Canonical dimension name or HTTPS URI. AdCP 3.2 defines `age`; other values remain provider- or taxonomy-scoped.' - ), - ] - - -class Value(RootModel[list[str | StrictFloat | StrictBool]]): - root: Annotated[ - list[str | StrictFloat | StrictBool], - Field(description='Scalar or set value for the dimension.', min_length=1), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - - - -class Range(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - min: StrictFloat | None = None - max: StrictFloat | None = None - - -class Taxonomy(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - system: AnyUrl - version: Annotated[str | None, Field(min_length=1)] = None - value_id: Annotated[str | None, Field(min_length=1)] = None - - -class AudienceCharacteristic(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - dimension: Annotated[ - Literal['age'] | Dimension, - Field( - description='Canonical dimension name or HTTPS URI. AdCP 3.2 defines `age`; other values remain provider- or taxonomy-scoped.' - ), - ] - value: Annotated[ - str | StrictFloat | StrictBool | Value | None, - Field(description='Scalar or set value for the dimension.'), - ] = None - range: Annotated[ - Range | None, - Field( - description='Inclusive numeric interval. For dimension `age`, values are completed years and MUST be non-negative integers, and min MUST be less than or equal to max. JSON Schema enforces the age value and bound types; implementations enforce the relational min <= max constraint.' - ), - ] = None - taxonomy: Annotated[ - Taxonomy | None, - Field( - description='External system that defines the dimension or value. Taxonomy metadata is descriptive and does not create exact demographic targeting semantics.' - ), - ] = None - label: Annotated[ - str | None, Field(description='Human-readable display label; never the comparison key.') - ] = None - - @model_validator(mode='after') - def _require_schema_required_group(self) -> AudienceCharacteristic: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('value',), ('range',),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'AudienceCharacteristic requires at least one of these field groups: value | range' - ) diff --git a/src/adcp/types/generated_poc/core/audience_evidence.py b/src/adcp/types/generated_poc/core/audience_evidence.py deleted file mode 100644 index 826e8a853..000000000 --- a/src/adcp/types/generated_poc/core/audience_evidence.py +++ /dev/null @@ -1,476 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/audience_evidence.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Any, Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, RootModel, StrictFloat - -from ..enums import audience_evidence_methodology, audience_resolution_method, audience_subject_type -from . import audience_characteristic, brand_ref, date_range -from . import ext as ext_1 -from .attestation_reference import AttestationReference - - -class Relationship(StrEnum): - composition = 'composition' - index = 'index' # type: ignore[assignment] - reach_estimate = 'reach_estimate' - - -class Unit(StrEnum): - fraction = 'fraction' - ratio = 'ratio' - count = 'count' # type: ignore[assignment] - - -class Baseline(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - system: AnyUrl - population_id: Annotated[str, Field(min_length=1)] - version: Annotated[str, Field(min_length=1)] - description: str | None = None - - -class EvidenceType(StrEnum): - measured = 'measured' - forecast = 'forecast' - seller_declared = 'seller_declared' - - -class Subject12(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Literal['agent'] = 'agent' - agent_url: Annotated[ - AnyUrl, Field(description='Canonical HTTPS endpoint of the agent the claim concerns.') - ] - ext: ext_1.ExtensionObject | None = None - - -class Subject13(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Literal['resource'] = 'resource' - resource_type: Annotated[ - AnyUrl, - Field( - description='Open, absolute URI naming the subject vocabulary, such as https://adcontextprotocol.org/claims/subjects/signal. AdCP does not maintain an exhaustive enum.' - ), - ] - namespace: Annotated[ - AnyUrl, - Field( - description='Absolute URI identifying the namespace in which id is unique. This may be an AdCP agent endpoint, a catalog origin, or a domain-specific namespace URI.' - ), - ] - id: Annotated[ - str, - Field( - description='Stable identifier for the subject within namespace. It MUST NOT be compared without resource_type and namespace.', - max_length=1024, - min_length=1, - ), - ] - content_digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 pin for the exact content or immutable snapshot identified by this resource subject. This is part of the complete typed subject identity and is distinct from AttestationReference.content_digest, which pins credential bytes.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - ext: ext_1.ExtensionObject | None = None - - -class Subject22(Subject12): - pass - - -class Subject23(Subject13): - pass - - -class Subject24(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Literal['resource'] = 'resource' - resource_type: Literal['https://adcontextprotocol.org/claims/subjects/audience-evidence'] = 'https://adcontextprotocol.org/claims/subjects/audience-evidence' - content_digest: Annotated[str, Field(pattern='^sha256:[a-f0-9]{64}$')] - agent_url: Annotated[ - AnyUrl, Field(description='Canonical HTTPS endpoint of the agent the claim concerns.') - ] - ext: ext_1.ExtensionObject | None = None - - -class Subject26(Subject22): - model_config = ConfigDict( - extra='forbid', - ) - - -class Subject27(Subject24): - model_config = ConfigDict( - extra='forbid', - ) - - -class Subject32(Subject12): - pass - - -class Subject33(Subject13): - pass - - -class Subject34(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Literal['resource'] = 'resource' - resource_type: Literal['https://adcontextprotocol.org/claims/subjects/audience-evidence'] = 'https://adcontextprotocol.org/claims/subjects/audience-evidence' - content_digest: Annotated[str, Field(pattern='^sha256:[a-f0-9]{64}$')] - namespace: Annotated[ - AnyUrl, - Field( - description='Absolute URI identifying the namespace in which id is unique. This may be an AdCP agent endpoint, a catalog origin, or a domain-specific namespace URI.' - ), - ] - id: Annotated[ - str, - Field( - description='Stable identifier for the subject within namespace. It MUST NOT be compared without resource_type and namespace.', - max_length=1024, - min_length=1, - ), - ] - ext: ext_1.ExtensionObject | None = None - - -class Subject36(Subject32): - model_config = ConfigDict( - extra='forbid', - ) - resource_type: Annotated[ - AnyUrl, - Field( - description='Open, absolute URI naming the subject vocabulary, such as https://adcontextprotocol.org/claims/subjects/signal. AdCP does not maintain an exhaustive enum.' - ), - ] - namespace: Annotated[ - AnyUrl, - Field( - description='Absolute URI identifying the namespace in which id is unique. This may be an AdCP agent endpoint, a catalog origin, or a domain-specific namespace URI.' - ), - ] - id: Annotated[ - str, - Field( - description='Stable identifier for the subject within namespace. It MUST NOT be compared without resource_type and namespace.', - max_length=1024, - min_length=1, - ), - ] - content_digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 pin for the exact content or immutable snapshot identified by this resource subject. This is part of the complete typed subject identity and is distinct from AttestationReference.content_digest, which pins credential bytes.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - - -class Subject37(Subject34): - model_config = ConfigDict( - extra='forbid', - ) - - -class Subject11(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Literal['brand'] = 'brand' - brand: brand_ref.BrandReference - ext: ext_1.ExtensionObject | None = None - - -class Subject14(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Literal['resource'] = 'resource' - resource_type: Literal['https://adcontextprotocol.org/claims/subjects/audience-evidence'] = 'https://adcontextprotocol.org/claims/subjects/audience-evidence' - content_digest: Annotated[str, Field(pattern='^sha256:[a-f0-9]{64}$')] - brand: brand_ref.BrandReference - ext: ext_1.ExtensionObject | None = None - - -class Subject15(Subject11): - model_config = ConfigDict( - extra='forbid', - ) - - -class Subject16(Subject12): - model_config = ConfigDict( - extra='forbid', - ) - brand: brand_ref.BrandReference - - -class Subject17(Subject14): - model_config = ConfigDict( - extra='forbid', - ) - - -class Subject(RootModel[Subject15 | Subject16 | Subject17]): - root: Annotated[ - Subject15 | Subject16 | Subject17, - Field( - description='Typed identity of the entity or object an attestation credential is about. Brand and agent subjects reuse canonical AdCP identities. Other resources use an open, URI-namespaced resource_type plus an identifier whose namespace is explicit. Evaluators MUST compare the resolved credential subject to this complete typed identity, not to id alone.', - discriminator='type', - examples=[ - { - 'type': 'brand', - 'brand': {'domain': 'nova-brands.example', 'brand_id': 'nova_motors'}, - }, - { - 'type': 'resource', - 'resource_type': 'https://adcontextprotocol.org/claims/subjects/signal', - 'namespace': 'https://signals.meridian.example/adcp', - 'id': 'signal_urban_commuters', - }, - ], - title='AttestationBrandSubject', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - - - -class Subject21(Subject11): - pass - - -class Subject25(Subject21): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, Field(description='Canonical HTTPS endpoint of the agent the claim concerns.') - ] - - -class Subject2(RootModel[Subject25 | Subject26 | Subject27]): - root: Annotated[ - Subject25 | Subject26 | Subject27, - Field( - description='Typed identity of the entity or object an attestation credential is about. Brand and agent subjects reuse canonical AdCP identities. Other resources use an open, URI-namespaced resource_type plus an identifier whose namespace is explicit. Evaluators MUST compare the resolved credential subject to this complete typed identity, not to id alone.', - discriminator='type', - examples=[ - { - 'type': 'brand', - 'brand': {'domain': 'nova-brands.example', 'brand_id': 'nova_motors'}, - }, - { - 'type': 'resource', - 'resource_type': 'https://adcontextprotocol.org/claims/subjects/signal', - 'namespace': 'https://signals.meridian.example/adcp', - 'id': 'signal_urban_commuters', - }, - ], - title='AttestationAgentSubject', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - - - -class Subject31(Subject11): - pass - - -class Subject35(Subject31): - model_config = ConfigDict( - extra='forbid', - ) - resource_type: Annotated[ - AnyUrl, - Field( - description='Open, absolute URI naming the subject vocabulary, such as https://adcontextprotocol.org/claims/subjects/signal. AdCP does not maintain an exhaustive enum.' - ), - ] - namespace: Annotated[ - AnyUrl, - Field( - description='Absolute URI identifying the namespace in which id is unique. This may be an AdCP agent endpoint, a catalog origin, or a domain-specific namespace URI.' - ), - ] - id: Annotated[ - str, - Field( - description='Stable identifier for the subject within namespace. It MUST NOT be compared without resource_type and namespace.', - max_length=1024, - min_length=1, - ), - ] - content_digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 pin for the exact content or immutable snapshot identified by this resource subject. This is part of the complete typed subject identity and is distinct from AttestationReference.content_digest, which pins credential bytes.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - - -class Subject3(RootModel[Subject35 | Subject36 | Subject37]): - root: Annotated[ - Subject35 | Subject36 | Subject37, - Field( - description='Typed identity of the entity or object an attestation credential is about. Brand and agent subjects reuse canonical AdCP identities. Other resources use an open, URI-namespaced resource_type plus an identifier whose namespace is explicit. Evaluators MUST compare the resolved credential subject to this complete typed identity, not to id alone.', - discriminator='type', - examples=[ - { - 'type': 'brand', - 'brand': {'domain': 'nova-brands.example', 'brand_id': 'nova_motors'}, - }, - { - 'type': 'resource', - 'resource_type': 'https://adcontextprotocol.org/claims/subjects/signal', - 'namespace': 'https://signals.meridian.example/adcp', - 'id': 'signal_urban_commuters', - }, - ], - title='AttestationResourceSubject', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - - - -class AttestationRef(AttestationReference): - subject: Subject34 - -class AudienceEvidence(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - evidence_id: Annotated[ - str, - Field( - description='Stable provider-scoped identifier for the logical evidence series across versions.', - min_length=1, - ), - ] - snapshot_id: Annotated[ - str, - Field( - description='Seller-scoped immutable snapshot identifier. It MUST never be reused for different content.', - min_length=1, - ), - ] - version: Annotated[ - str, Field(description='Provider version of this evidence snapshot.', min_length=1) - ] - content_digest: Annotated[ - str, - Field( - description='SHA-256 of the RFC 8785 (JCS) canonical evidence core with content_digest and attestation_refs omitted. Attestation references are excluded so independently issued credentials can be attached without changing the immutable evidence snapshot and without creating a recursive digest. This digest is required even when snapshot_id is present so buyers can detect identifier reuse or catalog drift.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] - audience: audience_characteristic.AudienceCharacteristic - relationship: Annotated[ - Relationship, - Field( - description='What the estimate says: population share, ratio to a baseline, or estimated reachable count.' - ), - ] - value: Annotated[ - StrictFloat, - Field(description='Estimated relationship value, interpreted according to unit.', ge=0.0), - ] - unit: Annotated[ - Unit, - Field( - description='composition uses fraction in [0,1], index uses ratio, and reach_estimate uses count.' - ), - ] - baseline: Annotated[ - Baseline, - Field(description='Reference population against which the estimate was calculated.'), - ] - evidence_type: Annotated[ - EvidenceType, - Field( - description='Nature of the evidence. Independent verification is represented separately by attestation_refs and never inferred from this value.' - ), - ] - methodology: audience_evidence_methodology.AudienceEvidenceMethodology - subject_type: audience_subject_type.AudienceSubjectType - resolution_method: Annotated[ - audience_resolution_method.AudienceResolutionMethod | None, - Field( - description='Optional subject-resolution method used by the study or estimate. This describes population methodology, not a promise that the product can execute that resolution at serving time.' - ), - ] = None - provider: Annotated[ - brand_ref.BrandReference, Field(description='Brand responsible for producing the evidence.') - ] - measurement_window: date_range.DateRange - sample_size: Annotated[ - SchemaInt | None, - Field( - description='Number of observations or respondents underlying the estimate, when applicable.', - ge=1, - ), - ] = None - confidence: Annotated[ - StrictFloat | None, - Field( - description="Provider-defined confidence score in [0,1]. Consumers MUST compare scores only when the provider's methodology makes them comparable.", - ge=0.0, - le=1.0, - ), - ] = None - last_updated: Annotated[ - AwareDatetime, - Field(description='When the provider last produced or refreshed this immutable snapshot.'), - ] - methodology_url: Annotated[ - AnyUrl | None, - Field( - description='Documentation for interpreting the methodology. It is disclosure, not executable routing.' - ), - ] = None - attestation_refs: Annotated[ - list[AttestationRef] | None, - Field( - description="Optional portable attestations about this exact evidence snapshot. Each subject MUST be resource type https://adcontextprotocol.org/claims/subjects/audience-evidence, subject.id MUST equal snapshot_id, and subject.content_digest MUST equal this evidence object's content_digest. Buyer references do not override evaluator issuer or resolver policy.", - max_length=10, - min_length=1, - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/audience_evidence_pin.py b/src/adcp/types/generated_poc/core/audience_evidence_pin.py deleted file mode 100644 index cc35d766b..000000000 --- a/src/adcp/types/generated_poc/core/audience_evidence_pin.py +++ /dev/null @@ -1,23 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/audience_evidence_pin.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from . import ext as ext_1 - - -class AudienceEvidencePin(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - evidence_id: Annotated[str, Field(min_length=1)] - snapshot_id: Annotated[str, Field(min_length=1)] - version: Annotated[str, Field(min_length=1)] - content_digest: Annotated[str, Field(pattern='^sha256:[a-f0-9]{64}$')] - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/audience_evidence_requirements.py b/src/adcp/types/generated_poc/core/audience_evidence_requirements.py deleted file mode 100644 index fb55a146c..000000000 --- a/src/adcp/types/generated_poc/core/audience_evidence_requirements.py +++ /dev/null @@ -1,135 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/audience_evidence_requirements.json -# timestamp: 2026-10-03T15:09:44+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, StrictBool, StrictFloat - -from ..enums import audience_evidence_methodology, audience_resolution_method, audience_subject_type -from . import attestation_issuer, brand_ref -from . import ext as ext_1 -from .duration import Duration - - -class RequirementMode(StrEnum): - required = 'required' - preferred = 'preferred' - - -class EvidencePresence(StrEnum): - required = 'required' - when_available = 'when_available' - - -class AcceptedEvidenceType(StrEnum): - measured = 'measured' - forecast = 'forecast' - seller_declared = 'seller_declared' - - -class Unit(StrEnum): - seconds = 'seconds' - minutes = 'minutes' - hours = 'hours' - days = 'days' - - -class MaximumAge(Duration): - unit: Annotated[ - Unit | None, - Field( - description="Time unit. 'seconds' for sub-minute precision. 'campaign' spans the full campaign flight." - ), - ] = None - - -class AudienceEvidenceRequirements(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - requirement_mode: Annotated[ - RequirementMode, - Field( - description='required excludes products whose published evidence violates the constraints; preferred only affects ranking and explanation. When this policy affects inclusion or ranking, the seller MUST return the exact matching audience_evidence_selections even if get_products.fields omitted that field.' - ), - ] - evidence_presence: Annotated[ - EvidencePresence, - Field( - description='required applies the mode to evidence presence itself. when_available applies constraints only when a product publishes evidence: with requirement_mode required, a product with no evidence remains eligible but a product with evidence must have an admissible item; with preferred, presence and admissibility only affect ranking.' - ), - ] - accepted_methodologies: Annotated[ - list[audience_evidence_methodology.AudienceEvidenceMethodology] | None, - Field( - description='Allowed methodologies. excluded_methodologies always wins if a value appears in both lists.', - min_length=1, - ), - ] = None - excluded_methodologies: Annotated[ - list[audience_evidence_methodology.AudienceEvidenceMethodology] | None, - Field( - description='Disallowed methodologies. Exclusion wins over accepted_methodologies when the lists overlap.', - min_length=1, - ), - ] = None - accepted_evidence_types: Annotated[list[AcceptedEvidenceType] | None, Field(min_length=1)] = ( - None - ) - accepted_providers: Annotated[ - list[brand_ref.BrandReference] | None, - Field( - description='Allowed evidence providers. excluded_providers always wins if the same canonical BrandRef appears in both lists.', - min_length=1, - ), - ] = None - excluded_providers: Annotated[ - list[brand_ref.BrandReference] | None, - Field( - description='Disallowed evidence providers. Exclusion wins over accepted_providers when the lists overlap.', - min_length=1, - ), - ] = None - accepted_subject_types: Annotated[ - list[audience_subject_type.AudienceSubjectType] | None, Field(min_length=1) - ] = None - accepted_resolution_methods: Annotated[ - list[audience_resolution_method.AudienceResolutionMethod] | None, Field(min_length=1) - ] = None - minimum_confidence: Annotated[StrictFloat | None, Field(ge=0.0, le=1.0)] = None - maximum_age: Annotated[ - MaximumAge | None, - Field( - description='Maximum age at evaluation time measured from last_updated. Campaign-relative duration is not valid for evidence age.' - ), - ] = None - methodology_documentation_required: Annotated[ - StrictBool | None, - Field(description='Whether an admissible item must carry methodology_url.'), - ] = False - independent_attestation_required: Annotated[ - StrictBool | None, - Field( - description="Whether an admissible item must have at least one exact reference/evaluation pair whose outcome is verified, whose reference is published in the evidence, and whose claim type and issuer match the buyer's accepted lists as well as the seller's issuer and resolver policy. Setting true requires accepted_attestation_issuers." - ), - ] = False - accepted_attestation_issuers: Annotated[ - list[attestation_issuer.AttestationIssuer] | None, - Field( - description="Buyer allowlist for independent attestation issuers. An exact reference/evaluation pair satisfies the requirement only when reference.issuer matches an entry here. This list narrows and never broadens the seller's accepted issuer policy.", - min_length=1, - ), - ] = None - accepted_attestation_claim_types: Annotated[ - list[AnyUrl] | None, - Field( - description='Optional acceptable claim types when attestation is used. Buyer-supplied claim types further constrain but never broaden seller policy.', - min_length=1, - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/audience_evidence_selection.py b/src/adcp/types/generated_poc/core/audience_evidence_selection.py deleted file mode 100644 index 06ba87388..000000000 --- a/src/adcp/types/generated_poc/core/audience_evidence_selection.py +++ /dev/null @@ -1,96 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/audience_evidence_selection.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from . import attestation_reference, audience_evidence -from . import ext as ext_1 -from .attestation_evaluation import AttestationEvaluation as AttestationEvaluation_1 - - -class DecisionUse(StrEnum): - recommendation = 'recommendation' - eligibility = 'eligibility' - package_construction = 'package_construction' - - -class ActionBinding(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - action_type: Annotated[ - Literal['https://adcontextprotocol.org/actions/audience-evidence-evaluation'], - Field( - description='Absolute URI naming the action vocabulary, such as an AdCP governance-check or audience-evidence snapshot type.' - ), - ] = 'https://adcontextprotocol.org/actions/audience-evidence-evaluation' - action_id: Annotated[ - str, - Field( - description="Stable identifier of the consuming action within the domain consumer's namespace.", - max_length=1024, - min_length=1, - ), - ] - action_digest: Annotated[ - str, - Field( - description='SHA-256 digest of the domain-defined canonical action preimage.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] - - -class Evaluation(AttestationEvaluation_1): - action_binding: Annotated[ - ActionBinding, - Field( - description='Optional binding to the consuming action or readback. Domain consumers that rely on an evaluation MUST carry either an action id or an action digest so the result cannot be transplanted to an unrelated decision.' - ), - ] - - -class AttestationEvaluation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - reference: attestation_reference.AttestationReference - evaluation: Evaluation - - -class AudienceEvidenceSelection(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - evidence_id: Annotated[str, Field(min_length=1)] - snapshot_id: Annotated[str, Field(min_length=1)] - version: Annotated[str, Field(min_length=1)] - content_digest: Annotated[str, Field(pattern='^sha256:[a-f0-9]{64}$')] - decision_use: Annotated[ - DecisionUse, - Field( - description="How this evidence affected the seller's decision. This is not targeting execution." - ), - ] - evidence: Annotated[ - audience_evidence.AudienceEvidence | None, - Field( - description='Optional inline immutable snapshot. Its identity, version, and digest MUST equal the surrounding fields.' - ), - ] = None - attestation_evaluations: Annotated[ - list[AttestationEvaluation] | None, - Field( - description="Exact reference/evaluation pairs used in the decision. The reference MUST be one of the selected evidence snapshot's attestation_refs, evaluation.reference_digest MUST equal the digest of that same reference, reference.subject.content_digest and evaluation.action_binding.action_digest MUST equal this selection's content_digest, evaluation.action_binding.action_id MUST equal snapshot_id, and action_type MUST be https://adcontextprotocol.org/actions/audience-evidence-evaluation.", - max_length=10, - min_length=1, - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/audience_member.py b/src/adcp/types/generated_poc/core/audience_member.py deleted file mode 100644 index 9265d1a96..000000000 --- a/src/adcp/types/generated_poc/core/audience_member.py +++ /dev/null @@ -1,68 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/audience_member.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, model_validator - -from ..enums import uid_type -from . import ext as ext_1 - - -class Uid(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[uid_type.UidType, Field(description='Universal ID type')] - value: Annotated[str, Field(description='Universal ID value')] - - -class AudienceMember(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - external_id: Annotated[ - str, - Field( - description="Buyer-assigned stable identifier for this audience member (e.g. CRM record ID, loyalty ID). Used for deduplication, removal, and cross-referencing with buyer systems. Adapters for CDPs that don't natively assign IDs can derive one (e.g. hash of the member's identifiers)." - ), - ] - hashed_email: Annotated[ - str | None, - Field( - description='SHA-256 hash of lowercase, trimmed email address. Pseudonymous PII, not anonymous — the email namespace is small enough that an unsalted SHA-256 is recoverable via precomputed dictionaries. Treat as PII for retention, consent, and access-control purposes. See docs/reference/privacy-considerations#unsalted-hashed-identifiers-are-pseudonymous-not-anonymous.', - pattern='^[a-f0-9]{64}$', - ), - ] = None - hashed_phone: Annotated[ - str | None, - Field( - description='SHA-256 hash of E.164-formatted phone number (e.g. +12065551234). Pseudonymous PII, not anonymous — the E.164 namespace is small enough that an unsalted SHA-256 is recoverable via precomputed dictionaries. Treat as PII for retention, consent, and access-control purposes. See docs/reference/privacy-considerations#unsalted-hashed-identifiers-are-pseudonymous-not-anonymous.', - pattern='^[a-f0-9]{64}$', - ), - ] = None - uids: Annotated[ - list[Uid] | None, - Field( - description='Universal ID values (MAIDs, RampID, UID2, etc.) for user matching.', - min_length=1, - ), - ] = None - ext: ext_1.ExtensionObject | None = None - - @model_validator(mode='after') - def _require_schema_required_group(self) -> AudienceMember: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('hashed_email',), ('hashed_phone',), ('uids',),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'AudienceMember requires at least one of these field groups: hashed_email | hashed_phone | uids' - ) diff --git a/src/adcp/types/generated_poc/core/audience_selector.py b/src/adcp/types/generated_poc/core/audience_selector.py deleted file mode 100644 index 0fb308a27..000000000 --- a/src/adcp/types/generated_poc/core/audience_selector.py +++ /dev/null @@ -1,137 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/audience_selector.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictBool, StrictFloat - -from . import signal_id as signal_id_1 -from . import signal_ref as signal_ref_1 - - -class AudienceSelector4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Literal['description'], Field(description='Discriminator for description-based selectors') - ] = 'description' - description: Annotated[ - str, - Field( - description="Natural language description of the audience (e.g., 'likely EV buyers', 'high net worth individuals', 'vulnerable communities')", - max_length=2000, - min_length=1, - ), - ] - category: Annotated[ - str | None, - Field( - description="Optional grouping hint for the governance agent (e.g., 'demographic', 'behavioral', 'contextual', 'financial')" - ), - ] = None - - -class AudienceSelector1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Literal['signal'], Field(description='Discriminator for signal-based selectors') - ] = 'signal' - signal_ref: Annotated[ - signal_ref_1.SignalRef | None, - Field(description='The signal to target. New selectors SHOULD use signal_ref.'), - ] = None - signal_id: Annotated[ - signal_id_1.SignalId | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - ), - ] = None - value_type: Annotated[Literal['binary'], Field(description='Discriminator for binary signals')] = 'binary' - value: Annotated[ - StrictBool, - Field( - description='Whether to include (true) or exclude (false) users matching this signal' - ), - ] - - -class AudienceSelector2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Literal['signal'], Field(description='Discriminator for signal-based selectors') - ] = 'signal' - signal_ref: Annotated[ - signal_ref_1.SignalRef | None, - Field(description='The signal to target. New selectors SHOULD use signal_ref.'), - ] = None - signal_id: Annotated[ - signal_id_1.SignalId | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - ), - ] = None - value_type: Annotated[ - Literal['categorical'], Field(description='Discriminator for categorical signals') - ] = 'categorical' - values: Annotated[ - list[str], - Field( - description='Values to target. Users with any of these values will be included.', - min_length=1, - ), - ] - - -class AudienceSelector3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Literal['signal'], Field(description='Discriminator for signal-based selectors') - ] = 'signal' - signal_ref: Annotated[ - signal_ref_1.SignalRef | None, - Field(description='The signal to target. New selectors SHOULD use signal_ref.'), - ] = None - signal_id: Annotated[ - signal_id_1.SignalId | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - ), - ] = None - value_type: Annotated[ - Literal['numeric'], Field(description='Discriminator for numeric signals') - ] = 'numeric' - min_value: Annotated[ - StrictFloat | None, - Field( - description='Minimum value (inclusive). Omit for no minimum. Must be <= max_value when both are provided.' - ), - ] = None - max_value: Annotated[ - StrictFloat | None, - Field( - description='Maximum value (inclusive). Omit for no maximum. Must be >= min_value when both are provided.' - ), - ] = None - - -AudienceSelector = Annotated[ - AudienceSelector1 | AudienceSelector2 | AudienceSelector3 | AudienceSelector4, - Field( - description="Selects an audience by signal reference or natural language description. Uses 'type' as the primary discriminator (signal vs description). Signal selectors additionally use 'value_type' to determine the targeting expression format (matching signal-targeting.json variants).", - title='Audience Selector', - ), -] diff --git a/src/adcp/types/generated_poc/core/audience_source.py b/src/adcp/types/generated_poc/core/audience_source.py deleted file mode 100644 index d4ac99053..000000000 --- a/src/adcp/types/generated_poc/core/audience_source.py +++ /dev/null @@ -1,77 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/audience_source.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - -from . import brand_ref - - -class AudienceSource1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['dataset'] = 'dataset' - vendor: Annotated[ - brand_ref.BrandReference, - Field(description='Data-sharing platform hosting the shared object.'), - ] - locator: Annotated[ - str, - Field( - description="Vendor-native reference to the shared object (share/database/table path). Opaque to AdCP; meaningful to the vendor. Never a credential. For the Databricks path, the locator convention is share:////.; this identifies what to read and is distinct from the seller's recipient identity in consumer_identities[].", - max_length=512, - min_length=1, - ), - ] - access_expires_at: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 time after which the buyer will revoke the grant. Declarative — tells the seller when the pipe closes so re-read behavior is predictable. Expiry bounds the access window, not retention: revocation does not claw back matched membership, and retention remains governed by the buyer-seller data processing agreement.' - ), - ] = None - - -class AudienceSource2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['platform_segment'] = 'platform_segment' - vendor: Annotated[ - brand_ref.BrandReference, Field(description='Distribution platform delivering the segment.') - ] - segment_ref: Annotated[ - str, - Field( - description="The vendor's segment identifier as issued to the buyer (the ID observable in the vendor's console/API). The seller owns the mapping to whatever identifier its ingest minted — it configured the destination and is the only party that can see both sides.", - max_length=256, - min_length=1, - ), - ] - - -AudienceSource = Annotated[ - AudienceSource1 | AudienceSource2, - Field( - description="External source reference for an audience: a pointer to buyer data that the seller ingests over a declared activation rail, instead of inline member deltas. The data never transits AdCP — the protocol carries the reference; ingestion happens on the vendor's rails. kind discriminates the entry. A buyer MUST only send a source kind whose corresponding activation pattern (dataset_query for dataset, platform_distribution for platform_segment) the seller has declared via audience_activation; sellers reject undeclared kinds with UNSUPPORTED_FEATURE. References are never credentials: grant-based paths are in-protocol only when the vendor flow is grantee-identified (the grant names a principal; no secret is conveyed), and requests carrying credential material are rejected with CREDENTIAL_IN_ARGS. Experimental (x-status: experimental): part of the media_buy.audience_activation surface — sellers implementing external sources MUST list media_buy.audience_activation in experimental_features. Per docs/reference/experimental-status, this surface MAY change between 3.x releases with notice.", - examples=[ - { - 'kind': 'dataset', - 'vendor': {'domain': 'data-cloud.example'}, - 'locator': 'PINNACLE.AUDIENCES.HIGH_VALUE_V3', - 'access_expires_at': '2026-12-01T00:00:00Z', - }, - { - 'kind': 'platform_segment', - 'vendor': {'domain': 'activation-hub.example'}, - 'segment_ref': 'seg_88213', - }, - ], - title='Audience Source', - ), -] diff --git a/src/adcp/types/generated_poc/core/authorized_agent_base.py b/src/adcp/types/generated_poc/core/authorized_agent_base.py deleted file mode 100644 index 98159143c..000000000 --- a/src/adcp/types/generated_poc/core/authorized_agent_base.py +++ /dev/null @@ -1,49 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/authorized_agent_base.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, Field - -from . import agent_encryption_key, agent_signing_key - - -class AuthorizedAgentBaseFields(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description="The authorized agent's API endpoint URL. Callers comparing this URL against a registry (sales-agent list, signal-provider registry, TMP provider lookup, etc.) MUST canonicalize both sides per the AdCP URL canonicalization rules, not byte-equality — two URLs that differ only in case, default port, or percent-encoding of unreserved characters are the same agent. See docs/reference/url-canonicalization." - ), - ] - authorized_for: Annotated[ - str, - Field( - description='Human-readable description of what this agent is authorized to do — what it sells (for sales/property agents) or what data it provides (for signal agents). The variant the entry uses (`property_ids`, `signal_tags`, etc.) tells consumers which inflection applies; this field carries the operator-supplied label.', - max_length=500, - min_length=1, - ), - ] - signing_keys: Annotated[ - list[agent_signing_key.AgentSigningKey] | None, - Field( - description='Optional publisher-attested public signing keys for this agent. Use these as the trust anchor for verifying signed agent responses instead of relying on key discovery from the agent domain alone.', - min_length=1, - ), - ] = None - encryption_keys: Annotated[ - list[agent_encryption_key.AgentEncryptionKey] | None, - Field( - description='X25519 public keys for TMPX exposure token encryption. Each key identifies a cluster master that can decrypt TMPX tokens. Used with HPKE mode_base — read replicas encrypt with this public key, only the master can decrypt.', - min_length=1, - ), - ] = None - last_updated: Annotated[ - AwareDatetime | None, - Field( - description='Optional ISO 8601 timestamp indicating when this `authorized_agents[]` entry last changed. Independent of the file-level `last_updated`. Lets validators perform a partial walk by skipping entries whose `last_updated` is older than their indexed value. Advisory — consumers MAY ignore and re-index the full file.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/bidding_policy.py b/src/adcp/types/generated_poc/core/bidding_policy.py deleted file mode 100644 index d6d448201..000000000 --- a/src/adcp/types/generated_poc/core/bidding_policy.py +++ /dev/null @@ -1,97 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/bidding_policy.json -# timestamp: 2026-10-03T15:09:44+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictFloat - - -class Strength(StrEnum): - cap = 'cap' - target = 'target' - - -class CostPer(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - amount: Annotated[ - StrictFloat, - Field( - description='Average cost amount per scope-bound primary-goal result, denominated in the media-buy currency.', - gt=0.0, - ), - ] - strength: Annotated[ - Strength, - Field( - description='`cap` optimizes for an average at or below the amount and accepts underdelivery when necessary; `target` optimizes around the amount while balancing volume and spend. Neither is a per-result or per-auction guarantee.' - ), - ] - - -class Strength1(StrEnum): - floor = 'floor' - target = 'target' - - -class Roas(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - value: Annotated[ - StrictFloat, - Field( - description='Return per unit of ad spend; 4 means 4 units of value per 1 unit spent.', - gt=0.0, - ), - ] - strength: Annotated[ - Strength1, - Field( - description='`floor` prefers underdelivery to knowingly optimizing below the requested return; `target` optimizes around the requested return. Neither guarantees realized return.' - ), - ] - - -class BiddingPolicy(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - automatic: Annotated[ - Literal[True] | None, - Field( - description='Explicitly use seller/provider automatic bidding at this authored scope. At package scope this is a complete override of a media-buy policy, not inheritance. It MUST be the only field in the block and MUST be preserved on readback.' - ), - ] = None - bid_amount: Annotated[ - StrictFloat | None, - Field( - description="Manual auction bid denominated in the media-buy currency and expressed per the selected pricing option's auction unit. For example, a CPM option interprets the amount per thousand impressions. This is the amount submitted to the auction, not a promise that the clearing price equals it. Requires an auction-priced pricing option whose currency equals the media-buy currency.", - gt=0.0, - ), - ] = None - max_bid: Annotated[ - StrictFloat | None, - Field( - description="Hard per-auction ceiling denominated in the media-buy currency and expressed per the selected pricing option's auction unit. This is the only canonical hard auction ceiling and MUST NOT be translated into an average outcome-cost control. Requires an auction-priced pricing option whose currency equals the media-buy currency. May stand alone or supplement cost_per/roas only when the relevant scope capability advertises that combination.", - gt=0.0, - ), - ] = None - cost_per: Annotated[ - CostPer | None, - Field( - description="Average cost control per result of the scope-bound primary optimization goal. At seller-optimized media-buy scope it binds to budget_allocation.optimization_goals; at package scope it binds to that package's optimization_goals; at fixed media-buy scope it binds independently to each inheriting package and is valid only when their primary-goal result units are compatible. Metric goals are compatible only when metric and every result-defining qualifier match; vendor_metric goals only when vendor and metric_id match; event goals only when the event_type/custom_event_name set and resolved attribution_window match. Primary is the earliest array entry among goals tied for the lowest explicit numeric priority; unprioritized goals follow explicitly prioritized goals; when all priorities are absent, the first entry is primary." - ), - ] = None - roas: Annotated[ - Roas | None, - Field( - description='Dimensionless return-on-ad-spend control bound to the same scope-specific primary goal rules as cost_per. The bound goal must be value-bearing; a fixed media-buy default requires a value-bearing primary goal on every inheriting package. Every referenced value-bearing event source MUST declare value_currencies containing the media-buy currency. The seller validates this at buy creation; each buy consumes only exact-currency records, while other declared currencies remain available to other buys. Sellers MUST NOT perform currency conversion.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/bidding_policy_capability.py b/src/adcp/types/generated_poc/core/bidding_policy_capability.py deleted file mode 100644 index 02de476bf..000000000 --- a/src/adcp/types/generated_poc/core/bidding_policy_capability.py +++ /dev/null @@ -1,117 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/bidding_policy_capability.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - - -class Mode(StrEnum): - automatic = 'automatic' - bid_amount = 'bid_amount' - max_bid = 'max_bid' - cost_per = 'cost_per' - roas = 'roas' - - -class CostPerStrength(StrEnum): - cap = 'cap' - target = 'target' - - -class RoasStrength(StrEnum): - floor = 'floor' - target = 'target' - - -class MaxBidWithCostPer(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['max_bid_with_cost_per'] = 'max_bid_with_cost_per' - cost_per_strengths: Annotated[ - list[CostPerStrength], - Field(description='cost_per strengths supported when combined with max_bid.', min_length=1), - ] - - -class MaxBidWithRoas(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['max_bid_with_roas'] = 'max_bid_with_roas' - roas_strengths: Annotated[ - list[RoasStrength], - Field(description='roas strengths supported when combined with max_bid.', min_length=1), - ] - - -class PolicyProfile(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - modes: Annotated[ - list[Mode] | None, - Field( - description='Standalone policy modes accepted in this exact scope and allocation context. Combination-only components belong only in supported_combinations and need not appear here. `automatic` means the seller accepts and preserves an explicit `{automatic:true}` authored block; omission only invokes inheritance/default behavior.', - min_length=1, - ), - ] = None - cost_per_strengths: Annotated[ - list[CostPerStrength] | None, - Field( - description='Supported standalone cost_per strengths in this profile. Required exactly when modes includes cost_per.', - min_length=1, - ), - ] = None - roas_strengths: Annotated[ - list[RoasStrength] | None, - Field( - description='Supported standalone roas strengths in this profile. Required exactly when modes includes roas.', - min_length=1, - ), - ] = None - supported_combinations: Annotated[ - list[MaxBidWithCostPer | MaxBidWithRoas] | None, - Field( - description='Multi-field policies supported in this exact scope and allocation context. Each entry independently qualifies the strengths supported in that combination; standalone strength support does not imply combination support. Absence means no multi-field combination is claimed.', - min_length=1, - ), - ] = None - - -class ScopeCapability(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - fixed: Annotated[ - PolicyProfile | None, - Field(description='Policies supported when budget allocation is fixed or omitted.'), - ] = None - seller_optimized: Annotated[ - PolicyProfile | None, - Field(description='Policies supported when budget_allocation.mode is seller_optimized.'), - ] = None - - -class BiddingPolicyCapability(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - media_buy: Annotated[ - ScopeCapability | None, - Field( - description='Policies accepted at media-buy scope and inherited by packages that omit a package override.' - ), - ] = None - package: Annotated[ - ScopeCapability | None, - Field( - description='Policies accepted as package-authored policies, including explicit automatic overrides.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/brand_id.py b/src/adcp/types/generated_poc/core/brand_id.py deleted file mode 100644 index 40c782997..000000000 --- a/src/adcp/types/generated_poc/core/brand_id.py +++ /dev/null @@ -1,17 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/brand_id.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from adcp.types._scalar import ScalarStr - - -class BrandId(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[a-z0-9_]+$'} - _json_schema_extra = { - 'description': 'Identifier for a brand within a house portfolio. Must be lowercase alphanumeric with underscores only. The house chooses this identifier.', - 'examples': ['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - 'title': 'Brand ID', - } diff --git a/src/adcp/types/generated_poc/core/brand_key.py b/src/adcp/types/generated_poc/core/brand_key.py deleted file mode 100644 index 57dfbd434..000000000 --- a/src/adcp/types/generated_poc/core/brand_key.py +++ /dev/null @@ -1,44 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/brand_key.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field - -from . import brand_id as brand_id_1 - - -class Country(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{2}$'} - - -class BrandKey(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description='Domain that hosts /.well-known/brand.json or is registered for the brand.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - brand_id_1.BrandId | None, - Field( - description='Brand within a house-of-brands manifest. Omit for a single-brand domain.' - ), - ] = None - countries: Annotated[ - list[Country] | None, - Field( - description='Canonical set of ISO 3166-1 alpha-2 countries for this advertiser identity. Omit when the identity is global or the house does not split the brand geographically. Array order is not meaningful; producers MUST sort codes lexicographically before computing keys or signatures. This qualifies account and proposal identity and is not delivery targeting.', - min_length=1, - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/brand_ref.py b/src/adcp/types/generated_poc/core/brand_ref.py deleted file mode 100644 index 9ce889552..000000000 --- a/src/adcp/types/generated_poc/core/brand_ref.py +++ /dev/null @@ -1,98 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/brand_ref.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import AnyUrl, ConfigDict, EmailStr, Field - -from . import brand_id as brand_id_1 -from .assets import image_asset - - -class Country(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{2}$'} - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[image_asset.ImageAsset | None, Field(description='Override logo asset.')] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class BrandReference(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - brand_id_1.BrandId | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.' - ), - ] = None - countries: Annotated[ - list[Country] | None, - Field( - description='Canonical set of ISO 3166-1 alpha-2 countries for this advertiser identity. Omit for a global/default identity. Array order is not meaningful; producers MUST sort codes lexicographically before computing keys or signatures. This qualifies account identity and is not delivery targeting.', - min_length=1, - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/brand_response_authorization_result.py b/src/adcp/types/generated_poc/core/brand_response_authorization_result.py deleted file mode 100644 index 33a5de9f6..000000000 --- a/src/adcp/types/generated_poc/core/brand_response_authorization_result.py +++ /dev/null @@ -1,80 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/brand_response_authorization_result.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - - -class Trust(StrEnum): - trusted = 'trusted' - untrusted = 'untrusted' - - -class Reason(StrEnum): - brand_json_unavailable = 'brand_json_unavailable' - agent_not_authorized = 'agent_not_authorized' - agent_authorization_ambiguous = 'agent_authorization_ambiguous' - jwks_unavailable = 'jwks_unavailable' - kid_not_authorized = 'kid_not_authorized' - kid_authorization_ambiguous = 'kid_authorization_ambiguous' - key_material_mismatch = 'key_material_mismatch' - key_purpose_invalid = 'key_purpose_invalid' - - -class BrandResponseAuthorizationResult1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - trust: Annotated[ - Literal['trusted'], - Field( - description='Whether the signing key was bound to the asserted brand_domain through an authorized brand-agent entry. An untrusted result MUST NOT extend or revoke relationship trust on its own.' - ), - ] = 'trusted' - reason: Annotated[ - Reason | None, Field(description='Machine-readable reason for an untrusted result.') - ] = None - kid: Annotated[str, Field(description='JWS kid evaluated by the cross-check.', min_length=1)] - jwks_uri: Annotated[ - AnyUrl, - Field( - description='JWKS URI selected only from the matched brand.json agent entry, or its same-origin default.' - ), - ] - - -class BrandResponseAuthorizationResult2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - trust: Annotated[ - Literal['untrusted'], - Field( - description='Whether the signing key was bound to the asserted brand_domain through an authorized brand-agent entry. An untrusted result MUST NOT extend or revoke relationship trust on its own.' - ), - ] = 'untrusted' - reason: Annotated[Reason, Field(description='Machine-readable reason for an untrusted result.')] - kid: Annotated[ - str | None, Field(description='JWS kid evaluated by the cross-check.', min_length=1) - ] = None - jwks_uri: Annotated[ - AnyUrl | None, - Field( - description='JWKS URI selected only from the matched brand.json agent entry, or its same-origin default.' - ), - ] = None - - -BrandResponseAuthorizationResult = Annotated[ - BrandResponseAuthorizationResult1 | BrandResponseAuthorizationResult2, - Field( - description='Verifier-local result of the additive 3.2 brand.json authorization cross-check for a cryptographically valid verify_brand_claim or verify_brand_claims signed response. This object is produced by the verifier and is not a field asserted by the responding brand agent.', - title='Brand Response Authorization Result', - ), -] diff --git a/src/adcp/types/generated_poc/core/budget_allocation.py b/src/adcp/types/generated_poc/core/budget_allocation.py deleted file mode 100644 index 1a7055a44..000000000 --- a/src/adcp/types/generated_poc/core/budget_allocation.py +++ /dev/null @@ -1,360 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/budget_allocation.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictFloat - -from ..enums import event_type as event_type_1 -from ..enums import reach_unit as reach_unit_1 -from ..enums import viewability_standard -from . import attribution_window as attribution_window_1 -from . import brand_ref, duration, vendor_metric_id - - -class BudgetAllocation1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - mode: Annotated[ - Literal['fixed'], - Field( - description='Packages have independent budgets. The seller does not automatically move budget between packages.' - ), - ] = 'fixed' - - -class Metric(StrEnum): - clicks = 'clicks' - views = 'views' - completed_views = 'completed_views' - viewed_seconds = 'viewed_seconds' - viewable_rate = 'viewable_rate' - attention_seconds = 'attention_seconds' - attention_score = 'attention_score' - engagements = 'engagements' - follows = 'follows' - saves = 'saves' - profile_visits = 'profile_visits' - reach = 'reach' - - -class Target(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['cost_per'] = 'cost_per' - value: Annotated[ - StrictFloat, Field(description='Target cost per metric unit in the buy currency', gt=0.0) - ] - - -class Target3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['threshold_rate'] = 'threshold_rate' - value: Annotated[ - StrictFloat, - Field( - description='Minimum per-impression value. Units depend on the metric: proportion (clicks, views, completed_views, viewable_rate), seconds (viewed_seconds, attention_seconds), or score (attention_score).', - gt=0.0, - ), - ] - - -class Target4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['cost_per'] = 'cost_per' - value: Annotated[ - StrictFloat, Field(description='Target cost per event in the buy currency', gt=0.0) - ] - - -class Target5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['per_ad_spend'] = 'per_ad_spend' - value: Annotated[ - StrictFloat, - Field(description='Target return ratio (e.g., 4.0 means $4 of value per $1 spent)', gt=0.0), - ] - - -class Target6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['maximize_value'] = 'maximize_value' - - -class Target7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['cost_per'] = 'cost_per' - value: Annotated[ - StrictFloat, - Field( - description='Target cost per metric unit in the buy currency. Units of the metric are vendor-defined.', - gt=0.0, - ), - ] - - -class Target8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['threshold_rate'] = 'threshold_rate' - value: Annotated[ - StrictFloat, - Field( - description='Minimum per-impression value. Units of the metric are vendor-defined.', - gt=0.0, - ), - ] - - -class OptimizationGoal4(AdCPBaseModel): - pass - - -class TargetFrequency(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - min: Annotated[ - SchemaInt | None, - Field( - description='Minimum frequency for an entity to be considered meaningfully reached within the window. Impressions that would bring an entity below this threshold are treated as high-value (growing reach). When omitted, the seller uses their platform default (typically 1).', - ge=1, - ), - ] = None - max: Annotated[ - SchemaInt | None, - Field( - description='Frequency at which an entity is considered saturated within the window. Impressions toward entities at or above this threshold are treated as lower-value. When both min and max are present, max must be greater than or equal to min. When omitted, the seller determines the saturation point.', - ge=1, - ), - ] = None - window: Annotated[ - duration.Duration, - Field( - description='Time window over which frequency is measured (e.g. {"interval": 7, "unit": "days"} or {"interval": 1, "unit": "campaign"} for the full flight). Weekly windows are typical for brand campaigns; daily windows suit high-cadence direct response.' - ), - ] - - -class EventSource(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - event_source_id: Annotated[ - str, - Field( - description='Event source to include (must be configured on this account via sync_event_sources)', - min_length=1, - ), - ] - event_type: Annotated[ - event_type_1.EventType, - Field( - description='Event type to include from this source (e.g., purchase, lead, app_install, refund)' - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description="Required when event_type is 'custom'. Platform-specific name for the custom event." - ), - ] = None - value_field: Annotated[ - str | None, - Field( - description="Which field in the event's custom_data carries the monetary value. The seller must use this field for value extraction and aggregation when computing ROAS and conversion value metrics. Required on at least one entry when target.kind is 'per_ad_spend' or 'maximize_value' — sellers must reject these target kinds when no event source entry includes value_field. When canonical bidding.roas binds to this goal, every referenced value-bearing source MUST declare value_currencies containing the media-buy currency; the seller validates this at buy creation. Once that source contract is declared, log_event rejects monetary records whose currency is missing or absent from the list. For each buy, only records exactly matching its media-buy currency contribute to ROAS; records in other declared currencies remain available to matching buys, and sellers MUST NOT convert between them. When present without a value-oriented target, the seller may use it for delivery reporting but must not change the objective. Common values: 'value', 'order_total', 'profit_margin'. This is not passed as a parameter to underlying platform APIs — the seller maps it to their platform's value ingestion mechanism." - ), - ] = None - value_factor: Annotated[ - StrictFloat | None, - Field( - description="Unit-scaling multiplier the seller must apply to value_field before aggregation. Use -1 for refund events (negate the value), 0.01 for values in cents, -0.01 for refunds in cents. It MUST NOT be used for currency conversion. A value of 0 zeroes out this source's value contribution (the source still counts for event dedup). Defaults to 1. This is not passed as a parameter to underlying platform APIs — the seller applies it when computing aggregated value metrics." - ), - ] = 1 - - -class OptimizationGoal2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['event'] = 'event' - event_sources: Annotated[ - list[EventSource], - Field( - description='Event source and type pairs that feed this goal. Each entry identifies a source and event type to include. When the seller supports multi_source_event_dedup (declared in get_adcp_capabilities), they deduplicate by event_id across all entries — the same business event from multiple sources counts once, using value_field and value_factor from the first matching entry. When multi_source_event_dedup is false or absent, buyers should use a single entry per goal; the seller will use only the first entry. All event sources must be configured via sync_event_sources.', - min_length=1, - ), - ] - target: Annotated[ - Target4 | Target5 | Target6 | None, - Field( - description='Target cost or return for this event goal. When omitted, the seller optimizes for maximum conversion count within budget — regardless of whether value_field is present on event sources. The presence of value_field alone does not change the optimization objective; it only makes value available for reporting. An explicit target of maximize_value or per_ad_spend is required to steer toward value.', - discriminator='kind', - ), - ] = None - attribution_window: Annotated[ - attribution_window_1.AttributionWindow | None, - Field( - description="Attribution window for this optimization goal — references the canonical `attribution-window` shape (post_click, post_view, model). Values must match an option declared in the seller's `conversion_tracking.attribution_windows` capability. Sellers MUST reject windows not in their declared capabilities. When the entire field is omitted, the seller uses their default window." - ), - ] = None - priority: Annotated[ - SchemaInt | None, - Field( - description='Relative priority among sibling goals. Lower numbers rank first. Goals without priority follow explicitly prioritized goals. Ties use array order, so the earliest goal at the lowest explicit priority is primary; when all priorities are omitted, the first goal is primary.', - ge=1, - ), - ] = None - - -class OptimizationGoal6(OptimizationGoal2, OptimizationGoal4): - pass - - -class OptimizationGoal1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['metric'] = 'metric' - metric: Annotated[ - Metric, - Field( - description="Seller-native metric to optimize for. Delivery metrics: clicks (link clicks, swipe-throughs, CTA taps that navigate away), views (content views at the billable view threshold, as defined by delivery-metrics `views`; for viewability use viewable_rate), completed_views (video/audio completions — see view_duration_seconds), reach (unique audience reach — see reach_unit and target_frequency). Duration/score metrics: viewed_seconds (time in view per impression — reported back via `delivery-metrics.viewability.viewed_seconds`, governed by the viewability `standard`). Quality-rate metrics: viewable_rate (viewable / measurable impressions under the goal's `standard`; requires `standard`). Audience action metrics: engagements (any direct interaction with the ad unit beyond viewing — social reactions/comments/shares, story/unit opens, interactive overlay taps, companion banner interactions on audio and CTV), follows (new followers, page likes, artist/podcast/channel follows, or free channel/feed subscribes; paid subscriptions use event_type: subscribe), saves (saves, bookmarks, playlist adds, pins — signals of intent to return), profile_visits (visits to the brand's in-platform page — profile, artist page, channel, or storefront. Does not include external website clicks, which are covered by 'clicks'). **DEPRECATED values** (slated for removal at next major): `attention_seconds` and `attention_score` — these have no industry-graduated definition (DoubleVerify, IAS, Adelaide, TVision, Lumen each define them differently) and cannot be meaningfully optimized for without a vendor binding. Use `kind: 'vendor_metric'` with an explicit `vendor` and `metric_id` instead — that path binds the goal to a specific measurement vendor and reconciles to the same `(vendor, metric_id)` key in delivery's `vendor_metric_values[]`. Sellers MAY reject the deprecated values with `TERMS_REJECTED` and a suggestion to use the `vendor_metric` kind." - ), - ] - standard: Annotated[ - viewability_standard.ViewabilityStandard | None, - Field( - description="Viewability standard the goal is judged against. Required when metric is 'viewable_rate'; optional for 'viewed_seconds' (seller default standard when omitted); not allowed for other metrics. Must be in metric_optimization.supported_viewability_standards when declared. A goal below a same-standard viewability performance_standard never relaxes that standard." - ), - ] = None - vendor: Annotated[ - brand_ref.BrandReference | None, - Field( - description="Measurement vendor judging a viewable_rate or viewed_seconds goal; not allowed for other metrics. When omitted, the seller's default viewability measurement applies. Sellers MUST reject a vendor they cannot optimize against rather than substitute another." - ), - ] = None - reach_unit: Annotated[ - reach_unit_1.ReachUnit | None, - Field( - description="Unit for reach measurement. Required when metric is 'reach'. Must be a value declared in the product's metric_optimization.supported_reach_units." - ), - ] = None - target_frequency: Annotated[ - TargetFrequency | None, - Field( - description="Target frequency band for reach optimization. Only applicable when metric is 'reach'. Frames frequency as an optimization signal: the seller should treat impressions toward entities already within the [min, max] band as lower-value, and impressions toward unreached entities as higher-value. This shifts budget toward fresh reach rather than re-reaching known users. When omitted, the seller maximizes unique reach without a frequency constraint. A hard cap can still be layered via targeting_overlay.frequency_cap if a ceiling is needed." - ), - ] = None - view_duration_seconds: Annotated[ - StrictFloat | None, - Field( - description="Minimum video view duration in seconds that qualifies as a completed_view for this goal. Only applicable when metric is 'completed_views'. When omitted, the seller uses their platform default (typically 2–15 seconds). Common values: 2 (Snap/LinkedIn default), 6 (TikTok), 15 (Snap 15-second views, Meta ThruPlay). Sellers declare which durations they support in metric_optimization.supported_view_durations. Sellers must reject goals with unsupported values — silent rounding would create measurement discrepancies.", - gt=0.0, - ), - ] = None - target: Annotated[ - Target | Target3 | None, - Field( - description='Target for this metric. When omitted, the seller optimizes for maximum metric volume within budget.', - discriminator='kind', - ), - ] = None - priority: Annotated[ - SchemaInt | None, - Field( - description='Relative priority among sibling goals. Lower numbers rank first. Goals without priority follow explicitly prioritized goals. Ties use array order, so the earliest goal at the lowest explicit priority is primary; when all priorities are omitted, the first goal is primary.', - ge=1, - ), - ] = None - - -class OptimizationGoal3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['vendor_metric'] = 'vendor_metric' - vendor: Annotated[ - brand_ref.BrandReference, - Field( - description='Vendor that defines and computes this metric. Same shape as `vendor_metric_values.vendor`, `reporting_capabilities.vendor_metrics[].vendor`, and `vendor_metric_optimization.supported_metrics[].vendor` — symmetric across discovery, capability, commitment, optimization, and reporting surfaces.' - ), - ] - metric_id: Annotated[ - vendor_metric_id.VendorMetricId, - Field( - description="Identifier for the metric within the vendor's vocabulary (e.g., `attention_score`, `attention_seconds`, `gco2e_per_impression`, `awareness_lift`). MUST be present in the vendor's published `measurement.metrics[]` catalog and in the product's `vendor_metric_optimization.supported_metrics[]`." - ), - ] - target: Annotated[ - Target7 | Target8 | None, - Field( - description="Target for this vendor metric. When omitted, the seller optimizes for maximum metric volume / score within budget. `cost_per` and `threshold_rate` semantics mirror the same target kinds on the `metric` kind — units are vendor-defined and depend on the vendor's `measurement.metrics[]` declaration for this `metric_id`.", - discriminator='kind', - ), - ] = None - priority: Annotated[ - SchemaInt | None, - Field( - description='Relative priority among sibling goals. Lower numbers rank first. Goals without priority follow explicitly prioritized goals. Ties use array order, so the earliest goal at the lowest explicit priority is primary; when all priorities are omitted, the first goal is primary.', - ge=1, - ), - ] = None - - -class OptimizationGoal5(OptimizationGoal1, OptimizationGoal4): - pass - - -class OptimizationGoal7(OptimizationGoal3, OptimizationGoal4): - pass - - -OptimizationGoal = OptimizationGoal5 | OptimizationGoal6 | OptimizationGoal7 - - -class BudgetAllocation2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - mode: Annotated[ - Literal['seller_optimized'], - Field( - description='Packages draw from a shared media-buy budget. The seller continuously allocates spend across packages to optimize the declared goals.' - ), - ] = 'seller_optimized' - optimization_goals: Annotated[ - list[OptimizationGoal], - Field( - description='Goals the seller uses to allocate the shared budget across packages. These are distinct from packages[].optimization_goals, which optimize delivery within an individual package. The primary goal is the earliest array entry among goals with the lowest explicit numeric priority; goals without priority follow all explicitly prioritized goals; when all priorities are omitted, the first entry is primary. It supplies the result unit/value source for media-buy bidding.cost_per or bidding.roas. Legacy monetary target kinds are prohibited at allocation scope because canonical controls belong in media-buy bidding. Every participating product MUST support the primary cross-package goal; otherwise the seller MUST reject the request with TERMS_REJECTED or UNSUPPORTED_FEATURE and identify the incompatible package.', - min_length=1, - ), - ] - - -BudgetAllocation = Annotated[ - BudgetAllocation1 | BudgetAllocation2, - Field( - description='How a media buy\'s total budget is allocated across its packages. Omission on a request means fixed allocation (legacy-compatible); buyer agents SHOULD send this block explicitly ({mode: "fixed"} or seller_optimized) rather than rely on omission, and SHOULD ask the principal rather than guess when the instruction does not determine whether the budget is one shared pool or split per package. Fixed allocation preserves independent package budgets. Seller-optimized allocation delegates continuous cross-package allocation to the seller within the media-buy total, package caps, minimum-spend targets, flight windows, and pacing controls. The core seller-optimized contract is gated by media_buy.features.seller_optimized_budget; package caps, minimum-spend targets, and package pacing are separately gated by seller_optimized_package_budgets, seller_optimized_min_spend_targets, and seller_optimized_package_pacing; an undeclared package control is rejected with UNSUPPORTED_FEATURE before any over-subscription validation. Core seller-optimized pacing guarantees only omitted or `even` media-buy pacing; sellers MAY reject `asap` or `front_loaded` with UNSUPPORTED_FEATURE and MUST NOT coerce them to `even`. When a seller-optimized media-buy BiddingPolicy carries cost_per or roas, that control binds to this allocation block\'s primary optimization goal rather than independently to each package goal.', - discriminator='mode', - title='Budget Allocation', - ), -] diff --git a/src/adcp/types/generated_poc/core/budget_range.py b/src/adcp/types/generated_poc/core/budget_range.py deleted file mode 100644 index 986c3896a..000000000 --- a/src/adcp/types/generated_poc/core/budget_range.py +++ /dev/null @@ -1,32 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/budget_range.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictFloat, model_validator - - -class BudgetRange(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - min: Annotated[StrictFloat | None, Field(ge=0.0)] = None - max: Annotated[StrictFloat | None, Field(ge=0.0)] = None - currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] - - @model_validator(mode='after') - def _require_schema_required_group(self) -> BudgetRange: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('min',), ('max',),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'BudgetRange requires at least one of these field groups: min | max' - ) diff --git a/src/adcp/types/generated_poc/core/business_entity.py b/src/adcp/types/generated_poc/core/business_entity.py deleted file mode 100644 index 8d5f43ecb..000000000 --- a/src/adcp/types/generated_poc/core/business_entity.py +++ /dev/null @@ -1,129 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/business_entity.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, EmailStr, Field - -from . import ext as ext_1 - - -class Address(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - street: Annotated[ - str, Field(description='Street address including building number', max_length=200) - ] - city: Annotated[str, Field(max_length=100)] - postal_code: Annotated[str, Field(max_length=20)] - region: Annotated[ - str | None, Field(description='State, province, or region', max_length=100) - ] = None - country: Annotated[ - str, Field(description='ISO 3166-1 alpha-2 country code', pattern='^[A-Z]{2}$') - ] - - -class Role(StrEnum): - billing = 'billing' - legal = 'legal' - creative = 'creative' - general = 'general' - - -class Contact(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - role: Annotated[ - Role, Field(description="Contact's functional role in the business relationship") - ] - name: Annotated[str | None, Field(description='Full name of the contact', max_length=200)] = ( - None - ) - email: Annotated[EmailStr | None, Field(max_length=254)] = None - phone: Annotated[str | None, Field(max_length=30)] = None - - -class Bank(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_holder: Annotated[str, Field(description='Name on the bank account', max_length=200)] - iban: Annotated[ - str | None, - Field( - description='International Bank Account Number (SEPA markets)', - pattern='^[A-Z]{2}[0-9]{2}[A-Z0-9]{4,30}$', - ), - ] = None - bic: Annotated[ - str | None, - Field( - description='Bank Identifier Code / SWIFT code (SEPA markets)', - pattern='^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$', - ), - ] = None - routing_number: Annotated[ - str | None, - Field( - description='Bank routing number for non-SEPA markets (e.g., US ABA routing number, Canadian transit/institution number)', - max_length=30, - ), - ] = None - account_number: Annotated[ - str | None, Field(description='Bank account number for non-SEPA markets', max_length=30) - ] = None - - -class BusinessEntity(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - legal_name: Annotated[ - str, Field(description='Registered legal name of the business entity', max_length=200) - ] - vat_id: Annotated[ - str | None, - Field( - description='VAT identification number (e.g., DE123456789 for Germany, FR12345678901 for France). Required for B2B invoicing in the EU. Must be normalized: no spaces, dots, or dashes.', - pattern='^[A-Z]{2}[A-Z0-9]{2,13}$', - ), - ] = None - tax_id: Annotated[ - str | None, - Field( - description='Tax identification number for jurisdictions that do not use VAT (e.g., US EIN)', - max_length=30, - ), - ] = None - registration_number: Annotated[ - str | None, - Field( - description='Company registration number (e.g., HRB 12345 for German Handelsregister)', - max_length=50, - ), - ] = None - address: Annotated[ - Address | None, Field(description='Postal address for invoicing and legal correspondence') - ] = None - contacts: Annotated[ - list[Contact] | None, - Field( - description='Contacts for billing, legal, and operational matters. Contains personal data subject to GDPR and equivalent regulations. Implementations MUST use this data only for invoicing and account management.', - max_length=10, - ), - ] = None - bank: Annotated[ - Bank | None, - Field( - description='Bank account details for payment processing. Write-only: included in requests to provide payment coordinates, but MUST NOT be echoed in responses. Sellers store these details and confirm receipt without returning them.' - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/cancellation_policy.py b/src/adcp/types/generated_poc/core/cancellation_policy.py deleted file mode 100644 index a053919fe..000000000 --- a/src/adcp/types/generated_poc/core/cancellation_policy.py +++ /dev/null @@ -1,62 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/cancellation_policy.json -# timestamp: 2026-10-03T15:09:44+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictFloat - -from . import duration - - -class Type(StrEnum): - percent_remaining = 'percent_remaining' - full_commitment = 'full_commitment' - fixed_fee = 'fixed_fee' - none = 'none' - - -class CancellationFee(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Type, - Field( - description="Fee calculation method. 'percent_remaining': percentage of remaining uncommitted spend. 'full_commitment': buyer owes the full committed budget regardless of delivery. 'fixed_fee': flat monetary amount. 'none': no financial fee (cancellation with notice is free)." - ), - ] - rate: Annotated[ - StrictFloat | None, - Field( - description="Fee rate as a decimal proportion of remaining committed spend. Required when type is 'percent_remaining' (e.g., 0.5 means 50% of remaining spend).", - ge=0.0, - le=1.0, - ), - ] = None - amount: Annotated[ - StrictFloat | None, - Field( - description="Fixed fee amount in the buy's currency. Required when type is 'fixed_fee'.", - ge=0.0, - ), - ] = None - - -class CancellationPolicy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - notice_period: Annotated[ - duration.Duration, - Field( - description="Minimum notice period before cancellation takes effect (e.g., { interval: 30, unit: 'days' }). A guaranteed buy canceled without sufficient notice incurs the declared cancellation fee." - ), - ] - cancellation_fee: Annotated[ - CancellationFee, Field(description='Fee applied when the notice period is not met.') - ] diff --git a/src/adcp/types/generated_poc/core/canonical_account_ref.py b/src/adcp/types/generated_poc/core/canonical_account_ref.py deleted file mode 100644 index e8972c2a6..000000000 --- a/src/adcp/types/generated_poc/core/canonical_account_ref.py +++ /dev/null @@ -1,70 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/canonical_account_ref.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictBool - -from . import brand_key -from . import operator_unit as operator_unit_1 - - -class CanonicalAccountReference1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[str, Field(min_length=1)] - - -class CanonicalAccountReference2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brand: brand_key.BrandKey - operator: Annotated[ - str, Field(pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$') - ] - operator_unit: Annotated[ - operator_unit_1.OperatorUnit | None, - Field( - description='Optional operator-owned business unit, agency seat, or platform account. Only id participates in identity; name is mutable display metadata.' - ), - ] = None - currency: Annotated[ - str | None, - Field( - description='Immutable ISO 4217 transaction currency when the advertiser object is currency-bound. When present, this is part of the natural key and all media buys on the account use it. Omit for per-media-buy currency selection.', - pattern='^[A-Z]{3}$', - ), - ] = None - timezone: Annotated[ - str | None, - Field( - description='Immutable account timezone. Include it in the natural key only for buyer-selected account_fixed provisioning.', - min_length=1, - ), - ] = None - sandbox: StrictBool | None = False - - -CanonicalAccountReference = Annotated[ - CanonicalAccountReference1 | CanonicalAccountReference2, - Field( - description='Compact advertiser-account identity for canonical 3.2 tools. Uses either the seller/storefront account_id returned by list_accounts or the buyer-declared natural key without BrandRef inline overrides. The natural key is brand + operator + optional operator_unit.id + optional fixed currency + optional buyer-selected account timezone + sandbox. brand.countries qualifies the commercial advertiser identity without targeting delivery; operator_unit identifies the operator-owned buying context. operator_unit.name is display metadata and is excluded from identity.', - examples=[ - {'account_id': 'acc_nova_nl'}, - { - 'brand': {'domain': 'nova-athletics.example', 'countries': ['NL']}, - 'operator': 'nova-athletics.example', - 'operator_unit': {'id': '234284238', 'name': 'Nova EMEA'}, - 'currency': 'EUR', - 'timezone': 'Europe/Amsterdam', - }, - ], - title='Canonical Account Reference', - ), -] diff --git a/src/adcp/types/generated_poc/core/canonical_audience_evidence.py b/src/adcp/types/generated_poc/core/canonical_audience_evidence.py deleted file mode 100644 index 3b07cb61a..000000000 --- a/src/adcp/types/generated_poc/core/canonical_audience_evidence.py +++ /dev/null @@ -1,82 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/canonical_audience_evidence.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarStr -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictFloat - -from ..enums import audience_evidence_methodology, audience_resolution_method, audience_subject_type -from . import audience_characteristic, brand_key, date_range -from . import ext as ext_1 - - -class Relationship(StrEnum): - composition = 'composition' - index = 'index' # type: ignore[assignment] - reach_estimate = 'reach_estimate' - - -class Unit(StrEnum): - fraction = 'fraction' - ratio = 'ratio' - count = 'count' # type: ignore[assignment] - - -class Baseline(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - system: AnyUrl - population_id: Annotated[str, Field(min_length=1)] - version: Annotated[str, Field(min_length=1)] - description: str | None = None - - -class EvidenceType(StrEnum): - measured = 'measured' - forecast = 'forecast' - seller_declared = 'seller_declared' - - -class AttestationDigest(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^sha256:[a-f0-9]{64}$'} - - -class CanonicalAudienceEvidence(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - evidence_id: Annotated[str, Field(min_length=1)] - snapshot_id: Annotated[str, Field(min_length=1)] - version: Annotated[str, Field(min_length=1)] - content_digest: Annotated[str, Field(pattern='^sha256:[a-f0-9]{64}$')] - audience: audience_characteristic.AudienceCharacteristic - relationship: Relationship - value: Annotated[StrictFloat, Field(ge=0.0)] - unit: Unit - baseline: Baseline - evidence_type: EvidenceType - methodology: audience_evidence_methodology.AudienceEvidenceMethodology - subject_type: audience_subject_type.AudienceSubjectType - resolution_method: audience_resolution_method.AudienceResolutionMethod | None = None - provider: brand_key.BrandKey - measurement_window: date_range.DateRange - sample_size: Annotated[SchemaInt | None, Field(ge=1)] = None - confidence: Annotated[StrictFloat | None, Field(ge=0.0, le=1.0)] = None - last_updated: AwareDatetime - methodology_url: AnyUrl | None = None - attestation_digests: Annotated[ - list[AttestationDigest] | None, - Field( - description='Portable-attestation reference digests available through the evidence provider; credential bodies are not inlined into product discovery.', - min_length=1, - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/canonical_audience_evidence_selection.py b/src/adcp/types/generated_poc/core/canonical_audience_evidence_selection.py deleted file mode 100644 index 870e13462..000000000 --- a/src/adcp/types/generated_poc/core/canonical_audience_evidence_selection.py +++ /dev/null @@ -1,42 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/canonical_audience_evidence_selection.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field - -from . import canonical_audience_evidence -from . import ext as ext_1 - - -class DecisionUse(StrEnum): - recommendation = 'recommendation' - eligibility = 'eligibility' - package_construction = 'package_construction' - - -class VerifiedAttestationDigest(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^sha256:[a-f0-9]{64}$'} - - -class CanonicalAudienceEvidenceSelection(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - evidence_id: Annotated[str, Field(min_length=1)] - snapshot_id: Annotated[str, Field(min_length=1)] - version: Annotated[str, Field(min_length=1)] - content_digest: Annotated[str, Field(pattern='^sha256:[a-f0-9]{64}$')] - decision_use: DecisionUse - evidence: canonical_audience_evidence.CanonicalAudienceEvidence | None = None - verified_attestation_digests: Annotated[ - list[VerifiedAttestationDigest] | None, Field(min_length=1) - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/canonical_budget_allocation.py b/src/adcp/types/generated_poc/core/canonical_budget_allocation.py deleted file mode 100644 index 1cd3fc12f..000000000 --- a/src/adcp/types/generated_poc/core/canonical_budget_allocation.py +++ /dev/null @@ -1,39 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/canonical_budget_allocation.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from . import canonical_optimization_goal - - -class CanonicalBudgetAllocation1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - mode: Literal['fixed'] = 'fixed' - - -class CanonicalBudgetAllocation2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - mode: Literal['seller_optimized'] = 'seller_optimized' - optimization_goals: Annotated[ - list[canonical_optimization_goal.CanonicalOptimizationGoal], Field(min_length=1) - ] - - -CanonicalBudgetAllocation = Annotated[ - CanonicalBudgetAllocation1 | CanonicalBudgetAllocation2, - Field( - description='Canonical fixed or seller-optimized allocation using only canonical optimization goals.', - discriminator='mode', - title='Canonical Budget Allocation', - ), -] diff --git a/src/adcp/types/generated_poc/core/canonical_delivery_forecast.py b/src/adcp/types/generated_poc/core/canonical_delivery_forecast.py deleted file mode 100644 index fbf98ddee..000000000 --- a/src/adcp/types/generated_poc/core/canonical_delivery_forecast.py +++ /dev/null @@ -1,34 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/canonical_delivery_forecast.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - -from ..enums import demographic_system as demographic_system_1 -from ..enums import forecast_method -from ..enums import forecast_range_unit as forecast_range_unit_1 -from ..enums import reach_unit as reach_unit_1 -from . import canonical_forecast_point -from . import ext as ext_1 - - -class CanonicalDeliveryForecast(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - points: Annotated[list[canonical_forecast_point.CanonicalForecastPoint], Field(min_length=1)] - forecast_range_unit: forecast_range_unit_1.ForecastRangeUnit | None = None - method: forecast_method.ForecastMethod - currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] - demographic_system: demographic_system_1.DemographicSystem | None = None - demographic: str | None = None - measurement_source: Annotated[str | None, Field(max_length=64, pattern='^[a-z0-9_]+$')] = None - reach_unit: reach_unit_1.ReachUnit | None = None - generated_at: AwareDatetime | None = None - valid_until: AwareDatetime | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/canonical_forecast_point.py b/src/adcp/types/generated_poc/core/canonical_forecast_point.py deleted file mode 100644 index 2513975a5..000000000 --- a/src/adcp/types/generated_poc/core/canonical_forecast_point.py +++ /dev/null @@ -1,58 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/canonical_forecast_point.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Dict - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictFloat - -from ..enums import availability_status as availability_status_1 -from ..enums import viewability_standard -from . import ( - brand_key, - canonical_forecast_vendor_metric_value, - forecast_point_dimensions, - forecast_range, - forecast_rate_range, -) - - -class Metrics(AdCPBaseModel): - __pydantic_extra__: Dict[str, forecast_range.ForecastRange] - model_config = ConfigDict( - extra='allow', - ) - coverage_rate: forecast_rate_range.ForecastRateRange | None = None - - - - -class Viewability(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: brand_key.BrandKey | None = None - measurable_impressions: forecast_range.ForecastRange | None = None - viewable_impressions: forecast_range.ForecastRange | None = None - viewable_rate: forecast_rate_range.ForecastRateRange | None = None - viewed_seconds: forecast_range.ForecastRange | None = None - standard: viewability_standard.ViewabilityStandard | None = None - - -class CanonicalForecastPoint(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - label: Annotated[str | None, Field(max_length=128)] = None - budget: Annotated[StrictFloat | None, Field(ge=0.0)] = None - product_id: str | None = None - dimensions: forecast_point_dimensions.ForecastPointDimensions | None = None - availability_status: availability_status_1.AvailabilityStatus | None = None - metrics: Metrics - viewability: Viewability | None = None - vendor_metric_values: ( - list[canonical_forecast_vendor_metric_value.CanonicalForecastVendorMetricValue] | None - ) = None diff --git a/src/adcp/types/generated_poc/core/canonical_forecast_vendor_metric_value.py b/src/adcp/types/generated_poc/core/canonical_forecast_vendor_metric_value.py deleted file mode 100644 index 0132ddf65..000000000 --- a/src/adcp/types/generated_poc/core/canonical_forecast_vendor_metric_value.py +++ /dev/null @@ -1,24 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/canonical_forecast_vendor_metric_value.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Any - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict - -from . import brand_key, forecast_range, vendor_metric_id - - -class CanonicalForecastVendorMetricValue(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: brand_key.BrandKey - metric_id: vendor_metric_id.VendorMetricId - value: forecast_range.ForecastRange - unit: str | None = None - measurable_impressions: forecast_range.ForecastRange | None = None - breakdown: dict[str, Any] | None = None diff --git a/src/adcp/types/generated_poc/core/canonical_format_kind.py b/src/adcp/types/generated_poc/core/canonical_format_kind.py deleted file mode 100644 index e0039d139..000000000 --- a/src/adcp/types/generated_poc/core/canonical_format_kind.py +++ /dev/null @@ -1,26 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/canonical_format_kind.json -# timestamp: 2026-09-01T19:37:00+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class CanonicalFormatKind(StrEnum): - image = 'image' - html5 = 'html5' - display_tag = 'display_tag' - image_carousel = 'image_carousel' - video_hosted = 'video_hosted' - video_vast = 'video_vast' - audio_hosted = 'audio_hosted' - audio_vast = 'audio_vast' - audio_daast = 'audio_daast' - sponsored_placement = 'sponsored_placement' - native_in_feed = 'native_in_feed' - responsive_creative = 'responsive_creative' - agent_placement = 'agent_placement' - seller_rendered_stateful_display = 'seller_rendered_stateful_display' - coordinated_placements = 'coordinated_placements' - custom = 'custom' diff --git a/src/adcp/types/generated_poc/core/canonical_format_option.py b/src/adcp/types/generated_poc/core/canonical_format_option.py deleted file mode 100644 index 85983d6f1..000000000 --- a/src/adcp/types/generated_poc/core/canonical_format_option.py +++ /dev/null @@ -1,79 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/canonical_format_option.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, StrictBool - -from ..enums import channels -from . import creative_locale_policy, macro_resolution_capability, platform_extension_ref -from . import tracker_execution_contract as tracker_execution_contract_1 - - -class SellerPreference(StrEnum): - preferred = 'preferred' - accepted = 'accepted' - discouraged = 'discouraged' - - -class FormatKind(StrEnum): - image = 'image' - html5 = 'html5' - display_tag = 'display_tag' - image_carousel = 'image_carousel' - video_hosted = 'video_hosted' - video_vast = 'video_vast' - audio_hosted = 'audio_hosted' - audio_vast = 'audio_vast' - audio_daast = 'audio_daast' - sponsored_placement = 'sponsored_placement' - native_in_feed = 'native_in_feed' - responsive_creative = 'responsive_creative' - agent_placement = 'agent_placement' - custom = 'custom' - - -class CanonicalFormatOption(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - format_option_id: Annotated[str | None, Field(min_length=1)] = None - publisher_domain: Annotated[ - str | None, - Field(pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$'), - ] = None - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored production commitment for first-class manifest tracker execution. Presence requires a stable format_option_id. Creative-agent projections MUST strip this authority-bearing field.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='When true, every technical creative-acceptance constraint for this declaration layer is expressed and omitted technical fields mean no constraint. When false or absent, omitted technical fields are undeclared and MUST NOT be inferred. The effective contract is complete only when every applicable declaration layer asserts true.' - ), - ] = None - display_name: Annotated[str | None, Field(min_length=1)] = None - sample_render_url: AnyUrl | None = None - applies_to_channels: Annotated[list[channels.MediaChannel] | None, Field(min_length=1)] = None - seller_preference: SellerPreference | None = None - locale_policy: creative_locale_policy.CreativeLocalePolicy | None = None - canonical_formats_only: StrictBool | None = False - experimental: StrictBool | None = False - format_kind: FormatKind - params: dict[str, Any] - format_shape: Annotated[str | None, Field(min_length=1)] = None - format_schema: platform_extension_ref.PlatformExtensionReference | None = None diff --git a/src/adcp/types/generated_poc/core/canonical_measurement_terms.py b/src/adcp/types/generated_poc/core/canonical_measurement_terms.py deleted file mode 100644 index 96c9c47b0..000000000 --- a/src/adcp/types/generated_poc/core/canonical_measurement_terms.py +++ /dev/null @@ -1,38 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/canonical_measurement_terms.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictFloat - -from ..enums import makegood_remedy -from . import brand_key - - -class BillingMeasurement(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: brand_key.BrandKey - max_variance_percent: Annotated[StrictFloat | None, Field(ge=0.0, lt=100.0)] = None - measurement_window: Annotated[str | None, Field(min_length=1)] = None - finalization_deadline_hours: Annotated[SchemaInt | None, Field(ge=0)] = None - - -class MakegoodPolicy(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - available_remedies: Annotated[list[makegood_remedy.MakegoodRemedy], Field(min_length=1)] - - -class CanonicalMeasurementTerms(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - billing_measurement: BillingMeasurement | None = None - makegood_policy: MakegoodPolicy | None = None diff --git a/src/adcp/types/generated_poc/core/canonical_media_buy_action.py b/src/adcp/types/generated_poc/core/canonical_media_buy_action.py deleted file mode 100644 index 75b00f1c0..000000000 --- a/src/adcp/types/generated_poc/core/canonical_media_buy_action.py +++ /dev/null @@ -1,84 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/canonical_media_buy_action.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from pydantic import Field - -from .canonical_media_buy_action_fields import CanonicalMediaBuyActionFields - - -class Action(StrEnum): - pause = 'pause' - resume = 'resume' - cancel = 'cancel' - update_name = 'update_name' - increase_budget = 'increase_budget' - decrease_budget = 'decrease_budget' - reallocate_budget = 'reallocate_budget' - update_budget_allocation = 'update_budget_allocation' - update_targeting = 'update_targeting' - update_pacing = 'update_pacing' - update_bidding = 'update_bidding' - update_frequency_caps = 'update_frequency_caps' - update_media_buy_frequency_cap = 'update_media_buy_frequency_cap' - update_catalog_assignments = 'update_catalog_assignments' - update_keywords = 'update_keywords' - update_optimization_goals = 'update_optimization_goals' - update_impression_goal = 'update_impression_goal' - update_spend_target = 'update_spend_target' - update_reporting_webhook = 'update_reporting_webhook' - remove_packages = 'remove_packages' - - -class Action3(StrEnum): - cancel = 'cancel' - extend_flight = 'extend_flight' - shorten_flight = 'shorten_flight' - update_flight_dates = 'update_flight_dates' - increase_budget = 'increase_budget' - decrease_budget = 'decrease_budget' - reallocate_budget = 'reallocate_budget' - update_budget_allocation = 'update_budget_allocation' - update_targeting = 'update_targeting' - update_pacing = 'update_pacing' - update_bidding = 'update_bidding' - update_frequency_caps = 'update_frequency_caps' - update_media_buy_frequency_cap = 'update_media_buy_frequency_cap' - add_packages = 'add_packages' - remove_packages = 'remove_packages' - - -class Action4(StrEnum): - replace_creative = 'replace_creative' - update_creative_assignments = 'update_creative_assignments' - remove_creative = 'remove_creative' - - -class CanonicalMediaBuyAction1(CanonicalMediaBuyActionFields): - task: Literal['control_media_buy'] = 'control_media_buy' - action: Action - - -class CanonicalMediaBuyAction2(CanonicalMediaBuyActionFields): - task: Literal['refine_proposals'] = 'refine_proposals' - action: Action3 - - -class CanonicalMediaBuyAction3(CanonicalMediaBuyActionFields): - task: Literal['sync_creatives'] = 'sync_creatives' - action: Action4 | None = None - - -CanonicalMediaBuyAction = Annotated[ - CanonicalMediaBuyAction1 | CanonicalMediaBuyAction2 | CanonicalMediaBuyAction3, - Field( - description='A currently available MediaBuy action with the compact-lifecycle task an SDK calls to exercise it. Deprecated coarse action values are absent.', - discriminator='task', - title='Canonical Media Buy Action', - ), -] diff --git a/src/adcp/types/generated_poc/core/canonical_media_buy_action_fields.py b/src/adcp/types/generated_poc/core/canonical_media_buy_action_fields.py deleted file mode 100644 index 278979e8b..000000000 --- a/src/adcp/types/generated_poc/core/canonical_media_buy_action_fields.py +++ /dev/null @@ -1,44 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/canonical_media_buy_action_fields.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..enums import canonical_media_buy_action_mode -from . import ( - applicable_package_id, - media_buy_change_term_id, - media_buy_legacy_terms_ref, - sla_window, -) - - -class Task(StrEnum): - control_media_buy = 'control_media_buy' - refine_proposals = 'refine_proposals' - sync_creatives = 'sync_creatives' - - -class CanonicalMediaBuyActionFields(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - task: Task - action: str - mode: canonical_media_buy_action_mode.CanonicalMediaBuyActionMode - sla: sla_window.SlaWindow | None = None - change_term_id: media_buy_change_term_id.MediaBuyChangeTermId | None = None - terms_ref: media_buy_legacy_terms_ref.MediaBuyTermsReference | None = None - applicable_package_ids: Annotated[ - list[applicable_package_id.ApplicablePackageId] | None, - Field( - description='Exact eligible packages for a package-scoped action; omission means all relevant packages.', - min_length=1, - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/canonical_media_buy_features.py b/src/adcp/types/generated_poc/core/canonical_media_buy_features.py deleted file mode 100644 index 75711473a..000000000 --- a/src/adcp/types/generated_poc/core/canonical_media_buy_features.py +++ /dev/null @@ -1,52 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/canonical_media_buy_features.json -# timestamp: 2026-10-03T15:09:44+00:00 - -from __future__ import annotations - -from typing import Annotated, Dict - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictBool - -from . import bidding_policy_capability - - -class CanonicalMediaBuyFeatures(AdCPBaseModel): - __pydantic_extra__: Dict[str, StrictBool] - model_config = ConfigDict( - extra='allow', - ) - property_filtering: StrictBool | None = None - catalog_management: StrictBool | None = None - reporting_commitment_snapshots: Annotated[ - StrictBool | None, - Field( - description='Seller preserves the accepted per-purchase reporting contract and exposes it on proposal and MediaBuy readback.' - ), - ] = None - seller_optimized_budget: Annotated[ - StrictBool | None, - Field( - description='Core seller-optimized shared-budget contract: shared total_budget, seller allocation across packages, media-buy pacing, and allocation echo.' - ), - ] = None - seller_optimized_package_budgets: Annotated[ - StrictBool | None, - Field( - description='Package budget caps inside seller-optimized buys; implies seller_optimized_budget.' - ), - ] = None - seller_optimized_min_spend_targets: Annotated[ - StrictBool | None, - Field( - description='Package minimum-spend targets inside seller-optimized buys; implies seller_optimized_budget.' - ), - ] = None - seller_optimized_package_pacing: Annotated[ - StrictBool | None, - Field( - description='Package pacing inside seller-optimized buys; implies seller_optimized_budget.' - ), - ] = None - bidding_policy: bidding_policy_capability.BiddingPolicyCapability | None = None diff --git a/src/adcp/types/generated_poc/core/canonical_metric_qualifier.py b/src/adcp/types/generated_poc/core/canonical_metric_qualifier.py deleted file mode 100644 index c58737f95..000000000 --- a/src/adcp/types/generated_poc/core/canonical_metric_qualifier.py +++ /dev/null @@ -1,25 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/canonical_metric_qualifier.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict - -from ..enums import attribution_methodology as attribution_methodology_1 -from ..enums import completion_source as completion_source_1 -from ..enums import lift_dimension as lift_dimension_1 -from ..enums import viewability_standard as viewability_standard_1 -from . import duration - - -class CanonicalMetricQualifier(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - viewability_standard: viewability_standard_1.ViewabilityStandard | None = None - completion_source: completion_source_1.CompletionSource | None = None - attribution_methodology: attribution_methodology_1.AttributionMethodology | None = None - attribution_window: duration.Duration | None = None - lift_dimension: lift_dimension_1.LiftDimension | None = None diff --git a/src/adcp/types/generated_poc/core/canonical_optimization_goal.py b/src/adcp/types/generated_poc/core/canonical_optimization_goal.py deleted file mode 100644 index 200de5597..000000000 --- a/src/adcp/types/generated_poc/core/canonical_optimization_goal.py +++ /dev/null @@ -1,116 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/canonical_optimization_goal.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictFloat - -from ..enums import event_type as event_type_1 -from ..enums import reach_unit as reach_unit_1 -from ..enums import viewability_standard -from . import attribution_window as attribution_window_1 -from . import brand_key, duration, vendor_metric_id - - -class Metric(StrEnum): - clicks = 'clicks' - views = 'views' - completed_views = 'completed_views' - viewed_seconds = 'viewed_seconds' - viewable_rate = 'viewable_rate' - engagements = 'engagements' - follows = 'follows' - saves = 'saves' - profile_visits = 'profile_visits' - reach = 'reach' - - -class Target(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['threshold_rate'] = 'threshold_rate' - value: Annotated[StrictFloat, Field(gt=0.0)] - - -class Target10(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['maximize_value'] = 'maximize_value' - - -class Target11(Target): - pass - - -class TargetFrequency(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - min: Annotated[SchemaInt | None, Field(ge=1)] = None - max: Annotated[SchemaInt | None, Field(ge=1)] = None - window: duration.Duration - - -class CanonicalOptimizationGoal1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['metric'] = 'metric' - metric: Metric - standard: viewability_standard.ViewabilityStandard | None = None - vendor: brand_key.BrandKey | None = None - reach_unit: reach_unit_1.ReachUnit | None = None - target_frequency: TargetFrequency | None = None - view_duration_seconds: Annotated[StrictFloat | None, Field(gt=0.0)] = None - target: Target | None = None - priority: Annotated[SchemaInt | None, Field(ge=1)] = None - - -class EventSource(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - event_source_id: Annotated[str, Field(min_length=1)] - event_type: event_type_1.EventType - custom_event_name: Annotated[str | None, Field(min_length=1)] = None - value_field: Annotated[str | None, Field(min_length=1)] = None - value_factor: StrictFloat | None = 1 - - -class CanonicalOptimizationGoal2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['event'] = 'event' - event_sources: Annotated[list[EventSource], Field(min_length=1)] - target: Target10 | None = None - attribution_window: attribution_window_1.AttributionWindow | None = None - priority: Annotated[SchemaInt | None, Field(ge=1)] = None - - -class CanonicalOptimizationGoal3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['vendor_metric'] = 'vendor_metric' - vendor: brand_key.BrandKey - metric_id: vendor_metric_id.VendorMetricId - target: Target11 | None = None - priority: Annotated[SchemaInt | None, Field(ge=1)] = None - - -CanonicalOptimizationGoal = Annotated[ - CanonicalOptimizationGoal1 | CanonicalOptimizationGoal2 | CanonicalOptimizationGoal3, - Field( - description='Canonical 3.2 optimization objective. Monetary execution policy belongs in BiddingPolicy; legacy monetary targets and unbound attention metrics are excluded.', - discriminator='kind', - title='Canonical Optimization Goal', - ), -] diff --git a/src/adcp/types/generated_poc/core/canonical_performance_standard.py b/src/adcp/types/generated_poc/core/canonical_performance_standard.py deleted file mode 100644 index 193960353..000000000 --- a/src/adcp/types/generated_poc/core/canonical_performance_standard.py +++ /dev/null @@ -1,23 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/canonical_performance_standard.json -# timestamp: 2026-10-03T15:09:44+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictFloat - -from ..enums import performance_standard_metric, viewability_standard -from . import brand_key - - -class CanonicalPerformanceStandard(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - metric: performance_standard_metric.PerformanceStandardMetric - threshold: Annotated[StrictFloat, Field(ge=0.0, le=1.0)] - standard: viewability_standard.ViewabilityStandard | None = None - vendor: brand_key.BrandKey diff --git a/src/adcp/types/generated_poc/core/canonical_placement.py b/src/adcp/types/generated_poc/core/canonical_placement.py deleted file mode 100644 index 738eabc38..000000000 --- a/src/adcp/types/generated_poc/core/canonical_placement.py +++ /dev/null @@ -1,179 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/canonical_placement.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field - -from ..enums import ( - audio_distribution_type, - dooh_motion_type, - identifier_types, - social_placement_surface, - sponsored_placement_type, - video_placement_type, -) -from . import canonical_format_option, seller_agent_ref - - -class Kind(StrEnum): - publisher_ref = 'publisher_ref' - seller_inline = 'seller_inline' - - -class Mode(StrEnum): - targetable = 'targetable' - included = 'included' - - -class CanonicalDoohScreenResolution(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - width: Annotated[SchemaInt, Field(ge=1)] - height: Annotated[SchemaInt, Field(ge=1)] - - -class Identifier(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: identifier_types.PropertyIdentifierTypes - value: str - - -class CanonicalDoohPlacementAttributes(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - slot_duration_seconds: Annotated[ - SchemaInt | None, - Field( - description='Scheduled duration of one ad slot in seconds; not the creative-duration contract.', - ge=1, - ), - ] = None - loop_duration_seconds: Annotated[ - SchemaInt | None, - Field( - description='Duration of the full ad loop rotation in seconds and the canonical source for loop duration.', - ge=1, - ), - ] = None - screen_resolution: CanonicalDoohScreenResolution | None = None - motion: Annotated[ - dooh_motion_type.DoohMotionType | None, - Field( - description='Physical motion capability of a visual DOOH screen, not an accepted-format declaration. Omit for audio-only placements.' - ), - ] = None - - -class CanonicalProductPlacement1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['publisher_ref'] = 'publisher_ref' - placement_id: Annotated[str, Field(min_length=1)] - publisher_domain: Annotated[ - str, - Field( - description='For publisher_ref, the adagents.json publisher namespace. For seller_inline, optional inventory-publisher attribution only.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - seller_agent: Annotated[ - seller_agent_ref.SellerAgentReference | None, - Field( - description='Defining sales agent for seller_inline identity. Recommended for new declarations; optional only to preserve legacy product-context placements.' - ), - ] = None - name: Annotated[str | None, Field(min_length=1)] = None - description: str | None = None - mode: Mode - tags: list[str] | None = None - format_options: Annotated[ - list[canonical_format_option.CanonicalFormatOption] | None, Field(min_length=1) - ] = None - video_placement_types: Annotated[ - list[video_placement_type.VideoPlacementType] | None, Field(min_length=1) - ] = None - audio_distribution_types: Annotated[ - list[audio_distribution_type.AudioDistributionType] | None, Field(min_length=1) - ] = None - sponsored_placement_types: Annotated[ - list[sponsored_placement_type.SponsoredPlacementType] | None, Field(min_length=1) - ] = None - social_placement_surfaces: Annotated[ - list[social_placement_surface.SocialPlacementSurface] | None, Field(min_length=1) - ] = None - identifiers: Annotated[ - list[Identifier] | None, - Field( - description='Optional external inventory identifiers for this placement. Externally governed values should be authority-prefixed; seller-local values are scoped by the surrounding publisher namespace. For publisher_ref placements, the effective set is the union of publisher and product declarations, de-duplicated by exact (type, value).', - min_length=1, - ), - ] = None - dooh_placement_attributes: CanonicalDoohPlacementAttributes | None = None - - -class CanonicalProductPlacement2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['seller_inline'] = 'seller_inline' - placement_id: Annotated[str, Field(min_length=1)] - publisher_domain: Annotated[ - str | None, - Field( - description='For publisher_ref, the adagents.json publisher namespace. For seller_inline, optional inventory-publisher attribution only.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - seller_agent: Annotated[ - seller_agent_ref.SellerAgentReference | None, - Field( - description='Defining sales agent for seller_inline identity. Recommended for new declarations; optional only to preserve legacy product-context placements.' - ), - ] = None - name: Annotated[str, Field(min_length=1)] - description: str | None = None - mode: Mode - tags: list[str] | None = None - format_options: Annotated[ - list[canonical_format_option.CanonicalFormatOption] | None, Field(min_length=1) - ] = None - video_placement_types: Annotated[ - list[video_placement_type.VideoPlacementType] | None, Field(min_length=1) - ] = None - audio_distribution_types: Annotated[ - list[audio_distribution_type.AudioDistributionType] | None, Field(min_length=1) - ] = None - sponsored_placement_types: Annotated[ - list[sponsored_placement_type.SponsoredPlacementType] | None, Field(min_length=1) - ] = None - social_placement_surfaces: Annotated[ - list[social_placement_surface.SocialPlacementSurface] | None, Field(min_length=1) - ] = None - identifiers: Annotated[ - list[Identifier] | None, - Field( - description='Optional external inventory identifiers for this placement. Externally governed values should be authority-prefixed; seller-local values are scoped by the surrounding publisher namespace. For publisher_ref placements, the effective set is the union of publisher and product declarations, de-duplicated by exact (type, value).', - min_length=1, - ), - ] = None - dooh_placement_attributes: CanonicalDoohPlacementAttributes | None = None - - -CanonicalProductPlacement = Annotated[ - CanonicalProductPlacement1 | CanonicalProductPlacement2, - Field( - description='Compact product placement with canonical format narrowing. Publisher references carry their publisher authority. New seller-inline placements SHOULD carry seller_agent as their defining authority; legacy seller-inline placements without it remain valid in product context.', - title='Canonical Product Placement', - ), -] diff --git a/src/adcp/types/generated_poc/core/canonical_pricing_option.py b/src/adcp/types/generated_poc/core/canonical_pricing_option.py deleted file mode 100644 index 09fed7ff6..000000000 --- a/src/adcp/types/generated_poc/core/canonical_pricing_option.py +++ /dev/null @@ -1,50 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/canonical_pricing_option.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictFloat - -from ..enums import adjustment_kind -from ..enums import event_type as event_type_1 -from ..pricing_options import price_breakdown as price_breakdown_1 -from ..pricing_options import price_guidance as price_guidance_1 - - -class PricingModel(StrEnum): - cpm = 'cpm' - vcpm = 'vcpm' - cpc = 'cpc' - cpcv = 'cpcv' - cpv = 'cpv' - cpp = 'cpp' - cpa = 'cpa' - revenue_share = 'revenue_share' - flat_rate = 'flat_rate' - time = 'time' - - -class CanonicalPricingOption(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - pricing_option_id: Annotated[str, Field(min_length=1)] - pricing_model: PricingModel - currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] - fixed_price: Annotated[StrictFloat | None, Field(ge=0.0)] = None - floor_price: Annotated[StrictFloat | None, Field(ge=0.0)] = None - price_guidance: price_guidance_1.PriceGuidance | None = None - min_spend_per_package: Annotated[StrictFloat | None, Field(ge=0.0)] = None - price_breakdown: price_breakdown_1.PriceBreakdown | None = None - eligible_adjustments: list[adjustment_kind.PriceAdjustmentKind] | None = None - parameters: dict[str, Any] | None = None - event_type: event_type_1.EventType | None = None - custom_event_name: Annotated[str | None, Field(min_length=1)] = None - event_source_id: Annotated[str | None, Field(min_length=1)] = None - commission_rate: Annotated[StrictFloat | None, Field(gt=0.0, le=1.0)] = None - commission_basis_description: Annotated[str | None, Field(max_length=1000, min_length=1)] = None diff --git a/src/adcp/types/generated_poc/core/canonical_product.py b/src/adcp/types/generated_poc/core/canonical_product.py deleted file mode 100644 index 68373692c..000000000 --- a/src/adcp/types/generated_poc/core/canonical_product.py +++ /dev/null @@ -1,290 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/canonical_product.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarStr -from pydantic import AwareDatetime, ConfigDict, Field, StrictBool - -from ..enums import audio_distribution_type, catalog_type -from ..enums import channels as channels_1 -from ..enums import delivery_type as delivery_type_1 -from ..enums import exclusivity as exclusivity_1 -from ..enums import social_placement_surface, sponsored_placement_type, video_placement_type -from . import acceptance_policy_profile_ids as acceptance_policy_profile_ids_1 -from . import ( - canonical_audience_evidence, - canonical_audience_evidence_selection, - canonical_delivery_forecast, - canonical_format_option, - canonical_measurement_terms, - canonical_performance_standard, - canonical_placement, - canonical_pricing_option, - canonical_product_action, - canonical_reporting_capabilities, - collection_selector, - demographic_targeting_capability, -) -from . import ext as ext_1 -from . import inventory_list_application -from . import media_buy_support as media_buy_support_1 -from . import ( - product_execution_requirement, - product_identity, - product_targeting_resolution, - property_id, - property_tag, -) -from . import signal_targeting_rules as signal_targeting_rules_1 -from . import targeting_overlay_support - - -class PublisherDomain(ScalarStr): - __slots__ = () - _constraints = { - 'pattern': '^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - } - - -class PublisherProperty1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Annotated[ - str | None, - Field( - description="Domain where publisher's adagents.json is hosted (e.g., 'cnn.com'). XOR with `publisher_domains` — exactly one MUST be present on each `publisher_properties[]` entry; both-present and neither-present both fail validation.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - publisher_domains: Annotated[ - list[PublisherDomain] | None, - Field( - description="Compact form for fanning the same selector across many publishers (e.g., a managed network listing every publisher it represents). Each entry is the domain where that publisher's adagents.json is hosted. Each listed domain MUST be canonicalized to lowercase (the `pattern` already rejects uppercase). Mutually exclusive with `publisher_domain`. Each listed domain counts as explicitly scoped for the `managerdomain` fallback safety rule.", - min_length=1, - ), - ] = None - selection_type: Annotated[ - Literal['all'], - Field( - description='Discriminator indicating all properties from each addressed publisher are included' - ), - ] = 'all' - - -class PublisherProperty4(AdCPBaseModel): - pass - - -class PublisherProperty5(PublisherProperty1, PublisherProperty4): - pass - - -class MatchedGtin(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[0-9]{8,14}$'} - - -class CatalogMatch(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - matched_gtins: list[MatchedGtin] | None = None - matched_ids: list[str] | None = None - matched_count: Annotated[SchemaInt | None, Field(ge=0)] = None - submitted_count: Annotated[SchemaInt, Field(ge=0)] - - -class PublisherProperty2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Annotated[ - str, - Field( - description="Domain where publisher's adagents.json is hosted (e.g., 'cnn.com').", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - selection_type: Annotated[ - Literal['by_id'], - Field(description='Discriminator indicating selection by specific property IDs'), - ] = 'by_id' - property_ids: Annotated[ - list[property_id.PropertyId], - Field(description="Specific property IDs from the publisher's adagents.json", min_length=1), - ] - - -class PublisherProperty3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Annotated[ - str | None, - Field( - description="Domain where publisher's adagents.json is hosted (e.g., 'cnn.com'). XOR with `publisher_domains` — exactly one MUST be present on each `publisher_properties[]` entry; both-present and neither-present both fail validation.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - publisher_domains: Annotated[ - list[PublisherDomain] | None, - Field( - description="Compact form for fanning the same tag predicate across many publishers (canonical managed-network shape). Each entry is the domain where that publisher's adagents.json is hosted. Each listed domain MUST be canonicalized to lowercase (the `pattern` already rejects uppercase). Mutually exclusive with `publisher_domain`. Each listed domain counts as explicitly scoped for the `managerdomain` fallback safety rule.", - min_length=1, - ), - ] = None - selection_type: Annotated[ - Literal['by_tag'], Field(description='Discriminator indicating selection by property tags') - ] = 'by_tag' - property_tags: Annotated[ - list[property_tag.PropertyTag], - Field( - description="Property tags resolved against each addressed publisher's adagents.json, OR against the parent file's top-level `properties[]` when those properties carry a `publisher_domain` matching the selector. Selector covers all properties carrying any of these tags.", - min_length=1, - ), - ] - - -class PublisherProperty6(PublisherProperty2, PublisherProperty4): - pass - - -class PublisherProperty7(PublisherProperty3, PublisherProperty4): - pass - - -PublisherProperty = PublisherProperty5 | PublisherProperty6 | PublisherProperty7 - - -class CanonicalProduct(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - product_id: Annotated[str, Field(min_length=1)] - name: Annotated[str, Field(min_length=1)] - description: str | None = None - publisher_properties: Annotated[list[PublisherProperty] | None, Field(min_length=1)] = None - channels: list[channels_1.MediaChannel] | None = None - video_placement_types: Annotated[ - list[video_placement_type.VideoPlacementType] | None, Field(min_length=1) - ] = None - audio_distribution_types: Annotated[ - list[audio_distribution_type.AudioDistributionType] | None, Field(min_length=1) - ] = None - sponsored_placement_types: Annotated[ - list[sponsored_placement_type.SponsoredPlacementType] | None, Field(min_length=1) - ] = None - social_placement_surfaces: Annotated[ - list[social_placement_surface.SocialPlacementSurface] | None, Field(min_length=1) - ] = None - format_options: Annotated[ - list[canonical_format_option.CanonicalFormatOption] | None, Field(min_length=1) - ] = None - placements: list[canonical_placement.CanonicalProductPlacement] | None = None - collections: Annotated[ - list[collection_selector.CollectionSelector] | None, - Field( - description='Collections available in this product, each referencing collections declared in an adagents.json by domain and explicit collection_ids. The domain-only bulk-grant selector form is for authorization scoping, not product composition. A selected-mode collection_selection names collections from this set.', - min_length=1, - ), - ] = None - collection_targeting_allowed: Annotated[ - StrictBool | None, - Field( - description="Whether buyers can select a subset of this product's collections through targeting_overlay.collection_list or targeting_overlay.collection_selection. When false, the product is a fixed bundle (a collection_selection that exactly restates the complete bundle remains an inherent match)." - ), - ] = False - delivery_type: delivery_type_1.DeliveryType | None = None - exclusivity: exclusivity_1.Exclusivity | None = None - pricing_options: Annotated[ - list[canonical_pricing_option.CanonicalPricingOption] | None, Field(min_length=1) - ] = None - forecast: canonical_delivery_forecast.CanonicalDeliveryForecast | None = None - reporting_capabilities: ( - canonical_reporting_capabilities.CanonicalReportingCapabilities | None - ) = None - measurement_terms: Annotated[ - canonical_measurement_terms.CanonicalMeasurementTerms | None, - Field( - description='Default billing measurement and makegood terms inherited by a direct purchase unless a negotiated proposal replaces them.' - ), - ] = None - performance_standards: Annotated[ - list[canonical_performance_standard.CanonicalPerformanceStandard] | None, - Field( - description='Default performance thresholds and measurement vendors inherited by a direct purchase.', - min_length=1, - ), - ] = None - catalog_types: Annotated[list[catalog_type.CatalogType] | None, Field(min_length=1)] = None - signal_targeting_allowed: StrictBool | None = None - signal_targeting_rules: signal_targeting_rules_1.SignalTargetingRules | None = None - demographic_targeting: ( - demographic_targeting_capability.DemographicTargetingCapability | None - ) = None - overlay_support: Annotated[ - targeting_overlay_support.TargetingOverlaySupport | None, - Field( - description='Binding product-scoped targeting dimensions the buyer may set independently on a package after discovery.' - ), - ] = None - media_buy_support: Annotated[ - media_buy_support_1.ProductMediaBuySupport | None, - Field(description='Binding product participation in shared MediaBuy-level controls.'), - ] = None - identity: Annotated[ - product_identity.ProductIdentity | None, - Field( - description='Experimental product-scoped identity and reach-measurement facts. See Product.identity for the cross-object delivery and frequency-cap rules.' - ), - ] = None - execution_requirements: Annotated[ - list[product_execution_requirement.ProductExecutionRequirement] | None, - Field( - description='Experimental account resources a package on this product needs before it can be created. See Product.execution_requirements for completeness, binding, and rejection rules.', - min_length=1, - ), - ] = None - audience_evidence: Annotated[ - list[canonical_audience_evidence.CanonicalAudienceEvidence] | None, Field(min_length=1) - ] = None - audience_evidence_selections: Annotated[ - list[canonical_audience_evidence_selection.CanonicalAudienceEvidenceSelection] | None, - Field( - description='Exact evidence snapshots that affected eligibility or ranking. Returned whenever evidence requirements affected the result, even if not requested explicitly.', - min_length=1, - ), - ] = None - max_optimization_goals: Annotated[SchemaInt | None, Field(ge=0)] = None - catalog_match: CatalogMatch | None = None - list_applications: Annotated[ - list[inventory_list_application.InventoryListApplication] | None, - Field( - description='Product-scoped receipts for every effective property- or collection-list targeting reference. Sellers MUST return one receipt per application regardless of response field projection; exclusion applications receive a receipt even when summary.matched is zero, while zero matches for any inclusion application make the product ineligible and it is not returned. Each receipt uses the same pre-list product inventory baseline; pricing and forecast reflect inventory remaining after all effective lists are composed.', - min_length=1, - ), - ] = None - brief_relevance: str | None = None - targeting_resolution: Annotated[ - product_targeting_resolution.ProductTargetingResolution | None, - Field( - description='Discovery-time targeting resolution bound to this configured product. modifications sparsely disclose product-specific differences from criteria.targeting_overlay; absence means exact acceptance of the structured overlay. Request-level brief interpretation is returned once on the response-root targeting_resolution. Selecting product_id accepts the disclosed modifications; pricing and forecast MUST reflect them. Requires expires_at.' - ), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field( - description='Expiration of a request-specific configured offer. Canonical products have no is_custom flag; expires_at is what marks an offer as request-specific. After it, the buyer rediscovers.' - ), - ] = None - allowed_actions: list[canonical_product_action.CanonicalProductAction] | None = None - acceptance_policy_profile_ids: ( - acceptance_policy_profile_ids_1.AcceptancePolicyProfileIds | None - ) = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/canonical_product_action.py b/src/adcp/types/generated_poc/core/canonical_product_action.py deleted file mode 100644 index c15fdeb61..000000000 --- a/src/adcp/types/generated_poc/core/canonical_product_action.py +++ /dev/null @@ -1,40 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/canonical_product_action.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..enums import canonical_media_buy_action, canonical_media_buy_action_mode, media_buy_status -from ..media_buy import change_term_constraints -from . import sla_window - - -class CanonicalProductAction(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - action: canonical_media_buy_action.CanonicalMediaBuyActionName - modes: Annotated[ - list[canonical_media_buy_action_mode.CanonicalMediaBuyActionMode], Field(min_length=1) - ] - allowed_statuses: Annotated[ - list[media_buy_status.MediaBuyStatus] | None, Field(min_length=1) - ] = None - sla: sla_window.SlaWindow | None = None - constraints: Annotated[ - change_term_constraints.MediaBuyChangeTermConstraints | None, - Field( - description='Advisory machine-readable bounds for product selection; proposal change terms restate binding bounds.' - ), - ] = None - terms_ref: Annotated[ - str | None, - Field( - description='Optional advisory pointer to published commercial terms. It is not a proposal change-term identity.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/canonical_projection_ref.py b/src/adcp/types/generated_poc/core/canonical_projection_ref.py deleted file mode 100644 index 20df5f294..000000000 --- a/src/adcp/types/generated_poc/core/canonical_projection_ref.py +++ /dev/null @@ -1,47 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/canonical_projection_ref.json -# timestamp: 2026-09-01T19:37:00+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from . import canonical_format_kind, canonical_projection_slot_override - - -class AssetSource(StrEnum): - buyer_uploaded = 'buyer_uploaded' - publisher_host_recorded = 'publisher_host_recorded' - seller_pre_rendered_from_brief = 'seller_pre_rendered_from_brief' - seller_human_designed = 'seller_human_designed' - agent_synthesized = 'agent_synthesized' - publisher_owned_reference = 'publisher_owned_reference' - - -class CanonicalProjectionReference(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[ - canonical_format_kind.CanonicalFormatKind, - Field( - description='The v2 canonical-format-kind this v1 format projects to (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_vast`, `audio_daast`, `sponsored_placement`, `native_in_feed`, `responsive_creative`, `agent_placement`, `seller_rendered_stateful_display`, `coordinated_placements`, or `custom`).' - ), - ] - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered asset bytes come from on the projected v2 declaration. Default (when omitted) is `buyer_uploaded` — the canonical's default. Set explicitly when the v1 named format doesn't follow that default. Required for generative entries (`agent_synthesized` or `seller_pre_rendered_from_brief`) because their asset shape doesn't carry image/video/audio bytes, and for published-post reference entries (`publisher_owned_reference`) because their asset shape carries a post reference rather than uploaded bytes. Projection without this hint produces a lossy v2 declaration that claims buyer-uploaded bytes." - ), - ] = None - slots_override: Annotated[ - list[canonical_projection_slot_override.CanonicalProjectionSlotOverride] | None, - Field( - description="When the v1 named format's slot shape differs from the canonical's default slots, this carries the override that the projected v2 declaration's `params.slots[]` should use. REPLACES (does not merge with) the canonical's default slots — projection-time semantics. The slot vocabulary follows `asset-group-vocabulary.json`. Asset IDs in the v1 format's `assets[*]` MUST resolve (directly or via the vocabulary's aliases) to the `asset_group_id` values declared here.", - min_length=1, - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/canonical_projection_slot_override.py b/src/adcp/types/generated_poc/core/canonical_projection_slot_override.py deleted file mode 100644 index 25df5ee65..000000000 --- a/src/adcp/types/generated_poc/core/canonical_projection_slot_override.py +++ /dev/null @@ -1,41 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/canonical_projection_slot_override.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictBool - - -class CanonicalProjectionSlotOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_group_id: Annotated[ - str, - Field( - description='Asset group identifier from `asset-group-vocabulary.json` (e.g., `generation_prompt`, `creative_brief`, `image_main`, `video_main`).' - ), - ] - asset_type: Annotated[ - str, - Field( - description='Asset type — `image`, `video`, `audio`, `text`, `html`, `javascript`, `url`, `zip`, `brief`, `catalog`, `published_post`, or another canonical slot asset type.' - ), - ] - required: Annotated[ - StrictBool | None, - Field(description='Whether the slot is required in the projected declaration.'), - ] = False - max_chars: Annotated[ - SchemaInt | None, Field(description='Max character count for text slots.', ge=1) - ] = None - consumed_for_production: Annotated[ - StrictBool | None, - Field( - description="When false, slot is for moderation/review only and is NOT consumed by the seller's renderer (e.g., a brand-safety brief that informs review but doesn't appear in the rendered ad)." - ), - ] = True diff --git a/src/adcp/types/generated_poc/core/canonical_proposal.py b/src/adcp/types/generated_poc/core/canonical_proposal.py deleted file mode 100644 index 8e085820c..000000000 --- a/src/adcp/types/generated_poc/core/canonical_proposal.py +++ /dev/null @@ -1,96 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/canonical_proposal.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat - -from ..enums import proposal_status as proposal_status_1 -from ..media_buy import commercial_terms as commercial_terms_1 -from . import canonical_delivery_forecast -from . import insertion_order as insertion_order_1 - - -class ProposalKind(StrEnum): - new_media_buy = 'new_media_buy' - media_buy_update = 'media_buy_update' - media_buy_cancellation = 'media_buy_cancellation' - - -class TotalBudgetGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - min: Annotated[StrictFloat | None, Field(ge=0.0)] = None - recommended: Annotated[StrictFloat | None, Field(ge=0.0)] = None - max: Annotated[StrictFloat | None, Field(ge=0.0)] = None - currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] - - -class CanonicalProposal(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - proposal_id: Annotated[str, Field(max_length=255, min_length=1)] - proposal_kind: ProposalKind - parent_proposal_id: Annotated[ - str | None, - Field( - description="Immediate predecessor this snapshot was forked from. Every proposal produced by refine_proposals carries it, equal to the request's source proposal_id, so negotiation lineage is reconstructible from proposals alone.", - max_length=255, - min_length=1, - ), - ] = None - media_buy_id: Annotated[str | None, Field(min_length=1)] = None - opportunity_id: Annotated[ - str | None, - Field( - description='Buyer planning cycle associated with this proposal. Revisions inherit it; it does not participate in proposal identity.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - base_media_buy_revision: Annotated[SchemaInt | None, Field(ge=1)] = None - proposal_status: Annotated[ - proposal_status_1.ProposalStatus, - Field( - description='draft is indicative and unreserved; committed has firm terms with inventory reserved until expires_at; accepted is the historical snapshot attached to a MediaBuy.' - ), - ] - accepted_at: AwareDatetime | None = None - expires_at: Annotated[ - AwareDatetime | None, - Field( - description='For a draft, the indicative-terms freshness deadline. For a committed proposal, the inventory-hold deadline.' - ), - ] = None - name: Annotated[str, Field(max_length=500, min_length=1)] - description: Annotated[str | None, Field(max_length=2000)] = None - brief_alignment: Annotated[str | None, Field(max_length=2000)] = None - commercial_terms: commercial_terms_1.CommercialTerms - terms_digest: Annotated[ - str, - Field( - description='Base64url SHA-256 digest of the RFC 8785 JCS serialization of commercial_terms, prefixed with sha256:.', - pattern='^sha256:[A-Za-z0-9_-]{43}$', - ), - ] - insertion_order: insertion_order_1.InsertionOrder | None = None - total_budget_guidance: Annotated[ - TotalBudgetGuidance | None, - Field( - description="Optional budget guidance for this proposal — the planning answer to criteria.outcome_target and to open-budget briefs. commercial_terms_1.total_budget remains the concrete figure the plan is priced at; this band expresses the seller's recommended range around it. When criteria.outcome_target carries cost_per, the cost answer is commercial_terms_1.bidding.cost_per and this band's currency equals cost_per.currency." - ), - ] = None - forecast: Annotated[ - canonical_delivery_forecast.CanonicalDeliveryForecast | None, - Field( - description="Aggregate forecasted delivery for the proposal. For outcome_target requests, points carry the goal's metric or event key in metrics; with cost_per, that is the goal volume planned under the commercial_terms_1.bidding policy, and currency equals cost_per.currency." - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/canonical_reporting_capabilities.py b/src/adcp/types/generated_poc/core/canonical_reporting_capabilities.py deleted file mode 100644 index 5130f4082..000000000 --- a/src/adcp/types/generated_poc/core/canonical_reporting_capabilities.py +++ /dev/null @@ -1,83 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/canonical_reporting_capabilities.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictBool - -from ..enums import available_metric, reporting_frequency -from . import ( - brand_key, - demographic_reporting_capability, - geo_breakdown_support, - measurement_window, - reporting_delivery_offering_id, - spot_reporting_capability, - vendor_metric_id, -) - - -class DateRangeSupport(StrEnum): - date_range = 'date_range' - lifetime_only = 'lifetime_only' - - -class VendorMetric(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: brand_key.BrandKey - metric_id: vendor_metric_id.VendorMetricId - - -class CanonicalReportingCapabilities(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - available_reporting_frequencies: Annotated[ - list[reporting_frequency.ReportingFrequency], Field(min_length=1) - ] - expected_delay_minutes: Annotated[SchemaInt, Field(ge=0)] - timezone: Annotated[ - str, - Field( - description='Explicit reporting-period timezone for this product. It may equal Account.timezone or differ when the upstream platform reports on a separate boundary.' - ), - ] - supports_webhooks: StrictBool - reporting_delivery_offering_ids: Annotated[ - list[reporting_delivery_offering_id.ReportingDeliveryOfferingId] | None, - Field( - description='Product-scoped subset of get_adcp_capabilities.media_buy.reporting_delivery.offerings[].offering_id that packages using this product can satisfy. This binds seller-wide managed-delivery offerings to product/package eligibility. An empty array explicitly declares no managed offering; absence means product-level applicability is unknown and MUST NOT be inferred from the seller-wide list. Account, seat, credential, or provider constraints may narrow support further during sync_accounts validation.' - ), - ] = None - available_metrics: list[available_metric.AvailableMetric] - vendor_metrics: list[VendorMetric] | None = None - supports_creative_breakdown: StrictBool | None = None - supports_format_breakdown: StrictBool | None = None - supports_keyword_breakdown: StrictBool | None = None - supports_geo_breakdown: geo_breakdown_support.GeographicBreakdownSupport | None = None - supports_device_type_breakdown: StrictBool | None = None - supports_device_platform_breakdown: StrictBool | None = None - supports_audience_breakdown: StrictBool | None = None - supports_demographic_breakdown: ( - demographic_reporting_capability.DemographicReportingCapability | None - ) = None - supports_placement_breakdown: StrictBool | None = None - supports_property_breakdown: StrictBool | None = None - supports_collection_breakdown: StrictBool | None = None - supports_installment_breakdown: StrictBool | None = None - supports_collection_property_breakdown: StrictBool | None = None - supports_installment_property_breakdown: StrictBool | None = None - supports_placement_property_breakdown: StrictBool | None = None - supports_spot_breakdown: spot_reporting_capability.SpotReportingCapability | None = None - date_range_support: DateRangeSupport - windowed_pull_granularities: list[reporting_frequency.ReportingFrequency] | None = None - measurement_windows: Annotated[ - list[measurement_window.MeasurementWindow] | None, Field(min_length=1) - ] = None diff --git a/src/adcp/types/generated_poc/core/canonical_reporting_commitment.py b/src/adcp/types/generated_poc/core/canonical_reporting_commitment.py deleted file mode 100644 index e7d121d35..000000000 --- a/src/adcp/types/generated_poc/core/canonical_reporting_commitment.py +++ /dev/null @@ -1,44 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/canonical_reporting_commitment.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - -from ..enums import available_metric -from . import brand_key, canonical_metric_qualifier, vendor_metric_id - - -class CanonicalReportingCommitment1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Literal['standard'] = 'standard' - metric_id: available_metric.AvailableMetric - qualifier: canonical_metric_qualifier.CanonicalMetricQualifier | None = None - effective_at: AwareDatetime | None = None - - -class CanonicalReportingCommitment2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Literal['vendor'] = 'vendor' - vendor: brand_key.BrandKey - metric_id: vendor_metric_id.VendorMetricId - qualifier: canonical_metric_qualifier.CanonicalMetricQualifier | None = None - effective_at: AwareDatetime | None = None - - -CanonicalReportingCommitment = Annotated[ - CanonicalReportingCommitment1 | CanonicalReportingCommitment2, - Field( - description='One metric the seller commits to report for a commercial product term. Vendor identity uses BrandKey so the compact lifecycle does not inline brand or provenance documents.', - discriminator='scope', - title='Canonical Reporting Commitment', - ), -] diff --git a/src/adcp/types/generated_poc/core/canvas_constraint.py b/src/adcp/types/generated_poc/core/canvas_constraint.py deleted file mode 100644 index 27dd15603..000000000 --- a/src/adcp/types/generated_poc/core/canvas_constraint.py +++ /dev/null @@ -1,44 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/canvas_constraint.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictFloat - - -class Constraint(StrEnum): - safe_area = 'safe_area' - reserved_region = 'reserved_region' - decoration_only_edge = 'decoration_only_edge' - no_text_or_logos = 'no_text_or_logos' - - -class Unit(StrEnum): - px = 'px' - percent = 'percent' - - -class Region(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - x: Annotated[StrictFloat, Field(ge=0.0)] - y: Annotated[StrictFloat, Field(ge=0.0)] - width: Annotated[StrictFloat, Field(gt=0.0)] - height: Annotated[StrictFloat, Field(gt=0.0)] - unit: Unit | None = Unit.px - - -class CanvasConstraint(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - constraint: Constraint - state_id: Annotated[str | None, Field(pattern='^[a-z][a-z0-9_]*$')] = None - breakpoint_id: Annotated[str | None, Field(pattern='^[a-z][a-z0-9_]*$')] = None - region: Region diff --git a/src/adcp/types/generated_poc/core/capabilities_changed_webhook.py b/src/adcp/types/generated_poc/core/capabilities_changed_webhook.py deleted file mode 100644 index 7bc784bb7..000000000 --- a/src/adcp/types/generated_poc/core/capabilities_changed_webhook.py +++ /dev/null @@ -1,108 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/capabilities_changed_webhook.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - -from . import ext as ext_1 - - -class Reason(StrEnum): - configuration_changed = 'configuration_changed' - deployment_changed = 'deployment_changed' - capability_enabled = 'capability_enabled' - capability_disabled = 'capability_disabled' - protocol_versions_changed = 'protocol_versions_changed' - manual_refresh = 'manual_refresh' - other = 'other' - - -class ChangedPath(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^(/[^\\s]*)$'} - - -class CapabilitiesChangedWebhook(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - idempotency_key: Annotated[ - str, - Field( - description='Sender-generated key stable across retries of the same fire. Sellers MUST generate a cryptographically random value (UUID v4 recommended) per distinct fire and reuse it on every retry of the same fire. Receivers MUST dedupe by this key, scoped to the authenticated sender identity.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - notification_id: Annotated[ - str, - Field( - description='Stable identifier for this logical capability-change event. Re-emissions of the same logical change reuse this value under a new idempotency_key; a later material capability revision receives a new id.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] - notification_type: Annotated[ - Literal['capabilities.changed'], - Field( - description="Fixed notification type discriminator. Matches the value registered on the subscriber's `event_types`." - ), - ] = 'capabilities.changed' - fired_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the seller initiated this fire. Distinct from `changed_at`, which is when the seller recorded the material capability change.' - ), - ] - subscriber_id: Annotated[ - str, - Field( - description="Identifies which caller-scoped notification_configs[] entry is receiving this fire. Echoed verbatim from the entry's subscriber_id.", - max_length=64, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,64}$', - ), - ] - agent_url: Annotated[ - AnyUrl, - Field( - description='Canonical seller agent URL whose capabilities changed. Receivers that subscribe to multiple agents use this to select the cache entry to invalidate.' - ), - ] - changed_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the seller recorded the material capability change. SHOULD match or precede `adcp.capability_changes.last_modified` on the next `get_adcp_capabilities` response.' - ), - ] - reason: Annotated[ - Reason, - Field( - description='Coarse reason for the invalidation. This is advisory routing/debug metadata; receivers MUST re-read `get_adcp_capabilities` rather than relying on the reason to infer the new capability surface.' - ), - ] - capabilities_version: Annotated[ - str, - Field( - description='Required opaque revision token for the capability document after the change. MUST equal `adcp.capability_changes.capabilities_version` on the authoritative `get_adcp_capabilities` response available before this webhook is sent. Receivers MUST treat the token as opaque and compare it only for equality.', - max_length=255, - min_length=1, - ), - ] - changed_paths: Annotated[ - list[ChangedPath] | None, - Field( - description='Optional advisory JSON Pointer-style paths that identify the top-level or nested capability subtrees that changed (for example `/account/sandbox` or `/supported_protocols`). Receivers MAY use this for logging or selective downstream invalidation, but MUST still treat the full `get_adcp_capabilities` response as the authoritative replacement snapshot.', - min_length=1, - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/catalog.py b/src/adcp/types/generated_poc/core/catalog.py deleted file mode 100644 index 9cd49ead5..000000000 --- a/src/adcp/types/generated_poc/core/catalog.py +++ /dev/null @@ -1,125 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/catalog.json -# timestamp: 2026-10-04T00:56:30+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import AnyUrl, ConfigDict, Field - -from ..enums import catalog_type -from ..enums import content_id_type as content_id_type_1 -from ..enums import event_type -from ..enums import feed_format as feed_format_1 -from ..enums import update_frequency as update_frequency_1 -from . import catalog_field_mapping - - -class Gtin(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[0-9]{8,14}$'} - - -class Catalog(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: Annotated[ - catalog_type.CatalogType, - Field( - description="Catalog type. Structural types: 'offering' (AdCP Offering objects), 'product' (ecommerce entries), 'inventory' (stock per location), 'store' (physical locations), 'promotion' (deals and pricing). Vertical types: 'hotel', 'flight', 'job', 'vehicle', 'real_estate', 'education', 'destination', 'app' — each with an industry-specific item schema." - ), - ] - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: Annotated[ - feed_format_1.FeedFormat | None, - Field( - description='Format of the external feed at url. Required when url points to a non-AdCP feed (e.g., Google Merchant Center XML, Meta Product Catalog). Omit for offering-type catalogs where the feed is native AdCP JSON.' - ), - ] = None - update_frequency: Annotated[ - update_frequency_1.UpdateFrequency | None, - Field( - description='How often the platform should re-fetch the feed from url. Only applicable when url is provided. Platforms may use this as a hint for polling schedules.' - ), - ] = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to exact canonical item keys. The key field is offering_id for offering, store_id for store, hotel_id for hotel, flight_id for flight, job_id for job, vehicle_id for vehicle, listing_id for real_estate, program_id for education, destination_id for destination, and app_id for app. Product, inventory, and promotion catalogs use the stable normalized source identifier retained during ingestion (for example a retailer SKU). The same canonical key is used by catalog availability item_id.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[event_type.EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: Annotated[ - content_id_type_1.ContentIdType | None, - Field( - description="Identifier type that the event's content_ids field should be matched against for items in this catalog. For example, 'gtin' means content_ids values are Global Trade Item Numbers, 'sku' means retailer SKUs. Omit when using a custom identifier scheme not listed in the enum." - ), - ] = None - feed_field_mappings: Annotated[ - list[catalog_field_mapping.CatalogFieldMapping] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/catalog_field_mapping.py b/src/adcp/types/generated_poc/core/catalog_field_mapping.py deleted file mode 100644 index e02382404..000000000 --- a/src/adcp/types/generated_poc/core/catalog_field_mapping.py +++ /dev/null @@ -1,88 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/catalog_field_mapping.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictFloat - -from . import ext as ext_1 - - -class Transform(StrEnum): - date = 'date' - divide = 'divide' - boolean = 'boolean' - split = 'split' # type: ignore[assignment] - - -class CatalogFieldMapping(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - feed_field: Annotated[ - str | None, - Field( - description='Field name in the external feed record. Omit when injecting a static literal value (use the value property instead).' - ), - ] = None - catalog_field: Annotated[ - str | None, - Field( - description="Target field on the catalog item schema, using dot notation for nested fields (e.g., 'name', 'price.amount', 'location.city'). Mutually exclusive with asset_group_id." - ), - ] = None - asset_group_id: Annotated[ - str | None, - Field( - description="Places the feed field value (a URL) into a typed asset pool on the catalog item's assets array. The value is wrapped as an image or video asset in a group with this ID. Use standard group IDs: 'images_landscape', 'images_vertical', 'images_square', 'logo', 'video'. Mutually exclusive with catalog_field." - ), - ] = None - value: Annotated[ - Any | None, - Field( - description='Static literal value to inject into catalog_field for every item, regardless of what the feed contains. Mutually exclusive with feed_field. Useful for fields the feed omits (e.g., currency when price is always USD, or a constant category value).' - ), - ] = None - transform: Annotated[ - Transform | None, - Field( - description='Named transform to apply to the feed field value before writing to the catalog schema. See transform-specific parameters (format, timezone, by, separator).' - ), - ] = None - format: Annotated[ - str | None, - Field( - description="For transform 'date': the input date format string (e.g., 'YYYYMMDD', 'MM/DD/YYYY', 'DD-MM-YYYY'). Output is always ISO 8601 (e.g., '2025-03-01'). Uses Unicode date pattern tokens." - ), - ] = None - timezone: Annotated[ - str | None, - Field( - description="For transform 'date': the timezone of the input value. IANA timezone identifier (e.g., 'UTC', 'America/New_York', 'Europe/Amsterdam'). Defaults to UTC when omitted." - ), - ] = None - by: Annotated[ - StrictFloat | None, - Field( - description="For transform 'divide': the divisor to apply (e.g., 100 to convert integer cents to decimal dollars).", - gt=0.0, - ), - ] = None - separator: Annotated[ - str | None, - Field( - description="For transform 'split': the separator character or string to split on. Defaults to ','." - ), - ] = ',' - default: Annotated[ - Any | None, - Field( - description='Fallback value to use when feed_field is absent, null, or empty. Applied after any transform would have been applied. Allows optional feed fields to have a guaranteed baseline value.' - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/catalog_item_availability_error.py b/src/adcp/types/generated_poc/core/catalog_item_availability_error.py deleted file mode 100644 index 44ad6db10..000000000 --- a/src/adcp/types/generated_poc/core/catalog_item_availability_error.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/catalog_item_availability_error.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from .error import Error - - -class CatalogItemAvailabilityError(Error): - pass diff --git a/src/adcp/types/generated_poc/core/catalog_item_availability_ref.py b/src/adcp/types/generated_poc/core/catalog_item_availability_ref.py deleted file mode 100644 index 1fd98ad77..000000000 --- a/src/adcp/types/generated_poc/core/catalog_item_availability_ref.py +++ /dev/null @@ -1,35 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/catalog_item_availability_ref.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - - -class CatalogItemAvailabilityReference(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - catalog_id: Annotated[ - str, Field(description='Buyer-assigned catalog ID.', max_length=255, min_length=1) - ] - catalog_generation: Annotated[ - str, - Field( - description='Opaque seller-issued token for the referenced catalog incarnation. It remains stable across ordinary upserts and feed refreshes, changes when a deleted catalog_id is recreated, and MUST never be reused for that account and catalog_id.', - max_length=255, - min_length=1, - ), - ] - item_id: Annotated[ - str, - Field( - description='Exact canonical item key used by Catalog.ids. For typed catalogs this is the type-specific identifier field; for product, inventory, and promotion catalogs it is the stable normalized source identifier retained during ingestion.', - max_length=255, - min_length=1, - ), - ] diff --git a/src/adcp/types/generated_poc/core/catalog_item_availability_state.py b/src/adcp/types/generated_poc/core/catalog_item_availability_state.py deleted file mode 100644 index 48a4ec100..000000000 --- a/src/adcp/types/generated_poc/core/catalog_item_availability_state.py +++ /dev/null @@ -1,74 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/catalog_item_availability_state.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field - -from . import catalog_item_availability_error -from . import ext as ext_1 - - -class Status(StrEnum): - found = 'found' - failed = 'failed' - - -class Availability(StrEnum): - active = 'active' - suppressed = 'suppressed' - - -class CatalogItemAvailabilityState(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - request_index: Annotated[ - SchemaInt, - Field( - description='Zero-based index of the corresponding item_availability_queries entry.', - ge=0, - ), - ] - catalog_id: Annotated[str, Field(max_length=255, min_length=1)] - catalog_generation: Annotated[str, Field(max_length=255, min_length=1)] - item_id: Annotated[str, Field(max_length=255, min_length=1)] - status: Annotated[ - Status, - Field( - description='found returns current state; failed means the reference could not be read.' - ), - ] - availability: Annotated[ - Availability | None, - Field( - description='Current buyer-authored overlay state. active does not imply seller approval or delivery eligibility.' - ), - ] = None - overlay_revision: Annotated[ - SchemaInt | None, - Field( - description='Current optimistic-concurrency token. Revision 0 is the initial active state. Every applied suppress, applied restore, and automatic expiry increments it exactly once; unchanged updates, reads, and idempotent replays do not increment it.', - ge=0, - ), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field( - description='Current automatic expiry, present only while availability is suppressed with an expiry.' - ), - ] = None - updated_at: Annotated[ - AwareDatetime | None, - Field(description='Seller timestamp of the state represented by overlay_revision.'), - ] = None - errors: Annotated[ - list[catalog_item_availability_error.CatalogItemAvailabilityError] | None, - Field(min_length=1), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/catalog_item_availability_update.py b/src/adcp/types/generated_poc/core/catalog_item_availability_update.py deleted file mode 100644 index 0e4d5164f..000000000 --- a/src/adcp/types/generated_poc/core/catalog_item_availability_update.py +++ /dev/null @@ -1,95 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/catalog_item_availability_update.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field - -from . import ext as ext_1 - - -class Action(StrEnum): - suppress = 'suppress' - restore = 'restore' - - -class Reason(StrEnum): - out_of_stock = 'out_of_stock' - back_in_stock = 'back_in_stock' - content_unavailable = 'content_unavailable' - content_available = 'content_available' - promotion_start = 'promotion_start' - promotion_end = 'promotion_end' - time_window_started = 'time_window_started' - time_window_expired = 'time_window_expired' - buyer_request = 'buyer_request' - other = 'other' - - -class CatalogItemAvailabilityUpdate(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - catalog_id: Annotated[ - str, - Field( - description='Buyer-assigned ID of the buyer-managed catalog containing the item.', - max_length=255, - min_length=1, - ), - ] - catalog_generation: Annotated[ - str, - Field( - description='Opaque seller-issued token for the catalog incarnation. Obtain it from sync_catalogs discovery or mutation results. A generation mismatch is treated as REFERENCE_NOT_FOUND, preventing a delayed update from affecting a deleted-and-recreated catalog.', - max_length=255, - min_length=1, - ), - ] - item_id: Annotated[ - str, - Field( - description='Exact canonical item key used by Catalog.ids. For typed catalogs this is the type-specific identifier field; for product, inventory, and promotion catalogs it is the stable normalized source identifier retained during ingestion.', - max_length=255, - min_length=1, - ), - ] - expected_overlay_revision: Annotated[ - SchemaInt, - Field( - description='Required optimistic-concurrency token obtained from item_availability_states. Revision 0 is the initial active state. The seller MUST compare this value atomically with the write and return CONFLICT without mutation when it differs from current state.', - ge=0, - ), - ] - action: Annotated[ - Action, - Field( - description='suppress makes the item and known derived creative variants ineligible immediately; restore removes the buyer-authored suppression but does not bypass seller controls.' - ), - ] - reason: Annotated[ - Reason, - Field( - description='Machine-readable reason for the transition. Use other only when no standard reason applies and explain the condition in reason_detail.' - ), - ] - reason_detail: Annotated[ - str | None, - Field( - description='Optional human-readable context. Required when reason is other. Plain text only; receivers MUST treat it as untrusted buyer input.', - max_length=1000, - min_length=1, - ), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field( - description='Optional expiry for a suppress overlay. At this instant the seller automatically removes the buyer-authored suppression as if it received restore. Valid only with action suppress; omit for an indefinite suppression. Sellers MUST reject a timestamp that is not in the future when the request is processed. On a repeated suppress, this value is complete replacement state: a changed timestamp replaces the prior expiry, and omission clears a prior expiry to make suppression indefinite. Either change returns status applied, not unchanged.' - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/catalog_item_availability_update_result.py b/src/adcp/types/generated_poc/core/catalog_item_availability_update_result.py deleted file mode 100644 index 675646cfc..000000000 --- a/src/adcp/types/generated_poc/core/catalog_item_availability_update_result.py +++ /dev/null @@ -1,85 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/catalog_item_availability_update_result.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field - -from . import catalog_item_availability_error -from . import ext as ext_1 - - -class Action(StrEnum): - suppress = 'suppress' - restore = 'restore' - - -class Status(StrEnum): - applied = 'applied' - unchanged = 'unchanged' - failed = 'failed' - - -class Availability(StrEnum): - active = 'active' - suppressed = 'suppressed' - - -class CatalogItemAvailabilityUpdateResult(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - request_index: Annotated[ - SchemaInt, - Field( - description='Zero-based index of the corresponding item_availability_updates entry.', - ge=0, - ), - ] - catalog_id: Annotated[str, Field(description='Catalog ID from the request.')] - catalog_generation: Annotated[ - str, Field(description='Catalog generation from the request.', max_length=255, min_length=1) - ] - item_id: Annotated[str, Field(description='Item ID from the request.')] - action: Annotated[Action, Field(description='Action from the request.')] - status: Annotated[ - Status, - Field( - description='applied means the requested overlay transition completed, including replacement or removal of an existing expires_at; unchanged means the item was already in the requested buyer-availability state with the same expiry; failed means no transition was applied.' - ), - ] - availability: Annotated[ - Availability | None, - Field(description='Persisted buyer-authored state after an applied or unchanged result.'), - ] = None - overlay_revision: Annotated[ - SchemaInt | None, - Field( - description="Persisted state revision after an applied or unchanged result. Applied increments the request's expected_overlay_revision exactly once; unchanged preserves it.", - ge=0, - ), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field( - description='Persisted expiry after this update, present only for a suppressed state with an expiry.' - ), - ] = None - applied_at: Annotated[ - AwareDatetime | None, - Field( - description='Seller timestamp when the buyer-availability state took effect. Required for applied. Optional for unchanged when the seller knows the timestamp of the already-persisted state.' - ), - ] = None - errors: Annotated[ - list[catalog_item_availability_error.CatalogItemAvailabilityError] | None, - Field( - description='Why this item update failed. Required when status is failed.', min_length=1 - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/catalog_item_delivery_metrics.py b/src/adcp/types/generated_poc/core/catalog_item_delivery_metrics.py deleted file mode 100644 index d8ce20ece..000000000 --- a/src/adcp/types/generated_poc/core/catalog_item_delivery_metrics.py +++ /dev/null @@ -1,24 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/catalog_item_delivery_metrics.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from pydantic import Field - -from ..enums import content_id_type as content_id_type_1 -from .delivery_metrics import DeliveryMetrics - - -class CatalogItemDeliveryMetrics(DeliveryMetrics): - content_id: Annotated[ - str, Field(description='Catalog item identifier (e.g., SKU, GTIN, job_id, offering_id)') - ] - content_id_type: Annotated[ - content_id_type_1.ContentIdType | None, - Field(description='Identifier type for this content_id'), - ] = None - impressions: Any - spend: Any diff --git a/src/adcp/types/generated_poc/core/catalog_item_reference_not_found_error.py b/src/adcp/types/generated_poc/core/catalog_item_reference_not_found_error.py deleted file mode 100644 index 0f54748ca..000000000 --- a/src/adcp/types/generated_poc/core/catalog_item_reference_not_found_error.py +++ /dev/null @@ -1,19 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/catalog_item_reference_not_found_error.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from typing import Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict - - -class CatalogItemReferenceNotFoundError(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - code: Literal['REFERENCE_NOT_FOUND'] = 'REFERENCE_NOT_FOUND' - message: Literal['Catalog item not found'] = 'Catalog item not found' - recovery: Literal['correctable'] = 'correctable' diff --git a/src/adcp/types/generated_poc/core/catalog_selection.py b/src/adcp/types/generated_poc/core/catalog_selection.py deleted file mode 100644 index 3db938d81..000000000 --- a/src/adcp/types/generated_poc/core/catalog_selection.py +++ /dev/null @@ -1,70 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/catalog_selection.json -# timestamp: 2026-10-04T00:56:30+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field - -from ..enums import catalog_type - - -class Gtin(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[0-9]{8,14}$'} - - -class CatalogSelection(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - catalog_id: Annotated[ - str, - Field( - description='Seller-known catalog identifier returned by sync_catalogs.', min_length=1 - ), - ] - type: Annotated[ - catalog_type.CatalogType | None, - Field( - description="Catalog type. Structural types: 'offering' (AdCP Offering objects), 'product' (ecommerce entries), 'inventory' (stock per location), 'store' (physical locations), 'promotion' (deals and pricing). Vertical types: 'hotel', 'flight', 'job', 'vehicle', 'real_estate', 'education', 'destination', 'app' — each with an industry-specific item schema." - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to exact canonical item keys. The key field is offering_id for offering, store_id for store, hotel_id for hotel, flight_id for flight, job_id for job, vehicle_id for vehicle, listing_id for real_estate, program_id for education, destination_id for destination, and app_id for app. Product, inventory, and promotion catalogs use the stable normalized source identifier retained during ingestion (for example a retailer SKU). The same canonical key is used by catalog availability item_id.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies').", - min_length=1, - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/catchment.py b/src/adcp/types/generated_poc/core/catchment.py deleted file mode 100644 index d89d3aa3b..000000000 --- a/src/adcp/types/generated_poc/core/catchment.py +++ /dev/null @@ -1,106 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/catchment.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictFloat, model_validator - -from ..enums import distance_unit -from ..enums import transport_mode as transport_mode_1 -from ..enums import travel_time_unit -from . import ext as ext_1 - - -class Type(StrEnum): - Polygon = 'Polygon' - MultiPolygon = 'MultiPolygon' - - -class Geometry(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Annotated[Type, Field(description='GeoJSON geometry type.')] - coordinates: Annotated[ - list[Any], - Field( - description='GeoJSON coordinates array. For Polygon: array of linear rings. For MultiPolygon: array of polygons.' - ), - ] - - -class TravelTime(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - value: Annotated[StrictFloat, Field(description='Travel time limit.', ge=1.0)] - unit: travel_time_unit.TravelTimeUnit - - -class Radius(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - value: Annotated[StrictFloat, Field(description='Radius distance.', gt=0.0)] - unit: Annotated[distance_unit.DistanceUnit, Field(description='Distance unit.')] - - -class Catchment(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - catchment_id: Annotated[ - str, - Field( - description="Identifier for this catchment, used to reference specific catchment areas in targeting (e.g., 'walk', 'drive', 'primary')." - ), - ] - label: Annotated[ - str | None, - Field( - description="Human-readable label for this catchment (e.g., '15-min drive', '1km walking radius')." - ), - ] = None - travel_time: Annotated[ - TravelTime | None, - Field( - description='Travel time limit for isochrone calculation. The platform resolves this to a geographic boundary based on actual transportation networks, accounting for road connectivity, transit schedules, and terrain.' - ), - ] = None - transport_mode: Annotated[ - transport_mode_1.TransportMode | None, - Field( - description='Transportation mode for isochrone calculation. Required when travel_time is provided.' - ), - ] = None - radius: Annotated[ - Radius | None, - Field( - description="Simple radius from the store location. The platform draws a circle of this distance around the store's coordinates." - ), - ] = None - geometry: Annotated[ - Geometry | None, - Field( - description='Pre-computed GeoJSON geometry defining the catchment boundary. Use this when the buyer has already calculated isochrones (via TravelTime, Mapbox, etc.) or has custom trade area boundaries. Supports Polygon and MultiPolygon types.' - ), - ] = None - ext: ext_1.ExtensionObject | None = None - - @model_validator(mode='after') - def _require_schema_required_group(self) -> Catchment: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('travel_time', 'transport_mode'), ('radius',), ('geometry',),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'Catchment requires at least one of these field groups: travel_time+transport_mode | radius | geometry' - ) diff --git a/src/adcp/types/generated_poc/core/collection.py b/src/adcp/types/generated_poc/core/collection.py deleted file mode 100644 index 093ddae7a..000000000 --- a/src/adcp/types/generated_poc/core/collection.py +++ /dev/null @@ -1,139 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/collection.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..enums import collection_cadence, collection_kind, collection_relationship, collection_status -from ..enums import production_quality as production_quality_1 -from . import collection_distribution -from . import content_rating as content_rating_1 -from . import deadline_policy as deadline_policy_1 -from . import ext as ext_1 -from . import limited_series as limited_series_1 -from . import special as special_1 -from . import talent as talent_1 - - -class RelatedCollection(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - collection_id: Annotated[ - str, - Field(description="The related collection's collection_id within this seller's response"), - ] - relationship: Annotated[ - collection_relationship.CollectionRelationship, - Field(description='How the collections are related'), - ] - - -class Collection(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Annotated[ - str | None, - Field( - description="Publisher namespace that owns this collection. Required for supply-path verification when the declaration is read from a cross-origin authoritative document; it must match the publisher whose origin delegated retrieval. A pointer alone cannot claim another publisher's collection. May be omitted for a publisher-origin document, where that origin supplies the namespace.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - collection_id: Annotated[ - str, - Field( - description="Publisher-assigned identifier for this collection. Declared in the publisher's adagents.json collections array. Products reference collections via collection selectors with publisher_domain and collection_ids. Use distribution identifiers for cross-seller matching across publishers." - ), - ] - name: Annotated[str, Field(description='Human-readable collection name')] - kind: Annotated[ - collection_kind.CollectionKind | None, - Field( - description="What kind of content program this is. Helps agents interpret installments correctly. A channel collection represents the persistent programmed stream; its installments, when present, are scheduled airings or programming blocks. Defaults to 'series' when absent." - ), - ] = None - description: Annotated[str | None, Field(description='What the collection is about')] = None - genre: Annotated[ - list[str] | None, - Field( - description='Genre tags. When genre_taxonomy is present, values are taxonomy IDs (e.g., IAB Content Taxonomy 3.0 codes). Otherwise free-form.' - ), - ] = None - genre_taxonomy: Annotated[ - str | None, - Field( - description="Taxonomy system for genre values (e.g., 'iab_content_3.0'). When present, genre values should be valid taxonomy IDs. Recommended for machine-readable brand safety evaluation." - ), - ] = None - language: Annotated[ - str | None, Field(description="Primary language (BCP 47 tag, e.g., 'en', 'es-MX')") - ] = None - content_rating: Annotated[ - content_rating_1.ContentRating | None, - Field( - description='Baseline content rating for the collection. Individual installments may override this.' - ), - ] = None - cadence: Annotated[ - collection_cadence.CollectionCadence | None, - Field(description='How frequently the collection releases new installments'), - ] = None - season: Annotated[ - str | None, - Field( - description="Current or most recent season identifier (e.g., '3', '2026', 'spring_2026'). A lightweight label — not a full season object." - ), - ] = None - status: Annotated[ - collection_status.CollectionStatus | None, - Field(description='Lifecycle status of the collection'), - ] = None - production_quality: Annotated[ - production_quality_1.ProductionQuality | None, - Field( - description='Production quality tier. Seller-declared. Maps to OpenRTB content.prodq (professional=1, prosumer=2, ugc=3).' - ), - ] = None - talent: Annotated[ - list[talent_1.Talent] | None, - Field( - description='Hosts, recurring cast, creators associated with the collection. Each talent entry may include a brand_url linking to their brand.json identity.' - ), - ] = None - special: Annotated[ - special_1.Special | None, - Field( - description='When present, this collection is a special — content anchored to a real-world event or occasion. Individual installments may override with their own event context.' - ), - ] = None - limited_series: Annotated[ - limited_series_1.LimitedSeries | None, - Field( - description='When present, this collection is a limited series — a bounded run with a defined arc, installment count, and end date.' - ), - ] = None - distribution: Annotated[ - list[collection_distribution.CollectionDistribution] | None, - Field( - description="Where this collection is distributed. Each entry maps the collection to a publisher platform using host property IDs, identifiers, or both. For channel collections this is the carriage map. It is a publisher assertion for discovery, not sales authorization; buyers validate owner-sold carriage against the host publisher's collection-scoped authorized_agents declaration. Collections SHOULD include at least one platform-independent identifier (imdb_id, gracenote_id, eidr_id) when available." - ), - ] = None - deadline_policy: Annotated[ - deadline_policy_1.DeadlinePolicy | None, - Field( - description="Default deadline rules for installments of this collection. Agents compute absolute deadlines from each installment's scheduled_at and these lead times. Installments with explicit deadlines override this policy. Only meaningful when installments carry scheduled_at; a continuously programmed channel collection without enumerated installments has no deadlines to derive." - ), - ] = None - related_collections: Annotated[ - list[RelatedCollection] | None, - Field( - description="Relationships to other collections (spin-offs, companion collections, etc.). Each entry references another collection by collection_id within the same publisher's adagents.json." - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/collection_delivery_metrics.py b/src/adcp/types/generated_poc/core/collection_delivery_metrics.py deleted file mode 100644 index b18260eec..000000000 --- a/src/adcp/types/generated_poc/core/collection_delivery_metrics.py +++ /dev/null @@ -1,24 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/collection_delivery_metrics.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from pydantic import Field - -from . import collection_ref as collection_ref_1 -from .delivery_metrics import DeliveryMetrics - - -class CollectionDeliveryMetrics(DeliveryMetrics): - collection_ref: collection_ref_1.CollectionReference - collection_name: Annotated[ - str | None, - Field( - description='Current human-readable collection name. Convenience metadata only; collection_ref is stable identity.' - ), - ] = None - impressions: Any - spend: Any diff --git a/src/adcp/types/generated_poc/core/collection_distribution.py b/src/adcp/types/generated_poc/core/collection_distribution.py deleted file mode 100644 index 8cb4f0c3b..000000000 --- a/src/adcp/types/generated_poc/core/collection_distribution.py +++ /dev/null @@ -1,63 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/collection_distribution.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, model_validator - -from ..enums import distribution_identifier_type -from . import property_id - - -class Identifier(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Annotated[ - distribution_identifier_type.DistributionIdentifierType, - Field(description='Type of distribution identifier'), - ] - value: Annotated[str, Field(description='The identifier value')] - - -class CollectionDistribution(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Annotated[ - str, - Field( - description="Domain of the publisher platform where the collection is distributed (e.g., 'youtube.com', 'spotify.com'). Property IDs and publisher-scoped identifiers in this object resolve in this publisher's namespace." - ), - ] - property_ids: Annotated[ - list[property_id.PropertyId] | None, - Field( - description="Publisher-scoped property IDs from publisher_domain's adagents.json that carry this collection. For owner-sold channel inventory, these identify the host apps without requiring the host to publish channel-owner placement definitions.", - min_length=1, - ), - ] = None - identifiers: Annotated[ - list[Identifier] | None, - Field( - description='Identifiers for the collection on this publisher, including publisher-scoped channel/EPG identifiers and platform-independent metadata identifiers', - min_length=1, - ), - ] = None - - @model_validator(mode='after') - def _require_schema_required_group(self) -> CollectionDistribution: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('property_ids',), ('identifiers',),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'CollectionDistribution requires at least one of these field groups: property_ids | identifiers' - ) diff --git a/src/adcp/types/generated_poc/core/collection_list_ref.py b/src/adcp/types/generated_poc/core/collection_list_ref.py deleted file mode 100644 index 622bb3130..000000000 --- a/src/adcp/types/generated_poc/core/collection_list_ref.py +++ /dev/null @@ -1,26 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/collection_list_ref.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - - -class CollectionListReference(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[AnyUrl, Field(description='URL of the agent managing the collection list')] - list_id: Annotated[ - str, Field(description='Identifier for the collection list within the agent', min_length=1) - ] - auth_token: Annotated[ - str | None, - Field( - description='JWT or other authorization token for accessing the list. Optional if the list is public or caller has implicit access.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/collection_property_delivery_metrics.py b/src/adcp/types/generated_poc/core/collection_property_delivery_metrics.py deleted file mode 100644 index ba0a0ff65..000000000 --- a/src/adcp/types/generated_poc/core/collection_property_delivery_metrics.py +++ /dev/null @@ -1,45 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/collection_property_delivery_metrics.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from pydantic import Field - -from . import collection_ref as collection_ref_1 -from . import identifier as identifier_1 -from . import property_ref as property_ref_1 -from .delivery_metrics import DeliveryMetrics - - -class CollectionPropertyDeliveryMetrics(DeliveryMetrics): - collection_ref: collection_ref_1.CollectionReference - collection_name: Annotated[ - str | None, - Field(description='Current human-readable collection name. Convenience metadata only.'), - ] = None - publisher_domain: Annotated[ - str, - Field( - description='Publisher or platform authority that namespaces the property identifier, including for an unregistered surface.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - identifier: Annotated[ - identifier_1.Identifier, - Field(description='Operational identity of the property that delivered.'), - ] - property_ref: Annotated[ - property_ref_1.PropertyReference | None, - Field( - description="Canonical publisher-scoped catalog identity when available. Its publisher_domain MUST equal the row's publisher_domain." - ), - ] = None - property_name: Annotated[ - str | None, - Field(description='Current human-readable property name. Convenience metadata only.'), - ] = None - impressions: Any - spend: Any diff --git a/src/adcp/types/generated_poc/core/collection_ref.py b/src/adcp/types/generated_poc/core/collection_ref.py deleted file mode 100644 index a842ae070..000000000 --- a/src/adcp/types/generated_poc/core/collection_ref.py +++ /dev/null @@ -1,30 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/collection_ref.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - - -class CollectionReference(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Annotated[ - str, - Field( - description='Domain where the adagents.json declaring this collection is hosted.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - collection_id: Annotated[ - str, - Field( - description="Collection ID from the publisher's adagents.json collection catalog.", - min_length=1, - ), - ] diff --git a/src/adcp/types/generated_poc/core/collection_selection.py b/src/adcp/types/generated_poc/core/collection_selection.py deleted file mode 100644 index 334f3ed50..000000000 --- a/src/adcp/types/generated_poc/core/collection_selection.py +++ /dev/null @@ -1,46 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/collection_selection.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from . import collection_selector -from . import ext as ext_1 - - -class CollectionSelection1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - mode: Literal['selected'] = 'selected' - collections: Annotated[ - list[collection_selector.CollectionSelector], - Field( - description="Complete required collection set as domain-qualified selectors with explicit collection_ids; the domain-only bulk-grant form is authorization scoping, not selection. publisher_domain may be an external channel owner's domain.", - min_length=1, - ), - ] - ext: ext_1.ExtensionObject | None = None - - -class CollectionSelection2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - mode: Literal['default'] = 'default' - ext: ext_1.ExtensionObject | None = None - - -CollectionSelection = Annotated[ - CollectionSelection1 | CollectionSelection2, - Field( - description="Complete purchased collection set within a product. Mode selected names the complete required set (partial selection requires product collection_targeting_allowed: true; exactly restating the full bundle is an inherent match); mode default accepts the product's full bundle. Package readback MUST echo the committed selection here as concrete selectors, materializing any collection_list composition; a purchase made with mode default therefore reads back as mode selected naming the product's complete bundle. Rejection of a schema-valid selector that references an unknown collection_id or publisher_domain uses REFERENCE_NOT_FOUND per the error-code registry (INVALID_REQUEST applies only to schema-invalid shapes such as a selected-mode item omitting collection_ids). Sellers reject unknown, duplicate, or partially applicable selectors rather than silently changing the selection.", - discriminator='mode', - title='Collection Selection', - ), -] diff --git a/src/adcp/types/generated_poc/core/collection_selector.py b/src/adcp/types/generated_poc/core/collection_selector.py deleted file mode 100644 index 08e0e9415..000000000 --- a/src/adcp/types/generated_poc/core/collection_selector.py +++ /dev/null @@ -1,30 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/collection_selector.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - - -class CollectionSelector(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Annotated[ - str, - Field( - description="Domain where the adagents.json declaring these collections is hosted (e.g., 'mrbeast.com'). The collections array in that file contains the authoritative collection definitions.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - collection_ids: Annotated[ - list[str] | None, - Field( - description='Collection IDs from the adagents.json collections array. Each ID must match a collection_id declared in that file. Omit to reference all collections declared in that file.', - min_length=1, - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/committed_metric.py b/src/adcp/types/generated_poc/core/committed_metric.py deleted file mode 100644 index edb267c8c..000000000 --- a/src/adcp/types/generated_poc/core/committed_metric.py +++ /dev/null @@ -1,158 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/committed_metric.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - -from ..enums import attribution_methodology as attribution_methodology_1 -from ..enums import available_metric -from ..enums import completion_source as completion_source_1 -from ..enums import lift_dimension as lift_dimension_1 -from ..enums import viewability_standard as viewability_standard_1 -from . import brand_ref, duration, vendor_metric_id - - -class Qualifier(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - viewability_standard: Annotated[ - viewability_standard_1.ViewabilityStandard | None, - Field(description='Viewability standard for this vendor metric, as a closed enum.'), - ] = None - completion_source: Annotated[ - completion_source_1.CompletionSource | None, - Field(description='Completion attestation source for this vendor metric.'), - ] = None - attribution_methodology: Annotated[ - attribution_methodology_1.AttributionMethodology | None, - Field(description='Attribution methodology for this vendor outcome metric.'), - ] = None - attribution_window: Annotated[ - duration.Duration | None, - Field( - description='Attribution window for this vendor outcome metric, as a structured duration.' - ), - ] = None - lift_dimension: Annotated[ - lift_dimension_1.LiftDimension | None, - Field(description='Brand-lift dimension for this vendor metric.'), - ] = None - - -class QualifierModel(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - viewability_standard: Annotated[ - viewability_standard_1.ViewabilityStandard | None, - Field( - description='Viewability standard the seller commits to for this metric. MUST be set when `metric_id` ∈ {`viewable_impressions`, `viewable_rate`, `measurable_impressions`, `viewed_seconds`, `viewed_seconds_percentiles`, `viewed_seconds_histogram`} and the seller commits to a specific standard; absence means the contract leaves the standard unspecified and reconciliation falls back to whatever standard the delivery report carries on its `viewability.standard` field. A distribution whose standard remains unspecified MUST NOT be combined with any other row, including another row with an unspecified standard.' - ), - ] = None - completion_source: Annotated[ - completion_source_1.CompletionSource | None, - Field( - description="Source of `completion_rate` attestation. MUST be set when `metric_id` is `completion_rate` and the seller commits to a specific source — `seller_attested` when the player/ad server's own completion event is the contract, `vendor_attested` when a third-party measurement vendor (anchored on the matching `performance_standard.vendor` BrandRef) is the contract. Absence means the contract leaves the source unspecified and reconciliation falls back to whatever the delivery report happens to carry." - ), - ] = None - attribution_methodology: Annotated[ - attribution_methodology_1.AttributionMethodology | None, - Field( - description='How attribution between ad exposure and outcome events was computed. SHOULD be set when `metric_id` is an outcome metric (`conversions`, `conversion_value`, `roas`, `cost_per_acquisition`, `incremental_sales_lift`, `brand_lift`, `foot_traffic`, `conversion_lift`, `brand_search_lift`, `units_sold`, `new_to_brand_rate`, `new_to_brand_units`, `leads`) and the seller commits to a specific methodology. `deterministic_purchase` is the retail-media default; `modeled` covers MMM and clean-room outputs; `probabilistic` and `panel_based` cover their respective methodologies. Two outcome rows with the same `metric_id` and different `attribution_methodology` are not interchangeable and must not be summed.' - ), - ] = None - attribution_window: Annotated[ - duration.Duration | None, - Field( - description="Time window over which outcome attribution is computed. **Object-valued, not string** — MUST be a structured duration object like `{interval: 14, unit: 'days'}`, NEVER a shorthand string like `'14d'`. SHOULD be set when `metric_id` is an outcome metric and the seller commits to a specific window. Common windows: `{interval: 7, unit: 'days'}`, `{interval: 14, unit: 'days'}`, `{interval: 30, unit: 'days'}`, `{interval: 90, unit: 'days'}`. Two outcome rows with the same `metric_id` and `attribution_methodology` but different `attribution_window` represent the same metric measured over different periods — the join on `(metric_id, qualifier)` keeps them as separate rows so buyers don't accidentally aggregate across windows." - ), - ] = None - lift_dimension: Annotated[ - lift_dimension_1.LiftDimension | None, - Field( - description='Which dimension of `brand_lift` this row represents — awareness, consideration, favorability, purchase intent, or ad recall. MUST be set when `metric_id` is `brand_lift` and the seller commits to (or is reporting) a specific dimension. Brand-lift vendors (Kantar, Upwave, Cint, DoubleVerify) report each dimension separately with its own sample size and confidence interval; combining them into a single number is a category error. Two `brand_lift` rows under different `lift_dimension` are different surveyed outcomes and must not be summed.' - ), - ] = None - - -class CommittedMetric1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['standard'], - Field(description='Standard metric from the closed `available-metric.json` enum.'), - ] = 'standard' - metric_id: Annotated[ - available_metric.AvailableMetric, Field(description='Identifier for the standard metric.') - ] - qualifier: Annotated[ - QualifierModel | None, - Field( - description="Disambiguates metrics whose definition varies by qualifier. Today carries five keys — `viewability_standard` (MRC vs GroupM viewability), `completion_source` (seller- vs vendor-attested completion), `attribution_methodology` (how attribution was computed for outcome metrics), `attribution_window` (the time window over which outcomes were attributed), and `lift_dimension` (which dimension of brand_lift this row represents — awareness, consideration, etc.). Required when the underlying `metric_id` has multiple incompatible measurement paths AND the seller commits to a specific one. Symmetric on `missing_metrics`. Reserved for additive qualifiers in future minors — schema is closed (`additionalProperties: false`); new keys ship explicitly. **Heterogeneous value types**: qualifier values can be either string enums (`viewability_standard`, `completion_source`, `attribution_methodology`, `lift_dimension`) or structured objects (`attribution_window` is a duration `{interval, unit}`). Consumers MUST dispatch on key name to know value shape; structured-value qualifiers join on canonical (key-sorted) deep equality so `{interval: 14, unit: 'days'}` and `{unit: 'days', interval: 14}` resolve to the same partition. Rate-style metrics (`new_to_brand_rate`, `engagement_rate`, etc.) inherit the methodology of their numerator — when a rate carries `attribution_methodology` qualifier, it applies to the underlying conversions/events being rated." - ), - ] = None - committed_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when this metric became part of the contract. Day-1 commitments use `create_media_buy.confirmed_at`; mid-flight additions use the time the amendment was accepted.' - ), - ] - - -class Qualifier1(Qualifier): - pass - - -class CommittedMetric2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['vendor'], - Field(description='Vendor-defined metric, identified by the tuple `(vendor, metric_id)`.'), - ] = 'vendor' - vendor: Annotated[ - brand_ref.BrandReference, - Field( - description="Vendor that defines and computes this metric. The vendor's `brand.json` `agents[type='measurement']` is the canonical anchor." - ), - ] - metric_id: Annotated[ - vendor_metric_id.VendorMetricId, - Field(description="Identifier for the metric within the vendor's vocabulary."), - ] - methodology_version: Annotated[ - str | None, - Field( - description="Optional pin of the vendor's `get_adcp_capabilities.measurement.metrics[].methodology_version` that this commitment is contracted against. When present, the seller commits to reporting values computed under this methodology version; a vendor-side methodology change that alters the value definition is a contract change and SHOULD surface as a new appended entry with its own `committed_at`, never a silent substitution. Absence means the contract does not pin a version and buyers MUST treat methodology changes as untracked. Opaque string — compare for equality, do not parse." - ), - ] = None - qualifier: Annotated[ - Qualifier1 | None, - Field( - description='Optional qualifier disambiguating commitments to the same vendor metric measured under different methodologies or windows. Same closed key set as standard-scope entries; new keys ship explicitly.' - ), - ] = None - committed_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when this vendor metric became part of the contract.' - ), - ] - - -CommittedMetric = Annotated[ - CommittedMetric1 | CommittedMetric2, - Field( - description="One metric in a package's binding reporting contract. Each entry uses `scope` as the discriminator and identifies a standard or vendor-defined metric that the seller has committed to populate in delivery reports.", - discriminator='scope', - title='Committed Metric', - ), -] diff --git a/src/adcp/types/generated_poc/core/compact_task_input_required.py b/src/adcp/types/generated_poc/core/compact_task_input_required.py deleted file mode 100644 index 2973fa742..000000000 --- a/src/adcp/types/generated_poc/core/compact_task_input_required.py +++ /dev/null @@ -1,25 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/compact_task_input_required.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from . import context as context_1 -from . import error -from . import ext as ext_1 - - -class CompactTaskInputRequired(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - reason: Annotated[str | None, Field(max_length=200, min_length=1)] = None - message: Annotated[str | None, Field(max_length=2000)] = None - errors: list[error.Error] | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/compact_task_submitted.py b/src/adcp/types/generated_poc/core/compact_task_submitted.py deleted file mode 100644 index 56ea1ecc0..000000000 --- a/src/adcp/types/generated_poc/core/compact_task_submitted.py +++ /dev/null @@ -1,26 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/compact_task_submitted.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from . import context as context_1 -from . import error -from . import ext as ext_1 - - -class CompactTaskSubmitted(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Literal['submitted'] = 'submitted' - task_id: Annotated[str, Field(min_length=1)] - message: Annotated[str | None, Field(max_length=2000)] = None - errors: list[error.Error] | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/compact_task_working.py b/src/adcp/types/generated_poc/core/compact_task_working.py deleted file mode 100644 index 6e5601ed3..000000000 --- a/src/adcp/types/generated_poc/core/compact_task_working.py +++ /dev/null @@ -1,25 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/compact_task_working.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictFloat - -from . import context as context_1 -from . import ext as ext_1 - - -class CompactTaskWorking(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - percentage: Annotated[StrictFloat | None, Field(ge=0.0, le=100.0)] = None - current_step: Annotated[str | None, Field(max_length=500)] = None - total_steps: Annotated[SchemaInt | None, Field(ge=1)] = None - step_number: Annotated[SchemaInt | None, Field(ge=1)] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/content_rating.py b/src/adcp/types/generated_poc/core/content_rating.py deleted file mode 100644 index 8b8da4063..000000000 --- a/src/adcp/types/generated_poc/core/content_rating.py +++ /dev/null @@ -1,24 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/content_rating.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..enums import content_rating_system - - -class ContentRating(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - system: Annotated[ - content_rating_system.ContentRatingSystem, Field(description='Rating system used') - ] - rating: Annotated[ - str, Field(description="Rating value within the system (e.g., 'TV-PG', 'R', 'explicit')") - ] diff --git a/src/adcp/types/generated_poc/core/context.py b/src/adcp/types/generated_poc/core/context.py deleted file mode 100644 index d22c456a9..000000000 --- a/src/adcp/types/generated_poc/core/context.py +++ /dev/null @@ -1,14 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/context.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict - - -class ContextObject(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) diff --git a/src/adcp/types/generated_poc/core/creative_approval_scope.py b/src/adcp/types/generated_poc/core/creative_approval_scope.py deleted file mode 100644 index ed2909a68..000000000 --- a/src/adcp/types/generated_poc/core/creative_approval_scope.py +++ /dev/null @@ -1,27 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/creative_approval_scope.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..enums import creative_approval_status -from . import indicator_scope - - -class ScopedCreativeApproval(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - indicator_scope.IndicatorScope, - Field(description='Publisher or placement to which this approval outcome applies.'), - ] - approval_status: creative_approval_status.CreativeApprovalStatus - rejection_reason: Annotated[ - str | None, Field(description='Human-readable explanation when this scope is rejected.') - ] = None diff --git a/src/adcp/types/generated_poc/core/creative_asset.py b/src/adcp/types/generated_poc/core/creative_asset.py deleted file mode 100644 index 82b03e922..000000000 --- a/src/adcp/types/generated_poc/core/creative_asset.py +++ /dev/null @@ -1,158 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/creative_asset.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel, StrictFloat, StringConstraints, model_validator - -from ..enums import creative_status -from . import canonical_format_kind, creative_assets -from . import format_id as format_id_1 -from . import format_option_ref as format_option_ref_1 -from . import industry_identifier, placement_ref -from . import provenance as provenance_1 -from . import representation_selection as representation_selection_1 -from . import rights_constraint -from .assets import asset_union - - -class Input(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[str, Field(description='Human-readable name for this preview variant')] - macros: Annotated[ - dict[str, str] | None, Field(description='Macro values to apply for this preview') - ] = None - context_description: Annotated[ - str | None, - Field(description='Natural language description of the context for AI-generated content'), - ] = None - - -class Assets(RootModel[list[asset_union.AssetVariant]]): - root: Annotated[list[asset_union.AssetVariant], Field(min_length=1)] - - -class CreativeAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - creative_id: Annotated[ - str, - Field( - description='Unique identifier for the creative. Stable across legacy named-format and 3.1+ canonical-format paths — a creative registered against `format_id` retains the same `creative_id` when later viewed through a canonical-format flatten.' - ), - ] - name: Annotated[str, Field(description='Human-readable creative name')] - format_id: Annotated[ - format_id_1.FormatReferenceStructuredObject | None, - Field( - deprecated=True, - description='**DEPRECATED in 3.2.** Legacy named-format path retained for older 3.x peers. New creative assets use `format_kind` and optional `format_option_ref`.', - ), - ] = None - format_kind: Annotated[ - canonical_format_kind.CanonicalFormatKind | None, - Field( - description='Canonical format name this creative targets (e.g., `image`, `video_hosted`). Mutually exclusive with deprecated `format_id`.' - ), - ] = None - format_option_ref: Annotated[ - format_option_ref_1.FormatOptionReference | None, - Field( - description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product has multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the creative to a single declaration. Product-scoped refs require an enclosing target product/package context.' - ), - ] = None - representation_selection: Annotated[ - representation_selection_1.RepresentationSelection | None, - Field( - description='Readback lineage to the complete CreativeRepresentationSet revision and representation selected before this seller-bound creative was synced.' - ), - ] = None - assets: Annotated[ - dict[Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], asset_union.AssetVariant | Assets], - Field( - description='Assets required by the format, keyed by asset_id or canonical asset_group_id. Each slot value is either a single asset object or an array of asset objects (for slots with `min`/`max > 1` like carousel `cards` or responsive_creative `headlines`). Each asset value carries an `asset_type` discriminator that selects the matching asset schema, including reference assets such as `published_post` when a product accepts already-published post references.' - ), - ] - component_assets: Annotated[ - dict[Annotated[str, StringConstraints(pattern=r'^[a-z][a-z0-9_]*$')], creative_assets.CreativeAssets] | None, - Field( - description='Component-addressed canonical asset maps for `coordinated_placements`. Keys match coordinated component IDs. This field is preserved by creative-library sync and list readback; it MUST be absent for every other format kind.' - ), - ] = None - inputs: Annotated[ - list[Input] | None, - Field( - description='Preview contexts for generative formats - defines what scenarios to generate previews for' - ), - ] = None - tags: Annotated[ - list[str] | None, Field(description='User-defined tags for organization and searchability') - ] = None - status: Annotated[ - creative_status.CreativeStatus | None, - Field( - description="For generative creatives: set to 'approved' to finalize, 'rejected' to request regeneration with updated assets/message. Omit for non-generative creatives (system will set based on processing state)." - ), - ] = None - weight: Annotated[ - StrictFloat | None, - Field( - description='Optional delivery weight for creative rotation when uploading via create_media_buy or update_media_buy (0-100). If omitted, platform determines rotation. Only used during upload to media buy - not stored in creative library.', - ge=0.0, - le=100.0, - ), - ] = None - placement_refs: Annotated[ - list[placement_ref.PlacementReference] | None, - Field( - description="Optional structured product-context placement references where this uploaded creative should run when uploading via create_media_buy or update_media_buy. These items always use placement-ref product-context semantics, even when tolerated additional members make an item resemble placement-identity. Receivers match only against the target package's committed placement set; kind and seller_agent are non-authoritative for routing and MUST NOT expand or reinterpret that set. A receiver MUST reject a ref when the enclosing product and committed set do not yield one unambiguous match. New senders SHOULD include publisher_domain for publisher-catalog placements. If omitted, creative runs on all buyer-targetable placements. If both `placement_refs` and legacy `placement_ids` are present, `placement_refs` wins and receivers MUST ignore `placement_ids`. Only used during upload to media buy - not stored in creative library.", - min_length=1, - ), - ] = None - placement_ids: Annotated[ - list[str] | None, - Field( - deprecated=True, - description='Legacy shorthand array of placement IDs where this creative should run when uploading via create_media_buy or update_media_buy. New senders SHOULD use `placement_refs` because placement IDs are publisher-scoped and strings are ambiguous in multi-publisher products. If omitted, creative runs on all buyer-targetable placements. If `placement_refs` is also present, receivers MUST ignore this field. Only used during upload to media buy - not stored in creative library.', - min_length=1, - ), - ] = None - industry_identifiers: Annotated[ - list[industry_identifier.IndustryIdentifier] | None, - Field( - description='Industry-standard or market-specific identifiers for this creative (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). In broadcast and scheduled audio/video buying, these identifiers tie the creative to rotation instructions, clearance records, and traffic systems. A creative may have multiple identifiers when different systems reference the same asset. Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' - ), - ] = None - provenance: Annotated[ - provenance_1.Provenance | None, - Field( - description='Provenance metadata for this creative. Serves as the default provenance for all manifests and assets within this creative. A manifest or asset with its own provenance replaces this object entirely (no field-level merging).' - ), - ] = None - rights: Annotated[ - list[rights_constraint.RightsConstraint] | None, - Field( - description='Rights constraints that MUST survive sync, package assignment, and list readback. Buyer-carried constraints and references do not authorize serving; the seller evaluates them under media_buy.rights_attestations and its adcp.attestations policy.', - min_length=1, - ), - ] = None - - @model_validator(mode='after') - def _require_schema_required_group(self) -> CreativeAsset: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('format_id',), ('format_kind',),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'CreativeAsset requires at least one of these field groups: format_id | format_kind' - ) diff --git a/src/adcp/types/generated_poc/core/creative_assets.py b/src/adcp/types/generated_poc/core/creative_assets.py deleted file mode 100644 index e4cdb5529..000000000 --- a/src/adcp/types/generated_poc/core/creative_assets.py +++ /dev/null @@ -1,32 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/creative_assets.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Any, Annotated - -from pydantic import Field, RootModel, StringConstraints - -from .assets import asset_union - - -class CreativeAssets1(RootModel[list[asset_union.AssetVariant]]): - root: Annotated[list[asset_union.AssetVariant], Field(min_length=1)] - - -class CreativeAssets( - RootModel[dict[Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], asset_union.AssetVariant | CreativeAssets1]] -): - root: Annotated[ - dict[Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], asset_union.AssetVariant | CreativeAssets1], - Field( - description='Map of canonical asset-group or legacy asset identifiers to supplied creative assets. Values are either a single discriminated asset or a non-empty repeatable asset array.', - title='Creative Assets', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) diff --git a/src/adcp/types/generated_poc/core/creative_assignment.py b/src/adcp/types/generated_poc/core/creative_assignment.py deleted file mode 100644 index c2b5857fe..000000000 --- a/src/adcp/types/generated_poc/core/creative_assignment.py +++ /dev/null @@ -1,70 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/creative_assignment.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictFloat - -from . import placement_ref - - -class RotationMode(StrEnum): - weighted = 'weighted' - even = 'even' - sequential = 'sequential' - random = 'random' - - -class CreativeAssignment(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - creative_id: Annotated[str, Field(description='Unique identifier for the creative')] - weight: Annotated[ - StrictFloat | None, - Field( - description="Relative delivery weight for this creative (0–100). Valid when the package's effective rotation_mode is weighted, including the backward-compatible default when rotation_mode is omitted. Weights determine impression distribution proportionally — a creative with weight 2 gets twice the delivery of weight 1. When omitted, the creative receives equal weight with other unweighted creatives. A weight of 0 means the creative is assigned but paused (receives no delivery).", - ge=0.0, - le=100.0, - ), - ] = None - rotation_mode: Annotated[ - RotationMode | None, - Field( - description='Package-scoped rotation policy repeated on assignment rows for wire compatibility. Omission means weighted, preserving existing weight behavior. Every assignment in a package MUST resolve to the same effective mode: weighted uses relative weights; even balances delivery across eligible assignments; sequential cycles through sequence_position in ascending order within each group; random makes an independent uniform selection from eligible assignments. Sellers MUST reject conflicting effective modes rather than choose one by array order.' - ), - ] = None - group_id: Annotated[ - str | None, - Field( - description="Package-local creative pool identifier. The identifier has no meaning outside this package. Assignments that omit group_id belong to the package's default group; one eligible creative is selected from each applicable group per serving opportunity.", - min_length=1, - ), - ] = None - sequence_position: Annotated[ - SchemaInt | None, - Field( - description="One-based order within the assignment's package-local group. Required only for sequential rotation and unique within that group.", - ge=1, - ), - ] = None - placement_refs: Annotated[ - list[placement_ref.PlacementReference] | None, - Field( - description="Optional structured product-context refs routing this creative within already-purchased package inventory. These items always use placement-ref product-context semantics, even when tolerated additional members make an item resemble placement-identity. Receivers match only against the package's committed placement set; kind and seller_agent are non-authoritative for routing and MUST NOT expand or reinterpret that set. A receiver MUST reject a ref when the enclosing product and committed set do not yield one unambiguous match. This field never narrows purchased inventory; use targeting_overlay.placement_selection for that. Every ref MUST fall within the package's committed placement selection. New senders SHOULD include publisher_domain for publisher-catalog placements. When omitted, the creative runs across the purchased placements compatible with its format. If both placement_refs and legacy placement_ids are present, placement_refs wins.", - min_length=1, - ), - ] = None - placement_ids: Annotated[ - list[str] | None, - Field( - deprecated=True, - description='Legacy shorthand routing IDs within already-purchased inventory. This field never narrows purchased inventory; use targeting_overlay.placement_selection. New senders SHOULD use placement_refs because IDs are publisher-scoped. If placement_refs is also present, receivers MUST ignore this field.', - min_length=1, - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/creative_brief.py b/src/adcp/types/generated_poc/core/creative_brief.py deleted file mode 100644 index 5a5c4a3f4..000000000 --- a/src/adcp/types/generated_poc/core/creative_brief.py +++ /dev/null @@ -1,146 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/creative_brief.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field - -from ..enums import disclosure_persistence, disclosure_position -from . import reference_asset - - -class Objective(StrEnum): - awareness = 'awareness' - consideration = 'consideration' - conversion = 'conversion' - retention = 'retention' - engagement = 'engagement' - - -class Messaging(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - headline: Annotated[str | None, Field(description='Primary headline')] = None - tagline: Annotated[str | None, Field(description='Supporting tagline or sub-headline')] = None - cta: Annotated[str | None, Field(description='Call-to-action text')] = None - key_messages: Annotated[ - list[str] | None, Field(description='Key messages to communicate in priority order') - ] = None - - -class Jurisdiction(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{2}(-[A-Z0-9]{1,3})?$'} - - -class RequiredDisclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - text: Annotated[str, Field(description='The disclosure text that must appear in the creative')] - position: Annotated[ - disclosure_position.DisclosurePosition | None, - Field( - description='Where the disclosure should appear within the creative. prominent: clearly visible in the main creative area; footer: at the bottom of visual creatives; audio: spoken in audio/video creatives; subtitle: displayed in the subtitle or closed-caption track; overlay: superimposed on video content; end_card: displayed on video end card; pre_roll: spoken or displayed before main content; companion: in companion ad unit alongside primary creative' - ), - ] = None - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field( - description="Jurisdictions where this disclosure is required. ISO 3166-1 alpha-2 country codes or ISO 3166-2 subdivision codes (e.g., 'US', 'GB', 'US-NJ', 'CA-QC'). If omitted, the disclosure applies to all jurisdictions in the campaign.", - min_length=1, - ), - ] = None - regulation: Annotated[ - str | None, - Field( - description="The regulation or legal authority requiring this disclosure (e.g., 'SEC Rule 156', 'FCA COBS 4.5', 'FDA 21 CFR 202')" - ), - ] = None - min_duration_ms: Annotated[ - SchemaInt | None, - Field( - description='Minimum display duration in milliseconds. For video/audio disclosures, how long the disclosure must be visible or audible. For static formats, how long the disclosure must remain on screen before any auto-advance.', - ge=1, - ), - ] = None - language: Annotated[ - str | None, - Field( - description="Language of the disclosure text as a BCP 47 language tag (e.g., 'en', 'fr-CA', 'es'). When omitted, the disclosure is assumed to match the creative's language." - ), - ] = None - persistence: Annotated[ - disclosure_persistence.DisclosurePersistence | None, - Field( - description='How long the disclosure must persist during content playback or display. When omitted, the creative agent should use the most restrictive persistence mode supported by the target format.' - ), - ] = None - - -class Compliance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class CreativeBrief(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[reference_asset.ReferenceAsset] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/creative_consumption.py b/src/adcp/types/generated_poc/core/creative_consumption.py deleted file mode 100644 index 1028709f6..000000000 --- a/src/adcp/types/generated_poc/core/creative_consumption.py +++ /dev/null @@ -1,34 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/creative_consumption.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictFloat - - -class CreativeConsumption(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - tokens: Annotated[ - SchemaInt | None, - Field(description='LLM or generation tokens consumed during creative generation.', ge=0), - ] = None - images_generated: Annotated[ - SchemaInt | None, Field(description='Number of images produced during generation.', ge=0) - ] = None - renders: Annotated[ - SchemaInt | None, - Field(description='Number of render passes performed (video, animation).', ge=0), - ] = None - duration_seconds: Annotated[ - StrictFloat | None, - Field( - description='Processing time billed, in seconds. For compute-time pricing models.', - ge=0.0, - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/creative_delivery_metrics.py b/src/adcp/types/generated_poc/core/creative_delivery_metrics.py deleted file mode 100644 index 3f5ce36b8..000000000 --- a/src/adcp/types/generated_poc/core/creative_delivery_metrics.py +++ /dev/null @@ -1,33 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/creative_delivery_metrics.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from pydantic import Field, StrictFloat - -from .delivery_metrics import DeliveryMetrics - - -class CreativeDeliveryMetrics(DeliveryMetrics): - creative_id: Annotated[ - str, Field(description='Creative identifier matching the creative assignment') - ] - creative_name: Annotated[ - str | None, - Field( - description='Optional human-readable creative name current when the report is generated. Convenience metadata only: names may change and buyers MUST use creative_id as the stable identity.' - ), - ] = None - weight: Annotated[ - StrictFloat | None, - Field( - description='Observed delivery share for this creative within the package during the reporting period, expressed as a percentage (0-100). Reflects actual delivery distribution, not a configured setting.', - ge=0.0, - le=100.0, - ), - ] = None - impressions: Any - spend: Any diff --git a/src/adcp/types/generated_poc/core/creative_filters.py b/src/adcp/types/generated_poc/core/creative_filters.py deleted file mode 100644 index 5dff5f57f..000000000 --- a/src/adcp/types/generated_poc/core/creative_filters.py +++ /dev/null @@ -1,138 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/creative_filters.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field, StrictBool - -from ..enums import asset_content_type, creative_status, indicator_type -from . import account_ref, canonical_format_kind -from . import ext as ext_1 -from . import format_id - - -class CreativeFilters(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - accounts: Annotated[ - list[account_ref.AccountReference] | None, - Field( - description='Filter creatives by owning accounts. Useful for agencies managing multiple client accounts.', - min_length=1, - ), - ] = None - statuses: Annotated[ - list[creative_status.CreativeStatus] | None, - Field(description='Filter by creative approval statuses', min_length=1), - ] = None - tags: Annotated[ - list[str] | None, - Field(description='Filter by creative tags (all tags must match)', min_length=1), - ] = None - tags_any: Annotated[ - list[str] | None, - Field(description='Filter by creative tags (any tag must match)', min_length=1), - ] = None - name_contains: Annotated[ - str | None, - Field(description='Filter by creative names containing this text (case-insensitive)'), - ] = None - creative_ids: Annotated[ - list[str] | None, - Field(description='Filter by specific creative IDs', max_length=100, min_length=1), - ] = None - created_after: Annotated[ - AwareDatetime | None, - Field(description='Filter creatives created after this date (ISO 8601)'), - ] = None - created_before: Annotated[ - AwareDatetime | None, - Field(description='Filter creatives created before this date (ISO 8601)'), - ] = None - updated_after: Annotated[ - AwareDatetime | None, - Field(description='Filter creatives last updated after this date (ISO 8601)'), - ] = None - updated_before: Annotated[ - AwareDatetime | None, - Field(description='Filter creatives last updated before this date (ISO 8601)'), - ] = None - assigned_to_packages: Annotated[ - list[str] | None, - Field( - description='Filter creatives assigned to any of these packages. Sales-agent-specific — standalone creative agents SHOULD ignore this filter.', - min_length=1, - ), - ] = None - media_buy_ids: Annotated[ - list[str] | None, - Field( - description='Filter creatives assigned to any of these media buys. Sales-agent-specific — standalone creative agents SHOULD ignore this filter.', - min_length=1, - ), - ] = None - unassigned: Annotated[ - StrictBool | None, - Field( - description='Filter for unassigned creatives when true, assigned creatives when false. Sales-agent-specific — standalone creative agents SHOULD ignore this filter.' - ), - ] = None - has_served: Annotated[ - StrictBool | None, - Field( - description='When true, return only creatives that have served at least one impression. When false, return only creatives that have never served.' - ), - ] = None - indicator_types: Annotated[ - list[indicator_type.IndicatorType] | None, - Field( - description='Return creatives with at least one package assignment carrying any requested current indicator type. Values within this field use OR logic; this field composes with other filters using AND logic. Sales-agent-specific: sellers support this filter only when media_buy.relationship_notifications.projection_tasks includes list_creatives. Other agents SHOULD ignore it and apply remaining filters. Buyers needing exact results MUST verify capability support and paginate the outer result set; assignment_projection: matching bounds nested rows.', - min_length=1, - ), - ] = None - concept_ids: Annotated[ - list[str] | None, - Field( - description='Filter by creative concept IDs. Concepts group related creatives across sizes and formats (e.g., Flashtalking concepts, Celtra campaign folders, CM360 creative groups).', - min_length=1, - ), - ] = None - format_ids: Annotated[ - list[format_id.FormatReferenceStructuredObject] | None, - Field( - deprecated=True, - description='Deprecated in AdCP 3.2; removed in AdCP 4.0. Filter legacy named-format creatives. Use `format_kinds` for canonical libraries.', - min_length=1, - ), - ] = None - format_kinds: Annotated[ - list[canonical_format_kind.CanonicalFormatKind] | None, - Field( - description='Filter by canonical format kinds. Returns creatives matching any supplied kind.', - min_length=1, - ), - ] = None - asset_types: Annotated[ - list[asset_content_type.AssetContentType] | None, - Field( - description="Filter by asset types present on direct object values in the creative's top-level `assets` map. A creative matches when any directly assigned object has an `asset_type` in this array (OR within this field); this filter is conjunctive with every other active filter (AND across fields). Do not inspect array-valued slots or recurse into nested asset fields such as `cards[].media`; broader traversal is deferred. Agents that do not implement this filter MUST ignore it and apply the remaining filters rather than reject the request. Exact asset-type values use the shared AssetContentType vocabulary; `published_post` selects existing-published-post reference creatives without relying on publisher-specific format IDs.", - min_length=1, - ), - ] = None - has_variables: Annotated[ - StrictBool | None, - Field( - description='When true, return only creatives with dynamic variables (DCO). When false, return only static creatives.' - ), - ] = None - ext: Annotated[ - ext_1.ExtensionObject | None, - Field( - description='Vendor-namespaced extension parameters for seller- or platform-specific creative filter criteria not covered by standard fields. Keys MUST be namespaced under a vendor or platform key (e.g., ext.gam, ext.platform_x). Sellers MUST treat all values as untrusted buyer input; avoid unbounded logging or labels, and do not interpolate values into caller-visible error strings, LLM prompts, SQL queries, or system commands without sanitization. Persistent use of an extension key across multiple buyers is a signal to propose standardization.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/creative_item.py b/src/adcp/types/generated_poc/core/creative_item.py deleted file mode 100644 index 8d7210e69..000000000 --- a/src/adcp/types/generated_poc/core/creative_item.py +++ /dev/null @@ -1,65 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/creative_item.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - - -class CreativeItem1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_kind: Annotated[ - Literal['media'], - Field(description='Discriminator indicating this is a media asset with content_uri'), - ] = 'media' - asset_type: Annotated[ - str, - Field( - description='Type of asset. Common types: thumbnail_image, product_image, featured_image, logo' - ), - ] - asset_id: Annotated[ - str, Field(description='Unique identifier for the asset within the creative') - ] - content_uri: Annotated[AnyUrl, Field(description='URL for media assets (images, videos, etc.)')] - - -class CreativeItem2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_kind: Annotated[ - Literal['text'], - Field(description='Discriminator indicating this is a text asset with content'), - ] = 'text' - asset_type: Annotated[ - str, - Field( - description='Type of asset. Common types: headline, body_text, cta_text, price_text, sponsor_name, author_name, click_url' - ), - ] - asset_id: Annotated[ - str, Field(description='Unique identifier for the asset within the creative') - ] - content: Annotated[ - str | list[str], - Field( - description='Text content for text-based assets like headlines, body text, CTA text, etc.' - ), - ] - - -CreativeItem = Annotated[ - CreativeItem1 | CreativeItem2, - Field( - description='Item within a multi-asset creative format. Used for carousel products, native ad components, and other formats composed of multiple distinct elements.', - discriminator='asset_kind', - title='Creative Item', - ), -] diff --git a/src/adcp/types/generated_poc/core/creative_locale_policy.py b/src/adcp/types/generated_poc/core/creative_locale_policy.py deleted file mode 100644 index 947ce6956..000000000 --- a/src/adcp/types/generated_poc/core/creative_locale_policy.py +++ /dev/null @@ -1,26 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/creative_locale_policy.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from . import locale_tag - - -class CreativeLocalePolicy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - accepted_language_ranges: Annotated[ - list[locale_tag.LanguageTag], - Field( - description='Concrete canonical BCP 47 language ranges accepted by this format option. RFC 4647 Basic Filtering is directional: seller range fr accepts variant fr-CA, but seller range fr-CA does not accept variant fr or fr-FR. Use zxx explicitly for language-neutral creative; und means unknown and is not a wildcard.', - max_length=50, - min_length=1, - ), - ] diff --git a/src/adcp/types/generated_poc/core/creative_localization.py b/src/adcp/types/generated_poc/core/creative_localization.py deleted file mode 100644 index ac8e26459..000000000 --- a/src/adcp/types/generated_poc/core/creative_localization.py +++ /dev/null @@ -1,123 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/creative_localization.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel, StringConstraints - -from . import locale_tag, localized_creative_asset - - -class UnmatchedLocaleAction(StrEnum): - serve_default = 'serve_default' - do_not_serve = 'do_not_serve' - - -class Source(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - locale_variant_id: Annotated[ - str, - Field( - description='Buyer-assigned stable identity for the source locale variant. This is a separate namespace from build_variant_id and the served execution variant_id.', - max_length=255, - min_length=1, - ), - ] - locale: locale_tag.LanguageTag - - -class LocaleFallback(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - language_range: Annotated[ - locale_tag.LanguageTag, - Field( - description='Canonical concrete BCP 47 range matched against progressively truncated requested ranges. Wildcards are not supported.' - ), - ] - locale_variant_id: Annotated[ - str, - Field( - description='Source or target locale variant to serve when this explicit fallback rule matches.', - max_length=255, - min_length=1, - ), - ] - - -class Assets(RootModel[list[localized_creative_asset.LocalizedCreativeAsset]]): - root: Annotated[list[localized_creative_asset.LocalizedCreativeAsset], Field(min_length=1)] - - -class TargetVariant(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - locale_variant_id: Annotated[ - str, - Field( - description='Buyer-assigned stable identity for this target locale variant. The same value round-trips through sync_creatives, list_creatives, and localized delivery reporting.', - max_length=255, - min_length=1, - ), - ] - locale: locale_tag.LanguageTag - assets: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - localized_creative_asset.LocalizedCreativeAsset | Assets, - ], - Field( - description='Materialized locale-specific asset overrides keyed by the same slot IDs as the source creative. Missing slots inherit source assets.' - ), - ] - - -class CreativeLocalization(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - source: Annotated[ - Source, - Field( - description="Identity and locale of the source variant. Its asset payload is the parent creative's top-level `assets` object. Source identifies production provenance; it is not implicitly the serving default." - ), - ] - target_variants: Annotated[ - list[TargetVariant], - Field( - description='Complete desired target-locale set for this creative. May be empty for a monolingual source-only creative. Every target contains materialized locale-specific asset overrides. Missing slots inherit source assets; after inheritance every resolved variant MUST satisfy the selected creative format.', - max_length=50, - min_length=0, - ), - ] - locale_fallbacks: Annotated[ - list[LocaleFallback] | None, - Field( - description='Optional explicit language-family substitutions evaluated only when strict RFC 4647 Lookup finds no equal available tag for the current requested preference. For each preference in order, the seller checks progressively truncated ranges from most to least specific and applies the first rule whose language_range equals that candidate. A rule may map a regional request to a different materialized regional variant, but no substitution is inferred when a rule is absent.', - max_length=50, - min_length=1, - ), - ] = None - default_locale_variant_id: Annotated[ - str, - Field( - description='The source or target locale_variant_id used when neither strict RFC 4647 Lookup nor an explicit locale_fallbacks rule matches and unmatched_locale_action is serve_default.', - max_length=255, - min_length=1, - ), - ] - unmatched_locale_action: Annotated[ - UnmatchedLocaleAction, - Field( - description='Required seller behavior when neither strict RFC 4647 Lookup nor an explicit locale_fallbacks rule matches. serve_default serves default_locale_variant_id; do_not_serve makes this creative ineligible for that opportunity.' - ), - ] diff --git a/src/adcp/types/generated_poc/core/creative_localization_readback.py b/src/adcp/types/generated_poc/core/creative_localization_readback.py deleted file mode 100644 index b8c16b238..000000000 --- a/src/adcp/types/generated_poc/core/creative_localization_readback.py +++ /dev/null @@ -1,135 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/creative_localization_readback.json -# timestamp: 2026-10-03T00:36:50+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Any, Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel, StringConstraints - -from . import locale_tag, localized_creative_asset - - -class UnmatchedLocaleAction(StrEnum): - serve_default = 'serve_default' - do_not_serve = 'do_not_serve' - - -class LocaleFallback(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - language_range: locale_tag.LanguageTag - locale_variant_id: Annotated[str, Field(max_length=255, min_length=1)] - - -class ResolvedAssets1(RootModel[list[localized_creative_asset.LocalizedCreativeAsset]]): - root: Annotated[list[localized_creative_asset.LocalizedCreativeAsset], Field(min_length=1)] - - -class ResolvedAssets( - RootModel[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - localized_creative_asset.LocalizedCreativeAsset | ResolvedAssets1, - ] - ] -): - root: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - localized_creative_asset.LocalizedCreativeAsset | ResolvedAssets1, - ], - Field(description='Complete resolved assets for this locale, keyed by creative slot.'), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - - - -class Variants1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - locale_variant_id: Annotated[ - str, - Field( - description='Buyer-assigned stable locale-variant identity from the request.', - max_length=255, - min_length=1, - ), - ] - locale: locale_tag.LanguageTag - role: Literal['source'] = 'source' - assets: ResolvedAssets - - -class Variants2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - locale_variant_id: Annotated[ - str, - Field( - description='Buyer-assigned stable locale-variant identity from the request.', - max_length=255, - min_length=1, - ), - ] - locale: locale_tag.LanguageTag - role: Literal['target'] = 'target' - assets: ResolvedAssets - - -Variants = Annotated[ - Variants1 | Variants2, - Field(discriminator='role'), -] - - -class CreativeLocalizationReadback(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - default_locale_variant_id: Annotated[ - str, - Field( - description='The source or target locale variant used after strict Lookup and explicit fallback rules both miss when unmatched_locale_action is serve_default.', - max_length=255, - min_length=1, - ), - ] - unmatched_locale_action: Annotated[ - UnmatchedLocaleAction, - Field( - description='Final action after strict Lookup and explicit locale_fallbacks both miss.' - ), - ] - locale_matching: Annotated[ - Literal['rfc4647_lookup'], - Field( - description="Strict RFC 4647 section 3.4 Lookup applied to an ordered language-preference list supplied by the seller's serving environment. AdCP does not carry or define how that list is derived. The seller progressively truncates each requested range and matches only an available canonical tag equal to that range; it does not prefix-match sibling regional tags." - ), - ] = 'rfc4647_lookup' - locale_fallbacks: Annotated[ - list[LocaleFallback] | None, - Field( - description='Exact buyer-declared language-family substitutions preserved from sync. For each requested preference, rules are checked from the most specific progressively truncated range to the least specific only after strict Lookup finds no equal available tag for that preference.', - max_length=50, - min_length=1, - ), - ] = None - variants: Annotated[ - list[Variants], - Field( - description='The source variant followed by every target variant, or only the source for monolingual topology. Assets are fully resolved after source inheritance. The enclosing creative status applies to the set as a whole.', - max_length=51, - min_length=1, - ), - ] diff --git a/src/adcp/types/generated_poc/core/creative_manifest.py b/src/adcp/types/generated_poc/core/creative_manifest.py deleted file mode 100644 index 6b273820c..000000000 --- a/src/adcp/types/generated_poc/core/creative_manifest.py +++ /dev/null @@ -1,124 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/creative_manifest.json -# timestamp: 2026-10-04T06:16:29+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel, StringConstraints, model_validator - -from . import brand_ref, canonical_format_kind, creative_assets -from . import ext as ext_1 -from . import format_id as format_id_1 -from . import format_option_ref as format_option_ref_1 -from . import industry_identifier -from . import provenance as provenance_1 -from . import representation_selection as representation_selection_1 -from . import rights_constraint -from .assets import asset_union - - -def _normalize_asset_models(value: Any) -> Any: - if isinstance(value, AdCPBaseModel): - return value.model_dump(mode='json', exclude_none=True) - if isinstance(value, list): - return [_normalize_asset_models(item) for item in value] - return value - - -class Assets(RootModel[list[asset_union.AssetVariant]]): - root: Annotated[list[asset_union.AssetVariant], Field(min_length=1)] - - -class CreativeManifest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - - @model_validator(mode='before') - @classmethod - def _coerce_standalone_assets(cls, data: Any) -> Any: - if not isinstance(data, dict) or not isinstance(data.get('assets'), dict): - return data - return { - **data, - 'assets': {key: _normalize_asset_models(value) for key, value in data['assets'].items()}, - } - - format_id: Annotated[ - format_id_1.FormatReferenceStructuredObject | None, - Field( - deprecated=True, - description='**DEPRECATED in 3.2.** Legacy named-format path retained for 3.x compatibility. New manifests use canonical `format_kind` and, when product routing requires it, `format_option_ref`. Mutually exclusive with format_kind.', - ), - ] = None - format_kind: Annotated[ - canonical_format_kind.CanonicalFormatKind | None, - Field( - description="Canonical 3.2 path. The canonical format name this manifest targets (e.g., `image`, `video_hosted`, `audio_vast`, `seller_rendered_stateful_display`, `coordinated_placements`). Selects the contract against which the seller validates the manifest's assets. Mutually exclusive with deprecated `format_id`." - ), - ] = None - format_option_ref: Annotated[ - format_option_ref_1.FormatOptionReference | None, - Field( - description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product carries multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the manifest to a single declaration. Product-scoped refs require an enclosing target product/package context.' - ), - ] = None - representation_selection: Annotated[ - representation_selection_1.RepresentationSelection | None, - Field( - description='Present when this seller-bound manifest was selected from a CreativeRepresentationSet. Preserves creative, complete revision digest, and selected representation lineage through sync and reporting.' - ), - ] = None - assets: Annotated[ - dict[Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], asset_union.AssetVariant | Assets], - Field( - description="Map of slot keys to actual asset content. Legacy named-format path: each key matches an `asset_id` from the format's `assets` array (e.g., 'banner_image', 'clickthrough_url', 'video_file', 'vast_tag'). 3.1+ canonical-format path: each key matches an `asset_group_id` from the format's `slots` declaration drawn from the canonical vocabulary registry (e.g., 'images_landscape', 'video', 'published_post', 'landing_page_url', 'vast_tag', 'script', 'creative_brief'). Either path produces the same envelope shape; only the slot-key vocabulary differs.\n\nEach slot value is **either** a single asset object (most slots — image, video, published_post, vast_tag, landing_page_url, etc.) **or** an array of asset objects (slots with `min`/`max` counts on the format declaration — `cards` on `image_carousel`, `headlines` / `descriptions` / `images_landscape` on `responsive_creative`, etc.). Single-vs-array shape is governed by the format's `slots[].min` and `slots[].max` parameters: when `max > 1` (or when the slot is conceptually a pool), the value MUST be an array; when the slot is single-valued, the value MUST be a single object. Each asset value (single or array element) carries an `asset_type` discriminator (image, video, audio, vast, daast, text, markdown, url, html, css, webhook, javascript, brief, catalog, published_post, zip, card) that selects the matching asset schema. Validators with OpenAPI-style discriminator support use `asset_type` to report errors against only the selected branch instead of all branches." - ), - ] - component_assets: Annotated[ - dict[Annotated[str, StringConstraints(pattern=r'^[a-z][a-z0-9_]*$')], creative_assets.CreativeAssets] | None, - Field( - description="Component-addressed asset maps for `coordinated_placements`. Each key MUST match one `params.components[].component_id`; its value supplies that component's canonical slots. Shared assets remain in top-level `assets` and are injected only into components named by `shared_slots[].consumed_by`. This namespace allows two components to use the same canonical slot name, such as `image_main`, without collision. It MUST be absent for non-`coordinated_placements` manifests." - ), - ] = None - brand: Annotated[ - brand_ref.BrandReference | None, - Field( - description="Brand identity reference (BrandRef — `domain` plus optional `brand_id` for house-of-brands; plus optional inline `brand_kit_override` for per-creative tweaks where brand.json is missing/stale). When present, the seller pulls master brand identity (logo, palette, fonts, voice, and visual guidelines) from the brand's brand.json automatically; supported fields present in `brand_kit_override` take precedence, and all other master identity fields continue to come from brand.json. Catalogs supply product or item payload. Catalog item asset groups — including an item-level `logo` for a property or franchise — are item identity selected through format field bindings; they do not override brand.json's master logo or other brand identity fields. v2 formats no longer redeclare brand_logo / brand_colors / brand_voice as explicit slots — brand identity is implicit context." - ), - ] = None - rights: Annotated[ - list[rights_constraint.RightsConstraint] | None, - Field( - description='Rights constraints attached to this creative. Buyer-carried fields are informational until a serving party evaluates an issuer-bound attestation reference under its own policy. Only a verified, unexpired, unrevoked, digest-matched evaluation can support serving authorization; verification_url is never authority.' - ), - ] = None - industry_identifiers: Annotated[ - list[industry_identifier.IndustryIdentifier] | None, - Field( - description='Industry-standard or market-specific identifiers for this specific manifest (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). When present, overrides creative-level identifiers. Use when different format versions of the same source creative have distinct traffic identifiers (e.g., the :15 and :30 cuts, or separate TV and radio versions). Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' - ), - ] = None - provenance: Annotated[ - provenance_1.Provenance | None, - Field( - description='Provenance metadata for this creative manifest. Serves as the default provenance for all assets in this manifest. An asset with its own provenance replaces this object entirely (no field-level merging).' - ), - ] = None - ext: ext_1.ExtensionObject | None = None - - @model_validator(mode='after') - def _require_schema_required_group(self) -> CreativeManifest: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('format_id',), ('format_kind',),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'CreativeManifest requires at least one of these field groups: format_id | format_kind' - ) diff --git a/src/adcp/types/generated_poc/core/creative_operation_format_declaration.py b/src/adcp/types/generated_poc/core/creative_operation_format_declaration.py deleted file mode 100644 index 744aded7f..000000000 --- a/src/adcp/types/generated_poc/core/creative_operation_format_declaration.py +++ /dev/null @@ -1,69 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/creative_operation_format_declaration.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, Field, StrictBool - -from ..enums import channels -from . import ( - canonical_format_kind, - creative_locale_policy, - format_id, - macro_resolution_capability, - platform_extension_ref, -) - - -class SellerPreference(StrEnum): - preferred = 'preferred' - accepted = 'accepted' - discouraged = 'discouraged' - - -class CreativeOperationFormatDeclaration(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description='Stable publisher- or product-declaration identity that this creative operation can satisfy. When publisher_domain is present, this field is required.' - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description='Publisher namespace for format_option_id when this operation claims compatibility with a publisher declaration.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Creative-route processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. These capabilities describe build, validation, or preview processing and do not grant seller production authority.', - min_length=1, - ), - ] = None - technical_requirements_complete: StrictBool | None = None - display_name: str | None = None - sample_render_url: AnyUrl | None = None - applies_to_channels: list[channels.MediaChannel] | None = None - seller_preference: SellerPreference | None = None - locale_policy: creative_locale_policy.CreativeLocalePolicy | None = None - canonical_formats_only: StrictBool | None = False - experimental: StrictBool | None = False - format_shape: str | None = None - v1_format_ref: Annotated[ - list[format_id.FormatReferenceStructuredObject] | None, Field(min_length=1) - ] = None - format_schema: platform_extension_ref.PlatformExtensionReference | None = None - format_kind: canonical_format_kind.CanonicalFormatKind - params: Annotated[ - dict[str, Any], - Field( - description='Canonical creative-shape parameters. Validate against the schema selected by format_kind; custom params validate against the fetched format_schema.' - ), - ] diff --git a/src/adcp/types/generated_poc/core/creative_policy.py b/src/adcp/types/generated_poc/core/creative_policy.py deleted file mode 100644 index e7e2b1f8b..000000000 --- a/src/adcp/types/generated_poc/core/creative_policy.py +++ /dev/null @@ -1,102 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/creative_policy.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, StrictBool - -from ..enums import co_branding_requirement, landing_page_requirement - - -class ProvenanceRequirements(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - require_digital_source_type: Annotated[ - StrictBool | None, - Field( - description='When true, the seller requires creatives to include a `digital_source_type` field in their provenance, set to a valid value from the `digital-source-type` enum (not null or absent). Submissions that omit this field are rejected with `PROVENANCE_DIGITAL_SOURCE_TYPE_MISSING`. Supports EU AI Act Art. 50 and CA SB 942 compliance workflows where AI disclosure metadata must be present at the protocol level.' - ), - ] = None - require_synthetic_depiction: Annotated[ - StrictBool | None, - Field( - description='When true, the seller requires creatives to include an assessed `synthetic_depiction` boolean in their resolved provenance. Both true and false satisfy the requirement; absence means unassessed and is rejected with `PROVENANCE_SYNTHETIC_DEPICTION_MISSING`. This gate requires a declaration only — it does not establish consent, legality, or independent verification.' - ), - ] = None - require_disclosure_metadata: Annotated[ - StrictBool | None, - Field( - description='When true, the seller requires creatives to include a `disclosure` object in their provenance with `disclosure.required` set to a boolean value (true or false). When `disclosure.required` is true, at least one entry in `disclosure.jurisdictions` is expected. Submissions that omit `disclosure.required` are rejected with `PROVENANCE_DISCLOSURE_MISSING`.' - ), - ] = None - require_embedded_provenance: Annotated[ - StrictBool | None, - Field( - description='When true, the seller requires creatives to include at least one `embedded_provenance` entry. For pipelines where sidecar metadata is stripped by intermediaries, this ensures provenance data persists through delivery. Submissions that omit `embedded_provenance` are rejected with `PROVENANCE_EMBEDDED_MISSING`.' - ), - ] = None - - -class AcceptedVerifier(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent. MUST use the `https://` scheme. The seller calls this URL via `get_creative_features` to verify a buyer's claim; the seller has already vetted the endpoint and accepts responsibility for outbound calls to it." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional canonical `feature_id` the seller will request against this agent (e.g., `encypher.markers_present_v2`). When present, the buyer's `verify_agent.feature_id` SHOULD either match this value or be omitted. When absent, the seller selects a feature from the agent's `governance.creative_features` catalog at evaluation time. Resolves the selector ambiguity that would otherwise let two compliant receivers reach different verdicts." - ), - ] = None - providers: Annotated[ - list[str] | None, - Field( - description="Optional `provider` labels this agent verifies (e.g., `['Encypher', 'Digimarc']`). When present, sellers SHOULD only invoke this agent for `embedded_provenance[]` / `watermarks[]` entries whose `provider` field matches one of these labels — letting buyers pre-flight whether their attached evidence is verifiable against the seller's allowlist. When absent, the agent is treated as provider-agnostic.", - min_length=1, - ), - ] = None - - -class CreativePolicy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - co_branding: Annotated[ - co_branding_requirement.CoBrandingRequirement, Field(description='Co-branding requirement') - ] - landing_page: Annotated[ - landing_page_requirement.LandingPageRequirement, - Field(description='Landing page requirements'), - ] - templates_available: Annotated[ - StrictBool, Field(description='Whether creative templates are provided') - ] - provenance_required: Annotated[ - StrictBool | None, - Field( - description='Whether creatives must include provenance metadata. When true, the seller requires buyers to attach provenance declarations to creative submissions. The seller may independently verify claims via get_creative_features.' - ), - ] = None - provenance_requirements: Annotated[ - ProvenanceRequirements | None, - Field( - description='Structured provenance requirements for creatives. Refines `provenance_required`: when `provenance_required` is true, the fields in this object specify which provenance features the seller requires. When `provenance_required` is false or absent, this object SHOULD be absent; if present, receivers MUST ignore it. Existing seller agents that do not read this object are unaffected; the wire shape does not change for them. Sellers that publish a requirement here MUST enforce it on creative submission: a `sync_creatives` request that omits a required field is rejected with the corresponding `PROVENANCE_*` error code (see error-code.json), and a creative whose provenance claim is contradicted by an independent verification (`get_creative_features` against a governance agent the seller operates or has allowlisted via `accepted_verifiers`) is rejected with `PROVENANCE_CLAIM_CONTRADICTED`. This is the structural-rejection surface; the truth-of-claim surface lives in `get_creative_features`. Field-level requirements are seller-enforced — JSON Schema validation does not check them.' - ), - ] = None - accepted_verifiers: Annotated[ - list[AcceptedVerifier] | None, - Field( - description='Governance agents the seller operates, has allowlisted, or otherwise trusts to verify provenance claims via `get_creative_features`. Buyers attaching a `verify_agent` pointer on `embedded_provenance[]` or `watermarks[]` MUST select an `agent_url` that appears in this list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments) - the buyer is *representing* that they used a verifier the seller will recognize, not asserting unilateral routing. Sellers MUST reject `sync_creatives` submissions whose `verify_agent.agent_url` does not match any entry here with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. The seller is the verifier-of-record: it is the seller, not the buyer, that decides which agent it will call. Publishing the list lets buyers pre-flight their creative shape against `get_products` and lets multiple buyers converge on the same verifier without coordinating with each other.', - min_length=1, - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/creative_representation.py b/src/adcp/types/generated_poc/core/creative_representation.py deleted file mode 100644 index ce878b927..000000000 --- a/src/adcp/types/generated_poc/core/creative_representation.py +++ /dev/null @@ -1,73 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/creative_representation.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, model_validator - -from .canonical_format_kind import CanonicalFormatKind -from .creative_manifest import CreativeManifest - - -class Source(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - system: Annotated[str, Field(description='Source system namespace.', min_length=1)] - source_representation: Annotated[ - str, - Field( - description="Source system's human-readable name for this representation.", min_length=1 - ), - ] - source_id: Annotated[ - str | None, - Field(description='Optional immutable source-system identifier for this representation.'), - ] = None - - -class CreativeRepresentation(CreativeManifest): - model_config = ConfigDict( - extra='allow', - json_schema_extra={ - 'not': { - 'anyOf': [ - {'required': ['format_id']}, - {'required': ['format_option_ref']}, - {'required': ['representation_selection']}, - ] - } - }, - ) - representation_id: Annotated[ - str, - Field( - description='Stable representation identifier, unique within the enclosing CreativeRepresentationSet revision.', - min_length=1, - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - source: Source - format_kind: Annotated[ - CanonicalFormatKind, - Field( - description="Canonical 3.2 path. The canonical format name this manifest targets (e.g., `image`, `video_hosted`, `audio_vast`, `seller_rendered_stateful_display`, `coordinated_placements`). Selects the contract against which the seller validates the manifest's assets. Mutually exclusive with deprecated `format_id`." - ), - ] - - @model_validator(mode='before') - @classmethod - def _reject_seller_bound_manifest_fields(cls, data: Any) -> Any: - """Representations cannot carry seller-side manifest selectors.""" - if isinstance(data, dict): - forbidden = ('format_id', 'format_option_ref', 'representation_selection') - present = [field for field in forbidden if field in data] - if present: - raise ValueError( - 'creative representations must not include ' + ', '.join(present) - ) - return data diff --git a/src/adcp/types/generated_poc/core/creative_representation_set.py b/src/adcp/types/generated_poc/core/creative_representation_set.py deleted file mode 100644 index acb11b8f9..000000000 --- a/src/adcp/types/generated_poc/core/creative_representation_set.py +++ /dev/null @@ -1,53 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/creative_representation_set.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from . import creative_representation, creative_revision_id -from . import provenance as provenance_1 - - -class CreativeRepresentationSet(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - creative_id: Annotated[ - str, - Field( - description='Stable logical creative identifier across all representations and later seller-bound manifests.', - min_length=1, - ), - ] - revision_id: Annotated[ - creative_revision_id.CreativeRevisionId, - Field( - description='Buyer-assigned immutable revision identity for this complete representation set. Changing any revision-bearing part of the set requires a new revision_id; selection does not.' - ), - ] - revision_content_digest: Annotated[ - str, - Field( - description='SHA-256 of the RFC 8785 JCS canonical revision content defined by this schema. The producer computes it over the complete representation set after removing exactly the top-level $schema, creative_id, revision_id, revision_content_digest, and name properties. `$schema` is transport/schema-location metadata and never mints creative content identity. Resolvers MUST recompute and reject a mismatch; selection readback carries the verified digest so downstream sellers bind the revision to the complete source set rather than only the selected manifest.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] - name: Annotated[str, Field(min_length=1)] - representations: Annotated[ - list[creative_representation.CreativeRepresentation], - Field( - description="Complete ordered set of equivalent representations. `representation_id` values MUST be unique within this array. Unsupported entries remain byte-for-byte unchanged; neither filtering nor selection changes this revision's canonical content.", - min_length=1, - ), - ] - provenance: Annotated[ - provenance_1.Provenance | None, - Field( - description='Default provenance for every retained representation and for the selected seller-bound output unless a representation supplies a narrower provenance object.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/creative_revision_id.py b/src/adcp/types/generated_poc/core/creative_revision_id.py deleted file mode 100644 index ab06a0778..000000000 --- a/src/adcp/types/generated_poc/core/creative_revision_id.py +++ /dev/null @@ -1,16 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/creative_revision_id.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from adcp.types._scalar import ScalarStr - - -class CreativeRevisionId(ScalarStr): - __slots__ = () - _constraints = {'max_length': 255, 'min_length': 1} - _json_schema_extra = { - 'description': 'Buyer-assigned identity for one immutable input-content state of a durable creative. Scoped to the parent creative_id. Seller transcoding, normalization, and delivery representations do not change this identity.', - 'title': 'Creative Revision ID', - } diff --git a/src/adcp/types/generated_poc/core/creative_variable.py b/src/adcp/types/generated_poc/core/creative_variable.py deleted file mode 100644 index 1f53dc90c..000000000 --- a/src/adcp/types/generated_poc/core/creative_variable.py +++ /dev/null @@ -1,47 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/creative_variable.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictBool - - -class VariableType(StrEnum): - text = 'text' - image = 'image' - video = 'video' - audio = 'audio' - url = 'url' - number = 'number' - boolean = 'boolean' - color = 'color' - date = 'date' - - -class CreativeVariable(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - variable_id: Annotated[str, Field(description='Variable identifier on the creative platform')] - name: Annotated[str, Field(description='Human-readable variable name')] - variable_type: Annotated[ - VariableType, - Field( - description='Data type of the variable. Each type represents a semantic content slot: text (headlines, body copy), image/video/audio (media URLs), url (clickthrough or tracking URLs), number (prices, counts), boolean (conditional flags like show_discount or is_raining), color (hex color values), date (ISO 8601 date-time for countdowns and offer expirations).' - ), - ] - default_value: Annotated[ - str | None, - Field( - description='Default value used when no dynamic value is provided at serve time. All types are string-encoded: text/image/video/audio/url as literal strings, number as decimal (e.g., "42.99"), boolean as "true"/"false", color as "#RRGGBB", date as ISO 8601 (e.g., "2026-12-25T00:00:00Z").' - ), - ] = None - required: Annotated[ - StrictBool | None, - Field(description='Whether this variable must have a value for the creative to serve'), - ] = False diff --git a/src/adcp/types/generated_poc/core/creative_variant.py b/src/adcp/types/generated_poc/core/creative_variant.py deleted file mode 100644 index 5b0571c9e..000000000 --- a/src/adcp/types/generated_poc/core/creative_variant.py +++ /dev/null @@ -1,79 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/creative_variant.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from . import creative_manifest, creative_revision_id -from . import ext as ext_1 -from . import identifier -from .delivery_metrics import DeliveryMetrics - - -class Artifact(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_id: Annotated[ - identifier.Identifier, Field(description='Property where the artifact appears') - ] - artifact_id: Annotated[str, Field(description='Artifact identifier within the property')] - - -class GenerationContext(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - context_type: Annotated[ - str | None, - Field( - description="Type of context that triggered generation (e.g., 'web_page', 'conversational', 'search', 'app', 'dooh')" - ), - ] = None - artifact: Annotated[ - Artifact | None, - Field( - description='Reference to the content-standards artifact that provided the generation context. Links this variant to the specific piece of content (article, video, podcast segment, etc.) where the ad was placed.' - ), - ] = None - ext: ext_1.ExtensionObject | None = None - - -class CreativeVariant(DeliveryMetrics): - variant_id: Annotated[ - str, - Field( - description='Agent-assigned served-execution identifier. The legacy contract scopes uniqueness to the agent and creative. When the agent advertises creative.supports_revisions, the identifier is agent-unique and MUST NOT be reused for another creative, source revision, locale, or rendered manifest. A revision-capable adapter whose native platform reuses an identifier maps each distinct execution to a distinct AdCP variant_id and may retain the native identifier in ext. When that agent supports variant preview, this ID is its unambiguous lookup key.' - ), - ] - revision_id: Annotated[ - creative_revision_id.CreativeRevisionId | None, - Field( - description='Buyer-authored input revision from which this served execution was derived. Required when that execution derives from a revision-aware creative. Historical reporting may legitimately return an older revision after list_creatives shows a newer current revision. Delivery optimization, generation, transcoding, or representation selection does not mint a revision.' - ), - ] = None - locale_variant_id: Annotated[ - str | None, - Field( - description='Buyer-assigned locale variant that supplied the served assets. Required for every delivered variant of a localized creative, including default fallback delivery. Omitted for unlocalized creatives.', - max_length=255, - min_length=1, - ), - ] = None - manifest: Annotated[ - creative_manifest.CreativeManifest | None, - Field( - description='The rendered creative manifest for this variant — the actual output that was served, not the input assets. In 3.2 it carries canonical `format_kind`, optional `format_option_ref`, and the resolved assets (specific headline, image, video, etc. the platform selected or generated). For Tier 2, shows which asset combination was picked. For Tier 3, contains the generated assets which may differ entirely from the input brand identity. Pass to preview_creative to re-render.' - ), - ] = None - generation_context: Annotated[ - GenerationContext | None, - Field( - description='Input signals that triggered generation of this variant (Tier 3). Describes why the platform created this specific variant. Platforms should provide summarized or anonymized signals rather than raw user input. For web contexts, may include page topic or URL. For conversational contexts, an anonymized content signal. For search, query category or intent. When the content context is managed through AdCP content standards, reference the artifact directly via the artifact field.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/daast_tracker_constraints.py b/src/adcp/types/generated_poc/core/daast_tracker_constraints.py deleted file mode 100644 index e28dfb3b8..000000000 --- a/src/adcp/types/generated_poc/core/daast_tracker_constraints.py +++ /dev/null @@ -1,52 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/daast_tracker_constraints.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import Field, RootModel - -from ..enums import daast_version - - -class DaastEvent(StrEnum): - creativeView = 'creativeView' - start = 'start' - firstQuartile = 'firstQuartile' - midpoint = 'midpoint' - thirdQuartile = 'thirdQuartile' - complete = 'complete' - mute = 'mute' - unmute = 'unmute' - pause = 'pause' - resume = 'resume' - rewind = 'rewind' - skip = 'skip' - progress = 'progress' - close = 'close' - - -class DaastTarget(StrEnum): - linear = 'linear' - companion = 'companion' - - -class DaastOffset(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$'} - - -class DaastVersions(RootModel[list[daast_version.DaastVersion]]): - root: Annotated[list[daast_version.DaastVersion], Field(min_length=1)] - - -class DaastTrackerConstraints(AdCPBaseModel): - daast_event: DaastEvent | None = None - target: DaastTarget | None = DaastTarget.linear - offset: DaastOffset | None = None - daast_versions: DaastVersions | None = None diff --git a/src/adcp/types/generated_poc/core/data_provider_signal_selector.py b/src/adcp/types/generated_poc/core/data_provider_signal_selector.py deleted file mode 100644 index 8de6cfde2..000000000 --- a/src/adcp/types/generated_poc/core/data_provider_signal_selector.py +++ /dev/null @@ -1,97 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/data_provider_signal_selector.json -# timestamp: 2026-10-04T00:53:27+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field - - -class DataProviderSignalSelector1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - data_provider_domain: Annotated[ - str, - Field( - description="Domain where data provider's adagents.json is hosted (e.g., 'polk.com')", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - selection_type: Annotated[ - Literal['all'], - Field( - description='Discriminator indicating all signals from this data provider are included' - ), - ] = 'all' - - -class SignalId(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[a-zA-Z0-9_-]+$'} - - -class DataProviderSignalSelector2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - data_provider_domain: Annotated[ - str, - Field( - description="Domain where data provider's adagents.json is hosted (e.g., 'polk.com')", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - selection_type: Annotated[ - Literal['by_id'], - Field(description='Discriminator indicating selection by specific signal IDs'), - ] = 'by_id' - signal_ids: Annotated[ - list[SignalId], - Field( - description="Specific signal IDs from the data provider's published signal definitions", - min_length=1, - ), - ] - - -class SignalTag(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[a-z0-9_-]+$'} - - -class DataProviderSignalSelector3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - data_provider_domain: Annotated[ - str, - Field( - description="Domain where data provider's adagents.json is hosted (e.g., 'polk.com')", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - selection_type: Annotated[ - Literal['by_tag'], Field(description='Discriminator indicating selection by signal tags') - ] = 'by_tag' - signal_tags: Annotated[ - list[SignalTag], - Field( - description="Signal tags from the data provider's published signal definitions. Selector covers all signals with these tags", - min_length=1, - ), - ] - - -DataProviderSignalSelector = Annotated[ - DataProviderSignalSelector1 | DataProviderSignalSelector2 | DataProviderSignalSelector3, - Field( - description="Selects signals from a data provider's adagents.json signals[]. Used for product definitions and agent authorization. Supports three selection patterns: all signals, specific IDs, or by tags.", - discriminator='selection_type', - title='Data Provider Signal Selector', - ), -] diff --git a/src/adcp/types/generated_poc/core/date_range.py b/src/adcp/types/generated_poc/core/date_range.py deleted file mode 100644 index 6ce039d75..000000000 --- a/src/adcp/types/generated_poc/core/date_range.py +++ /dev/null @@ -1,19 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/date_range.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from datetime import date -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - - -class DateRange(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - start: Annotated[date, Field(description='Start date (inclusive), ISO 8601')] - end: Annotated[date, Field(description='End date (inclusive), ISO 8601')] diff --git a/src/adcp/types/generated_poc/core/datetime_range.py b/src/adcp/types/generated_poc/core/datetime_range.py deleted file mode 100644 index cca442b54..000000000 --- a/src/adcp/types/generated_poc/core/datetime_range.py +++ /dev/null @@ -1,18 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/datetime_range.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - - -class DatetimeRange(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - start: Annotated[AwareDatetime, Field(description='Start timestamp (inclusive), ISO 8601')] - end: Annotated[AwareDatetime, Field(description='End timestamp (inclusive), ISO 8601')] diff --git a/src/adcp/types/generated_poc/core/daypart_target.py b/src/adcp/types/generated_poc/core/daypart_target.py deleted file mode 100644 index a6734ee28..000000000 --- a/src/adcp/types/generated_poc/core/daypart_target.py +++ /dev/null @@ -1,55 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/daypart_target.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field - -from ..enums import day_of_week -from . import iana_timezone - - -class DaypartTarget(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - days: Annotated[ - list[day_of_week.DayOfWeek], - Field( - description='Days of week this window applies to. Use multiple days for compact targeting (e.g., monday-friday in one object).', - min_length=1, - ), - ] - start_hour: Annotated[ - SchemaInt, - Field( - description='Start hour (inclusive), 0-23 in 24-hour format. 0 = midnight, 6 = 6:00am, 18 = 6:00pm.', - ge=0, - le=23, - ), - ] - end_hour: Annotated[ - SchemaInt, - Field( - description='End hour (exclusive), 1-24 in 24-hour format. 10 = 10:00am, 24 = midnight. Must be greater than start_hour.', - ge=1, - le=24, - ), - ] - timezone: Annotated[ - Literal['inventory_local'] | iana_timezone.IanaTimezoneIdentifier | None, - Field( - description="Civil-time clock used to evaluate this window. 'inventory_local' evaluates the hours in the seller-assigned local timezone of each inventory unit that can deliver the impression, such as a screen, venue, station, or publisher property; it never means the buyer, account, or server timezone. A concrete IANA timezone identifier (for example, 'America/New_York', 'CET', or 'UTC') evaluates one shared civil-time clock across the targeted inventory. Omission defaults to 'inventory_local'. Buyers that begin with a user or account preference MUST resolve it to a concrete IANA identifier before sending the daypart; 'user_timezone' and 'account_timezone' are not wire values. For each candidate delivery instant, convert the instant into this clock and compare its resulting local day and hour with the half-open window: a skipped DST hour has no matching instants, while both occurrences of a repeated hour match. This delivery clock is independent of reporting_capabilities.timezone.", - validate_default=True, - ), - ] = 'inventory_local' - label: Annotated[ - str | None, - Field( - description="Optional human-readable name for this time window (e.g., 'Morning Drive', 'Prime Time')" - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/deadline_policy.py b/src/adcp/types/generated_poc/core/deadline_policy.py deleted file mode 100644 index 1f01c52cd..000000000 --- a/src/adcp/types/generated_poc/core/deadline_policy.py +++ /dev/null @@ -1,54 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/deadline_policy.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictBool - - -class MaterialStage(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - stage: Annotated[ - str, - Field( - description="Stage identifier. Standard values: 'draft' (needs seller processing), 'final' (production-ready).", - examples=['draft', 'final'], - ), - ] - lead_days: Annotated[ - SchemaInt, Field(description='Days before scheduled_at this stage is due', ge=0) - ] - label: Annotated[str | None, Field(description='What the seller needs at this stage')] = None - - -class DeadlinePolicy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - booking_lead_days: Annotated[ - SchemaInt | None, - Field(description='Days before scheduled_at by which the placement must be booked', ge=0), - ] = None - cancellation_lead_days: Annotated[ - SchemaInt | None, - Field(description='Days before scheduled_at by which cancellation is penalty-free', ge=0), - ] = None - material_stages: Annotated[ - list[MaterialStage] | None, - Field( - description='Default material submission stages. Items MUST be in chronological order (earliest due first). Agents compute due_at as: installment.scheduled_at minus lead_days.', - min_length=1, - ), - ] = None - business_days_only: Annotated[ - StrictBool | None, - Field( - description='When true, lead_days counts business days (Mon-Fri) rather than calendar days. Defaults to false.' - ), - ] = False diff --git a/src/adcp/types/generated_poc/core/delivery_breakdown_controls.py b/src/adcp/types/generated_poc/core/delivery_breakdown_controls.py deleted file mode 100644 index 6dc9fb561..000000000 --- a/src/adcp/types/generated_poc/core/delivery_breakdown_controls.py +++ /dev/null @@ -1,35 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/delivery_breakdown_controls.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field - -from ..enums import sort_direction as sort_direction_1 -from ..enums import sort_metric - - -class DeliveryBreakdownControls(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - limit: Annotated[ - SchemaInt | None, - Field(description='Maximum number of rows to return. Defaults to 25.', ge=1), - ] = 25 - sort_by: Annotated[ - sort_metric.SortMetric | None, - Field( - description='Metric used to order rows. Falls back to spend when unavailable at this row grain; on fallback sort_direction resets to desc. Rows lacking a value for the applied sort metric order last regardless of direction.' - ), - ] = sort_metric.SortMetric.spend - sort_direction: Annotated[ - sort_direction_1.SortDirection | None, - Field( - description='Direction for sort_by ordering. Sellers MUST apply the requested direction to the applied sort metric; direction has no availability fallback.' - ), - ] = sort_direction_1.SortDirection.desc diff --git a/src/adcp/types/generated_poc/core/delivery_forecast.py b/src/adcp/types/generated_poc/core/delivery_forecast.py deleted file mode 100644 index 03c967c2f..000000000 --- a/src/adcp/types/generated_poc/core/delivery_forecast.py +++ /dev/null @@ -1,96 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/delivery_forecast.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - -from ..enums import demographic_system as demographic_system_1 -from ..enums import forecast_method -from ..enums import forecast_range_unit as forecast_range_unit_1 -from ..enums import reach_unit as reach_unit_1 -from . import ext as ext_1 -from . import forecast_point - - -class DeliveryForecast(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - points: Annotated[ - list[forecast_point.ForecastPoint], - Field( - description='Forecasted delivery data points. For spend curves (default), points at ascending budget levels show how metrics scale with spend. For availability forecasts, points represent total available inventory independent of budget. See forecast_range_unit for interpretation.', - min_length=1, - ), - ] - forecast_range_unit: Annotated[ - forecast_range_unit_1.ForecastRangeUnit | None, - Field( - description="How to interpret the points array. 'spend' (default when omitted): points at ascending budget levels. 'availability': total available inventory, budget omitted. 'reach_freq': points at ascending reach/frequency targets. 'weekly'/'daily': metrics are per-period values. 'clicks'/'conversions': points at ascending outcome targets. 'package': each point is a distinct inventory package." - ), - ] = None - method: Annotated[ - forecast_method.ForecastMethod, Field(description='Method used to produce this forecast') - ] - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code for monetary values in this forecast (spend, budget)' - ), - ] - demographic_system: Annotated[ - demographic_system_1.DemographicSystem | None, - Field( - description='Measurement system for the demographic field. Ensures buyer and seller agree on demographic notation.' - ), - ] = None - demographic: Annotated[ - str | None, - Field( - description='Target demographic code within the specified demographic_system. For Nielsen: P18-49, M25-54, W35+. For BARB: ABC1 Adults, 16-34. For AGF: E 14-49.', - examples=['P18-49', 'A25-54', 'W35+', 'M18-34'], - ), - ] = None - measurement_source: Annotated[ - str | None, - Field( - description='Third-party measurement provider whose data was used to produce this forecast. Distinct from demographic_system, which specifies demographic notation — measurement_source identifies whose data produced the forecast numbers. Should be present when measured_impressions is used. Lowercase slug format.', - examples=[ - 'nielsen', - 'videoamp', - 'comscore', - 'geopath', - 'barb', - 'agf', - 'oztam', - 'kantar', - 'barc', - 'route', - 'rajar', - 'triton', - ], - max_length=64, - pattern='^[a-z0-9_]+$', - ), - ] = None - reach_unit: Annotated[ - reach_unit_1.ReachUnit | None, - Field( - description='Unit of measurement for reach and audience_size metrics in this forecast. Required for cross-channel forecast comparison.' - ), - ] = None - generated_at: Annotated[ - AwareDatetime | None, Field(description='When this forecast was computed') - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='When this forecast expires. After this time, the forecast should be refreshed. Forecast expiry does not affect proposal executability.' - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/delivery_metric_aggregate.py b/src/adcp/types/generated_poc/core/delivery_metric_aggregate.py deleted file mode 100644 index ac7db5ec2..000000000 --- a/src/adcp/types/generated_poc/core/delivery_metric_aggregate.py +++ /dev/null @@ -1,209 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/delivery_metric_aggregate.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictFloat - -from ..enums import attribution_methodology as attribution_methodology_1 -from ..enums import available_metric -from ..enums import completion_source as completion_source_1 -from ..enums import lift_dimension as lift_dimension_1 -from ..enums import viewability_standard as viewability_standard_1 -from . import brand_ref, duration, vendor_metric_id - - -class Qualifier(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - viewability_standard: Annotated[ - viewability_standard_1.ViewabilityStandard | None, - Field( - description='Viewability standard under which this row was measured. MRC and GroupM define materially different thresholds; never sum across standards.' - ), - ] = None - completion_source: Annotated[ - completion_source_1.CompletionSource | None, - Field( - description='Source of `completion_rate` attestation for this row — `seller_attested` from player/ad server, `vendor_attested` from a third-party measurement vendor. The two paths can yield different rates; never sum across sources.' - ), - ] = None - attribution_methodology: Annotated[ - attribution_methodology_1.AttributionMethodology | None, - Field( - description='Attribution methodology under which this outcome row was computed (`deterministic_purchase`, `probabilistic`, `panel_based`, `modeled`). Outcome metrics measured under different methodologies represent materially different numbers; never sum across methodologies.' - ), - ] = None - attribution_window: Annotated[ - duration.Duration | None, - Field( - description="Attribution window for this outcome row. Object-valued duration (`{interval, unit}`), not a shorthand string. Outcome metrics measured over different windows represent the same metric over different time periods; the partition keeps them as separate rows so buyers don't accidentally aggregate." - ), - ] = None - lift_dimension: Annotated[ - lift_dimension_1.LiftDimension | None, - Field( - description='Brand-lift dimension this row represents (awareness, consideration, favorability, purchase intent, or ad recall). Required when `metric_id` is `brand_lift`. Each dimension is a separate surveyed outcome with its own sample size and confidence interval; never sum across dimensions.' - ), - ] = None - - -class Field0(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['standard'], - Field(description='Standard metric from the closed `available-metric.json` enum.'), - ] = 'standard' - metric_id: Annotated[ - available_metric.AvailableMetric, - Field( - description='Identifier for a scalar standard metric. Container tokens and structured distribution identities are committed and selected through their canonical carriers, not represented as numeric aggregate rows.' - ), - ] - qualifier: Annotated[ - Qualifier | None, - Field( - description="Qualifier keys disambiguating this row from sibling rows under the same `metric_id`. Symmetric with `committed_metrics.qualifier` today; expected to diverge in future minors as transparency disclosures buyers don't commit to ship delivery-only. Closed (`additionalProperties: false`) — new qualifier keys ship explicitly." - ), - ] = None - value: Annotated[ - StrictFloat, - Field( - description='Aggregated metric value for this `(metric_id, qualifier)` partition. Heterogeneous by `metric_id` — rate metrics (`viewable_rate`, `completion_rate`) are 0.0–1.0; cost-per metrics (`cost_per_acquisition`, `cost_per_completed_view`) are currency amounts; count metrics (`impressions`, `clicks`) are non-negative integers as numbers; ratio metrics (`roas`) are non-negative numbers. Buyer agents MUST inspect `metric_id` before doing arithmetic — same dispatch convention as `committed_metrics`.' - ), - ] - measurable_impressions: Annotated[ - StrictFloat | None, - Field( - description='Coverage denominator for verification metrics (e.g., `viewable_rate`). Buyers compute coverage as `measurable_impressions / impressions` from the partition.', - ge=0.0, - ), - ] = None - viewable_impressions: Annotated[ - StrictFloat | None, Field(description='Component for `viewable_rate` (numerator).', ge=0.0) - ] = None - impressions: Annotated[ - StrictFloat | None, - Field( - description='Component for rate metrics whose denominator is total impressions (e.g., `completion_rate`, `engagement_rate`).', - ge=0.0, - ), - ] = None - completed_views: Annotated[ - StrictFloat | None, - Field(description='Component for `completion_rate` (numerator).', ge=0.0), - ] = None - spend: Annotated[ - StrictFloat | None, - Field( - description='Component for cost-per metrics (denominator-ish; the cost half of the ratio).', - ge=0.0, - ), - ] = None - conversions: Annotated[ - StrictFloat | None, - Field(description='Component for `cost_per_acquisition` and ROAS-family metrics.', ge=0.0), - ] = None - conversion_value: Annotated[ - StrictFloat | None, Field(description='Component for `roas` (numerator).', ge=0.0) - ] = None - clicks: Annotated[ - StrictFloat | None, - Field(description='Component for `cost_per_click` and click-rate metrics.', ge=0.0), - ] = None - - -class DeliveryMetricAggregate1(Field0): - scope: Literal['standard'] = 'standard' - - -class Qualifier3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - viewability_standard: Annotated[ - viewability_standard_1.ViewabilityStandard | None, - Field( - description='Viewability standard under which this row was measured. MRC and GroupM define materially different thresholds; never sum across standards.' - ), - ] = None - completion_source: Annotated[ - completion_source_1.CompletionSource | None, - Field( - description='Attestation source for a vendor completion-style metric — seller_attested from player/ad server, vendor_attested from an independent measurement path. Applicability is defined by the vendor metric; never sum across sources.' - ), - ] = None - attribution_methodology: Annotated[ - attribution_methodology_1.AttributionMethodology | None, - Field( - description='Attribution methodology under which this outcome row was computed (`deterministic_purchase`, `probabilistic`, `panel_based`, `modeled`). Outcome metrics measured under different methodologies represent materially different numbers; never sum across methodologies.' - ), - ] = None - attribution_window: Annotated[ - duration.Duration | None, - Field( - description="Attribution window for this outcome row. Object-valued duration (`{interval, unit}`), not a shorthand string. Outcome metrics measured over different windows represent the same metric over different time periods; the partition keeps them as separate rows so buyers don't accidentally aggregate." - ), - ] = None - lift_dimension: Annotated[ - lift_dimension_1.LiftDimension | None, - Field( - description='Lift dimension this row represents (awareness, consideration, favorability, purchase intent, or ad recall) for vendor lift-style metrics. Applicability is defined by the vendor metric; each dimension is a separate surveyed outcome and rows under different dimensions must not be summed.' - ), - ] = None - - -class DeliveryMetricAggregate2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['vendor'], - Field(description='Vendor-defined metric, identified by the tuple `(vendor, metric_id)`.'), - ] = 'vendor' - vendor: Annotated[ - brand_ref.BrandReference, - Field( - description="Vendor that defines and computes this metric. The vendor's `brand.json` `agents[type='measurement']` is the canonical anchor; metric definitions live on `get_adcp_capabilities.measurement.metrics[]`." - ), - ] - metric_id: Annotated[ - vendor_metric_id.VendorMetricId, - Field(description="Identifier for the metric within the vendor's vocabulary."), - ] - qualifier: Annotated[ - Qualifier3 | None, - Field( - description='Optional qualifier keys disambiguating this vendor-metric row from sibling rows under the same (vendor, metric_id) — e.g., attribution_window on a vendor outcome metric. Same closed key set as the standard branch; new keys ship explicitly.' - ), - ] = None - value: Annotated[ - StrictFloat, - Field( - description="Aggregated vendor-attested value. Unit semantics defined by the vendor — see the vendor's measurement-agent metric definition." - ), - ] - measurable_impressions: Annotated[ - StrictFloat | None, - Field( - description="Coverage denominator — vendor measurement is rarely 100% of delivery (only impressions where the vendor's SDK fired or panel matched). Buyers compute coverage as `measurable_impressions / impressions`. Same convention as `vendor_metric_value.measurable_impressions`.", - ge=0.0, - ), - ] = None - - -DeliveryMetricAggregate = Annotated[ - DeliveryMetricAggregate1 | DeliveryMetricAggregate2, - Field( - description='One delivery metric value partitioned by metric scope and qualifier. Its standard branch is used at package grain by `by_package[].metric_values`; both branches are retained for the deprecated cross-buy `aggregated_totals.metric_aggregates` compatibility field. Row-symmetric with `package.committed_metrics` and delivery `missing_metrics` so buyers can reconcile by `(scope, metric_id, qualifier)`.', - discriminator='scope', - title='Delivery Metric Aggregate', - ), -] diff --git a/src/adcp/types/generated_poc/core/delivery_metrics.py b/src/adcp/types/generated_poc/core/delivery_metrics.py deleted file mode 100644 index ebd4f2f33..000000000 --- a/src/adcp/types/generated_poc/core/delivery_metrics.py +++ /dev/null @@ -1,731 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/delivery_metrics.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from datetime import date -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictBool, StrictFloat - -from ..enums import action_source as action_source_1 -from ..enums import event_type as event_type_1 -from ..enums import reach_unit as reach_unit_1 -from ..enums import view_threshold_basis, viewability_standard -from . import brand_ref, duration, placement_evidence, vendor_metric_value - - -class Kind(StrEnum): - cumulative = 'cumulative' - period = 'period' - rolling = 'rolling' - - -class QuartileData(AdCPBaseModel): - q1_views: Annotated[StrictFloat | None, Field(description='25% completion views', ge=0.0)] = ( - None - ) - q2_views: Annotated[StrictFloat | None, Field(description='50% completion views', ge=0.0)] = ( - None - ) - q3_views: Annotated[StrictFloat | None, Field(description='75% completion views', ge=0.0)] = ( - None - ) - q4_views: Annotated[StrictFloat | None, Field(description='100% completion views', ge=0.0)] = ( - None - ) - - -class VenueBreakdownItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - venue_id: Annotated[str, Field(description='Venue identifier')] - venue_name: Annotated[str | None, Field(description='Human-readable venue name')] = None - venue_type: Annotated[ - str | None, - Field(description="Venue type (e.g., 'airport', 'transit', 'retail', 'billboard')"), - ] = None - impressions: Annotated[ - SchemaInt, Field(description='Impressions delivered at this venue', ge=0) - ] - loop_plays: Annotated[SchemaInt | None, Field(description='Loop plays at this venue', ge=0)] = ( - None - ) - screens_used: Annotated[ - SchemaInt | None, Field(description='Number of screens used at this venue', ge=0) - ] = None - - -class DoohMetrics1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - loop_plays: Annotated[ - SchemaInt | None, Field(description='Number of times ad played in rotation', ge=0) - ] = None - screens_used: Annotated[ - SchemaInt | None, Field(description='Number of unique screens displaying the ad', ge=0) - ] = None - screen_time_seconds: Annotated[ - SchemaInt | None, Field(description='Total display time in seconds', ge=0) - ] = None - sov_achieved: Annotated[ - StrictFloat | None, - Field( - description='Actual share of voice delivered on a 0.0-1.0 scale. To compare this achieved value with the selected flat-rate DOOH parameters.sov_percentage, multiply sov_achieved by 100. Share is time-weighted: the sum of the delivered segment durations divided by the full loop duration. On equal-duration loops this equals the slot-count ratio. See also ooh_metrics.share_of_voice_contracted, which uses the same 0.0-1.0 scale.', - ge=0.0, - le=1.0, - ), - ] = None - calculation_notes: Annotated[ - str | None, - Field( - description="Per-row supplementary methodology notes for DOOH impression calculation (e.g., 'rotation-based; 6-second slot weighted by 70% audience overlap'). Free-form prose for context that doesn't fit the structured measurement-vendor surface. Canonical methodology declarations belong on the measurement vendor's `get_adcp_capabilities.measurement.metrics[]` block where they're discoverable once and inherited across delivery rows; this field is for row-specific context (a particular daypart's calculation, a venue-mix exception) rather than the seller's general methodology." - ), - ] = None - venue_breakdown: Annotated[ - list[VenueBreakdownItem] | None, Field(description='Per-venue performance breakdown') - ] = None - - -class IdType(StrEnum): - geopath = 'geopath' - route_frame = 'route_frame' - plant_face = 'plant_face' - other = 'other' - - -class Identifier(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - id: Annotated[str, Field(description='Panel/face identifier within the id_type namespace')] - id_type: Annotated[ - IdType, - Field( - description="Identifier scheme: geopath (US Geopath Spot ID), route_frame (UK Route frame ID), plant_face (the operator's own panel number — the identifier both sides of a direct contract know), other (markets without a listed scheme). New schemes ship as enum additions in minors." - ), - ] - - -class Panel(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - identifiers: Annotated[ - list[Identifier], - Field(description='All identifiers for this panel, one entry per scheme', min_length=1), - ] - name: Annotated[ - str | None, - Field( - description="Human-readable location description (e.g., 'I-95 N of Exit 12, right-hand read')" - ), - ] = None - - -class EstimationBasis(StrEnum): - currency_measured = 'currency_measured' - seller_modeled = 'seller_modeled' - - -class EventType(StrEnum): - posted = 'posted' - rotated = 'rotated' - repaired = 'repaired' - removed = 'removed' - - -class ViewedSecondsPercentiles(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - p25: Annotated[ - StrictFloat, Field(description='25th-percentile in-view duration in seconds.', ge=0.0) - ] - p50: Annotated[ - StrictFloat, - Field(description='Median (50th-percentile) in-view duration in seconds.', ge=0.0), - ] - p75: Annotated[ - StrictFloat, Field(description='75th-percentile in-view duration in seconds.', ge=0.0) - ] - p90: Annotated[ - StrictFloat, Field(description='90th-percentile in-view duration in seconds.', ge=0.0) - ] - p95: Annotated[ - StrictFloat, Field(description='95th-percentile in-view duration in seconds.', ge=0.0) - ] - - -class ViewedSecondsHistogramItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - lower_bound_seconds: Annotated[ - StrictFloat, - Field(description='Inclusive lower bound of this duration bucket, in seconds.', ge=0.0), - ] - upper_bound_seconds: Annotated[ - StrictFloat | None, - Field( - description='Exclusive upper bound of this duration bucket, in seconds. MUST be greater than `lower_bound_seconds`. Omit only on the final bucket to represent an unbounded upper range.', - ge=0.0, - ), - ] = None - impressions: Annotated[ - SchemaInt, - Field( - description='Number of measurable impressions whose in-view duration falls in this bucket.', - ge=0, - ), - ] - - -class ByActionSourceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - action_source: Annotated[ - action_source_1.ActionSource, Field(description='Where the conversion occurred') - ] - event_source_id: Annotated[ - str | None, - Field( - description='Event source that produced these conversions (for disambiguation when multiple event sources are configured)' - ), - ] = None - count: Annotated[ - StrictFloat, Field(description='Number of conversions from this action source', ge=0.0) - ] - value: Annotated[ - StrictFloat | None, - Field(description='Total monetary value of conversions from this action source', ge=0.0), - ] = None - - -class DoohMetrics(DoohMetrics1): - pass - - -class ByEventTypeItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - event_type: Annotated[event_type_1.EventType, Field(description='The event type')] - event_source_id: Annotated[ - str | None, - Field( - description='Event source that produced these conversions (for disambiguation when multiple event sources are configured)' - ), - ] = None - count: Annotated[StrictFloat, Field(description='Number of events of this type', ge=0.0)] - value: Annotated[ - StrictFloat | None, Field(description='Total monetary value of events of this type', ge=0.0) - ] = None - - -class ReachWindow(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[ - Kind, - Field( - description="Window semantics. `cumulative` — uniques since campaign start; the value is the total unique count to date and MUST NOT be summed across rows (each later row supersedes the earlier value). `period` — uniques within a single non-overlapping reporting period (e.g., a daily snapshot for a specific calendar day). Adjacent `period` rows do not share audiences by construction, but the same person MAY appear across multiple periods, so MUST NOT be summed across rows to compute campaign reach. `rolling` — uniques within a trailing window ending at the row's reporting timestamp (e.g., trailing-7-day reach). Adjacent rolling rows overlap and MUST NOT be summed; each row's value stands alone." - ), - ] - period: Annotated[ - duration.Duration | None, - Field( - description='Duration of the measurement window. REQUIRED when `kind` is `period` or `rolling` — declares the snapshot length (e.g., `{"interval": 1, "unit": "days"}` for a daily snapshot) or the trailing-window length (e.g., `{"interval": 7, "unit": "days"}` for trailing-7-day rolling reach). When `kind` is `cumulative`, this field is implicit (campaign-to-date) and SHOULD be omitted.' - ), - ] = None - - -class TimeBasedView(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - threshold_seconds: Annotated[ - StrictFloat, - Field( - description='Continuous duration threshold in seconds an impression must meet to count as a view in this entry.', - gt=0.0, - ), - ] - basis: Annotated[ - view_threshold_basis.ViewThresholdBasis, - Field( - description='Whether the threshold clock runs on playback time or in-view time. Required because play-time and in-view counts at the same threshold are materially different numbers.' - ), - ] - views: Annotated[ - StrictFloat, - Field(description="Count of views meeting this entry's threshold and basis.", ge=0.0), - ] - standard: Annotated[ - viewability_standard.ViewabilityStandard | None, - Field( - description="Viewability standard governing the in-view clock for this entry. RECOMMENDED when basis is 'in_view' (MRC and GroupM thresholds differ); not applicable to play_time entries." - ), - ] = None - - -class Posting(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - panel_id: Annotated[ - str, Field(description='Panel reference matching one of panels[].identifiers[].id') - ] - event_type: Annotated[ - EventType | None, - Field( - description='What the record attests: initial posting, rotary rotation to a new face, repair/reposting after damage, or removal at flight end' - ), - ] = EventType.posted - occurred_at: Annotated[ - date, - Field( - description='Date the attested event happened. For posted events this is the posting date — display terms customarily run from the average posting date across units.' - ), - ] - evidence: Annotated[ - placement_evidence.PlacementEvidence | None, - Field( - description='Evidence artifact for this event (completion photograph); capture time and location ride the artifact' - ), - ] = None - - -class OohMetrics(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - panels: Annotated[ - list[Panel] | None, - Field( - description="Panels (faces) covered by this row. A panel commonly carries multiple identifiers at once — OOH contracts key line items on the measurement-currency panel number AND the operator's own panel number together." - ), - ] = None - posting_period_start: Annotated[ - date | None, Field(description='First in-charge date of the posting period this row covers') - ] = None - posting_period_end: Annotated[ - date | None, Field(description='Last date of the posting period this row covers') - ] = None - average_posted_date: Annotated[ - date | None, - Field( - description="Average actual posting date across the row's units — the date the display term runs from under OAAA Bulletin §2.2 / Poster §3.2(a) when materials were timely. Read with materials_timely to know whether that rule was in force." - ), - ] = None - materials_timely: Annotated[ - StrictBool | None, - Field( - description="Seller's assertion that the buyer delivered acceptable materials by the contractual deadline, which determines whether the §2.2 posting-completion rule (term runs from average posting date) applied. An assertion about the buyer's delivery, not a derived value — it is not recomputable from postings[] evidence, and is the one field in this block that isn't checkable against it." - ), - ] = None - share_of_voice_contracted: Annotated[ - StrictFloat | None, - Field( - description='Contracted share of voice where the buy is rotation-based (e.g., rotary bulletin programs) rather than an exclusive face, on a 0.0-1.0 scale. Share is time-weighted: the sum of contracted display durations divided by the full rotation duration. On equal-duration rotations this equals the slot-count ratio.', - ge=0.0, - le=1.0, - ), - ] = None - illuminated_hours: Annotated[ - StrictFloat | None, - Field( - description='Contracted daily illumination hours for the panels in this row. Determines the measured day-part basis (12/18/24-hour impressions) and the illumination-credit remedy when unmet.', - ge=0.0, - le=24.0, - ), - ] = None - estimated_impressions: Annotated[ - SchemaInt | None, - Field( - description="Modeled audience impressions for the panels and period in this row. This is the channel's delivery number — there is no event-counted alternative. The methodology tier MUST be declared in estimation_basis; provider identity is declared in the row-level measurement_source; the billing vendor is declared in measurement_terms.billing_measurement. The row's top-level impressions SHOULD carry the same value so cross-channel aggregation works without channel-specific logic.", - ge=0, - ), - ] = None - estimation_basis: Annotated[ - EstimationBasis | None, - Field( - description="Methodology tier of estimated_impressions: currency_measured (an audience measurement currency's estimate for the panel and period — Geopath, Route, MOVE, COMMB — with the provider named in the row-level measurement_source) or seller_modeled (the seller's own model — the honest fallback for markets without a measurement currency). Tier, not provider: currencies change over time, so provider identity is data on measurement_source, not values in this enum." - ), - ] = None - postings: Annotated[ - list[Posting] | None, - Field( - description='Posting records — the seller-attested settlement artifact proving each panel was posted for the period. Industry convention (OAAA model contracts, Proof of Performance §3.5): photo evidence per bulletin within five calendar days of posting and again after each rotary rotation; one representative close-up photograph per creative design variation for poster showings (no deadline attached). Distinct from the §2.2 posting-completion obligation (posting within five business days of the scheduled date), which governs average_posted_date, not evidence.' - ), - ] = None - calculation_notes: Annotated[ - str | None, - Field( - description="Row-specific methodology context that doesn't fit the structured fields (e.g., a partial-period proration or a market-specific estimate adjustment). Same role as dooh_metrics.calculation_notes — canonical methodology declarations belong on the measurement vendor's discoverable surfaces, not here." - ), - ] = None - - -class Viewability1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - brand_ref.BrandReference | None, - Field( - description="Vendor that produced these viewability values. Optional but RECOMMENDED so the row is self-describing — buyer agents reading delivery in isolation can attribute the numbers without joining back to `package.committed_metrics` or `package.performance_standards`. The vendor's `brand.json` `agents[type='measurement']` is the discovery anchor; the metric definitions live on the agent's `get_adcp_capabilities.measurement.metrics[]` block. Same shape as `vendor_metric_value.vendor` for symmetry across vendor-attested surfaces." - ), - ] = None - measurable_impressions: Annotated[ - StrictFloat | None, - Field( - description='Impressions where viewability could be measured. Excludes environments without measurement capability (e.g., non-Intersection Observer browsers, certain app environments). Coverage denominator for `viewable_rate`, `viewed_seconds`, and both viewed-seconds distributions — every duration statistic is computed over this same measurable population.', - ge=0.0, - ), - ] = None - viewable_impressions: Annotated[ - StrictFloat | None, - Field( - description='Impressions that met the viewability threshold defined by the measurement standard.', - ge=0.0, - ), - ] = None - viewable_rate: Annotated[ - StrictFloat | None, - Field( - description='Viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.', - ge=0.0, - le=1.0, - ), - ] = None - viewed_seconds: Annotated[ - StrictFloat | None, - Field( - description="Average in-view duration per measurable impression, in seconds. Reporting-side counterpart to the `viewed_seconds` optimization metric in `optimization-goal.json`. Computed over `measurable_impressions`, not total impressions — the same denominator as `viewable_rate`. The viewability `standard` governs the threshold (e.g., MRC's 50% pixels for 1s display / 2s video) that defines when an impression is in view and therefore when the clock is running. Sellers reporting against a `viewed_seconds` optimization goal MUST populate this field.", - ge=0.0, - ), - ] = None - viewed_seconds_percentiles: Annotated[ - ViewedSecondsPercentiles | None, - Field( - description='Percentile summary of the per-impression in-view durations whose arithmetic mean is reported in `viewed_seconds`. This object MUST use the same reporting row, measurement vendor, viewability `standard`, and `measurable_impressions` population as `viewed_seconds`; sellers MUST omit it when `measurable_impressions` is zero. Percentiles use the nearest-rank definition: sort the N observed durations in ascending order and select rank `ceil(p × N)` (one-based) for percentile p. Values MUST be non-decreasing from p25 through p95. The structured metric identity `viewed_seconds_percentiles` makes this optional surface discoverable and requestable; it is not sortable and the nested object remains the canonical carrier.' - ), - ] = None - viewed_seconds_histogram: Annotated[ - list[ViewedSecondsHistogramItem] | None, - Field( - description='Bucketed counts of the per-impression in-view durations whose arithmetic mean is reported in `viewed_seconds`. Buckets MUST be ordered by ascending lower bound, MUST NOT overlap, and MUST partition every impression in the same `measurable_impressions` population exactly once; therefore the sum of `impressions` MUST equal `measurable_impressions`. Buckets need not be contiguous: a gap between consecutive bucket boundaries is permitted when no impressions fall within that range — the sum constraint enforces this implicitly, and validators MUST NOT independently require contiguity. Each bucket is half-open `[lower_bound_seconds, upper_bound_seconds)`; only the final bucket MAY omit `upper_bound_seconds`, representing an unbounded upper range. Sellers choose boundaries, but buyers MUST combine histograms only when the complete boundary sequence, measurement vendor, and viewability `standard` match. The structured metric identity `viewed_seconds_histogram` makes this optional surface discoverable and requestable; it is not sortable and this nested array remains the canonical carrier.', - min_length=1, - ), - ] = None - standard: Annotated[ - viewability_standard.ViewabilityStandard | None, - Field( - description='Viewability measurement standard applied to these metrics. Governs the in-view threshold for `viewable_rate`, `viewed_seconds`, and both viewed-seconds distributions.' - ), - ] = None - - -class Viewability(Viewability1): - pass - - -class DeliveryMetrics(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - impressions: Annotated[ - StrictFloat | None, Field(description='Impressions delivered', ge=0.0) - ] = None - spend: Annotated[StrictFloat | None, Field(description='Amount spent', ge=0.0)] = None - clicks: Annotated[StrictFloat | None, Field(description='Total clicks', ge=0.0)] = None - ctr: Annotated[ - StrictFloat | None, - Field(description='Click-through rate (clicks/impressions)', ge=0.0, le=1.0), - ] = None - views: Annotated[ - StrictFloat | None, - Field( - description="Content engagements counted toward the billable view threshold. For video this is a platform-defined view event (e.g., 30 seconds or video midpoint); for audio/podcast it is a stream start; for other formats it follows the pricing model's view definition. When the package uses CPV pricing, spend = views × rate.", - ge=0.0, - ), - ] = None - completed_views: Annotated[ - StrictFloat | None, - Field( - description='Video/audio completions. When the package has a completed_views optimization goal with view_duration_seconds, completions are counted at that threshold rather than 100% completion.', - ge=0.0, - ), - ] = None - completion_rate: Annotated[ - StrictFloat | None, - Field( - description='Completion rate (completed_views/impressions). Null indicates the metric is not applicable to this package/buy (e.g. completion rate on a non-video buy).', - ge=0.0, - le=1.0, - ), - ] = None - conversions: Annotated[ - StrictFloat | None, - Field( - description='Total conversions attributed to this delivery. When by_event_type is present, this equals the sum of all by_event_type[].count entries.', - ge=0.0, - ), - ] = None - conversion_value: Annotated[ - StrictFloat | None, - Field( - description='Total monetary value of attributed conversions (in the reporting currency)', - ge=0.0, - ), - ] = None - commissionable_value: Annotated[ - StrictFloat | None, - Field( - description='Settled portion of attributed conversion value eligible for revenue-share commission, in the reporting currency. For revenue_share pricing, spend = round_currency(commissionable_value × commission_rate). This is distinct from conversion_value because taxes, shipping, discounts, returns, cancellations, or ineligible items may be excluded under the agreed commission basis.', - ge=0.0, - ), - ] = None - roas: Annotated[ - StrictFloat | None, - Field(description='Return on ad spend (conversion_value / spend)', ge=0.0), - ] = None - cost_per_acquisition: Annotated[ - StrictFloat | None, Field(description='Cost per conversion (spend / conversions)', ge=0.0) - ] = None - new_to_brand_rate: Annotated[ - StrictFloat | None, - Field( - description='Fraction of `conversions` (transactions) from first-time brand buyers, 0 = none, 1 = all. For retail-media unit-volume tracking of first-time buyers, see `new_to_brand_units` (count, not rate).', - ge=0.0, - le=1.0, - ), - ] = None - leads: Annotated[ - StrictFloat | None, - Field( - description="Leads generated (convenience alias for by_event_type where event_type='lead')", - ge=0.0, - ), - ] = None - incremental_sales_lift: Annotated[ - StrictFloat | None, - Field( - description="Incremental sales lift attributed to the campaign — sales above the control/holdout baseline. Reported as a fraction (0.15 = 15% lift) or as an absolute value depending on seller convention. The seller's `attribution_methodology` qualifier (typically `deterministic_purchase` or `modeled`) and `attribution_window` qualifier on the matching `committed_metrics` entry disambiguate the methodology and window.", - ge=0.0, - ), - ] = None - brand_lift: Annotated[ - StrictFloat | None, - Field( - description="Brand lift — measured change in a brand metric (awareness, consideration, favorability, purchase intent, or ad recall) attributed to the campaign. Typically panel-based or survey-based. Reported as a fraction (0.05 = 5% lift). **Multidimensional in production** — Kantar, Upwave, Cint, DV all report each dimension separately with its own sample size and confidence interval. The dimension flows through `qualifier.lift_dimension` on `committed_metrics` / `by_package[].metric_values` (`awareness` | `consideration` | `favorability` | `purchase_intent` | `ad_recall`); rows under different dimensions are different surveyed outcomes and must not be combined. Use `attribution_methodology: 'panel_based'` qualifier when the underlying methodology is a panel.", - ge=0.0, - ), - ] = None - foot_traffic: Annotated[ - StrictFloat | None, - Field( - description="Store visits attributed to ad exposure. Count of incremental visits over baseline. Typically uses location-data panel methodology (`attribution_methodology: 'panel_based'`) or deterministic loyalty-card match (`attribution_methodology: 'deterministic_purchase'`).", - ge=0.0, - ), - ] = None - conversion_lift: Annotated[ - StrictFloat | None, - Field( - description='Incremental conversions attributed to the campaign — conversions above the control/holdout baseline. Reported as a fraction (0.10 = 10% lift) or as an absolute count depending on seller convention. Distinct from `conversions` (raw count of attributed conversions); conversion_lift requires a control group and an incrementality methodology.', - ge=0.0, - ), - ] = None - brand_search_lift: Annotated[ - StrictFloat | None, - Field( - description='Lift in brand search query volume attributed to the campaign — measured via search-data partnerships (Google, Microsoft) or survey methodology. Reported as a fraction (0.20 = 20% lift in branded search).', - ge=0.0, - ), - ] = None - plays: Annotated[ - StrictFloat | None, - Field( - description="Number of times the ad creative was displayed or played on DOOH or broadcast inventory. Raw play count before any impression multiplier is applied. Mirrors `forecastable-metric.json`'s `plays` token for forecast↔delivery reconciliation. Distinct from `dooh_metrics.loop_plays` (scheduled-rotation count) and from `impressions` (multiplied audience figure).", - ge=0.0, - ), - ] = None - measurement_source: Annotated[ - str | None, - Field( - description="Third-party measurement provider whose data produced this row's audience numbers. Mirrors delivery-forecast.json's measurement_source so forecast and delivery reconcile on the same declaration — distinct from demographic_system, which specifies demographic notation. Makes measured-channel rows (radio, broadcast, OOH) self-describing: a reconciliation join can tie delivered numbers to the system that measured them without consulting out-of-band context. Lowercase slug format.", - examples=[ - 'nielsen', - 'nielsen_audio', - 'videoamp', - 'comscore', - 'geopath', - 'barb', - 'agf', - 'oztam', - 'kantar', - 'barc', - 'route', - 'rajar', - 'triton', - ], - max_length=64, - pattern='^[a-z0-9_]+$', - ), - ] = None - by_event_type: Annotated[ - list[ByEventTypeItem] | None, - Field( - description='Conversion metrics broken down by event type. Spend-derived metrics (ROAS, CPA) are only available at the package/totals level since spend cannot be attributed to individual event types.' - ), - ] = None - grps: Annotated[ - StrictFloat | None, Field(description='Gross Rating Points delivered (for CPP)', ge=0.0) - ] = None - reach: Annotated[ - StrictFloat | None, - Field( - description='Unique reach in the units specified by reach_unit. When reach_unit is omitted, units are unspecified — do not compare reach values across packages or media buys without a common reach_unit. The measurement window for this value is declared in `reach_window`; when `reach_window` is omitted, the window is unspecified and buyers MUST NOT sum reach across reports (the value MAY be a daily snapshot, a cumulative total, or something else).', - ge=0.0, - ), - ] = None - reach_unit: Annotated[ - reach_unit_1.ReachUnit | None, - Field( - description='Unit of measurement for the reach field. Aligns with the reach_unit declared on optimization goals and delivery forecasts. Required when reach is present to enable cross-platform comparison.' - ), - ] = None - reach_window: Annotated[ - ReachWindow | None, - Field( - description='Measurement window for the reported `reach` and `frequency` values in this row. Declares whether the values are a per-period snapshot, a trailing rolling window, or cumulative-to-date — without this declaration, buyers summing `reach` across rows (e.g., daily delivery reports) can silently double-count audiences. Sellers SHOULD populate this whenever `reach` is present.' - ), - ] = None - frequency: Annotated[ - StrictFloat | None, - Field( - description="Average frequency per reach unit, measured over the window declared in `reach_window`. When `reach_unit` is 'households', this is average exposures per household; when 'accounts', per logged-in account; etc. When `reach_window` is omitted, the window is unspecified — buyers MUST NOT compare or average frequency values across rows.", - ge=0.0, - ), - ] = None - quartile_data: Annotated[ - QuartileData | None, - Field( - description="Audio/video quartile completion data. Null indicates the metric is not applicable to this package/buy (e.g. quartile data on a non-video buy). Individual quartiles are addressable via the leaf metric identities `quartile_25` (q1_views), `quartile_50` (q2_views), `quartile_75` (q3_views), and `quartile_100` (q4_views) for declaration, commitments, aggregates, and breakdown sorting; this object remains the canonical carrier of the values. Quartiles are player-fired events (VAST firstQuartile/midpoint/thirdQuartile/complete). `quartile_100` counts 100%-of-duration completions and is distinct from `completed_views`, which counts completions at the seller's billable view threshold (`view_duration_seconds`) when one is set." - ), - ] = None - time_based_views: Annotated[ - list[TimeBasedView] | None, - Field( - description="Time-threshold video view counts. Each entry reports views that met a continuous duration threshold under a stated basis, rather than a completion percentage (percentage-based completion is quartile_data). Thresholds of 2 and 6 seconds are RECOMMENDED cross-platform reporting points; any seller-defined threshold is permitted. One entry per (threshold_seconds, basis) pair per reporting period — sellers MUST de-duplicate before emission and MUST NOT emit the same pair twice; buyers MAY treat duplicate pairs as a seller-side conformance bug. Entries under different bases are different metrics and MUST NOT be summed (see view-threshold-basis). Primarily an autoplay/skippable-video metric (social, olv, in-feed video); completion metrics remain the currency for lean-back CTV/cinema inventory. Distinct from `views` (the single billable-threshold scalar) and from `viewability.viewed_seconds` (average in-view duration, not a threshold count). Array entries are not individually sortable in breakdown sort_by. Disclosure-grade surface: (threshold_seconds, basis) is not part of the committed-metric qualifier vocabulary, so a `committed_metrics` entry for `time_based_views` contracts the array's presence, not specific thresholds." - ), - ] = None - dooh_metrics: Annotated[ - DoohMetrics1 | None, - Field(description='DOOH-specific metrics (only included for DOOH campaigns)'), - ] = None - ooh_metrics: Annotated[ - OohMetrics | None, - Field( - description='Classic (static) OOH metrics — printed bulletins, posters, transit, and street furniture (only included for ooh campaigns). Experimental in AdCP 3.2. Static units have no play event: the delivery number is a period-level modeled audience estimate whose methodology tier is declared in estimation_basis (provider identity rides the row-level measurement_source), and the settlement artifact is the posting record — it proves the posting period, not an airing.' - ), - ] = None - viewability: Annotated[ - Viewability1 | None, - Field( - description="Viewability metrics. Viewable rate should be calculated as viewable_impressions / measurable_impressions (not total impressions), since some environments cannot measure viewability. Includes `viewed_seconds` — average in-view duration — plus optional percentile and histogram distributions over that duration; all three use the same `measurable_impressions` population and are governed by the same viewability threshold (`standard`). Sellers SHOULD include `standard` whenever measured viewability values are reported because MRC and GroupM rows are not interchangeable. The numeric leaves are addressable via the leaf metric identities `viewable_rate`, `viewable_impressions`, `measurable_impressions`, and `viewed_seconds` for declaration, commitments, aggregates, and breakdown sorting. The structured distribution carriers require explicit `viewed_seconds_percentiles` and `viewed_seconds_histogram` identities for declaration, commitment, and selection; they are not numeric aggregate rows or sort keys. This object remains the canonical carrier of every value. When a buy reports under more than one standard, contract a specific standard via the `viewability_standard` qualifier on `committed_metrics`; when the package's `committed_metrics` carry a `viewability_standard` qualifier, sellers MUST populate `standard` on reported viewability objects so reconciliation can match the qualifier." - ), - ] = None - engagements: Annotated[ - StrictFloat | None, - Field( - description="Total engagements — direct interactions with the ad beyond viewing. Includes social reactions/comments/shares, story/unit opens, interactive overlay taps on CTV, companion banner interactions on audio. Platform-specific; corresponds to the 'engagements' optimization metric. Maps to DBCFM KPI_INTERACTIONS (Interaktionen) in the Reporting/Performance block.", - ge=0.0, - ), - ] = None - follows: Annotated[ - StrictFloat | None, - Field( - description='New followers, page likes, artist/podcast/channel follows, or free channel/feed subscribes attributed to this delivery. Paid subscriptions are conversion events with `event_type: subscribe`, not `follows`.', - ge=0.0, - ), - ] = None - saves: Annotated[ - StrictFloat | None, - Field( - description='Saves, bookmarks, playlist adds, pins attributed to this delivery.', ge=0.0 - ), - ] = None - profile_visits: Annotated[ - StrictFloat | None, - Field( - description="Visits to the brand's in-platform page (profile, artist page, channel, or storefront) attributed to this delivery. Does not include external website clicks.", - ge=0.0, - ), - ] = None - engagement_rate: Annotated[ - StrictFloat | None, - Field( - description='Platform-specific engagement rate (0.0 to 1.0). Typically engagements/impressions, but definition varies by platform.', - ge=0.0, - le=1.0, - ), - ] = None - cost_per_click: Annotated[ - StrictFloat | None, Field(description='Cost per click (spend / clicks)', ge=0.0) - ] = None - cost_per_completed_view: Annotated[ - StrictFloat | None, - Field( - description="Cost per completed view (spend / completed_views). Primary CPCV pricing scalar for video/audio inventory; the package's `pricing_model` is `cpcv` when this field is the billing basis.", - ge=0.0, - ), - ] = None - cpm: Annotated[ - StrictFloat | None, - Field( - description="Cost per thousand impressions, computed as (spend / impressions) × 1000. Universal pricing scalar across CTV, display, mobile/web video, native, audio, and DOOH inventory; the package's `pricing_model` is `cpm` when this field is the billing basis. Field name aligns with the canonical `cpm` token in `enums/pricing-model.json` and `pricing-options/cpm-option.json` so buyers cross-walk pricing model → reported scalar without a translation table.", - ge=0.0, - ), - ] = None - downloads: Annotated[ - StrictFloat | None, - Field( - description="Audio/podcast downloads (IAB Podcast Measurement Technical Guidelines 2.x methodology). Distinct from `views` — for podcast inventory this is the count of podcast episode downloads; for streaming audio it is the count of stream starts that meet the platform's download threshold. Prefer this over `views` for audio inventory.", - ge=0.0, - ), - ] = None - units_sold: Annotated[ - StrictFloat | None, - Field( - description='Items sold attributed to this delivery. Retail-media scalar distinct from `conversions` — a single conversion (transaction) may carry multiple `units_sold`. Used by retail media platforms where the buyer optimizes against unit movement, not transaction count. Attribution lookback windows are platform-specific (commonly 7/14/30 days, view-through and click-through variants); sellers SHOULD declare the window via `reporting_capabilities.measurement_windows` or `measurement_terms` rather than encoding it in this scalar.', - ge=0.0, - ), - ] = None - new_to_brand_units: Annotated[ - StrictFloat | None, - Field( - description='Units sold to first-time brand buyers (count, not rate). Retail-media scalar — the unit-volume parallel to the conversion-fraction `new_to_brand_rate`. Used by retail media platforms where new-customer acquisition unit volume is a primary KPI. Same attribution-window note as `units_sold` applies.', - ge=0.0, - ), - ] = None - by_action_source: Annotated[ - list[ByActionSourceItem] | None, - Field( - description='Conversion metrics broken down by action source (website, app, in_store, etc.). Useful for omnichannel sellers where conversions occur across digital and physical channels.' - ), - ] = None - vendor_metric_values: Annotated[ - list[vendor_metric_value.VendorMetricValue] | None, - Field( - description="Reported values for vendor-defined metrics that the product's `reporting_capabilities.vendor_metrics` declared. Each entry carries the vendor (BrandRef), the metric identifier within the vendor's vocabulary, the value, optional unit, and `measurable_impressions` as the coverage denominator — vendor measurement is rarely 100% of delivered impressions, since vendors only score impressions where their SDK fires or their panel matches. When a declared vendor metric is omitted from this array, buyers infer no measurement happened (no integration). One row per `(vendor.domain, vendor.brand_id, metric_id, qualifier)` per reporting period — the same vendor metric MAY appear in multiple rows only when each carries a distinct qualifier (e.g., 7-day and 30-day attribution windows); sellers MUST de-duplicate before emission and MUST NOT emit two rows with the same tuple; buyers MAY treat duplicate rows as a seller-side conformance bug. The structured `vendor_metric_values` array is the recommended path for vendor metrics; `additionalProperties: true` on this parent object is preserved so existing free-form vendor emissions remain conformant during migration." - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/delivery_provider.py b/src/adcp/types/generated_poc/core/delivery_provider.py deleted file mode 100644 index af5d0fc85..000000000 --- a/src/adcp/types/generated_poc/core/delivery_provider.py +++ /dev/null @@ -1,23 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/delivery_provider.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - - -class DeliveryProvider(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Lowercase dotted provider domain, such as a provider's operating domain. Single-label and localhost-style names are invalid.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$', - ), - ] diff --git a/src/adcp/types/generated_poc/core/delivery_recipient.py b/src/adcp/types/generated_poc/core/delivery_recipient.py deleted file mode 100644 index 4eadba187..000000000 --- a/src/adcp/types/generated_poc/core/delivery_recipient.py +++ /dev/null @@ -1,21 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/delivery_recipient.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..enums import delivery_recipient_cloud - - -class DeliveryRecipient(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - identity: Annotated[str, Field(max_length=512, min_length=1)] - cloud: delivery_recipient_cloud.DeliveryRecipientCloud | None = None - region: Annotated[str | None, Field(max_length=128, min_length=1)] = None diff --git a/src/adcp/types/generated_poc/core/demographic_age_range.py b/src/adcp/types/generated_poc/core/demographic_age_range.py deleted file mode 100644 index a9ce5b885..000000000 --- a/src/adcp/types/generated_poc/core/demographic_age_range.py +++ /dev/null @@ -1,51 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/demographic_age_range.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictBool, model_validator - - -class DemographicAgeRange(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - min: Annotated[ - SchemaInt | None, - Field( - description='Inclusive minimum age in completed years. Omit for an open lower bound.', - ge=0, - le=150, - ), - ] = None - max: Annotated[ - SchemaInt | None, - Field( - description='Inclusive maximum age in completed years. Omit for an open upper bound.', - ge=0, - le=150, - ), - ] = None - include_unknown: Annotated[ - StrictBool, - Field( - description='Whether delivery to people whose age is unavailable is part of this predicate. This field has no default and MUST be supplied.' - ), - ] - - @model_validator(mode='after') - def _require_schema_required_group(self) -> DemographicAgeRange: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('min',), ('max',),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'DemographicAgeRange requires at least one of these field groups: min | max' - ) diff --git a/src/adcp/types/generated_poc/core/demographic_predicate.py b/src/adcp/types/generated_poc/core/demographic_predicate.py deleted file mode 100644 index 51cee67db..000000000 --- a/src/adcp/types/generated_poc/core/demographic_predicate.py +++ /dev/null @@ -1,17 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/demographic_predicate.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict - -from . import demographic_age_range - - -class DemographicPredicate(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - age: demographic_age_range.DemographicAgeRange diff --git a/src/adcp/types/generated_poc/core/demographic_reporting_capability.py b/src/adcp/types/generated_poc/core/demographic_reporting_capability.py deleted file mode 100644 index 746e36616..000000000 --- a/src/adcp/types/generated_poc/core/demographic_reporting_capability.py +++ /dev/null @@ -1,118 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/demographic_reporting_capability.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictBool, model_validator - -from ..enums import demographic_system as demographic_system_1 -from . import demographic_age_range - - -class ReportingMode(StrEnum): - exact_predicates = 'exact_predicates' - enumerated_intervals = 'enumerated_intervals' - - -class Interval(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - age: demographic_age_range.DemographicAgeRange - demographic: Annotated[ - str | None, - Field( - description='Optional native demographic label returned for this interval.', - min_length=1, - pattern='\\S', - ), - ] = None - demographic_system: demographic_system_1.DemographicSystem | None = None - - -class Age(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - reporting_modes: Annotated[ - list[ReportingMode], - Field( - description='Ways this product can produce age rows. exact_predicates accepts requested canonical age predicates within the declared bounds. enumerated_intervals reports only the exact intervals declared in intervals; buyers may aggregate adjacent complete intervals but MUST NOT treat a wider or narrower interval as the requested audience.', - min_length=1, - ), - ] - min_reportable_age: Annotated[ - SchemaInt | None, - Field( - description='Lowest explicit min or max bound accepted by exact_predicates. Required when that mode is declared.', - ge=0, - le=150, - ), - ] = None - max_reportable_age: Annotated[ - SchemaInt | None, - Field( - description='Highest explicit min or max bound accepted by exact_predicates. Required when that mode is declared.', - ge=0, - le=150, - ), - ] = None - supports_unbounded_min: Annotated[ - StrictBool | None, - Field( - description='Whether exact_predicates accepts a canonical age predicate with min omitted. Required when that mode is declared.' - ), - ] = None - supports_unbounded_max: Annotated[ - StrictBool | None, - Field( - description='Whether exact_predicates accepts a canonical age predicate with max omitted. Required when that mode is declared.' - ), - ] = None - intervals: Annotated[ - list[Interval] | None, - Field( - description='Fixed reportable age intervals for enumerated_intervals. Bounds are authoritative capability data, not a protocol-wide bucket taxonomy. Optional native labels identify the measurement-system code returned with the interval.', - min_length=1, - ), - ] = None - - -class DemographicReportingCapability(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - age: Annotated[ - Age | None, Field(description='Machine-comparable age ranges this product can report.') - ] = None - demographic_systems: Annotated[ - list[demographic_system_1.DemographicSystem] | None, - Field( - description='Measurement-system notations this product may return. A code remains opaque unless its capability interval and response row also carry a canonical age predicate.', - min_length=1, - ), - ] = None - may_suppress_small_cells: Annotated[ - StrictBool, - Field( - description='Whether privacy, policy, or measurement thresholds may suppress otherwise reportable demographic rows. When true, buyers must inspect by_demographic_suppressed before testing whether rows reconcile to package totals.' - ), - ] - - @model_validator(mode='after') - def _require_schema_required_group(self) -> DemographicReportingCapability: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('age',), ('demographic_systems',),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'DemographicReportingCapability requires at least one of these field groups: age | demographic_systems' - ) diff --git a/src/adcp/types/generated_poc/core/demographic_targeting_capability.py b/src/adcp/types/generated_poc/core/demographic_targeting_capability.py deleted file mode 100644 index ff4cd3a67..000000000 --- a/src/adcp/types/generated_poc/core/demographic_targeting_capability.py +++ /dev/null @@ -1,115 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/demographic_targeting_capability.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictBool - -from ..enums import age_determination_basis, age_verification_method -from . import demographic_age_range - - -class ExecutionMode(StrEnum): - continuous_bounds = 'continuous_bounds' - enumerated_intervals = 'enumerated_intervals' - signals = 'signals' - - -class UnknownHandling(StrEnum): - selectable = 'selectable' - always_excluded = 'always_excluded' - always_included = 'always_included' - - -class Interval(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - interval_id: Annotated[ - str, - Field( - description='Seller-scoped interval identifier echoed in demographic targeting readback.', - min_length=1, - ), - ] - age: demographic_age_range.DemographicAgeRange - - -class Age(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - execution_modes: Annotated[ - list[ExecutionMode], - Field( - description='Ways this product can execute canonical age intent. continuous_bounds accepts canonical predicates whose explicit integer bounds fall between min_supported_age and max_supported_age, inclusive; omitted bounds additionally require the corresponding supports_unbounded_min or supports_unbounded_max flag. enumerated_intervals accepts exact unions of the declared intervals; signals resolves through signals carrying authoritative demographic_predicate metadata.', - min_length=1, - ), - ] - min_supported_age: Annotated[ - SchemaInt | None, - Field( - description='Lowest integer accepted as an explicit min or max bound by continuous_bounds. This limit does not replace an omitted min: omission means no lower age restriction. Required when that execution mode is declared.', - ge=0, - le=150, - ), - ] = None - max_supported_age: Annotated[ - SchemaInt | None, - Field( - description='Highest integer accepted as an explicit min or max bound by continuous_bounds. This limit does not replace an omitted max: omission means no upper age restriction, so a request such as min 65 remains 65+ even when max_supported_age is 65. Required when that execution mode is declared.', - ge=0, - le=150, - ), - ] = None - supports_unbounded_min: Annotated[ - StrictBool | None, - Field( - description='Whether continuous_bounds accepts a predicate with min omitted. When true, the omitted min means no lower age restriction and MUST NOT be replaced by min_supported_age. When false, the seller MUST reject open-lower predicates. Required when continuous_bounds is declared.' - ), - ] = None - supports_unbounded_max: Annotated[ - StrictBool | None, - Field( - description='Whether continuous_bounds accepts a predicate with max omitted. When true, the omitted max means no upper age restriction and MUST NOT be replaced by max_supported_age; for example, min 65 remains 65+. When false, the seller MUST reject open-upper predicates. Required when continuous_bounds is declared.' - ), - ] = None - unknown_handling: Annotated[ - UnknownHandling, - Field( - description='How this product handles people whose age is unavailable. selectable honors either include_unknown value; always_excluded supports only false; always_included supports only true. A seller MUST reject a request incompatible with this declaration rather than silently changing it.' - ), - ] - supported_bases: Annotated[ - list[age_determination_basis.AgeDeterminationBasis] | None, - Field( - description='User-level age determination bases this product can use for exact execution. Sellers MUST publish only bases whose provenance they can preserve and audit; they MUST NOT infer a basis from an opaque platform age bucket. When omitted, the product makes no basis-aware targeting commitment and MUST reject requests that specify accepted_bases.', - min_length=1, - ), - ] = None - supported_verification_methods: Annotated[ - list[age_verification_method.AgeVerificationMethod] | None, - Field( - description='Age verification methods available when supported_bases contains verified. This is an availability declaration only: a method must still establish the entire requested age predicate. Threshold-only methods such as World ID cannot establish an arbitrary bounded range.', - min_length=1, - ), - ] = None - intervals: Annotated[ - list[Interval] | None, - Field( - description='Seller-owned age intervals available to enumerated_intervals execution. Bounds are authoritative capability data, not a protocol-wide bucket taxonomy. Exact compilation may union adjacent intervals but MUST NOT introduce gaps or extra ages.', - min_length=1, - ), - ] = None - - -class DemographicTargetingCapability(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - age: Annotated[Age, Field(description='Age-targeting execution available for this product.')] diff --git a/src/adcp/types/generated_poc/core/demographic_targeting_intent.py b/src/adcp/types/generated_poc/core/demographic_targeting_intent.py deleted file mode 100644 index cc75e5c63..000000000 --- a/src/adcp/types/generated_poc/core/demographic_targeting_intent.py +++ /dev/null @@ -1,37 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/demographic_targeting_intent.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..enums import age_determination_basis, age_verification_method -from .demographic_age_range import DemographicAgeRange - - -class Age(DemographicAgeRange): - accepted_bases: Annotated[ - list[age_determination_basis.AgeDeterminationBasis] | None, - Field( - description='User-level age determination bases the buyer permits for this predicate. Sellers MUST use only these bases or reject the request; they MUST NOT silently fall back to another basis. When omitted, the buyer imposes no determination-basis constraint beyond product capability and any age_restriction. population_estimate is structurally unavailable because aggregate evidence cannot identify an eligible user.', - min_length=1, - ), - ] = None - accepted_verification_methods: Annotated[ - list[age_verification_method.AgeVerificationMethod] | None, - Field( - description='Verification methods acceptable when verified is an accepted basis. This field narrows verified demographic execution; age_restriction.accepted_methods independently governs legal eligibility and the two sets are intersected when both are present.', - min_length=1, - ), - ] = None - - -class DemographicTargetingIntent(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - age: Age diff --git a/src/adcp/types/generated_poc/core/demographic_targeting_resolution.py b/src/adcp/types/generated_poc/core/demographic_targeting_resolution.py deleted file mode 100644 index 20bc8cbdb..000000000 --- a/src/adcp/types/generated_poc/core/demographic_targeting_resolution.py +++ /dev/null @@ -1,97 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/demographic_targeting_resolution.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field - -from ..enums import age_determination_basis, age_verification_method -from . import demographic_predicate, demographic_targeting_intent -from . import ext as ext_1 -from . import signal_ref - - -class IntervalId(ScalarStr): - __slots__ = () - _constraints = {'min_length': 1} - - -class Execution(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Literal['continuous_bounds'] = 'continuous_bounds' - ext: ext_1.ExtensionObject | None = None - - -class Execution1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Literal['enumerated_intervals'] = 'enumerated_intervals' - interval_ids: Annotated[ - list[IntervalId], - Field( - description='Seller-scoped interval identifiers whose union produced applied.', - min_length=1, - ), - ] - ext: ext_1.ExtensionObject | None = None - - -class Execution2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Literal['signals'] = 'signals' - signal_refs: Annotated[ - list[signal_ref.SignalRef], - Field( - description='Authoritative signal references whose demographic predicates were unioned to produce applied. Signal names alone never establish equivalence.', - min_length=1, - ), - ] - ext: ext_1.ExtensionObject | None = None - - -class DemographicTargetingResolution(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - requested: Annotated[ - demographic_targeting_intent.DemographicTargetingIntent, - Field( - description='Canonical demographic predicate and determination constraints requested by the buyer.' - ), - ] - applied: Annotated[ - demographic_predicate.DemographicPredicate, - Field(description='Canonical demographic predicate actually applied by the seller.'), - ] - equivalent: Annotated[ - Literal[True], - Field( - description='Always true for a stored package. requested and applied MUST denote exactly the same set, including unknown-age membership; sellers reject non-equivalent requests instead of storing an alternative.' - ), - ] - execution: Execution | Execution1 | Execution2 - applied_bases: Annotated[ - list[age_determination_basis.AgeDeterminationBasis] | None, - Field( - description='Effective user-level determination bases configured for this package after intersecting buyer accepted_bases, product supported_bases, and age_restriction. This is an auditable configuration readback, not proof that every impression used every listed basis.', - min_length=1, - ), - ] = None - applied_verification_methods: Annotated[ - list[age_verification_method.AgeVerificationMethod] | None, - Field( - description='Effective verification methods when applied_bases contains verified. A method name does not establish that a particular proof or claim was valid; runtime verification and claim entailment remain required.', - min_length=1, - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/deployment.py b/src/adcp/types/generated_poc/core/deployment.py deleted file mode 100644 index 533d40d74..000000000 --- a/src/adcp/types/generated_poc/core/deployment.py +++ /dev/null @@ -1,86 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/deployment.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat - -from . import activation_key as activation_key_1 - - -class Deployment1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Literal['platform'], - Field(description='Discriminator indicating this is a platform-based deployment'), - ] = 'platform' - platform: Annotated[str, Field(description='Platform identifier for DSPs')] - account: Annotated[str | None, Field(description='Account identifier if applicable')] = None - is_live: Annotated[ - StrictBool, Field(description='Whether signal is currently active on this deployment') - ] - activation_key: Annotated[ - activation_key_1.ActivationKey | None, - Field( - description='The key to use for targeting. Only present if is_live=true AND requester has access to this deployment.' - ), - ] = None - estimated_activation_duration_minutes: Annotated[ - StrictFloat | None, - Field( - description='Estimated time to activate if not live, or to complete activation if in progress', - ge=0.0, - ), - ] = None - deployed_at: Annotated[ - AwareDatetime | None, - Field(description='Timestamp when activation completed (if is_live=true)'), - ] = None - - -class Deployment2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Literal['agent'], - Field(description='Discriminator indicating this is an agent URL-based deployment'), - ] = 'agent' - agent_url: Annotated[AnyUrl, Field(description='URL identifying the deployment agent')] - account: Annotated[str | None, Field(description='Account identifier if applicable')] = None - is_live: Annotated[ - StrictBool, Field(description='Whether signal is currently active on this deployment') - ] - activation_key: Annotated[ - activation_key_1.ActivationKey | None, - Field( - description='The key to use for targeting. Only present if is_live=true AND requester has access to this deployment.' - ), - ] = None - estimated_activation_duration_minutes: Annotated[ - StrictFloat | None, - Field( - description='Estimated time to activate if not live, or to complete activation if in progress', - ge=0.0, - ), - ] = None - deployed_at: Annotated[ - AwareDatetime | None, - Field(description='Timestamp when activation completed (if is_live=true)'), - ] = None - - -Deployment = Annotated[ - Deployment1 | Deployment2, - Field( - description='A signal deployment to a specific deployment target with activation status and key', - discriminator='type', - title='Deployment', - ), -] diff --git a/src/adcp/types/generated_poc/core/destination.py b/src/adcp/types/generated_poc/core/destination.py deleted file mode 100644 index cbb8703fe..000000000 --- a/src/adcp/types/generated_poc/core/destination.py +++ /dev/null @@ -1,53 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/destination.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - - -class Destination1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Literal['platform'], - Field(description='Discriminator indicating this is a platform-based deployment'), - ] = 'platform' - platform: Annotated[ - str, - Field(description="Platform identifier for DSPs (e.g., 'the-trade-desk', 'amazon-dsp')"), - ] - account: Annotated[ - str | None, Field(description='Optional account identifier on the platform') - ] = None - - -class Destination2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Literal['agent'], - Field(description='Discriminator indicating this is an agent URL-based deployment'), - ] = 'agent' - agent_url: Annotated[ - AnyUrl, Field(description='URL identifying the deployment agent (for sales agents, etc.)') - ] - account: Annotated[ - str | None, Field(description='Optional account identifier on the agent') - ] = None - - -Destination = Annotated[ - Destination1 | Destination2, - Field( - description='A deployment target where signals can be activated (DSP, sales agent, etc.)', - discriminator='type', - title='Destination', - ), -] diff --git a/src/adcp/types/generated_poc/core/destination_item.py b/src/adcp/types/generated_poc/core/destination_item.py deleted file mode 100644 index d7d8aa0eb..000000000 --- a/src/adcp/types/generated_poc/core/destination_item.py +++ /dev/null @@ -1,88 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/destination_item.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, StrictFloat - -from . import ext as ext_1 -from . import offering_asset_group -from . import price as price_1 - - -class Location(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - lat: Annotated[ - StrictFloat, Field(description='Latitude in decimal degrees (WGS 84).', ge=-90.0, le=90.0) - ] - lng: Annotated[ - StrictFloat, - Field(description='Longitude in decimal degrees (WGS 84).', ge=-180.0, le=180.0), - ] - - -class DestinationType(StrEnum): - beach = 'beach' - mountain = 'mountain' - urban = 'urban' - cultural = 'cultural' - adventure = 'adventure' - wellness = 'wellness' - cruise = 'cruise' - - -class DestinationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - destination_id: Annotated[str, Field(description='Unique identifier for this destination.')] - name: Annotated[ - str, Field(description="Destination name (e.g., 'Barcelona', 'Bali', 'Swiss Alps').") - ] - description: Annotated[ - str | None, - Field(description='Destination description highlighting attractions and appeal.'), - ] = None - city: Annotated[str | None, Field(description='City name, if applicable.')] = None - region: Annotated[str | None, Field(description='State, province, or region name.')] = None - country: Annotated[ - str | None, Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = None - location: Annotated[ - Location | None, Field(description='Geographic coordinates of the destination.') - ] = None - destination_type: Annotated[ - DestinationType | None, Field(description='Destination category.') - ] = None - price: Annotated[ - price_1.Price | None, Field(description='Starting price for a trip to this destination.') - ] = None - image_url: Annotated[AnyUrl | None, Field(description='Destination hero image URL.')] = None - url: Annotated[AnyUrl | None, Field(description='Destination landing page or booking URL.')] = ( - None - ) - rating: Annotated[ - StrictFloat | None, Field(description='Destination rating (1–5).', ge=1.0, le=5.0) - ] = None - tags: Annotated[ - list[str] | None, - Field( - description="Tags for filtering (e.g., 'family', 'romantic', 'solo', 'winter-sun').", - min_length=1, - ), - ] = None - assets: Annotated[ - list[offering_asset_group.OfferingAssetGroup] | None, - Field( - description="Typed creative asset pools for this destination. Uses the same OfferingAssetGroup structure as offering-type catalogs. Standard group IDs: 'images_landscape' (destination hero), 'images_vertical' (9:16 for Snap, Stories), 'images_square' (1:1). Enables formats to declare typed image requirements that map unambiguously to the right asset regardless of platform.", - min_length=1, - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/diagnostic_issue.py b/src/adcp/types/generated_poc/core/diagnostic_issue.py deleted file mode 100644 index d3412f7b0..000000000 --- a/src/adcp/types/generated_poc/core/diagnostic_issue.py +++ /dev/null @@ -1,33 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/diagnostic_issue.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - - -class Severity(StrEnum): - error = 'error' - warning = 'warning' - info = 'info' - - -class DiagnosticIssue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - severity: Annotated[ - Severity, - Field( - description="'error': blocks optimization until resolved. 'warning': optimization works but effectiveness is reduced. 'info': suggestion for improvement." - ), - ] - message: Annotated[ - str, - Field(description='Human/agent-readable description of the issue and how to resolve it.'), - ] diff --git a/src/adcp/types/generated_poc/core/downstream_connection_requirement.py b/src/adcp/types/generated_poc/core/downstream_connection_requirement.py deleted file mode 100644 index 8477377df..000000000 --- a/src/adcp/types/generated_poc/core/downstream_connection_requirement.py +++ /dev/null @@ -1,136 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/downstream_connection_requirement.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - - -class ConnectionType(StrEnum): - advertiser_account = 'advertiser_account' - publisher_identity = 'publisher_identity' - post_authorization = 'post_authorization' - - -class RequiredForItem(ScalarStr): - __slots__ = () - _constraints = {'min_length': 1} - _json_schema_extra = { - 'examples': ['list_creatives', 'sync_creatives', 'create_media_buy', 'get_media_buy_delivery', 'get_creative_delivery'], - } - - -class Scope(StrEnum): - account = 'account' - identity = 'identity' - post = 'post' - unknown = 'unknown' - - -class Status(StrEnum): - connected = 'connected' - missing = 'missing' - pending = 'pending' - expired = 'expired' - revoked = 'revoked' - not_required = 'not_required' - unknown = 'unknown' - - -class ResourceRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - platform_account_id: Annotated[ - str | None, - Field( - description='Provider-native advertiser or business account id, when safe to disclose.' - ), - ] = None - identity_id: Annotated[ - str | None, - Field( - description='Provider-native creator, page, channel, organization, or profile id, when safe to disclose.' - ), - ] = None - handle: Annotated[ - str | None, - Field(description='Provider-native public handle for the owning identity, when available.'), - ] = None - profile_url: Annotated[ - AnyUrl | None, Field(description='Public URL for the owning identity, when available.') - ] = None - post_id: Annotated[ - str | None, - Field( - description='Provider-native post id, when the grant is post-scoped or the failed request referenced a specific post.' - ), - ] = None - post_url: Annotated[ - AnyUrl | None, Field(description='Public URL for the referenced post, when available.') - ] = None - - -class DownstreamConnectionRequirement(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - provider: Annotated[ - str | None, - Field( - description='Stable provider or platform namespace, preferably lowercase. Examples: `social.example`, `shortvideo.example`, or a seller-defined namespace. Omit only when the requirement is provider-agnostic, or when an `authorization_url` fully routes the human to the correct provider-specific connection flow.' - ), - ] = None - connection_type: Annotated[ - ConnectionType, - Field( - description='Kind of downstream connection required. `advertiser_account` is the platform account used to buy/manage ads. `publisher_identity` is the creator, page, channel, organization, or profile that owns source posts. `post_authorization` is a post-scoped grant when the platform authorizes individual posts instead of, or in addition to, the owning identity.' - ), - ] - required_for: Annotated[ - list[RequiredForItem] | None, - Field( - description='Concrete AdCP protocol operation names that require this downstream connection. Sellers SHOULD include this in product declarations when the requirement is known ahead of time, and in AUTHORIZATION_REQUIRED details when it explains the failed operation. Prefer specific operation names such as `list_creatives`, `sync_creatives`, `create_media_buy`, `get_media_buy_delivery`, or `get_creative_delivery` over broad category labels such as `reporting`.' - ), - ] = None - scope: Annotated[Scope | None, Field(description='Granularity of the downstream grant.')] = None - status: Annotated[ - Status | None, - Field( - description='Current seller-observed state for this downstream connection when known. Product declarations MAY omit status or use `unknown`; AUTHORIZATION_REQUIRED details SHOULD use `missing`, `expired`, or `revoked` for the connection that blocked the call.' - ), - ] = None - connection_id: Annotated[ - str | None, - Field( - description='Seller-defined identifier for an already-created downstream connection. Omit when no connection exists yet or when exposing it would leak platform/account state.' - ), - ] = None - resource_ref: Annotated[ - ResourceRef | None, - Field( - description='Optional opaque provider-native resource hint, such as a platform account id, profile URL, handle, channel id, post id, or post URL. This is a hint for routing authorization, not proof that authorization exists.' - ), - ] = None - authorization_url: Annotated[ - AnyUrl | None, - Field( - description='Seller-hosted or provider-hosted URL where a human can complete or restore this downstream connection.' - ), - ] = None - authorization_instructions: Annotated[ - str | None, - Field( - description='Human-readable instructions for completing or restoring this downstream connection.' - ), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field(description='Expiration time for the downstream grant, when known.'), - ] = None diff --git a/src/adcp/types/generated_poc/core/duration.py b/src/adcp/types/generated_poc/core/duration.py deleted file mode 100644 index 9fc909dbe..000000000 --- a/src/adcp/types/generated_poc/core/duration.py +++ /dev/null @@ -1,35 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/duration.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field - - -class Unit(StrEnum): - seconds = 'seconds' - minutes = 'minutes' - hours = 'hours' - days = 'days' - campaign = 'campaign' - - -class Duration(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - interval: Annotated[ - SchemaInt, - Field(description="Number of time units. Must be 1 when unit is 'campaign'.", ge=1), - ] - unit: Annotated[ - Unit, - Field( - description="Time unit. 'seconds' for sub-minute precision. 'campaign' spans the full campaign flight." - ), - ] diff --git a/src/adcp/types/generated_poc/core/education_item.py b/src/adcp/types/generated_poc/core/education_item.py deleted file mode 100644 index 8871d79a5..000000000 --- a/src/adcp/types/generated_poc/core/education_item.py +++ /dev/null @@ -1,107 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/education_item.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from datetime import date -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - -from . import ext as ext_1 -from . import offering_asset_group -from . import price as price_1 - - -class DegreeType(StrEnum): - certificate = 'certificate' - associate = 'associate' - bachelor = 'bachelor' - master = 'master' - doctorate = 'doctorate' - professional = 'professional' - bootcamp = 'bootcamp' - - -class Level(StrEnum): - beginner = 'beginner' - intermediate = 'intermediate' - advanced = 'advanced' - - -class Modality(StrEnum): - online = 'online' - in_person = 'in_person' - hybrid = 'hybrid' - - -class EducationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - program_id: Annotated[str, Field(description='Unique identifier for this program or course.')] - name: Annotated[ - str, - Field( - description="Program or course name (e.g., 'MSc Computer Science', 'Digital Marketing Certificate')." - ), - ] - school: Annotated[str, Field(description='Institution or provider name.')] - description: Annotated[ - str | None, - Field(description='Program description including curriculum highlights and outcomes.'), - ] = None - subject: Annotated[ - str | None, - Field( - description="Subject area or field of study (e.g., 'computer-science', 'business', 'healthcare')." - ), - ] = None - degree_type: Annotated[DegreeType | None, Field(description='Type of credential awarded.')] = ( - None - ) - level: Annotated[Level | None, Field(description='Difficulty or prerequisite level.')] = None - price: Annotated[price_1.Price | None, Field(description='Tuition or course fee.')] = None - duration: Annotated[ - str | None, - Field( - description="Program duration as a human-readable string (e.g., '4 weeks', '2 years', '6 months')." - ), - ] = None - start_date: Annotated[ - date | None, Field(description='Next available start date (ISO 8601 date).') - ] = None - language: Annotated[ - str | None, Field(description="Language of instruction (e.g., 'en', 'nl', 'es').") - ] = None - modality: Annotated[Modality | None, Field(description='Delivery format.')] = None - location: Annotated[ - str | None, - Field( - description="Campus or instruction location (e.g., 'Amsterdam, NL'). Omit for fully online programs." - ), - ] = None - image_url: Annotated[AnyUrl | None, Field(description='Program or institution image URL.')] = ( - None - ) - url: Annotated[AnyUrl | None, Field(description='Program landing page or enrollment URL.')] = ( - None - ) - tags: Annotated[ - list[str] | None, - Field( - description="Tags for filtering (e.g., 'stem', 'scholarship-available', 'evening-classes').", - min_length=1, - ), - ] = None - assets: Annotated[ - list[offering_asset_group.OfferingAssetGroup] | None, - Field( - description="Typed creative asset pools for this program. Uses the same OfferingAssetGroup structure as offering-type catalogs. Standard group IDs: 'images_landscape' (campus/program hero), 'images_vertical' (9:16 for Stories), 'logo' (institution logo). Enables formats to declare typed image requirements that map unambiguously to the right asset regardless of platform.", - min_length=1, - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/error.py b/src/adcp/types/generated_poc/core/error.py deleted file mode 100644 index 755bed423..000000000 --- a/src/adcp/types/generated_poc/core/error.py +++ /dev/null @@ -1,167 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/error.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictBool, StrictFloat - - -class BuyerReason(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description="Machine-readable buyer-actionable reason from the standard `enums/error-code.json` vocabulary or a producer-specific extension. Extensions MUST follow the standard `X_{VENDOR}_{CODE}` naming rule. The wire field is open for forward compatibility. Receivers MUST accept unknown values and use the enclosing `error.recovery` plus this object's `message` as the fallback.", - max_length=64, - min_length=1, - ), - ] - message: Annotated[ - str, - Field( - description='Buyer-safe explanation of the actionable failure. MUST NOT expose vendor identifiers, ad-server type names, internal object names, internal IDs, stack traces, or other producer-private implementation details.', - min_length=1, - ), - ] - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | StrictFloat | StrictBool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class Error(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - buyer_reason: Annotated[ - BuyerReason | None, - Field( - description='Optional buyer-actionable classification of the failure. Use this when the enclosing error code or message is too coarse or contains producer-internal context that must not cross the buyer trust boundary. `code` reuses the comprehensive standard vocabulary and recovery classifications published in `enums/error-code.json`, including policy, governance, account, commercial, inventory, and creative failures. The wire field remains open for forward compatibility. `message` MUST be safe to show to the buyer and MUST NOT contain vendor identifiers, ad-server type names, internal object names, internal IDs, stack traces, or other producer-private implementation details. When present, the enclosing `error.recovery` MUST classify the buyer-actionable reason. If both the enclosing code and buyer reason are registered, their standard recovery classifications MUST agree with each other and with `error.recovery`; sellers MUST choose the closest compatible enclosing code rather than retain a conflicting upstream transport wrapper. One buyer_reason classifies one error object. If a rejection contains independently actionable failures from different classes, sellers MUST emit separate error objects or omit buyer_reason rather than select a misleading single class.' - ), - ] = None - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - StrictFloat | None, - Field( - description='Seconds to wait before retrying the operation. AdCP 3.2 producers MUST emit an integer from 1 through 3600. The 3.x schema continues to accept finite fractional values for backward compatibility with earlier producers; consumers receiving one MUST round up to the next whole second before clamping so the retry is never scheduled earlier than intended. Non-finite values are treated as absent.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_OPTION",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). AdCP 3.2 producers MUST populate `recovery` on every error; 3.1 producers SHOULD populate it. The shared 3.x schema intentionally does not add `recovery` to `required` so retained and live errors from earlier 3.x producers remain decodable. When `buyer_reason` is present, `recovery` is required and MUST classify that buyer-actionable reason. If the enclosing code and buyer reason are both registered, their standard recovery classifications MUST agree with each other and with this field. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. When a legacy error omits `recovery`, receivers use the registered classification for a known code and fall back to `transient` for an unknown code, subject to the bounded retry budget. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known top-level and buyer-reason codes; `error.recovery` on the wire is authoritative when present.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/evaluator_spec.py b/src/adcp/types/generated_poc/core/evaluator_spec.py deleted file mode 100644 index 7ad721736..000000000 --- a/src/adcp/types/generated_poc/core/evaluator_spec.py +++ /dev/null @@ -1,220 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/evaluator_spec.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, ConfigDict, Field, StrictFloat - -from ..content_standards import artifact -from . import ext as ext_1 -from . import feature_requirement as feature_requirement_1 - - -class Direction(StrEnum): - maximize = 'maximize' - minimize = 'minimize' - - -class RankByItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - feature_id: Annotated[ - str, - Field( - description='Creative feature to order by (discovered via get_adcp_capabilities; the same feature_id space the chosen evaluator form returns in eval.features[]).' - ), - ] - direction: Annotated[ - Direction | None, - Field( - description='Sort direction for this feature: `maximize` ranks higher feature values first (e.g. creative_quality_score), `minimize` ranks lower values first (e.g. predicted_cpa).' - ), - ] = Direction.maximize - - -class FeatureAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the get_creative_features-capable agent the producing agent calls to obtain creative-feature values for the gate. MUST use https:// and MUST match an entry in the seller's `creative_policy.accepted_verifiers[].agent_url`; off-list → `EVALUATOR_AGENT_NOT_ACCEPTED`." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional canonical feature_id the producing agent SHOULD request against this agent. When present it SHOULD match the agent's `accepted_verifiers[].feature_id` or be omitted; when absent the seller selects a feature at evaluation time. Resolves selector ambiguity exactly as the provenance verify_agent.feature_id does." - ), - ] = None - - -class EvalBudget(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - max_calls: Annotated[ - SchemaInt | None, - Field(description='Soft cap on the number of judge calls the evaluator should make.', ge=1), - ] = None - max_seconds: Annotated[ - StrictFloat | None, - Field(description='Soft cap on wall-clock seconds the evaluation should consume.', ge=0.0), - ] = None - - -class RankByItem1(RankByItem): - pass - - -class RankByItem2(RankByItem): - pass - - -class EvaluatorSpec2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - feature_requirement: Annotated[ - list[feature_requirement_1.FeatureRequirement] | None, - Field( - description='Optional hard GATE over creative-feature values — the predicates a leaf MUST satisfy for the producing agent to recommend/return it. Reuses the feature-requirement shape (min_value/max_value for quantitative features like creative_quality_score, allowed_values for binary/categorical) — the same predicate vocabulary that gates property/audience filters, which its own schema names as an intended creative-gate reuse. A leaf that fails any predicate is DROPPED from the agent\'s best_of_n survivors before ranking — this is internal pruning of which leaves the agent recommends, not an AdCP-layer block of an already-produced billable leaf (what is produced/billed is governed by max_variants/max_creatives/max_spend). Distinct from `rank_by`: the gate is a pass/fail predicate (drop on fail), `rank_by` is an ordering over survivors. Each predicate\'s `if_not_covered` (exclude|include, default exclude) is the fail-open knob when the source cannot measure that feature. A pass/warn/fail verdict is expressed as a categorical string feature value gated via `allowed_values` (e.g. ["pass"] or ["pass","warn"]) — the buyer\'s predicate decides whether warn passes; the verdict is derived, never stored on creative-feature-result. Omit to leave evaluation advisory (no leaf is dropped).', - min_length=1, - ), - ] = None - rank_by: Annotated[ - list[RankByItem1] | None, - Field( - description='Optional RANK ordering over creative-feature values — an ordered list (most significant first) the agent uses to order the gate survivors into recommended/rank. An explicit {feature_id, direction} ordering rather than the feature-requirement predicate (which has no sort direction): the gate decides pass/fail, rank_by decides better/worse. Soft preference, never a gate: leaves are not dropped by rank_by, they are only ordered. Omit to let the evaluator/seller choose the ordering.', - min_length=1, - ), - ] = None - feature_agent: Annotated[ - FeatureAgent | None, - Field( - description="Optional buyer-attached pointer to a get_creative_features-capable creative-feature / governance agent the producing agent calls to evaluate each leaf (the gate's SOURCE of feature values). This is the buyer-represents → seller-calls pattern #5280 established for provenance, generalized to the evaluator gate: the buyer REPRESENTS which agent it used, but the seller is the verifier-of-record and decides which agent it actually calls. `agent_url` MUST appear (canonicalized per /docs/reference/url-canonicalization) in the seller's `creative_policy.accepted_verifiers[].agent_url`; an off-list agent is rejected with `EVALUATOR_AGENT_NOT_ACCEPTED` (mirrors PROVENANCE_VERIFIER_NOT_ACCEPTED) before any outbound call. The outbound evaluator call authenticates on the transport; this pointer MUST NOT carry API keys, bearer tokens, client secrets, authorization values, JWKs, JWKS documents, or JWKS URIs. Reuses the same allowlist mechanism — no new allowlist is introduced. Distinct from the `agent_url` oneOf form, which names the evaluator's source directly; `feature_agent` attaches the gate's measurement source alongside any of the three forms." - ), - ] = None - eval_budget: Annotated[ - EvalBudget | None, - Field( - description='Optional soft ceiling on evaluation effort. Advisory in v1 with no billing coupling. Well-known soft fields max_calls / max_seconds; open for evaluator-specific knobs.' - ), - ] = None - ext: ext_1.ExtensionObject | None = None - evaluator_id: Annotated[ - str, - Field( - description='Account-scoped house evaluator preset selected by the buyer. This id is pre-provisioned/account-arranged, not discovered from get_adcp_capabilities governance.creative_features. That catalog only discovers the feature vocabulary the preset emits. An unknown id degrades to seller-default ranking (advisory errors[] note), not a failure.' - ), - ] - - -class EvaluatorSpec3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - feature_requirement: Annotated[ - list[feature_requirement_1.FeatureRequirement] | None, - Field( - description='Optional hard GATE over creative-feature values — the predicates a leaf MUST satisfy for the producing agent to recommend/return it. Reuses the feature-requirement shape (min_value/max_value for quantitative features like creative_quality_score, allowed_values for binary/categorical) — the same predicate vocabulary that gates property/audience filters, which its own schema names as an intended creative-gate reuse. A leaf that fails any predicate is DROPPED from the agent\'s best_of_n survivors before ranking — this is internal pruning of which leaves the agent recommends, not an AdCP-layer block of an already-produced billable leaf (what is produced/billed is governed by max_variants/max_creatives/max_spend). Distinct from `rank_by`: the gate is a pass/fail predicate (drop on fail), `rank_by` is an ordering over survivors. Each predicate\'s `if_not_covered` (exclude|include, default exclude) is the fail-open knob when the source cannot measure that feature. A pass/warn/fail verdict is expressed as a categorical string feature value gated via `allowed_values` (e.g. ["pass"] or ["pass","warn"]) — the buyer\'s predicate decides whether warn passes; the verdict is derived, never stored on creative-feature-result. Omit to leave evaluation advisory (no leaf is dropped).', - min_length=1, - ), - ] = None - rank_by: Annotated[ - list[RankByItem2] | None, - Field( - description='Optional RANK ordering over creative-feature values — an ordered list (most significant first) the agent uses to order the gate survivors into recommended/rank. An explicit {feature_id, direction} ordering rather than the feature-requirement predicate (which has no sort direction): the gate decides pass/fail, rank_by decides better/worse. Soft preference, never a gate: leaves are not dropped by rank_by, they are only ordered. Omit to let the evaluator/seller choose the ordering.', - min_length=1, - ), - ] = None - feature_agent: Annotated[ - FeatureAgent | None, - Field( - description="Optional buyer-attached pointer to a get_creative_features-capable creative-feature / governance agent the producing agent calls to evaluate each leaf (the gate's SOURCE of feature values). This is the buyer-represents → seller-calls pattern #5280 established for provenance, generalized to the evaluator gate: the buyer REPRESENTS which agent it used, but the seller is the verifier-of-record and decides which agent it actually calls. `agent_url` MUST appear (canonicalized per /docs/reference/url-canonicalization) in the seller's `creative_policy.accepted_verifiers[].agent_url`; an off-list agent is rejected with `EVALUATOR_AGENT_NOT_ACCEPTED` (mirrors PROVENANCE_VERIFIER_NOT_ACCEPTED) before any outbound call. The outbound evaluator call authenticates on the transport; this pointer MUST NOT carry API keys, bearer tokens, client secrets, authorization values, JWKs, JWKS documents, or JWKS URIs. Reuses the same allowlist mechanism — no new allowlist is introduced. Distinct from the `agent_url` oneOf form, which names the evaluator's source directly; `feature_agent` attaches the gate's measurement source alongside any of the three forms." - ), - ] = None - eval_budget: Annotated[ - EvalBudget | None, - Field( - description='Optional soft ceiling on evaluation effort. Advisory in v1 with no billing coupling. Well-known soft fields max_calls / max_seconds; open for evaluator-specific knobs.' - ), - ] = None - ext: ext_1.ExtensionObject | None = None - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of an external get_creative_features-capable judge agent the seller calls to score the produced leaves. MUST match an entry in the seller's `creative_policy.accepted_verifiers[].agent_url` (off-list → `EVALUATOR_AGENT_NOT_ACCEPTED`); an on-list agent that is unreachable or rejects the producing agent's transport authentication degrades to seller-default ranking (advisory errors[] note), not a failure. Authentication and trust material for this call belongs on the transport or in account provisioning, not in the evaluator payload." - ), - ] - - -class Exemplars(AdCPBaseModel): - pass_: Annotated[ - list[artifact.Artifact] | None, - Field( - alias='pass', - description='Artifacts exemplifying variants the buyer considers good — the high end (≈1) of the calibrated prediction feature.', - ), - ] = None - fail: Annotated[ - list[artifact.Artifact] | None, - Field( - description='Artifacts exemplifying variants the buyer considers bad — the low end (≈0) of the calibrated prediction feature.' - ), - ] = None - - -class EvaluatorSpec1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - feature_requirement: Annotated[ - list[feature_requirement_1.FeatureRequirement] | None, - Field( - description='Optional hard GATE over creative-feature values — the predicates a leaf MUST satisfy for the producing agent to recommend/return it. Reuses the feature-requirement shape (min_value/max_value for quantitative features like creative_quality_score, allowed_values for binary/categorical) — the same predicate vocabulary that gates property/audience filters, which its own schema names as an intended creative-gate reuse. A leaf that fails any predicate is DROPPED from the agent\'s best_of_n survivors before ranking — this is internal pruning of which leaves the agent recommends, not an AdCP-layer block of an already-produced billable leaf (what is produced/billed is governed by max_variants/max_creatives/max_spend). Distinct from `rank_by`: the gate is a pass/fail predicate (drop on fail), `rank_by` is an ordering over survivors. Each predicate\'s `if_not_covered` (exclude|include, default exclude) is the fail-open knob when the source cannot measure that feature. A pass/warn/fail verdict is expressed as a categorical string feature value gated via `allowed_values` (e.g. ["pass"] or ["pass","warn"]) — the buyer\'s predicate decides whether warn passes; the verdict is derived, never stored on creative-feature-result. Omit to leave evaluation advisory (no leaf is dropped).', - min_length=1, - ), - ] = None - rank_by: Annotated[ - list[RankByItem] | None, - Field( - description='Optional RANK ordering over creative-feature values — an ordered list (most significant first) the agent uses to order the gate survivors into recommended/rank. An explicit {feature_id, direction} ordering rather than the feature-requirement predicate (which has no sort direction): the gate decides pass/fail, rank_by decides better/worse. Soft preference, never a gate: leaves are not dropped by rank_by, they are only ordered. Omit to let the evaluator/seller choose the ordering.', - min_length=1, - ), - ] = None - feature_agent: Annotated[ - FeatureAgent | None, - Field( - description="Optional buyer-attached pointer to a get_creative_features-capable creative-feature / governance agent the producing agent calls to evaluate each leaf (the gate's SOURCE of feature values). This is the buyer-represents → seller-calls pattern #5280 established for provenance, generalized to the evaluator gate: the buyer REPRESENTS which agent it used, but the seller is the verifier-of-record and decides which agent it actually calls. `agent_url` MUST appear (canonicalized per /docs/reference/url-canonicalization) in the seller's `creative_policy.accepted_verifiers[].agent_url`; an off-list agent is rejected with `EVALUATOR_AGENT_NOT_ACCEPTED` (mirrors PROVENANCE_VERIFIER_NOT_ACCEPTED) before any outbound call. The outbound evaluator call authenticates on the transport; this pointer MUST NOT carry API keys, bearer tokens, client secrets, authorization values, JWKs, JWKS documents, or JWKS URIs. Reuses the same allowlist mechanism — no new allowlist is introduced. Distinct from the `agent_url` oneOf form, which names the evaluator's source directly; `feature_agent` attaches the gate's measurement source alongside any of the three forms." - ), - ] = None - eval_budget: Annotated[ - EvalBudget | None, - Field( - description='Optional soft ceiling on evaluation effort. Advisory in v1 with no billing coupling. Well-known soft fields max_calls / max_seconds; open for evaluator-specific knobs.' - ), - ] = None - ext: ext_1.ExtensionObject | None = None - exemplars: Annotated[ - Exemplars, - Field( - description='Pass/fail examples that calibrate the single prediction feature (e.g. `predicted_performance` in [0,1]) the evaluator returns in eval.features[]. Artifact-based, mirroring content-standards.json calibration_exemplars.' - ), - ] - - -EvaluatorSpec = Annotated[ - EvaluatorSpec1 | EvaluatorSpec2 | EvaluatorSpec3, - Field( - description="Advisory buyer-attached pointer (#5280) declaring HOW produced build_creative variants should be evaluated and ranked. The rank-side of the get_creative_features feature oracle: the chosen form is the SOURCE of feature evaluation that yields creative-feature values for each leaf, `feature_requirement` is an optional hard GATE over those values, and `rank_by` is the RANK ordering over the survivors. Informs the agent's best_of_n recommended/rank and, when the agent advertises creative.supports_evaluator, populates a per-leaf `eval` block (creative-feature-result[]) on the response.\n\nGate-then-rank pipeline (per produced leaf in a best_of_n run): the producing agent (1) evaluates the leaf via the chosen form, obtaining creative-feature values; (2) when `feature_requirement` is present, applies those predicates and DROPS any leaf that fails (internal best_of_n pruning) — survivors only; (3) orders survivors by `rank_by`. The gate operates BEFORE return: it prunes which leaves the agent recommends/returns from its own best_of_n exploration, never an AdCP-layer block of an already-produced billable leaf — what is produced and billed is governed by max_variants/max_creatives/max_spend, not by the evaluator. With no `feature_requirement`, evaluation stays advisory: every produced leaf is returned and billed and ranking is a soft preference, so `rank_by` failures rank lower but are never dropped.\n\nWhen the chosen form is `feature_agent` (or `agent_url`), the seller calls an external get_creative_features-capable agent, so the pointer is subject to the seller's `creative_policy.accepted_verifiers[]` allowlist exactly as #5280 established for provenance verify_agent: the buyer REPRESENTS which on-list agent it used, the seller (verifier-of-record) CALLS it. An off-list agent is rejected with `EVALUATOR_AGENT_NOT_ACCEPTED` (mirrors PROVENANCE_VERIFIER_NOT_ACCEPTED) before any outbound call; an on-list agent that is unreachable or unknown degrades to seller-default ranking with an advisory errors[] note (non-terminal), NOT a failure.\n\nEvaluator credentials and caller-supplied trust material are never carried in this object, `context`, `ext`, or the creative payload. The producing creative agent authenticates its outbound call to the evaluator on the transport (RFC 9421 request signing/JWKS, mTLS, or a pre-provisioned Bearer/API-key credential). Credential- or trust-material payload keys (for example `api_key`, `client_secret`, `authorization`, `jwk`, `jwks`, or `jwks_uri`) are non-conforming and should be rejected with `CREDENTIAL_IN_ARGS`.\n\nProvide exactly one of three forms; an optional `feature_requirement` gate, an optional `rank_by` ordering, an optional `feature_agent` allowlisted pointer, and a shared optional eval_budget apply to whichever form is chosen. Feature discovery uses the existing governance.creative_features catalog on get_adcp_capabilities; evaluator outputs reuse creative-feature-result[] and do not mint a parallel verdict/result surface. `evaluator_id` is not discovered from governance.creative_features: it is a pre-provisioned, account-arranged preset whose emitted feature_ids still come from that catalog.\n\nExperimental (x-status: experimental): this whole evaluator surface — the `evaluator` input, the per-leaf `eval` response block, and `creative.supports_evaluator` — is new and not yet field-tested across parties. Sellers that implement it MUST list `creative.evaluator` in `experimental_features`. Reserved 3.x follow-ons that may reshape these fields: a separate `supports_evaluator_gate` capability and a hard MUST-enforce-gate semantic. Per docs/reference/experimental-status, it MAY change between 3.x releases with notice.", - title='Evaluator Spec', - ), -] diff --git a/src/adcp/types/generated_poc/core/event.py b/src/adcp/types/generated_poc/core/event.py deleted file mode 100644 index 96c9afd97..000000000 --- a/src/adcp/types/generated_poc/core/event.py +++ /dev/null @@ -1,61 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/event.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - -from ..enums import action_source as action_source_1 -from ..enums import event_type as event_type_1 -from . import event_custom_data, event_surface -from . import ext as ext_1 -from . import user_match as user_match_1 - - -class Event(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - event_id: Annotated[ - str, - Field( - description='Unique identifier for deduplication (scoped to event_type + event_source_id)', - max_length=256, - min_length=1, - ), - ] - event_type: Annotated[event_type_1.EventType, Field(description='Standard event type')] - event_time: Annotated[ - AwareDatetime, Field(description='ISO 8601 timestamp when the event occurred') - ] - user_match: Annotated[ - user_match_1.UserMatch | None, - Field(description='User identifiers for attribution matching'), - ] = None - custom_data: Annotated[ - event_custom_data.EventCustomData | None, - Field(description='Event-specific data (value, currency, items, etc.)'), - ] = None - action_source: Annotated[ - action_source_1.ActionSource | None, Field(description='Where the event originated') - ] = None - surface: Annotated[ - event_surface.EventSurface | None, - Field( - description='Optional structured surface context for the event, such as an owned channel, profile, feed, podcast, newsletter list, website, app, or store. Complements `action_source`; use it when optimization-relevant meaning would otherwise live only in platform-specific `ext` metadata.' - ), - ] = None - event_source_url: Annotated[ - AnyUrl | None, - Field( - description="URL where the event occurred (required when action_source is 'website')" - ), - ] = None - custom_event_name: Annotated[ - str | None, Field(description="Name for custom events (used when event_type is 'custom')") - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/event_custom_data.py b/src/adcp/types/generated_poc/core/event_custom_data.py deleted file mode 100644 index 222cf806f..000000000 --- a/src/adcp/types/generated_poc/core/event_custom_data.py +++ /dev/null @@ -1,86 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/event_custom_data.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictFloat - -from . import ext as ext_1 - - -class Content(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - id: Annotated[str, Field(description='Product or content identifier')] - quantity: Annotated[SchemaInt | None, Field(description='Quantity of this item', ge=1)] = None - price: Annotated[ - StrictFloat | None, Field(description='Price per unit of this item', ge=0.0) - ] = None - brand: Annotated[str | None, Field(description='Brand name of this item')] = None - - -class EventCustomData(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - value: Annotated[ - StrictFloat | None, - Field( - description='Monetary value of the event. For an event source that declares value_currencies, currency is required and MUST appear in that list. Legacy sources without the new contract remain schema-compatible but are ineligible for canonical ROAS.', - ge=0.0, - ), - ] = None - currency: Annotated[ - str | None, - Field( - description='ISO 4217 currency code. For a source declaring value_currencies, monetary records MUST carry a currency from that list.', - pattern='^[A-Z]{3}$', - ), - ] = None - order_id: Annotated[str | None, Field(description='Unique order or transaction identifier')] = ( - None - ) - content_ids: Annotated[ - list[str] | None, - Field( - description="Item identifiers for catalog attribution. Values are matched against catalog items using the identifier type declared by the catalog's content_id_type field (e.g., SKUs, GTINs, or vertical-specific IDs like job_id)." - ), - ] = None - content_type: Annotated[ - str | None, - Field( - description="Category of content associated with the event (e.g., 'product', 'job', 'hotel'). Corresponds to the catalog type when used for catalog attribution." - ), - ] = None - content_name: Annotated[str | None, Field(description='Name of the product or content')] = None - content_category: Annotated[ - str | None, Field(description='Category of the product or content') - ] = None - num_items: Annotated[ - SchemaInt | None, Field(description='Number of items in the event', ge=0) - ] = None - search_string: Annotated[str | None, Field(description='Search query for search events')] = None - progress_percent: Annotated[ - StrictFloat | None, - Field( - description='Content progress percentage reached, primarily for `watch_milestone` events. Use 25, 50, 75, or 100 for quartiles, or another publisher-defined threshold. Do not use `value` for non-monetary progress.', - ge=0.0, - le=100.0, - ), - ] = None - progress_seconds: Annotated[ - StrictFloat | None, - Field( - description='Content progress duration reached in seconds, primarily for `watch_milestone` events. Use when the milestone is time-based rather than percentage-based.', - ge=0.0, - ), - ] = None - contents: Annotated[ - list[Content] | None, Field(description='Per-item details for e-commerce events') - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/event_source_health.py b/src/adcp/types/generated_poc/core/event_source_health.py deleted file mode 100644 index 01c4f5c1f..000000000 --- a/src/adcp/types/generated_poc/core/event_source_health.py +++ /dev/null @@ -1,86 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/event_source_health.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat - -from ..enums import assessment_status -from . import diagnostic_issue - - -class Detail(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - score: Annotated[ - StrictFloat, - Field( - description='Seller-defined quality score. Scale varies by seller — only compare within the same seller.', - ge=0.0, - ), - ] - max_score: Annotated[ - StrictFloat, Field(description="Maximum possible score on this seller's scale.", ge=1.0) - ] - label: Annotated[ - str | None, - Field( - description="Seller's name for this score (e.g., 'Event Quality Score', 'Event Match Quality')." - ), - ] = None - - -class EventSourceHealth(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - assessment_status.AssessmentStatus, - Field( - description="Overall health status. Use this for cross-seller decisions — do not rely on detail.score for comparability. 'insufficient' covers the source-offline case (zero events received over the seller's evaluation window) — disambiguate via `events_received_24h: 0` and a stale `last_event_at`. Sellers MUST surface a corresponding impairment (reason_code: source_offline) on any active media buy whose conversion goals depend on an event source that has gone offline." - ), - ] - detail: Annotated[ - Detail | None, - Field( - description='Seller-specific scoring detail. Only present when the seller has a native quality score to relay. Buyer agents should use status (not detail) for cross-seller decisions. Detail is supplementary context for human review or advanced diagnostics.' - ), - ] = None - match_rate: Annotated[ - StrictFloat | None, - Field( - description='Fraction of events from this source that the seller successfully matched to ad interactions (0.0-1.0). Low match rates indicate weak user_match identifiers. Absent when the seller does not compute match rates.', - ge=0.0, - le=1.0, - ), - ] = None - last_event_at: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp of the most recent event received from this source. Absent when no events have been received.' - ), - ] = None - evaluated_at: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp of when this health assessment was computed. When health is derived from reporting data, this may lag real-time. Buyer agents can use this to decide whether to trust stale assessments or re-request.' - ), - ] = None - events_received_24h: Annotated[ - SchemaInt | None, - Field( - description='Number of events received from this source in the last 24 hours. Zero indicates the source is configured but not firing.', - ge=0, - ), - ] = None - issues: Annotated[ - list[diagnostic_issue.DiagnosticIssue] | None, - Field( - description='Actionable issues detected with this event source. Sellers should limit to the top 3-5 most actionable items. Buyer agents should sort by severity rather than relying on array position.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/event_surface.py b/src/adcp/types/generated_poc/core/event_surface.py deleted file mode 100644 index 1255d8a3c..000000000 --- a/src/adcp/types/generated_poc/core/event_surface.py +++ /dev/null @@ -1,63 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/event_surface.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from . import ext as ext_1 - - -class Category(StrEnum): - owned_property = 'owned_property' - website = 'website' - app = 'app' - offline = 'offline' - phone_call = 'phone_call' - chat = 'chat' - email = 'email' - in_store = 'in_store' - system_generated = 'system_generated' - other = 'other' - - -class EventSurface(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - category: Annotated[ - Category, - Field( - description='Generic surface category. `owned_property` covers durable creator or brand-controlled properties hosted by a platform, such as channels, profiles, feeds, lists, podcasts, or playlists.' - ), - ] - property_type: Annotated[ - str | None, - Field( - description='Open vocabulary describing the kind of property, for example `channel`, `profile`, `feed`, `list`, `podcast`, `playlist`, or `newsletter`. Required by convention when `category` is `owned_property`; optional for other categories.', - max_length=128, - min_length=1, - ), - ] = None - namespace: Annotated[ - str | None, - Field( - description='Platform, publisher, or system namespace for the property, such as `video_platform`, `short_video_app`, `audio_service`, or a seller-defined namespace. This is intentionally not an enum.', - max_length=128, - min_length=1, - ), - ] = None - property_id: Annotated[ - str | None, - Field( - description='Optional identifier for the property within `namespace`.', - max_length=256, - min_length=1, - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/experimental_feature_id.py b/src/adcp/types/generated_poc/core/experimental_feature_id.py deleted file mode 100644 index 637e6157d..000000000 --- a/src/adcp/types/generated_poc/core/experimental_feature_id.py +++ /dev/null @@ -1,20 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/experimental_feature_id.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from adcp.types._scalar import ScalarStr - - -class ExperimentalFeatureId(ScalarStr): - __slots__ = () - _constraints = { - 'max_length': 128, - 'min_length': 1, - 'pattern': '^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$', - } - _json_schema_extra = { - 'description': 'Dot-separated lowercase identifier for an experimental AdCP surface, such as protocol.principal or media_buy.reporting_delivery.', - 'title': 'Experimental Feature ID', - } diff --git a/src/adcp/types/generated_poc/core/ext.py b/src/adcp/types/generated_poc/core/ext.py deleted file mode 100644 index f2cd14185..000000000 --- a/src/adcp/types/generated_poc/core/ext.py +++ /dev/null @@ -1,14 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/ext.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict - - -class ExtensionObject(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) diff --git a/src/adcp/types/generated_poc/core/feature_requirement.py b/src/adcp/types/generated_poc/core/feature_requirement.py deleted file mode 100644 index 101e00bf0..000000000 --- a/src/adcp/types/generated_poc/core/feature_requirement.py +++ /dev/null @@ -1,52 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/feature_requirement.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictFloat - - -class IfNotCovered(StrEnum): - exclude = 'exclude' - include = 'include' - - -class FeatureRequirement(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - feature_id: Annotated[ - str, Field(description='Feature to evaluate (discovered via get_adcp_capabilities)') - ] - min_value: Annotated[ - StrictFloat | None, - Field(description='Minimum numeric value required (for quantitative features)'), - ] = None - max_value: Annotated[ - StrictFloat | None, - Field(description='Maximum numeric value allowed (for quantitative features)'), - ] = None - allowed_values: Annotated[ - list[Any] | None, - Field( - description='Values that pass the requirement (for binary/categorical features)', - min_length=1, - ), - ] = None - if_not_covered: Annotated[ - IfNotCovered | None, - Field( - description="How to handle properties where this feature is not covered. 'exclude' (default): property is removed from the list. 'include': property passes this requirement (fail-open)." - ), - ] = IfNotCovered.exclude - policy_id: Annotated[ - str | None, - Field( - description='Optional attribution — when this requirement encodes a specific buyer-chosen threshold authorized by a policy, policy_id references the authorizing PolicyEntry. Producers populate when the mechanism exists because of a specific policy (e.g., max_value: 15 on audience_children_composition because of uk_hfss); do NOT populate when the requirement is a general filter unrelated to any policy. Governance findings echo this policy_id when emitting denials traced to the requirement. See /docs/governance/policy-attribution.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/flight_item.py b/src/adcp/types/generated_poc/core/flight_item.py deleted file mode 100644 index eacf6d9c7..000000000 --- a/src/adcp/types/generated_poc/core/flight_item.py +++ /dev/null @@ -1,78 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/flight_item.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - -from . import ext as ext_1 -from . import offering_asset_group -from . import price as price_1 - - -class Origin(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - airport_code: Annotated[ - str, - Field(description="IATA airport code (e.g., 'AMS', 'JFK', 'LHR').", pattern='^[A-Z]{3}$'), - ] - city: Annotated[str | None, Field(description="City name (e.g., 'Amsterdam', 'New York').")] = ( - None - ) - - -class Destination(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - airport_code: Annotated[str, Field(description='IATA airport code.', pattern='^[A-Z]{3}$')] - city: Annotated[str | None, Field(description='City name.')] = None - - -class FlightItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - flight_id: Annotated[ - str, Field(description='Unique identifier for this flight route or offer.') - ] - origin: Annotated[Origin, Field(description='Departure airport or city.')] - destination: Annotated[Destination, Field(description='Arrival airport or city.')] - airline: Annotated[str | None, Field(description='Airline name or IATA airline code.')] = None - price: Annotated[price_1.Price | None, Field(description='Ticket price or starting fare.')] = ( - None - ) - description: Annotated[ - str | None, Field(description='Route description or promotional text.') - ] = None - departure_time: Annotated[ - AwareDatetime | None, Field(description='Departure date and time (ISO 8601).') - ] = None - arrival_time: Annotated[ - AwareDatetime | None, Field(description='Arrival date and time (ISO 8601).') - ] = None - image_url: Annotated[ - AnyUrl | None, Field(description='Promotional image URL (typically a destination photo).') - ] = None - url: Annotated[AnyUrl | None, Field(description='Booking page URL for this route.')] = None - tags: Annotated[ - list[str] | None, - Field( - description="Tags for filtering (e.g., 'direct', 'red-eye', 'business-class').", - min_length=1, - ), - ] = None - assets: Annotated[ - list[offering_asset_group.OfferingAssetGroup] | None, - Field( - description="Typed creative asset pools for this flight. Uses the same OfferingAssetGroup structure as offering-type catalogs. Standard group IDs: 'images_landscape' (destination hero), 'images_vertical' (9:16 for Stories), 'images_square' (1:1). Enables formats to declare typed image requirements that map unambiguously to the right asset regardless of platform.", - min_length=1, - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/forecast_dimension_audience.py b/src/adcp/types/generated_poc/core/forecast_dimension_audience.py deleted file mode 100644 index d523f7699..000000000 --- a/src/adcp/types/generated_poc/core/forecast_dimension_audience.py +++ /dev/null @@ -1,28 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/forecast_dimension_audience.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..enums import audience_source as audience_source_1 - - -class AudienceForecastDimension(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['audience'], Field(description='Dimension family discriminator.')] = 'audience' - audience_id: Annotated[ - str, Field(description='Audience segment identifier for this forecast row.') - ] - audience_source: Annotated[ - audience_source_1.AudienceSource, Field(description='Origin of the audience segment.') - ] - audience_name: Annotated[ - str | None, Field(description='Human-readable audience segment name.') - ] = None diff --git a/src/adcp/types/generated_poc/core/forecast_dimension_device_platform.py b/src/adcp/types/generated_poc/core/forecast_dimension_device_platform.py deleted file mode 100644 index 6264c7110..000000000 --- a/src/adcp/types/generated_poc/core/forecast_dimension_device_platform.py +++ /dev/null @@ -1,25 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/forecast_dimension_device_platform.json -# timestamp: 2026-06-01T00:32:59+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..enums import device_platform as device_platform_1 - - -class DevicePlatformForecastDimension(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[ - Literal['device_platform'], Field(description='Dimension family discriminator.') - ] = 'device_platform' - device_platform: Annotated[ - device_platform_1.DevicePlatform, - Field(description='Operating system or platform for this forecast row.'), - ] diff --git a/src/adcp/types/generated_poc/core/forecast_dimension_device_type.py b/src/adcp/types/generated_poc/core/forecast_dimension_device_type.py deleted file mode 100644 index 709e3a5f5..000000000 --- a/src/adcp/types/generated_poc/core/forecast_dimension_device_type.py +++ /dev/null @@ -1,22 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/forecast_dimension_device_type.json -# timestamp: 2026-06-01T00:32:59+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..enums import device_type as device_type_1 - - -class DeviceTypeForecastDimension(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['device_type'], Field(description='Dimension family discriminator.')] = 'device_type' - device_type: Annotated[ - device_type_1.DeviceType, Field(description='Device form factor for this forecast row.') - ] diff --git a/src/adcp/types/generated_poc/core/forecast_dimension_geo.py b/src/adcp/types/generated_poc/core/forecast_dimension_geo.py deleted file mode 100644 index c53d9c428..000000000 --- a/src/adcp/types/generated_poc/core/forecast_dimension_geo.py +++ /dev/null @@ -1,48 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/forecast_dimension_geo.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..enums import geo_level as geo_level_1 - - -class GeoForecastDimension(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['geo'], Field(description='Dimension family discriminator.')] = 'geo' - geo_level: Annotated[ - geo_level_1.GeographicTargetingLevel, - Field(description='Geographic level for this forecast point.'), - ] - system: Annotated[ - str | None, - Field( - description="Classification system for metro or postal_area levels. Required when geo_level is 'metro' or 'postal_area'. Metro rows use metro-system enum values such as 'nielsen_dma'; native postal rows use country-local postal-system enum values such as 'zip' with country 'US'; deprecated legacy postal rows may use legacy-postal-system enum values such as 'us_zip'. Omit for country and region rows." - ), - ] = None - country: Annotated[ - str | None, - Field( - description='ISO 3166-1 alpha-2 country code. Required for native postal_area rows and omitted for legacy postal rows, metro rows, country rows, and region rows.', - pattern='^[A-Z]{2}$', - ), - ] = None - geo_code: Annotated[ - str, - Field( - description="Geographic code within the level and system. Country: ISO 3166-1 alpha-2 ('US'). Region: ISO 3166-2 with country prefix ('US-CA'). Metro/postal: system-specific code ('501', '10001')." - ), - ] - geo_name: Annotated[ - str | None, - Field( - description="Human-readable geographic name (e.g., 'United States', 'California', 'New York DMA')." - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/forecast_dimension_placement.py b/src/adcp/types/generated_poc/core/forecast_dimension_placement.py deleted file mode 100644 index d0baa3c53..000000000 --- a/src/adcp/types/generated_poc/core/forecast_dimension_placement.py +++ /dev/null @@ -1,31 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/forecast_dimension_placement.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from . import placement_ref as placement_ref_1 - - -class PlacementForecastDimension(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['placement'], Field(description='Dimension family discriminator.')] = 'placement' - placement_ref: Annotated[ - placement_ref_1.PlacementReference, - Field( - description="Structured placement reference for this forecast row. References an entry from the product's placements array." - ), - ] - placement_name: Annotated[ - str | None, - Field( - description='Human-readable placement name, useful when the buyer has not resolved the placement catalog.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/forecast_dimension_signal.py b/src/adcp/types/generated_poc/core/forecast_dimension_signal.py deleted file mode 100644 index 8e25eb220..000000000 --- a/src/adcp/types/generated_poc/core/forecast_dimension_signal.py +++ /dev/null @@ -1,72 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/forecast_dimension_signal.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictBool, StrictFloat, model_validator - -from . import signal_ref as signal_ref_1 - - -class Presence(StrEnum): - present = 'present' - absent = 'absent' - - -class SignalForecastDimension(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['signal'], Field(description='Dimension family discriminator.')] = 'signal' - signal_ref: Annotated[ - signal_ref_1.SignalRef | None, - Field( - description='Canonical signal reference for this forecast row. Required when the row needs to disambiguate product-local, data-provider, or signal-source identity. Product-relative forecasts SHOULD use signal_ref.' - ), - ] = None - signal_id: Annotated[ - str | None, - Field( - description='Signal identifier shorthand for this forecast row. Use only when the enclosing context already identifies the signal unambiguously, such as a coverage_forecast nested directly under one get_signals signal item. Otherwise use signal_ref.', - pattern='^[a-zA-Z0-9_-]+$', - ), - ] = None - signal_value: Annotated[ - str | StrictFloat | StrictBool | None, - Field( - description="Signal value bucket represented by this point. Use null with presence 'absent' to represent inventory where the signal is not present. Omit when the row describes any present value rather than one specific value." - ), - ] = None - presence: Annotated[ - Presence, - Field( - description="Whether the signal is present for this point. Use 'absent' for the explicit not-present bucket." - ), - ] - signal_name: Annotated[ - str | None, - Field( - description='Human-readable signal name, useful when the buyer has not resolved the signal definition.' - ), - ] = None - signal_value_name: Annotated[ - str | None, Field(description='Human-readable label for the signal value bucket.') - ] = None - - @model_validator(mode='after') - def _require_schema_required_group(self) -> SignalForecastDimension: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('signal_ref',), ('signal_id',),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'SignalForecastDimension requires at least one of these field groups: signal_ref | signal_id' - ) diff --git a/src/adcp/types/generated_poc/core/forecast_dimension_time.py b/src/adcp/types/generated_poc/core/forecast_dimension_time.py deleted file mode 100644 index ef4f3838b..000000000 --- a/src/adcp/types/generated_poc/core/forecast_dimension_time.py +++ /dev/null @@ -1,27 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/forecast_dimension_time.json -# timestamp: 2026-08-21T04:35:18+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - - -class TimeForecastDimension(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['time'], Field(description='Dimension family discriminator.')] = 'time' - start_time: Annotated[ - AwareDatetime, - Field(description='Inclusive window start (RFC 3339 date-time with timezone offset).'), - ] - end_time: Annotated[ - AwareDatetime, - Field( - description='Exclusive window end (RFC 3339 date-time with timezone offset). MUST be after start_time.' - ), - ] diff --git a/src/adcp/types/generated_poc/core/forecast_point.py b/src/adcp/types/generated_poc/core/forecast_point.py deleted file mode 100644 index 33eaa000d..000000000 --- a/src/adcp/types/generated_poc/core/forecast_point.py +++ /dev/null @@ -1,160 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/forecast_point.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Dict - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictFloat - -from ..enums import availability_status as availability_status_1 -from ..enums import viewability_standard -from . import brand_ref, forecast_point_dimensions, forecast_range, forecast_vendor_metric_value -from .forecast_range import ForecastRange - - -class CoverageRate(ForecastRange): - low: Annotated[ - StrictFloat | None, - Field(description='Conservative (low-end) forecast value', ge=0.0, le=1.0), - ] = None - mid: Annotated[ - StrictFloat | None, - Field(description='Expected (most likely) forecast value', ge=0.0, le=1.0), - ] = None - high: Annotated[ - StrictFloat | None, - Field(description='Optimistic (high-end) forecast value', ge=0.0, le=1.0), - ] = None - - -class Metrics(AdCPBaseModel): - __pydantic_extra__: Dict[str, forecast_range.ForecastRange] - model_config = ConfigDict( - extra='allow', - ) - audience_size: forecast_range.ForecastRange | None = None - reach: forecast_range.ForecastRange | None = None - frequency: forecast_range.ForecastRange | None = None - impressions: forecast_range.ForecastRange | None = None - clicks: forecast_range.ForecastRange | None = None - spend: forecast_range.ForecastRange | None = None - views: forecast_range.ForecastRange | None = None - completed_views: forecast_range.ForecastRange | None = None - grps: forecast_range.ForecastRange | None = None - engagements: forecast_range.ForecastRange | None = None - follows: forecast_range.ForecastRange | None = None - saves: forecast_range.ForecastRange | None = None - profile_visits: forecast_range.ForecastRange | None = None - measured_impressions: forecast_range.ForecastRange | None = None - downloads: forecast_range.ForecastRange | None = None - plays: forecast_range.ForecastRange | None = None - coverage_rate: Annotated[ - CoverageRate | None, - Field( - description="Share of the declared forecast scope represented by this point. For signal coverage forecasts, this is the point's count divided by the coverage_forecast.scope denominator. Range 0.0 to 1.0." - ), - ] = None - - - - -class ViewableRate(CoverageRate): - pass - - -class Viewability(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - brand_ref.BrandReference | None, - Field(description='Vendor expected to produce or verify these viewability values.'), - ] = None - measurable_impressions: Annotated[ - forecast_range.ForecastRange | None, - Field( - description='Forecasted impressions where viewability can be measured. Coverage denominator for viewable_rate and viewed_seconds.' - ), - ] = None - viewable_impressions: Annotated[ - forecast_range.ForecastRange | None, - Field( - description='Forecasted impressions expected to meet the viewability threshold defined by standard.' - ), - ] = None - viewable_rate: Annotated[ - ViewableRate | None, - Field( - description='Forecasted viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.' - ), - ] = None - viewed_seconds: Annotated[ - forecast_range.ForecastRange | None, - Field( - description='Forecasted average in-view duration per measurable impression, in seconds.' - ), - ] = None - standard: Annotated[ - viewability_standard.ViewabilityStandard | None, - Field(description='Viewability measurement standard applied to these forecasted values.'), - ] = None - - -class ForecastPoint(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - label: Annotated[ - str | None, - Field( - description="Human-readable name for this forecast point. Required when forecast_range_unit is 'package' so buyer agents can identify and reference individual packages. Optional for other forecast types.", - examples=['Primetime', 'Morning Drive', 'Large Format Transit'], - max_length=128, - ), - ] = None - budget: Annotated[ - StrictFloat | None, - Field( - description='Budget amount for this forecast point. Required for spend curves; omit for availability forecasts where the metrics represent total available inventory. For allocation-level forecasts, this is the absolute budget for that allocation (not the percentage). For proposal-level forecasts, this is the total proposal budget. When omitted, use metrics.spend to express the estimated cost of the available inventory.', - ge=0.0, - ), - ] = None - product_id: Annotated[ - str | None, - Field( - description='Optional product context for this forecast row. Usually omitted on product-level and allocation-level forecasts where the product is already implied. On proposal-level forecasts, populate when a dimensional row, especially a placement row, maps to a specific product allocation so buyers can turn the row into an executable package choice. Omit for true aggregate proposal rows spanning multiple products.' - ), - ] = None - dimensions: Annotated[ - forecast_point_dimensions.ForecastPointDimensions | None, - Field( - description='Dimension constraints represented by this forecast point, such as country, region, placement, device type, platform, audience, signal value, time window, or intersections such as placement x country or product x signal. Each item declares one dimension family; when multiple items are present, the point represents their intersection. Sellers MUST NOT emit more than one item for each `kind` on a point; consumers MUST NOT treat repeated kinds as OR semantics. Use multiple points with dimensions to expose country/placement/signal availability within one product, proposal, or signal coverage forecast without creating separate products solely for each dimension. Dimensions describe the forecast row and are independent of pricing_options.' - ), - ] = None - availability_status: Annotated[ - availability_status_1.AvailabilityStatus | None, - Field( - description="Bookability of the inventory this row describes, as of the forecast's generated_at. Most meaningful on rows with a time dimension in an availability forecast (forecast_range_unit 'availability'). This is a snapshot, not a hold: valid_until bounds freshness, and proposal finalization or purchase remains the commitment boundary. When omitted, the row makes no bookability claim. 'unavailable' rows may carry empty metrics." - ), - ] = None - metrics: Annotated[ - Metrics, - Field( - description='Forecasted metric values. Keys are forecastable-metric enum values for delivery/engagement or event-type enum values for outcomes. Values are ForecastRange objects (low/mid/high). Use { "mid": value } for point estimates. When budget is present, these are the expected metrics at that spend level. When budget is omitted, these represent total available inventory — use spend to express the estimated cost. Additional keys beyond the documented properties are allowed for event-type values (purchase, lead, app_install, etc.).' - ), - ] - viewability: Annotated[ - Viewability | None, - Field( - description='Forecasted viewability metrics. Mirrors delivery-metrics.viewability, but numeric values are ForecastRange objects because forecast rows may provide low/mid/high bounds. Use this for pre-buy viewability expectations by forecast point without folding measurement metrics into pricing_options.' - ), - ] = None - vendor_metric_values: Annotated[ - list[forecast_vendor_metric_value.ForecastVendorMetricValue] | None, - Field( - description="Forecasted values for vendor-defined metrics that the product's reporting_capabilities.vendor_metrics declared. Mirrors delivery-metrics.vendor_metric_values, but value and measurable_impressions use ForecastRange. These forecasted measurement values are independent of pricing_options." - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/forecast_point_dimensions.py b/src/adcp/types/generated_poc/core/forecast_point_dimensions.py deleted file mode 100644 index e760b7340..000000000 --- a/src/adcp/types/generated_poc/core/forecast_point_dimensions.py +++ /dev/null @@ -1,55 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/forecast_point_dimensions.json -# timestamp: 2026-08-21T04:35:18+00:00 - -from __future__ import annotations - -from typing import Any, Annotated - -from pydantic import Field, RootModel - -from . import ( - forecast_dimension_audience, - forecast_dimension_device_platform, - forecast_dimension_device_type, - forecast_dimension_geo, - forecast_dimension_placement, - forecast_dimension_signal, - forecast_dimension_time, -) - - -class ForecastPointDimensions( - RootModel[ - list[ - forecast_dimension_geo.GeoForecastDimension - | forecast_dimension_placement.PlacementForecastDimension - | forecast_dimension_device_type.DeviceTypeForecastDimension - | forecast_dimension_device_platform.DevicePlatformForecastDimension - | forecast_dimension_audience.AudienceForecastDimension - | forecast_dimension_signal.SignalForecastDimension - | forecast_dimension_time.TimeForecastDimension - ] - ] -): - root: Annotated[ - list[ - forecast_dimension_geo.GeoForecastDimension - | forecast_dimension_placement.PlacementForecastDimension - | forecast_dimension_device_type.DeviceTypeForecastDimension - | forecast_dimension_device_platform.DevicePlatformForecastDimension - | forecast_dimension_audience.AudienceForecastDimension - | forecast_dimension_signal.SignalForecastDimension - | forecast_dimension_time.TimeForecastDimension - ], - Field( - description='Dimension constraints represented by a ForecastPoint. Use this when one product, proposal, or signal coverage forecast needs to expose availability or forecasted delivery by country, region, placement, device, audience, signal value, time window, or intersections such as placement x country without creating separate products solely for each slice. Each item declares one dimension family via `kind`; when multiple items are present, the point represents their intersection. Sellers MUST NOT emit more than one item for each `kind` in a point. Consumers MUST NOT treat repeated kinds as OR semantics; repeated peer values such as two countries are a seller conformance issue. Dimension values are descriptors of the forecast row and are independent of pricing_options.', - min_length=1, - title='Forecast Point Dimensions', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) diff --git a/src/adcp/types/generated_poc/core/forecast_range.py b/src/adcp/types/generated_poc/core/forecast_range.py deleted file mode 100644 index 0873f3be7..000000000 --- a/src/adcp/types/generated_poc/core/forecast_range.py +++ /dev/null @@ -1,38 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/forecast_range.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictFloat, model_validator - - -class ForecastRange(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - low: Annotated[ - StrictFloat | None, Field(description='Conservative (low-end) forecast value', ge=0.0) - ] = None - mid: Annotated[ - StrictFloat | None, Field(description='Expected (most likely) forecast value', ge=0.0) - ] = None - high: Annotated[ - StrictFloat | None, Field(description='Optimistic (high-end) forecast value', ge=0.0) - ] = None - - @model_validator(mode='after') - def _require_schema_required_group(self) -> ForecastRange: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('mid',), ('low', 'high'),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'ForecastRange requires at least one of these field groups: mid | low+high' - ) diff --git a/src/adcp/types/generated_poc/core/forecast_rate_range.py b/src/adcp/types/generated_poc/core/forecast_rate_range.py deleted file mode 100644 index b91ad0e10..000000000 --- a/src/adcp/types/generated_poc/core/forecast_rate_range.py +++ /dev/null @@ -1,26 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/forecast_rate_range.json -# timestamp: 2026-10-03T15:09:44+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import Field, StrictFloat - -from .forecast_range import ForecastRange - - -class ForecastRateRange(ForecastRange): - low: Annotated[ - StrictFloat | None, - Field(description='Conservative (low-end) forecast value', ge=0.0, le=1.0), - ] = None - mid: Annotated[ - StrictFloat | None, - Field(description='Expected (most likely) forecast value', ge=0.0, le=1.0), - ] = None - high: Annotated[ - StrictFloat | None, - Field(description='Optimistic (high-end) forecast value', ge=0.0, le=1.0), - ] = None diff --git a/src/adcp/types/generated_poc/core/forecast_vendor_metric_value.py b/src/adcp/types/generated_poc/core/forecast_vendor_metric_value.py deleted file mode 100644 index 9ce6f425b..000000000 --- a/src/adcp/types/generated_poc/core/forecast_vendor_metric_value.py +++ /dev/null @@ -1,67 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/forecast_vendor_metric_value.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from . import brand_ref, forecast_range, vendor_metric_id - - -class ForecastVendorMetricValue(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - brand_ref.BrandReference, - Field( - description='Vendor that defines and forecasts this metric. Matches a reporting_capabilities.vendor_metrics declaration on the product.' - ), - ] - metric_id: Annotated[ - vendor_metric_id.VendorMetricId, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a vendor_metrics[].metric_id declaration on the product." - ), - ] - value: Annotated[ - forecast_range.ForecastRange, - Field( - description="Forecasted vendor metric value. Unit semantics are vendor-defined; see unit and the vendor's measurement-agent metric definition." - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate heterogeneous vendor metrics (e.g., 'score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'). When populated inline, SHOULD match the vendor's published unit.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - forecast_range.ForecastRange | None, - Field( - description='Forecasted number of impressions the vendor expects to be able to measure. Coverage denominator for the vendor metric; buyers compute estimated coverage as measurable_impressions / impressions when both are present. For play-based channels (DOOH, cinema, place-based) forecast measurable_plays or measurable_play_seconds instead.' - ), - ] = None - measurable_plays: Annotated[ - forecast_range.ForecastRange | None, - Field( - description='Forecasted number of plays the vendor expects to be able to measure. Coverage denominator for channels where a play, not an impression, is the atomic observation unit; buyers compute estimated coverage as measurable_plays / plays when both are present.' - ), - ] = None - measurable_play_seconds: Annotated[ - forecast_range.ForecastRange | None, - Field( - description='Forecasted play-seconds (creative playout seconds summed across endpoints — screens, speakers, players) the vendor expects to be able to measure. Coverage denominator when the vendor meters exposure duration rather than discrete plays.' - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that do not fit a single scalar. Forecast rows SHOULD use ForecastRange values inside breakdown when sub-values are numeric forecasts. Buyers MUST treat this object as opaque without consulting the vendor's documentation." - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/format.py b/src/adcp/types/generated_poc/core/format.py deleted file mode 100644 index b2132f820..000000000 --- a/src/adcp/types/generated_poc/core/format.py +++ /dev/null @@ -1,2409 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/format.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, ConfigDict, Field, RootModel, StrictBool, StrictFloat - -from ..enums import ( - available_metric, - channels, - dimension_unit, - disclosure_persistence, - disclosure_position, - format_id_parameter, - universal_macro, -) -from ..enums import wcag_level as wcag_level_1 -from ..formats.canonical import ( - agent_placement, - audio_daast, - audio_hosted, - audio_vast, - coordinated_placements, - display_tag, - html5, - image, - image_carousel, - native_in_feed, - responsive_creative, - seller_rendered_stateful_display, - sponsored_placement, - video_hosted, - video_vast, -) -from . import canonical_projection_ref, creative_locale_policy -from . import format_id as format_id_1 -from . import macro_resolution_capability, overlay, platform_extension_ref -from . import tracker_execution_contract as tracker_execution_contract_1 -from . import vendor_pricing_option -from .requirements import ( - audio_asset_requirements, - catalog_requirements, - css_asset_requirements, - daast_asset_requirements, - html_asset_requirements, - image_asset_requirements, - javascript_asset_requirements, - markdown_asset_requirements, - text_asset_requirements, - url_asset_requirements, - vast_asset_requirements, - video_asset_requirements, - webhook_asset_requirements, -) - - -class Responsive(AdCPBaseModel): - width: StrictBool - height: StrictBool - - -class Dimensions(AdCPBaseModel): - width: Annotated[ - StrictFloat | None, - Field(description='Fixed width. Interpretation depends on unit (default: pixels).', gt=0.0), - ] = None - height: Annotated[ - StrictFloat | None, - Field( - description='Fixed height. Interpretation depends on unit (default: pixels).', gt=0.0 - ), - ] = None - min_width: Annotated[ - StrictFloat | None, Field(description='Minimum width for responsive renders', gt=0.0) - ] = None - min_height: Annotated[ - StrictFloat | None, Field(description='Minimum height for responsive renders', gt=0.0) - ] = None - max_width: Annotated[ - StrictFloat | None, Field(description='Maximum width for responsive renders', gt=0.0) - ] = None - max_height: Annotated[ - StrictFloat | None, Field(description='Maximum height for responsive renders', gt=0.0) - ] = None - unit: Annotated[ - dimension_unit.DimensionUnit | None, - Field( - description="Unit of measurement for width/height values. Defaults to 'px' when absent. Print formats use 'inches' or 'cm'." - ), - ] = None - responsive: Annotated[ - Responsive | None, Field(description='Indicates which dimensions are responsive/fluid') - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description="Fixed aspect ratio constraint (e.g., '16:9', '4:3', '1:1', '1.91:1')", - pattern='^\\d+(\\.\\d+)?:\\d+(\\.\\d+)?$', - ), - ] = None - - -class Renders(AdCPBaseModel): - role: Annotated[ - str, - Field( - description="Semantic role of this rendered piece (e.g., 'primary', 'companion', 'mobile_variant')" - ), - ] - parameters_from_format_id: Annotated[ - StrictBool | None, - Field( - description='When true, parameters for this render (dimensions and/or duration) are specified in the format_id. Used for template formats that accept parameters. Mutually exclusive with specifying dimensions object explicitly.' - ), - ] = None - dimensions: Annotated[ - Dimensions, - Field( - description='Dimensions for this rendered piece. Defaults to pixels when unit is absent.' - ), - ] - - -class Dimensions1(Dimensions): - pass - - -class Renders1(AdCPBaseModel): - role: Annotated[ - str, - Field( - description="Semantic role of this rendered piece (e.g., 'primary', 'companion', 'mobile_variant')" - ), - ] - parameters_from_format_id: Annotated[ - Literal[True], - Field( - description='When true, parameters for this render (dimensions and/or duration) are specified in the format_id. Used for template formats that accept parameters. Mutually exclusive with specifying dimensions object explicitly.' - ), - ] - dimensions: Annotated[ - Dimensions1 | None, - Field( - description='Dimensions for this rendered piece. Defaults to pixels when unit is absent.' - ), - ] = None - - -class SelectionMode(StrEnum): - sequential = 'sequential' - optimize = 'optimize' - - -class DisclosureCapability(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - position: Annotated[ - disclosure_position.DisclosurePosition, Field(description='The disclosure position') - ] - persistence: Annotated[ - list[disclosure_persistence.DisclosurePersistence], - Field(description='Persistence modes this position supports', min_length=1), - ] - - -class SellerPreference(StrEnum): - preferred = 'preferred' - accepted = 'accepted' - discouraged = 'discouraged' - - -class CanonicalParameters17(AdCPBaseModel): - pass - - -class FormatCard(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - format_id: Annotated[ - format_id_1.FormatReferenceStructuredObject, - Field( - description='Creative format defining the card layout (typically format_card_standard)' - ), - ] - manifest: Annotated[ - dict[str, Any], - Field(description='Asset manifest for rendering the card, structure defined by the format'), - ] - - -class Accessibility(AdCPBaseModel): - wcag_level: Annotated[ - wcag_level_1.WcagLevel, - Field( - description='WCAG conformance level that this format achieves. For format-rendered creatives, the format guarantees this level. For opaque creatives, the format requires assets that self-certify to this level.' - ), - ] - requires_accessible_assets: Annotated[ - StrictBool | None, - Field( - description='When true, all assets with x-accessibility fields must include those fields. For inspectable assets (image, video, audio), this means providing accessibility metadata like alt_text or captions. For opaque assets (HTML, JavaScript), this means providing self-declared accessibility properties.' - ), - ] = None - - -class FormatCardDetailed(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - format_id: Annotated[ - format_id_1.FormatReferenceStructuredObject, - Field( - description='Creative format defining the detailed card layout (typically format_card_detailed)' - ), - ] - manifest: Annotated[ - dict[str, Any], - Field( - description='Asset manifest for rendering the detailed card, structure defined by the format' - ), - ] - - -class BaseIndividualAsset(AdCPBaseModel): - item_type: Annotated[ - Literal['individual'], - Field(description='Discriminator indicating this is an individual asset'), - ] = 'individual' - asset_id: Annotated[ - str, - Field( - description='Unique identifier for this asset. Creative manifests MUST use this exact value as the key in the assets object.' - ), - ] - asset_role: Annotated[ - str | None, - Field( - description="Descriptive label for this asset's purpose (e.g., 'hero_image', 'logo', 'third_party_tracking'). For documentation and UI display only — manifests key assets by asset_id, not asset_role." - ), - ] = None - required: Annotated[ - StrictBool, - Field( - description='Whether this asset is required (true) or optional (false). Required assets must be provided for a valid creative. Optional assets enhance the creative but are not mandatory.' - ), - ] - overlays: Annotated[ - list[overlay.Overlay] | None, - Field( - description="Publisher-controlled elements rendered on top of buyer content at this asset's position (e.g., video player controls, publisher logos). Creative agents should avoid placing critical content (CTAs, logos, key copy) within overlay bounds." - ), - ] = None - asset_group_id: Annotated[ - str | None, - Field( - description="Optional canonical asset_group_id this slot fills, drawn from /schemas/core/asset-group-vocabulary.json. Lets buyers and migration tools resolve v1 author-invented slot names (e.g., `click_url`) to canonical names (e.g., `landing_page_url`). Validators MAY soft-warn when a v1 slot's asset_id is a known alias but no asset_group_id is declared." - ), - ] = None - - -class BaseGroupAsset(AdCPBaseModel): - asset_id: Annotated[str, Field(description='Identifier for this asset within the group')] - asset_role: Annotated[ - str | None, - Field( - description="Descriptive label for this asset's purpose. For documentation and UI display only — manifests key assets by asset_id, not asset_role." - ), - ] = None - asset_group_id: Annotated[ - str | None, - Field( - description='Optional canonical asset_group_id this slot fills, drawn from /schemas/core/asset-group-vocabulary.json. Same semantics as on baseIndividualAsset — lets buyers and migration tools resolve v1 author-invented slot names to canonical names.' - ), - ] = None - required: Annotated[ - StrictBool, - Field(description='Whether this asset is required within each repetition of the group'), - ] - overlays: Annotated[ - list[overlay.Overlay] | None, - Field( - description="Publisher-controlled elements rendered on top of buyer content at this asset's position (e.g., carousel navigation arrows, slide indicators). Creative agents should avoid placing critical content within overlay bounds." - ), - ] = None - - -class Assets(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['image'] = 'image' - requirements: image_asset_requirements.ImageAssetRequirements | None = None - - -class Assets9(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['video'] = 'video' - requirements: video_asset_requirements.VideoAssetRequirements | None = None - - -class Assets10(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['audio'] = 'audio' - requirements: audio_asset_requirements.AudioAssetRequirements | None = None - - -class Assets11(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['text'] = 'text' - requirements: text_asset_requirements.TextAssetRequirements | None = None - - -class Assets12(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['markdown'] = 'markdown' - requirements: markdown_asset_requirements.MarkdownAssetRequirements | None = None - - -class Assets13(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['html'] = 'html' - requirements: html_asset_requirements.HtmlAssetRequirements | None = None - - -class Assets14(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['css'] = 'css' - requirements: css_asset_requirements.CssAssetRequirements | None = None - - -class Assets15(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['javascript'] = 'javascript' - requirements: javascript_asset_requirements.JavascriptAssetRequirements | None = None - - -class Assets16(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['zip'] = 'zip' - - -class Assets17(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['vast'] = 'vast' - requirements: vast_asset_requirements.VastAssetRequirements | None = None - - -class Assets18(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['daast'] = 'daast' - requirements: daast_asset_requirements.DaastAssetRequirements | None = None - - -class Assets19(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['url'] = 'url' - requirements: url_asset_requirements.UrlAssetRequirements | None = None - - -class Assets20(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['webhook'] = 'webhook' - requirements: webhook_asset_requirements.WebhookAssetRequirements | None = None - - -class Assets21(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['brief'] = 'brief' - - -class Assets23(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['display_tag'] = 'display_tag' - - -class Assets24(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['published_post'] = 'published_post' - - -class Assets25(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['card'] = 'card' - - -class Assets26(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['pixel_tracker'] = 'pixel_tracker' - - -class Assets27(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['vast_tracker'] = 'vast_tracker' - - -class Assets28(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['daast_tracker'] = 'daast_tracker' - - -class Assets31(BaseGroupAsset): - asset_type: Literal['image'] = 'image' - requirements: image_asset_requirements.ImageAssetRequirements | None = None - - -class Assets32(BaseGroupAsset): - asset_type: Literal['video'] = 'video' - requirements: video_asset_requirements.VideoAssetRequirements | None = None - - -class Assets33(BaseGroupAsset): - asset_type: Literal['audio'] = 'audio' - requirements: audio_asset_requirements.AudioAssetRequirements | None = None - - -class Assets34(BaseGroupAsset): - asset_type: Literal['text'] = 'text' - requirements: text_asset_requirements.TextAssetRequirements | None = None - - -class Assets35(BaseGroupAsset): - asset_type: Literal['markdown'] = 'markdown' - requirements: markdown_asset_requirements.MarkdownAssetRequirements | None = None - - -class Assets36(BaseGroupAsset): - asset_type: Literal['html'] = 'html' - requirements: html_asset_requirements.HtmlAssetRequirements | None = None - - -class Assets37(BaseGroupAsset): - asset_type: Literal['css'] = 'css' - requirements: css_asset_requirements.CssAssetRequirements | None = None - - -class Assets38(BaseGroupAsset): - asset_type: Literal['javascript'] = 'javascript' - requirements: javascript_asset_requirements.JavascriptAssetRequirements | None = None - - -class Assets39(BaseGroupAsset): - asset_type: Literal['zip'] = 'zip' - - -class Assets40(BaseGroupAsset): - asset_type: Literal['vast'] = 'vast' - requirements: vast_asset_requirements.VastAssetRequirements | None = None - - -class Assets41(BaseGroupAsset): - asset_type: Literal['daast'] = 'daast' - requirements: daast_asset_requirements.DaastAssetRequirements | None = None - - -class Assets42(BaseGroupAsset): - asset_type: Literal['url'] = 'url' - requirements: url_asset_requirements.UrlAssetRequirements | None = None - - -class Assets43(BaseGroupAsset): - asset_type: Literal['webhook'] = 'webhook' - requirements: webhook_asset_requirements.WebhookAssetRequirements | None = None - - -class Assets44(BaseGroupAsset): - asset_type: Literal['brief'] = 'brief' - - -class Assets46(BaseGroupAsset): - asset_type: Literal['display_tag'] = 'display_tag' - - -class Assets47(BaseGroupAsset): - asset_type: Literal['published_post'] = 'published_post' - - -class Assets48(BaseGroupAsset): - asset_type: Literal['card'] = 'card' - - -class Assets49(BaseGroupAsset): - asset_type: Literal['pixel_tracker'] = 'pixel_tracker' - - -class Assets50(BaseGroupAsset): - asset_type: Literal['vast_tracker'] = 'vast_tracker' - - -class Assets51(BaseGroupAsset): - asset_type: Literal['daast_tracker'] = 'daast_tracker' - - -class Assets22(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['catalog'] = 'catalog' - requirements: catalog_requirements.CatalogRequirements | None = None - - -class Assets45(BaseGroupAsset): - asset_type: Literal['catalog'] = 'catalog' - requirements: catalog_requirements.CatalogRequirements | None = None - - -Assets30 = Annotated[ - Assets31 - | Assets32 - | Assets33 - | Assets34 - | Assets35 - | Assets36 - | Assets37 - | Assets38 - | Assets39 - | Assets40 - | Assets41 - | Assets42 - | Assets43 - | Assets44 - | Assets45 - | Assets46 - | Assets47 - | Assets48 - | Assets49 - | Assets50 - | Assets51, - Field(discriminator='asset_type'), -] - - -class Assets29(AdCPBaseModel): - item_type: Annotated[ - Literal['repeatable_group'], - Field(description='Discriminator indicating this is a repeatable asset group'), - ] = 'repeatable_group' - asset_group_id: Annotated[ - str, Field(description="Identifier for this asset group (e.g., 'product', 'slide', 'card')") - ] - required: Annotated[ - StrictBool, - Field( - description='Whether this asset group is required. If true, at least min_count repetitions must be provided.' - ), - ] - min_count: Annotated[ - SchemaInt, - Field( - description='Minimum number of repetitions required (if group is required) or allowed (if optional)', - ge=0, - ), - ] - max_count: Annotated[ - SchemaInt, Field(description='Maximum number of repetitions allowed', ge=1) - ] - selection_mode: Annotated[ - SelectionMode | None, - Field( - description="How the platform uses repetitions of this group. 'sequential' means all items display in order (carousels, playlists). 'optimize' means the platform selects the best-performing combination from alternatives (asset group optimization like Meta Advantage+ or Google Pmax)." - ), - ] = SelectionMode.sequential - assets: Annotated[ - list[Assets30], Field(description='Assets within each repetition of this group') - ] - - -class CanonicalParameters1(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = None - v1_format_ref: Annotated[ - list[format_id_1.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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['image'] = 'image' - params: image.CanonicalFormatImage - - -class CanonicalParameters2(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = None - v1_format_ref: Annotated[ - list[format_id_1.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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['html5'] = 'html5' - params: html5.CanonicalFormatHtml5Banner - - -class CanonicalParameters3(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = None - v1_format_ref: Annotated[ - list[format_id_1.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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['display_tag'] = 'display_tag' - params: display_tag.CanonicalFormatDisplayTag - - -class CanonicalParameters4(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = None - v1_format_ref: Annotated[ - list[format_id_1.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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['image_carousel'] = 'image_carousel' - params: image_carousel.CanonicalFormatImageCarousel - - -class CanonicalParameters5(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = None - v1_format_ref: Annotated[ - list[format_id_1.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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['video_hosted'] = 'video_hosted' - params: video_hosted.CanonicalFormatHostedVideo - - -class CanonicalParameters6(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = None - v1_format_ref: Annotated[ - list[format_id_1.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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['video_vast'] = 'video_vast' - params: video_vast.CanonicalFormatVastVideo - - -class CanonicalParameters7(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = None - v1_format_ref: Annotated[ - list[format_id_1.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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['audio_hosted'] = 'audio_hosted' - params: audio_hosted.CanonicalFormatHostedAudio - - -class CanonicalParameters8(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = None - v1_format_ref: Annotated[ - list[format_id_1.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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['audio_vast'] = 'audio_vast' - params: audio_vast.CanonicalFormatVastAudio - - -class CanonicalParameters9(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = None - v1_format_ref: Annotated[ - list[format_id_1.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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['audio_daast'] = 'audio_daast' - params: audio_daast.CanonicalFormatDaastAudio - - -class CanonicalParameters10(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = None - v1_format_ref: Annotated[ - list[format_id_1.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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['sponsored_placement'] = 'sponsored_placement' - params: sponsored_placement.CanonicalFormatSponsoredPlacementRetailMediaCatalogDriven - - -class CanonicalParameters11(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = None - v1_format_ref: Annotated[ - list[format_id_1.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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['native_in_feed'] = 'native_in_feed' - params: native_in_feed.CanonicalFormatNativeInFeed - - -class CanonicalParameters12(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = None - v1_format_ref: Annotated[ - list[format_id_1.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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['responsive_creative'] = 'responsive_creative' - params: responsive_creative.CanonicalFormatResponsiveCreative - - -class CanonicalParameters13(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = None - v1_format_ref: Annotated[ - list[format_id_1.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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['agent_placement'] = 'agent_placement' - params: agent_placement.CanonicalFormatAgentPlacementAiSurfaceSponsoredPlacement - - -class CanonicalParameters14(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = None - v1_format_ref: Annotated[ - list[format_id_1.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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['seller_rendered_stateful_display'] = 'seller_rendered_stateful_display' - params: seller_rendered_stateful_display.CanonicalFormatSellerRenderedStatefulDisplay - - -class CanonicalParameters15(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = None - v1_format_ref: Annotated[ - list[format_id_1.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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['coordinated_placements'] = 'coordinated_placements' - params: coordinated_placements.CanonicalFormatCoordinatedPlacements - - -class CanonicalParameters16(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = None - v1_format_ref: Annotated[ - list[format_id_1.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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['custom'] = 'custom' - params: Annotated[ - dict[str, Any], - Field( - description="Custom shape's params. Validated against the schema fetched from `format_schema.uri` at the cached `format_schema.digest`." - ), - ] - - -class CanonicalParameters18(CanonicalParameters1, CanonicalParameters17): - pass - - -class CanonicalParameters19(CanonicalParameters2, CanonicalParameters17): - pass - - -class CanonicalParameters20(CanonicalParameters3, CanonicalParameters17): - pass - - -class CanonicalParameters21(CanonicalParameters4, CanonicalParameters17): - pass - - -class CanonicalParameters22(CanonicalParameters5, CanonicalParameters17): - pass - - -class CanonicalParameters23(CanonicalParameters6, CanonicalParameters17): - pass - - -class CanonicalParameters24(CanonicalParameters7, CanonicalParameters17): - pass - - -class CanonicalParameters25(CanonicalParameters8, CanonicalParameters17): - pass - - -class CanonicalParameters26(CanonicalParameters9, CanonicalParameters17): - pass - - -class CanonicalParameters27(CanonicalParameters10, CanonicalParameters17): - pass - - -class CanonicalParameters28(CanonicalParameters11, CanonicalParameters17): - pass - - -class CanonicalParameters29(CanonicalParameters12, CanonicalParameters17): - pass - - -class CanonicalParameters30(CanonicalParameters13, CanonicalParameters17): - pass - - -class CanonicalParameters31(CanonicalParameters14, CanonicalParameters17): - pass - - -class CanonicalParameters32(CanonicalParameters15, CanonicalParameters17): - pass - - -class CanonicalParameters33(CanonicalParameters16, CanonicalParameters17): - pass - - -class CanonicalParameters( - RootModel[ - CanonicalParameters18 - | CanonicalParameters19 - | CanonicalParameters20 - | CanonicalParameters21 - | CanonicalParameters22 - | CanonicalParameters23 - | CanonicalParameters24 - | CanonicalParameters25 - | CanonicalParameters26 - | CanonicalParameters27 - | CanonicalParameters28 - | CanonicalParameters29 - | CanonicalParameters30 - | CanonicalParameters31 - | CanonicalParameters32 - | CanonicalParameters33 - ] -): - root: Annotated[ - CanonicalParameters18 - | CanonicalParameters19 - | CanonicalParameters20 - | CanonicalParameters21 - | CanonicalParameters22 - | CanonicalParameters23 - | CanonicalParameters24 - | CanonicalParameters25 - | CanonicalParameters26 - | CanonicalParameters27 - | CanonicalParameters28 - | CanonicalParameters29 - | CanonicalParameters30 - | CanonicalParameters31 - | CanonicalParameters32 - | CanonicalParameters33, - Field( - description="**DEPRECATED in 3.1. Removed at 4.0.** Use `v1_format_ref` on the v2 `ProductFormatDeclaration` instead — the seller authors a v2 declaration (in `Product.format_options` or `creative.supported_formats`) and links it back to this v1 format via `v1_format_ref: { agent_url, id }`. The directional link from v2 → v1 is the same fact as `canonical_parameters` without the parallel-shape drift surface (v1 file and `canonical_parameters` were two declarations of the same thing; hand-authored, drifting silently).\n\nMigration: every seller currently authoring `canonical_parameters` SHOULD migrate to authoring a v2 declaration on the corresponding product (or capability) with `v1_format_ref` pointing back at this v1 format. v1 files become pure v1 again — no v2-shape mirroring.\n\n*Legacy behavior, retained for 3.1–3.x backward compatibility:* When `canonical` is set, this field carries the full ProductFormatDeclaration that the SDK projects this v1 format into. The `format_kind` MUST equal the `canonical` field value (validators enforce). When set, this is the authoritative source for SDK v1→v2 projection — the registry's structural-match parameter inference is bypassed. SDKs reading 3.1 catalogs MUST continue to honor `canonical_parameters` when present; 4.0+ SDKs MAY reject the field. New code SHOULD NOT emit this field. Seller execution authority is never projected through this deprecated field, so tracker_execution_contract and tracker_execution_contract_digest are forbidden.\n\n**Drift contract (still normative while supported).** Hand-authored `canonical_parameters` MUST satisfy the *narrows* relation against this v1 format's `requirements` and `assets[*]` shape (see canonical-formats.mdx 'Narrows — formal definition'). SDKs that read this v1 file SHOULD lint-time check the equivalence at build/load and emit `FORMAT_PROJECTION_FAILED` if the two disagree.", - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - - - -class Format(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - format_id: Annotated[ - format_id_1.FormatReferenceStructuredObject, - Field( - description="This format's own identifier — a structured object {agent_url, id}, not a string. See /schemas/core/format-id.json for the full shape." - ), - ] - name: Annotated[str, Field(description='Human-readable format name')] - description: Annotated[ - str | None, - Field( - description='Plain text explanation of what this format does and what assets it requires' - ), - ] = None - example_url: Annotated[ - AnyUrl | None, - Field( - description='Optional URL to showcase page with examples and interactive demos of this format' - ), - ] = None - accepts_parameters: Annotated[ - list[format_id_parameter.FormatIdParameter] | None, - Field( - description='List of parameters this format accepts in format_id. Template formats define which parameters (dimensions, duration, etc.) can be specified when instantiating the format. Empty or omitted means this is a concrete format with fixed parameters.' - ), - ] = None - renders: Annotated[ - list[Renders | Renders1] | None, - Field( - description='Specification of rendered pieces for this format. Most formats produce a single render. Companion ad formats (video + banner), adaptive formats, and multi-placement formats produce multiple renders. Each render specifies its role and dimensions.', - min_length=1, - ), - ] = None - assets: Annotated[ - list[ - Assets - | Assets9 - | Assets10 - | Assets11 - | Assets12 - | Assets13 - | Assets14 - | Assets15 - | Assets16 - | Assets17 - | Assets18 - | Assets19 - | Assets20 - | Assets21 - | Assets22 - | Assets23 - | Assets24 - | Assets25 - | Assets26 - | Assets27 - | Assets28 - | Assets29 - ] - | None, - Field( - description="Array of all assets supported for this format. Each asset is identified by its asset_id, which must be used as the key in creative manifests. Use the 'required' boolean on each asset to indicate whether it's mandatory." - ), - ] = None - delivery: Annotated[ - dict[str, Any] | None, - Field(description='Delivery method specifications (e.g., hosted, VAST, third-party tags)'), - ] = None - supported_macros: Annotated[ - list[universal_macro.UniversalMacro | str] | None, - Field( - description='List of universal macros supported by this format (e.g., MEDIA_BUY_ID, CACHEBUSTER, DEVICE_ID). Used for validation and developer tooling. See docs/creative/universal-macros.mdx for full documentation.' - ), - ] = None - input_format_ids: Annotated[ - list[format_id_1.FormatReferenceStructuredObject] | None, - Field( - deprecated=True, - description='**DEPRECATED in 3.1. Removed at 4.0.** Use `list_transformers` instead — a transformer declares its own `input_format_ids`/`output_format_ids`, so build capability is a property of the transformer (the unit you select and that carries pricing), not a relationship hung on a format. Discover build capability via `list_transformers` (optionally filtered by `input_format_ids`/`output_format_ids`).\n\nMigration: sellers that expressed transform capability by hanging `input_format_ids` on a format SHOULD declare a transformer via `list_transformers` instead. Buyers SHOULD discover build capability via `list_transformers` rather than filtering formats.\n\n*Legacy behavior, retained for 3.1–3.x backward compatibility:* array of format IDs this format accepts as input creative manifests; when present, indicates this format can take existing creatives in these formats as input. SDKs reading 3.1 catalogs MUST continue to honor this field when present; 4.0+ SDKs MAY reject it. New code SHOULD NOT emit this field.', - ), - ] = None - output_format_ids: Annotated[ - list[format_id_1.FormatReferenceStructuredObject] | None, - Field( - deprecated=True, - description='**DEPRECATED in 3.1. Removed at 4.0.** Use `list_transformers` instead — a transformer declares its own `output_format_ids`, so what a builder can produce is a property of the transformer, not a relationship hung on a format. Discover via `list_transformers`.\n\nMigration: sellers that expressed multi-output build capability (e.g. a multi-publisher template) by hanging `output_format_ids` on a format SHOULD declare a transformer via `list_transformers` instead.\n\n*Legacy behavior, retained for 3.1–3.x backward compatibility:* array of format IDs this format can produce as output; when present, indicates this format can build creatives in these output formats. SDKs reading 3.1 catalogs MUST continue to honor this field when present; 4.0+ SDKs MAY reject it. New code SHOULD NOT emit this field.', - ), - ] = None - format_card: Annotated[ - FormatCard | None, - Field( - description='Optional standard visual card (300x400px) for displaying this format in user interfaces. Can be rendered via preview_creative or pre-generated.' - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field( - description='Accessibility posture of this format. Declares the WCAG conformance level that creatives produced by this format will meet.' - ), - ] = None - supported_disclosure_positions: Annotated[ - list[disclosure_position.DisclosurePosition] | None, - Field( - description='Disclosure positions this format can render. Buyers use this to determine whether a format can satisfy their compliance requirements before submitting a creative. When omitted, the format makes no disclosure rendering guarantees — creative agents SHOULD treat this as incompatible with briefs that require specific disclosure positions. Values correspond to positions on creative-brief.json required_disclosures.', - min_length=1, - ), - ] = None - disclosure_capabilities: Annotated[ - list[DisclosureCapability] | None, - Field( - description='Structured disclosure capabilities per position with persistence modes. Declares which persistence behaviors each disclosure position supports, enabling persistence-aware matching against provenance render guidance and brief requirements. When present, supersedes supported_disclosure_positions for persistence-aware queries. The flat supported_disclosure_positions field is retained for backward compatibility. Each position MUST appear at most once; validators and agents SHOULD reject duplicates.', - min_length=1, - ), - ] = None - format_card_detailed: Annotated[ - FormatCardDetailed | None, - Field( - description='Optional detailed card with carousel and full specifications. Provides rich format documentation similar to ad spec pages.' - ), - ] = None - reported_metrics: Annotated[ - list[available_metric.AvailableMetric] | None, - Field( - description='Metrics this format can produce in delivery reporting. Buyers receive the intersection of format reported_metrics and product available_metrics. Under `enums/available-metric.json`, a container such as `viewability` subsumes numeric leaves such as `viewable_rate`; structured distributions match only their explicit `viewed_seconds_percentiles` or `viewed_seconds_histogram` identities. If omitted, the format defers entirely to product-level metric declarations.', - min_length=1, - ), - ] = None - pricing_options: Annotated[ - list[vendor_pricing_option.VendorPricingOption] | None, - Field( - deprecated=True, - description='**DEPRECATED in 3.1. Removed at 4.0.** Use `transformer.pricing_options` (via `list_transformers`) instead — pricing belongs on the transformer (the unit selected and billed), exactly as it belongs on a media-buy product. Once formats only describe output shape, format-level pricing is vestigial.\n\nMigration: transformation/generation agents that charged via `format.pricing_options` SHOULD move the same `vendor-pricing-option` entries onto the corresponding transformer. The applied option is echoed per-leaf on the build_creative response and reconciled via report_usage, unchanged.\n\n*Legacy behavior, retained for 3.1–3.x backward compatibility:* pricing options for this format, used by transformation/generation agents that charge per format adapted, per image generated, or per unit of work; present when the request included include_pricing=true and account. SDKs reading 3.1 catalogs MUST continue to honor this field when present; 4.0+ SDKs MAY reject it. New code SHOULD NOT emit this field.', - min_length=1, - ), - ] = None - canonical: Annotated[ - canonical_projection_ref.CanonicalProjectionReference | None, - Field( - description='Optional v2 canonical-projection annotation. Always an object — bare-string shorthand (`canonical: "image"`) is not supported; the minimal form is `canonical: { "kind": "image" }`. Carries `kind` (which canonical the v1 format projects to) plus optional `asset_source` and `slots_override` for cases where the v1 format\'s shape doesn\'t follow the canonical\'s defaults (e.g., generative entries whose input is `generation_prompt: text` instead of `image_main: image`).\n\nWhen set, SDKs use this annotation as the authoritative v1 → v2 mapping for this format, bypassing the [v1 canonical mapping registry](/schemas/registries/v1-canonical-mapping.json) lookup. Combined with the slot-level `asset_group_id` declarations on each `assets[i]` entry, a v1 format declaration with `canonical` set is fully self-describing for v1↔v2 translation.\n\nResolution order for SDK projection from v1 wire shape to v2 (per RFC #3305 amendment #3767):\n1. If this `canonical` field is set, use it (seller-declared, highest priority). Apply `asset_source` and `slots_override` from the projection ref when present; otherwise inherit the canonical\'s defaults.\n2. Else, look up `format_id` in the canonical mapping registry\'s `format_id_glob` entries.\n3. Else, attempt structural match against the registry\'s `structural` entries (asset types, slot shape, vast_versions, etc.).\n4. Else, fail closed: SDK MUST NOT emit `format_options` for products carrying this format. Surface `FORMAT_PROJECTION_FAILED` on the response `errors[]` suggesting the seller add an explicit `canonical` annotation or file a registry entry.\n\nWhen `canonical.kind` is `custom`, the seller MUST also declare `canonical_format_shape` and `canonical_format_schema` (parallel to ProductFormatDeclaration\'s `format_shape` and `format_schema`) so buyer SDKs can fetch the seller\'s custom format schema.\n\nSee `canonical-projection-ref.json` for full projection semantics and examples (default-slot case, generative case, brief-driven case).' - ), - ] = None - canonical_parameters: Annotated[ - CanonicalParameters | None, - Field( - deprecated=True, - description="**DEPRECATED in 3.1. Removed at 4.0.** Use `v1_format_ref` on the v2 `ProductFormatDeclaration` instead — the seller authors a v2 declaration (in `Product.format_options` or `creative.supported_formats`) and links it back to this v1 format via `v1_format_ref: { agent_url, id }`. The directional link from v2 → v1 is the same fact as `canonical_parameters` without the parallel-shape drift surface (v1 file and `canonical_parameters` were two declarations of the same thing; hand-authored, drifting silently).\n\nMigration: every seller currently authoring `canonical_parameters` SHOULD migrate to authoring a v2 declaration on the corresponding product (or capability) with `v1_format_ref` pointing back at this v1 format. v1 files become pure v1 again — no v2-shape mirroring.\n\n*Legacy behavior, retained for 3.1–3.x backward compatibility:* When `canonical` is set, this field carries the full ProductFormatDeclaration that the SDK projects this v1 format into. The `format_kind` MUST equal the `canonical` field value (validators enforce). When set, this is the authoritative source for SDK v1→v2 projection — the registry's structural-match parameter inference is bypassed. SDKs reading 3.1 catalogs MUST continue to honor `canonical_parameters` when present; 4.0+ SDKs MAY reject the field. New code SHOULD NOT emit this field. Seller execution authority is never projected through this deprecated field, so tracker_execution_contract and tracker_execution_contract_digest are forbidden.\n\n**Drift contract (still normative while supported).** Hand-authored `canonical_parameters` MUST satisfy the *narrows* relation against this v1 format's `requirements` and `assets[*]` shape (see canonical-formats.mdx 'Narrows — formal definition'). SDKs that read this v1 file SHOULD lint-time check the equivalence at build/load and emit `FORMAT_PROJECTION_FAILED` if the two disagree.", - ), - ] = None - - -# Backward compatibility for the pre-beta3 generated repeatable group name. -Assets94 = Assets29 diff --git a/src/adcp/types/generated_poc/core/format_id.py b/src/adcp/types/generated_poc/core/format_id.py deleted file mode 100644 index 174688852..000000000 --- a/src/adcp/types/generated_poc/core/format_id.py +++ /dev/null @@ -1,57 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/format_id.json -# timestamp: 2026-10-04T01:40:09+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt, WireUrl -from pydantic import ConfigDict, Field, StrictFloat - - -class FormatReferenceStructuredObject(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - WireUrl, - Field( - description="URL of the agent that defines this format (e.g., 'https://creative.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats). Callers comparing two `format-id` values MUST canonicalize `agent_url` per the AdCP URL canonicalization rules before treating two formats as the same. See docs/reference/url-canonicalization." - ), - ] - id: Annotated[ - str, - Field( - description="Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - width: Annotated[ - SchemaInt | None, - Field( - description='Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - height: Annotated[ - SchemaInt | None, - Field( - description='Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - duration_ms: Annotated[ - StrictFloat | None, - Field( - description='Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters.', - ge=1.0, - ), - ] = None - pixel_ratio: Annotated[ - StrictFloat | None, - Field( - description='Required intrinsic-pixel density for a parameterized visual format, expressed as intrinsic pixels per logical pixel. Requires `width` and `height`. Example: `{id: "display_image", width: 300, height: 250, pixel_ratio: 2}` identifies a 300×250 logical render supplied by a 600×500 image. Omit for the backward-compatible 1x variant.', - gt=0.0, - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/format_option_ref.py b/src/adcp/types/generated_poc/core/format_option_ref.py deleted file mode 100644 index 1a2d5e5bb..000000000 --- a/src/adcp/types/generated_poc/core/format_option_ref.py +++ /dev/null @@ -1,64 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/format_option_ref.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - - -class FormatOptionReference1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['publisher'], - Field( - description="Reference resolves against the named publisher's adagents.json top-level `formats[]` catalog." - ), - ] = 'publisher' - publisher_domain: Annotated[ - str, - Field( - description='Publisher domain where the adagents.json declaring this format option is hosted.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - format_option_id: Annotated[ - str, - Field( - description="Stable format option ID from the publisher's adagents.json top-level `formats[]`, matching a publisher-catalog-backed entry in the target product's `format_options[]`." - ), - ] - - -class FormatOptionReference2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['product'], - Field( - description="Reference resolves only against the target product's inline `format_options[]`." - ), - ] = 'product' - format_option_id: Annotated[ - str, - Field( - description="Stable format option ID from the target product's inline `format_options[]`." - ), - ] - publisher_domain: Any | None = None - - -FormatOptionReference = Annotated[ - FormatOptionReference1 | FormatOptionReference2, - Field( - description='Discriminated reference to a product format option. The global canonical shape is still named by `format_kind`; this reference selects one concrete product `format_options[]` entry. `scope: "publisher"` identifies a publisher-declared catalog option by `{ publisher_domain, format_option_id }`. `scope: "product"` identifies a product-local option by `format_option_id`; the enclosing package/product context supplies the namespace.', - discriminator='scope', - title='Format Option Reference', - ), -] diff --git a/src/adcp/types/generated_poc/core/format_shape_vocabulary.py b/src/adcp/types/generated_poc/core/format_shape_vocabulary.py deleted file mode 100644 index 780be1cf0..000000000 --- a/src/adcp/types/generated_poc/core/format_shape_vocabulary.py +++ /dev/null @@ -1,19 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/format_shape_vocabulary.json -# timestamp: 2026-05-22T13:15:12+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from pydantic import Field, RootModel - - -class AdcpFormatShapeVocabularyRegistry(RootModel[Any]): - root: Annotated[ - Any, - Field( - description='Canonical registry of `format_shape` values used on `ProductFormatDeclaration` when `format_kind: "custom"`. Captures recognized creative-structure patterns that are NOT yet first-class canonical formats — composed/coordinated/sponsorship shapes that high-end publishers and broadcast networks sell as headline products. Each registry entry names a global shape; the seller\'s actual structure lives in `format_schema` (URI+digest reference to the seller-hosted or AAO-mirrored schema) so buyer agents can fetch and validate against a real schema rather than reasoning over an opaque ext blob.\n\n**Two-layer extensibility:**\n- **Canonical** (`format_kind: image`, `video_vast`, etc.): full spec coverage, stable contract.\n- **Custom + format_shape + format_schema** (`format_kind: "custom"`): recognized pattern, classified against this vocabulary, but the params/slots structure is supplied by a fetchable schema rather than baked into AdCP.\n\nNon-canonical `format_shape` values remain valid (validators MAY soft-warn) so adopters CAN ship a shape that isn\'t yet in the registry — adding entries is a vocabulary PR, not a major-version bump. 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 (creates `/schemas/formats/canonical/.json`, adds the value to `canonical-format-kind.json`, retires the registry entry). See [adcp#3666](https://github.com/adcontextprotocol/adcp/issues/3666) for the promotion queue.\n\n**Promotion migration contract (normative).** Promotion is a wire-shape change for any consumer code branching on `format_kind == "custom"` — the same publisher\'s product now ships under `format_kind: ""`. To avoid silent breakage:\n\n1. **Transition window.** When the working group promotes a `format_shape` to a first-class `format_kind`, sellers MAY ship a product under BOTH shapes during the transition window — two declarations on the same product\'s `format_options` array, one `format_kind: "custom"` + `format_shape: ""` and one `format_kind: ""`. Transition window is at minimum 90 days; the promotion PR sets the calendar.\n2. **Consumer-SDK deprecation warning.** SDKs encountering a `format_kind: "custom"` declaration whose `format_shape` matches an entry promoted to a first-class canonical SHOULD emit a structured deprecation warning via their lint channel (same pattern as `FORMAT_PROJECTION_FAILED` cross-boundary visibility) carrying `{ format_shape, promoted_to: format_kind, promotion_release, transition_end }`. Adopters branching on `format_kind == "custom"` past `transition_end` silently lose that publisher\'s inventory; the deprecation warning is the early signal.\n3. **`promotion_status` lifecycle.** When promotion is scheduled, the entry\'s `promotion_status` SHOULD update from `tracking — see adcp#3666` to `promoted to in ; transition ends `. SDKs MAY read the registry at codegen / runtime to populate the deprecation warning\'s `transition_end`.\n4. **Producer-side hygiene.** After the transition window closes, sellers SHOULD drop the legacy `format_kind: "custom"` declaration and ship only the first-class canonical. Buyers MAY then assume `format_kind == "custom"` + `format_shape: ""` is a long-tail / non-promoted shape, not a promoted-canonical-shipped-under-the-old-name.\n\nWithout this contract, every promotion event silently breaks adopter code branching on `format_kind == "custom"`. With it, the breakage surfaces as a structured warning during the transition window and adopters can update their branching ahead of the cutover.', - title='AdCP Format Shape Vocabulary Registry', - ), - ] diff --git a/src/adcp/types/generated_poc/core/frequency_cap.py b/src/adcp/types/generated_poc/core/frequency_cap.py deleted file mode 100644 index 06dbfdec0..000000000 --- a/src/adcp/types/generated_poc/core/frequency_cap.py +++ /dev/null @@ -1,65 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/frequency_cap.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictFloat, model_validator - -from ..enums import reach_unit -from . import duration - - -class FrequencyCap(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - suppress: Annotated[ - duration.Duration | None, - Field( - description='Cooldown period between consecutive exposures to the same entity. Prevents back-to-back ad delivery (e.g. {"interval": 60, "unit": "minutes"} for a 1-hour cooldown). Preferred over suppress_minutes.' - ), - ] = None - suppress_minutes: Annotated[ - StrictFloat | None, - Field( - deprecated=True, - description='Deprecated — use suppress instead. Cooldown period in minutes between consecutive exposures to the same entity (e.g. 60 for a 1-hour cooldown).', - ge=0.0, - ), - ] = None - max_impressions: Annotated[ - SchemaInt | None, - Field( - description="Maximum number of impressions per entity per window. For duration windows, implementations typically use a rolling window. campaign applies across the owning field's full flight: the package flight for a targeting overlay, or the MediaBuy flight for a root cap.", - ge=1, - ), - ] = None - per: Annotated[ - reach_unit.ReachUnit | None, - Field( - description='Entity granularity for impression counting. Required when max_impressions is set.' - ), - ] = None - window: Annotated[ - duration.Duration | None, - Field( - description='Time window for the max_impressions cap (e.g. {"interval": 7, "unit": "days"} or {"interval": 1, "unit": "campaign"} for the full flight). Required when max_impressions is set.' - ), - ] = None - - @model_validator(mode='after') - def _require_schema_required_group(self) -> FrequencyCap: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('suppress',), ('suppress_minutes',), ('max_impressions',),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'FrequencyCap requires at least one of these field groups: suppress | suppress_minutes | max_impressions' - ) diff --git a/src/adcp/types/generated_poc/core/frequency_cap_constraints.py b/src/adcp/types/generated_poc/core/frequency_cap_constraints.py deleted file mode 100644 index b7e16aafd..000000000 --- a/src/adcp/types/generated_poc/core/frequency_cap_constraints.py +++ /dev/null @@ -1,69 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/frequency_cap_constraints.json -# timestamp: 2026-10-02T23:28:30+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, model_validator - -from ..enums import frequency_cap_control_mode, frequency_cap_mutable_field, reach_unit -from . import ext as ext_1 -from . import frequency_cap_impression_constraints, frequency_cap_interval_constraints - - -class FrequencyCapConstraints(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - mutable_fields: Annotated[ - list[frequency_cap_mutable_field.FrequencyCapMutableField] | None, - Field( - description='Logical cap fields that can change after creation. An empty array means create-only. For example, [max_impressions] permits changing the count while keeping per and window fixed. Omission means update support is undeclared and the legacy broad meaning applies.' - ), - ] = None - supported_control_modes: Annotated[ - list[frequency_cap_control_mode.FrequencyCapControlMode] | None, - Field( - description='FrequencyCap shapes accepted by the product. max_impressions_and_suppress means both controls may appear together and are enforced with AND semantics.', - min_length=1, - ), - ] = None - supported_per_units: Annotated[ - list[reach_unit.ReachUnit] | None, - Field(description='Entity granularities accepted by max_impressions caps.', min_length=1), - ] = None - max_impressions_constraints: Annotated[ - frequency_cap_impression_constraints.FrequencyCapImpressionConstraints | None, - Field(description='Exact supported max_impressions presets or range.'), - ] = None - window_constraints: Annotated[ - list[frequency_cap_interval_constraints.FrequencyCapIntervalConstraints] | None, - Field( - description='Exact supported max_impressions.window intervals, one entry per duration unit.', - min_length=1, - ), - ] = None - suppression_constraints: Annotated[ - list[frequency_cap_interval_constraints.FrequencyCapIntervalConstraints] | None, - Field( - description='Exact supported suppress cooldown intervals, one entry per duration unit.', - min_length=1, - ), - ] = None - ext: ext_1.ExtensionObject | None = None - - @model_validator(mode='after') - def _require_schema_required_group(self) -> FrequencyCapConstraints: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('mutable_fields',), ('supported_control_modes',), ('supported_per_units',), ('max_impressions_constraints',), ('window_constraints',), ('suppression_constraints',),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'FrequencyCapConstraints requires at least one of these field groups: mutable_fields | supported_control_modes | supported_per_units | max_impressions_constraints | window_constraints | suppression_constraints' - ) diff --git a/src/adcp/types/generated_poc/core/frequency_cap_duration_unit.py b/src/adcp/types/generated_poc/core/frequency_cap_duration_unit.py deleted file mode 100644 index 2464b11f3..000000000 --- a/src/adcp/types/generated_poc/core/frequency_cap_duration_unit.py +++ /dev/null @@ -1,15 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/frequency_cap_duration_unit.json -# timestamp: 2026-09-14T14:16:02+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class FrequencyCapDurationUnit(StrEnum): - seconds = 'seconds' - minutes = 'minutes' - hours = 'hours' - days = 'days' - campaign = 'campaign' diff --git a/src/adcp/types/generated_poc/core/frequency_cap_impression_constraints.py b/src/adcp/types/generated_poc/core/frequency_cap_impression_constraints.py deleted file mode 100644 index e51a2b74d..000000000 --- a/src/adcp/types/generated_poc/core/frequency_cap_impression_constraints.py +++ /dev/null @@ -1,41 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/frequency_cap_impression_constraints.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarInt -from pydantic import ConfigDict, Field, model_validator - -from . import ext as ext_1 - - -class AllowedValue(ScalarInt): - __slots__ = () - _constraints = {'ge': 1} - - -class FrequencyCapImpressionConstraints(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - minimum: Annotated[SchemaInt | None, Field(ge=1)] = None - maximum: Annotated[SchemaInt | None, Field(ge=1)] = None - allowed_values: Annotated[list[AllowedValue] | None, Field(min_length=1)] = None - ext: ext_1.ExtensionObject | None = None - - @model_validator(mode='after') - def _require_schema_required_group(self) -> FrequencyCapImpressionConstraints: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('allowed_values',), ('minimum', 'maximum'),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'FrequencyCapImpressionConstraints requires at least one of these field groups: allowed_values | minimum+maximum' - ) diff --git a/src/adcp/types/generated_poc/core/frequency_cap_interval_constraints.py b/src/adcp/types/generated_poc/core/frequency_cap_interval_constraints.py deleted file mode 100644 index 8c4b030ee..000000000 --- a/src/adcp/types/generated_poc/core/frequency_cap_interval_constraints.py +++ /dev/null @@ -1,43 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/frequency_cap_interval_constraints.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarInt -from pydantic import ConfigDict, Field, model_validator - -from . import ext as ext_1 -from . import frequency_cap_duration_unit - - -class AllowedInterval(ScalarInt): - __slots__ = () - _constraints = {'ge': 1} - - -class FrequencyCapIntervalConstraints(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - unit: frequency_cap_duration_unit.FrequencyCapDurationUnit - minimum_interval: Annotated[SchemaInt | None, Field(ge=1)] = None - maximum_interval: Annotated[SchemaInt | None, Field(ge=1)] = None - allowed_intervals: Annotated[list[AllowedInterval] | None, Field(min_length=1)] = None - ext: ext_1.ExtensionObject | None = None - - @model_validator(mode='after') - def _require_schema_required_group(self) -> FrequencyCapIntervalConstraints: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('allowed_intervals',), ('minimum_interval', 'maximum_interval'),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'FrequencyCapIntervalConstraints requires at least one of these field groups: allowed_intervals | minimum_interval+maximum_interval' - ) diff --git a/src/adcp/types/generated_poc/core/frequency_cap_requirements.py b/src/adcp/types/generated_poc/core/frequency_cap_requirements.py deleted file mode 100644 index 0a034bfd0..000000000 --- a/src/adcp/types/generated_poc/core/frequency_cap_requirements.py +++ /dev/null @@ -1,44 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/frequency_cap_requirements.json -# timestamp: 2026-09-14T14:16:02+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..enums import frequency_cap_control_mode, frequency_cap_mutable_field, reach_unit -from . import frequency_cap_duration_unit - - -class FrequencyCapRequirements(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - mutable_fields: Annotated[ - list[frequency_cap_mutable_field.FrequencyCapMutableField] | None, - Field( - description='Require each listed logical field to be mutable after creation. Matches a product whose mutable_fields contains every listed value, or a product that omits mutable_fields, or legacy frequency_cap: true. A product declaring mutable_fields: [] is create-only and never matches a non-empty list.', - min_length=1, - ), - ] = None - supported_control_modes: Annotated[ - list[frequency_cap_control_mode.FrequencyCapControlMode] | None, Field(min_length=1) - ] = None - supported_per_units: Annotated[list[reach_unit.ReachUnit] | None, Field(min_length=1)] = None - supported_window_units: Annotated[ - list[frequency_cap_duration_unit.FrequencyCapDurationUnit] | None, - Field( - description='Require each listed unit to be a supported max_impressions.window unit after seller-wide inheritance.', - min_length=1, - ), - ] = None - supported_suppression_units: Annotated[ - list[frequency_cap_duration_unit.FrequencyCapDurationUnit] | None, - Field( - description='Require each listed unit to be a supported suppress unit after seller-wide inheritance.', - min_length=1, - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/generation_credential.py b/src/adcp/types/generated_poc/core/generation_credential.py deleted file mode 100644 index 88054752f..000000000 --- a/src/adcp/types/generated_poc/core/generation_credential.py +++ /dev/null @@ -1,48 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/generation_credential.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - -from ..enums import right_use -from . import ext as ext_1 - - -class GenerationCredential(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - provider: Annotated[ - str, - Field( - description="LLM or generation service provider identifier (e.g., 'midjourney', 'elevenlabs', 'stability')" - ), - ] - rights_key: Annotated[ - str, - Field( - description='Scoped API key or token for generating rights-cleared content. The provider validates this key at generation time to verify the caller is authorized.' - ), - ] - uses: Annotated[ - list[right_use.RightUse], - Field(description='Which rights uses this credential covers', min_length=1), - ] - expires_at: Annotated[ - AwareDatetime | None, - Field( - description='When this credential expires. Key lifetime is determined by the provider.' - ), - ] = None - endpoint: Annotated[ - AnyUrl | None, - Field( - description="Provider API endpoint to use with this credential, if different from the provider's default" - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/geo_breakdown_support.py b/src/adcp/types/generated_poc/core/geo_breakdown_support.py deleted file mode 100644 index c69e3f8a1..000000000 --- a/src/adcp/types/generated_poc/core/geo_breakdown_support.py +++ /dev/null @@ -1,39 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/geo_breakdown_support.json -# timestamp: 2026-10-03T15:09:44+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictBool - -from ..enums import metro_system -from . import postal_area_support - - -class GeographicBreakdownSupport(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - country: Annotated[ - StrictBool | None, - Field(description='Supports country-level geo breakdown (ISO 3166-1 alpha-2)'), - ] = None - region: Annotated[ - StrictBool | None, - Field(description='Supports region/state-level geo breakdown (ISO 3166-2)'), - ] = None - metro: Annotated[ - dict[metro_system.MetroAreaSystem, StrictBool] | None, - Field( - description='Metro area breakdown support. Keys are metro-system enum values; true means supported.' - ), - ] = None - postal_area: Annotated[ - postal_area_support.PostalAreaSupport | None, - Field( - description='Postal area breakdown support. Prefer the native country-keyed map where each ISO 3166-1 alpha-2 country lists supported country-local postal systems. Deprecated legacy country-fused postal-system boolean maps remain accepted for compatibility.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/geo_delivery_metrics.py b/src/adcp/types/generated_poc/core/geo_delivery_metrics.py deleted file mode 100644 index 05560e37c..000000000 --- a/src/adcp/types/generated_poc/core/geo_delivery_metrics.py +++ /dev/null @@ -1,46 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/geo_delivery_metrics.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from pydantic import Field - -from ..enums import geo_level as geo_level_1 -from .delivery_metrics import DeliveryMetrics - - -class GeoDeliveryMetrics(DeliveryMetrics): - geo_level: Annotated[ - geo_level_1.GeographicTargetingLevel, - Field(description='Geographic level of this entry (country, region, metro, postal_area)'), - ] - system: Annotated[ - str | None, - Field( - description='Classification system for metro or postal_area levels. Metro rows use metro-system values. Native postal rows use country-local postal-system values with country; deprecated legacy postal rows may use legacy-postal-system values.' - ), - ] = None - country: Annotated[ - str | None, - Field( - description='ISO 3166-1 alpha-2 country code for native postal_area rows.', - pattern='^[A-Z]{2}$', - ), - ] = None - geo_code: Annotated[ - str, - Field( - description="Geographic code within the level and system. Country: ISO 3166-1 alpha-2 ('US'). Region: ISO 3166-2 with country prefix ('US-CA'). Metro/postal: system-specific code ('501', '10001')." - ), - ] - geo_name: Annotated[ - str | None, - Field( - description="Human-readable geographic name (e.g., 'United States', 'California', 'New York DMA')" - ), - ] = None - impressions: Any - spend: Any diff --git a/src/adcp/types/generated_poc/core/geo_metro.py b/src/adcp/types/generated_poc/core/geo_metro.py deleted file mode 100644 index af0904b2c..000000000 --- a/src/adcp/types/generated_poc/core/geo_metro.py +++ /dev/null @@ -1,29 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/geo_metro.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..enums import metro_system - - -class GeoMetro(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - system: Annotated[ - metro_system.MetroAreaSystem, - Field(description="Metro area classification system (e.g., 'nielsen_dma', 'uk_itl2')"), - ] - values: Annotated[ - list[str], - Field( - description="Metro codes within the system (e.g., ['501', '602'] for Nielsen DMAs)", - min_length=1, - ), - ] diff --git a/src/adcp/types/generated_poc/core/geo_place_area.py b/src/adcp/types/generated_poc/core/geo_place_area.py deleted file mode 100644 index 5268362c4..000000000 --- a/src/adcp/types/generated_poc/core/geo_place_area.py +++ /dev/null @@ -1,55 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/geo_place_area.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field - -from . import ext as ext_1 -from . import geo_place_system, geo_place_type - - -class Value(ScalarStr): - __slots__ = () - _constraints = {'min_length': 1} - - -class GeographicPlaceArea(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - country: Annotated[ - str, - Field( - description='ISO 3166-1 alpha-2 country code containing the place.', - pattern='^[A-Z]{2}$', - ), - ] - system: geo_place_system.GeographicPlaceIdentifierSystem - system_version: Annotated[ - str | None, - Field( - description="Optional exact catalog version from the seller's declared supported_versions. When omitted, the seller applies catalog.current_version and MUST echo that version on persisted package state.", - min_length=1, - ), - ] = None - place_type: geo_place_type.GeographicPlaceType - values: Annotated[ - list[Value], - Field( - description='Stable place identifiers in the declared system. Display names are not valid targeting values.', - min_length=1, - ), - ] - value_labels: Annotated[ - dict[str, str] | None, - Field( - description='Optional human-readable diagnostic labels keyed by identifiers present in values. Extra keys are a conformance error. Labels are non-authoritative and MUST NOT be used to resolve or apply targeting.' - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/geo_place_catalog_capability.py b/src/adcp/types/generated_poc/core/geo_place_catalog_capability.py deleted file mode 100644 index f4be47dc1..000000000 --- a/src/adcp/types/generated_poc/core/geo_place_catalog_capability.py +++ /dev/null @@ -1,45 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/geo_place_catalog_capability.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import AnyUrl, ConfigDict, Field - -from . import geo_place_resolver - - -class SupportedVersion(ScalarStr): - __slots__ = () - _constraints = {'min_length': 1} - - -class GeographicPlaceCatalogCapability(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - source: Annotated[ - AnyUrl | None, - Field( - description='Optional catalog dataset or derivative-source identifier. This records provenance/coverage and does not change the identifier namespace in system.' - ), - ] = None - current_version: Annotated[ - str, - Field( - description='Version applied when a buyer omits system_version. Sellers MUST echo this version on persisted package state.', - min_length=1, - ), - ] - supported_versions: Annotated[ - list[SupportedVersion], - Field( - description='Exact catalog versions accepted for new targeting or target-changing updates. Must include current_version. Removing a version does not mutate or silently invalidate targets already pinned to it.', - min_length=1, - ), - ] - resolver: geo_place_resolver.GeographicPlaceResolver diff --git a/src/adcp/types/generated_poc/core/geo_place_catalog_entry.py b/src/adcp/types/generated_poc/core/geo_place_catalog_entry.py deleted file mode 100644 index 6967d6b1f..000000000 --- a/src/adcp/types/generated_poc/core/geo_place_catalog_entry.py +++ /dev/null @@ -1,87 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/geo_place_catalog_entry.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import AwareDatetime, ConfigDict, Field - -from . import ext as ext_1 -from . import geo_place_type - - -class ParentLabel(ScalarStr): - __slots__ = () - _constraints = {'min_length': 1} - - -class Status(StrEnum): - active = 'active' - removal_planned = 'removal_planned' - deprecated = 'deprecated' - - -class ReplacedByValue(ParentLabel): - pass - - -class GeographicPlaceCatalogEntry(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - value: Annotated[ - str, - Field( - description='Stable identifier in the response system and system_version.', min_length=1 - ), - ] - country: Annotated[str, Field(pattern='^[A-Z]{2}$')] - subdivision: Annotated[ - str | None, - Field( - description='ISO 3166-2 subdivision containing the place when the catalog has a subdivision mapping. Required by resolver semantics when the request constrained subdivision.', - pattern='^[A-Z]{2}-[A-Z0-9]{1,3}$', - ), - ] = None - place_type: geo_place_type.GeographicPlaceType - label: Annotated[ - str, - Field( - description='Human-readable display label; never an authoritative targeting key.', - min_length=1, - ), - ] - canonical_name: Annotated[ - str, - Field( - description='Required fully qualified display name suitable for distinguishing same-named results.', - min_length=1, - ), - ] - parent_labels: Annotated[ - list[ParentLabel], - Field( - description='Ordered human-readable parent hierarchy for disambiguation only. Must include at least the containing country.', - min_length=1, - ), - ] - status: Status - replaced_by_values: Annotated[ - list[ReplacedByValue] | None, - Field( - description='Replacement identifiers in the same system and response system_version.', - min_length=1, - ), - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='Known time after which the identifier must no longer be accepted for new targeting.' - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/geo_place_requirement.py b/src/adcp/types/generated_poc/core/geo_place_requirement.py deleted file mode 100644 index 37c6239e6..000000000 --- a/src/adcp/types/generated_poc/core/geo_place_requirement.py +++ /dev/null @@ -1,45 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/geo_place_requirement.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, StringConstraints - -from . import geo_place_system, geo_place_type - - -class SystemVersion(ScalarStr): - __slots__ = () - _constraints = {'min_length': 1} - - -class CatalogRequirement(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - countries: Annotated[ - dict[Annotated[str, StringConstraints(pattern=r'^[A-Z]{2}$')], list[geo_place_type.GeographicPlaceType]], - Field(min_length=1), - ] - system_versions: Annotated[ - list[SystemVersion] | None, - Field( - description='Optional exact catalog versions that must remain selectable. Versions are opaque strings, not ordered ranges.', - min_length=1, - ), - ] = None - - -class GeographicPlaceRequirement(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - systems: Annotated[ - dict[geo_place_system.GeographicPlaceIdentifierSystem, CatalogRequirement], - Field(min_length=1), - ] diff --git a/src/adcp/types/generated_poc/core/geo_place_resolver.py b/src/adcp/types/generated_poc/core/geo_place_resolver.py deleted file mode 100644 index 743e54b41..000000000 --- a/src/adcp/types/generated_poc/core/geo_place_resolver.py +++ /dev/null @@ -1,38 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/geo_place_resolver.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - - -class Auth(StrEnum): - none = 'none' - seller_credentials = 'seller_credentials' - - -class GeographicPlaceResolver(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: Annotated[ - AnyUrl, - Field( - description='HTTPS endpoint accepting the standard geo-place resolution query parameters.' - ), - ] - auth: Annotated[ - Auth, - Field( - description="Authentication mode. seller_credentials means use the same authorization credentials as the seller's AdCP endpoint and is valid only for a same-origin resolver URL; credentials MUST NOT be forwarded cross-origin." - ), - ] - protocol: Annotated[ - Literal['adcp_geo_place_resolver_v1'], - Field(description='Resolver request/response contract version.'), - ] = 'adcp_geo_place_resolver_v1' diff --git a/src/adcp/types/generated_poc/core/geo_place_support.py b/src/adcp/types/generated_poc/core/geo_place_support.py deleted file mode 100644 index 7293403ec..000000000 --- a/src/adcp/types/generated_poc/core/geo_place_support.py +++ /dev/null @@ -1,26 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/geo_place_support.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StringConstraints - -from . import geo_place_catalog_capability, geo_place_type - - -class GeographicPlaceSystemSupport(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - countries: Annotated[ - dict[Annotated[str, StringConstraints(pattern=r'^[A-Z]{2}$')], list[geo_place_type.GeographicPlaceType]], - Field( - description='Supported place types keyed by ISO 3166-1 alpha-2 country. Only explicitly listed country/type pairs are supported.', - min_length=1, - ), - ] - catalog: geo_place_catalog_capability.GeographicPlaceCatalogCapability diff --git a/src/adcp/types/generated_poc/core/geo_place_system.py b/src/adcp/types/generated_poc/core/geo_place_system.py deleted file mode 100644 index 5fba727fb..000000000 --- a/src/adcp/types/generated_poc/core/geo_place_system.py +++ /dev/null @@ -1,47 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/geo_place_system.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from pydantic import AnyUrl, Field, RootModel - - -class GeographicPlaceIdentifierSystem1(StrEnum): - geonames = 'geonames' - google_ads = 'google_ads' - microsoft_ads = 'microsoft_ads' - - -class GeographicPlaceIdentifierSystem2(RootModel[AnyUrl]): - root: Annotated[ - AnyUrl, - Field( - description='Collision-safe identifier namespace for geographic places. Registered tokens have protocol-defined semantics. Unregistered systems MUST use an absolute HTTPS URI controlled by the catalog owner; consumers compare URI systems as exact opaque strings.', - examples=[ - 'geonames', - 'google_ads', - 'microsoft_ads', - 'https://seller.example/geo/catalogs/places', - ], - title='Geographic Place Identifier System', - ), - ] - - -GeographicPlaceIdentifierSystem = Annotated[ - GeographicPlaceIdentifierSystem1 | GeographicPlaceIdentifierSystem2, - Field( - description='Collision-safe identifier namespace for geographic places. Registered tokens have protocol-defined semantics. Unregistered systems MUST use an absolute HTTPS URI controlled by the catalog owner; consumers compare URI systems as exact opaque strings.', - examples=[ - 'geonames', - 'google_ads', - 'microsoft_ads', - 'https://seller.example/geo/catalogs/places', - ], - title='Geographic Place Identifier System', - ), -] diff --git a/src/adcp/types/generated_poc/core/geo_place_type.py b/src/adcp/types/generated_poc/core/geo_place_type.py deleted file mode 100644 index 3bc1f9929..000000000 --- a/src/adcp/types/generated_poc/core/geo_place_type.py +++ /dev/null @@ -1,68 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/geo_place_type.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from pydantic import AnyUrl, Field, RootModel - - -class GeographicPlaceType1(StrEnum): - airport = 'airport' - borough = 'borough' - city = 'city' - city_region = 'city_region' - commune = 'commune' - county = 'county' - district = 'district' - municipality = 'municipality' - neighborhood = 'neighborhood' - post_town = 'post_town' - prefecture = 'prefecture' - province = 'province' - quarter = 'quarter' - state = 'state' - territory = 'territory' - ward = 'ward' - - -class GeographicPlaceType2(RootModel[AnyUrl]): - root: Annotated[ - AnyUrl, - Field( - description='Canonical place classification. Registered tokens have protocol-defined meanings. Catalog-specific classifications without a registered mapping MUST use an absolute HTTPS URI controlled by the vocabulary owner; consumers compare URI types as exact opaque strings.', - examples=[ - 'city', - 'municipality', - 'borough', - 'neighborhood', - 'post_town', - 'city_region', - 'county', - 'https://seller.example/geo/place-types/trade-area', - ], - title='Geographic Place Type', - ), - ] - - -GeographicPlaceType = Annotated[ - GeographicPlaceType1 | GeographicPlaceType2, - Field( - description='Canonical place classification. Registered tokens have protocol-defined meanings. Catalog-specific classifications without a registered mapping MUST use an absolute HTTPS URI controlled by the vocabulary owner; consumers compare URI types as exact opaque strings.', - examples=[ - 'city', - 'municipality', - 'borough', - 'neighborhood', - 'post_town', - 'city_region', - 'county', - 'https://seller.example/geo/place-types/trade-area', - ], - title='Geographic Place Type', - ), -] diff --git a/src/adcp/types/generated_poc/core/geo_region_requirement.py b/src/adcp/types/generated_poc/core/geo_region_requirement.py deleted file mode 100644 index 7e0b38671..000000000 --- a/src/adcp/types/generated_poc/core/geo_region_requirement.py +++ /dev/null @@ -1,54 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/geo_region_requirement.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, StringConstraints - - -class Countries(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - all_values: Annotated[ - Literal[True], - Field( - description="Require every protocol-valid ISO 3166-2 subdivision active in the seller's returned support snapshot for this country to remain selectable." - ), - ] - - -class Value(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{2}-[A-Z0-9]{1,3}$'} - - -class Countries1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[Value], - Field( - description='Exact finite set of ISO 3166-2 identifiers the buyer may select later.', - min_length=1, - ), - ] - - -class GeographicRegionRequirement(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - countries: Annotated[ - dict[Annotated[str, StringConstraints(pattern=r'^[A-Z]{2}$')], Countries | Countries1], - Field( - description='Required ISO subdivision support keyed by ISO 3166-1 alpha-2 country.', - min_length=1, - ), - ] diff --git a/src/adcp/types/generated_poc/core/geo_region_support.py b/src/adcp/types/generated_poc/core/geo_region_support.py deleted file mode 100644 index e43e5c62c..000000000 --- a/src/adcp/types/generated_poc/core/geo_region_support.py +++ /dev/null @@ -1,84 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/geo_region_support.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from datetime import date -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, StringConstraints - -from . import ext as ext_1 - - -class Countries(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - all_values: Annotated[ - Literal[True], - Field( - description="Every protocol-valid ISO 3166-2 subdivision active in this declaration's support snapshot is selectable. Values introduced by a later catalog revision are not included automatically." - ), - ] - - -class Value(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{2}-[A-Z0-9]{1,3}$'} - - -class Countries3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[Value], - Field( - description='Exact finite selectable subset of ISO 3166-2 identifiers for this country.', - min_length=1, - ), - ] - - -class GeographicRegionSupport(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - countries: Annotated[ - dict[Annotated[str, StringConstraints(pattern=r'^[A-Z]{2}$')], Countries | Countries3], - Field( - description='Selectable ISO subdivision values keyed by ISO 3166-1 alpha-2 country.', - min_length=1, - ), - ] - catalog_version: Annotated[ - str | None, - Field( - description='Optional opaque ISO subdivision catalog or seller-mapping version that identifies the support snapshot. It is provenance, not part of subdivision identity.', - min_length=1, - ), - ] = None - as_of: Annotated[ - date | None, - Field( - description='Optional date on which the seller last validated this support declaration.' - ), - ] = None - max_values_per_package: Annotated[ - SchemaInt | None, - Field( - description='Optional maximum number of region values selectable on one package.', ge=1 - ), - ] = None - max_packages: Annotated[ - SchemaInt | None, - Field( - description='Optional maximum number of independently region-targeted packages created from this configured product.', - ge=1, - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/get_geo_place_resolution_request.py b/src/adcp/types/generated_poc/core/get_geo_place_resolution_request.py deleted file mode 100644 index b2cb4aa68..000000000 --- a/src/adcp/types/generated_poc/core/get_geo_place_resolution_request.py +++ /dev/null @@ -1,67 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/get_geo_place_resolution_request.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, model_validator - -from . import geo_place_type, locale_tag - - -class GetGeographicPlaceResolutionRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - q: Annotated[ - str | None, - Field(description='Unresolved place name or alias supplied by the user.', min_length=1), - ] = None - value: Annotated[ - str | None, - Field( - description='Existing identifier to look up in the requested/current catalog version for lifecycle refresh or replacement discovery. Mutually exclusive with q.', - min_length=1, - ), - ] = None - country: Annotated[ - str, - Field( - description='ISO 3166-1 alpha-2 country code used to disambiguate the query.', - pattern='^[A-Z]{2}$', - ), - ] - subdivision: Annotated[ - str | None, - Field( - description='Optional ISO 3166-2 subdivision constraint.', - pattern='^[A-Z]{2}-[A-Z0-9]{1,3}$', - ), - ] = None - place_type: geo_place_type.GeographicPlaceType | None = None - system_version: Annotated[ - str | None, - Field(description='Optional exact supported catalog version to search.', min_length=1), - ] = None - locale: Annotated[ - locale_tag.LanguageTag | None, - Field(description='Optional BCP 47 language tag for returned labels.'), - ] = None - cursor: Annotated[str | None, Field(min_length=1)] = None - limit: Annotated[SchemaInt | None, Field(ge=1, le=100)] = 20 - - @model_validator(mode='after') - def _require_schema_required_group(self) -> GetGeographicPlaceResolutionRequest: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('q',), ('value',),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'GetGeographicPlaceResolutionRequest requires at least one of these field groups: q | value' - ) diff --git a/src/adcp/types/generated_poc/core/get_geo_place_resolution_response.py b/src/adcp/types/generated_poc/core/get_geo_place_resolution_response.py deleted file mode 100644 index b1cef6059..000000000 --- a/src/adcp/types/generated_poc/core/get_geo_place_resolution_response.py +++ /dev/null @@ -1,28 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/get_geo_place_resolution_response.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from . import geo_place_catalog_entry, geo_place_system, get_geo_place_resolution_request - - -class GetGeographicPlaceResolutionResponse(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - request: Annotated[ - get_geo_place_resolution_request.GetGeographicPlaceResolutionRequest, - Field( - description='Exact normalized request represented by this page. Pagination responses repeat the original query fields and may carry the page cursor.' - ), - ] - system: geo_place_system.GeographicPlaceIdentifierSystem - system_version: Annotated[str, Field(min_length=1)] - matches: list[geo_place_catalog_entry.GeographicPlaceCatalogEntry] - next_cursor: Annotated[str | None, Field(min_length=1)] = None diff --git a/src/adcp/types/generated_poc/core/hotel_item.py b/src/adcp/types/generated_poc/core/hotel_item.py deleted file mode 100644 index 30f7a7e63..000000000 --- a/src/adcp/types/generated_poc/core/hotel_item.py +++ /dev/null @@ -1,122 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/hotel_item.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from datetime import date -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, ConfigDict, Field, StrictFloat - -from . import ext as ext_1 -from . import offering_asset_group -from . import price as price_1 - - -class Location(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - lat: Annotated[ - StrictFloat, Field(description='Latitude in decimal degrees (WGS 84).', ge=-90.0, le=90.0) - ] - lng: Annotated[ - StrictFloat, - Field(description='Longitude in decimal degrees (WGS 84).', ge=-180.0, le=180.0), - ] - - -class Address(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - street: Annotated[str | None, Field(description='Street address.')] = None - city: Annotated[str | None, Field(description='City name.')] = None - region: Annotated[str | None, Field(description='State, province, or region.')] = None - postal_code: Annotated[str | None, Field(description='Postal or ZIP code.')] = None - country: Annotated[ - str | None, Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = None - - -class HotelItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - hotel_id: Annotated[ - str, - Field( - description='Unique identifier for this property. Used to match remarketing events and inventory feeds to the correct hotel.' - ), - ] - name: Annotated[ - str, - Field(description="Property name (e.g., 'Grand Hotel Amsterdam', 'Seaside Resort & Spa')."), - ] - description: Annotated[ - str | None, Field(description='Property description highlighting features and location.') - ] = None - location: Annotated[Location, Field(description='Geographic coordinates of the property.')] - address: Annotated[ - Address | None, Field(description='Structured address for display and geocoding.') - ] = None - star_rating: Annotated[ - SchemaInt | None, Field(description='Official star rating (1–5).', ge=1, le=5) - ] = None - price: Annotated[ - price_1.Price | None, - Field(description="Nightly rate or starting price. Use period 'night' for nightly rates."), - ] = None - image_url: Annotated[AnyUrl | None, Field(description='Primary property image URL.')] = None - url: Annotated[AnyUrl | None, Field(description='Property landing page or booking URL.')] = None - phone: Annotated[str | None, Field(description='Property phone number in E.164 format.')] = None - amenities: Annotated[ - list[str] | None, - Field( - description="Property amenities (e.g., 'pool', 'wifi', 'spa', 'parking', 'restaurant').", - min_length=1, - ), - ] = None - check_in_time: Annotated[ - str | None, - Field( - description="Standard check-in time in HH:MM format (e.g., '15:00').", - pattern='^[0-2][0-9]:[0-5][0-9]$', - ), - ] = None - check_out_time: Annotated[ - str | None, - Field( - description="Standard check-out time in HH:MM format (e.g., '11:00').", - pattern='^[0-2][0-9]:[0-5][0-9]$', - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description="Tags for filtering and targeting (e.g., 'boutique', 'family', 'business', 'luxury').", - min_length=1, - ), - ] = None - valid_from: Annotated[ - date | None, - Field( - description="Date from which this item is available or this rate applies (ISO 8601, e.g., '2025-03-01'). Used for seasonal availability windows in feed imports." - ), - ] = None - valid_to: Annotated[ - date | None, - Field( - description="Date until which this item is available or this rate applies (ISO 8601, e.g., '2025-09-30'). Used for seasonal availability windows in feed imports." - ), - ] = None - assets: Annotated[ - list[offering_asset_group.OfferingAssetGroup] | None, - Field( - description="Typed creative asset pools for this hotel. Uses the same OfferingAssetGroup structure as offering-type catalogs. Standard group IDs: 'images_landscape' (16:9 hero images), 'images_vertical' (9:16 for Snap, Stories), 'images_square' (1:1), 'logo'. Enables formats to declare typed image requirements that map unambiguously to the right asset regardless of platform.", - min_length=1, - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/iana_timezone.py b/src/adcp/types/generated_poc/core/iana_timezone.py deleted file mode 100644 index 314d05296..000000000 --- a/src/adcp/types/generated_poc/core/iana_timezone.py +++ /dev/null @@ -1,20 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/iana_timezone.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from adcp.types._scalar import ScalarStr - - -class IanaTimezoneIdentifier(ScalarStr): - __slots__ = () - _constraints = { - 'max_length': 255, - 'min_length': 1, - 'pattern': '^[A-Za-z0-9._+-]+(?:/[A-Za-z0-9._+-]+)*$', - } - _json_schema_extra = { - 'description': "Concrete timezone identifier in the implementation's supported IANA Time Zone Database, such as America/New_York, CET, or UTC.", - 'title': 'IANA Timezone Identifier', - } diff --git a/src/adcp/types/generated_poc/core/identifier.py b/src/adcp/types/generated_poc/core/identifier.py deleted file mode 100644 index afcd271d8..000000000 --- a/src/adcp/types/generated_poc/core/identifier.py +++ /dev/null @@ -1,27 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/identifier.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..enums import identifier_types - - -class Identifier(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Annotated[ - identifier_types.PropertyIdentifierTypes, Field(description='Type of identifier') - ] - value: Annotated[ - str, - Field( - description="The identifier value. For domain type: 'example.com' matches base domain plus www and m subdomains; 'edition.example.com' matches that specific subdomain; '*.example.com' matches ALL subdomains but NOT base domain" - ), - ] diff --git a/src/adcp/types/generated_poc/core/impairment.py b/src/adcp/types/generated_poc/core/impairment.py deleted file mode 100644 index 14d2e4a71..000000000 --- a/src/adcp/types/generated_poc/core/impairment.py +++ /dev/null @@ -1,102 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/impairment.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - -from ..enums import impairment_offline_state, impairment_reason_code - - -class ResourceType(StrEnum): - audience = 'audience' - creative = 'creative' - catalog_item = 'catalog_item' - event_source = 'event_source' - property = 'property' - - -class Transition(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - from_: Annotated[ - str | None, - Field( - alias='from', - description="Prior status of the resource (e.g., 'ready', 'approved', 'good'). Optional — sellers SHOULD include when known, MAY omit when the resource was discovered already in an offline state (e.g., a property depublished via brand.json crawl with no prior snapshot). Open string at the schema layer because each resource_type has its own serviceable-state vocabulary; the pattern constraint blocks free-form garbage, and the impairment.coherence assertion validates that 'from' is a known serviceable value for the resource_type.", - pattern='^[a-z][a-z0-9_]*$', - ), - ] = None - to: Annotated[ - impairment_offline_state.ImpairmentOfflineState, - Field( - description="Current (offline) status of the resource. Drawn from the resource_type's canonical lifecycle enum; see impairment-offline-state for per-value resource_type pairing. The pairing is validated by impairment.coherence." - ), - ] - - -class Impairment(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - impairment_id: Annotated[ - str, - Field( - description="Stable identifier for this impairment, used as the notification_id when the impairment fires via webhook. Stable across re-emissions of the same open impairment (e.g., the seller re-fires after the buyer's receiver was down) and across the closing fire that signals resolution. A new impairment for the same resource_id after closure receives a new impairment_id. Distinct from the per-fire idempotency_key issued at the webhook transport layer — see snapshot-and-log Rule 1. Receivers correlate webhook fires to current impairments[] state by impairment_id; receivers suppress duplicate transport-layer retries by idempotency_key. Seeing the same impairment_id with different idempotency_keys is a re-emission signal, not a retry — the buyer should treat it as a notice that something may have been missed." - ), - ] - resource_type: Annotated[ - ResourceType, - Field( - description="The kind of upstream dependency that transitioned to an offline state. Values are drawn from the x-entity vocabulary (see core/x-entity-types.json) and identify a buyer-referenced object with its own lifecycle that the seller can take offline. This is the subset of x-entity types for which a media buy's serving depends on the resource — not a new typology, just the impairment-relevant slice." - ), - ] - resource_id: Annotated[ - str, - Field( - description="Seller's identifier for the specific resource that transitioned. References the same id space as the corresponding sync_/list_ task responses (e.g., audience_id, creative_id)." - ), - ] - package_ids: Annotated[ - list[str], - Field( - description="Packages within this media buy whose delivery is degraded by the impairment. MUST list at least one package — cosmetic effects that do not degrade any package's ability to serve MUST NOT be reported as impairments.", - min_length=1, - ), - ] - transition: Annotated[ - Transition, - Field(description='The resource-level status transition that triggered this impairment.'), - ] - reason_code: Annotated[ - impairment_reason_code.ImpairmentReasonCode, - Field( - description='Categorical reason for the offline transition. Drives buyer-side remediation logic.' - ), - ] - reason: Annotated[ - str | None, - Field( - description='Human-readable explanation. Supplements reason_code with seller-specific detail.', - max_length=500, - ), - ] = None - observed_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the seller observed the resource transition to its offline state.' - ), - ] - remediation: Annotated[ - str | None, - Field( - description='Action the buyer can take to clear the impairment, if any. Free text. Absent when no buyer-side remediation is possible (e.g., seller-initiated withdrawal pending re-publication).', - max_length=500, - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/indicator.py b/src/adcp/types/generated_poc/core/indicator.py deleted file mode 100644 index 03c30f6a0..000000000 --- a/src/adcp/types/generated_poc/core/indicator.py +++ /dev/null @@ -1,40 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/indicator.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - -from ..enums import indicator_type -from . import ext as ext_1 -from . import indicator_scope - - -class Indicator(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: indicator_type.IndicatorType - detected_at: Annotated[ - AwareDatetime | None, - Field( - description='When the seller first detected the current uninterrupted occurrence of this indicator. Keep this value stable while the condition remains present. If the condition clears and is later detected again, use the new detection time. Optional because some upstream platforms expose current assessments without an original detection timestamp.' - ), - ] = None - scope: Annotated[ - list[indicator_scope.IndicatorScope] | None, - Field( - description='Optional narrower publisher or placement scope within the enclosing media buy, package, or package–creative assignment. Omit only when the seller evaluated and asserts the indicator across the whole enclosing resource/relationship. When partial indicators_evaluated_scope is declared, every returned indicator MUST include scope and every entry MUST fall within that coverage. This is scope, not source: the responding seller remains the source.', - min_length=1, - ), - ] = None - ext: Annotated[ - ext_1.ExtensionObject | None, - Field( - description='Seller- or provider-specific detail such as scores, thresholds, evaluation windows, methodology identifiers, or upstream attribution. Core consumers must not need ext to understand the broad meaning of type.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/indicator_bearing.py b/src/adcp/types/generated_poc/core/indicator_bearing.py deleted file mode 100644 index 3d5b17295..000000000 --- a/src/adcp/types/generated_poc/core/indicator_bearing.py +++ /dev/null @@ -1,45 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/indicator_bearing.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - -from ..enums import indicator_type -from . import indicator, indicator_scope - - -class IndicatorBearingResourceState(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - indicators: Annotated[ - list[indicator.Indicator] | None, - Field( - description='Current seller assertions for the indicator types and publisher/placement coverage named by the sibling evaluation fields. Omitted means unknown or not evaluated. A present empty array means evaluated with no current assertion for indicator_types_evaluated in the evaluated scope.' - ), - ] = None - indicator_types_evaluated: Annotated[ - list[indicator_type.IndicatorType] | None, - Field( - description='Indicator types covered by this snapshot. Required whenever indicators is present. Types omitted from this list remain unknown even when indicators is empty. Every returned indicator.type MUST appear in this list.', - min_length=1, - ), - ] = None - indicators_as_of: Annotated[ - AwareDatetime | None, - Field( - description='When the seller completed this evaluation. Required whenever indicators is present, including an empty array. State changes require a strictly newer timestamp; equal-timestamp conflicts are invalid and buyers retain stored state.' - ), - ] = None - indicators_evaluated_scope: Annotated[ - list[indicator_scope.IndicatorScope] | None, - Field( - description='Optional publisher or placement coverage for this evaluation. Omit when the named indicator types were evaluated across the whole enclosing resource or relationship. Unlisted scopes remain unknown. When present, every returned indicator MUST include scope and every scope entry MUST be contained by this coverage.', - min_length=1, - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/indicator_scope.py b/src/adcp/types/generated_poc/core/indicator_scope.py deleted file mode 100644 index 4d13d271b..000000000 --- a/src/adcp/types/generated_poc/core/indicator_scope.py +++ /dev/null @@ -1,29 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/indicator_scope.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - - -class IndicatorScope(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Annotated[ - str, - Field( - description="Domain where the publisher's adagents.json is hosted.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - placement_id: Annotated[ - str | None, - Field( - description='Optional placement ID within publisher_domain. Omit to scope the assertion or evaluation to all delivery for the publisher in the enclosing object.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/indicators_changed_webhook.py b/src/adcp/types/generated_poc/core/indicators_changed_webhook.py deleted file mode 100644 index 9f5a27f27..000000000 --- a/src/adcp/types/generated_poc/core/indicators_changed_webhook.py +++ /dev/null @@ -1,82 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/indicators_changed_webhook.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - -from ..enums import indicator_type -from . import ext as ext_1 - - -class RelationshipKind(StrEnum): - media_buy = 'media_buy' - package = 'package' - creative_assignment = 'creative_assignment' - - -class ChangeKind(StrEnum): - asserted = 'asserted' - cleared = 'cleared' - updated = 'updated' - invalidated = 'invalidated' - coverage_changed = 'coverage_changed' - assignment_removed = 'assignment_removed' - - -class IndicatorsChangedWebhook(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - idempotency_key: Annotated[ - str, - Field( - description='Random fire identifier reused across retries. Receivers dedupe within the authenticated sender scope.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - notification_id: Annotated[ - str, - Field( - description='Stable identity for this logical snapshot change across re-emissions. A later semantic change receives a new value.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] - notification_type: Literal['indicators.changed'] = 'indicators.changed' - fired_at: AwareDatetime - subscriber_id: Annotated[ - str, Field(max_length=64, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,64}$') - ] - account_id: str - relationship_kind: Annotated[ - RelationshipKind, Field(description='Identifies which indicator-bearing snapshot changed.') - ] - media_buy_id: str - package_id: str | None = None - creative_id: str | None = None - change_kind: Annotated[ - ChangeKind, - Field( - description='Coarse invalidation reason. Buyers MUST reread rather than applying this value as state. invalidated is emitted when a material creative-content update retires the prior evaluation and the relationship becomes unknown until reevaluated. assignment_removed is emitted to indicator subscribers when deletion retires keys for a package–creative relationship.' - ), - ] - changed_indicator_types: Annotated[ - list[indicator_type.IndicatorType], - Field( - description='Types known to be affected. This is a reread hint, not a complete current set.', - min_length=1, - ), - ] - observed_at: Annotated[ - AwareDatetime, Field(description='When the seller observed the semantic snapshot change.') - ] - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/industry_identifier.py b/src/adcp/types/generated_poc/core/industry_identifier.py deleted file mode 100644 index 4f49dc3ff..000000000 --- a/src/adcp/types/generated_poc/core/industry_identifier.py +++ /dev/null @@ -1,26 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/industry_identifier.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..enums import creative_identifier_type - - -class IndustryIdentifier(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: creative_identifier_type.CreativeIdentifierType - value: Annotated[ - str, - Field( - description="The identifier value (e.g., 'ABCD1234000H' for Ad-ID). Preserve the value exactly as the traffic or clearance system expects it.", - max_length=64, - ), - ] diff --git a/src/adcp/types/generated_poc/core/insertion_order.py b/src/adcp/types/generated_poc/core/insertion_order.py deleted file mode 100644 index 3829890d7..000000000 --- a/src/adcp/types/generated_poc/core/insertion_order.py +++ /dev/null @@ -1,79 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/insertion_order.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat - - -class TotalBudget(AdCPBaseModel): - amount: Annotated[StrictFloat, Field(ge=0.0)] - currency: Annotated[ - str, Field(description='ISO 4217 currency code', max_length=3, min_length=3) - ] - - -class PaymentTerms(StrEnum): - net_30 = 'net_30' - net_60 = 'net_60' - net_90 = 'net_90' - prepaid = 'prepaid' - due_on_receipt = 'due_on_receipt' - - -class Terms(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - advertiser: Annotated[ - str | None, Field(description='Advertiser name or identifier', max_length=500) - ] = None - publisher: Annotated[ - str | None, Field(description='Publisher name or identifier', max_length=500) - ] = None - total_budget: Annotated[TotalBudget | None, Field(description='Total committed budget')] = None - flight_start: Annotated[AwareDatetime | None, Field(description='Campaign start date')] = None - flight_end: Annotated[AwareDatetime | None, Field(description='Campaign end date')] = None - payment_terms: Annotated[PaymentTerms | None, Field(description='Payment terms')] = None - - -class InsertionOrder(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - io_id: Annotated[ - str, - Field( - description='Unique identifier for this insertion order. Referenced by io_acceptance on create_media_buy.', - max_length=255, - ), - ] - terms: Annotated[ - Terms | None, - Field( - description='Summary fields echoed from the committed proposal for agent verification. Buyer agents use these to confirm the IO matches what was negotiated before a human signs. These are read-only summaries, not negotiation surfaces — deal terms live on products and packages.' - ), - ] = None - terms_url: Annotated[ - AnyUrl | None, - Field( - description='URL to a human-readable document containing the full insertion order terms' - ), - ] = None - signing_url: Annotated[ - AnyUrl | None, - Field( - description='URL to an electronic signing service (e.g., DocuSign) for human signature workflows. When present, a human must sign before the buyer agent can proceed with create_media_buy.' - ), - ] = None - requires_signature: Annotated[ - StrictBool, - Field( - description='Whether the buyer must accept this IO before creating a media buy. When true, create_media_buy requires an io_acceptance referencing this io_id.' - ), - ] diff --git a/src/adcp/types/generated_poc/core/installment.py b/src/adcp/types/generated_poc/core/installment.py deleted file mode 100644 index 6a5ccfe09..000000000 --- a/src/adcp/types/generated_poc/core/installment.py +++ /dev/null @@ -1,123 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/installment.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field, StrictBool - -from ..enums import derivative_type, installment_status -from . import ad_inventory_config -from . import collection_ref as collection_ref_1 -from . import content_rating as content_rating_1 -from . import ext as ext_1 -from . import installment_deadlines -from . import special as special_1 -from . import talent - - -class DerivativeOf(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - installment_id: Annotated[ - str, Field(description='The source installment this content is derived from') - ] - type: Annotated[ - derivative_type.DerivativeType, Field(description='What kind of derivative content this is') - ] - - -class Installment(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - installment_id: Annotated[ - str, Field(description='Unique identifier for this installment within the collection') - ] - collection_ref: Annotated[ - collection_ref_1.CollectionReference | None, - Field( - description='Canonical parent collection identity. Products spanning multiple collections or publisher namespaces MUST use this field.' - ), - ] = None - collection_id: Annotated[ - str | None, - Field( - deprecated=True, - description='Deprecated publisher-domain-free parent collection shorthand. Use collection_ref. It is unambiguous only when the enclosing product addresses one publisher namespace.', - ), - ] = None - name: Annotated[str | None, Field(description='Installment title')] = None - season: Annotated[ - str | None, Field(description="Season identifier (e.g., '1', '2024', 'spring_2026')") - ] = None - installment_number: Annotated[ - str | None, Field(description="Installment number within the season (e.g., '3', '47')") - ] = None - scheduled_at: Annotated[ - AwareDatetime | None, Field(description='When the installment airs or publishes (ISO 8601)') - ] = None - status: Annotated[ - installment_status.InstallmentStatus | None, - Field(description='Lifecycle status of the installment'), - ] = None - duration_seconds: Annotated[ - SchemaInt | None, Field(description='Expected duration of the installment in seconds', ge=0) - ] = None - flexible_end: Annotated[ - StrictBool | None, - Field(description='Whether the end time is approximate (live events, sports)'), - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='When this installment data expires and should be re-queried. Agents should re-query before committing budget to products with tentative installments.' - ), - ] = None - content_rating: Annotated[ - content_rating_1.ContentRating | None, - Field( - description="Installment-specific content rating. Overrides the collection's baseline content_rating when present." - ), - ] = None - topics: Annotated[ - list[str] | None, - Field( - description="Content topics for this installment. Uses the same taxonomy as the collection's genre_taxonomy when present. Enables installment-level brand safety evaluation beyond content_rating." - ), - ] = None - special: Annotated[ - special_1.Special | None, - Field( - description='Installment-specific event context. When present, this installment is anchored to a real-world event. Overrides the collection-level special when present.' - ), - ] = None - guest_talent: Annotated[ - list[talent.Talent] | None, - Field( - description="Installment-specific guests and talent. Additive to the collection's recurring talent." - ), - ] = None - ad_inventory: Annotated[ - ad_inventory_config.AdInventoryConfiguration | None, - Field( - description='Break-based ad inventory for this installment. For non-break formats (host reads, integrations), use product placements.' - ), - ] = None - deadlines: Annotated[ - installment_deadlines.InstallmentDeadlines | None, - Field( - description='Booking, cancellation, and material submission deadlines for this installment. Present when the installment has time-sensitive inventory that requires advance commitment or material delivery.' - ), - ] = None - derivative_of: Annotated[ - DerivativeOf | None, - Field( - description='When this installment is a clip, highlight, or recap derived from a full installment. The source installment_id must reference an installment within the same response.' - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/installment_deadlines.py b/src/adcp/types/generated_poc/core/installment_deadlines.py deleted file mode 100644 index 98619ea9a..000000000 --- a/src/adcp/types/generated_poc/core/installment_deadlines.py +++ /dev/null @@ -1,37 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/installment_deadlines.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - -from . import material_deadline - - -class InstallmentDeadlines(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - booking_deadline: Annotated[ - AwareDatetime | None, - Field( - description='Last date/time to book a placement in this installment (ISO 8601). After this point, the seller will not accept new bookings.' - ), - ] = None - cancellation_deadline: Annotated[ - AwareDatetime | None, - Field( - description="Last date/time to cancel without penalty (ISO 8601). Cancellations after this point may incur fees per the seller's terms." - ), - ] = None - material_deadlines: Annotated[ - list[material_deadline.MaterialDeadline] | None, - Field( - description="Stages for creative material submission. Items MUST be in chronological order by due_at (earliest first). Typical pattern: 'draft' for raw materials the seller will process, 'final' for production-ready assets. Print example: draft artwork then press-ready PDF. Influencer example: talking points then approved script.", - min_length=1, - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/installment_delivery_metrics.py b/src/adcp/types/generated_poc/core/installment_delivery_metrics.py deleted file mode 100644 index f8a824060..000000000 --- a/src/adcp/types/generated_poc/core/installment_delivery_metrics.py +++ /dev/null @@ -1,30 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/installment_delivery_metrics.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from pydantic import AwareDatetime, Field - -from . import installment_ref as installment_ref_1 -from .delivery_metrics import DeliveryMetrics - - -class InstallmentDeliveryMetrics(DeliveryMetrics): - installment_ref: installment_ref_1.InstallmentReference - installment_name: Annotated[ - str | None, - Field( - description='Current human-readable installment name. Convenience metadata only; installment_ref is stable identity.' - ), - ] = None - scheduled_at: Annotated[ - AwareDatetime | None, - Field( - description="Convenience echo of the installment's scheduled publication or air time." - ), - ] = None - impressions: Any - spend: Any diff --git a/src/adcp/types/generated_poc/core/installment_property_delivery_metrics.py b/src/adcp/types/generated_poc/core/installment_property_delivery_metrics.py deleted file mode 100644 index 483577736..000000000 --- a/src/adcp/types/generated_poc/core/installment_property_delivery_metrics.py +++ /dev/null @@ -1,51 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/installment_property_delivery_metrics.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from pydantic import AwareDatetime, Field - -from . import identifier as identifier_1 -from . import installment_ref as installment_ref_1 -from . import property_ref as property_ref_1 -from .delivery_metrics import DeliveryMetrics - - -class InstallmentPropertyDeliveryMetrics(DeliveryMetrics): - installment_ref: installment_ref_1.InstallmentReference - installment_name: Annotated[ - str | None, - Field(description='Current human-readable installment name. Convenience metadata only.'), - ] = None - scheduled_at: Annotated[ - AwareDatetime | None, - Field( - description="Convenience echo of the installment's scheduled publication or air time." - ), - ] = None - publisher_domain: Annotated[ - str, - Field( - description='Publisher or platform authority that namespaces the property identifier, including for an unregistered surface.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - identifier: Annotated[ - identifier_1.Identifier, - Field(description='Operational identity of the property that delivered the installment.'), - ] - property_ref: Annotated[ - property_ref_1.PropertyReference | None, - Field( - description="Canonical publisher-scoped catalog identity when available. Its publisher_domain MUST equal the row's publisher_domain." - ), - ] = None - property_name: Annotated[ - str | None, - Field(description='Current human-readable property name. Convenience metadata only.'), - ] = None - impressions: Any - spend: Any diff --git a/src/adcp/types/generated_poc/core/installment_ref.py b/src/adcp/types/generated_poc/core/installment_ref.py deleted file mode 100644 index 528203dc1..000000000 --- a/src/adcp/types/generated_poc/core/installment_ref.py +++ /dev/null @@ -1,22 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/installment_ref.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from . import collection_ref as collection_ref_1 - - -class InstallmentReference(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - collection_ref: collection_ref_1.CollectionReference - installment_id: Annotated[ - str, Field(description='Installment ID within the referenced collection.', min_length=1) - ] diff --git a/src/adcp/types/generated_poc/core/inventory_list_application.py b/src/adcp/types/generated_poc/core/inventory_list_application.py deleted file mode 100644 index 0224d0156..000000000 --- a/src/adcp/types/generated_poc/core/inventory_list_application.py +++ /dev/null @@ -1,148 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/inventory_list_application.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - - -class Effect(StrEnum): - include = 'include' - exclude = 'exclude' - - -class Summary(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - matched: Annotated[ - SchemaInt, - Field( - description='Resolved entries that matched at least one property in this product.', ge=0 - ), - ] - unmatched: Annotated[ - SchemaInt, - Field(description='Resolved entries that did not match a property in this product.', ge=0), - ] - - -class InventoryListApplication1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - list_type: Annotated[ - Literal['property'], Field(description='The receipt describes a property list.') - ] = 'property' - effect: Annotated[ - Effect, - Field( - description='Whether matching properties were retained as an allowlist or removed as a blocklist.' - ), - ] - agent_url: Annotated[ - AnyUrl, Field(description='Agent URL from the effective property-list targeting reference.') - ] - list_id: Annotated[ - str, - Field( - description='Identifier from the effective property-list targeting reference.', - min_length=1, - ), - ] - resolved_at: Annotated[ - AwareDatetime, - Field( - description='Timestamp identifying the complete resolved-list snapshot the seller evaluated. If the list response supplied resolved_at, the seller MUST copy it; otherwise the seller records when it completed assembling the snapshot, including all fetched pages.' - ), - ] - evaluated_at: Annotated[ - AwareDatetime, - Field( - description="When the seller intersected that snapshot with this product's then-current inventory." - ), - ] - summary: Annotated[ - Summary, - Field( - description="Partition of every entry in the resolved list snapshot against this product's common pre-list inventory baseline." - ), - ] - - -class Summary2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - matched: Annotated[ - SchemaInt, - Field( - description='Resolved entries that matched at least one collection in this product.', - ge=0, - ), - ] - unmatched: Annotated[ - SchemaInt, - Field( - description='Resolved entries that did not match a collection in this product.', ge=0 - ), - ] - - -class InventoryListApplication2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - list_type: Annotated[ - Literal['collection'], Field(description='The receipt describes a collection list.') - ] = 'collection' - effect: Annotated[ - Effect, - Field( - description='Whether matching collections were retained as an allowlist or removed as a blocklist.' - ), - ] - agent_url: Annotated[ - AnyUrl, - Field(description='Agent URL from the effective collection-list targeting reference.'), - ] - list_id: Annotated[ - str, - Field( - description='Identifier from the effective collection-list targeting reference.', - min_length=1, - ), - ] - resolved_at: Annotated[ - AwareDatetime, - Field( - description='Timestamp identifying the complete resolved-list snapshot the seller evaluated. If the list response supplied resolved_at, the seller MUST copy it; otherwise the seller records when it completed assembling the snapshot, including all fetched pages.' - ), - ] - evaluated_at: Annotated[ - AwareDatetime, - Field( - description="When the seller intersected that snapshot with this product's then-current inventory." - ), - ] - summary: Annotated[ - Summary2, - Field( - description="Partition of every entry in the resolved list snapshot against this product's common pre-list inventory baseline." - ), - ] - - -InventoryListApplication = Annotated[ - InventoryListApplication1 | InventoryListApplication2, - Field( - description='Seller-issued, product-scoped receipt showing how one effective property- or collection-list targeting reference intersected the inventory represented by a returned product. Each receipt compares its resolved entries independently against the same product inventory before any effective list is applied; product pricing and forecasts reflect the eligible inventory after all effective lists are composed. Sellers MUST NOT echo auth_token or other separately supplied fetch credentials; the closed receipt shape defines no credential field.', - discriminator='list_type', - title='Inventory List Application', - ), -] diff --git a/src/adcp/types/generated_poc/core/job_item.py b/src/adcp/types/generated_poc/core/job_item.py deleted file mode 100644 index 26139ed2f..000000000 --- a/src/adcp/types/generated_poc/core/job_item.py +++ /dev/null @@ -1,115 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/job_item.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from datetime import date -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, StrictFloat - -from . import ext as ext_1 -from . import offering_asset_group - - -class EmploymentType(StrEnum): - full_time = 'full_time' - part_time = 'part_time' - contract = 'contract' - temporary = 'temporary' - internship = 'internship' - freelance = 'freelance' - - -class ExperienceLevel(StrEnum): - entry_level = 'entry_level' - mid_level = 'mid_level' - senior = 'senior' - director = 'director' - executive = 'executive' - - -class Period(StrEnum): - hour = 'hour' - month = 'month' - year = 'year' - - -class Salary(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - min: Annotated[StrictFloat | None, Field(description='Minimum salary.', ge=0.0)] = None - max: Annotated[StrictFloat | None, Field(description='Maximum salary.', ge=0.0)] = None - currency: Annotated[str, Field(description='ISO 4217 currency code.', pattern='^[A-Z]{3}$')] - period: Annotated[Period, Field(description='Pay period.')] - - -class JobItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - job_id: Annotated[str, Field(description='Unique identifier for this job posting.')] - title: Annotated[ - str, Field(description="Job title (e.g., 'Senior Software Engineer', 'Marketing Manager').") - ] - company_name: Annotated[str, Field(description='Hiring company or organization name.')] - description: Annotated[ - str, - Field(description='Full job description including responsibilities and qualifications.'), - ] - location: Annotated[ - str | None, - Field( - description="Job location as a display string (e.g., 'Amsterdam, NL', 'Remote', 'New York, NY'). Use 'Remote' for fully remote positions." - ), - ] = None - employment_type: Annotated[EmploymentType | None, Field(description='Type of employment.')] = ( - None - ) - experience_level: Annotated[ - ExperienceLevel | None, Field(description='Required experience level.') - ] = None - salary: Annotated[ - Salary | None, - Field(description='Salary range. Specify min and/or max with currency and period.'), - ] = None - date_posted: Annotated[ - date | None, Field(description='Date the job was posted (ISO 8601 date).') - ] = None - valid_through: Annotated[ - date | None, Field(description='Application deadline (ISO 8601 date).') - ] = None - apply_url: Annotated[AnyUrl | None, Field(description='Direct application URL.')] = None - job_functions: Annotated[ - list[str] | None, - Field( - description="Job function categories (e.g., 'engineering', 'marketing', 'sales', 'finance').", - min_length=1, - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Industry classifications (e.g., 'technology', 'healthcare', 'retail').", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description="Tags for filtering (e.g., 'remote', 'visa-sponsorship', 'equity').", - min_length=1, - ), - ] = None - assets: Annotated[ - list[offering_asset_group.OfferingAssetGroup] | None, - Field( - description="Typed creative asset pools for this job. Uses the same OfferingAssetGroup structure as offering-type catalogs. Standard group IDs: 'images_landscape' (company/role hero), 'images_vertical' (9:16 for Stories), 'logo' (company logo). Enables formats to declare typed image requirements that map unambiguously to the right asset regardless of platform.", - min_length=1, - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/keyword_delivery_metrics.py b/src/adcp/types/generated_poc/core/keyword_delivery_metrics.py deleted file mode 100644 index 438c676f6..000000000 --- a/src/adcp/types/generated_poc/core/keyword_delivery_metrics.py +++ /dev/null @@ -1,19 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/keyword_delivery_metrics.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from pydantic import Field - -from ..enums import match_type as match_type_1 -from .delivery_metrics import DeliveryMetrics - - -class KeywordDeliveryMetrics(DeliveryMetrics): - keyword: Annotated[str, Field(description='The targeted keyword')] - match_type: match_type_1.MatchType - impressions: Any - spend: Any diff --git a/src/adcp/types/generated_poc/core/keyword_target.py b/src/adcp/types/generated_poc/core/keyword_target.py deleted file mode 100644 index bd0f95b11..000000000 --- a/src/adcp/types/generated_poc/core/keyword_target.py +++ /dev/null @@ -1,21 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/keyword_target.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictFloat - -from ..enums import match_type as match_type_1 - - -class KeywordTarget(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - keyword: Annotated[str, Field(min_length=1)] - match_type: match_type_1.MatchType - bid_price: Annotated[StrictFloat | None, Field(ge=0.0)] = None diff --git a/src/adcp/types/generated_poc/core/limited_series.py b/src/adcp/types/generated_poc/core/limited_series.py deleted file mode 100644 index b89c41294..000000000 --- a/src/adcp/types/generated_poc/core/limited_series.py +++ /dev/null @@ -1,25 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/limited_series.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field - - -class LimitedSeries(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - total_installments: Annotated[ - SchemaInt, Field(description='Planned number of installments in the series', ge=1) - ] - starts: Annotated[ - AwareDatetime | None, Field(description='When the series begins (ISO 8601)') - ] = None - ends: Annotated[AwareDatetime | None, Field(description='When the series ends (ISO 8601)')] = ( - None - ) diff --git a/src/adcp/types/generated_poc/core/locale_tag.py b/src/adcp/types/generated_poc/core/locale_tag.py deleted file mode 100644 index 587349fcd..000000000 --- a/src/adcp/types/generated_poc/core/locale_tag.py +++ /dev/null @@ -1,21 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/locale_tag.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from adcp.types._scalar import ScalarStr - - -class LanguageTag(ScalarStr): - __slots__ = () - _constraints = { - 'max_length': 63, - 'min_length': 2, - 'pattern': '^(?:[a-z]{2,8}(?:-[A-Z][a-z]{3})?(?:-(?:[A-Z]{2}|[0-9]{3}))?(?:-(?:[a-z0-9]{5,8}|[0-9][a-z0-9]{3}))*(?:-[0-9a-wy-z](?:-[a-z0-9]{2,8})+)*(?:-x(?:-[a-z0-9]{1,8})+)?|x(?:-[a-z0-9]{1,8})+)$', - } - _json_schema_extra = { - 'description': 'A well-formed BCP 47 language tag used by AdCP only as language identity. Script and region may refine that identity; other valid BCP 47 subtags remain part of tag matching but do not make this a general locale-settings object. It does not determine currency, time zone, number/date formatting, market, or legal jurisdiction. The AdCP canonical wire profile requires lower-case language and variants, title-case script, and upper-case region (for example `en-US`, `zh-Hant-TW`, or `x-private`). RFC 5646 comparisons are case-insensitive and its case regularization is optional; AdCP intentionally requires this stricter single wire spelling and receivers MUST reject differently cased tags rather than silently normalizing them. The schema pattern enforces the AdCP casing profile and extension structure for commonly used tags; conforming receivers additionally validate the complete RFC 5646 grammar and registry rules. Every new AdCP field carrying BCP 47 language identity or a concrete language range MUST reference this schema instead of declaring independent string constraints.', - 'examples': ['en-US', 'es-ES', 'zh-Hant-TW', 'x-private'], - 'title': 'Language Tag', - } diff --git a/src/adcp/types/generated_poc/core/localized_creative_asset.py b/src/adcp/types/generated_poc/core/localized_creative_asset.py deleted file mode 100644 index fe80934dd..000000000 --- a/src/adcp/types/generated_poc/core/localized_creative_asset.py +++ /dev/null @@ -1,171 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/localized_creative_asset.json -# timestamp: 2026-10-04T04:53:46+00:00 - -from __future__ import annotations - -from typing import Any, Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import Field, RootModel - -from .assets.asset_union import ( - AudioAsset, - BriefAsset, - CardAsset, - CatalogAsset, - CssAsset, - DaastTrackerAsset, - HtmlAsset, - ImageAsset, - JavascriptAsset, - MarkdownAsset, - PixelTrackerAsset, - TextAsset, - UrlAsset, - VastTrackerAsset, - VideoAsset, - WebhookAsset, - ZipAsset, -) - - -class LocalizedCreativeAsset1(AdCPBaseModel): - pass - - -class LocalizedCreativeAsset3(VideoAsset, LocalizedCreativeAsset1): - pass - - -class LocalizedCreativeAsset4(AudioAsset, LocalizedCreativeAsset1): - pass - - -class LocalizedCreativeAsset7(TextAsset, LocalizedCreativeAsset1): - pass - - -class LocalizedCreativeAsset9(HtmlAsset, LocalizedCreativeAsset1): - pass - - -class LocalizedCreativeAsset10(JavascriptAsset, LocalizedCreativeAsset1): - pass - - -class LocalizedCreativeAsset11(ZipAsset, LocalizedCreativeAsset1): - pass - - -class LocalizedCreativeAsset12(WebhookAsset, LocalizedCreativeAsset1): - pass - - -class LocalizedCreativeAsset13(CssAsset, LocalizedCreativeAsset1): - pass - - -class LocalizedCreativeAsset14(LocalizedCreativeAsset1): - pass - - -class LocalizedCreativeAsset15(MarkdownAsset, LocalizedCreativeAsset1): - pass - - -class LocalizedCreativeAsset18(LocalizedCreativeAsset14): - pass - - -class LocalizedCreativeAsset2(ImageAsset, LocalizedCreativeAsset1): - pass - - -class LocalizedCreativeAsset5(LocalizedCreativeAsset14): - pass - - -class LocalizedCreativeAsset8(UrlAsset, LocalizedCreativeAsset1): - pass - - -class LocalizedCreativeAsset16(BriefAsset, LocalizedCreativeAsset1): - pass - - -class LocalizedCreativeAsset17(CatalogAsset, LocalizedCreativeAsset1): - pass - - -class LocalizedCreativeAsset19(CardAsset, LocalizedCreativeAsset1): - pass - - -class LocalizedCreativeAsset20(PixelTrackerAsset, LocalizedCreativeAsset1): - pass - - -class LocalizedCreativeAsset21(VastTrackerAsset, LocalizedCreativeAsset1): - pass - - -class LocalizedCreativeAsset22(DaastTrackerAsset, LocalizedCreativeAsset1): - pass - - -class LocalizedCreativeAsset( - RootModel[ - LocalizedCreativeAsset2 - | LocalizedCreativeAsset3 - | LocalizedCreativeAsset4 - | LocalizedCreativeAsset5 - | LocalizedCreativeAsset7 - | LocalizedCreativeAsset8 - | LocalizedCreativeAsset9 - | LocalizedCreativeAsset10 - | LocalizedCreativeAsset11 - | LocalizedCreativeAsset12 - | LocalizedCreativeAsset13 - | LocalizedCreativeAsset14 - | LocalizedCreativeAsset15 - | LocalizedCreativeAsset16 - | LocalizedCreativeAsset17 - | LocalizedCreativeAsset18 - | LocalizedCreativeAsset19 - | LocalizedCreativeAsset20 - | LocalizedCreativeAsset21 - | LocalizedCreativeAsset22 - ] -): - root: Annotated[ - LocalizedCreativeAsset2 - | LocalizedCreativeAsset3 - | LocalizedCreativeAsset4 - | LocalizedCreativeAsset5 - | LocalizedCreativeAsset7 - | LocalizedCreativeAsset8 - | LocalizedCreativeAsset9 - | LocalizedCreativeAsset10 - | LocalizedCreativeAsset11 - | LocalizedCreativeAsset12 - | LocalizedCreativeAsset13 - | LocalizedCreativeAsset14 - | LocalizedCreativeAsset15 - | LocalizedCreativeAsset16 - | LocalizedCreativeAsset17 - | LocalizedCreativeAsset18 - | LocalizedCreativeAsset19 - | LocalizedCreativeAsset20 - | LocalizedCreativeAsset21 - | LocalizedCreativeAsset22, - Field( - description='An asset inside a materialized creative locale variant. Text and markdown assets may omit language when they make no language claim. When language is present, it MUST use the shared AdCP BCP 47 wire profile; conformance additionally requires exact equality with the enclosing variant locale.', - title='Localized Creative Asset', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) diff --git a/src/adcp/types/generated_poc/core/macro_bearing_url.py b/src/adcp/types/generated_poc/core/macro_bearing_url.py deleted file mode 100644 index edadb821a..000000000 --- a/src/adcp/types/generated_poc/core/macro_bearing_url.py +++ /dev/null @@ -1,41 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/macro_bearing_url.json -# timestamp: 2026-10-04T00:53:27+00:00 - -from __future__ import annotations - -from adcp.types._scalar import ScalarStr - -from typing import Annotated - -from pydantic import AnyUrl, Field, RootModel - - -class MacroBearingUrl3(RootModel[AnyUrl]): - root: Annotated[ - AnyUrl, - Field( - description='Backward-compatible URL value that preserves the existing `uri-template` branch (including deep links, protocol-relative references, and RFC 6570 host variables) while additionally accepting absolute HTTP(S) URLs with byte-preserved macro delimiters not legal in a strict URI, including `%%...%%`, `[...]`, and `${...}`. Macro-bearing HTTP(S) URLs require a valid lexical authority and valid percent triplets; an AdCP verifier replaces each recognized token with an unreserved sentinel and validates the resulting absolute URI. Token semantics are established only by `macro_declarations`.', - title='Macro-bearing URL', - ), - ] - - -class MacroBearingUrl4(ScalarStr): - __slots__ = () - _constraints = { - 'pattern': '^https?://(?:[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?|\\[[0-9A-Fa-f:.]+\\])(?::[0-9]{1,5})?(?:[/?#](?:[^%\\s\\u0000-\\u001F\\u007F"<>`\\\\]|%[0-9A-Fa-f]{2}|%%[A-Za-z0-9_.:-]+%%)*)?$', - } - _json_schema_extra = { - 'description': 'Backward-compatible URL value that preserves the existing `uri-template` branch (including deep links, protocol-relative references, and RFC 6570 host variables) while additionally accepting absolute HTTP(S) URLs with byte-preserved macro delimiters not legal in a strict URI, including `%%...%%`, `[...]`, and `${...}`. Macro-bearing HTTP(S) URLs require a valid lexical authority and valid percent triplets; an AdCP verifier replaces each recognized token with an unreserved sentinel and validates the resulting absolute URI. Token semantics are established only by `macro_declarations`.', - 'title': 'Macro-bearing URL', - } - - -MacroBearingUrl = Annotated[ - str | MacroBearingUrl3 | MacroBearingUrl4, - Field( - description='Backward-compatible URL value that preserves the existing `uri-template` branch (including deep links, protocol-relative references, and RFC 6570 host variables) while additionally accepting absolute HTTP(S) URLs with byte-preserved macro delimiters not legal in a strict URI, including `%%...%%`, `[...]`, and `${...}`. Macro-bearing HTTP(S) URLs require a valid lexical authority and valid percent triplets; an AdCP verifier replaces each recognized token with an unreserved sentinel and validates the resulting absolute URI. Token semantics are established only by `macro_declarations`.', - title='Macro-bearing URL', - ), -] diff --git a/src/adcp/types/generated_poc/core/macro_declaration.py b/src/adcp/types/generated_poc/core/macro_declaration.py deleted file mode 100644 index 5d8879a08..000000000 --- a/src/adcp/types/generated_poc/core/macro_declaration.py +++ /dev/null @@ -1,123 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/macro_declaration.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, ConfigDict, Field, StrictBool - -from .assets import asset_union - - -class Field1(StrEnum): - url = 'url' - markup = 'markup' - content = 'content' - ad_request_url = 'ad_request_url' - clickthrough_url = 'clickthrough_url' - - -class Location(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - field: Annotated[Field1, Field(description='Asset field containing this occurrence.')] - occurrence: Annotated[ - SchemaInt, - Field( - description='Zero-based occurrence of this exact token within the named field.', ge=0 - ), - ] - context: asset_union.MacroValueContext - - -class UnavailableBehavior(StrEnum): - preserve = 'preserve' - omit_parameter = 'omit_parameter' - dialect_sentinel = 'dialect_sentinel' - reject = 'reject' - - -class MacroDeclaration(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - declaration_id: Annotated[ - str, - Field( - description='Identifier unique within the enclosing asset, used to correlate validation results.', - min_length=1, - pattern='^[A-Za-z0-9_-]+$', - ), - ] - token: Annotated[ - str, - Field( - description='Exact byte-preserved token expression at this occurrence, including delimiters.', - min_length=1, - ), - ] - dialect: asset_union.MacroDialect - dialect_namespace: Annotated[ - AnyUrl | None, - Field( - description='Authority-controlled registry or mapping URI. Required for IAB VAST, IAB DAAST, and vendor dialects.' - ), - ] = None - dialect_revision: Annotated[ - str | None, - Field( - description='Immutable registry/mapping revision, release, commit, or digest. Required for IAB VAST, IAB DAAST, and vendor dialects.', - min_length=1, - ), - ] = None - dialect_semantic: Annotated[ - str, - Field( - description='Exact semantic identifier in the cited dialect, such as `CACHEBUSTING`, `PLAYERSTATE`, or a documented vendor name. This is not inferred from token spelling.', - min_length=1, - ), - ] - mapping_status: asset_union.MacroMappingStatus - universal_semantic: Annotated[ - asset_union.UniversalMacro | None, - Field( - description='Verified AdCP universal meaning. Present only when mapping_status is `verified_universal`.' - ), - ] = None - operation: asset_union.MacroProcessingOperation - performed_by: Annotated[ - asset_union.MacroResolver | None, - Field( - description='Actor authorized to perform the declared translation or value resolution. Omitted for preservation.' - ), - ] = None - translation_target: Annotated[ - asset_union.MacroTranslationTarget | None, - Field( - description='Required only for `translate_to_native`. The output token contract that replaces this declaration after translation.' - ), - ] = None - location: Location - encoding: Annotated[ - asset_union.MacroEncoding, - Field( - description='Encoding for the concrete value. Translation/preservation uses `none` at depth zero.' - ), - ] - required: Annotated[ - StrictBool, - Field( - description='Whether terminal delivery must fail if the declared operation cannot complete.' - ), - ] - unavailable_behavior: Annotated[ - UnavailableBehavior, - Field( - description='Action when the responsible actor has no value. Omission is legal only for a complete URL query-value occurrence; dialect sentinels require a cited dialect rule.' - ), - ] diff --git a/src/adcp/types/generated_poc/core/macro_encoding.py b/src/adcp/types/generated_poc/core/macro_encoding.py deleted file mode 100644 index 6d378c82e..000000000 --- a/src/adcp/types/generated_poc/core/macro_encoding.py +++ /dev/null @@ -1,25 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/macro_encoding.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field - - -class Kind(StrEnum): - none = 'none' - rfc3986 = 'rfc3986' - iab_vast_uri = 'iab_vast_uri' - - -class MacroEncoding(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Kind - depth: Annotated[SchemaInt, Field(ge=0)] diff --git a/src/adcp/types/generated_poc/core/macro_resolution_capability.py b/src/adcp/types/generated_poc/core/macro_resolution_capability.py deleted file mode 100644 index de4877035..000000000 --- a/src/adcp/types/generated_poc/core/macro_resolution_capability.py +++ /dev/null @@ -1,47 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/macro_resolution_capability.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - -from ..enums import macro_dialect, macro_resolver, macro_value_context, universal_macro -from . import macro_encoding, macro_translation_target - - -class MappingStatus(StrEnum): - verified_universal = 'verified_universal' - dialect_defined = 'dialect_defined' - - -class Operation(StrEnum): - translate_to_native = 'translate_to_native' - resolve_value = 'resolve_value' - - -class MacroProcessingCapability(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - dialect: macro_dialect.MacroDialectFamily - dialect_namespace: AnyUrl | None = None - dialect_revision: Annotated[str | None, Field(min_length=1)] = None - dialect_semantic: Annotated[str, Field(min_length=1)] - mapping_status: MappingStatus - universal_semantic: universal_macro.UniversalMacro | None = None - operation: Operation - performed_by: macro_resolver.MacroProcessingActor - supported_contexts: Annotated[list[macro_value_context.MacroValueContext], Field(min_length=1)] - supported_encodings: Annotated[ - list[macro_encoding.MacroEncoding] | None, - Field( - description='Exact supported encoding profiles, not a maximum depth. Required for value resolution.', - min_length=1, - ), - ] = None - translation_target: macro_translation_target.MacroTranslationTarget | None = None diff --git a/src/adcp/types/generated_poc/core/macro_resolution_result.py b/src/adcp/types/generated_poc/core/macro_resolution_result.py deleted file mode 100644 index 5529952a4..000000000 --- a/src/adcp/types/generated_poc/core/macro_resolution_result.py +++ /dev/null @@ -1,64 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/macro_resolution_result.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, StrictBool - -from ..enums import ( - macro_dialect, - macro_mapping_status, - macro_processing_operation, - macro_resolution_reason, - macro_resolver, - universal_macro, -) -from . import macro_encoding - - -class UnavailableBehavior(StrEnum): - preserve = 'preserve' - omit_parameter = 'omit_parameter' - dialect_sentinel = 'dialect_sentinel' - reject = 'reject' - - -class Status(StrEnum): - resolvable = 'resolvable' - preserved_for_downstream = 'preserved_for_downstream' - unsupported = 'unsupported' - ambiguous = 'ambiguous' - - -class MacroResolutionResult(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - declaration_id: Annotated[str, Field(min_length=1)] - asset_path: Annotated[ - str, Field(description='JSON Pointer to the asset carrying the declaration.', pattern='^/') - ] - token: Annotated[str, Field(min_length=1)] - dialect: macro_dialect.MacroDialectFamily - dialect_namespace: AnyUrl | None = None - dialect_revision: Annotated[str | None, Field(min_length=1)] = None - dialect_semantic: Annotated[str, Field(min_length=1)] - mapping_status: macro_mapping_status.MacroMappingStatus - universal_semantic: universal_macro.UniversalMacro | None = None - operation: macro_processing_operation.MacroProcessingOperation - performed_by: macro_resolver.MacroProcessingActor | None = None - requested_encoding: macro_encoding.MacroEncoding - required: StrictBool - unavailable_behavior: UnavailableBehavior - status: Status - reason: macro_resolution_reason.MacroResolutionReason - matched_encodings: Annotated[ - list[macro_encoding.MacroEncoding] | None, - Field(description='Exact advertised profiles considered for this match, when relevant.'), - ] = None - message: Annotated[str | None, Field(min_length=1)] = None diff --git a/src/adcp/types/generated_poc/core/macro_translation_target.py b/src/adcp/types/generated_poc/core/macro_translation_target.py deleted file mode 100644 index e3d4143ed..000000000 --- a/src/adcp/types/generated_poc/core/macro_translation_target.py +++ /dev/null @@ -1,29 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/macro_translation_target.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - -from ..enums import macro_dialect, macro_mapping_status, macro_resolver, universal_macro -from . import macro_encoding - - -class MacroTranslationTarget(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - token: Annotated[str, Field(min_length=1)] - dialect: macro_dialect.MacroDialectFamily - dialect_namespace: AnyUrl | None = None - dialect_revision: Annotated[str | None, Field(min_length=1)] = None - dialect_semantic: Annotated[str, Field(min_length=1)] - mapping_status: macro_mapping_status.MacroMappingStatus - universal_semantic: universal_macro.UniversalMacro | None = None - next_operation: Literal['resolve_value'] = 'resolve_value' - performed_by: macro_resolver.MacroProcessingActor - encoding: macro_encoding.MacroEncoding diff --git a/src/adcp/types/generated_poc/core/material_deadline.py b/src/adcp/types/generated_poc/core/material_deadline.py deleted file mode 100644 index 03a4deab2..000000000 --- a/src/adcp/types/generated_poc/core/material_deadline.py +++ /dev/null @@ -1,32 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/material_deadline.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - - -class MaterialDeadline(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - stage: Annotated[ - str, - Field( - description="Submission stage identifier. Use 'draft' for materials that need seller processing and 'final' for production-ready assets. Sellers may define additional stages.", - examples=['draft', 'final'], - ), - ] - due_at: Annotated[ - AwareDatetime, Field(description='When materials for this stage are due (ISO 8601)') - ] - label: Annotated[ - str | None, - Field( - description="What the seller needs at this stage (e.g., 'Talking points and brand guidelines', 'Press-ready PDF with bleed')" - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/mcp_webhook_payload.py b/src/adcp/types/generated_poc/core/mcp_webhook_payload.py deleted file mode 100644 index 9fad1fac0..000000000 --- a/src/adcp/types/generated_poc/core/mcp_webhook_payload.py +++ /dev/null @@ -1,100 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/mcp_webhook_payload.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - -from ..enums import adcp_protocol, task_status -from ..enums import task_type as task_type_1 -from . import async_response_data - - -class McpWebhookPayload(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - idempotency_key: Annotated[ - str, - Field( - description='Sender-generated delivery key stable across RFC 8785 JCS-equivalent retries of the complete authenticated webhook payload. Publishers MUST generate a cryptographically random value (UUID v4 recommended), bind it immutably to the first canonical payload for the advertised delivery retry horizon, and use a fresh key for a changed payload or distinct delivery. Receivers scope the binding to the authenticated sender identity. Same key plus identical payload while active returns retryable 503; after durable acknowledgement it returns 2xx; same key plus a different canonical payload returns non-retryable 409. This is the transport delivery identity, not request idempotency or stable logical notification identity.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - notification_id: Annotated[ - str | None, - Field( - description='Optional event-layer identifier for one logical notification. Stable across re-emissions of the same logical event and distinct from the per-delivery `idempotency_key`. For terminal task webhooks, the authoritative terminal identity remains the authenticated seller plus the bound task_id; when notification_id is present, different delivery keys carrying the same value are re-emissions and MUST NOT republish terminal effects. For other event families, population and repair identity remain event-shape-dependent (see notification-type.json enumDescriptions): impairment aliases impairment_id, creative and account notifications use transition identifiers, wholesale events alias event.event_id, and capability changes use a revision-event identifier. Point-in-time delivery events (scheduled, final, delayed, adjusted, window_update) omit this field and dedupe by idempotency_key plus their delivery-report identity. Charset is constrained to `[A-Za-z0-9_.:-]`.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - operation_id: Annotated[ - str, - Field( - description='Client-generated correlation identifier for the operation that produced this webhook. Buyers supply this value at webhook registration time via `push_notification_config.operation_id`; sellers MUST echo it verbatim in every webhook payload. Sellers MUST NOT derive `operation_id` by parsing `push_notification_config.url` — the URL is opaque to the seller. Receivers MAY dispatch endpoints by URL path or query string, but MUST correlate the operation using this payload field, not URL-derived values. See [Webhooks — Operation IDs and URL templates](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates) for the full normative wire contract.' - ), - ] - task_id: Annotated[ - str, - Field( - description='Unique identifier for this task. Use this to correlate webhook notifications with the original task submission.' - ), - ] - task_type: Annotated[ - task_type_1.TaskType, - Field( - description='Type of AdCP operation that triggered this webhook. Enables webhook handlers to route to appropriate processing logic.' - ), - ] - protocol: Annotated[ - adcp_protocol.AdcpProtocol | None, - Field( - description='AdCP protocol this task belongs to. Helps classify the operation type at a high level.' - ), - ] = None - status: Annotated[ - task_status.TaskStatus, - Field( - description='Current task status. Webhooks are triggered for status changes after initial submission.' - ), - ] - timestamp: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when this logical webhook delivery was first generated. Every retry under the same idempotency_key MUST repeat this exact body value, along with every other payload member; only transport/signature metadata such as a fresh RFC 9421 nonce or created parameter may change between attempts.' - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the current task state. Provides context about what happened and what action may be needed.' - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Compatibility metadata copied from the originating response when present. This value alone is not continuation authority and MUST NOT be used to resume input-required or auth-required work or to select session state.' - ), - ] = None - token: Annotated[ - str | None, - Field( - description='Authentication token echoed verbatim from [`PushNotificationConfig.token`](/schemas/core/push-notification-config.json). Receivers that configured a token MUST compare it to this value to validate request authenticity, and SHOULD use a constant-time equality check to mitigate timing attacks. Absent when no token was configured at registration. Length bounds mirror the config-side field — receivers MAY reject payloads whose token length falls outside the configured range as a defensive check, provided the length check is performed only after the configured token is known to exist for this subscription, and the length comparison is not used as a fast-path to short-circuit the constant-time compare on equal-length inputs. Receivers MUST NOT treat absence as an authenticity failure when no token was configured.', - max_length=4096, - min_length=16, - ), - ] = None - result: Annotated[ - async_response_data.AdcpAsyncResponseData | None, - Field( - description='Task-specific payload matching the status. For completed/failed, contains the full task response. For working/input-required/submitted, contains status-specific data. This is the data layer that AdCP specs - same structure used in A2A status.message.parts[].data.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/measurement_readiness.py b/src/adcp/types/generated_poc/core/measurement_readiness.py deleted file mode 100644 index 79db1b455..000000000 --- a/src/adcp/types/generated_poc/core/measurement_readiness.py +++ /dev/null @@ -1,50 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/measurement_readiness.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..enums import assessment_status, event_type -from . import diagnostic_issue - - -class MeasurementReadiness(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - assessment_status.AssessmentStatus, - Field( - description="Overall measurement readiness level for this product given the buyer's event setup. 'insufficient' means the product cannot optimize effectively with the current setup." - ), - ] - required_event_types: Annotated[ - list[event_type.EventType] | None, - Field( - description='Event types this product needs for effective optimization. Buyers should ensure their event sources cover these types.', - min_length=1, - ), - ] = None - missing_event_types: Annotated[ - list[event_type.EventType] | None, - Field( - description='Event types this product requires that the buyer has not configured. Empty or absent when all required types are covered.' - ), - ] = None - issues: Annotated[ - list[diagnostic_issue.DiagnosticIssue] | None, - Field( - description='Actionable issues preventing full measurement readiness. Sellers should limit to the top 3-5 most actionable items. Buyer agents should sort by severity rather than relying on array position.' - ), - ] = None - notes: Annotated[ - str | None, - Field( - description='Seller explanation of the readiness assessment, recommendations for improvement, or context about what the buyer needs to change.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/measurement_terms.py b/src/adcp/types/generated_poc/core/measurement_terms.py deleted file mode 100644 index 5e8b7144e..000000000 --- a/src/adcp/types/generated_poc/core/measurement_terms.py +++ /dev/null @@ -1,87 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/measurement_terms.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictFloat - -from ..enums import makegood_remedy -from . import brand_ref - - -class MakegoodPolicy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - available_remedies: Annotated[ - list[makegood_remedy.MakegoodRemedy], - Field( - description='Remedy types the seller supports. Ordered by seller preference (first = preferred). Seller proposes from this list when a breach occurs; buyer accepts or disputes.', - min_length=1, - ), - ] - - -class BillingMeasurement(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - brand_ref.BrandReference, - Field( - description="Vendor whose measurement of the billing metric is authoritative for invoicing (e.g., { domain: 'campaignmanager.google.com' } for buyer's DCM, { domain: 'admanager.google.com' } for seller's GAM)." - ), - ] - max_variance_percent: Annotated[ - StrictFloat | None, - Field( - description="Maximum acceptable variance between the billing vendor's count and the other party's count before resolution is triggered (e.g., 10 means a 10% divergence triggers review).", - ge=0.0, - lt=100.0, - ), - ] = None - measurement_window: Annotated[ - str | None, - Field( - description="Which measurement maturation stage the billing metric is reconciled against. References a window_id from the product's reporting_capabilities.measurement_windows. Examples: 'c7' for broadcast TV guarantees (live + 7 days DVR), 'final' for DOOH after IVT/fraud-check processing, 'post_sivt' for digital after sophisticated invalid-traffic filtering, 'downloads_30d' for podcast. When absent, billing is based on the seller's standard reporting without windowed maturation.", - examples=[ - 'live', - 'c3', - 'c7', - 'tentative', - 'final', - 'post_ivt', - 'post_sivt', - 'downloads_30d', - ], - ), - ] = None - finalization_deadline_hours: Annotated[ - SchemaInt | None, - Field( - description='Maximum hours by which the authoritative party MUST publish a final record (`is_final: true` / `finalized_at` on `get_media_buy_delivery`, or `final: true` / `finalized_at` on `report_usage`). **Anchor:** when `measurement_window` is set, hours are counted from the close of that window (e.g., 240h after `c7` close = ~10 days after the 7-day DVR accumulation completes); when `measurement_window` is absent, hours are counted from `reporting_period.end`. Picking a single anchor avoids ambiguity for windowed channels where `reporting_period.end` and window close differ by days. The deadline applies to whichever party is named in `vendor` — seller, buyer, or third-party vendor — symmetrically. When the deadline elapses without a final record, the counterparty MAY fall back to its own attestation for invoicing (seller falls back to seller-attested numbers via `get_media_buy_delivery`; buyer falls back to a buyer-attested `report_usage` push), and the breach is treated like any other measurement-terms breach under `makegood_policy`. Absent means no contractual deadline — finalization is best-effort and disagreements resolve out of band.', - ge=0, - ), - ] = None - - -class MeasurementTerms(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - billing_measurement: Annotated[ - BillingMeasurement | None, - Field( - description="Which vendor's value for the billing metric governs invoicing. The billing metric is determined by the pricing_model on the selected pricing_option (e.g., impressions for CPM, completed views for CPCV, commissionable_value for revenue_share)." - ), - ] = None - makegood_policy: Annotated[ - MakegoodPolicy | None, - Field( - description='Remedies available when a performance standard or billing measurement variance is breached. Seller declares which remedy types they support. When a breach occurs, the seller proposes a remedy from this menu; the buyer accepts or disputes.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/measurement_window.py b/src/adcp/types/generated_poc/core/measurement_window.py deleted file mode 100644 index 073da0fae..000000000 --- a/src/adcp/types/generated_poc/core/measurement_window.py +++ /dev/null @@ -1,58 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/measurement_window.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictBool - - -class MeasurementWindow(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - window_id: Annotated[ - str, - Field( - description="Identifier for this maturation stage. Standard broadcast values: 'live' (real-time viewers only), 'c3' (live + 3 days time-shifted), 'c7' (live + 7 days time-shifted). Standard values for other channels include 'tentative' (provisional data available quickly), 'final' (post-processing certified data), 'post_ivt' (digital after invalid-traffic filtering), 'post_sivt' (digital after sophisticated-IVT filtering), 'downloads_7d' / 'downloads_30d' (podcast download maturation). Sellers may define custom IDs.", - examples=['live', 'c3', 'c7', 'tentative', 'final', 'post_ivt', 'downloads_30d'], - max_length=50, - ), - ] - description: Annotated[ - str | None, - Field( - description='Human-readable description of what this window measures', - examples=[ - 'Live broadcast impressions only', - 'Live plus 7 days of time-shifted viewing', - 'Tentative plays before IVT and fraud-check processing', - 'Final plays after IVT and fraud-check processing', - 'Impressions after sophisticated invalid-traffic filtering', - ], - max_length=500, - ), - ] = None - duration_days: Annotated[ - SchemaInt, - Field( - description='Number of days of accumulation included in this window before processing begins. For broadcast, this is DVR accumulation (0 = live only, 3 = live + 3 days DVR, 7 = live + 7 days DVR). For channels without an accumulation period (DOOH tentative→final, digital IVT filtering), this is 0 — maturation is entirely vendor processing time captured in expected_availability_days.', - ge=0, - ), - ] - expected_availability_days: Annotated[ - SchemaInt | None, - Field( - description="Expected number of days after delivery before this window's data is available from the measurement vendor. Captures accumulation time plus vendor processing time. Examples: broadcast C7 from VideoAmp ~22 days (7-day accumulation + ~15-day processing); DOOH tentative plays same-day; DOOH final (post-IVT/fraud-check) ~1 day; digital post-SIVT ~2–3 days.", - ge=0, - ), - ] = None - is_guarantee_basis: Annotated[ - StrictBool | None, - Field( - description="Whether this window is the basis for delivery guarantees, reconciliation, and invoicing. A product typically has one guarantee basis window (e.g., C7 for most US broadcast, post-IVT final for DOOH). Buyers reconcile against the guarantee basis window's final numbers." - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/media_buy.py b/src/adcp/types/generated_poc/core/media_buy.py deleted file mode 100644 index 0fc9af67a..000000000 --- a/src/adcp/types/generated_poc/core/media_buy.py +++ /dev/null @@ -1,178 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/media_buy.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat - -from ..enums import canceled_by as canceled_by_1 -from ..enums import media_buy_health, media_buy_status -from ..enums import pacing as pacing_1 -from . import account as account_1 -from . import bidding_policy -from . import budget_allocation as budget_allocation_1 -from . import business_entity -from . import context as context_1 -from . import ext as ext_1 -from . import impairment, media_buy_frequency_cap, package - - -class Cancellation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - canceled_at: Annotated[ - AwareDatetime, Field(description='ISO 8601 timestamp when this media buy was canceled.') - ] - canceled_by: Annotated[ - canceled_by_1.CanceledBy, - Field( - description="Which party initiated the cancellation. 'buyer' when canceled through control_media_buy or the 3.x update_media_buy facade; 'seller' when the seller cancels (e.g., policy violation, inventory withdrawal)." - ), - ] - reason: Annotated[ - str | None, - Field(description='Reason provided when the media buy was canceled.', max_length=500), - ] = None - - -class MediaBuy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_buy_id: Annotated[str, Field(description="Seller's unique identifier for the media buy")] - name: Annotated[ - str | None, - Field( - description='Human-readable name for this media buy, shared by buyer and seller for trafficking UI display and operational communication. Sellers MUST include the persisted name on read surfaces such as get_media_buys when the media buy was created through AdCP with name. Sellers MAY omit name for media buys created outside AdCP or created without name. This display label is not an identifier or financial reference.', - max_length=255, - min_length=1, - pattern='\\S', - ), - ] = None - accepted_proposal_id: Annotated[ - str | None, - Field( - description='Current accepted commercial snapshot. Compact-lifecycle buyers pass this ID to refine_proposals after restart or handoff. Updated atomically when an amendment or negotiated cancellation is accepted.', - max_length=255, - min_length=1, - ), - ] = None - accepted_proposal_terms_digest: Annotated[ - str | None, - Field( - description='Digest of the current accepted proposal commercial_terms, allowing buyers and governance agents to verify the recovered snapshot.', - pattern='^sha256:[A-Za-z0-9_-]{43}$', - ), - ] = None - account: Annotated[ - account_1.Account | None, Field(description='Account billed for this media buy') - ] = None - status: media_buy_status.MediaBuyStatus - health: Annotated[ - media_buy_health.MediaBuyHealth | None, - Field( - description="Aggregate health based on open impairments[]. Orthogonal to status — a paused, pending, or active buy can each be impaired. Defaults to 'ok' when impairments[] is empty." - ), - ] = media_buy_health.MediaBuyHealth.ok - impairments: Annotated[ - list[impairment.Impairment] | None, - Field( - description="Open impairments — upstream dependency state changes that affect delivery for at least one package on this buy. Empty when health is 'ok'. Sellers MUST add an entry on next sync/poll response after a referenced resource transitions to an offline state, and MUST remove the entry (flipping health to 'ok' when the array empties) when the resource returns to a serviceable state. Staleness budget: the snapshot MUST reflect the impairment within 5 minutes of impairment.observed_at regardless of buyer poll cadence — sellers cannot rely on rare buyer polls to defer write propagation. See impairment.coherence assertion for the cross-resource invariant." - ), - ] = None - rejection_reason: Annotated[ - str | None, - Field( - description="Reason provided by the seller when status is 'rejected'. Present only when status is 'rejected'." - ), - ] = None - confirmed_at: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the seller committed to this media buy. May be null until seller commitment occurs in deferred/manual approval flows. Once populated, remains stable through later pause, resume, activation, completion, cancellation, and reporting transitions.' - ), - ] - cancellation: Annotated[ - Cancellation | None, - Field(description="Cancellation metadata. Present only when status is 'canceled'."), - ] = None - total_budget: Annotated[ - StrictFloat, Field(description='Hard aggregate lifetime budget amount', ge=0.0) - ] - daily_budget_cap: Annotated[ - StrictFloat | None, - Field( - description='Current hard aggregate spend ceiling per calendar day. Sellers MUST echo this whenever an aggregate daily cap is set. It bounds total media-buy spend without allocating or reserving spend for packages.', - ge=0.0, - ), - ] = None - frequency_cap: Annotated[ - media_buy_frequency_cap.MediaBuyFrequencyCap | None, - Field( - description='Current hard MediaBuy-level cap. Sellers MUST echo it whenever set. Its counter aggregates exposures across all participating packages; each package targeting_overlay.frequency_cap remains independently binding.' - ), - ] = None - budget_cap_timezone: Annotated[ - str | None, - Field( - description='IANA timezone defining the shared calendar-day boundary for every aggregate and package daily cap on this media buy. Sellers MUST echo it whenever any daily cap is set.' - ), - ] = None - currency: Annotated[ - str | None, - Field( - description="Single ISO 4217 denomination for total_budget, every package budget/minimum, and every canonical BiddingPolicy monetary field. Every package's selected pricing option MUST declare this currency; packages requiring another currency belong in a separate media buy.", - pattern='^[A-Z]{3}$', - ), - ] = None - budget_allocation: Annotated[ - budget_allocation_1.BudgetAllocation | None, - Field( - description='Accepted cross-package budget allocation configuration. Omitted means fixed allocation for legacy buys.' - ), - ] = None - pacing: Annotated[ - pacing_1.Pacing | None, - Field( - description='Aggregate pacing strategy for total_budget across the media-buy flight.' - ), - ] = None - bidding: Annotated[ - bidding_policy.BiddingPolicy | None, - Field( - description='Media-buy-authored bidding policy. This is the complete default inherited by packages that omit package.bidding; `{automatic:true}` is an explicit authored automatic policy. In seller-optimized mode, cost_per/roas bind to the primary budget_allocation optimization goal. In fixed mode, inherited cost_per requires compatible package primary-goal result units and inherited roas requires value-bearing primary goals. Monetary fields use media_buy.currency; every affected pricing option MUST declare the same currency. Package overrides are permitted only where advertised; conflicts MUST be rejected atomically with BIDDING_PLACEMENT_CONFLICT.' - ), - ] = None - packages: Annotated[ - list[package.Package], Field(description='Array of packages within this media buy') - ] - context: Annotated[ - context_1.ContextObject | None, - Field( - description='Opaque media-buy-level correlation data echoed unchanged from the create_media_buy request. Sellers MUST include persisted context on read surfaces such as get_media_buys when the media buy was created through AdCP with context, so buyers can reconcile seller-assigned media_buy_id values with their own tracking state. Sellers MAY omit context for media buys created outside AdCP or created without context. Sellers MUST NOT parse this object for business logic.' - ), - ] = None - invoice_recipient: Annotated[ - business_entity.BusinessEntity | None, - Field( - description="Per-buy override for who receives the invoice. When provided, the seller invoices this entity instead of the account's default billing_entity. The seller MUST validate the invoice recipient is authorized for this account. When governance_agents are configured, the seller MUST include invoice_recipient in the check_governance request." - ), - ] = None - creative_deadline: Annotated[ - AwareDatetime | None, Field(description='ISO 8601 timestamp for creative upload deadline') - ] = None - revision: Annotated[ - SchemaInt, - Field( - description='Monotonically increasing optimistic concurrency token. Incremented on every mutating state change or update; reads, validation-only calls, and exact idempotency replays do not increment it. Callers SHOULD include this in update_media_buy requests intended to change state — when provided, sellers MUST reject with CONFLICT if the revision does not match the current value, and MUST enforce that comparison atomically with the write.', - ge=1, - ), - ] - created_at: Annotated[AwareDatetime | None, Field(description='Creation timestamp')] = None - updated_at: Annotated[AwareDatetime | None, Field(description='Last update timestamp')] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/media_buy_available_action.py b/src/adcp/types/generated_poc/core/media_buy_available_action.py deleted file mode 100644 index e1527e69b..000000000 --- a/src/adcp/types/generated_poc/core/media_buy_available_action.py +++ /dev/null @@ -1,63 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/media_buy_available_action.json -# timestamp: 2026-09-14T14:16:02+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..enums import media_buy_action_mode -from . import ( - applicable_package_id, - media_buy_available_action_id, - media_buy_change_term_id, - media_buy_legacy_terms_ref, - sla_window, -) - - -class Task(StrEnum): - control_media_buy = 'control_media_buy' - refine_proposals = 'refine_proposals' - sync_creatives = 'sync_creatives' - - -class MediaBuyAvailableAction(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - action: Annotated[ - media_buy_available_action_id.MediaBuyAvailableActionId, - Field(description='The action identifier.'), - ] - mode: Annotated[ - media_buy_action_mode.MediaBuyActionMode, - Field( - description='The single mode that applies right now on this buy for this action. Singular because the buy has a concrete state, exactly one mode applies. Buyer SDKs branch on this to decide whether to expect a synchronous response, conditional handling, or an asynchronous approval callback.' - ), - ] - task: Annotated[ - Task | None, - Field( - description='Compact-lifecycle task for this resolved action: operational control, commercial refinement, or creative lifecycle mutation.' - ), - ] = None - sla: Annotated[ - sla_window.SlaWindow | None, - Field( - description='Optional SLA commitment for this action on this buy. Absence means no commitment, not zero commitment.' - ), - ] = None - change_term_id: media_buy_change_term_id.MediaBuyChangeTermId | None = None - terms_ref: media_buy_legacy_terms_ref.MediaBuyTermsReference | None = None - applicable_package_ids: Annotated[ - list[applicable_package_id.ApplicablePackageId] | None, - Field( - description='For a package-scoped action, the exact packages currently eligible. Omission means every relevant package. Root actions omit this field.', - min_length=1, - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/media_buy_available_action_id.py b/src/adcp/types/generated_poc/core/media_buy_available_action_id.py deleted file mode 100644 index d35554f9f..000000000 --- a/src/adcp/types/generated_poc/core/media_buy_available_action_id.py +++ /dev/null @@ -1,20 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/media_buy_available_action_id.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from pydantic import Field - -from ..enums import media_buy_valid_action - - -MediaBuyAvailableActionId = Annotated[ - media_buy_valid_action.MediaBuyValidAction | Literal['update_media_buy_frequency_cap'], - Field( - description='An action identifier for the structured available_actions surface. It contains every legacy valid_actions value plus structured-only actions introduced after that deprecated flat surface.', - title='Media Buy Available Action ID', - ), -] diff --git a/src/adcp/types/generated_poc/core/media_buy_change_term_id.py b/src/adcp/types/generated_poc/core/media_buy_change_term_id.py deleted file mode 100644 index 073a73782..000000000 --- a/src/adcp/types/generated_poc/core/media_buy_change_term_id.py +++ /dev/null @@ -1,16 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/media_buy_change_term_id.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from adcp.types._scalar import ScalarStr - - -class MediaBuyChangeTermId(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Za-z0-9_.:-]+$'} - _json_schema_extra = { - 'description': 'The accepted proposal change_terms[].term_id from which this current-state action projection was derived.', - 'title': 'Media Buy Change Term ID', - } diff --git a/src/adcp/types/generated_poc/core/media_buy_features.py b/src/adcp/types/generated_poc/core/media_buy_features.py deleted file mode 100644 index f41afaf1b..000000000 --- a/src/adcp/types/generated_poc/core/media_buy_features.py +++ /dev/null @@ -1,80 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/media_buy_features.json -# timestamp: 2026-10-03T15:09:44+00:00 - -from __future__ import annotations - -from typing import Annotated, Dict - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictBool - -from . import bidding_policy_capability - - -class MediaBuyFeatures(AdCPBaseModel): - __pydantic_extra__: Dict[str, StrictBool] - model_config = ConfigDict( - extra='allow', - ) - inline_creative_management: Annotated[ - StrictBool | None, - Field( - deprecated=True, - description='Deprecated 3.x compatibility capability for creatives provided inline in create_media_buy and update_media_buy package payloads. buy_products, accept_proposal, and control_media_buy never accept inline creatives. New integrations use the dedicated creative lifecycle. Removed in 4.0.', - ), - ] = None - property_list_filtering: Annotated[ - StrictBool | None, - Field( - description='Honors property_list parameter in get_products to filter results to buyer-approved properties' - ), - ] = None - catalog_management: Annotated[ - StrictBool | None, - Field( - description='Supports sync_catalogs task for catalog feed management with platform review and approval' - ), - ] = None - catalog_item_availability_updates: Annotated[ - StrictBool | None, - Field( - description='Supports buyer-pushed item_availability_updates and item_availability_queries on sync_catalogs for immediate suppression/restoration and current-state readback in buyer-managed catalogs. Seller declarations may be true only with catalog_management: true; buyer required_features filters may request this feature alone. Requests containing availability operations are synchronous and accept at most 1,000 combined update and query entries. A successful suppress covers selection, dynamic rendering, and cached or pre-generated creatives materialized from the item. Internal lineage MUST retain resolved_account_id, catalog_id, catalog_generation, and item_id. Static creatives supplied or promoted by the buyer without catalog lineage remain outside this automatic guarantee. Suppression persists across feed refreshes and ordinary upserts until restore, expires_at, or catalog deletion. A seller that does not declare true MUST reject availability operations with UNSUPPORTED_FEATURE before lookup or mutation and MUST NOT interpret them as discovery. This does not control seller-owned wholesale inventory or let restore bypass seller controls.' - ), - ] = None - committed_metrics_supported: Annotated[ - StrictBool | None, - Field( - description="Seller has per-package snapshot infrastructure for the reporting contract. When true, the seller MUST populate `package.committed_metrics` on committed `create_media_buy` responses where `confirmed_at` is non-null, MUST omit `package.committed_metrics` while `confirmed_at` is null for a provisional buy, and MUST honor append-only mid-flight metric additions via `update_media_buy`. The unified `committed_metrics` array (per the metric-accountability design) covers both standard and vendor-defined metric entries, so a single flag is load-bearing. Buyers filtering on this flag are detecting 'this seller can stamp the reporting contract,' which closes the audit gap from PR #3510 where absence of `committed_metrics` was indistinguishable between 'didn't snapshot' and 'snapshot infrastructure not implemented.'" - ), - ] = None - seller_optimized_budget: Annotated[ - StrictBool | None, - Field( - description="Supports the core seller-optimized shared-budget contract for budget_allocation.mode `seller_optimized`: one hard shared total_budget, seller allocation of that total across the buy's packages against budget_allocation.optimization_goals, media-buy-level pacing, and echo of the allocation configuration on buy read surfaces. Sellers declaring true MUST accept eligible explicit-package and proposal executions that use only these core controls and MUST enforce the aggregate budget. Core media-buy pacing: sellers declaring true MUST accept omitted media-buy pacing (which defaults to even when total_budget is present) and pacing `even` on seller-optimized buys; they MAY reject `asap` or `front_loaded` with `UNSUPPORTED_FEATURE` (error.field `pacing`) before any provider mutation, and MUST NOT silently coerce them to `even`. Package-level controls inside a seller-optimized buy are separate capabilities: package budget caps (seller_optimized_package_budgets), package minimum-spend targets (seller_optimized_min_spend_targets), and package pacing (seller_optimized_package_pacing). A seller declaring this feature but not one of those sub-capabilities MUST reject any request that would leave that package control on a seller-optimized buy with `UNSUPPORTED_FEATURE` before any over-subscription validation or provider mutation, and MUST NOT silently drop, soften, or coerce it. Over-subscription validation (`INVALID_REQUEST`) applies only to package controls the seller has declared; see seller_optimized_min_spend_targets. Product combinations may still be rejected when their currencies, optimization capabilities, pricing terms, or delivery constraints are incompatible. Sellers that do not declare this feature MUST reject any request carrying `budget_allocation.mode: 'seller_optimized'` with `UNSUPPORTED_FEATURE` before any provider mutation, and MUST NOT coerce the request to fixed allocation." - ), - ] = None - seller_optimized_package_budgets: Annotated[ - StrictBool | None, - Field( - description='Honors package `budget` as an optional hard lifetime package spend cap inside a seller-optimized buy: packages[].budget and new_packages[].budget on create_media_buy and update_media_buy, purchases[].budget on buy_products, package budget controls on control_media_buy, and max_spend_percentage on seller-optimized proposal allocations. The cap is a ceiling, not a reserved or current allocation, and package caps may sum above total_budget. Meaningful only with seller_optimized_budget: true; seller declarations may be true only with seller_optimized_budget: true, while buyer required_features filters may request this feature alone. A seller that declares seller_optimized_budget without this feature MUST reject a request that would leave a package budget on a seller-optimized buy, including an allocation-mode switch that retains fixed-mode package budgets (the buyer clears them with null in the same atomic update), with `UNSUPPORTED_FEATURE` before any over-subscription validation or provider mutation, and MUST NOT issue seller-optimized proposals carrying max_spend_percentage. Does not govern fixed allocation, where package budgets remain required.' - ), - ] = None - seller_optimized_min_spend_targets: Annotated[ - StrictBool | None, - Field( - description='Honors package `min_spend_target` as a soft lifetime minimum-spend target inside a seller-optimized buy: packages[].min_spend_target and new_packages[].min_spend_target on create_media_buy and update_media_buy, purchases[].min_spend_target on buy_products, package min_spend_target controls on control_media_buy, and min_spend_target_percentage on seller-optimized proposal allocations. The seller SHOULD attempt to deliver at least the target before allocating incremental spend elsewhere; it is not a billing or delivery guarantee. Meaningful only with seller_optimized_budget: true; seller declarations may be true only with seller_optimized_budget: true, while buyer required_features filters may request this feature alone. Sellers declaring this feature MUST reject package minimum-spend targets summing above total_budget with `INVALID_REQUEST` before mutation, and, when they also declare seller_optimized_package_budgets, MUST likewise reject a min_spend_target above its own package budget. A seller that declares seller_optimized_budget without this feature MUST reject a request carrying a numeric min_spend_target with `UNSUPPORTED_FEATURE` before any over-subscription validation or provider mutation, so an over-subscribed target sent to such a seller yields `UNSUPPORTED_FEATURE`, and MUST NOT issue seller-optimized proposals carrying min_spend_target_percentage.' - ), - ] = None - seller_optimized_package_pacing: Annotated[ - StrictBool | None, - Field( - description='Honors package `pacing` as subordinate per-package pacing inside a seller-optimized buy, in addition to the media-buy-level pacing covered by seller_optimized_budget: packages[].pacing and new_packages[].pacing on create_media_buy and update_media_buy, purchases[].pacing on buy_products, package pacing controls on control_media_buy, and allocation pacing on seller-optimized proposals. Package pacing MUST NOT cause delivery to exceed aggregate media-buy pacing. Meaningful only with seller_optimized_budget: true; seller declarations may be true only with seller_optimized_budget: true, while buyer required_features filters may request this feature alone. Package pacing equal to the effective media-buy pacing adds no subordinate constraint and does not require this feature; buyers SHOULD omit package pacing on seller-optimized buys unless this feature is advertised. A seller that declares seller_optimized_budget without this feature MUST reject a request that would leave package pacing differing from media-buy pacing on a seller-optimized buy, including an allocation-mode switch that retains such fixed-mode package pacing (the buyer can align it in the same update), with `UNSUPPORTED_FEATURE` before any provider mutation, and MUST NOT issue seller-optimized proposals carrying allocation pacing. Does not govern package pacing in fixed allocation.' - ), - ] = None - bidding_policy: Annotated[ - bidding_policy_capability.BiddingPolicyCapability | None, - Field( - description='Structured support for canonical bidding by authored scope, allocation context, mode, strength, and strength-qualified multi-field combination. Presence does not imply support for both scopes, both allocation modes, or every policy shape. Sellers MUST preserve every advertised semantic exactly and reject unadvertised policies rather than translating them.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/media_buy_frequency_cap.py b/src/adcp/types/generated_poc/core/media_buy_frequency_cap.py deleted file mode 100644 index dff95d887..000000000 --- a/src/adcp/types/generated_poc/core/media_buy_frequency_cap.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/media_buy_frequency_cap.json -# timestamp: 2026-09-14T14:16:02+00:00 - -from __future__ import annotations - -from .frequency_cap import FrequencyCap - - -class MediaBuyFrequencyCap(FrequencyCap): - pass diff --git a/src/adcp/types/generated_poc/core/media_buy_frequency_cap_capability.py b/src/adcp/types/generated_poc/core/media_buy_frequency_cap_capability.py deleted file mode 100644 index fd09c87c4..000000000 --- a/src/adcp/types/generated_poc/core/media_buy_frequency_cap_capability.py +++ /dev/null @@ -1,25 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/media_buy_frequency_cap_capability.json -# timestamp: 2026-09-14T14:16:02+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from pydantic import Field - -from ..enums import media_buy_frequency_cap_control_mode -from .frequency_cap_constraints import FrequencyCapConstraints - - -class MediaBuyFrequencyCapCapability(FrequencyCapConstraints): - supported_control_modes: Annotated[ - list[media_buy_frequency_cap_control_mode.MediaBuyFrequencyCapControlMode], - Field( - description='FrequencyCap shapes accepted by the product. max_impressions_and_suppress means both controls may appear together and are enforced with AND semantics.', - min_length=1, - ), - ] - supported_per_units: Any - max_impressions_constraints: Any - window_constraints: Any diff --git a/src/adcp/types/generated_poc/core/media_buy_frequency_cap_requirement.py b/src/adcp/types/generated_poc/core/media_buy_frequency_cap_requirement.py deleted file mode 100644 index 620276de3..000000000 --- a/src/adcp/types/generated_poc/core/media_buy_frequency_cap_requirement.py +++ /dev/null @@ -1,19 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/media_buy_frequency_cap_requirement.json -# timestamp: 2026-09-14T14:16:02+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import Field - -from ..enums import media_buy_frequency_cap_control_mode -from .frequency_cap_requirements import FrequencyCapRequirements - - -class MediaBuyFrequencyCapRequirement(FrequencyCapRequirements): - supported_control_modes: Annotated[ - list[media_buy_frequency_cap_control_mode.MediaBuyFrequencyCapControlMode] | None, - Field(min_length=1), - ] = None diff --git a/src/adcp/types/generated_poc/core/media_buy_frequency_cap_support.py b/src/adcp/types/generated_poc/core/media_buy_frequency_cap_support.py deleted file mode 100644 index c8c103793..000000000 --- a/src/adcp/types/generated_poc/core/media_buy_frequency_cap_support.py +++ /dev/null @@ -1,22 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/media_buy_frequency_cap_support.json -# timestamp: 2026-09-14T14:16:02+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import Field - -from ..enums import media_buy_frequency_cap_control_mode -from .frequency_cap_constraints import FrequencyCapConstraints - - -class MediaBuyFrequencyCapSupport(FrequencyCapConstraints): - supported_control_modes: Annotated[ - list[media_buy_frequency_cap_control_mode.MediaBuyFrequencyCapControlMode] | None, - Field( - description='FrequencyCap shapes accepted by the product. max_impressions_and_suppress means both controls may appear together and are enforced with AND semantics.', - min_length=1, - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/media_buy_legacy_terms_ref.py b/src/adcp/types/generated_poc/core/media_buy_legacy_terms_ref.py deleted file mode 100644 index a6463de92..000000000 --- a/src/adcp/types/generated_poc/core/media_buy_legacy_terms_ref.py +++ /dev/null @@ -1,15 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/media_buy_legacy_terms_ref.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from adcp.types._scalar import ScalarStr - - -class MediaBuyTermsReference(ScalarStr): - __slots__ = () - _json_schema_extra = { - 'description': 'Deprecated 3.1 opaque commercial-terms pointer. A 3.2 compatibility projection MAY echo change_term_id here for older buyers, but new buyers MUST prefer change_term_id and MUST NOT assume an arbitrary 3.1 value identifies an accepted change term.', - 'title': 'Media Buy Terms Reference', - } diff --git a/src/adcp/types/generated_poc/core/media_buy_support.py b/src/adcp/types/generated_poc/core/media_buy_support.py deleted file mode 100644 index dcf3c1975..000000000 --- a/src/adcp/types/generated_poc/core/media_buy_support.py +++ /dev/null @@ -1,29 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/media_buy_support.json -# timestamp: 2026-09-14T14:16:02+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from . import ext as ext_1 -from . import media_buy_frequency_cap_support - - -class ProductMediaBuySupport(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - frequency_cap: Annotated[ - Literal[True] | None, - Field( - description="This product can participate in the seller's shared counter for a root MediaBuy.frequency_cap. A buy mixing this product with one that lacks this declaration, or whose resolved supported_per_units omits the root cap's per value, is rejected atomically." - ), - ] = None - frequency_cap_constraints: ( - media_buy_frequency_cap_support.MediaBuyFrequencyCapSupport | None - ) = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/media_buy_support_requirements.py b/src/adcp/types/generated_poc/core/media_buy_support_requirements.py deleted file mode 100644 index 5647803db..000000000 --- a/src/adcp/types/generated_poc/core/media_buy_support_requirements.py +++ /dev/null @@ -1,27 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/media_buy_support_requirements.json -# timestamp: 2026-09-14T14:16:02+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from . import media_buy_frequency_cap_requirement - - -class ProductMediaBuySupportRequirements(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - frequency_cap: Annotated[ - Literal[True] | None, - Field( - description='Require product participation in one shared MediaBuy frequency-cap counter.' - ), - ] = None - frequency_cap_constraints: ( - media_buy_frequency_cap_requirement.MediaBuyFrequencyCapRequirement | None - ) = None diff --git a/src/adcp/types/generated_poc/core/missing_metric.py b/src/adcp/types/generated_poc/core/missing_metric.py deleted file mode 100644 index a9e5c46ac..000000000 --- a/src/adcp/types/generated_poc/core/missing_metric.py +++ /dev/null @@ -1,67 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/missing_metric.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..enums import attribution_methodology as attribution_methodology_1 -from ..enums import available_metric -from ..enums import completion_source as completion_source_1 -from ..enums import lift_dimension as lift_dimension_1 -from ..enums import viewability_standard as viewability_standard_1 -from . import brand_ref, duration, vendor_metric_id - - -class Qualifier(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - viewability_standard: viewability_standard_1.ViewabilityStandard | None = None - completion_source: completion_source_1.CompletionSource | None = None - attribution_methodology: attribution_methodology_1.AttributionMethodology | None = None - attribution_window: duration.Duration | None = None - lift_dimension: lift_dimension_1.LiftDimension | None = None - - -class MissingMetric1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Literal['standard'] = 'standard' - metric_id: available_metric.AvailableMetric - qualifier: Annotated[ - Qualifier | None, - Field( - description='Mirrors the qualifier on `committed_metrics` so the missing entry preserves the contract distinction (e.g., flagging MRC viewability as missing when only GroupM was reported, vendor-attested completion as missing when only seller-attested was reported, or deterministic_purchase attribution as missing when only probabilistic was reported). MUST match the qualifier on the corresponding `committed_metrics` entry the missing flag refers to.' - ), - ] = None - - -class MissingMetric2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Literal['vendor'] = 'vendor' - vendor: brand_ref.BrandReference - metric_id: vendor_metric_id.VendorMetricId - qualifier: Annotated[ - Qualifier | None, - Field( - description='Mirrors the qualifier on the corresponding vendor-scope `committed_metrics` entry. MUST match that entry so reconciliation joins on (vendor, metric_id, qualifier).' - ), - ] = None - - -MissingMetric = Annotated[ - MissingMetric1 | MissingMetric2, - Field( - description='One metric the binding reporting contract declared but that is not populated in a delivery report. Symmetric with `committed_metrics` and discriminated by `scope`.', - discriminator='scope', - title='Missing Metric', - ), -] diff --git a/src/adcp/types/generated_poc/core/negative_keyword.py b/src/adcp/types/generated_poc/core/negative_keyword.py deleted file mode 100644 index bbd836d29..000000000 --- a/src/adcp/types/generated_poc/core/negative_keyword.py +++ /dev/null @@ -1,20 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/negative_keyword.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..enums import match_type as match_type_1 - - -class NegativeKeyword(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - keyword: Annotated[str, Field(description='The keyword to exclude', min_length=1)] - match_type: match_type_1.MatchType diff --git a/src/adcp/types/generated_poc/core/notification_config.py b/src/adcp/types/generated_poc/core/notification_config.py deleted file mode 100644 index 4d4905261..000000000 --- a/src/adcp/types/generated_poc/core/notification_config.py +++ /dev/null @@ -1,102 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/notification_config.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, StrictBool - -from ..enums import auth_scheme -from . import ext as ext_1 - - -class EventType(StrEnum): - creative_status_changed = 'creative.status_changed' - creative_assignment_changed = 'creative.assignment_changed' - indicators_changed = 'indicators.changed' - creative_purged = 'creative.purged' - account_status_changed = 'account.status_changed' - account_change_recorded = 'account.change_recorded' - product_created = 'product.created' - product_updated = 'product.updated' - product_priced = 'product.priced' - product_removed = 'product.removed' - signal_created = 'signal.created' - signal_updated = 'signal.updated' - signal_priced = 'signal.priced' - signal_removed = 'signal.removed' - wholesale_feed_bulk_change = 'wholesale_feed.bulk_change' - reporting_delivery_ready = 'reporting.delivery_ready' - reporting_status_changed = 'reporting.status_changed' - reporting_ledger_changed = 'reporting.ledger_changed' - - -class ProductPayloadView(StrEnum): - canonical = 'canonical' - legacy = 'legacy' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[list[auth_scheme.AuthenticationScheme], Field(max_length=1, min_length=1)] - credentials: Annotated[ - str | None, - Field( - description='Credentials for the legacy scheme. Bearer: token. HMAC-SHA256: shared secret. Minimum 32 characters. Exchanged out-of-band during onboarding. Write-only.', - min_length=32, - ), - ] = None - - -class NotificationConfig(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - subscriber_id: Annotated[ - str, - Field( - description="Buyer-supplied identifier for this subscription endpoint. This is the stable logical key within one account's notification_configs[] set: re-sending the same subscriber_id for the same account replaces that subscriber's URL, event_types, authentication selector, and active flag rather than creating a duplicate. Echoed on every webhook payload and on every `webhook_activity[]` record fired against this config so the buyer can attribute fires across multiple endpoints. MUST be unique within the account's `notification_configs[]`. Sending two entries with the same `subscriber_id` in a single `sync_accounts` request array is rejected as a per-account validation failure with `INVALID_REQUEST` or `VALIDATION_ERROR`, and `error.field` MUST point at the duplicate entry. `subscriber_id` is the stable match key for the per-account declarative-replace diff. Always required (even with a single subscriber) so the SDK contract is uniform — no conditional required-when-multiple rules to trip up implementations. Format is opaque — recommended values are short kebab-case slugs (`buyer-primary`, `audit-bus`, `dx-team`).", - max_length=64, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,64}$', - ), - ] - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL. Same wire contract as `push-notification-config.url` — `format: "uri"`, no destination-port allowlist enforced by the protocol, SSRF protection via the IP-range check defined in docs/building/by-layer/L1/security.mdx#webhook-url-validation-ssrf. Sellers MUST validate URL syntax, HTTPS usage, hostname normalization, and reserved-range rejection when writing any config, including `active: false` configs. Sellers MUST complete an activation challenge or equivalent proof-of-control before treating a new or changed active subscriber as active.' - ), - ] - event_types: Annotated[ - list[EventType], - Field( - description='Account-anchored notification types this subscriber wishes to receive on the registered `url`. The seller MUST NOT fire other types against this endpoint, and MUST NOT silently widen the filter when new account-anchored types are added. Creative lifecycle, assignment, indicator, account status, wholesale feed, reporting.delivery_ready, reporting.status_changed, and reporting.ledger_changed events are valid here; media-buy-anchored types (`scheduled`, `final`, `delayed`, `adjusted`, `window_update`, `impairment`) and agent-anchored types (`capabilities.changed`) are schema-invalid on this surface and sellers MUST reject those entries as per-account validation failures with `INVALID_REQUEST` or `VALIDATION_ERROR` and `error.field` pointing at the invalid `event_types` entry rather than silently dropping them.', - min_length=1, - ), - ] - product_payload_view: Annotated[ - ProductPayloadView | None, - Field( - description='Product webhook representation selected by this subscriber. Use canonical with lifecycle_tools.list_products; legacy is the default for 3.x get_products consumers. Sellers emit exactly canonical_product/canonical_pricing_options or product/pricing_options accordingly. Valid only when event_types includes a product.* event.' - ), - ] = ProductPayloadView.legacy - authentication: Annotated[ - Authentication | None, - Field( - deprecated=True, - description="Legacy authentication selector. Same precedence and semantics as `push-notification-config.authentication` — presence opts the seller into Bearer or HMAC-SHA256 signing; absence selects the default RFC 9421 webhook profile keyed off the seller's brand.json `agents[]` JWKS. The same signed-registration downgrade-resistance rules apply to accounts[].notification_configs[].authentication. Deprecated; removed in AdCP 4.0. Credentials are write-only and MUST NOT be echoed on `list_accounts` reads.", - ), - ] = None - active: Annotated[ - StrictBool | None, - Field( - description="When false, the seller persists the configuration but suppresses fires. Use to pause a noisy subscriber without losing the registration. Sellers MUST NOT skip persisting the entry when `active: false` — the buyer's next `sync_accounts` MUST observe the same array, otherwise the buyer cannot distinguish pause from drop. Paused configs may skip only the outbound proof challenge while inactive; sellers MUST still enforce URL parsing, HTTPS, hostname normalization, and reserved-range rejection at write time. Reactivation requires full SSRF validation with connect pinning plus proof-of-control for any tuple without current valid proof." - ), - ] = True - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/offering.py b/src/adcp/types/generated_poc/core/offering.py deleted file mode 100644 index ea6d21f8f..000000000 --- a/src/adcp/types/generated_poc/core/offering.py +++ /dev/null @@ -1,143 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/offering.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - -from ..enums import metro_system -from . import ext as ext_1 -from . import offering_asset_group, postal_area - - -class Country(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{2}$'} - - -class Region(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{2}-[A-Z0-9]{1,3}$'} - - -class Metro(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - system: Annotated[ - metro_system.MetroAreaSystem, - Field(description="Metro area classification system (e.g., 'nielsen_dma', 'uk_itl2')"), - ] - values: Annotated[list[str], Field(description='Metro codes within the system', min_length=1)] - - -class GeoTargets(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - countries: Annotated[ - list[Country] | None, - Field( - description="Countries where this offering is relevant. ISO 3166-1 alpha-2 codes (e.g., 'US', 'NL', 'DE').", - min_length=1, - ), - ] = None - regions: Annotated[ - list[Region] | None, - Field( - description="Regions or states where this offering is relevant. ISO 3166-2 subdivision codes (e.g., 'NL-NH', 'US-CA').", - min_length=1, - ), - ] = None - metros: Annotated[ - list[Metro] | None, - Field( - description='Metro areas where this offering is relevant. Each entry specifies the classification system and target values.', - min_length=1, - ), - ] = None - postal_areas: Annotated[ - list[postal_area.PostalArea] | None, - Field( - description='Postal areas where this offering is relevant. Prefer the native country + postal system form. Deprecated legacy country-fused postal-system tokens remain accepted for compatibility.', - min_length=1, - ), - ] = None - - -class Offering(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - offering_id: Annotated[ - str, - Field( - description='Unique identifier for this offering. Used by hosts to reference specific offerings in si_get_offering calls.' - ), - ] - name: Annotated[ - str, - Field( - description="Human-readable offering name (e.g., 'Winter Sale', 'Free Trial', 'Enterprise Platform')" - ), - ] - description: Annotated[str | None, Field(description="Description of what's being offered")] = ( - None - ) - tagline: Annotated[ - str | None, Field(description='Short promotional tagline for the offering') - ] = None - valid_from: Annotated[ - AwareDatetime | None, - Field( - description='When the offering becomes available. If not specified, offering is immediately available.' - ), - ] = None - valid_to: Annotated[ - AwareDatetime | None, - Field( - description='When the offering expires. If not specified, offering has no expiration.' - ), - ] = None - checkout_url: Annotated[ - AnyUrl | None, - Field( - description="URL for checkout/purchase flow when the brand doesn't support agentic checkout." - ), - ] = None - landing_url: Annotated[ - AnyUrl | None, - Field( - description="Landing page URL for this offering. For catalog-driven creatives, this is the per-item click-through destination that platforms map to the ad's link-out URL. Every offering in a catalog should have a landing_url unless the format provides its own destination logic." - ), - ] = None - assets: Annotated[ - list[offering_asset_group.OfferingAssetGroup] | None, - Field( - description='Structured asset groups for this offering. Each group carries a typed pool of creative assets (headlines, images, videos, etc.) identified by a group ID that matches format-level vocabulary.' - ), - ] = None - geo_targets: Annotated[ - GeoTargets | None, - Field( - description="Geographic scope of this offering. Declares where the offering is relevant — for location-specific offerings such as job vacancies, in-store promotions, or local events. Platforms use this to target geographically appropriate audiences and to filter out offerings irrelevant to a user's location. Uses the same geographic structures as targeting_overlay in create_media_buy." - ), - ] = None - keywords: Annotated[ - list[str] | None, - Field( - description='Keywords for matching this offering to user intent. Hosts use these for retrieval/relevance scoring.' - ), - ] = None - categories: Annotated[ - list[str] | None, - Field( - description="Categories this offering belongs to (e.g., 'measurement', 'identity', 'programmatic')" - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/offering_asset_group.py b/src/adcp/types/generated_poc/core/offering_asset_group.py deleted file mode 100644 index cc091b559..000000000 --- a/src/adcp/types/generated_poc/core/offering_asset_group.py +++ /dev/null @@ -1,70 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/offering_asset_group.json -# timestamp: 2026-10-03T00:36:50+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..enums import asset_content_type -from . import ext as ext_1 -from .assets import ( - audio_asset, - css_asset, - daast_asset, - html_asset, - image_asset, - javascript_asset, - markdown_asset, - text_asset, - url_asset, - vast_asset, - video_asset, - webhook_asset, - zip_asset, -) - - -Items = Annotated[ - text_asset.TextAsset - | image_asset.ImageAsset - | video_asset.VideoAsset - | audio_asset.AudioAsset - | url_asset.UrlAsset - | html_asset.HtmlAsset - | markdown_asset.MarkdownAsset - | vast_asset.VastAsset - | daast_asset.DaastAsset - | css_asset.CssAsset - | javascript_asset.JavascriptAsset - | zip_asset.ZipAsset - | webhook_asset.WebhookAsset, - Field(discriminator='asset_type'), -] - - -class OfferingAssetGroup(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_group_id: Annotated[ - str, - Field( - description="Identifies the creative role this group fills. Values are defined by each canonical format declaration's offering_asset_constraints — not protocol constants. Discover creative-agent declarations via get_adcp_capabilities creative.supported_formats[] and sales-product declarations via Product.format_options[] (e.g., 'headlines', 'images', or 'videos')." - ), - ] - asset_type: Annotated[ - asset_content_type.AssetContentType, - Field(description='The content type of all items in this group.'), - ] - items: Annotated[ - list[Items], - Field( - description='The assets in this group. Each item carries an `asset_type` discriminator that selects the matching asset schema. Note: the group-level `asset_type` declares the expected type; individual items must also self-tag so validators can narrow errors. Intentionally excludes `brief-asset` and `catalog-asset` — those are campaign-input metadata types, not delivery-ready creative assets suitable for a pooled offering group. See core/assets/asset-union.json for the full asset-variant union.', - min_length=1, - ), - ] - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/operator_identity.py b/src/adcp/types/generated_poc/core/operator_identity.py deleted file mode 100644 index 149b12a0e..000000000 --- a/src/adcp/types/generated_poc/core/operator_identity.py +++ /dev/null @@ -1,31 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/operator_identity.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from . import operator_unit as operator_unit_1 - - -class OperatorIdentity(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - operator_unit: Annotated[ - operator_unit_1.OperatorUnit | None, - Field( - description='Optional operator-owned business unit, agency seat, or platform account. Its id participates in account identity; name is mutable display metadata. Omission from a complete desired replacement means the account should have no operator unit.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/operator_unit.py b/src/adcp/types/generated_poc/core/operator_unit.py deleted file mode 100644 index d3fe73828..000000000 --- a/src/adcp/types/generated_poc/core/operator_unit.py +++ /dev/null @@ -1,33 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/operator_unit.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - - -class OperatorUnit(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - id: Annotated[ - str, - Field( - description='Stable identifier assigned by the operator. Numeric platform IDs and durable slugs are both valid. Scoped by the enclosing operator domain.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9][A-Za-z0-9._:/-]*$', - ), - ] - name: Annotated[ - str | None, - Field( - description='Human-readable seat or business-unit name, such as Nova EMEA. This label may change and is not part of the natural account key.', - max_length=200, - min_length=1, - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/opportunity_context.py b/src/adcp/types/generated_poc/core/opportunity_context.py deleted file mode 100644 index 495781001..000000000 --- a/src/adcp/types/generated_poc/core/opportunity_context.py +++ /dev/null @@ -1,89 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/opportunity_context.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - -from . import date_range - - -class Phase(StrEnum): - exploratory = 'exploratory' - planning = 'planning' - active_sourcing = 'active_sourcing' - - -class Intent(StrEnum): - test = 'test' - speculative = 'speculative' - planning = 'planning' - live_rfp = 'live_rfp' - - -class Status(StrEnum): - open = 'open' - closed = 'closed' - - -class CloseReason(StrEnum): - accepted_with_seller = 'accepted_with_seller' - purchased_elsewhere = 'purchased_elsewhere' - selected_alternative = 'selected_alternative' - not_pursued = 'not_pursued' - budget_changed = 'budget_changed' - timing_changed = 'timing_changed' - other = 'other' - - -class OpportunityContext(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - opportunity_id: Annotated[ - str, - Field( - description='Opaque buyer-assigned identifier for this planning cycle, scoped to the seller and account.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] - phase: Annotated[Phase | None, Field(description='Current stage of buyer planning.')] = None - intent: Annotated[ - Intent | None, - Field(description='How seriously the buyer is evaluating supply in this cycle.'), - ] = None - planning_horizon: Annotated[ - date_range.DateRange | None, - Field( - description='Inclusive calendar range in which the buyer expects the campaign to run.' - ), - ] = None - response_deadline: Annotated[ - AwareDatetime | None, - Field(description='Deadline by which the buyer needs a seller response.'), - ] = None - status: Annotated[ - Status | None, - Field( - description='Whether the planning cycle remains open. On calls after initial creation, omission means no status update and MUST NOT reopen or close the opportunity implicitly, except that successful create_media_buy proposal execution explicitly infers accepted closure.' - ), - ] = None - close_reason: Annotated[ - CloseReason | None, - Field(description='Why the opportunity closed. Required when status is closed.'), - ] = None - close_detail: Annotated[ - str | None, - Field( - description='Optional non-sensitive context about closure. MUST NOT identify a competitor or disclose confidential clearing terms.', - max_length=500, - min_length=1, - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/optimization_goal.py b/src/adcp/types/generated_poc/core/optimization_goal.py deleted file mode 100644 index b824e1591..000000000 --- a/src/adcp/types/generated_poc/core/optimization_goal.py +++ /dev/null @@ -1,310 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/optimization_goal.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictFloat - -from ..enums import event_type as event_type_1 -from ..enums import reach_unit as reach_unit_1 -from ..enums import viewability_standard -from . import attribution_window as attribution_window_1 -from . import brand_ref, duration, vendor_metric_id - - -class Metric(StrEnum): - clicks = 'clicks' - views = 'views' - completed_views = 'completed_views' - viewed_seconds = 'viewed_seconds' - viewable_rate = 'viewable_rate' - attention_seconds = 'attention_seconds' - attention_score = 'attention_score' - engagements = 'engagements' - follows = 'follows' - saves = 'saves' - profile_visits = 'profile_visits' - reach = 'reach' - - -class TargetFrequency(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - min: Annotated[ - SchemaInt | None, - Field( - description='Minimum frequency for an entity to be considered meaningfully reached within the window. Impressions that would bring an entity below this threshold are treated as high-value (growing reach). When omitted, the seller uses their platform default (typically 1).', - ge=1, - ), - ] = None - max: Annotated[ - SchemaInt | None, - Field( - description='Frequency at which an entity is considered saturated within the window. Impressions toward entities at or above this threshold are treated as lower-value. When both min and max are present, max must be greater than or equal to min. When omitted, the seller determines the saturation point.', - ge=1, - ), - ] = None - window: Annotated[ - duration.Duration, - Field( - description='Time window over which frequency is measured (e.g. {"interval": 7, "unit": "days"} or {"interval": 1, "unit": "campaign"} for the full flight). Weekly windows are typical for brand campaigns; daily windows suit high-cadence direct response.' - ), - ] - - -class Target(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['cost_per'] = 'cost_per' - value: Annotated[ - StrictFloat, Field(description='Target cost per metric unit in the buy currency', gt=0.0) - ] - - -class Target14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['threshold_rate'] = 'threshold_rate' - value: Annotated[ - StrictFloat, - Field( - description='Minimum per-impression value. Units depend on the metric: proportion (clicks, views, completed_views, viewable_rate), seconds (viewed_seconds, attention_seconds), or score (attention_score).', - gt=0.0, - ), - ] - - -class EventSource(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - event_source_id: Annotated[ - str, - Field( - description='Event source to include (must be configured on this account via sync_event_sources)', - min_length=1, - ), - ] - event_type: Annotated[ - event_type_1.EventType, - Field( - description='Event type to include from this source (e.g., purchase, lead, app_install, refund)' - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description="Required when event_type is 'custom'. Platform-specific name for the custom event." - ), - ] = None - value_field: Annotated[ - str | None, - Field( - description="Which field in the event's custom_data carries the monetary value. The seller must use this field for value extraction and aggregation when computing ROAS and conversion value metrics. Required on at least one entry when target.kind is 'per_ad_spend' or 'maximize_value' — sellers must reject these target kinds when no event source entry includes value_field. When canonical bidding.roas binds to this goal, every referenced value-bearing source MUST declare value_currencies containing the media-buy currency; the seller validates this at buy creation. Once that source contract is declared, log_event rejects monetary records whose currency is missing or absent from the list. For each buy, only records exactly matching its media-buy currency contribute to ROAS; records in other declared currencies remain available to matching buys, and sellers MUST NOT convert between them. When present without a value-oriented target, the seller may use it for delivery reporting but must not change the objective. Common values: 'value', 'order_total', 'profit_margin'. This is not passed as a parameter to underlying platform APIs — the seller maps it to their platform's value ingestion mechanism." - ), - ] = None - value_factor: Annotated[ - StrictFloat | None, - Field( - description="Unit-scaling multiplier the seller must apply to value_field before aggregation. Use -1 for refund events (negate the value), 0.01 for values in cents, -0.01 for refunds in cents. It MUST NOT be used for currency conversion. A value of 0 zeroes out this source's value contribution (the source still counts for event dedup). Defaults to 1. This is not passed as a parameter to underlying platform APIs — the seller applies it when computing aggregated value metrics." - ), - ] = 1 - - -class Target15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['cost_per'] = 'cost_per' - value: Annotated[ - StrictFloat, Field(description='Target cost per event in the buy currency', gt=0.0) - ] - - -class Target16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['per_ad_spend'] = 'per_ad_spend' - value: Annotated[ - StrictFloat, - Field(description='Target return ratio (e.g., 4.0 means $4 of value per $1 spent)', gt=0.0), - ] - - -class Target17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['maximize_value'] = 'maximize_value' - - -class Target18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['cost_per'] = 'cost_per' - value: Annotated[ - StrictFloat, - Field( - description='Target cost per metric unit in the buy currency. Units of the metric are vendor-defined.', - gt=0.0, - ), - ] - - -class Target19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['threshold_rate'] = 'threshold_rate' - value: Annotated[ - StrictFloat, - Field( - description='Minimum per-impression value. Units of the metric are vendor-defined.', - gt=0.0, - ), - ] - - -class OptimizationGoal9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['event'] = 'event' - event_sources: Annotated[ - list[EventSource], - Field( - description='Event source and type pairs that feed this goal. Each entry identifies a source and event type to include. When the seller supports multi_source_event_dedup (declared in get_adcp_capabilities), they deduplicate by event_id across all entries — the same business event from multiple sources counts once, using value_field and value_factor from the first matching entry. When multi_source_event_dedup is false or absent, buyers should use a single entry per goal; the seller will use only the first entry. All event sources must be configured via sync_event_sources.', - min_length=1, - ), - ] - target: Annotated[ - Target15 | Target16 | Target17 | None, - Field( - description='Target cost or return for this event goal. When omitted, the seller optimizes for maximum conversion count within budget — regardless of whether value_field is present on event sources. The presence of value_field alone does not change the optimization objective; it only makes value available for reporting. An explicit target of maximize_value or per_ad_spend is required to steer toward value.', - discriminator='kind', - ), - ] = None - attribution_window: Annotated[ - attribution_window_1.AttributionWindow | None, - Field( - description="Attribution window for this optimization goal — references the canonical `attribution-window` shape (post_click, post_view, model). Values must match an option declared in the seller's `conversion_tracking.attribution_windows` capability. Sellers MUST reject windows not in their declared capabilities. When the entire field is omitted, the seller uses their default window." - ), - ] = None - priority: Annotated[ - SchemaInt | None, - Field( - description='Relative priority among sibling goals. Lower numbers rank first. Goals without priority follow explicitly prioritized goals. Ties use array order, so the earliest goal at the lowest explicit priority is primary; when all priorities are omitted, the first goal is primary.', - ge=1, - ), - ] = None - - -class OptimizationGoal8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['metric'] = 'metric' - metric: Annotated[ - Metric, - Field( - description="Seller-native metric to optimize for. Delivery metrics: clicks (link clicks, swipe-throughs, CTA taps that navigate away), views (content views at the billable view threshold, as defined by delivery-metrics `views`; for viewability use viewable_rate), completed_views (video/audio completions — see view_duration_seconds), reach (unique audience reach — see reach_unit and target_frequency). Duration/score metrics: viewed_seconds (time in view per impression — reported back via `delivery-metrics.viewability.viewed_seconds`, governed by the viewability `standard`). Quality-rate metrics: viewable_rate (viewable / measurable impressions under the goal's `standard`; requires `standard`). Audience action metrics: engagements (any direct interaction with the ad unit beyond viewing — social reactions/comments/shares, story/unit opens, interactive overlay taps, companion banner interactions on audio and CTV), follows (new followers, page likes, artist/podcast/channel follows, or free channel/feed subscribes; paid subscriptions use event_type: subscribe), saves (saves, bookmarks, playlist adds, pins — signals of intent to return), profile_visits (visits to the brand's in-platform page — profile, artist page, channel, or storefront. Does not include external website clicks, which are covered by 'clicks'). **DEPRECATED values** (slated for removal at next major): `attention_seconds` and `attention_score` — these have no industry-graduated definition (DoubleVerify, IAS, Adelaide, TVision, Lumen each define them differently) and cannot be meaningfully optimized for without a vendor binding. Use `kind: 'vendor_metric'` with an explicit `vendor` and `metric_id` instead — that path binds the goal to a specific measurement vendor and reconciles to the same `(vendor, metric_id)` key in delivery's `vendor_metric_values[]`. Sellers MAY reject the deprecated values with `TERMS_REJECTED` and a suggestion to use the `vendor_metric` kind." - ), - ] - standard: Annotated[ - viewability_standard.ViewabilityStandard | None, - Field( - description="Viewability standard the goal is judged against. Required when metric is 'viewable_rate'; optional for 'viewed_seconds' (seller default standard when omitted); not allowed for other metrics. Must be in metric_optimization.supported_viewability_standards when declared. A goal below a same-standard viewability performance_standard never relaxes that standard." - ), - ] = None - vendor: Annotated[ - brand_ref.BrandReference | None, - Field( - description="Measurement vendor judging a viewable_rate or viewed_seconds goal; not allowed for other metrics. When omitted, the seller's default viewability measurement applies. Sellers MUST reject a vendor they cannot optimize against rather than substitute another." - ), - ] = None - reach_unit: Annotated[ - reach_unit_1.ReachUnit | None, - Field( - description="Unit for reach measurement. Required when metric is 'reach'. Must be a value declared in the product's metric_optimization.supported_reach_units." - ), - ] = None - target_frequency: Annotated[ - TargetFrequency | None, - Field( - description="Target frequency band for reach optimization. Only applicable when metric is 'reach'. Frames frequency as an optimization signal: the seller should treat impressions toward entities already within the [min, max] band as lower-value, and impressions toward unreached entities as higher-value. This shifts budget toward fresh reach rather than re-reaching known users. When omitted, the seller maximizes unique reach without a frequency constraint. A hard cap can still be layered via targeting_overlay.frequency_cap if a ceiling is needed." - ), - ] = None - view_duration_seconds: Annotated[ - StrictFloat | None, - Field( - description="Minimum video view duration in seconds that qualifies as a completed_view for this goal. Only applicable when metric is 'completed_views'. When omitted, the seller uses their platform default (typically 2–15 seconds). Common values: 2 (Snap/LinkedIn default), 6 (TikTok), 15 (Snap 15-second views, Meta ThruPlay). Sellers declare which durations they support in metric_optimization.supported_view_durations. Sellers must reject goals with unsupported values — silent rounding would create measurement discrepancies.", - gt=0.0, - ), - ] = None - target: Annotated[ - Target | Target14 | None, - Field( - description='Target for this metric. When omitted, the seller optimizes for maximum metric volume within budget.', - discriminator='kind', - ), - ] = None - priority: Annotated[ - SchemaInt | None, - Field( - description='Relative priority among sibling goals. Lower numbers rank first. Goals without priority follow explicitly prioritized goals. Ties use array order, so the earliest goal at the lowest explicit priority is primary; when all priorities are omitted, the first goal is primary.', - ge=1, - ), - ] = None - - -class OptimizationGoal10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['vendor_metric'] = 'vendor_metric' - vendor: Annotated[ - brand_ref.BrandReference, - Field( - description='Vendor that defines and computes this metric. Same shape as `vendor_metric_values.vendor`, `reporting_capabilities.vendor_metrics[].vendor`, and `vendor_metric_optimization.supported_metrics[].vendor` — symmetric across discovery, capability, commitment, optimization, and reporting surfaces.' - ), - ] - metric_id: Annotated[ - vendor_metric_id.VendorMetricId, - Field( - description="Identifier for the metric within the vendor's vocabulary (e.g., `attention_score`, `attention_seconds`, `gco2e_per_impression`, `awareness_lift`). MUST be present in the vendor's published `measurement.metrics[]` catalog and in the product's `vendor_metric_optimization.supported_metrics[]`." - ), - ] - target: Annotated[ - Target18 | Target19 | None, - Field( - description="Target for this vendor metric. When omitted, the seller optimizes for maximum metric volume / score within budget. `cost_per` and `threshold_rate` semantics mirror the same target kinds on the `metric` kind — units are vendor-defined and depend on the vendor's `measurement.metrics[]` declaration for this `metric_id`.", - discriminator='kind', - ), - ] = None - priority: Annotated[ - SchemaInt | None, - Field( - description='Relative priority among sibling goals. Lower numbers rank first. Goals without priority follow explicitly prioritized goals. Ties use array order, so the earliest goal at the lowest explicit priority is primary; when all priorities are omitted, the first goal is primary.', - ge=1, - ), - ] = None - - -OptimizationGoal = Annotated[ - OptimizationGoal8 | OptimizationGoal9 | OptimizationGoal10, - Field( - description='A single objective function: what to maximize or optimize, in what units, and in what priority order. Used on packages to optimize delivery within one package and on seller-optimized budget allocations to allocate spend across packages. Currency-bearing execution policy belongs in BiddingPolicy in 3.2. Legacy target.cost_per and target.per_ad_spend remain accepted only on package goals for migration and are deprecated. The primary goal is the earliest array entry among goals with the lowest explicit numeric priority; goals without priority follow all explicitly prioritized goals; when all priorities are omitted, the first entry is primary. This array-order tie-break makes duplicate priorities deterministic.', - discriminator='kind', - title='Optimization Goal', - ), -] diff --git a/src/adcp/types/generated_poc/core/outcome_measurement.py b/src/adcp/types/generated_poc/core/outcome_measurement.py deleted file mode 100644 index 95e9e18cf..000000000 --- a/src/adcp/types/generated_poc/core/outcome_measurement.py +++ /dev/null @@ -1,45 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/outcome_measurement.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from . import duration - - -class OutcomeMeasurement(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - str, - Field( - description='Type of measurement', - examples=['incremental_sales_lift', 'brand_lift', 'foot_traffic'], - ), - ] - attribution: Annotated[ - str, - Field( - description='Attribution methodology', - examples=['deterministic_purchase', 'probabilistic'], - ), - ] - window: Annotated[ - duration.Duration | None, - Field( - description='Attribution window as a structured duration (e.g., {"interval": 30, "unit": "days"}).' - ), - ] = None - reporting: Annotated[ - str, - Field( - description='Reporting frequency and format', - examples=['weekly_dashboard', 'real_time_api'], - ), - ] diff --git a/src/adcp/types/generated_poc/core/outcome_target_cost_per.py b/src/adcp/types/generated_poc/core/outcome_target_cost_per.py deleted file mode 100644 index 9f17bf5a1..000000000 --- a/src/adcp/types/generated_poc/core/outcome_target_cost_per.py +++ /dev/null @@ -1,38 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/outcome_target_cost_per.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictFloat - -from ..enums import outcome_target_cost_strength - - -class OutcomeTargetCostPer(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - amount: Annotated[ - StrictFloat, - Field( - description="Average cost amount per goal result, denominated in currency. The answered commercial_terms.bidding.cost_per.amount MUST be greater than or equal to it: the requested amount when it is plannable, otherwise the lowest plannable amount. An amount is plannable when the seller can forecast goal volume at or below (cap) or around (target) it within the buyer's budget, spending at least offer_filters.budget_range.min when present. When the planned spend at the answered amount is below commercial_terms.total_budget, each forecast point MUST carry metrics.spend. A seller MAY return additional proposals at higher amounts under the same strength to show what volume a higher cost buys.", - gt=0.0, - ), - ] - currency: Annotated[ - str, - Field( - description="ISO 4217 currency of amount. BiddingPolicy.cost_per has no currency because it inherits the media-buy currency, and no media buy exists at request time, so the request states it. It becomes the answer's currency: every answering proposal's purchases[].pricing.currency (which bidding amounts use) and forecast.currency MUST equal it, as MUST commercial_terms.total_budget.currency and total_budget_guidance.currency when present; sellers MUST NOT convert currency. It MUST equal offer_filters.budget_range.currency when present. The seller plans within budget_range.max when present, else at or above budget_range.min, and total_budget MUST NOT exceed max or fall below min. A seller rejects a conflict with budget_range, pricing_currencies, the account currency, or the requested products' pricing currencies with INVALID_REQUEST naming criteria.outcome_target.cost_per.", - pattern='^[A-Z]{3}$', - ), - ] - strength: Annotated[ - outcome_target_cost_strength.OutcomeTargetCostStrength, - Field( - description="BiddingPolicy.cost_per strength: `cap` optimizes for an average at or below the amount and accepts underdelivery when necessary; `target` optimizes around the amount while balancing volume and spend. Neither is a per-result or per-auction guarantee. The answer MUST keep this strength: a cap of 3 the seller can only meet at 4.50 returns {amount: 4.50, strength: 'cap'}, never 'target'. A seller that will not plan at any amount under this strength rejects instead." - ), - ] diff --git a/src/adcp/types/generated_poc/core/overlay.py b/src/adcp/types/generated_poc/core/overlay.py deleted file mode 100644 index 7e825a113..000000000 --- a/src/adcp/types/generated_poc/core/overlay.py +++ /dev/null @@ -1,88 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/overlay.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, StrictFloat - - -class Visual(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: Annotated[ - AnyUrl | None, - Field( - description='URL to a theme-neutral overlay graphic (SVG or PNG). Use when a single file works for all backgrounds, e.g. an SVG using CSS custom properties or currentColor.' - ), - ] = None - light: Annotated[ - AnyUrl | None, - Field( - description='URL to the overlay graphic for use on light/bright backgrounds (SVG or PNG)' - ), - ] = None - dark: Annotated[ - AnyUrl | None, - Field(description='URL to the overlay graphic for use on dark backgrounds (SVG or PNG)'), - ] = None - - -class Unit(StrEnum): - px = 'px' - fraction = 'fraction' - inches = 'inches' - cm = 'cm' - mm = 'mm' - pt = 'pt' - - -class Bounds(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - x: Annotated[StrictFloat, Field(description="Horizontal offset from the asset's left edge")] - y: Annotated[StrictFloat, Field(description="Vertical offset from the asset's top edge")] - width: Annotated[StrictFloat, Field(description='Width of the overlay', ge=0.0)] - height: Annotated[StrictFloat, Field(description='Height of the overlay', ge=0.0)] - unit: Annotated[ - Unit, - Field( - description="'px' = absolute pixels from asset top-left. 'fraction' = proportional to asset dimensions (0.0 = edge, 1.0 = opposite edge). 'inches', 'cm', 'mm', 'pt' (1/72 inch) = physical units for print overlays, measured from asset top-left." - ), - ] - - -class Overlay(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - id: Annotated[ - str, - Field( - description="Identifier for this overlay (e.g., 'play_pause', 'volume', 'publisher_logo', 'carousel_prev', 'carousel_next')" - ), - ] - description: Annotated[ - str | None, - Field( - description='Human-readable explanation of what this overlay is and how buyers should account for it' - ), - ] = None - visual: Annotated[ - Visual | None, - Field( - description='Optional visual reference for this overlay element. Useful for creative agents compositing previews and for buyers understanding what will appear over their content. Must include at least one of: url, light, or dark.' - ), - ] = None - bounds: Annotated[ - Bounds, - Field( - description="Position and size of the overlay relative to the asset's own top-left corner. See 'unit' for coordinate interpretation." - ), - ] diff --git a/src/adcp/types/generated_poc/core/package.py b/src/adcp/types/generated_poc/core/package.py deleted file mode 100644 index a30c54a52..000000000 --- a/src/adcp/types/generated_poc/core/package.py +++ /dev/null @@ -1,309 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/package.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat - -from ..enums import canceled_by as canceled_by_1 -from ..enums import pacing as pacing_1 -from ..pricing_options import price_breakdown as price_breakdown_1 -from . import ( - audience_evidence_selection, - bidding_policy, - canonical_format_kind, - catalog, - committed_metric, -) -from . import context as context_1 -from . import creative_assignment -from . import ext as ext_1 -from . import format_id, format_option_ref -from . import measurement_terms as measurement_terms_1 -from . import ( - optimization_goal, - package_format_snapshot, - package_targeting_resolution, - performance_standard, - targeting, -) - - -class Cancellation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - canceled_at: Annotated[ - AwareDatetime, Field(description='ISO 8601 timestamp when this package was canceled.') - ] - canceled_by: Annotated[ - canceled_by_1.CanceledBy, - Field(description='Which party initiated the package cancellation.'), - ] - reason: Annotated[ - str | None, Field(description='Reason the package was canceled.', max_length=500) - ] = None - acknowledged_at: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the seller acknowledged the cancellation. Confirms inventory has been released and billing stopped. Absent until the seller processes the cancellation.' - ), - ] = None - - -class Package(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - package_id: Annotated[str, Field(description="Seller's unique identifier for the package")] - product_id: Annotated[ - str | None, - Field( - description="ID of the product this package is based on. For packages created from an explicit create_media_buy package request, sellers MUST echo the request package's product_id on every response package object that represents that requested package." - ), - ] = None - audience_evidence_selections: Annotated[ - list[audience_evidence_selection.AudienceEvidenceSelection] | None, - Field( - description='Exact immutable audience-evidence snapshots that affected recommendation, eligibility, or package construction. A confirmed package MUST include a package_construction selection matching every buyer audience_evidence_pin and every snapshot used to satisfy package audience_evidence_requirements; this readback remains mandatory on subsequent package read surfaces. This is decision provenance only; applied targeting remains exclusively in targeting_overlay and targeting_resolution.demographics.', - min_length=1, - ), - ] = None - budget: Annotated[ - StrictFloat | None, - Field( - description='Hard lifetime spend cap for this package in the media-buy currency. Every selected pricing option in an AdCP-authored media buy MUST declare that same currency. In seller-optimized allocation mode this is a ceiling, not a current allocation. May be omitted when the package is bounded only by the shared media-buy total.', - ge=0.0, - ), - ] = None - min_spend_target: Annotated[ - StrictFloat | None, - Field( - description='Soft lifetime spend target accepted for this package under seller-optimized budget allocation. This is an allocation preference, not a billing or delivery guarantee.', - ge=0.0, - ), - ] = None - daily_budget_cap: Annotated[ - StrictFloat | None, - Field( - description="The hard package spend ceiling per shared media-buy cap day, in the media buy's currency. Sellers MUST echo this whenever a package daily cap is set. It is a subordinate ceiling, not a reserved or current allocation; the media buy's budget_cap_timezone defines its day boundary.", - ge=0.0, - ), - ] = None - pacing: pacing_1.Pacing | None = None - pricing_option_id: Annotated[ - str | None, - Field( - description="ID of the selected pricing option from the product's pricing_options array" - ), - ] = None - bid_price: Annotated[ - StrictFloat | None, - Field( - deprecated=True, - description='DEPRECATED legacy bidding representation. 3.2 sellers normalize accepted legacy input and SHOULD echo bidding instead. Removed in the next major.', - ge=0.0, - ), - ] = None - bidding: Annotated[ - bidding_policy.BiddingPolicy | None, - Field( - description='Package-authored bidding policy, echoed only when the buyer authored a package override. `{automatic:true}` is an explicit automatic-bidding override. Omission means the package inherits media-buy bidding or, when both scopes are absent, uses provider automatic delivery. Monetary fields are denominated in the media-buy currency. Sellers MUST NOT materialize inherited media-buy policy here.' - ), - ] = None - price_breakdown: Annotated[ - price_breakdown_1.PriceBreakdown | None, - Field( - description="Breakdown of the effective price for this package. On fixed-price packages, echoes the pricing option's breakdown. On auction packages, shows the clearing price breakdown including any commission or settlement terms." - ), - ] = None - impressions: Annotated[ - StrictFloat | None, Field(description='Impression goal for this package', ge=0.0) - ] = None - catalogs: Annotated[ - list[catalog.Catalog] | None, - Field( - description='Catalogs this package promotes. Each catalog MUST have a distinct type (e.g., one product catalog, one store catalog). This constraint is enforced at the application level — sellers MUST reject requests containing multiple catalogs of the same type with a validation_error. Echoed from the create_media_buy request.' - ), - ] = None - format_ids: Annotated[ - list[format_id.FormatReferenceStructuredObject] | None, - Field( - deprecated=True, - description='Deprecated in AdCP 3.2; removed in AdCP 4.0. Legacy named-format IDs supplied for this package on create_media_buy. Sellers SHOULD echo this field whenever the request included it, including dual-emission cases where `format_option_refs` was the winning selector, so read surfaces preserve the original wire contract. Omitted means the request did not carry legacy format_ids unless the seller cannot reconstruct legacy requests created before this field was persisted.', - ), - ] = None - format_option_refs: Annotated[ - list[format_option_ref.FormatOptionReference] | None, - Field( - description='Structured 3.1+ format option references supplied for this package on create_media_buy. Sellers SHOULD echo this field whenever the request included it. Publisher-catalog-backed options are identified by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options are identified by `{ scope: "product", format_option_id }` and resolve only against this package\'s target product. Omitted means the request did not carry format_option_refs unless the seller cannot reconstruct legacy requests created before this field was persisted.', - min_length=1, - ), - ] = None - format_kind: Annotated[ - canonical_format_kind.CanonicalFormatKind | None, - Field( - description='Direct canonical selector supplied for this package on create_media_buy. Sellers SHOULD echo this field whenever the request included it, including informational-echo cases where `format_ids` was the winning selector, so read surfaces preserve the original wire contract.' - ), - ] = None - params: Annotated[ - dict[str, Any] | None, - Field( - description='Parameters for the direct canonical selector in `format_kind`, echoed from the create_media_buy request whenever the request included it. Requires `format_kind`; omitted only when the request did not carry direct canonical params or when the seller cannot reconstruct legacy requests created before this field was persisted.' - ), - ] = None - targeting_overlay: Annotated[ - targeting.TargetingOverlay | None, - Field( - description='Complete effective targeting accepted for this package, including targeting bound through configured product selection plus package-specific targeting. Sellers MUST echo an applied package frequency_cap independently from any MediaBuy root cap. Sellers MUST also echo placement, property, and collection selection so buyers can audit purchased inventory: placements via placement_selection, collections via collection_selection (the committed concrete selectors, materialized even when the selection was produced through collection_list references).' - ), - ] = None - targeting_resolution: Annotated[ - package_targeting_resolution.PackageTargetingResolution | None, - Field( - description="Execution details for the package's accepted targeting. Sellers MUST include targeting_resolution.demographics whenever demographic targeting was requested or applied." - ), - ] = None - measurement_terms: Annotated[ - measurement_terms_1.MeasurementTerms | None, - Field( - description="Agreed billing measurement and makegood terms for this package. Reflects what was negotiated — may differ from the buyer's proposal or the product's defaults. When present, these terms are binding for the package's duration." - ), - ] = None - performance_standards: Annotated[ - list[performance_standard.PerformanceStandard] | None, - Field( - description='Agreed performance standards for this package. When any entry specifies a vendor, creatives assigned to this package MUST include corresponding tracker_script or tracker_pixel assets from that vendor.', - min_length=1, - ), - ] = None - committed_metrics: Annotated[ - list[committed_metric.CommittedMetric] | None, - Field( - description="The binding reporting contract for this package — what the seller has agreed to populate in delivery reports. Each entry carries an explicit `committed_at` timestamp, so the array also serves as the contract amendment ledger: day-1 commitments share `committed_at = create_media_buy.confirmed_at`; mid-flight additions carry their own timestamps. When `create_media_buy.confirmed_at` is null for a provisional buy, sellers MUST omit `committed_metrics` until commitment. The first response that sets `confirmed_at` MAY include the initial committed-metrics set, and each such entry's `committed_at` MUST equal `confirmed_at`. The `missing_metrics` field on `get_media_buy_delivery` reconciles against this list, filtering to entries where `committed_at < reporting_period.end` (a metric committed mid-flight is only audited from its commitment timestamp forward). Sellers stamp the day-1 set on the `create_media_buy` response; mid-flight additions are appended via `update_media_buy` (append-only — sellers MUST reject attempts to modify or remove existing entries with `validation_error`, suggested code: `IMMUTABLE_FIELD`). Optional in v1; absence means the seller does not provide an audit-grade contract and `missing_metrics` falls back to the product's live `available_metrics` (a known audit gap — buyers SHOULD treat absence as 'no audit-grade contract' rather than 'clean delivery'). Each entry uses an explicit `scope` discriminator: `standard` for entries from the closed `available-metric.json` enum, `vendor` for vendor-defined metrics anchored on a BrandRef. Standard entries are symmetric with `by_package[].metric_values`; vendor entries reconcile to `by_package[].vendor_metric_values`; both use `by_package[].missing_metrics` for gaps. The atomic key remains `(scope, metric_id, qualifier)`, with vendor identity included for vendor scope. Replaces the parallel-array design that shipped briefly in #3510.", - examples=[ - [ - { - 'scope': 'standard', - 'metric_id': 'impressions', - 'committed_at': '2026-04-29T10:53:00Z', - }, - { - 'scope': 'standard', - 'metric_id': 'spend', - 'committed_at': '2026-04-29T10:53:00Z', - }, - { - 'scope': 'standard', - 'metric_id': 'completed_views', - 'committed_at': '2026-04-29T10:53:00Z', - }, - { - 'scope': 'vendor', - 'vendor': {'domain': 'attentionvendor.example'}, - 'metric_id': 'attention_units', - 'committed_at': '2026-04-29T10:53:00Z', - }, - { - 'scope': 'standard', - 'metric_id': 'viewable_rate', - 'qualifier': {'viewability_standard': 'mrc'}, - 'committed_at': '2026-05-30T14:22:00Z', - }, - ] - ], - min_length=1, - ), - ] = None - creative_assignments: Annotated[ - list[creative_assignment.CreativeAssignment] | None, - Field( - description='Creative assets assigned to this package, including the committed package-scoped rotation policy. Omitted rotation_mode reads as weighted for backward compatibility; all assignments resolve to one effective mode, and sequential positions are unique within each package-local group.' - ), - ] = None - formats_to_provide: Annotated[ - list[package_format_snapshot.PackageFormatSnapshot] | None, - Field( - description='Immutable canonical creative contracts established for this package. Each entry is a PackageFormatSnapshot of the selected effective Product format declaration. A package whose selected format carries tracker_execution_contract MUST retain and return this checklist even after creative coverage is complete; the live Product is never substituted for the package snapshot.', - min_length=1, - ), - ] = None - formats_pending: Annotated[ - list[package_format_snapshot.PackageFormatSnapshot] | None, - Field( - description='PackageFormatSnapshot entries from formats_to_provide that do not yet have creative coverage through sync_creatives or inline assignment. Every entry MUST equal its formats_to_provide snapshot after RFC 8785 canonicalization and, when product_snapshot_digest is present, carry the identical digest. An empty emitted array means every required format is covered. Absence means readiness was not reported.' - ), - ] = None - format_ids_to_provide: Annotated[ - list[format_id.FormatReferenceStructuredObject] | None, - Field( - deprecated=True, - description='**DEPRECATED in 3.2.** Legacy named-format projection of formats_to_provide retained for older 3.x peers. New sellers emit canonical formats_to_provide declarations.', - ), - ] = None - format_ids_pending: Annotated[ - list[format_id.FormatReferenceStructuredObject] | None, - Field( - deprecated=True, - description='**DEPRECATED in 3.2.** Legacy named-format projection of formats_pending retained for older 3.x peers. New sellers emit canonical formats_pending declarations. An empty emitted array means every projected requirement is covered. Absence means legacy readiness was not reported and MUST NOT be interpreted as full coverage.', - ), - ] = None - optimization_goals: Annotated[ - list[optimization_goal.OptimizationGoal] | None, - Field( - description='Optimization targets for this package. The seller optimizes delivery toward these goals in priority order. Common pattern: event goals (purchase, install) as primary targets at priority 1; metric goals (clicks, views) as secondary proxy signals at priority 2+.', - min_length=1, - ), - ] = None - start_time: Annotated[ - AwareDatetime | None, - Field( - description="Flight start date/time for this package in ISO 8601 format. When omitted, the package inherits the media buy's start_time. Sellers SHOULD always include the resolved value in responses, even when inherited." - ), - ] = None - end_time: Annotated[ - AwareDatetime | None, - Field( - description="Flight end date/time for this package in ISO 8601 format. When omitted, the package inherits the media buy's end_time. Sellers SHOULD always include the resolved value in responses, even when inherited." - ), - ] = None - paused: Annotated[ - StrictBool | None, - Field( - description='Whether this package is paused by the buyer. Paused packages do not deliver impressions. Defaults to false.' - ), - ] = False - canceled: Annotated[ - StrictBool | None, - Field( - description='Whether this package has been canceled. Canceled packages stop delivery and cannot be reactivated. Defaults to false.' - ), - ] = False - cancellation: Annotated[ - Cancellation | None, - Field(description='Cancellation metadata. Present only when canceled is true.'), - ] = None - agency_estimate_number: Annotated[ - str | None, - Field( - description="Agency estimate or authorization number for this package. Echoed from the buyer's request. When present on the package, takes precedence over the media buy-level estimate number.", - max_length=100, - ), - ] = None - creative_deadline: Annotated[ - AwareDatetime | None, - Field( - description="ISO 8601 timestamp for creative upload or change deadline for this package. After this deadline, creative changes are rejected. When absent, the media buy's creative_deadline applies." - ), - ] = None - context: Annotated[ - context_1.ContextObject | None, - Field( - description='Opaque package-level correlation data echoed unchanged in responses, webhooks, and read surfaces. Buyers targeting mixed seller populations SHOULD include a per-package correlation value here, commonly context.buyer_ref, so responses from legacy sellers that do not echo product_id can still be mapped back to the requested product or line item. Sellers MUST preserve this object unchanged and MUST NOT parse it for business logic.' - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/package_delivery_metric_value.py b/src/adcp/types/generated_poc/core/package_delivery_metric_value.py deleted file mode 100644 index e54dda9c1..000000000 --- a/src/adcp/types/generated_poc/core/package_delivery_metric_value.py +++ /dev/null @@ -1,62 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/package_delivery_metric_value.json -# timestamp: 2026-10-04T00:56:30+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..enums import attribution_methodology as attribution_methodology_1 -from ..enums import completion_source as completion_source_1 -from ..enums import lift_dimension as lift_dimension_1 -from ..enums import viewability_standard as viewability_standard_1 -from . import duration -from .delivery_metric_aggregate import Field0 - - -class Qualifier(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - viewability_standard: Annotated[ - viewability_standard_1.ViewabilityStandard | None, - Field( - description='Viewability standard under which this row was measured. MRC and GroupM define materially different thresholds; never sum across standards.' - ), - ] = None - completion_source: Annotated[ - completion_source_1.CompletionSource | None, - Field( - description='Source of `completion_rate` attestation for this row — `seller_attested` from player/ad server, `vendor_attested` from a third-party measurement vendor. The two paths can yield different rates; never sum across sources.' - ), - ] = None - attribution_methodology: Annotated[ - attribution_methodology_1.AttributionMethodology | None, - Field( - description='Attribution methodology under which this outcome row was computed (`deterministic_purchase`, `probabilistic`, `panel_based`, `modeled`). Outcome metrics measured under different methodologies represent materially different numbers; never sum across methodologies.' - ), - ] = None - attribution_window: Annotated[ - duration.Duration | None, - Field( - description="Attribution window for this outcome row. Object-valued duration (`{interval, unit}`), not a shorthand string. Outcome metrics measured over different windows represent the same metric over different time periods; the partition keeps them as separate rows so buyers don't accidentally aggregate." - ), - ] = None - lift_dimension: Annotated[ - lift_dimension_1.LiftDimension | None, - Field( - description='Brand-lift dimension this row represents (awareness, consideration, favorability, purchase intent, or ad recall). Required when `metric_id` is `brand_lift`. Each dimension is a separate surveyed outcome with its own sample size and confidence interval; never sum across dimensions.' - ), - ] = None - - -class PackageDeliveryMetricValue(Field0): - qualifier: Annotated[ - Qualifier, - Field( - description="Qualifier keys disambiguating this row from sibling rows under the same `metric_id`. Symmetric with `committed_metrics.qualifier` today; expected to diverge in future minors as transparency disclosures buyers don't commit to ship delivery-only. Closed (`additionalProperties: false`) — new qualifier keys ship explicitly." - ), - ] diff --git a/src/adcp/types/generated_poc/core/package_format_snapshot.py b/src/adcp/types/generated_poc/core/package_format_snapshot.py deleted file mode 100644 index f7caf677d..000000000 --- a/src/adcp/types/generated_poc/core/package_format_snapshot.py +++ /dev/null @@ -1,2457 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/package_format_snapshot.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, StrictBool - -from ..enums import channels, daast_version, vast_version -from ..formats.canonical import ( - agent_placement, - audio_daast, - audio_hosted, - audio_vast, - coordinated_placements, - display_tag, - html5, - image, - image_carousel, - native_in_feed, - responsive_creative, - seller_rendered_stateful_display, - sponsored_placement, - video_hosted, - video_vast, -) -from . import ( - creative_locale_policy, - format_id, - macro_resolution_capability, - placement_ref, - platform_extension_ref, -) -from . import tracker_execution_contract as tracker_execution_contract_1 - - -class SellerPreference(StrEnum): - preferred = 'preferred' - accepted = 'accepted' - discouraged = 'discouraged' - - -class PackageFormatSnapshot17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - product_id: Annotated[ - str | None, - Field( - description='Product whose effective selected format was snapshotted. Required whenever tracker_execution_contract is present.', - min_length=1, - ), - ] = None - placement_refs: Annotated[ - list[placement_ref.PlacementReference] | None, - Field( - description='Nonempty, duplicate-free effective placement set, sorted by publisher_domain then placement_id using ascending UTF-8 bytes without Unicode normalization. Omission binds the product-wide common effective contract; it never means an inferred list of all current placements.', - min_length=1, - ), - ] = None - execution_vast_version: Annotated[ - vast_version.VastVersion | None, - Field( - description='Exact VAST execution version selected for first-class VAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' - ), - ] = None - execution_daast_version: Annotated[ - daast_version.DaastVersion | None, - Field( - description='Exact DAAST execution version selected for first-class DAAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' - ), - ] = None - tracker_execution_contract_digest: Annotated[ - str | None, - Field( - description='SHA-256 of RFC 8785 canonical JSON for tracker_execution_contract alone. Required if and only if the snapshot contains that contract.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - product_snapshot_digest: Annotated[ - str | None, - Field( - description="Immutable SHA-256 digest of the package format snapshot's closed product-binding preimage. Required for every contract-bearing snapshot and paired with product_id whenever present.", - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - format_kind: Any - params: Any - - -class PackageFormatSnapshot1(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['image'] = 'image' - params: image.CanonicalFormatImage - - -class PackageFormatSnapshot2(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['html5'] = 'html5' - params: html5.CanonicalFormatHtml5Banner - - -class PackageFormatSnapshot3(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['display_tag'] = 'display_tag' - params: display_tag.CanonicalFormatDisplayTag - - -class PackageFormatSnapshot4(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['image_carousel'] = 'image_carousel' - params: image_carousel.CanonicalFormatImageCarousel - - -class PackageFormatSnapshot5(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['video_hosted'] = 'video_hosted' - params: video_hosted.CanonicalFormatHostedVideo - - -class PackageFormatSnapshot6(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['video_vast'] = 'video_vast' - params: video_vast.CanonicalFormatVastVideo - - -class PackageFormatSnapshot7(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['audio_hosted'] = 'audio_hosted' - params: audio_hosted.CanonicalFormatHostedAudio - - -class PackageFormatSnapshot8(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['audio_vast'] = 'audio_vast' - params: audio_vast.CanonicalFormatVastAudio - - -class PackageFormatSnapshot9(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['audio_daast'] = 'audio_daast' - params: audio_daast.CanonicalFormatDaastAudio - - -class PackageFormatSnapshot10(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['sponsored_placement'] = 'sponsored_placement' - params: sponsored_placement.CanonicalFormatSponsoredPlacementRetailMediaCatalogDriven - - -class PackageFormatSnapshot11(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['native_in_feed'] = 'native_in_feed' - params: native_in_feed.CanonicalFormatNativeInFeed - - -class PackageFormatSnapshot12(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['responsive_creative'] = 'responsive_creative' - params: responsive_creative.CanonicalFormatResponsiveCreative - - -class PackageFormatSnapshot13(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['agent_placement'] = 'agent_placement' - params: agent_placement.CanonicalFormatAgentPlacementAiSurfaceSponsoredPlacement - - -class PackageFormatSnapshot14(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['seller_rendered_stateful_display'] = 'seller_rendered_stateful_display' - params: seller_rendered_stateful_display.CanonicalFormatSellerRenderedStatefulDisplay - - -class PackageFormatSnapshot15(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['coordinated_placements'] = 'coordinated_placements' - params: coordinated_placements.CanonicalFormatCoordinatedPlacements - - -class PackageFormatSnapshot16(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['custom'] = 'custom' - params: Annotated[ - dict[str, Any], - Field( - description="Custom shape's params. Validated against the schema fetched from `format_schema.uri` at the cached `format_schema.digest`." - ), - ] - - -class PackageFormatSnapshot18(PackageFormatSnapshot1): - model_config = ConfigDict( - extra='allow', - ) - product_id: Annotated[ - str | None, - Field( - description='Product whose effective selected format was snapshotted. Required whenever tracker_execution_contract is present.', - min_length=1, - ), - ] = None - placement_refs: Annotated[ - list[placement_ref.PlacementReference] | None, - Field( - description='Nonempty, duplicate-free effective placement set, sorted by publisher_domain then placement_id using ascending UTF-8 bytes without Unicode normalization. Omission binds the product-wide common effective contract; it never means an inferred list of all current placements.', - min_length=1, - ), - ] = None - execution_vast_version: Annotated[ - vast_version.VastVersion | None, - Field( - description='Exact VAST execution version selected for first-class VAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' - ), - ] = None - execution_daast_version: Annotated[ - daast_version.DaastVersion | None, - Field( - description='Exact DAAST execution version selected for first-class DAAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' - ), - ] = None - tracker_execution_contract_digest: Annotated[ - str | None, - Field( - description='SHA-256 of RFC 8785 canonical JSON for tracker_execution_contract alone. Required if and only if the snapshot contains that contract.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - product_snapshot_digest: Annotated[ - str | None, - Field( - description="Immutable SHA-256 digest of the package format snapshot's closed product-binding preimage. Required for every contract-bearing snapshot and paired with product_id whenever present.", - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - format_kind: Literal['image'] = 'image' - params: image.CanonicalFormatImage - - -class PackageFormatSnapshot19(PackageFormatSnapshot2): - model_config = ConfigDict( - extra='allow', - ) - product_id: Annotated[ - str | None, - Field( - description='Product whose effective selected format was snapshotted. Required whenever tracker_execution_contract is present.', - min_length=1, - ), - ] = None - placement_refs: Annotated[ - list[placement_ref.PlacementReference] | None, - Field( - description='Nonempty, duplicate-free effective placement set, sorted by publisher_domain then placement_id using ascending UTF-8 bytes without Unicode normalization. Omission binds the product-wide common effective contract; it never means an inferred list of all current placements.', - min_length=1, - ), - ] = None - execution_vast_version: Annotated[ - vast_version.VastVersion | None, - Field( - description='Exact VAST execution version selected for first-class VAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' - ), - ] = None - execution_daast_version: Annotated[ - daast_version.DaastVersion | None, - Field( - description='Exact DAAST execution version selected for first-class DAAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' - ), - ] = None - tracker_execution_contract_digest: Annotated[ - str | None, - Field( - description='SHA-256 of RFC 8785 canonical JSON for tracker_execution_contract alone. Required if and only if the snapshot contains that contract.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - product_snapshot_digest: Annotated[ - str | None, - Field( - description="Immutable SHA-256 digest of the package format snapshot's closed product-binding preimage. Required for every contract-bearing snapshot and paired with product_id whenever present.", - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - format_kind: Literal['html5'] = 'html5' - params: html5.CanonicalFormatHtml5Banner - - -class PackageFormatSnapshot20(PackageFormatSnapshot3): - model_config = ConfigDict( - extra='allow', - ) - product_id: Annotated[ - str | None, - Field( - description='Product whose effective selected format was snapshotted. Required whenever tracker_execution_contract is present.', - min_length=1, - ), - ] = None - placement_refs: Annotated[ - list[placement_ref.PlacementReference] | None, - Field( - description='Nonempty, duplicate-free effective placement set, sorted by publisher_domain then placement_id using ascending UTF-8 bytes without Unicode normalization. Omission binds the product-wide common effective contract; it never means an inferred list of all current placements.', - min_length=1, - ), - ] = None - execution_vast_version: Annotated[ - vast_version.VastVersion | None, - Field( - description='Exact VAST execution version selected for first-class VAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' - ), - ] = None - execution_daast_version: Annotated[ - daast_version.DaastVersion | None, - Field( - description='Exact DAAST execution version selected for first-class DAAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' - ), - ] = None - tracker_execution_contract_digest: Annotated[ - str | None, - Field( - description='SHA-256 of RFC 8785 canonical JSON for tracker_execution_contract alone. Required if and only if the snapshot contains that contract.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - product_snapshot_digest: Annotated[ - str | None, - Field( - description="Immutable SHA-256 digest of the package format snapshot's closed product-binding preimage. Required for every contract-bearing snapshot and paired with product_id whenever present.", - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - format_kind: Literal['display_tag'] = 'display_tag' - params: display_tag.CanonicalFormatDisplayTag - - -class PackageFormatSnapshot21(PackageFormatSnapshot4): - model_config = ConfigDict( - extra='allow', - ) - product_id: Annotated[ - str | None, - Field( - description='Product whose effective selected format was snapshotted. Required whenever tracker_execution_contract is present.', - min_length=1, - ), - ] = None - placement_refs: Annotated[ - list[placement_ref.PlacementReference] | None, - Field( - description='Nonempty, duplicate-free effective placement set, sorted by publisher_domain then placement_id using ascending UTF-8 bytes without Unicode normalization. Omission binds the product-wide common effective contract; it never means an inferred list of all current placements.', - min_length=1, - ), - ] = None - execution_vast_version: Annotated[ - vast_version.VastVersion | None, - Field( - description='Exact VAST execution version selected for first-class VAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' - ), - ] = None - execution_daast_version: Annotated[ - daast_version.DaastVersion | None, - Field( - description='Exact DAAST execution version selected for first-class DAAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' - ), - ] = None - tracker_execution_contract_digest: Annotated[ - str | None, - Field( - description='SHA-256 of RFC 8785 canonical JSON for tracker_execution_contract alone. Required if and only if the snapshot contains that contract.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - product_snapshot_digest: Annotated[ - str | None, - Field( - description="Immutable SHA-256 digest of the package format snapshot's closed product-binding preimage. Required for every contract-bearing snapshot and paired with product_id whenever present.", - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - format_kind: Literal['image_carousel'] = 'image_carousel' - params: image_carousel.CanonicalFormatImageCarousel - - -class PackageFormatSnapshot22(PackageFormatSnapshot5): - model_config = ConfigDict( - extra='allow', - ) - product_id: Annotated[ - str | None, - Field( - description='Product whose effective selected format was snapshotted. Required whenever tracker_execution_contract is present.', - min_length=1, - ), - ] = None - placement_refs: Annotated[ - list[placement_ref.PlacementReference] | None, - Field( - description='Nonempty, duplicate-free effective placement set, sorted by publisher_domain then placement_id using ascending UTF-8 bytes without Unicode normalization. Omission binds the product-wide common effective contract; it never means an inferred list of all current placements.', - min_length=1, - ), - ] = None - execution_vast_version: Annotated[ - vast_version.VastVersion | None, - Field( - description='Exact VAST execution version selected for first-class VAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' - ), - ] = None - execution_daast_version: Annotated[ - daast_version.DaastVersion | None, - Field( - description='Exact DAAST execution version selected for first-class DAAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' - ), - ] = None - tracker_execution_contract_digest: Annotated[ - str | None, - Field( - description='SHA-256 of RFC 8785 canonical JSON for tracker_execution_contract alone. Required if and only if the snapshot contains that contract.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - product_snapshot_digest: Annotated[ - str | None, - Field( - description="Immutable SHA-256 digest of the package format snapshot's closed product-binding preimage. Required for every contract-bearing snapshot and paired with product_id whenever present.", - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - format_kind: Literal['video_hosted'] = 'video_hosted' - params: video_hosted.CanonicalFormatHostedVideo - - -class PackageFormatSnapshot23(PackageFormatSnapshot6): - model_config = ConfigDict( - extra='allow', - ) - product_id: Annotated[ - str | None, - Field( - description='Product whose effective selected format was snapshotted. Required whenever tracker_execution_contract is present.', - min_length=1, - ), - ] = None - placement_refs: Annotated[ - list[placement_ref.PlacementReference] | None, - Field( - description='Nonempty, duplicate-free effective placement set, sorted by publisher_domain then placement_id using ascending UTF-8 bytes without Unicode normalization. Omission binds the product-wide common effective contract; it never means an inferred list of all current placements.', - min_length=1, - ), - ] = None - execution_vast_version: Annotated[ - vast_version.VastVersion | None, - Field( - description='Exact VAST execution version selected for first-class VAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' - ), - ] = None - execution_daast_version: Annotated[ - daast_version.DaastVersion | None, - Field( - description='Exact DAAST execution version selected for first-class DAAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' - ), - ] = None - tracker_execution_contract_digest: Annotated[ - str | None, - Field( - description='SHA-256 of RFC 8785 canonical JSON for tracker_execution_contract alone. Required if and only if the snapshot contains that contract.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - product_snapshot_digest: Annotated[ - str | None, - Field( - description="Immutable SHA-256 digest of the package format snapshot's closed product-binding preimage. Required for every contract-bearing snapshot and paired with product_id whenever present.", - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - format_kind: Literal['video_vast'] = 'video_vast' - params: video_vast.CanonicalFormatVastVideo - - -class PackageFormatSnapshot24(PackageFormatSnapshot7): - model_config = ConfigDict( - extra='allow', - ) - product_id: Annotated[ - str | None, - Field( - description='Product whose effective selected format was snapshotted. Required whenever tracker_execution_contract is present.', - min_length=1, - ), - ] = None - placement_refs: Annotated[ - list[placement_ref.PlacementReference] | None, - Field( - description='Nonempty, duplicate-free effective placement set, sorted by publisher_domain then placement_id using ascending UTF-8 bytes without Unicode normalization. Omission binds the product-wide common effective contract; it never means an inferred list of all current placements.', - min_length=1, - ), - ] = None - execution_vast_version: Annotated[ - vast_version.VastVersion | None, - Field( - description='Exact VAST execution version selected for first-class VAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' - ), - ] = None - execution_daast_version: Annotated[ - daast_version.DaastVersion | None, - Field( - description='Exact DAAST execution version selected for first-class DAAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' - ), - ] = None - tracker_execution_contract_digest: Annotated[ - str | None, - Field( - description='SHA-256 of RFC 8785 canonical JSON for tracker_execution_contract alone. Required if and only if the snapshot contains that contract.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - product_snapshot_digest: Annotated[ - str | None, - Field( - description="Immutable SHA-256 digest of the package format snapshot's closed product-binding preimage. Required for every contract-bearing snapshot and paired with product_id whenever present.", - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - format_kind: Literal['audio_hosted'] = 'audio_hosted' - params: audio_hosted.CanonicalFormatHostedAudio - - -class PackageFormatSnapshot25(PackageFormatSnapshot8): - model_config = ConfigDict( - extra='allow', - ) - product_id: Annotated[ - str | None, - Field( - description='Product whose effective selected format was snapshotted. Required whenever tracker_execution_contract is present.', - min_length=1, - ), - ] = None - placement_refs: Annotated[ - list[placement_ref.PlacementReference] | None, - Field( - description='Nonempty, duplicate-free effective placement set, sorted by publisher_domain then placement_id using ascending UTF-8 bytes without Unicode normalization. Omission binds the product-wide common effective contract; it never means an inferred list of all current placements.', - min_length=1, - ), - ] = None - execution_vast_version: Annotated[ - vast_version.VastVersion | None, - Field( - description='Exact VAST execution version selected for first-class VAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' - ), - ] = None - execution_daast_version: Annotated[ - daast_version.DaastVersion | None, - Field( - description='Exact DAAST execution version selected for first-class DAAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' - ), - ] = None - tracker_execution_contract_digest: Annotated[ - str | None, - Field( - description='SHA-256 of RFC 8785 canonical JSON for tracker_execution_contract alone. Required if and only if the snapshot contains that contract.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - product_snapshot_digest: Annotated[ - str | None, - Field( - description="Immutable SHA-256 digest of the package format snapshot's closed product-binding preimage. Required for every contract-bearing snapshot and paired with product_id whenever present.", - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - format_kind: Literal['audio_vast'] = 'audio_vast' - params: audio_vast.CanonicalFormatVastAudio - - -class PackageFormatSnapshot26(PackageFormatSnapshot9): - model_config = ConfigDict( - extra='allow', - ) - product_id: Annotated[ - str | None, - Field( - description='Product whose effective selected format was snapshotted. Required whenever tracker_execution_contract is present.', - min_length=1, - ), - ] = None - placement_refs: Annotated[ - list[placement_ref.PlacementReference] | None, - Field( - description='Nonempty, duplicate-free effective placement set, sorted by publisher_domain then placement_id using ascending UTF-8 bytes without Unicode normalization. Omission binds the product-wide common effective contract; it never means an inferred list of all current placements.', - min_length=1, - ), - ] = None - execution_vast_version: Annotated[ - vast_version.VastVersion | None, - Field( - description='Exact VAST execution version selected for first-class VAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' - ), - ] = None - execution_daast_version: Annotated[ - daast_version.DaastVersion | None, - Field( - description='Exact DAAST execution version selected for first-class DAAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' - ), - ] = None - tracker_execution_contract_digest: Annotated[ - str | None, - Field( - description='SHA-256 of RFC 8785 canonical JSON for tracker_execution_contract alone. Required if and only if the snapshot contains that contract.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - product_snapshot_digest: Annotated[ - str | None, - Field( - description="Immutable SHA-256 digest of the package format snapshot's closed product-binding preimage. Required for every contract-bearing snapshot and paired with product_id whenever present.", - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - format_kind: Literal['audio_daast'] = 'audio_daast' - params: audio_daast.CanonicalFormatDaastAudio - - -class PackageFormatSnapshot27(PackageFormatSnapshot10): - model_config = ConfigDict( - extra='allow', - ) - product_id: Annotated[ - str | None, - Field( - description='Product whose effective selected format was snapshotted. Required whenever tracker_execution_contract is present.', - min_length=1, - ), - ] = None - placement_refs: Annotated[ - list[placement_ref.PlacementReference] | None, - Field( - description='Nonempty, duplicate-free effective placement set, sorted by publisher_domain then placement_id using ascending UTF-8 bytes without Unicode normalization. Omission binds the product-wide common effective contract; it never means an inferred list of all current placements.', - min_length=1, - ), - ] = None - execution_vast_version: Annotated[ - vast_version.VastVersion | None, - Field( - description='Exact VAST execution version selected for first-class VAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' - ), - ] = None - execution_daast_version: Annotated[ - daast_version.DaastVersion | None, - Field( - description='Exact DAAST execution version selected for first-class DAAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' - ), - ] = None - tracker_execution_contract_digest: Annotated[ - str | None, - Field( - description='SHA-256 of RFC 8785 canonical JSON for tracker_execution_contract alone. Required if and only if the snapshot contains that contract.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - product_snapshot_digest: Annotated[ - str | None, - Field( - description="Immutable SHA-256 digest of the package format snapshot's closed product-binding preimage. Required for every contract-bearing snapshot and paired with product_id whenever present.", - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - format_kind: Literal['sponsored_placement'] = 'sponsored_placement' - params: sponsored_placement.CanonicalFormatSponsoredPlacementRetailMediaCatalogDriven - - -class PackageFormatSnapshot28(PackageFormatSnapshot11): - model_config = ConfigDict( - extra='allow', - ) - product_id: Annotated[ - str | None, - Field( - description='Product whose effective selected format was snapshotted. Required whenever tracker_execution_contract is present.', - min_length=1, - ), - ] = None - placement_refs: Annotated[ - list[placement_ref.PlacementReference] | None, - Field( - description='Nonempty, duplicate-free effective placement set, sorted by publisher_domain then placement_id using ascending UTF-8 bytes without Unicode normalization. Omission binds the product-wide common effective contract; it never means an inferred list of all current placements.', - min_length=1, - ), - ] = None - execution_vast_version: Annotated[ - vast_version.VastVersion | None, - Field( - description='Exact VAST execution version selected for first-class VAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' - ), - ] = None - execution_daast_version: Annotated[ - daast_version.DaastVersion | None, - Field( - description='Exact DAAST execution version selected for first-class DAAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' - ), - ] = None - tracker_execution_contract_digest: Annotated[ - str | None, - Field( - description='SHA-256 of RFC 8785 canonical JSON for tracker_execution_contract alone. Required if and only if the snapshot contains that contract.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - product_snapshot_digest: Annotated[ - str | None, - Field( - description="Immutable SHA-256 digest of the package format snapshot's closed product-binding preimage. Required for every contract-bearing snapshot and paired with product_id whenever present.", - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - format_kind: Literal['native_in_feed'] = 'native_in_feed' - params: native_in_feed.CanonicalFormatNativeInFeed - - -class PackageFormatSnapshot29(PackageFormatSnapshot12): - model_config = ConfigDict( - extra='allow', - ) - product_id: Annotated[ - str | None, - Field( - description='Product whose effective selected format was snapshotted. Required whenever tracker_execution_contract is present.', - min_length=1, - ), - ] = None - placement_refs: Annotated[ - list[placement_ref.PlacementReference] | None, - Field( - description='Nonempty, duplicate-free effective placement set, sorted by publisher_domain then placement_id using ascending UTF-8 bytes without Unicode normalization. Omission binds the product-wide common effective contract; it never means an inferred list of all current placements.', - min_length=1, - ), - ] = None - execution_vast_version: Annotated[ - vast_version.VastVersion | None, - Field( - description='Exact VAST execution version selected for first-class VAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' - ), - ] = None - execution_daast_version: Annotated[ - daast_version.DaastVersion | None, - Field( - description='Exact DAAST execution version selected for first-class DAAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' - ), - ] = None - tracker_execution_contract_digest: Annotated[ - str | None, - Field( - description='SHA-256 of RFC 8785 canonical JSON for tracker_execution_contract alone. Required if and only if the snapshot contains that contract.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - product_snapshot_digest: Annotated[ - str | None, - Field( - description="Immutable SHA-256 digest of the package format snapshot's closed product-binding preimage. Required for every contract-bearing snapshot and paired with product_id whenever present.", - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - format_kind: Literal['responsive_creative'] = 'responsive_creative' - params: responsive_creative.CanonicalFormatResponsiveCreative - - -class PackageFormatSnapshot30(PackageFormatSnapshot13): - model_config = ConfigDict( - extra='allow', - ) - product_id: Annotated[ - str | None, - Field( - description='Product whose effective selected format was snapshotted. Required whenever tracker_execution_contract is present.', - min_length=1, - ), - ] = None - placement_refs: Annotated[ - list[placement_ref.PlacementReference] | None, - Field( - description='Nonempty, duplicate-free effective placement set, sorted by publisher_domain then placement_id using ascending UTF-8 bytes without Unicode normalization. Omission binds the product-wide common effective contract; it never means an inferred list of all current placements.', - min_length=1, - ), - ] = None - execution_vast_version: Annotated[ - vast_version.VastVersion | None, - Field( - description='Exact VAST execution version selected for first-class VAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' - ), - ] = None - execution_daast_version: Annotated[ - daast_version.DaastVersion | None, - Field( - description='Exact DAAST execution version selected for first-class DAAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' - ), - ] = None - tracker_execution_contract_digest: Annotated[ - str | None, - Field( - description='SHA-256 of RFC 8785 canonical JSON for tracker_execution_contract alone. Required if and only if the snapshot contains that contract.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - product_snapshot_digest: Annotated[ - str | None, - Field( - description="Immutable SHA-256 digest of the package format snapshot's closed product-binding preimage. Required for every contract-bearing snapshot and paired with product_id whenever present.", - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - format_kind: Literal['agent_placement'] = 'agent_placement' - params: agent_placement.CanonicalFormatAgentPlacementAiSurfaceSponsoredPlacement - - -class PackageFormatSnapshot31(PackageFormatSnapshot14): - model_config = ConfigDict( - extra='allow', - ) - product_id: Annotated[ - str | None, - Field( - description='Product whose effective selected format was snapshotted. Required whenever tracker_execution_contract is present.', - min_length=1, - ), - ] = None - placement_refs: Annotated[ - list[placement_ref.PlacementReference] | None, - Field( - description='Nonempty, duplicate-free effective placement set, sorted by publisher_domain then placement_id using ascending UTF-8 bytes without Unicode normalization. Omission binds the product-wide common effective contract; it never means an inferred list of all current placements.', - min_length=1, - ), - ] = None - execution_vast_version: Annotated[ - vast_version.VastVersion | None, - Field( - description='Exact VAST execution version selected for first-class VAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' - ), - ] = None - execution_daast_version: Annotated[ - daast_version.DaastVersion | None, - Field( - description='Exact DAAST execution version selected for first-class DAAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' - ), - ] = None - tracker_execution_contract_digest: Annotated[ - str | None, - Field( - description='SHA-256 of RFC 8785 canonical JSON for tracker_execution_contract alone. Required if and only if the snapshot contains that contract.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - product_snapshot_digest: Annotated[ - str | None, - Field( - description="Immutable SHA-256 digest of the package format snapshot's closed product-binding preimage. Required for every contract-bearing snapshot and paired with product_id whenever present.", - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - format_kind: Literal['seller_rendered_stateful_display'] = 'seller_rendered_stateful_display' - params: seller_rendered_stateful_display.CanonicalFormatSellerRenderedStatefulDisplay - - -class PackageFormatSnapshot32(PackageFormatSnapshot15): - model_config = ConfigDict( - extra='allow', - ) - product_id: Annotated[ - str | None, - Field( - description='Product whose effective selected format was snapshotted. Required whenever tracker_execution_contract is present.', - min_length=1, - ), - ] = None - placement_refs: Annotated[ - list[placement_ref.PlacementReference] | None, - Field( - description='Nonempty, duplicate-free effective placement set, sorted by publisher_domain then placement_id using ascending UTF-8 bytes without Unicode normalization. Omission binds the product-wide common effective contract; it never means an inferred list of all current placements.', - min_length=1, - ), - ] = None - execution_vast_version: Annotated[ - vast_version.VastVersion | None, - Field( - description='Exact VAST execution version selected for first-class VAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' - ), - ] = None - execution_daast_version: Annotated[ - daast_version.DaastVersion | None, - Field( - description='Exact DAAST execution version selected for first-class DAAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' - ), - ] = None - tracker_execution_contract_digest: Annotated[ - str | None, - Field( - description='SHA-256 of RFC 8785 canonical JSON for tracker_execution_contract alone. Required if and only if the snapshot contains that contract.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - product_snapshot_digest: Annotated[ - str | None, - Field( - description="Immutable SHA-256 digest of the package format snapshot's closed product-binding preimage. Required for every contract-bearing snapshot and paired with product_id whenever present.", - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - format_kind: Literal['coordinated_placements'] = 'coordinated_placements' - params: coordinated_placements.CanonicalFormatCoordinatedPlacements - - -class PackageFormatSnapshot33(PackageFormatSnapshot16): - model_config = ConfigDict( - extra='allow', - ) - product_id: Annotated[ - str | None, - Field( - description='Product whose effective selected format was snapshotted. Required whenever tracker_execution_contract is present.', - min_length=1, - ), - ] = None - placement_refs: Annotated[ - list[placement_ref.PlacementReference] | None, - Field( - description='Nonempty, duplicate-free effective placement set, sorted by publisher_domain then placement_id using ascending UTF-8 bytes without Unicode normalization. Omission binds the product-wide common effective contract; it never means an inferred list of all current placements.', - min_length=1, - ), - ] = None - execution_vast_version: Annotated[ - vast_version.VastVersion | None, - Field( - description='Exact VAST execution version selected for first-class VAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' - ), - ] = None - execution_daast_version: Annotated[ - daast_version.DaastVersion | None, - Field( - description='Exact DAAST execution version selected for first-class DAAST tracker matching when no sibling delivery document supplies one. The value is immutable for the package.' - ), - ] = None - tracker_execution_contract_digest: Annotated[ - str | None, - Field( - description='SHA-256 of RFC 8785 canonical JSON for tracker_execution_contract alone. Required if and only if the snapshot contains that contract.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - product_snapshot_digest: Annotated[ - str | None, - Field( - description="Immutable SHA-256 digest of the package format snapshot's closed product-binding preimage. Required for every contract-bearing snapshot and paired with product_id whenever present.", - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - format_kind: Literal['custom'] = 'custom' - params: Annotated[dict[str, Any], Field(description="Custom shape's params. Validated against the schema fetched from `format_schema.uri` at the cached `format_schema.digest`.")] - - -PackageFormatSnapshot = Annotated[ - PackageFormatSnapshot18 - | PackageFormatSnapshot19 - | PackageFormatSnapshot20 - | PackageFormatSnapshot21 - | PackageFormatSnapshot22 - | PackageFormatSnapshot23 - | PackageFormatSnapshot24 - | PackageFormatSnapshot25 - | PackageFormatSnapshot26 - | PackageFormatSnapshot27 - | PackageFormatSnapshot28 - | PackageFormatSnapshot29 - | PackageFormatSnapshot30 - | PackageFormatSnapshot31 - | PackageFormatSnapshot32 - | PackageFormatSnapshot33, - Field( - description="Immutable package-time snapshot of one selected Product format declaration. Product-format fields remain flattened at the root for 3.x compatibility; product, placement, exact execution-version, and digest fields bind the seller's package-time decision. A contract-bearing snapshot is durably identified even when no preview or observation feature is offered.", - title='Package Format Snapshot', - ), -] diff --git a/src/adcp/types/generated_poc/core/package_signal_targeting.py b/src/adcp/types/generated_poc/core/package_signal_targeting.py deleted file mode 100644 index d96d92c3d..000000000 --- a/src/adcp/types/generated_poc/core/package_signal_targeting.py +++ /dev/null @@ -1,171 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/package_signal_targeting.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictFloat - -from . import activation_key as activation_key_1 -from . import signal_ref as signal_ref_1 - - -class PackageSignalTargeting4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - activation_key_1.ActivationKey | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.' - ), - ] = None - - -class PackageSignalTargeting1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[signal_ref_1.SignalRef, Field(description='Named signal being targeted.')] - value_type: Annotated[Literal['binary'], Field(description='Discriminator for binary signals.')] = 'binary' - value: Annotated[ - Literal[True], - Field( - description='Binary package signal entries match users for whom the signal is true. Use the parent group operator for include/exclude.' - ), - ] - - -class PackageSignalTargeting2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[signal_ref_1.SignalRef, Field(description='Named signal being targeted.')] - value_type: Annotated[ - Literal['categorical'], Field(description='Discriminator for categorical signals.') - ] = 'categorical' - values: Annotated[ - list[str], - Field( - description='Values to target. Users with any of these values match the expression.', - min_length=1, - ), - ] - - -class PackageSignalTargeting3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[signal_ref_1.SignalRef, Field(description='Named signal being targeted.')] - value_type: Annotated[ - Literal['numeric'], Field(description='Discriminator for numeric signals.') - ] = 'numeric' - min_value: Annotated[ - StrictFloat | None, - Field( - description="Minimum value, inclusive. Omit for no minimum. Should be within the signal definition's range when declared." - ), - ] = None - max_value: Annotated[ - StrictFloat | None, - Field( - description="Maximum value, inclusive. Omit for no maximum. Should be within the signal definition's range when declared." - ), - ] = None - - -class PackageSignalTargeting5(PackageSignalTargeting1, PackageSignalTargeting4): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - activation_key_1.ActivationKey | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.' - ), - ] = None - - -class PackageSignalTargeting6(PackageSignalTargeting2, PackageSignalTargeting4): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - activation_key_1.ActivationKey | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.' - ), - ] = None - - -class PackageSignalTargeting7(PackageSignalTargeting3, PackageSignalTargeting4): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - activation_key_1.ActivationKey | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.' - ), - ] = None - - -PackageSignalTargeting = Annotated[ - PackageSignalTargeting5 | PackageSignalTargeting6 | PackageSignalTargeting7, - Field( - description="Buy-time selection of one seller-offered signal inside a package signal targeting group. The signal_ref uses scope 'product' for a product-local signal option, scope 'data_provider' for a signal defined in a data provider's published adagents.json signals[], or scope 'signal_source' for a source-native signal that is not published in adagents.json signals[]. The selected product's inline Product.signal_targeting_options, get_signals feed when inline options are omitted, and signal_targeting_rules define buy-time eligibility. Inclusion and exclusion are controlled by the parent group operator: use operator 'any' to include users matching the signal expression and operator 'none' to exclude users matching the signal expression. For binary signals, value MUST be true; do not use value=false for exclusion inside signal_targeting_groups. Use audience_include/audience_exclude only for buyer-managed first-party audiences registered through sync_audiences.", - title='Package Signal Targeting', - ), -] diff --git a/src/adcp/types/generated_poc/core/package_signal_targeting_group.py b/src/adcp/types/generated_poc/core/package_signal_targeting_group.py deleted file mode 100644 index ccc23a195..000000000 --- a/src/adcp/types/generated_poc/core/package_signal_targeting_group.py +++ /dev/null @@ -1,37 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/package_signal_targeting_group.json -# timestamp: 2026-05-26T03:43:17+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from . import package_signal_targeting - - -class Operator(StrEnum): - any = 'any' - none = 'none' - - -class PackageSignalTargetingGroup(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - operator: Annotated[ - Operator, - Field( - description="How to evaluate the signals in this group. 'any' is an OR include group. 'none' is an exclusion group equivalent to NOT (A OR B OR C)." - ), - ] - signals: Annotated[ - list[package_signal_targeting.PackageSignalTargeting], - Field( - description='Signal targeting entries evaluated by this group. Each entry uses the package signal targeting shape, including signal_ref, value expression, and optional pricing, execution-handle, or activation fields.', - min_length=1, - ), - ] diff --git a/src/adcp/types/generated_poc/core/package_signal_targeting_groups.py b/src/adcp/types/generated_poc/core/package_signal_targeting_groups.py deleted file mode 100644 index f65bc9d4d..000000000 --- a/src/adcp/types/generated_poc/core/package_signal_targeting_groups.py +++ /dev/null @@ -1,31 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/package_signal_targeting_groups.json -# timestamp: 2026-05-26T03:43:17+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from . import package_signal_targeting_group - - -class PackageSignalTargetingGroups(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - operator: Annotated[ - Literal['all'], - Field( - description="Groups-level operator. Required even though v1 only supports 'all': every child group must be satisfied." - ), - ] = 'all' - groups: Annotated[ - list[package_signal_targeting_group.PackageSignalTargetingGroup], - Field( - description="Signal targeting groups to evaluate. Use operator 'any' for include groups and 'none' for exclusion groups.", - min_length=1, - ), - ] diff --git a/src/adcp/types/generated_poc/core/package_targeting_resolution.py b/src/adcp/types/generated_poc/core/package_targeting_resolution.py deleted file mode 100644 index 2c7d8c445..000000000 --- a/src/adcp/types/generated_poc/core/package_targeting_resolution.py +++ /dev/null @@ -1,26 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/package_targeting_resolution.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from . import demographic_targeting_resolution -from . import ext as ext_1 - - -class PackageTargetingResolution(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - demographics: Annotated[ - demographic_targeting_resolution.DemographicTargetingResolution, - Field( - description='Canonical demographic predicate and exact seller execution details. Include whenever demographic targeting was requested or applied.' - ), - ] - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/pagination_request.py b/src/adcp/types/generated_poc/core/pagination_request.py deleted file mode 100644 index 95ae0f082..000000000 --- a/src/adcp/types/generated_poc/core/pagination_request.py +++ /dev/null @@ -1,24 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/pagination_request.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field - - -class PaginationRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - max_results: Annotated[ - SchemaInt | None, - Field(description='Maximum number of items to return per page', ge=1, le=100), - ] = 50 - cursor: Annotated[ - str | None, - Field(description='Opaque cursor from a previous response to fetch the next page'), - ] = None diff --git a/src/adcp/types/generated_poc/core/pagination_response.py b/src/adcp/types/generated_poc/core/pagination_response.py deleted file mode 100644 index 741a459e8..000000000 --- a/src/adcp/types/generated_poc/core/pagination_response.py +++ /dev/null @@ -1,32 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/pagination_response.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictBool - - -class PaginationResponse(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - has_more: Annotated[ - StrictBool, Field(description='Whether more results are available beyond this page') - ] - cursor: Annotated[ - str | None, - Field( - description='Opaque cursor to pass in the next request to fetch the next page. Only present when has_more is true.' - ), - ] = None - total_count: Annotated[ - SchemaInt | None, - Field( - description='Total number of items matching the query across all pages. Optional because not all backends can efficiently compute this.', - ge=0, - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/performance_feedback.py b/src/adcp/types/generated_poc/core/performance_feedback.py deleted file mode 100644 index e00dcc6b7..000000000 --- a/src/adcp/types/generated_poc/core/performance_feedback.py +++ /dev/null @@ -1,157 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/performance_feedback.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat - -from ..enums import attribution_methodology as attribution_methodology_1 -from ..enums import available_metric -from ..enums import completion_source as completion_source_1 -from ..enums import feedback_source as feedback_source_1 -from ..enums import lift_dimension as lift_dimension_1 -from ..enums import metric_type as metric_type_1 -from ..enums import viewability_standard as viewability_standard_1 -from . import brand_ref, duration, vendor_metric_id - - -class MeasurementPeriod(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - start: Annotated[ - AwareDatetime, Field(description='ISO 8601 start timestamp for measurement period') - ] - end: Annotated[ - AwareDatetime, Field(description='ISO 8601 end timestamp for measurement period') - ] - - -class Status(StrEnum): - accepted = 'accepted' - queued = 'queued' - applied = 'applied' - rejected = 'rejected' - - -class Qualifier(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - viewability_standard: viewability_standard_1.ViewabilityStandard | None = None - completion_source: completion_source_1.CompletionSource | None = None - attribution_methodology: attribution_methodology_1.AttributionMethodology | None = None - attribution_window: duration.Duration | None = None - lift_dimension: lift_dimension_1.LiftDimension | None = None - - -class Metric(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['standard'], - Field(description='Standard metric from the closed `available-metric.json` enum.'), - ] = 'standard' - metric_id: Annotated[ - available_metric.AvailableMetric, Field(description='Identifier for the standard metric.') - ] - qualifier: Annotated[ - Qualifier | None, - Field( - description='Disambiguator — same shape as `committed_metrics.qualifier`. Carry `viewability_standard` for MRC vs GroupM viewability feedback; `completion_source` for seller- vs vendor-attested completion feedback; `attribution_methodology` and `attribution_window` for outcome-metric feedback (the buyer pinning a specific methodology or window when the feedback is methodology-sensitive). Closed (`additionalProperties: false`); new keys ship explicitly in subsequent minors.' - ), - ] = None - - -class Metric7(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['vendor'], - Field(description='Vendor-defined metric, identified by the tuple `(vendor, metric_id)`.'), - ] = 'vendor' - vendor: Annotated[ - brand_ref.BrandReference, - Field( - description="Vendor that defines and computes this metric. Same identity discipline as `vendor_metric_value.vendor` and `committed_metrics` vendor-scope entries. Distinct from the row-level `vendor` field at the top of `performance-feedback`, which identifies the *source* of the feedback (the party producing it); this nested vendor identifies the *vendor that defines the metric* (which may or may not be the same party). Typically the same when third-party verification reports its own metric; can differ when a buyer's MMM tool reports on a separately-defined vendor metric." - ), - ] - metric_id: Annotated[ - vendor_metric_id.VendorMetricId, - Field(description="Identifier for the metric within the vendor's vocabulary."), - ] - qualifier: Annotated[ - Qualifier | None, - Field( - description='Optional disambiguator mirroring the vendor-scope qualifier on `committed_metrics` — same closed key set as standard-scope entries.' - ), - ] = None - - -class PerformanceFeedback(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - feedback_id: Annotated[ - str, Field(description='Unique identifier for this performance feedback submission') - ] - media_buy_id: Annotated[str, Field(description="Publisher's media buy identifier")] - package_id: Annotated[ - str | None, - Field( - description='Specific package within the media buy (if feedback is package-specific)' - ), - ] = None - creative_id: Annotated[ - str | None, Field(description='Specific creative asset (if feedback is creative-specific)') - ] = None - measurement_period: Annotated[ - MeasurementPeriod, Field(description='Time period for performance measurement') - ] - performance_index: Annotated[ - StrictFloat, - Field( - description='Normalized performance score (0.0 = no value, 1.0 = expected, >1.0 = above expected)', - ge=0.0, - ), - ] - metric_type: Annotated[ - metric_type_1.MetricType | None, - Field( - deprecated=True, - description='**Deprecated as of this minor.** The legacy free-form metric enum that mixes metrics, verification, and attribution into one list. New implementations SHOULD use `metric` (the discriminated `(scope, metric_id, qualifier)` row shape) and populate `metric_type` with a best-effort string for one-minor backwards compatibility. When both `metric` and `metric_type` are present, consumers MUST use `metric` for dispatch. Removed at the next major. See [docs/measurement/taxonomy](https://docs.adcontextprotocol.org/docs/measurement/taxonomy) for why the layered shape replaces the flat enum.', - ), - ] = None - metric: Annotated[ - Metric | Metric7 | None, - Field( - description='The metric this feedback row pertains to, using the same `(scope, metric_id, qualifier)` row shape as `committed_metrics` and package-level delivery values (`metric_values` or `vendor_metric_values`). Preferred over the legacy `metric_type` field for new implementations. Brings performance-feedback into the same atomic unit and dispatch model as the rest of the measurement surface — buyer agents reconcile feedback against the contract surface using the row-level join on `(scope, metric_id, qualifier)`. **Optional and may be omitted entirely for holistic feedback** (e.g., a trader flagging a campaign as underperforming without a specific metric in mind — `performance_index` plus the response narrative carry the signal). Senders SHOULD populate `metric` when the feedback is metric-specific so consumers can route it to the right optimization path; senders MAY omit it for general performance feedback.', - discriminator='scope', - ), - ] = None - feedback_source: Annotated[ - feedback_source_1.FeedbackSource, Field(description='Source of the performance data') - ] - vendor: Annotated[ - brand_ref.BrandReference | None, - Field( - description="Vendor that produced this feedback. SHOULD be populated when `feedback_source` is `third_party_measurement` or `verification_partner` AND a single attesting vendor exists — without it, the row is unattributed and consumers can't verify authorization, resolve metric definitions, or route disputes. OMIT for blended outputs where no single vendor owns the result: MMM mixes (Nielsen MMM, Analytic Partners, in-house mix models combining multiple vendor inputs), multi-touch attribution outputs that join across vendors, and clean-room outputs (LiveRamp, Habu, AWS Clean Rooms) where the clean room is not the measurement source. For these cases, leave `vendor` absent and use the response's narrative payload to describe provenance. Optional for `buyer_attribution` and `platform_analytics` (those sources are implicit from context). The vendor's `brand.json` `agents[type='measurement']` is the discovery anchor; metric definitions live on the agent's `get_adcp_capabilities.measurement.metrics[]` block. Same identity discipline as `vendor_metric_value.vendor` and `performance-standard.vendor`." - ), - ] = None - status: Annotated[Status, Field(description='Processing status of the performance feedback')] - submitted_at: Annotated[ - AwareDatetime, Field(description='ISO 8601 timestamp when feedback was submitted') - ] - applied_at: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when feedback was applied to optimization algorithms' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/performance_feedback_assertion.py b/src/adcp/types/generated_poc/core/performance_feedback_assertion.py deleted file mode 100644 index b7ee9f325..000000000 --- a/src/adcp/types/generated_poc/core/performance_feedback_assertion.py +++ /dev/null @@ -1,163 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/performance_feedback_assertion.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat - -from ..enums import feedback_source as feedback_source_1 -from ..enums import metric_type as metric_type_1 -from ..enums import performance_baseline -from . import brand_ref, datetime_range, performance_feedback_metric - - -class ConfidenceInterval(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - lower: StrictFloat - upper: StrictFloat - level: Annotated[StrictFloat, Field(gt=0.0, lt=1.0)] - - -class Evidence(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - sample_size: Annotated[SchemaInt | None, Field(ge=1)] = None - confidence_interval: Annotated[ - ConfidenceInterval | None, - Field( - description='Confidence interval on the performance_index scale. sample_size may be omitted when the producer cannot disclose it.' - ), - ] = None - - -class PerformanceFeedbackAssertion(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_buy_id: Annotated[ - str, - Field( - description="Receiver-scoped media buy identifier. On the provider-to-orchestrator hop this is the orchestrator's measurement-facing identifier; on the orchestrator-to-seller hop it is the seller-assigned media buy identifier.", - min_length=1, - ), - ] - package_id: Annotated[ - str | None, - Field( - description='Receiver-scoped package identifier when the assertion is package-specific. The orchestrator maps its measurement-facing identifier to seller-local identifiers during fan-out.', - min_length=1, - ), - ] = None - creative_id: Annotated[ - str | None, - Field( - description='Receiver-scoped creative identifier when the assertion is creative-specific. The orchestrator maps its measurement-facing identifier to seller-local identifiers during fan-out.', - min_length=1, - ), - ] = None - measurement_period: Annotated[ - datetime_range.DatetimeRange, - Field(description='Period whose performance is summarized by this assertion.'), - ] - performance_index: Annotated[ - StrictFloat, - Field( - description='Normalized decision signal where 1.0 equals the named baseline, values below 1.0 underperform it, and values above 1.0 outperform it. Compact-contract producers (baseline present) MUST use observed divided by baseline for higher-is-better ratio metrics and baseline divided by observed for lower-is-better ratio metrics such as cost per acquisition.', - ge=0.0, - ), - ] - baseline: Annotated[ - performance_baseline.PerformanceBaseline | None, - Field( - description='Expectation represented by performance_index = 1.0. Compact-contract producers MUST populate this field; it remains optional in the schema so existing 3.x request payloads remain valid.' - ), - ] = None - metric: Annotated[ - performance_feedback_metric.PerformanceFeedbackMetric | None, - Field( - description='Metric this assertion describes. Omit only for holistic feedback that is not attributable to one metric.' - ), - ] = None - metric_type: Annotated[ - metric_type_1.MetricType | None, - Field( - deprecated=True, - description='Deprecated legacy metric classification retained for existing request payloads. New implementations SHOULD use metric; when both are present consumers MUST use metric for dispatch.', - ), - ] = None - producer: Annotated[ - brand_ref.BrandReference | None, - Field( - description='Brand that produced the analysis. The receiving orchestrator gateway verifies this reference against authenticated caller identity and preserves it during seller fan-out.' - ), - ] = None - vendor: Annotated[ - brand_ref.BrandReference | None, - Field( - deprecated=True, - description='Deprecated alias for producer retained for previously documented request payloads. When both are present consumers use producer.', - ), - ] = None - feedback_source: Annotated[ - feedback_source_1.FeedbackSource | None, - Field(description='Legacy categorical source hint retained for existing request payloads.'), - ] = None - methodology: Annotated[ - str | None, - Field( - description='Producer-scoped methodology identifier such as geo_incrementality, media_mix_model, or deterministic_attribution.', - max_length=100, - min_length=1, - ), - ] = None - methodology_version: Annotated[ - str | None, - Field( - description='Producer-defined version of the methodology used for this assertion.', - max_length=100, - min_length=1, - ), - ] = None - study_ref: Annotated[ - str | None, - Field( - description='Opaque producer-assigned study or model-run reference. Receivers use it for correlation only and MUST NOT interpret it as an experiment-execution instruction.', - max_length=255, - min_length=1, - ), - ] = None - evidence: Annotated[ - Evidence | None, - Field( - description='Small inline evidence summary used to weight the assertion without transporting the full study.' - ), - ] = None - evidence_ref: Annotated[ - AnyUrl | None, - Field( - description='Provider-hosted evidence or result reference for parties authorized to inspect the full study.' - ), - ] = None - as_of: Annotated[ - AwareDatetime | None, Field(description='When the producer computed this assertion.') - ] = None - final: Annotated[ - StrictBool | None, - Field( - description='Whether the producer expects this assertion to be revised as data matures.' - ), - ] = None - supersedes_feedback_id: Annotated[ - str | None, - Field( - description='Receiver-issued feedback_id of the earlier assertion this one replaces at the same hop.', - min_length=1, - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/performance_feedback_metric.py b/src/adcp/types/generated_poc/core/performance_feedback_metric.py deleted file mode 100644 index b349e74cc..000000000 --- a/src/adcp/types/generated_poc/core/performance_feedback_metric.py +++ /dev/null @@ -1,72 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/performance_feedback_metric.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..enums import attribution_methodology as attribution_methodology_1 -from ..enums import available_metric -from ..enums import completion_source as completion_source_1 -from ..enums import lift_dimension as lift_dimension_1 -from ..enums import viewability_standard as viewability_standard_1 -from . import brand_ref, duration, vendor_metric_id - - -class Qualifier(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - viewability_standard: viewability_standard_1.ViewabilityStandard | None = None - completion_source: completion_source_1.CompletionSource | None = None - attribution_methodology: attribution_methodology_1.AttributionMethodology | None = None - attribution_window: duration.Duration | None = None - lift_dimension: lift_dimension_1.LiftDimension | None = None - - -class PerformanceFeedbackMetric1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Literal['standard'] = 'standard' - metric_id: available_metric.AvailableMetric - qualifier: Annotated[ - Qualifier | None, - Field( - description='Identity-affecting metric qualifiers. Statistical evidence and feedback-producer identity do not belong here.' - ), - ] = None - - -class PerformanceFeedbackMetric2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Literal['vendor'] = 'vendor' - vendor: Annotated[ - brand_ref.BrandReference, - Field( - description='Vendor that defines this metric. This can differ from the producer of the feedback assertion.' - ), - ] - metric_id: vendor_metric_id.VendorMetricId - qualifier: Annotated[ - Qualifier | None, - Field( - description='Optional disambiguator mirroring the vendor-scope qualifier on `committed_metrics` — same closed key set as standard-scope entries.' - ), - ] = None - - -PerformanceFeedbackMetric = Annotated[ - PerformanceFeedbackMetric1 | PerformanceFeedbackMetric2, - Field( - description='Metric identity for a performance-feedback assertion. Uses the same standard/vendor discriminator and qualifier vocabulary as committed_metrics, missing_metrics, and package-level delivery values (metric_values or vendor_metric_values).', - discriminator='scope', - title='Performance Feedback Metric', - ), -] diff --git a/src/adcp/types/generated_poc/core/performance_standard.py b/src/adcp/types/generated_poc/core/performance_standard.py deleted file mode 100644 index 9026fbe00..000000000 --- a/src/adcp/types/generated_poc/core/performance_standard.py +++ /dev/null @@ -1,43 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/performance_standard.json -# timestamp: 2026-10-03T15:09:44+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictFloat - -from ..enums import performance_standard_metric, viewability_standard -from . import brand_ref - - -class PerformanceStandard(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - metric: Annotated[ - performance_standard_metric.PerformanceStandardMetric, - Field(description='The performance metric this standard applies to.'), - ] - threshold: Annotated[ - StrictFloat, - Field( - description='Rate threshold as a decimal (e.g., 0.70 for 70%). Whether this is a floor or ceiling depends on the metric: for viewability, completion_rate, brand_safety, attention_score the actual rate must be >= threshold; for ivt the actual rate must be <= threshold.', - ge=0.0, - le=1.0, - ), - ] - standard: Annotated[ - viewability_standard.ViewabilityStandard | None, - Field( - description="Measurement standard. Required when metric is 'viewability' (MRC and GroupM define materially different thresholds). Omit for other metrics." - ), - ] = None - vendor: Annotated[ - brand_ref.BrandReference, - Field( - description="Vendor measuring this metric (e.g., { domain: 'doubleverify.com' }). The vendor's brand.json agents array (type: 'measurement') is the discovery point for their measurement agent. When specified on a confirmed package, creatives MUST include tracker_script or tracker_pixel assets from this vendor." - ), - ] diff --git a/src/adcp/types/generated_poc/core/placement.py b/src/adcp/types/generated_poc/core/placement.py deleted file mode 100644 index c678776a6..000000000 --- a/src/adcp/types/generated_poc/core/placement.py +++ /dev/null @@ -1,196 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/placement.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from collections.abc import Sequence -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, model_validator - -from ..enums import ( - audio_distribution_type, - dooh_motion_type, - identifier_types, - social_placement_surface, - sponsored_placement_type, - video_placement_type, -) -from . import format_id, product_format_declaration, seller_agent_ref - - -class Kind(StrEnum): - publisher_ref = 'publisher_ref' - seller_inline = 'seller_inline' - - -class Mode(StrEnum): - targetable = 'targetable' - included = 'included' - - -class Identifier(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: identifier_types.PropertyIdentifierTypes - value: Annotated[ - str, - Field( - description='Identifier value, optionally authority-prefixed for externally governed IDs (e.g., space:1234931339).' - ), - ] - - -class ProductDoohScreenResolution(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - width: Annotated[SchemaInt, Field(description='Screen width in pixels.', ge=1)] - height: Annotated[SchemaInt, Field(description='Screen height in pixels.', ge=1)] - - -class ProductDoohPlacementAttributes(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - slot_duration_seconds: Annotated[ - SchemaInt | None, - Field( - description='Scheduled duration of one ad slot in seconds. This is an inventory fact used for loop and share calculations, not the creative-duration contract.', - ge=1, - ), - ] = None - loop_duration_seconds: Annotated[ - SchemaInt | None, - Field( - description='Duration of the full ad loop rotation in seconds and the canonical source for loop duration.', - ge=1, - ), - ] = None - screen_resolution: ProductDoohScreenResolution | None = None - motion: Annotated[ - dooh_motion_type.DoohMotionType | None, - Field( - description='Physical motion capability of a visual DOOH screen, not an accepted-format declaration. Omit for audio-only placements.' - ), - ] = None - - -class Placement(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[ - Kind, - Field( - description='Placement authority discriminator. `publisher_ref` is publisher-catalog identity; `seller_inline` is sales-agent-authored identity.' - ), - ] - placement_id: Annotated[ - str, - Field( - description='Placement identifier. For publisher_ref it is scoped by publisher_domain and resolves in adagents.json. For seller_inline it is scoped by seller_agent, or by the enclosing seller and product for legacy rows.' - ), - ] - publisher_domain: Annotated[ - str | None, - Field( - description="For publisher_ref, the domain whose adagents.json declares the placement and part of canonical identity. For seller_inline, optional inventory-publisher attribution only; it does not grant the seller authority to mint IDs in that publisher's catalog namespace.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - seller_agent: Annotated[ - seller_agent_ref.SellerAgentReference | None, - Field( - description='Sales agent that defines a seller_inline placement. Together with placement_id this is its self-contained identity. New 3.2 sellers SHOULD populate it; legacy product-context inline placements may omit it. Not used for publisher_ref.' - ), - ] = None - name: Annotated[ - str | None, - Field( - description='Human-readable name for the placement (e.g., \'Homepage Banner\', \'Article Sidebar\'). Required for `kind: "seller_inline"`. May be omitted for publisher-referenced placements because buyers resolve the name from the publisher declaration identified by `{publisher_domain, placement_id}`.' - ), - ] = None - description: Annotated[ - str | None, Field(description='Detailed description of where and how the placement appears') - ] = None - mode: Annotated[ - Mode, - Field( - description="Required product-level relationship to this placement. targetable means the buyer may include the publisher-scoped ref in targeting_overlay.placement_selection; a creative may be routed there only after it is purchased. included means fixed product inventory: it cannot be independently selected, but across discovery, create, and update a selected request exactly equal to the product's complete included placement set is an inherent restatement and may be echoed on the package without overlay_support.placement_selection. A product containing any included placement MUST NOT declare overlay_support.placement_selection; partial selection requires a separately selectable product configuration. During the migration window ending 2026-11-25, buyers MAY tolerate legacy products that omit mode and treat them as targetable; after that date buyers SHOULD fail closed." - ), - ] - tags: Annotated[ - list[str] | None, - Field( - description="Optional tags for grouping placements within a product (e.g., 'homepage', 'native', 'premium'). When the placement_id comes from the publisher registry, these should align with the registry tags unless the product is narrowing scope." - ), - ] = None - format_ids: Annotated[ - Sequence[format_id.FormatReferenceStructuredObject] | None, - Field( - deprecated=True, - description='Deprecated in AdCP 3.2; removed in AdCP 4.0. Legacy named-format placement narrowing. Can include concrete, template, or parameterized format IDs. When present on a product placement, this field narrows the product-level `format_ids` contract and MUST NOT introduce formats the product does not accept. Use canonical `format_options`.', - min_length=1, - ), - ] = None - format_options: Annotated[ - list[product_format_declaration.ProductFormatDeclaration] | None, - Field( - description="Canonical seller-side narrowing for this product placement. When present, these declarations are intersected with the product-level format_options and MUST NOT introduce a format outside that product upper bound. For kind publisher_ref, buyers MUST also resolve {publisher_domain, placement_id} in the publisher's adagents.json and intersect the publisher catalog constraint: use the public placement's format_options when present (resolving bare format_option_id references against same-file top-level formats[]), otherwise use applicable top-level formats[] scoped to that placement's properties. Omitting this inline field removes only the seller-inline layer; it does not bypass a publisher placement or property-scoped narrowing. The placement inherits the full product-level set only when no applicable publisher catalog narrowing exists. Unresolved publisher placement or format-option references fail closed. Locale policy participates in the same intersection: when the product policy is absent, a placement may introduce any concrete policy as a narrowing of the unconstrained option; when both are present, every placement accepted_language_range must be contained by a product range under RFC 4647 Basic Filtering (`fr-CA` narrows `fr`; `fr` does not narrow `fr-CA`). Buyers compute effective locale eligibility independently for each placement. Any effective locale-constrained route is canonical-only and has no projecting product or placement format_id.", - min_length=1, - ), - ] = None - video_placement_types: Annotated[ - list[video_placement_type.VideoPlacementType] | None, - Field( - description='Declared video placement types for this product placement, using IAB Tech Lab/OpenRTB 2.6 video.plcmt definitions with AdCP-native names. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - audio_distribution_types: Annotated[ - list[audio_distribution_type.AudioDistributionType] | None, - Field( - description='Declared audio distribution types for this product placement, using IAB Tech Lab/OpenRTB 2.6 audio.feed definitions with AdCP-native names. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - sponsored_placement_types: Annotated[ - list[sponsored_placement_type.SponsoredPlacementType] | None, - Field( - description='Declared sponsored-placement types for this product placement, distinguishing where the catalog-driven retail-media placement renders on the retailer surface. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - social_placement_surfaces: Annotated[ - list[social_placement_surface.SocialPlacementSurface] | None, - Field( - description='Declared social-placement surfaces for this product placement, distinguishing the in-app surface where the social placement renders. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - identifiers: Annotated[ - list[Identifier] | None, - Field( - description='Optional external inventory identifiers for this placement, using the same {type, value} shape as property identifiers. Externally governed IDs should be authority-prefixed (e.g., space:1234931339, geopath:30961, fcc:73953). Seller-local IDs are opaque values scoped by the surrounding publisher namespace. Useful for DOOH venue and installed-endpoint IDs, broadcast facility IDs, and any channel where placements map to externally registered inventory. For kind: publisher_ref, the effective identifier set is the union of the resolved publisher declaration and this product declaration, de-duplicated by exact (type, value); a product cannot suppress a publisher-declared identifier by omission.', - min_length=1, - ), - ] = None - dooh_placement_attributes: ProductDoohPlacementAttributes | None = None - - @model_validator(mode='after') - def _require_schema_required_group(self) -> Placement: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('name',), ('publisher_domain',),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'Placement requires at least one of these field groups: name | publisher_domain' - ) diff --git a/src/adcp/types/generated_poc/core/placement_definition.py b/src/adcp/types/generated_poc/core/placement_definition.py deleted file mode 100644 index 37d272494..000000000 --- a/src/adcp/types/generated_poc/core/placement_definition.py +++ /dev/null @@ -1,209 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/placement_definition.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, model_validator - -from ..enums import audio_distribution_type -from ..enums import channels as channels_1 -from ..enums import ( - dooh_motion_type, - identifier_types, - social_placement_surface, - sponsored_placement_type, - video_placement_type, -) -from . import creative_locale_policy -from . import ext as ext_1 -from . import presentation_ref as presentation_ref_1 -from . import preview_provider as preview_provider_1 -from . import product_format_declaration, property_id, property_tag - - -class Identifier(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: identifier_types.PropertyIdentifierTypes - value: Annotated[ - str, - Field( - description='Identifier value, optionally authority-prefixed for externally governed IDs (e.g., space:1234931339).' - ), - ] - - -class PublisherDoohScreenResolution(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - width: Annotated[SchemaInt, Field(description='Screen width in pixels.', ge=1)] - height: Annotated[SchemaInt, Field(description='Screen height in pixels.', ge=1)] - - -class PublisherDoohPlacementAttributes(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - slot_duration_seconds: Annotated[ - SchemaInt | None, - Field( - description='Default scheduled duration of one ad slot in seconds. This is an inventory fact used for loop and share calculations, not the creative-duration contract.', - ge=1, - ), - ] = None - loop_duration_seconds: Annotated[ - SchemaInt | None, - Field( - description='Duration of the full ad loop rotation in seconds and the canonical source for loop duration.', - ge=1, - ), - ] = None - screen_resolution: PublisherDoohScreenResolution | None = None - motion: Annotated[ - dooh_motion_type.DoohMotionType | None, - Field( - description='Physical motion capability of a visual DOOH screen, not an accepted-format declaration. Omit for audio-only placements.' - ), - ] = None - - -class FormatOptions(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - format_option_id: Annotated[ - str, Field(description="Matches a `format_option_id` in the file's top-level `formats[]`.") - ] - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional placement-local locale policy. When the resolved top-level format declares a policy, every placement range must be contained by one of its ranges; otherwise this introduces a narrowing of the unconstrained format. The resolved effective route remains canonical-only.' - ), - ] = None - - -class PlacementDefinition(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - placement_id: Annotated[ - str, Field(description='Stable placement identifier unique within this adagents.json file.') - ] - name: Annotated[ - str, - Field( - description="Human-readable placement name (e.g., 'Homepage Banner', 'Pre-roll', 'Sponsored Listing Slot 1')." - ), - ] - description: Annotated[ - str | None, Field(description='Description of where and how this placement appears.') - ] = None - tags: Annotated[ - list[str] | None, - Field( - description="Tags for grouping and querying placements across properties and products (e.g., 'homepage', 'native', 'premium', 'pre_roll')." - ), - ] = None - property_ids: Annotated[ - list[property_id.PropertyId] | None, - Field( - description='Property IDs in this adagents.json where this placement can appear.', - min_length=1, - ), - ] = None - property_tags: Annotated[ - list[property_tag.PropertyTag] | None, - Field( - description="Property tags in this adagents.json where this placement can appear. Useful for network-wide positions such as 'pre_roll' or 'homepage_native_feed'.", - min_length=1, - ), - ] = None - collection_ids: Annotated[ - list[str] | None, - Field( - description='Optional collection IDs in this adagents.json where this placement is valid. Use to narrow a placement to specific content programs carried on the selected properties.', - min_length=1, - ), - ] = None - channels: Annotated[ - list[channels_1.MediaChannel] | None, - Field( - description='Advertising channels where this placement can run. Products that reference the placement may narrow this set but should not broaden it.', - min_length=1, - ), - ] = None - presentation_ref: Annotated[ - presentation_ref_1.PlacementPresentationReference | None, - Field( - description="Optional publisher-specific declarative frame for representing this placement's real chrome offline. It composes around the selected creative rendering unless the publisher-delegated preview_provider route explicitly covers placement presentation. It MUST NOT be promoted to or copied onto a shared format entry." - ), - ] = None - preview_provider: Annotated[ - preview_provider_1.PublisherDesignatedPreviewProvider | None, - Field( - description="Optional publisher delegation to a callable AdCP preview provider for specific format options on this placement. This publisher-origin route is the only mechanism that grants preview authority inside the placement's scope; an agent's own rendering_origin description does not." - ), - ] = None - format_options: Annotated[ - list[FormatOptions | product_format_declaration.ProductFormatDeclaration] | None, - Field( - description="Optional 3.1+ canonical format-option declarations supported by this placement. This `adagents.json` placement surface supports two entry shapes: (1) reference an entry in the same file's top-level `formats[]` by `format_option_id` only — buyers resolve the full declaration from `formats[]` by matching `format_option_id` (recommended; avoids duplication). Top-level formats may be publisher-owned custom formats or narrowed canonical formats; their `format_kind` is the canonical anchor that the placement reference inherits. (2) carry an inline `ProductFormatDeclaration` directly — for placement-specific canonical narrowing that doesn't fit a reusable catalog entry. This bare-reference shape is placement-catalog specific; Product `format_options[]` entries are always full `ProductFormatDeclaration` objects with required `format_kind` and `params`.\n\nProduct-level formats remain the upper bound for a sellable product. Catalog placement formats describe placement support; when a product references the placement and also declares product-level formats, buyers use the intersection for that product placement. A catalog placement format that is absent from the product-level declaration is not accepted for that product unless the product explicitly includes it. When the product locale policy is absent, placement locale_policy may introduce any concrete narrowing; when both are present, every placement accepted_language_range must be contained by a product range under RFC 4647 Basic Filtering. An effective placement policy is canonical-only: its resolved catalog and matching product declarations cannot project through legacy format_ids.\n\n**Format-option reference shape.** A format-option reference entry SHOULD carry ONLY `format_option_id` — extra fields are allowed (`additionalProperties: true`) so adopters who want to attach a placement-local override like `display_name` or a narrower `locale_policy` don't get rejected by the branch boundary, but buyer SDKs MUST resolve the format from the top-level `formats[]` by `format_option_id` and apply additional fields on the entry as placement-level overrides (NOT as a partial inline declaration). If a publisher needs to materially narrow the format at the placement, use the inline-declaration form instead.\n\n**Resolution scope is same-file only.** `format_option_id` resolves only within this file's top-level `formats[]`; cross-file references are not supported by design because same-file resolution keeps validation bounded and prevents a file from squatting on or narrowing another publisher's format_option_id. When `format_options[]` references a `format_option_id` not declared in the file's top-level `formats[]`, validators MUST surface this as `FORMAT_OPTION_UNRESOLVED` on the response `errors[]`. Buyers MUST fail closed for that placement (drop the format from the placement's accepted set) rather than silently dropping the placement or guessing intent.", - min_length=1, - ), - ] = None - video_placement_types: Annotated[ - list[video_placement_type.VideoPlacementType] | None, - Field( - description='Declared video placement types for this publisher placement, using IAB Tech Lab/OpenRTB 2.6 video.plcmt definitions with AdCP-native names. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. Product-level placement declarations may narrow this set but SHOULD NOT broaden it. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - audio_distribution_types: Annotated[ - list[audio_distribution_type.AudioDistributionType] | None, - Field( - description='Declared audio distribution types for this publisher placement, using IAB Tech Lab/OpenRTB 2.6 audio.feed definitions with AdCP-native names. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. Product-level placement declarations may narrow this set but SHOULD NOT broaden it. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - sponsored_placement_types: Annotated[ - list[sponsored_placement_type.SponsoredPlacementType] | None, - Field( - description='Declared sponsored-placement types for this publisher placement, distinguishing where the catalog-driven retail-media placement renders on the retailer surface. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. Product-level placement declarations may narrow this set but SHOULD NOT broaden it. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - social_placement_surfaces: Annotated[ - list[social_placement_surface.SocialPlacementSurface] | None, - Field( - description='Declared social-placement surfaces for this publisher placement, distinguishing the in-app surface where the social placement renders. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. Product-level placement declarations may narrow this set but SHOULD NOT broaden it. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - identifiers: Annotated[ - list[Identifier] | None, - Field( - description='Optional external inventory identifiers for this placement, using the same {type, value} shape as property identifiers. Externally governed IDs should be authority-prefixed (e.g., space:1234931339, geopath:30961, fcc:73953). Seller-local IDs are opaque values scoped by the surrounding publisher namespace. Product-level placement declarations may carry additional identifiers but SHOULD NOT contradict publisher-declared identifiers for the same type.', - min_length=1, - ), - ] = None - dooh_placement_attributes: PublisherDoohPlacementAttributes | None = None - ext: ext_1.ExtensionObject | None = None - - @model_validator(mode='after') - def _require_schema_required_group(self) -> PlacementDefinition: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('property_ids',), ('property_tags',),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'PlacementDefinition requires at least one of these field groups: property_ids | property_tags' - ) diff --git a/src/adcp/types/generated_poc/core/placement_delivery_metrics.py b/src/adcp/types/generated_poc/core/placement_delivery_metrics.py deleted file mode 100644 index 6b908e6b6..000000000 --- a/src/adcp/types/generated_poc/core/placement_delivery_metrics.py +++ /dev/null @@ -1,42 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/placement_delivery_metrics.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from pydantic import Field - -from . import placement_identity as placement_identity_1 -from .delivery_metrics import DeliveryMetrics - - -class PlacementDeliveryMetrics(DeliveryMetrics): - placement_identity: Annotated[ - placement_identity_1.PlacementIdentity | None, - Field( - description='Self-contained identity. publisher_ref resolves in adagents.json; seller_inline is scoped by seller_agent.' - ), - ] = None - placement_name: Annotated[ - str | None, - Field( - description='Current human-readable placement name. Convenience metadata only; placement_identity is stable identity.' - ), - ] = None - placement_id: Annotated[ - str, - Field( - description='Required flat compatibility identity. It MUST equal placement_identity.placement_id when placement_identity is present.' - ), - ] - publisher_domain: Annotated[ - str | None, - Field( - description='Legacy publisher attribution. For publisher_ref identity it MUST equal placement_identity.publisher_domain. For seller_inline it identifies inventory context only and is not identity authority.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - impressions: Any - spend: Any diff --git a/src/adcp/types/generated_poc/core/placement_evidence.py b/src/adcp/types/generated_poc/core/placement_evidence.py deleted file mode 100644 index 7ef6fcb9e..000000000 --- a/src/adcp/types/generated_poc/core/placement_evidence.py +++ /dev/null @@ -1,37 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/placement_evidence.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictFloat - - -class PlacementEvidence(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - url: Annotated[ - AnyUrl, - Field( - description='The evidence artifact — photograph, scanned tearsheet, or evidence package' - ), - ] - captured_at: Annotated[ - AwareDatetime | None, Field(description='When the artifact was captured') - ] = None - latitude: Annotated[ - StrictFloat | None, Field(description='Capture location latitude', ge=-90.0, le=90.0) - ] = None - longitude: Annotated[ - StrictFloat | None, Field(description='Capture location longitude', ge=-180.0, le=180.0) - ] = None - notes: Annotated[ - str | None, - Field( - description="Context that doesn't fit the structured fields (e.g., 'shot from the eastbound approach at 40mph equivalent')" - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/placement_identity.py b/src/adcp/types/generated_poc/core/placement_identity.py deleted file mode 100644 index 22bbeca3d..000000000 --- a/src/adcp/types/generated_poc/core/placement_identity.py +++ /dev/null @@ -1,60 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/placement_identity.json -# timestamp: 2026-10-04T04:53:46+00:00 - -from __future__ import annotations - -from typing import Any, Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel - -from . import seller_agent_ref - - -class PlacementIdentity1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['publisher_ref'] = 'publisher_ref' - publisher_domain: Annotated[ - str, - Field( - description='Domain whose adagents.json declares placement_id.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - placement_id: Annotated[str, Field(min_length=1)] - - -class PlacementIdentity2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['seller_inline'] = 'seller_inline' - seller_agent: Annotated[ - seller_agent_ref.SellerAgentReference, - Field(description='Sales agent that defines and maintains the inline placement namespace.'), - ] - placement_id: Annotated[ - str, - Field( - description="Stable placement ID within the defining sales agent's namespace. The agent MUST NOT reuse it for a different semantic placement.", - min_length=1, - ), - ] - - -class PlacementIdentity(RootModel[PlacementIdentity1 | PlacementIdentity2]): - root: Annotated[ - PlacementIdentity1 | PlacementIdentity2, - Field( - description='Self-contained identity for either a publisher-catalog placement or a sales-agent-defined inline placement. The discriminator names which authority owns placement_id.', - title='Placement Identity', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) diff --git a/src/adcp/types/generated_poc/core/placement_presentation.py b/src/adcp/types/generated_poc/core/placement_presentation.py deleted file mode 100644 index 39fb84e90..000000000 --- a/src/adcp/types/generated_poc/core/placement_presentation.py +++ /dev/null @@ -1,142 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/placement_presentation.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarStr -from pydantic import AnyUrl, ConfigDict, Field, field_validator - - -class Canvas(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - width: Annotated[SchemaInt, Field(ge=1, le=8192)] - height: Annotated[SchemaInt, Field(ge=1, le=8192)] - background_color: Annotated[str | None, Field(pattern='^#[0-9A-Fa-f]{6}$')] = '#ffffff' - - -class Fit(StrEnum): - contain = 'contain' - cover = 'cover' - stretch = 'stretch' - - -class CreativeSlot(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - x: Annotated[SchemaInt, Field(ge=0, le=8192)] - y: Annotated[SchemaInt, Field(ge=0, le=8192)] - width: Annotated[SchemaInt, Field(ge=1, le=8192)] - height: Annotated[SchemaInt, Field(ge=1, le=8192)] - fit: Annotated[ - Fit, - Field( - description='How the creative render is scaled into the slot. contain preserves the whole render, cover fills and crops, and stretch fills without preserving aspect ratio.' - ), - ] - clip: Annotated[ - Literal[True], - Field(description='Slot overflow is always clipped so composition is deterministic.'), - ] - - -class Rectangle(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - x: Annotated[SchemaInt, Field(ge=0, le=8192)] - y: Annotated[SchemaInt, Field(ge=0, le=8192)] - width: Annotated[SchemaInt, Field(ge=1, le=8192)] - height: Annotated[SchemaInt, Field(ge=1, le=8192)] - - -class ImageRef(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - uri: Annotated[ - AnyUrl, - Field( - description='Untrusted image asset. Fetch with public-address-only SSRF protection, no redirects, DNS pinning, and body/time limits; do not attach ambient credentials.' - ), - ] - digest: Annotated[ - str, - Field( - description='SHA-256 digest of the exact image bytes. Consumers MUST fail closed on mismatch.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] - - @field_validator('uri') - @classmethod - def _require_https_uri(cls, value: AnyUrl) -> AnyUrl: - if value.scheme != 'https': - raise ValueError('uri must use https') - return value - -class Layer(StrEnum): - behind_creative = 'behind_creative' - in_front_of_creative = 'in_front_of_creative' - - -class Color(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^#[0-9A-Fa-f]{6}$'} - - -class BoxDecoration(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['box'] = 'box' - layer: Layer - bounds: Rectangle - fill_color: Color - - -class TextDecoration(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['text'] = 'text' - layer: Layer - bounds: Rectangle - text: Annotated[str, Field(max_length=4096)] - text_color: Color - font_size: Annotated[SchemaInt, Field(ge=6, le=256)] - - -class ImageDecoration(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['image'] = 'image' - layer: Layer - bounds: Rectangle - image_ref: ImageRef - fit: Fit - - -class PlacementPresentationDocument(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schema_version: Literal['1.0'] = '1.0' - canvas: Canvas - creative_slot: Annotated[ - CreativeSlot, - Field( - description='Rectangle into which the selected creative render is fitted and clipped without changing its manifest or renderer.' - ), - ] - decorations: Annotated[ - list[BoxDecoration | TextDecoration | ImageDecoration] | None, Field(max_length=100) - ] = None diff --git a/src/adcp/types/generated_poc/core/placement_property_delivery_metrics.py b/src/adcp/types/generated_poc/core/placement_property_delivery_metrics.py deleted file mode 100644 index 05f362f52..000000000 --- a/src/adcp/types/generated_poc/core/placement_property_delivery_metrics.py +++ /dev/null @@ -1,54 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/placement_property_delivery_metrics.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from pydantic import Field - -from . import identifier as identifier_1 -from . import placement_identity as placement_identity_1 -from . import property_ref as property_ref_1 -from .delivery_metrics import DeliveryMetrics - - -class PlacementPropertyDeliveryMetrics(DeliveryMetrics): - placement_id: Annotated[ - str, - Field( - description='Required flat compatibility ID. It MUST equal placement_identity.placement_id when placement_identity is present.' - ), - ] - placement_identity: Annotated[ - placement_identity_1.PlacementIdentity, - Field( - description='Required self-contained placement identity. Unlike the legacy-compatible by_placement dimension, the new by_placement_property dimension has no pre-3.2 row shape and always names the placement authority.' - ), - ] - placement_name: Annotated[ - str | None, Field(description='Current convenience name for the placement.') - ] = None - publisher_domain: Annotated[ - str, - Field( - description="Publisher or platform authority that namespaces the property identifier. This may differ from a publisher-catalog placement's publisher_domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - identifier: Annotated[ - identifier_1.Identifier, - Field(description='Primary operational identifier of the property that delivered.'), - ] - property_ref: Annotated[ - property_ref_1.PropertyReference | None, - Field( - description="Canonical publisher-scoped property catalog identity when available. Its publisher_domain MUST equal the row's publisher_domain." - ), - ] = None - property_name: Annotated[ - str | None, Field(description='Current convenience name for the property.') - ] = None - impressions: Any - spend: Any diff --git a/src/adcp/types/generated_poc/core/placement_ref.py b/src/adcp/types/generated_poc/core/placement_ref.py deleted file mode 100644 index 1a095e0dc..000000000 --- a/src/adcp/types/generated_poc/core/placement_ref.py +++ /dev/null @@ -1,29 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/placement_ref.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - - -class PlacementReference(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Annotated[ - str | None, - Field( - description='Domain where the adagents.json declaring a publisher-catalog placement is hosted, or the inventory publisher associated with an inline placement. Omitted only for legacy single-publisher product-context references.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - placement_id: Annotated[ - str, - Field( - description="Placement ID from the publisher's adagents.json placement catalog, or an inline seller-defined placement ID interpreted within the enclosing seller and product context." - ), - ] diff --git a/src/adcp/types/generated_poc/core/placement_selection.py b/src/adcp/types/generated_poc/core/placement_selection.py deleted file mode 100644 index 911c8ad8e..000000000 --- a/src/adcp/types/generated_poc/core/placement_selection.py +++ /dev/null @@ -1,46 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/placement_selection.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from . import ext as ext_1 -from . import placement_identity, placement_ref - - -class PlacementSelection2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - mode: Literal['default'] = 'default' - ext: ext_1.ExtensionObject | None = None - - -class PlacementSelection1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - mode: Literal['selected'] = 'selected' - placement_refs: Annotated[ - list[placement_ref.PlacementReference | placement_identity.PlacementIdentity], - Field( - description="Complete required placement set. A reference normally identifies a mode targetable placement. It MAY identify a mode included placement only when the set exactly equals the product's complete fixed included set, which is an inherent match rather than independent selection. Legacy publisher refs use {publisher_domain, placement_id}; authority-discriminated 3.2 identities use placement-identity.json so seller-inline inventory is selected by {seller_agent, placement_id}. An item that exactly matches placement-identity uses that canonical identity; otherwise a released-compatible item with publisher_domain and placement_id uses legacy product-context matching, and tolerated product metadata such as kind, name, or mode has no selection effect.", - min_length=1, - ), - ] - ext: ext_1.ExtensionObject | None = None - - -PlacementSelection = Annotated[ - PlacementSelection1 | PlacementSelection2, - Field( - description="Complete purchased placement set within a product. This is inventory targeting, not creative routing: creative_assignments[].placement_refs route a creative only within this purchased set. Requests use mode selected to name the complete required set or mode default to accept the product/seller default. Across discovery, create, and update, a selected set is satisfied inherently when it exactly equals a fixed product's complete included placement set; this exact restatement does not require overlay_support.placement_selection, while partial selection does. Included placements are never independently selectable. Products with any fixed mode:included placement cannot advertise selectable placement overlay support because the fixed placement would make a partial buyer-selected set incomplete; sellers expose separately selectable inventory when needed. Successful package readback MUST echo the committed selected set. Sellers reject unknown, duplicate, cross-publisher, incompatible, or partially applicable refs rather than silently changing the selection.", - discriminator='mode', - title='Placement Selection', - ), -] diff --git a/src/adcp/types/generated_poc/core/planned_delivery.py b/src/adcp/types/generated_poc/core/planned_delivery.py deleted file mode 100644 index d30ef3fb2..000000000 --- a/src/adcp/types/generated_poc/core/planned_delivery.py +++ /dev/null @@ -1,131 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/planned_delivery.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat - -from ..enums import channels as channels_1 -from ..enums import pacing as pacing_1 -from . import audience_selector, bidding_policy -from . import budget_allocation as budget_allocation_1 -from . import ext as ext_1 -from . import frequency_cap as frequency_cap_1 - - -class Geo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - countries: Annotated[ - list[str] | None, - Field(description='ISO 3166-1 alpha-2 country codes where ads will deliver.'), - ] = None - regions: Annotated[ - list[str] | None, Field(description='ISO 3166-2 subdivision codes where ads will deliver.') - ] = None - - -class PlannedDelivery(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_buy_id: Annotated[ - str | None, - Field( - description='Seller-assigned media buy identifier. Optional on a purchase-phase prepare/check because the service may not assign the identifier until commit; required on modification and delivery lifecycle checks.', - min_length=1, - ), - ] = None - proposal_id: Annotated[ - str | None, - Field( - description='Proposal snapshot being executed or currently governing the MediaBuy.', - max_length=255, - min_length=1, - ), - ] = None - proposal_terms_digest: Annotated[ - str | None, - Field( - description='Digest of the proposal commercial_terms. The governance agent compares it to the digest bound during the intent check.', - pattern='^sha256:[A-Za-z0-9_-]{43}$', - ), - ] = None - geo: Annotated[Geo | None, Field(description='Geographic targeting the seller will apply.')] = ( - None - ) - channels: Annotated[ - list[channels_1.MediaChannel] | None, - Field(description='Channels the seller will deliver on.'), - ] = None - start_time: Annotated[ - AwareDatetime | None, Field(description='Actual flight start the seller will use.') - ] = None - end_time: Annotated[ - AwareDatetime | None, Field(description='Actual flight end the seller will use.') - ] = None - frequency_cap: Annotated[ - frequency_cap_1.FrequencyCap | None, - Field(description='Frequency cap the seller will apply.'), - ] = None - audience_summary: Annotated[ - str | None, - Field(description='Human-readable summary of the audience the seller will target.'), - ] = None - audience_targeting: Annotated[ - list[audience_selector.AudienceSelector] | None, - Field( - description='Structured audience targeting the seller will activate. Each entry is either a signal reference or a descriptive criterion. When present, governance agents MUST use this for bias/fairness validation and SHOULD ignore audience_summary for validation purposes. The audience_summary field is a human-readable rendering of this array, not an independent declaration.', - min_length=1, - ), - ] = None - total_budget: Annotated[ - StrictFloat | None, - Field(description='Total budget the seller will deliver against.', ge=0.0), - ] = None - daily_budget_cap: Annotated[ - StrictFloat | None, - Field( - description='Hard aggregate daily spend ceiling the seller will enforce. Governance checks compare it with the authorized execution controls; it does not allocate spend to packages.', - ge=0.0, - ), - ] = None - budget_cap_timezone: Annotated[ - str | None, - Field( - description='IANA timezone defining the calendar-day boundary for every daily cap on the planned media buy.' - ), - ] = None - currency: Annotated[ - str | None, - Field( - description='ISO 4217 currency code for the budget. Governance execution checks require it whenever total_budget is present and require it to match the intent-authorized currency.', - pattern='^[A-Z]{3}$', - ), - ] = None - budget_allocation: Annotated[ - budget_allocation_1.BudgetAllocation | None, - Field( - description='Seller-accepted cross-package allocation authority and goals. Presence with seller_optimized mode means automatic within-buy reallocations are part of the committed delivery, not separate modification actions.' - ), - ] = None - pacing: Annotated[ - pacing_1.Pacing | None, - Field(description='Aggregate pacing strategy the seller will apply to total_budget.'), - ] = None - bidding: Annotated[ - bidding_policy.BiddingPolicy | None, - Field( - description='Seller-interpreted media-buy bidding policy used for governance and delivery transparency. Goal-bound controls follow budget-allocation scope semantics and monetary fields use the planned delivery currency. Package-authored overrides, including explicit automatic overrides, remain on packages rather than being copied into this aggregate field.' - ), - ] = None - enforced_policies: Annotated[ - list[str] | None, - Field(description='Registry policy IDs the seller will enforce for this delivery.'), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/platform_extension_ref.py b/src/adcp/types/generated_poc/core/platform_extension_ref.py deleted file mode 100644 index de8012c52..000000000 --- a/src/adcp/types/generated_poc/core/platform_extension_ref.py +++ /dev/null @@ -1,29 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/platform_extension_ref.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - - -class PlatformExtensionReference(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - uri: Annotated[ - AnyUrl, - Field( - description="HTTPS URL identifying the extension. `https://` is mandatory — `http://`, `file://`, `data:`, and other schemes are rejected at the schema layer (defense-in-depth on top of the fetch-contract normative rules). The URI base is the owning agent's URL; the path identifies the extension within that agent. Example: 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel'. The full fetch contract — SSRF allowlist, response-size cap, $ref sandbox, schema-compile bounds — is documented on `product-format-declaration.json#format_schema` and applies to ALL fetches of this reference shape regardless of whether the field is named `format_schema` (load-bearing for validation) or `platform_extensions` (informational); the *transport* rules are identical, only the *consumption* semantics differ." - ), - ] - digest: Annotated[ - str, - Field( - description='SHA-256 content digest of the extension definition (sha256:). Used to detect drift — if the agent revises the extension, the digest changes and cached definitions become invalid.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] diff --git a/src/adcp/types/generated_poc/core/positive_postal_area_support.py b/src/adcp/types/generated_poc/core/positive_postal_area_support.py deleted file mode 100644 index 89e553e00..000000000 --- a/src/adcp/types/generated_poc/core/positive_postal_area_support.py +++ /dev/null @@ -1,25 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/positive_postal_area_support.json -# timestamp: 2026-10-03T14:21:15+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from pydantic import Field - -from .postal_area_support import PostalAreaSupport - - -class PositivePostalAreaSupport(PostalAreaSupport): - us_zip: Annotated[Literal[True] | None, Field(deprecated=True)] = None - us_zip_plus_four: Annotated[Literal[True] | None, Field(deprecated=True)] = None - gb_outward: Annotated[Literal[True] | None, Field(deprecated=True)] = None - gb_full: Annotated[Literal[True] | None, Field(deprecated=True)] = None - ca_fsa: Annotated[Literal[True] | None, Field(deprecated=True)] = None - ca_full: Annotated[Literal[True] | None, Field(deprecated=True)] = None - de_plz: Annotated[Literal[True] | None, Field(deprecated=True)] = None - fr_code_postal: Annotated[Literal[True] | None, Field(deprecated=True)] = None - au_postcode: Annotated[Literal[True] | None, Field(deprecated=True)] = None - ch_plz: Annotated[Literal[True] | None, Field(deprecated=True)] = None - at_plz: Annotated[Literal[True] | None, Field(deprecated=True)] = None diff --git a/src/adcp/types/generated_poc/core/postal_area.py b/src/adcp/types/generated_poc/core/postal_area.py deleted file mode 100644 index b7d6aa29e..000000000 --- a/src/adcp/types/generated_poc/core/postal_area.py +++ /dev/null @@ -1,451 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/postal_area.json -# timestamp: 2026-10-03T00:35:15+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Any, Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel, model_validator - -from ..enums import legacy_postal_system, postal_system - - -class System(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - zip_1 = 'zip' - zip_plus_four_1 = 'zip_plus_four' - - -class PostalArea11(AdCPBaseModel): - country: Annotated[ - Literal['US'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'US' - system: Annotated[ - System, Field(description='Country-local postal code system.', title='Postal Code System') - ] - - -class System1(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - outward_1 = 'outward' - full_1 = 'full' - - -class PostalArea12(AdCPBaseModel): - country: Annotated[ - Literal['GB'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'GB' - system: Annotated[ - System1, Field(description='Country-local postal code system.', title='Postal Code System') - ] - - -class System2(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - fsa_1 = 'fsa' - full_1 = 'full' - - -class PostalArea13(AdCPBaseModel): - country: Annotated[ - Literal['CA'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'CA' - system: Annotated[ - System2, Field(description='Country-local postal code system.', title='Postal Code System') - ] - - -class Country(StrEnum): - DE = 'DE' - CH = 'CH' - AT = 'AT' - - -class System3(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - - -class PostalArea14(AdCPBaseModel): - country: Annotated[Country, Field(description='ISO 3166-1 alpha-2 country code.')] - system: Annotated[ - Literal['plz'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'plz' - - -class PostalArea15(AdCPBaseModel): - country: Annotated[ - Literal['FR'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'FR' - system: Annotated[ - Literal['code_postal'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'code_postal' - - -class PostalArea16(AdCPBaseModel): - country: Annotated[ - Literal['AU'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'AU' - system: Annotated[ - Literal['postcode'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'postcode' - - -class PostalArea17(AdCPBaseModel): - country: Annotated[ - Literal['BR'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'BR' - system: Annotated[ - Literal['cep'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'cep' - - -class PostalArea18(AdCPBaseModel): - country: Annotated[ - Literal['IN'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'IN' - system: Annotated[ - Literal['pin'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'pin' - - -class PostalArea19(AdCPBaseModel): - country: Annotated[ - Literal['ZA'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'ZA' - system: Annotated[ - Literal['postal_code'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'postal_code' - - -class System9(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - postal_code_1 = 'postal_code' - custom_1 = 'custom' - - -class PostalArea110(AdCPBaseModel): - country: Annotated[ - str, Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] - system: Annotated[ - System9, Field(description='Country-local postal code system.', title='Postal Code System') - ] - - -class PostalArea2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - system: Annotated[ - legacy_postal_system.CountryFusedPostalCodeSystem, - Field( - deprecated=True, - description="Deprecated country-fused postal code system (e.g., 'us_zip', 'gb_outward'). Prefer country + postal-system.", - ), - ] - values: Annotated[ - list[str], Field(description='Postal codes within the legacy system.', min_length=1) - ] - - -class PostalArea111(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - country: Annotated[ - str, - Field( - description='ISO 3166-1 alpha-2 country code for the postal values.', - pattern='^[A-Z]{2}$', - ), - ] - system: Annotated[ - postal_system.PostalCodeSystem, - Field( - description="Country-local postal code system (e.g., 'zip', 'outward', 'plz', 'postal_code')." - ), - ] - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class PostalArea112(PostalArea11): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class PostalArea113(PostalArea12): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class PostalArea114(PostalArea13): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class PostalArea115(PostalArea14): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class PostalArea116(PostalArea15): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class PostalArea117(PostalArea16): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class PostalArea118(PostalArea17): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class PostalArea119(PostalArea18): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class PostalArea120(PostalArea19): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class PostalArea121(PostalArea110): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class PostalArea1( - RootModel[ - PostalArea112 - | PostalArea113 - | PostalArea114 - | PostalArea115 - | PostalArea116 - | PostalArea117 - | PostalArea118 - | PostalArea119 - | PostalArea120 - | PostalArea121 - ] -): - root: Annotated[ - PostalArea112 - | PostalArea113 - | PostalArea114 - | PostalArea115 - | PostalArea116 - | PostalArea117 - | PostalArea118 - | PostalArea119 - | PostalArea120 - | PostalArea121, - Field( - description='Postal area values. Prefer the native country + postal system form. Deprecated legacy country-fused postal-system tokens remain accepted for compatibility.', - title='PostalArea', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - - - -class PostalArea(RootModel[PostalArea2 | PostalArea1]): - root: Annotated[ - PostalArea2 | PostalArea1, - Field( - description='Postal area values. Prefer the native country + postal system form. Deprecated legacy country-fused postal-system tokens remain accepted for compatibility.', - title='Postal Area', - ), - ] - @model_validator(mode='before') - @classmethod - def _validate_country_system_pairing(cls, value: Any) -> Any: - raw = value.get('root', value) if isinstance(value, dict) else value - country = raw.get('country') if isinstance(raw, dict) else getattr(raw, 'country', None) - if not isinstance(country, str): - return value - system = raw.get('system') if isinstance(raw, dict) else getattr(raw, 'system', None) - system = getattr(system, 'value', system) - if not isinstance(system, str): - return value - allowed_by_country = {'US': ('zip', 'zip_plus_four'), 'GB': ('outward', 'full'), 'CA': ('fsa', 'full'), 'DE': ('plz',), 'CH': ('plz',), 'AT': ('plz',), 'FR': ('code_postal',), 'AU': ('postcode',), 'BR': ('cep',), 'IN': ('pin',), 'ZA': ('postal_code',)} - allowed = allowed_by_country.get(country, ('postal_code', 'custom')) - if system not in allowed: - raise ValueError( - f"postal system {system!r} is not valid for country {country!r}; " - f"expected one of {list(allowed)!r}" - ) - return value - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) diff --git a/src/adcp/types/generated_poc/core/postal_area_support.py b/src/adcp/types/generated_poc/core/postal_area_support.py deleted file mode 100644 index 2dc9e2910..000000000 --- a/src/adcp/types/generated_poc/core/postal_area_support.py +++ /dev/null @@ -1,62 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/postal_area_support.json -# timestamp: 2026-10-03T15:09:44+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Dict, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictBool - - -class ME(StrEnum): - zip = 'zip' - zip_plus_four = 'zip_plus_four' - - -class GBEnum(StrEnum): - outward = 'outward' - full = 'full' - - -class CAEnum(StrEnum): - fsa = 'fsa' - full = 'full' - - -class PostalAreaSupportAdditionalPropertyEnum(StrEnum): - postal_code = 'postal_code' - custom = 'custom' - - -class PostalAreaSupport(AdCPBaseModel): - __pydantic_extra__: Dict[ - str, list[PostalAreaSupportAdditionalPropertyEnum] - ] - model_config = ConfigDict( - extra='allow', - ) - US: Annotated[list[ME] | None, Field(min_length=1)] = None - GB: Annotated[list[GBEnum] | None, Field(min_length=1)] = None - CA: Annotated[list[CAEnum] | None, Field(min_length=1)] = None - DE: Annotated[list[Literal['plz']] | None, Field(min_length=1)] = None - CH: Annotated[list[Literal['plz']] | None, Field(min_length=1)] = None - AT: Annotated[list[Literal['plz']] | None, Field(min_length=1)] = None - FR: Annotated[list[Literal['code_postal']] | None, Field(min_length=1)] = None - AU: Annotated[list[Literal['postcode']] | None, Field(min_length=1)] = None - BR: Annotated[list[Literal['cep']] | None, Field(min_length=1)] = None - IN: Annotated[list[Literal['pin']] | None, Field(min_length=1)] = None - ZA: Annotated[list[Literal['postal_code']] | None, Field(min_length=1)] = None - us_zip: Annotated[StrictBool | None, Field(deprecated=True)] = None - us_zip_plus_four: Annotated[StrictBool | None, Field(deprecated=True)] = None - gb_outward: Annotated[StrictBool | None, Field(deprecated=True)] = None - gb_full: Annotated[StrictBool | None, Field(deprecated=True)] = None - ca_fsa: Annotated[StrictBool | None, Field(deprecated=True)] = None - ca_full: Annotated[StrictBool | None, Field(deprecated=True)] = None - de_plz: Annotated[StrictBool | None, Field(deprecated=True)] = None - fr_code_postal: Annotated[StrictBool | None, Field(deprecated=True)] = None - au_postcode: Annotated[StrictBool | None, Field(deprecated=True)] = None - ch_plz: Annotated[StrictBool | None, Field(deprecated=True)] = None - at_plz: Annotated[StrictBool | None, Field(deprecated=True)] = None diff --git a/src/adcp/types/generated_poc/core/postal_country_system.py b/src/adcp/types/generated_poc/core/postal_country_system.py deleted file mode 100644 index 289c69f07..000000000 --- a/src/adcp/types/generated_poc/core/postal_country_system.py +++ /dev/null @@ -1,267 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/postal_country_system.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import Field - - -class System(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - zip_1 = 'zip' - zip_plus_four_1 = 'zip_plus_four' - - -class PostalCountrySystem1(AdCPBaseModel): - country: Annotated[ - Literal['US'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'US' - system: Annotated[ - System, Field(description='Country-local postal code system.', title='Postal Code System') - ] - - -class System11(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - outward_1 = 'outward' - full_1 = 'full' - - -class PostalCountrySystem2(AdCPBaseModel): - country: Annotated[ - Literal['GB'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'GB' - system: Annotated[ - System11, Field(description='Country-local postal code system.', title='Postal Code System') - ] - - -class System12(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - fsa_1 = 'fsa' - full_1 = 'full' - - -class PostalCountrySystem3(AdCPBaseModel): - country: Annotated[ - Literal['CA'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'CA' - system: Annotated[ - System12, Field(description='Country-local postal code system.', title='Postal Code System') - ] - - -class Country(StrEnum): - DE = 'DE' - CH = 'CH' - AT = 'AT' - - -class System13(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - - -class PostalCountrySystem4(AdCPBaseModel): - country: Annotated[Country, Field(description='ISO 3166-1 alpha-2 country code.')] - system: Annotated[ - Literal['plz'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'plz' - - -class PostalCountrySystem5(AdCPBaseModel): - country: Annotated[ - Literal['FR'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'FR' - system: Annotated[ - Literal['code_postal'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'code_postal' - - -class PostalCountrySystem6(AdCPBaseModel): - country: Annotated[ - Literal['AU'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'AU' - system: Annotated[ - Literal['postcode'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'postcode' - - -class PostalCountrySystem7(AdCPBaseModel): - country: Annotated[ - Literal['BR'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'BR' - system: Annotated[ - Literal['cep'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'cep' - - -class PostalCountrySystem8(AdCPBaseModel): - country: Annotated[ - Literal['IN'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'IN' - system: Annotated[ - Literal['pin'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'pin' - - -class PostalCountrySystem9(AdCPBaseModel): - country: Annotated[ - Literal['ZA'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'ZA' - system: Annotated[ - Literal['postal_code'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'postal_code' - - -class System19(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - postal_code_1 = 'postal_code' - custom_1 = 'custom' - - -class PostalCountrySystem10(AdCPBaseModel): - country: Annotated[ - str, Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] - system: Annotated[ - System19, Field(description='Country-local postal code system.', title='Postal Code System') - ] - - -PostalCountrySystem = Annotated[ - PostalCountrySystem1 - | PostalCountrySystem2 - | PostalCountrySystem3 - | PostalCountrySystem4 - | PostalCountrySystem5 - | PostalCountrySystem6 - | PostalCountrySystem7 - | PostalCountrySystem8 - | PostalCountrySystem9 - | PostalCountrySystem10, - Field( - description='Valid country-local postal system pairing. Registered countries only accept their registered local systems; countries without a registered local system use postal_code or custom.', - title='Postal Country System', - ), -] diff --git a/src/adcp/types/generated_poc/core/presentation_ref.py b/src/adcp/types/generated_poc/core/presentation_ref.py deleted file mode 100644 index af4450597..000000000 --- a/src/adcp/types/generated_poc/core/presentation_ref.py +++ /dev/null @@ -1,46 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/presentation_ref.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, field_validator - - -class PlacementPresentationReference(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - uri: Annotated[ - AnyUrl, - Field( - description='Publisher-controlled HTTPS URL for the presentation metadata. Consumers MUST apply the same SSRF, redirect, response-size, timeout, and DNS-rebinding protections used for format_schema fetches.' - ), - ] - digest: Annotated[ - str, - Field( - description='SHA-256 content digest. Consumers cache by uri@digest and MUST fail closed on a digest mismatch.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] - media_type: Annotated[ - Literal['application/vnd.adcp.placement-presentation+json'], - Field(description='Media type of the referenced declarative presentation document.'), - ] = 'application/vnd.adcp.placement-presentation+json' - schema_version: Annotated[ - Literal['1.0'], - Field( - description='Version of /schemas/core/placement-presentation.json used to validate and compose the referenced document.' - ), - ] = '1.0' - - @field_validator('uri') - @classmethod - def _require_https_uri(cls, value: AnyUrl) -> AnyUrl: - if value.scheme != 'https': - raise ValueError('uri must use https') - return value diff --git a/src/adcp/types/generated_poc/core/preview_provider.py b/src/adcp/types/generated_poc/core/preview_provider.py deleted file mode 100644 index db2e2065a..000000000 --- a/src/adcp/types/generated_poc/core/preview_provider.py +++ /dev/null @@ -1,62 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/preview_provider.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, StrictBool, field_validator - - -class Route(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - format_option_id: Annotated[ - str, - Field( - description='Format option in this adagents.json placement for which the delegation applies. It MUST resolve through the same-file top-level formats[] catalog or an inline placement format declaration.', - min_length=1, - ), - ] - capability_id: Annotated[ - str, - Field( - description="Agent-local preview capability advertised by the delegated provider. The provider's canonical format declaration MUST satisfy the resolved placement format option.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - covers_placement_presentation: Annotated[ - StrictBool | None, - Field( - description="True only when the publisher delegates both creative rendering and the complete placement-specific frame to this route. When false or omitted, consumers compose any presentation_ref around the provider's creative render." - ), - ] = False - - -class PublisherDesignatedPreviewProvider(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description='HTTPS URL of the delegated creative-agent endpoint. Buyers call get_adcp_capabilities and preview_creative on this endpoint. They MUST allow only public IPs, pin DNS resolution through connection, refuse redirects, cap time and response size, and attach provider credentials only after exact normalized-origin binding.' - ), - ] - authority: Annotated[ - Literal['publisher_designated'], - Field( - description="Explicitly states that authority comes from the publisher-hosted placement declaration. The provider's rendering_origin metadata is informational and remains non-authoritative elsewhere." - ), - ] = 'publisher_designated' - routes: Annotated[list[Route], Field(min_length=1)] - - @field_validator('agent_url') - @classmethod - def _require_https_agent_url(cls, value: AnyUrl) -> AnyUrl: - if value.scheme != 'https': - raise ValueError('agent_url must use https') - return value diff --git a/src/adcp/types/generated_poc/core/preview_renderer_metadata.py b/src/adcp/types/generated_poc/core/preview_renderer_metadata.py deleted file mode 100644 index 1196ac26a..000000000 --- a/src/adcp/types/generated_poc/core/preview_renderer_metadata.py +++ /dev/null @@ -1,50 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/preview_renderer_metadata.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictBool - - -class RenderingOrigin(StrEnum): - platform_native = 'platform_native' - agent_approximation = 'agent_approximation' - - -class PreviewRendererMetadata(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - renderer_id: Annotated[ - str, Field(description='Stable implementation identifier.', min_length=1) - ] - version: Annotated[ - str, - Field( - description='Exact semantic version of the renderer implementation.', - pattern='^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$', - ), - ] - export: Annotated[ - str, - Field( - description='Renderer export or entry-point name used for this render.', min_length=1 - ), - ] - rendering_origin: Annotated[ - RenderingOrigin, - Field( - description='Informational implementation origin copied from the selected route. It does not grant authority.' - ), - ] - tracking_suppressed: Annotated[ - StrictBool, - Field( - description='True only when the produced output cannot initiate impression, click, billing, conversion, viewability, or asset-fetch side effects. Renderers that retain any remote asset URL or navigation MUST emit false.' - ), - ] diff --git a/src/adcp/types/generated_poc/core/price.py b/src/adcp/types/generated_poc/core/price.py deleted file mode 100644 index 2efecc705..000000000 --- a/src/adcp/types/generated_poc/core/price.py +++ /dev/null @@ -1,39 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/price.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictFloat - - -class Period(StrEnum): - night = 'night' - month = 'month' - year = 'year' - one_time = 'one_time' - - -class Price(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - amount: Annotated[ - StrictFloat, Field(description='Monetary amount in the specified currency.', ge=0.0) - ] - currency: Annotated[ - str, - Field( - description="ISO 4217 currency code (e.g., 'USD', 'EUR', 'GBP').", pattern='^[A-Z]{3}$' - ), - ] - period: Annotated[ - Period | None, - Field( - description="Billing period. 'night' for hotel rates, 'month' or 'year' for salaries and rentals, 'one_time' for purchase prices. Omit when the period is obvious from context (e.g., a vehicle price is always one-time)." - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/pricing_option.py b/src/adcp/types/generated_poc/core/pricing_option.py deleted file mode 100644 index adef81621..000000000 --- a/src/adcp/types/generated_poc/core/pricing_option.py +++ /dev/null @@ -1,41 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/pricing_option.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import Field - -from ..pricing_options import ( - cpa_option, - cpc_option, - cpcv_option, - cpm_option, - cpp_option, - cpv_option, - flat_rate_option, - revenue_share_option, - time_option, - vcpm_option, -) - - -PricingOption = Annotated[ - cpm_option.CpmPricingOption - | vcpm_option.VcpmPricingOption - | cpc_option.CpcPricingOption - | cpcv_option.CpcvPricingOption - | cpv_option.CpvPricingOption - | cpp_option.CppPricingOption - | cpa_option.CpaPricingOption - | revenue_share_option.RevenueSharePricingOption - | flat_rate_option.FlatRatePricingOption - | time_option.TimeBasedPricingOption, - Field( - description="A seller's commercial pricing offer, discriminated by pricing_model. pricing_option_id is the immutable identity of its binding commercial terms: changing fixed price, floor, currency, model, or priced applicability requires a new ID rather than reinterpreting the old one. fixed_price is an agreed seller price; floor_price is the seller minimum for an auction-priced option; price_guidance is non-binding discovery guidance. Buyer execution instructions belong in BiddingPolicy. revenue_share is contingent pricing: its payable spend is calculated from settled commissionable_value and does not use fixed_price or auction bidding fields. The legacy max_bid boolean and package bid_price are deprecated in 3.2 and removed in the next major.", - discriminator='pricing_model', - title='Pricing Option', - ), -] diff --git a/src/adcp/types/generated_poc/core/principal_changed_webhook.py b/src/adcp/types/generated_poc/core/principal_changed_webhook.py deleted file mode 100644 index d95c37469..000000000 --- a/src/adcp/types/generated_poc/core/principal_changed_webhook.py +++ /dev/null @@ -1,95 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/principal_changed_webhook.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - -from . import ext as ext_1 - - -class Reason(StrEnum): - destination_state_changed = 'destination_state_changed' - setup_expiring = 'setup_expiring' - setup_expired = 'setup_expired' - proof_invalidated = 'proof_invalidated' - declarations_intersection_changed = 'declarations_intersection_changed' - other = 'other' - - -class PrincipalChangedWebhook(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - idempotency_key: Annotated[ - str, - Field( - description='Sender-generated key stable across retries of the same fire. Sellers MUST generate a cryptographically random value (UUID v4 recommended) per distinct fire and reuse it on every retry of the same fire. Receivers MUST dedupe by this key, scoped to the authenticated sender identity.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - notification_id: Annotated[ - str, - Field( - description='Stable identifier for this logical principal-state transition. Re-emissions of the same transition reuse this value under a new idempotency_key; a later distinct transition receives a new id.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] - notification_type: Annotated[ - Literal['principal.changed'], - Field( - description="Fixed notification type discriminator. Matches the value registered on the subscriber's `event_types`." - ), - ] = 'principal.changed' - fired_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the seller initiated this fire. Distinct from `changed_at`, which is when the seller recorded the state transition.' - ), - ] - subscriber_id: Annotated[ - str, - Field( - description="Identifies which caller-scoped notification_configs[] entry is receiving this fire. Echoed verbatim from the entry's subscriber_id.", - max_length=64, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,64}$', - ), - ] - agent_url: Annotated[ - AnyUrl, - Field( - description='Canonical seller agent URL whose principal state changed. Receivers connected to multiple agents use this to select which principal record to re-read.' - ), - ] - changed_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the seller recorded the principal-state transition.' - ), - ] - reason: Annotated[ - Reason, - Field( - description='Coarse reason for the invalidation. Advisory routing/debug metadata; receivers MUST re-read get_principal rather than inferring the new state from the reason.' - ), - ] - destination_id: Annotated[ - str | None, - Field( - description='Optional advisory hint naming the affected reporting destination for destination-scoped reasons. Receivers MAY use it for selective handling but MUST still treat the get_principal read as authoritative.', - max_length=64, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,64}$', - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/principal_declarations.py b/src/adcp/types/generated_poc/core/principal_declarations.py deleted file mode 100644 index a80f31bad..000000000 --- a/src/adcp/types/generated_poc/core/principal_declarations.py +++ /dev/null @@ -1,53 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/principal_declarations.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field - -from . import experimental_feature_id - - -class AsyncAdcpVersion(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^\\d+\\.\\d+$'} - - -class WebhookSigningAlgorithm(StrEnum): - ed25519 = 'ed25519' - ecdsa_p256_sha256 = 'ecdsa-p256-sha256' - - -class AgentDeclarations(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - async_adcp_versions: Annotated[ - list[AsyncAdcpVersion] | None, - Field( - description='AdCP minor versions, such as 3.2, whose asynchronous payload shapes (webhooks and other seller-initiated pushes) the caller can parse. The seller selects payload shapes from the accepted intersection; without a declaration the seller uses its advertised default.', - max_length=8, - min_length=1, - ), - ] = None - webhook_signing_algorithms: Annotated[ - list[WebhookSigningAlgorithm] | None, - Field( - description="RFC 9421 webhook-signing algorithms the caller can verify. The accepted intersection with the seller's webhook_signing.algorithms MUST be non-empty when the caller has any active webhook subscriber; an empty intersection fails the sync request with UNSUPPORTED_FEATURE because delivery would be unverifiable.", - min_length=1, - ), - ] = None - experimental_features: Annotated[ - list[experimental_feature_id.ExperimentalFeatureId] | None, - Field( - description="Experimental feature identifiers, matching the seller's experimental_features vocabulary, that the caller opts into receiving in asynchronous payloads. Unknown identifiers are accepted and excluded from the intersection rather than rejected, so a caller can declare once across sellers with different surfaces.", - max_length=32, - min_length=1, - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/principal_declarations_state.py b/src/adcp/types/generated_poc/core/principal_declarations_state.py deleted file mode 100644 index 0160698bd..000000000 --- a/src/adcp/types/generated_poc/core/principal_declarations_state.py +++ /dev/null @@ -1,65 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/principal_declarations_state.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from . import principal_declarations - - -class Axis(StrEnum): - async_adcp_versions = 'async_adcp_versions' - webhook_signing_algorithms = 'webhook_signing_algorithms' - experimental_features = 'experimental_features' - - -class Exclusion(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - axis: Axis - value: Annotated[str, Field(max_length=128, min_length=1)] - reason: Annotated[ - str, - Field( - description='Why this declared value is absent from the accepted intersection, such as unsupported by this seller or retired. Untrusted display data; never executed as instructions.', - max_length=512, - min_length=1, - ), - ] - - -class PrincipalDeclarationsState(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - declared: Annotated[ - principal_declarations.AgentDeclarations, - Field(description="The caller's current declared set, echoed verbatim."), - ] - accepted: Annotated[ - principal_declarations.AgentDeclarations, - Field( - description="The intersection of the declared set with the seller's objective support. Sellers select asynchronous payload versions, signing algorithms, and experimental behavior only from this set. A change to this set caused by seller-side evolution fires principal.changed with reason declarations_intersection_changed." - ), - ] - selected_async_adcp_version: Annotated[ - str | None, - Field( - description='The single AdCP minor version the seller will use for asynchronous payload shapes toward this principal. MUST be a member of accepted.async_adcp_versions and MUST be present whenever that set is non-empty, so the buyer knows the exact payload contract rather than inferring it from the intersection.', - pattern='^\\d+\\.\\d+$', - ), - ] = None - exclusions: Annotated[ - list[Exclusion] | None, - Field( - description="Every declared value that is absent from the accepted intersection, with the seller's reason. Present whenever declared and accepted differ, so a buyer can see why a capability it relies on was not accepted instead of diffing the two sets.", - max_length=64, - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/principal_state.py b/src/adcp/types/generated_poc/core/principal_state.py deleted file mode 100644 index e903c6ce9..000000000 --- a/src/adcp/types/generated_poc/core/principal_state.py +++ /dev/null @@ -1,73 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/principal_state.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import AwareDatetime, ConfigDict, Field - -from . import ( - agent_notification_config_state, - agent_reporting_destination_state, - principal_declarations_state, -) - - -class DestinationRef(ScalarStr): - __slots__ = () - _constraints = {'max_length': 255, 'min_length': 1} - - -class RetiredDestination(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - destination_id: Annotated[ - str, Field(max_length=64, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,64}$') - ] - destination_refs: Annotated[ - list[DestinationRef], - Field( - description='Retained generation references of the revoked destination, newest first, still resolvable for retained reporting history but ineligible for delivery and for new bindings.', - max_length=32, - min_length=1, - ), - ] - revoked_at: AwareDatetime | None = None - - -class PrincipalState(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - notification_configs: Annotated[ - list[agent_notification_config_state.AgentNotificationConfigState] | None, - Field( - description='Current agent-level webhook subscribers. authentication.credentials is always omitted because it is write-only.', - max_length=16, - ), - ] = None - reporting_destinations: Annotated[ - list[agent_reporting_destination_state.AgentReportingDestinationState] | None, - Field( - description='Current reusable reporting destination bindings and setup states. destination_id and destination_ref values MUST each be unique within this caller-scoped array; superseded generations of a current destination appear in its prior_destination_refs.', - max_length=64, - ), - ] = None - declarations: Annotated[ - principal_declarations_state.PrincipalDeclarationsState | None, - Field( - description='Declared consumption facts and the seller-computed accepted intersection. Present when and only when the seller supports the declarations section.' - ), - ] = None - retired_destinations: Annotated[ - list[RetiredDestination] | None, - Field( - description='Destinations the caller revoked by omitting them from a submitted reporting_destinations section, retained while any generation remains resolvable for reporting history. Enumerable only by the owning principal. Reusing a retired destination_id requires fresh registration and proof and produces a new generation.', - max_length=64, - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/product.py b/src/adcp/types/generated_poc/core/product.py deleted file mode 100644 index 6f99f0624..000000000 --- a/src/adcp/types/generated_poc/core/product.py +++ /dev/null @@ -1,893 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/product.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Any, Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarFloat, ScalarStr -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, StrictBool, model_validator - -from ..enums import action_source, audio_distribution_type, catalog_type -from ..enums import channels as channels_1 -from ..enums import delivery_type as delivery_type_1 -from ..enums import exclusivity as exclusivity_1 -from ..enums import ( - reach_unit, - response_type, - social_placement_surface, - sponsored_placement_type, - uid_type, - video_placement_type, - viewability_standard, -) -from . import acceptance_policy_profile_ids as acceptance_policy_profile_ids_1 -from . import audience_activation_method -from . import audience_evidence as audience_evidence_1 -from . import audience_evidence_selection, brand_ref -from . import cancellation_policy as cancellation_policy_1 -from . import collection_selector -from . import creative_policy as creative_policy_1 -from . import data_provider_signal_selector, delivery_forecast, demographic_targeting_capability -from . import ext as ext_1 -from . import format_id, installment, inventory_list_application -from . import measurement_readiness as measurement_readiness_1 -from . import measurement_terms as measurement_terms_1 -from . import media_buy_support as media_buy_support_1 -from . import outcome_measurement as outcome_measurement_1 -from . import ( - performance_standard, - placement, - pricing_option, - product_allowed_action, - product_card_reference_asset, - product_execution_requirement, - product_format_declaration, - product_identity, - product_signal_targeting_option, - product_targeting_resolution, - property_id, - property_tag, -) -from . import reporting_capabilities as reporting_capabilities_1 -from . import signal_listing -from . import signal_targeting_rules as signal_targeting_rules_1 -from . import targeting_overlay_support -from . import vendor_metric_optimization as vendor_metric_optimization_1 -from .assets import image_asset - - -class PublisherDomain(ScalarStr): - __slots__ = () - _constraints = { - 'pattern': '^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - } - - -class PublisherProperty81(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Annotated[ - str | None, - Field( - description="Domain where publisher's adagents.json is hosted (e.g., 'cnn.com'). XOR with `publisher_domains` — exactly one MUST be present on each `publisher_properties[]` entry; both-present and neither-present both fail validation.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - publisher_domains: Annotated[ - list[PublisherDomain] | None, - Field( - description="Compact form for fanning the same selector across many publishers (e.g., a managed network listing every publisher it represents). Each entry is the domain where that publisher's adagents.json is hosted. Each listed domain MUST be canonicalized to lowercase (the `pattern` already rejects uppercase). Mutually exclusive with `publisher_domain`. Each listed domain counts as explicitly scoped for the `managerdomain` fallback safety rule.", - min_length=1, - ), - ] = None - selection_type: Annotated[ - Literal['all'], - Field( - description='Discriminator indicating all properties from each addressed publisher are included' - ), - ] = 'all' - - -class PublisherProperty84(AdCPBaseModel): - pass - - -class PublisherProperty85(PublisherProperty81, PublisherProperty84): - pass - - -class SupportedMetric(StrEnum): - clicks = 'clicks' - views = 'views' - completed_views = 'completed_views' - viewed_seconds = 'viewed_seconds' - viewable_rate = 'viewable_rate' - attention_seconds = 'attention_seconds' - attention_score = 'attention_score' - engagements = 'engagements' - follows = 'follows' - saves = 'saves' - profile_visits = 'profile_visits' - reach = 'reach' - - -class SupportedViewDuration(ScalarFloat): - __slots__ = () - _constraints = {'gt': 0.0} - - -class SupportedTarget(StrEnum): - cost_per = 'cost_per' - threshold_rate = 'threshold_rate' - - -class SupportedTarget5(StrEnum): - cost_per = 'cost_per' - per_ad_spend = 'per_ad_spend' - maximize_value = 'maximize_value' - - -class ConversionTracking(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - action_sources: Annotated[ - list[action_source.ActionSource] | None, - Field( - description="Action sources relevant to this product (e.g. a retail media product might have 'in_store' and 'website', while a display product might only have 'website')", - min_length=1, - ), - ] = None - supported_targets: Annotated[ - list[SupportedTarget5] | None, - Field( - description='Target kinds available for event goals on this product. Values match target.kind on the optimization goal. cost_per: target cost per conversion event. per_ad_spend: target return on ad spend (requires value_field on event sources). maximize_value: maximize total conversion value without a specific ratio target (requires value_field). Only these target kinds are accepted — goals with unlisted target kinds will be rejected. A goal without a target implicitly maximizes conversion count within budget — no declaration needed for that mode. When omitted, buyers can still set target-less event goals.', - min_length=1, - ), - ] = None - platform_managed: Annotated[ - StrictBool | None, - Field( - description="Whether the seller provides its own always-on measurement (e.g. Amazon sales attribution for Amazon advertisers). When true, sync_event_sources response will include seller-managed event sources with managed_by='seller'." - ), - ] = None - - -class MatchedGtin(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[0-9]{8,14}$'} - - -class CatalogMatch(AdCPBaseModel): - matched_gtins: Annotated[ - list[MatchedGtin] | None, - Field( - description="GTINs from the buyer's catalog that are eligible on this product's inventory. Standard GTIN formats (GTIN-8 through GTIN-14). Only present for product-type catalogs with GTIN matching." - ), - ] = None - matched_ids: Annotated[ - list[str] | None, - Field( - description="Item IDs from the buyer's catalog that matched this product's inventory. The ID type depends on the catalog type and content_id_type (e.g., SKUs for product catalogs, job_ids for job catalogs, offering_ids for offering catalogs)." - ), - ] = None - matched_count: Annotated[ - SchemaInt | None, - Field(description="Number of catalog items that matched this product's inventory.", ge=0), - ] = None - submitted_count: Annotated[ - SchemaInt, - Field(description="Total catalog items evaluated from the buyer's catalog.", ge=0), - ] - - -class Specification(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - label: Annotated[str, Field(max_length=60)] - value: Annotated[str, Field(max_length=200)] - - -class Country(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{2}$'} - - -class MaterialSubmission(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - url: Annotated[ - AnyUrl | None, - Field(description='HTTPS URL for uploading or submitting physical creative materials'), - ] = None - email: Annotated[ - EmailStr | None, Field(description='Email address for creative material submission') - ] = None - instructions: Annotated[ - str | None, - Field( - description='Human-readable instructions for material submission (file naming conventions, shipping address, etc.)', - max_length=2000, - ), - ] = None - ext: ext_1.ExtensionObject | None = None - - -class PublisherProperty82(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Annotated[ - str, - Field( - description="Domain where publisher's adagents.json is hosted (e.g., 'cnn.com').", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - selection_type: Annotated[ - Literal['by_id'], - Field(description='Discriminator indicating selection by specific property IDs'), - ] = 'by_id' - property_ids: Annotated[ - list[property_id.PropertyId], - Field(description="Specific property IDs from the publisher's adagents.json", min_length=1), - ] - - -class PublisherProperty83(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Annotated[ - str | None, - Field( - description="Domain where publisher's adagents.json is hosted (e.g., 'cnn.com'). XOR with `publisher_domains` — exactly one MUST be present on each `publisher_properties[]` entry; both-present and neither-present both fail validation.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - publisher_domains: Annotated[ - list[PublisherDomain] | None, - Field( - description="Compact form for fanning the same tag predicate across many publishers (canonical managed-network shape). Each entry is the domain where that publisher's adagents.json is hosted. Each listed domain MUST be canonicalized to lowercase (the `pattern` already rejects uppercase). Mutually exclusive with `publisher_domain`. Each listed domain counts as explicitly scoped for the `managerdomain` fallback safety rule.", - min_length=1, - ), - ] = None - selection_type: Annotated[ - Literal['by_tag'], Field(description='Discriminator indicating selection by property tags') - ] = 'by_tag' - property_tags: Annotated[ - list[property_tag.PropertyTag], - Field( - description="Property tags resolved against each addressed publisher's adagents.json, OR against the parent file's top-level `properties[]` when those properties carry a `publisher_domain` matching the selector. Selector covers all properties carrying any of these tags.", - min_length=1, - ), - ] - - -class PublisherProperty86(PublisherProperty82, PublisherProperty84): - pass - - -class PublisherProperty87(PublisherProperty83, PublisherProperty84): - pass - - -PublisherProperty = PublisherProperty85 | PublisherProperty86 | PublisherProperty87 - - -class MetricOptimization(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supported_metrics: Annotated[ - list[SupportedMetric], - Field( - description='Metric kinds this product can optimize for. Buyers should only request metric goals for kinds listed here. **DEPRECATED values** (slated for removal at next major): `attention_seconds` and `attention_score` — declare vendor-attested attention/quality metrics via `vendor_metric_optimization.supported_metrics[]` with an explicit vendor binding instead. Sellers MAY reject the deprecated values with `TERMS_REJECTED` and a suggestion to use the `vendor_metric` kind.', - min_length=1, - ), - ] - supported_reach_units: Annotated[ - list[reach_unit.ReachUnit] | None, - Field( - description="Reach units this product can optimize for. Required when supported_metrics includes 'reach'. Buyers must set reach_unit to a value in this list on reach optimization goals — sellers reject unsupported values.", - min_length=1, - ), - ] = None - supported_view_durations: Annotated[ - list[SupportedViewDuration] | None, - Field( - description="Video view duration thresholds (in seconds) this product supports for completed_views goals. Only relevant when supported_metrics includes 'completed_views'. When absent, the seller uses their platform default. Buyers must set view_duration_seconds to a value in this list — sellers reject unsupported values." - ), - ] = None - supported_viewability_standards: Annotated[ - list[viewability_standard.ViewabilityStandard] | None, - Field( - description="Viewability standards this product can optimize viewable_rate goals against. Only relevant when supported_metrics includes 'viewable_rate'. When absent, buyers cannot assume a specific standard is supported and sellers reject unsupported values. Buyers must set the goal's standard to a value in this list when it is present.", - min_length=1, - ), - ] = None - supported_targets: Annotated[ - list[SupportedTarget] | None, - Field( - description='Target kinds available for metric goals on this product. Values match target.kind on the optimization goal. Only these target kinds are accepted — goals with unlisted target kinds will be rejected. When omitted, buyers can set target-less metric goals (maximize volume within budget) but cannot set specific targets.' - ), - ] = None - - -class Provider(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="Provider's agent URL from the registry. Canonical identifier for this TMP provider." - ), - ] - context_match: Annotated[ - StrictBool | None, - Field(description='Whether this provider handles context match for this product.'), - ] = False - identity_match: Annotated[ - StrictBool | None, - Field(description='Whether this provider handles identity match for this product.'), - ] = False - countries: Annotated[ - list[Country] | None, - Field( - description="ISO 3166-1 alpha-2 country codes this provider serves for identity match. The router uses this to select the correct regional provider based on the request's country field. Required when identity_match is true.", - min_length=1, - ), - ] = None - uid_types: Annotated[ - list[uid_type.UidType] | None, - Field( - description="Identity types this regional provider can resolve. The router filters providers whose uid_types includes the request's uid_type. Required when identity_match is true.", - min_length=1, - ), - ] = None - - -class TrustedMatch(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - context_match: Annotated[ - StrictBool, - Field( - description="Whether this product supports Context Match requests. When true, the publisher's TMP router will send context match requests to registered providers for this product's inventory." - ), - ] - identity_match: Annotated[ - StrictBool | None, - Field( - description="Whether this product supports Identity Match requests. When true, the publisher's TMP router will send identity match requests to evaluate user eligibility." - ), - ] = False - response_types: Annotated[ - list[response_type.TmpResponseType] | None, - Field(description='What the publisher can accept back from context match.', min_length=1), - ] = [response_type.TmpResponseType.activation] - dynamic_brands: Annotated[ - StrictBool | None, - Field( - description="Whether the buyer can select a brand at match time. When false (default), the brand must be specified on the media buy/package. When true, the buyer's offer can include any brand — the publisher applies approval rules at match time. Enables multi-brand agreements where the holding company or buyer agent selects brand based on context." - ), - ] = False - providers: Annotated[ - list[Provider] | None, - Field( - description="TMP providers integrated with this product's inventory. Each entry identifies a provider by agent_url (from the registry) and declares what match types it supports for this product. The product-level context_match and identity_match booleans declare what the product supports overall; the per-provider booleans declare which provider handles each match type. Enables buyer discovery: 'find products where a specific provider does context matching.'", - min_length=1, - ), - ] = None - - -class ProductCard(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - image: Annotated[ - image_asset.ImageAsset | None, - Field( - description='Hero image for the card. Recommended ~300x400 (4:3 portrait) for the standard card layout; receivers may scale.' - ), - ] = None - title: Annotated[ - str | None, Field(description='Card title (typically the product name).', max_length=60) - ] = None - description: Annotated[ - str | None, - Field(description='Short descriptive blurb shown below the title.', max_length=200), - ] = None - price_label: Annotated[ - str | None, - Field( - description="Formatted price or pricing summary (e.g., 'From $5 CPM', 'Auction floor $0.50 CPC'). Free-text — receivers render verbatim.", - max_length=30, - ), - ] = None - cta_label: Annotated[ - str | None, - Field( - description="Call-to-action button label (e.g., 'View details', 'Get proposal').", - max_length=25, - ), - ] = None - - -class DeliveryMeasurement(AdCPBaseModel): - vendors: Annotated[ - list[brand_ref.BrandReference] | None, - Field( - description="Measurement vendors used for this product, as structured `BrandRef` identities. Multiple entries when multiple vendors play different roles (e.g., the ad server plus a separate viewability vendor like IAS or DV; or a retail-media seller plus a third-party retail measurement vendor like Circana or NielsenIQ). Each vendor's `brand.json` `agents[type='measurement']` is the discovery anchor; metric definitions live on the agent's `get_adcp_capabilities.measurement.metrics[]` block. Distinct from `performance_standards[].vendor` which carries vendor identity for *committed* metrics with thresholds — this field carries vendor identity for the overall measurement story, including non-committed-but-reported metrics.", - min_length=1, - ), - ] = None - provider: Annotated[ - str | None, - Field( - deprecated=True, - description="**Deprecated as of this minor.** Free-form measurement provider description (e.g., 'Google Ad Manager with IAS viewability', 'Nielsen DAR', 'Geopath for DOOH impressions'). New implementations SHOULD use the structured `vendors` field instead. Retained for one-minor backwards compatibility; removed at the next major. When both `vendors` and `provider` are present, consumers MUST use `vendors` for vendor identity and treat `provider` as informational text.", - ), - ] = None - notes: Annotated[ - str | None, - Field( - description="Additional details about measurement methodology in plain language (e.g., 'MRC-accredited viewability. 50% in-view for 1s display / 2s video', 'Panel-based demographic measurement updated monthly'). Free-form prose for context that doesn't fit the structured `vendors` field." - ), - ] = None - - -class ProductCardDetailed(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - hero_image: Annotated[ - image_asset.ImageAsset | None, - Field(description='Primary hero image at the top of the detailed view.'), - ] = None - carousel_images: Annotated[ - list[image_asset.ImageAsset] | None, - Field(description='Additional images for a swipeable carousel below the hero.'), - ] = None - title: Annotated[str | None, Field(description='Page title (typically the product name).')] = ( - None - ) - description: Annotated[ - str | None, - Field( - description='Full descriptive copy. Markdown allowed in client renderers that support it; otherwise treat as plain text.' - ), - ] = None - specifications: Annotated[ - list[Specification] | None, - Field( - description="Structured key/value specifications (e.g., 'Aspect ratio: 9:16', 'Duration: 30s'). Each item is a labeled fact about the product." - ), - ] = None - price_label: Annotated[str | None, Field(description='Formatted price or pricing summary.')] = ( - None - ) - cta_label: Annotated[str | None, Field(description='Call-to-action button label.')] = None - reference_assets: Annotated[ - list[product_card_reference_asset.ProductCardReferenceAsset] | None, - Field( - description='Typed seller collateral for buyer planning — coverage maps, sample renders, environment photos, media kits. Distinct from hero_image/carousel_images, which are display-oriented.' - ), - ] = None - - -class AudienceActivation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - methods: Annotated[ - list[audience_activation_method.AudienceActivationMethod], - Field( - description='Activation capabilities available on this product, as an unordered set. Entries are usually independent options; a clean_room entry may compose with dataset_query or platform_distribution to declare how a targetable result leaves the room or reaches the buying platform.', - min_length=1, - ), - ] - preferred_method: Annotated[ - audience_activation_method.AudienceActivationMethod | None, - Field( - description="The seller's preferred path when the buyer supports several. MUST also appear in methods." - ), - ] = None - notes: Annotated[ - str | None, - Field( - description='Free-text caveats (onboarding lead times, data-format constraints, regional restrictions).' - ), - ] = None - - -class Product(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - - - @model_validator(mode='before') - @classmethod - def _coerce_publisher_property_models(cls, data: Any) -> Any: - if isinstance(data, dict) and isinstance(data.get('publisher_properties'), list): - coerced = [] - changed = False - for item in data['publisher_properties']: - if hasattr(item, 'model_dump'): - coerced.append(item.model_dump(mode='json', exclude_none=True)) - changed = True - else: - coerced.append(item) - if changed: - data = dict(data) - data['publisher_properties'] = coerced - return data - product_id: Annotated[ - str, - Field( - description='Opaque identifier for this buyable product. For a non-custom wholesale product, sellers MUST reuse the ID for the same logical catalog offer within the seller and declared cache_scope across reads and wholesale-feed webhooks; feed and pricing versions communicate temporal catalog mutation, while retirement or replacement may end the identity. Concurrent or request-bound configurations whose effective targeting, disclosed targeting modifications, forecast assumptions, terms, or overlay support differ MUST use distinguishable configured product IDs. For is_custom: true, the ID identifies only the request-specific discovery/refinement lineage and is not stable across independent contexts. Sellers MUST keep every issued configured ID resolvable for its promised lifetime. Pricing variants within one logical product are distinguished by pricing_option_id: a seller MUST mint a new pricing_option_id whenever a binding fixed price, floor, currency, model, or priced applicability changes, and MUST NOT reinterpret an issued option ID at a new price. Selecting product_id plus pricing_option_id in create_media_buy accepts that returned configuration and commercial option.' - ), - ] - name: Annotated[str, Field(description='Human-readable product name')] - description: Annotated[ - str, Field(description='Detailed description of the product and its inventory') - ] - publisher_properties: Annotated[ - list[PublisherProperty], - Field( - description="SDK implementers MUST enforce singular-only at runtime: each entry uses the singular `publisher_domain` form; the compact `publisher_domains[]` form is rejected on products. Codegen toolchains (json-schema-to-typescript, quicktype, datamodel-code-generator, openapi-typescript-codegen) often flatten the `allOf + $ref + not.required` restriction below poorly and may drop the rejection constraint silently, emitting an unrestricted type — runtime enforcement is the safety net. Publisher properties covered by this product. Buyers fetch actual property definitions from each publisher's adagents.json and validate agent authorization. Selection patterns mirror the authorization patterns in adagents.json for consistency. The compact `publisher_domains[]` form is reserved for adagents.json `authorized_agents[].publisher_properties[]` so that buy-side traffic-and-pricing flatteners can always treat each entry as exactly one publisher.", - min_length=1, - ), - ] - channels: Annotated[ - list[channels_1.MediaChannel] | None, - Field( - description="Advertising channels this product is sold as. Products inherit from their properties' supported_channels but may narrow the scope. For example, a product covering YouTube properties might be sold as ['ctv'] even though those properties support ['olv', 'social', 'ctv']." - ), - ] = None - format_ids: Annotated[ - list[format_id.FormatReferenceStructuredObject] | None, - Field( - deprecated=True, - description='Deprecated in AdCP 3.2; removed in AdCP 4.0. Legacy named-format compatibility path. Products MUST carry `format_ids`, `format_options`, or both during the 3.x migration window. New products MUST author canonical `format_options[]`; sellers MAY additionally project those declarations to `format_ids` for legacy buyers. When both fields are present they MUST describe the same underlying formats, and buyers MUST prefer `format_options`. Do not author a new product from `format_ids` alone.', - ), - ] = None - format_options: Annotated[ - list[product_format_declaration.ProductFormatDeclaration] | None, - Field( - description="Canonical format-option path: one or more inline format declarations the product accepts. Each element narrows a canonical format with parameters, slots, platform_extensions, and optional locale_policy. New 3.2 products MUST carry format_options; a seller MAY additionally project the same declarations to deprecated format_ids for older 3.x peers. A declaration carrying locale_policy is canonical-only because legacy format_ids cannot preserve locale eligibility; no product or placement format_id may project to an effective locale-constrained option.\n\nWhen placements are published, product-level format_options are the union of formats deliverable somewhere in the product and the upper bound for every placement. A placement's effective accepted set is the intersection of every applicable layer: (1) the product format_options; (2) the product placement's inline format_options, when present; and (3) for kind publisher_ref, the named publisher's adagents.json catalog narrowing. Resolve layer 3 by locating the matching placements[] entry: use its format_options when present, resolving bare format_option_id references against that same file's top-level formats[]; otherwise use top-level formats[] applicable to the placement's property_ids/property_tags. An omitted optional layer is unconstrained, but an unresolved publisher placement or format-option reference MUST fail closed. A publisher-referenced placement without inline product format_options therefore does NOT inherit the full product union when the publisher catalog supplies narrower placement or property-scoped acceptance.\n\nMatch publisher-declared options by {publisher_domain, format_option_id}, match product-local options by format_option_id when publisher_domain is omitted, and otherwise match declarations with the same format_kind whose narrower parameters satisfy the broader declaration. A product- or placement-level declaration MUST NOT introduce a format outside the product upper bound. Locale policy follows the same intersection. If the product locale policy is absent, a placement may introduce any concrete policy as a narrowing of an unconstrained option; when both are present, every placement range must be contained by a product range under RFC 4647 Basic Filtering. Locale eligibility is checked independently for every placement where an assignment may serve.\n\nFor a product or package containing multiple included placements, a single creative intended for every placement MUST lie in the intersection of every selected placement's effective set. Distinct per-placement creatives MAY use the union, but the selected creative set MUST cover every included placement; uncovered inventory MUST be rejected or refined, never silently omitted. If a product spans multiple publishers but omits placements[], there is no public routing key for per-placement creatives: its format_options MUST therefore be the common intersection accepted across every selected publisher/property scope. A seller that needs a union of publisher-specific formats MUST publish placements[] with publisher-scoped identities and narrowing. Commercial terms such as price, floor, availability, and deal eligibility are product facts, not format parameters.", - min_length=1, - ), - ] = None - placements: Annotated[ - list[placement.Placement] | None, - Field( - description="Optional array of specific public placements within this product. Product placements declare `kind` to distinguish publisher-catalog placements (`publisher_ref`) from sales-agent-defined placements (`seller_inline`). Publisher references use canonical `{publisher_domain, placement_id}` identity and may omit name because adagents.json resolves it. New seller-inline placements SHOULD carry `seller_agent`; legacy rows without it remain scoped to the enclosing seller and product. A seller-inline publisher_domain is inventory attribution, not authority to mint an ID in that publisher's catalog namespace. Each placement MUST declare mode: targetable or included. Creative assignments route creatives only after placement inventory is purchased.", - min_length=1, - ), - ] = None - video_placement_types: Annotated[ - list[video_placement_type.VideoPlacementType] | None, - Field( - description='Declared video placement types that may be included in this product, using IAB Tech Lab/OpenRTB 2.6 video.plcmt definitions with AdCP-native names. Use on OLV, CTV, and other video products when buyers need to distinguish instream, accompanying-content, interstitial, and standalone/no-content inventory. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `video_placement_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - audio_distribution_types: Annotated[ - list[audio_distribution_type.AudioDistributionType] | None, - Field( - description='Declared audio distribution types that may be included in this product, using IAB Tech Lab/OpenRTB 2.6 audio.feed definitions with AdCP-native names. Use on radio, streaming-audio, podcast, gaming, and other audio products when buyers need to distinguish music streaming services, FM/AM broadcast, podcasts, catch-up radio, web radio, video-game audio, and text-to-speech inventory without changing the buyer-facing channel or adagents.json property type. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `audio_distribution_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - sponsored_placement_types: Annotated[ - list[sponsored_placement_type.SponsoredPlacementType] | None, - Field( - description='Declared sponsored-placement types that may be included in this product, distinguishing where catalog-driven retail-media placements render on the retailer surface (sponsored search, sponsored display, or sponsored native). Use on retail-media products when buyers need to distinguish search-keyed, display, and native in-grid sponsored inventory. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `sponsored_placement_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - social_placement_surfaces: Annotated[ - list[social_placement_surface.SocialPlacementSurface] | None, - Field( - description='Declared social-placement surfaces that may be included in this product, distinguishing the in-app surface where social placements render (feed, stories, short_video, explore, or search). Use on social products when buyers need to distinguish feed, story, short-video, and discovery surfaces. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `social_placement_surfaces`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - delivery_type: delivery_type_1.DeliveryType - exclusivity: Annotated[ - exclusivity_1.Exclusivity | None, - Field( - description="Whether this product offers exclusive access to its inventory. Defaults to 'none' when absent. Most relevant for guaranteed products tied to specific collections or placements." - ), - ] = None - pricing_options: Annotated[ - list[pricing_option.PricingOption], - Field( - description="Available pricing models for this product. Fixed prices and auction floors are binding for every later targeting selection permitted by this product's overlay_support; price_guidance remains non-binding. Declaring broad overlay support alongside a binding option is therefore a uniform-price promise, not permission to calculate a different price at create time. A seller with value-dependent rates MUST return a request-specific configured product after rediscovery with concrete targeting, split the inventory into separately priced products, or expose only non-binding guidance until it can issue a binding option. The seller MUST mint a new pricing_option_id whenever a binding price, floor, currency, model, or priced applicability changes. It MUST NOT silently reprice a create request or reuse the selected option ID with different terms.", - min_length=1, - ), - ] - forecast: Annotated[ - delivery_forecast.DeliveryForecast | None, - Field( - description="Forecasted delivery metrics for this product. Concrete discovery targeting scopes the forecast to those effective values. When discovery requested only required_overlay_support for a dimension, the forecast describes the product's discovery/default scope and is not a value-specific forecast for every later selection; buyers rediscover with concrete targeting_overlay values when they need that forecast." - ), - ] = None - outcome_measurement: Annotated[ - outcome_measurement_1.OutcomeMeasurement | None, - Field( - deprecated=True, - description='**Deprecated as of this minor.** Outcome capabilities (incremental sales lift, brand lift, foot traffic, etc.) are now declared via `reporting_capabilities.available_metrics` (the same path used for impressions, conversions, ROAS) with `qualifier.attribution_methodology` and `qualifier.attribution_window` carrying the methodology and window on commit. New implementations SHOULD use the unified pattern; this field is retained for one-minor backwards compatibility and removed at the next major. See `outcome-measurement.json` description for migration guidance.', - ), - ] = None - delivery_measurement: Annotated[ - DeliveryMeasurement | None, - Field( - description='Measurement vendors and methodology for delivery metrics. The buyer accepts the declared vendors as the source of truth for the buy. When absent, buyers should apply their own measurement defaults. Senders SHOULD populate `vendors` (structured BrandRef array) for new implementations; the legacy `provider` string field is deprecated and retained for one-minor backwards compatibility.' - ), - ] = None - measurement_terms: Annotated[ - measurement_terms_1.MeasurementTerms | None, - Field( - description="Seller's default billing measurement and makegood terms. Declares who counts the billing metric and what remedies apply when thresholds are breached. Buyers may propose different terms at media buy creation — sellers accept, reject (TERMS_REJECTED), or adjust per their policy." - ), - ] = None - performance_standards: Annotated[ - list[performance_standard.PerformanceStandard] | None, - Field( - description="Seller's default performance standards for this product: viewability, IVT, completion rate, brand safety, attention score. Buyers may propose different standards at media buy creation. When absent, no structured performance standards apply.", - min_length=1, - ), - ] = None - cancellation_policy: Annotated[ - cancellation_policy_1.CancellationPolicy | None, - Field( - description='Cancellation terms for this product. Declares the minimum notice period required before cancellation takes effect and any penalties for insufficient notice. Relevant for guaranteed delivery products. Buyers accept these terms by creating a media buy against the product.' - ), - ] = None - allowed_actions: Annotated[ - list[product_allowed_action.ProductAllowedAction] | None, - Field( - description='Actions buyers may perform on buys created against this product, scoped to statuses and modes. Advisory template — the authoritative per-buy capability is `available_actions[]` on the buy response, which resolves modes against current buy state, account tier, and negotiated terms. Buyers SHOULD use this for pre-flight product selection ("which products let me self-serve cancel within 72hr?") and read `available_actions[]` for runtime decisions. The array is uniquely keyed by `action` — sellers MUST NOT emit two entries with the same `action` value. Absence means the seller has not declared a structured action surface for this product — buyers fall back to `valid_actions[]` on buy responses for the flat string vocabulary.', - min_length=1, - ), - ] = None - reporting_capabilities: reporting_capabilities_1.ReportingCapabilities - creative_policy: creative_policy_1.CreativePolicy | None = None - is_custom: Annotated[ - StrictBool | None, - Field( - description='Whether this product is a request-specific configured offer rather than a reusable baseline product. Sellers MUST set true when targeting, disclosed resolution, pricing, forecast assumptions, inventory, or terms are bound for a particular discovery/refinement lineage. Products issued through targeting-aware discovery include expires_at even when exact acceptance omits targeting_resolution. For backward compatibility, is_custom alone does not make expires_at schema-required.' - ), - ] = None - property_targeting_allowed: Annotated[ - StrictBool | None, - Field( - description="Whether buyers can select a subset of this product's publisher_properties through targeting_overlay.property_list. When false, the product is fixed inventory: it matches requested property targeting only when its inherent property set already satisfies the request, or when a configured product discloses additional inventory through targeting_resolution." - ), - ] = False - data_provider_signals: Annotated[ - list[data_provider_signal_selector.DataProviderSignalSelector] | None, - Field( - deprecated=True, - description='Deprecated. Legacy/non-selectable metadata for data-provider signals already bundled into or associated with this product. This field does not provide buyer-selectable options, prices, or seller activation handles. Use included_signals for non-selectable product signal metadata, or signal_targeting_options for selectable package-level signal groups.', - ), - ] = None - included_signals: Annotated[ - list[signal_listing.SignalListing] | None, - Field( - description="Non-selectable signal metadata for signals already included in, bundled with, or planned into this product. These signals describe what the product is; buyers do not select them in packages[].targeting_overlay.signal_targeting_groups and this field does not imply package-level signal targeting. Use signal_ref scope 'data_provider' or 'signal_source' to reference externally defined signals without redefining their name or value_type. Use signal_ref scope 'product' with name and value_type when the included signal is defined only by this product.", - min_length=1, - ), - ] = None - signal_targeting_options: Annotated[ - list[product_signal_targeting_option.ProductSignalTargetingOption] | None, - Field( - description="Inline seller-offered signals that may be applied to packages for this product at create_media_buy time. Each entry references a named signal definition with signal_ref scope 'product' for a product-local signal option, scope 'data_provider' for an external signal definition published in adagents.json signals[] that the seller is authorized to apply, or scope 'signal_source' for a source-native signal. Product-local options define name and value_type inline; data-provider and signal-source options may omit those fields when the referenced definition or source is authoritative. Use this field when the selectable menu is product-specific, has product-specific pricing or activation handles, is the relevant subset for a brief/refine result, or should be rendered without an additional get_signals call. Wholesale products may omit this field and rely on get_signals for the selectable signal feed. Buyers select eligible signals through packages[].targeting_overlay.signal_targeting_groups when signal_targeting_rules allow; fixed/default entries are applied by the seller and echoed on the package state. Sellers MUST set signal_targeting_allowed to true whenever this field is present. Bundled, non-selectable signal metadata belongs in included_signals; legacy data_provider_signals may appear only for backwards compatibility.", - min_length=1, - ), - ] = None - signal_targeting_rules: Annotated[ - signal_targeting_rules_1.SignalTargetingRules | None, - Field( - description='Composition rules for selecting signals on this product. The selectable signal menu may come from inline signal_targeting_options or from get_signals when a wholesale product omits inline options. This is product-scoped because products may be backed by different ad servers with different Boolean targeting support and group limits.' - ), - ] = None - signal_targeting_allowed: Annotated[ - StrictBool | None, - Field( - description='Whether this product has a package-level signal_targeting_groups surface. When false (default), signals are bundled into the product terms and cannot be selected or explicitly echoed as package signal groups. When true, eligible signals from inline signal_targeting_options or from get_signals may be buyer-selected or seller-applied according to signal_targeting_rules and are represented through packages[].targeting_overlay.signal_targeting_groups. Editability is controlled by signal_targeting_rules; fixed/default-only products still set this to true when applied signal groups are echoed.' - ), - ] = False - demographic_targeting: Annotated[ - demographic_targeting_capability.DemographicTargetingCapability | None, - Field( - description='Exact demographic execution available for this product. Buyers MUST use this product-scoped declaration, not the seller-wide get_adcp_capabilities rollup, to preflight a demographic predicate.' - ), - ] = None - overlay_support: Annotated[ - targeting_overlay_support.TargetingOverlaySupport | None, - Field( - description='Binding product-scoped targeting dimensions the buyer may set independently on packages after discovery. Presence guarantees selectable capability subject to disclosed limits, not inventory or a forecast for every possible value. Targeting satisfied only through inherent product scope does not appear here. Returned products MUST cover every field requested through get_products.required_overlay_support. A later supported selection with no available inventory returns PRODUCT_UNAVAILABLE on create; an update outside the original priced envelope may return REQUOTE_REQUIRED.' - ), - ] = None - media_buy_support: Annotated[ - media_buy_support_1.ProductMediaBuySupport | None, - Field( - description='Binding product participation in shared MediaBuy-level controls. This is separate from overlay_support because a root frequency cap aggregates exposures across packages rather than targeting one package. Returned products MUST cover every field requested through required_media_buy_support.' - ), - ] = None - identity: product_identity.ProductIdentity | None = None - execution_requirements: Annotated[ - list[product_execution_requirement.ProductExecutionRequirement] | None, - Field( - description="Experimental (`media_buy.execution_requirements`). Account resources a package on this product needs before `create_media_buy` succeeds. Every entry is required. Account-independent: it does not change `cache_scope` and carries no account resource IDs or names. When present, it plus the `required_connections` of the package's selected format declarations is complete for the kinds in `product-execution-requirement.json`: a seller MUST NOT reject a package that satisfies all of them for lacking an undeclared kind. Absence means undeclared. A declaring seller MUST reject an unmet `event_source` or `catalog` entry on a buyer-supplied `create_media_buy` `packages[]` or `update_media_buy` `new_packages[]` entry, or an update that removes a satisfying binding, with `VALIDATION_ERROR`, `error.field` at the binding, and `error.details` per `error-details/execution-requirement-unmet.json`.", - min_length=1, - ), - ] = None - targeting_resolution: Annotated[ - product_targeting_resolution.ProductTargetingResolution | None, - Field( - description='Discovery-time targeting resolution bound to this configured product. modifications sparsely disclose product-specific differences from get_products.targeting_overlay. Request-level brief interpretation is returned once on GetProductsResponse.targeting_resolution. Exact structured overlay values are not repeated. Selecting product_id accepts the disclosed modifications; product forecast and pricing MUST reflect them.' - ), - ] = None - audience_evidence: Annotated[ - list[audience_evidence_1.AudienceEvidence] | None, - Field( - description='Immutable population-level evidence explaining why this inventory may suit an audience. This supports discovery, comparison, and planning only. It does not imply exact demographic targeting, user-level signal membership, or legal-age verification. Sellers MUST publish each distinct snapshot with a new snapshot_id and content_digest.', - min_length=1, - ), - ] = None - audience_evidence_selections: Annotated[ - list[audience_evidence_selection.AudienceEvidenceSelection] | None, - Field( - description='Exact evidence snapshots that satisfied required eligibility or affected seller ranking for this get_products result. When audience_evidence_requirements was supplied and evidence influenced inclusion or rank, sellers MUST return the relevant selections; an absent-evidence match under evidence_presence when_available has no selection. Product selections use decision_use recommendation or eligibility.', - min_length=1, - ), - ] = None - catalog_types: Annotated[ - list[catalog_type.CatalogType] | None, - Field( - description='Catalog types this product supports for catalog-driven campaigns. A sponsored product listing declares ["product"], a job board declares ["job", "offering"]. Buyers match synced catalogs to products via this field.', - min_length=1, - ), - ] = None - metric_optimization: Annotated[ - MetricOptimization | None, - Field( - description="Metric optimization capabilities for this product. Presence indicates the product supports optimization_goals with kind: 'metric'. No event source or conversion tracking setup required — the seller tracks these metrics natively." - ), - ] = None - vendor_metric_optimization: Annotated[ - vendor_metric_optimization_1.VendorMetricOptimization | None, - Field( - description="Vendor-attested metric optimization capabilities for this product. Presence indicates the product supports `optimization_goals` with `kind: 'vendor_metric'` — the seller's bidding stack can steer delivery toward a specific vendor's measurement (e.g., DV/IAS/Adelaide attention, Scope3 emissions, Kantar brand lift, retail-media partner metrics). Distinct from `metric_optimization` (seller-native metrics with no vendor binding) and from `reporting_capabilities.vendor_metrics` (which declares what the product can *report* rather than what it can *optimize against*). A product may report a vendor metric without being able to optimize for it. Buyers MUST verify the goal's `(vendor, metric_id)` is in `supported_metrics` AND that the package's `committed_metrics[]` includes a matching `{ scope: 'vendor', vendor, metric_id }` entry — optimization without committed reporting is unverifiable and is rejected at the wire level." - ), - ] = None - max_optimization_goals: Annotated[ - SchemaInt | None, - Field( - description='Maximum number of optimization_goals this product accepts on a package. When absent, no limit is declared. Most social platforms accept only 1 goal — buyers sending arrays longer than this value should expect the seller to use only the highest-priority (lowest priority number) goal.', - ge=1, - ), - ] = None - measurement_readiness: Annotated[ - measurement_readiness_1.MeasurementReadiness | None, - Field( - description="Assessment of whether the buyer's event source setup is sufficient for this product to optimize effectively. Only present when the seller can evaluate the buyer's account context. Buyers should check this before creating media buys with event-based optimization goals." - ), - ] = None - conversion_tracking: Annotated[ - ConversionTracking | None, - Field( - description="Conversion event tracking for this product. Presence indicates the product supports optimization_goals with kind: 'event'. Seller-level capabilities (supported event types, UID types, attribution windows) are declared in get_adcp_capabilities." - ), - ] = None - catalog_match: Annotated[ - CatalogMatch | None, - Field( - description='When the buyer provides a catalog on get_products, indicates which catalog items are eligible for this product. Only present for products where catalog matching is relevant (e.g., sponsored product listings, job boards, hotel ads).' - ), - ] = None - brief_relevance: Annotated[ - str | None, - Field( - description='Explanation of why this product matches the brief (only included when brief is provided)' - ), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field( - description='Expiration timestamp. Required for request-specific configured products whose targeting resolution, price, forecast, inventory, or terms are time-bound. After this time, a seller that still recognizes the issued configured ID within the authenticated account and referenced discovery/refinement lineage rejects create_media_buy with PRODUCT_EXPIRED and the buyer re-runs get_products. Once the seller no longer retains an expiry tombstone, or whenever the ID belongs to another account or lineage, PRODUCT_NOT_FOUND applies instead; sellers are not required to retain tombstones indefinitely and MUST NOT disclose cross-tenant existence through error choice.' - ), - ] = None - product_card: Annotated[ - ProductCard | None, - Field( - description='Optional standard visual card for displaying this product in user interfaces (catalog browsers, dashboards, agent UIs). Distinct from `format` — product_card describes the UI rendering of the product itself, not the ad creative the product accepts. Typed inline; no format_id indirection. Receivers render the card directly from these fields.' - ), - ] = None - product_card_detailed: Annotated[ - ProductCardDetailed | None, - Field( - description='Optional detailed card with hero + carousel + structured specifications, for rich product presentation (media-kit-style pages, full product detail views). Distinct from `format` — describes the UI rendering of the product itself, not the ad creative the product accepts. Typed inline; no format_id indirection.' - ), - ] = None - collections: Annotated[ - list[collection_selector.CollectionSelector] | None, - Field( - description='Collections available in this product. Each entry references collections declared in an adagents.json by domain and collection ID. Buyers resolve full collection objects from the referenced adagents.json. Product selectors must name explicit collection_ids — the domain-only bulk-grant selector form is for authorization scoping, not product composition.', - min_length=1, - ), - ] = None - collection_targeting_allowed: Annotated[ - StrictBool | None, - Field( - description="Whether buyers can select a subset of this product's collections through targeting_overlay.collection_list or targeting_overlay.collection_selection. When false, the product is a fixed bundle (a collection_selection that exactly restates the complete bundle remains an inherent match); when true, collection selection is a product-scoped overlay capability." - ), - ] = False - list_applications: Annotated[ - list[inventory_list_application.InventoryListApplication] | None, - Field( - description='Product-scoped receipts for every effective property- or collection-list targeting reference. Sellers MUST return one receipt per application regardless of response field projection; exclusion applications receive a receipt even when summary.matched is zero, while zero matches for any inclusion application make the product ineligible and it is not returned. Each receipt uses the same pre-list product inventory baseline; pricing and forecast reflect inventory remaining after all effective lists are composed.', - min_length=1, - ), - ] = None - installments: Annotated[ - list[installment.Installment] | None, - Field( - description='Specific installments included in this product. Each installment references its parent through canonical collection_ref when the product spans multiple collections or publisher namespaces; collection_id remains a deprecated single-namespace shorthand. When absent with collections present, the product covers the collections broadly (run-of-collection).' - ), - ] = None - enforced_policies: Annotated[ - list[str] | None, - Field( - description='Registry policy IDs the seller enforces for this product. Enforcement level comes from the policy registry. Buyers can filter products by required policies.' - ), - ] = None - acceptance_policy_profile_ids: ( - acceptance_policy_profile_ids_1.AcceptancePolicyProfileIds | None - ) = None - trusted_match: Annotated[ - TrustedMatch | None, - Field( - description='Trusted Match Protocol capabilities for this product. When present, the product supports real-time contextual and/or identity matching via TMP. Buyers use this to determine what response types the publisher can accept and whether brands can be selected dynamically at match time.' - ), - ] = None - audience_activation: Annotated[ - AudienceActivation | None, - Field( - description="How buyer audience data can reach this product's targeting. Absence means undeclared — buyers SHOULD treat it as needs-clarification rather than non-support, except under an audience_activation_methods filter, where sellers MUST exclude undeclared products. Declare when the product accepts buyer audiences. Experimental: sellers declaring this MUST list media_buy.audience_activation in experimental_features on get_adcp_capabilities." - ), - ] = None - material_submission: Annotated[ - MaterialSubmission | None, - Field( - description="Instructions for submitting physical creative materials (print, static OOH, cinema). Present only for products requiring physical delivery outside the digital creative assignment flow. Buyer agents MUST validate url and email domains against the seller's known domains (from adagents.json) before submitting materials. Never auto-submit without human confirmation." - ), - ] = None - ext: ext_1.ExtensionObject | None = None - - @model_validator(mode='after') - def _require_schema_required_group(self) -> Product: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('format_ids',), ('format_options',),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'Product requires at least one of these field groups: format_ids | format_options' - ) diff --git a/src/adcp/types/generated_poc/core/product_allocation.py b/src/adcp/types/generated_poc/core/product_allocation.py deleted file mode 100644 index 92e36e431..000000000 --- a/src/adcp/types/generated_poc/core/product_allocation.py +++ /dev/null @@ -1,97 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/product_allocation.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat - -from ..enums import pacing as pacing_1 -from . import daypart_target, delivery_forecast -from . import ext as ext_1 - - -class ProductAllocation(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - product_id: Annotated[ - str, Field(description='ID of the product (must reference a product in the products array)') - ] - allocation_percentage: Annotated[ - StrictFloat | None, - Field( - description='Exact percentage of total budget allocated to this product in a fixed proposal. Percentages across all allocations MUST sum to 100. Must be absent in a seller-optimized proposal.', - ge=0.0, - le=100.0, - ), - ] = None - min_spend_target_percentage: Annotated[ - StrictFloat | None, - Field( - description='Soft minimum-spend target as a percentage of the executed total budget. Only valid in seller-optimized proposals, and sellers MUST NOT emit it unless they advertise media_buy.features.seller_optimized_min_spend_targets. The seller SHOULD attempt to reach it, but it is not a delivery guarantee. Minimum targets across allocations MUST sum to no more than 100.', - ge=0.0, - le=100.0, - ), - ] = None - max_spend_percentage: Annotated[ - StrictFloat | None, - Field( - description='Hard maximum share of the executed total budget that this product may spend. Only valid in seller-optimized proposals, and sellers MUST NOT emit it unless they advertise media_buy.features.seller_optimized_package_budgets. Maximums across allocations MUST collectively permit 100 percent of the budget to be spent.', - ge=0.0, - le=100.0, - ), - ] = None - pacing: Annotated[ - pacing_1.Pacing | None, - Field( - description='Recommended subordinate package pacing. On proposal execution this becomes pacing on the derived package and MUST NOT cause delivery to exceed aggregate proposal/media-buy pacing. On a committed proposal it is a firm delivery term. On a seller-optimized proposal, sellers MUST NOT emit it unless they advertise media_buy.features.seller_optimized_package_pacing.' - ), - ] = None - pricing_option_id: Annotated[ - str | None, - Field( - description="Selected pricing option ID from the product's pricing_options array. Required when the containing proposal is committed so create_media_buy executes the exact disclosed commercial terms; optional on legacy draft proposals." - ), - ] = None - rationale: Annotated[ - str | None, - Field(description='Explanation of why this product and allocation are recommended'), - ] = None - sequence: Annotated[ - SchemaInt | None, - Field(description='Optional ordering hint for multi-line-item plans (1-based)', ge=1), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description="Categorical tags for this allocation (e.g., 'desktop', 'german', 'mobile') - useful for grouping/filtering allocations by dimension" - ), - ] = None - start_time: Annotated[ - AwareDatetime | None, - Field( - description='Recommended flight start date/time for this allocation in ISO 8601 format. Allows publishers to propose per-flight scheduling within a proposal. When omitted, the allocation applies to the full campaign date range.' - ), - ] = None - end_time: Annotated[ - AwareDatetime | None, - Field( - description='Recommended flight end date/time for this allocation in ISO 8601 format. Allows publishers to propose per-flight scheduling within a proposal. When omitted, the allocation applies to the full campaign date range.' - ), - ] = None - daypart_targets: Annotated[ - list[daypart_target.DaypartTarget] | None, - Field( - description="Recommended time windows for this allocation in spot-plan proposals. Each entry's timezone defaults to inventory_local when omitted, and entries MAY use different clocks.", - min_length=1, - ), - ] = None - forecast: Annotated[ - delivery_forecast.DeliveryForecast | None, - Field(description='Forecasted delivery metrics for this allocation'), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/product_allowed_action.py b/src/adcp/types/generated_poc/core/product_allowed_action.py deleted file mode 100644 index 3f1c2c29c..000000000 --- a/src/adcp/types/generated_poc/core/product_allowed_action.py +++ /dev/null @@ -1,58 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/product_allowed_action.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..enums import media_buy_action_mode, media_buy_status -from ..media_buy import change_term_constraints -from . import media_buy_available_action_id, sla_window - - -class ProductAllowedAction(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - action: Annotated[ - media_buy_available_action_id.MediaBuyAvailableActionId, - Field( - description='The action identifier. Accepts every legacy valid_actions value plus structured-only actions such as update_media_buy_frequency_cap.' - ), - ] - modes: Annotated[ - list[media_buy_action_mode.MediaBuyActionMode], - Field( - description='Modes available for this action on this product. A product may declare multiple modes (for example `self_serve` within tolerances, escalating to `requires_approval` outside) — the buy-side `available_actions[].mode` resolves to the singular mode in effect at mutation time. SDKs that see multiple modes MUST NOT assume which one will fire; they must read the resolved `mode` on the buy.', - min_length=1, - ), - ] - allowed_statuses: Annotated[ - list[media_buy_status.MediaBuyStatus] | None, - Field( - description='Media buy statuses in which this action is permitted. When absent, the action is permitted in all non-terminal statuses (`pending_creatives`, `pending_start`, `active`, `paused`).', - min_length=1, - ), - ] = None - sla: Annotated[ - sla_window.SlaWindow | None, - Field( - description='Optional SLA commitment for this action on this product. Absence means no commitment.' - ), - ] = None - constraints: Annotated[ - change_term_constraints.MediaBuyChangeTermConstraints | None, - Field( - description='Optional advisory machine-readable bounds buyers can use during product selection. The proposal must restate any binding bounds in commercial_terms.change_terms[].constraints.' - ), - ] = None - terms_ref: Annotated[ - str | None, - Field( - description='Optional advisory pointer to published commercial terms governing this product action. It is not a proposal change-term identity and never grants a binding change right; a proposal materializes binding rights under commercial_terms.change_terms[].term_id.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/product_audience_evidence_requirements.py b/src/adcp/types/generated_poc/core/product_audience_evidence_requirements.py deleted file mode 100644 index d3421157a..000000000 --- a/src/adcp/types/generated_poc/core/product_audience_evidence_requirements.py +++ /dev/null @@ -1,115 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/product_audience_evidence_requirements.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, StrictBool, StrictFloat - -from ..enums import audience_evidence_methodology, audience_resolution_method, audience_subject_type -from . import brand_key -from . import ext as ext_1 -from .duration import Duration - - -class RequirementMode(StrEnum): - required = 'required' - preferred = 'preferred' - - -class EvidencePresence(StrEnum): - required = 'required' - when_available = 'when_available' - - -class AcceptedEvidenceType(StrEnum): - measured = 'measured' - forecast = 'forecast' - seller_declared = 'seller_declared' - - -class Unit(StrEnum): - seconds = 'seconds' - minutes = 'minutes' - hours = 'hours' - days = 'days' - - -class MaximumAge(Duration): - unit: Annotated[ - Unit | None, - Field( - description="Time unit. 'seconds' for sub-minute precision. 'campaign' spans the full campaign flight." - ), - ] = None - - -class AcceptedAttestationIssuers1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Literal['brand'] = 'brand' - brand: brand_key.BrandKey - ext: ext_1.ExtensionObject | None = None - - -class AcceptedAttestationIssuers2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Literal['agent'] = 'agent' - agent_url: AnyUrl - ext: ext_1.ExtensionObject | None = None - - -class AcceptedAttestationIssuers3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Literal['origin'] = 'origin' - origin: AnyUrl - ext: ext_1.ExtensionObject | None = None - - -AcceptedAttestationIssuers = Annotated[ - AcceptedAttestationIssuers1 | AcceptedAttestationIssuers2 | AcceptedAttestationIssuers3, - Field(discriminator='type'), -] - - -class ProductAudienceEvidenceRequirements(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - requirement_mode: RequirementMode - evidence_presence: EvidencePresence - accepted_methodologies: Annotated[ - list[audience_evidence_methodology.AudienceEvidenceMethodology] | None, Field(min_length=1) - ] = None - excluded_methodologies: Annotated[ - list[audience_evidence_methodology.AudienceEvidenceMethodology] | None, Field(min_length=1) - ] = None - accepted_evidence_types: Annotated[list[AcceptedEvidenceType] | None, Field(min_length=1)] = ( - None - ) - accepted_providers: Annotated[list[brand_key.BrandKey] | None, Field(min_length=1)] = None - excluded_providers: Annotated[list[brand_key.BrandKey] | None, Field(min_length=1)] = None - accepted_subject_types: Annotated[ - list[audience_subject_type.AudienceSubjectType] | None, Field(min_length=1) - ] = None - accepted_resolution_methods: Annotated[ - list[audience_resolution_method.AudienceResolutionMethod] | None, Field(min_length=1) - ] = None - minimum_confidence: Annotated[StrictFloat | None, Field(ge=0.0, le=1.0)] = None - maximum_age: MaximumAge | None = None - methodology_documentation_required: StrictBool | None = False - independent_attestation_required: StrictBool | None = False - accepted_attestation_issuers: Annotated[ - list[AcceptedAttestationIssuers] | None, Field(min_length=1) - ] = None - accepted_attestation_claim_types: Annotated[list[AnyUrl] | None, Field(min_length=1)] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/product_card_reference_asset.py b/src/adcp/types/generated_poc/core/product_card_reference_asset.py deleted file mode 100644 index b3586e47e..000000000 --- a/src/adcp/types/generated_poc/core/product_card_reference_asset.py +++ /dev/null @@ -1,52 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/product_card_reference_asset.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from .assets import image_asset, markdown_asset, url_asset, video_asset - - -class Role(StrEnum): - coverage_map = 'coverage_map' - sample_render = 'sample_render' - environment_photo = 'environment_photo' - media_kit = 'media_kit' - logo = 'logo' - other = 'other' - - -class ProductCardReferenceAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - role: Annotated[ - Role, - Field( - description='Semantic role of this asset. coverage_map: geographic or audience reach visualization; sample_render: mockup of ad placement in context; environment_photo: photo of the physical or digital environment; media_kit: downloadable media kit or spec sheet; logo: seller or property logo; other: seller-defined role (provide role_label).' - ), - ] - role_label: Annotated[ - str | None, - Field( - description="Human-readable label for the asset role. Required when role is 'other'; optional otherwise." - ), - ] = None - asset: Annotated[ - image_asset.ImageAsset - | video_asset.VideoAsset - | markdown_asset.MarkdownAsset - | url_asset.UrlAsset, - Field( - description='The asset payload, discriminated on asset_type (image, video, markdown, url).' - ), - ] - description: Annotated[ - str | None, Field(description='Optional human-readable context about this asset.') - ] = None diff --git a/src/adcp/types/generated_poc/core/product_change_map.py b/src/adcp/types/generated_poc/core/product_change_map.py deleted file mode 100644 index 1b6619e8b..000000000 --- a/src/adcp/types/generated_poc/core/product_change_map.py +++ /dev/null @@ -1,26 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/product_change_map.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from pydantic import Field, RootModel, StringConstraints - - -class ProductChangeMap1(StrEnum): - include = 'include' - omit = 'omit' - - -class ProductChangeMap(RootModel[dict[Annotated[str, StringConstraints(min_length=1)], ProductChangeMap1]]): - root: Annotated[ - dict[Annotated[str, StringConstraints(min_length=1)], ProductChangeMap1], - Field( - description='Product IDs mapped to deterministic membership actions. Object keys are product identifiers, so contradictory actions for one product cannot be represented.', - min_length=1, - title='Product Change Map', - ), - ] diff --git a/src/adcp/types/generated_poc/core/product_execution_requirement.py b/src/adcp/types/generated_poc/core/product_execution_requirement.py deleted file mode 100644 index 71063d91a..000000000 --- a/src/adcp/types/generated_poc/core/product_execution_requirement.py +++ /dev/null @@ -1,104 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/product_execution_requirement.json -# timestamp: 2026-10-04T00:53:27+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field - -from ..enums import catalog_type, event_type -from . import ext as ext_1 -from .downstream_connection_requirement import DownstreamConnectionRequirement - - -class ProductExecutionRequirement1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['event_source'] = 'event_source' - event_types: Annotated[ - list[event_type.EventType] | None, - Field( - description='Event types that satisfy the requirement (any-of): the satisfying `event_sources[]` entry MUST use one of these `event_type` values. Omit when any event type satisfies it.', - min_length=1, - ), - ] = None - ext: ext_1.ExtensionObject | None = None - - -class ProductExecutionRequirement2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['catalog'] = 'catalog' - catalog_types: Annotated[ - list[catalog_type.CatalogType], - Field( - description='Catalog types that satisfy the requirement (any-of). One catalog of any listed type satisfies it.', - min_length=1, - ), - ] - ext: ext_1.ExtensionObject | None = None - - -class Status(StrEnum): - connected = 'connected' - missing = 'missing' - pending = 'pending' - expired = 'expired' - revoked = 'revoked' - not_required = 'not_required' - unknown = 'unknown' - - -class RequiredForItem(ScalarStr): - __slots__ = () - _constraints = {'min_length': 1} - _json_schema_extra = { - 'examples': ['list_creatives', 'sync_creatives', 'create_media_buy', 'get_media_buy_delivery', 'get_creative_delivery'], - } - - -class Connection(DownstreamConnectionRequirement): - status: Annotated[ - Literal['unknown'], - Field( - description='Current seller-observed state for this downstream connection when known. Product declarations MAY omit status or use `unknown`; AUTHORIZATION_REQUIRED details SHOULD use `missing`, `expired`, or `revoked` for the connection that blocked the call.' - ), - ] = 'unknown' - required_for: Annotated[ - list[RequiredForItem] | None, - Field( - description='Concrete AdCP protocol operation names that require this downstream connection. Sellers SHOULD include this in product declarations when the requirement is known ahead of time, and in AUTHORIZATION_REQUIRED details when it explains the failed operation. Prefer specific operation names such as `list_creatives`, `sync_creatives`, `create_media_buy`, `get_media_buy_delivery`, or `get_creative_delivery` over broad category labels such as `reporting`.' - ), - ] = None - - -class ProductExecutionRequirement3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['downstream_connection'] = 'downstream_connection' - connection: Annotated[ - Connection, - Field( - description='The required connection. Account-independent: `status` is omitted or `unknown`; `resource_ref`, `connection_id`, and `expires_at` MUST be omitted; `required_for`, when present, includes `create_media_buy`.' - ), - ] - ext: ext_1.ExtensionObject | None = None - - -ProductExecutionRequirement = Annotated[ - ProductExecutionRequirement1 | ProductExecutionRequirement2 | ProductExecutionRequirement3, - Field( - description='Experimental. One account resource a package on the product needs before it can be created. `kind` determines the binding: `event_source` via `packages[].optimization_goals[].event_sources[]`, `catalog` via `packages[].catalogs[]`, `downstream_connection` by completing the connection. Account-independent: MUST NOT carry account-specific resource IDs or names. Alternatives inside one requirement are any-of.', - discriminator='kind', - examples=[{'kind': 'catalog', 'catalog_types': ['app']}], - title='Product Execution Requirement', - ), -] diff --git a/src/adcp/types/generated_poc/core/product_filters.py b/src/adcp/types/generated_poc/core/product_filters.py deleted file mode 100644 index 38b8ef653..000000000 --- a/src/adcp/types/generated_poc/core/product_filters.py +++ /dev/null @@ -1,701 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/product_filters.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from datetime import date -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarStr -from pydantic import AnyUrl, ConfigDict, Field, StrictBool, StrictFloat - -from ..enums import audio_distribution_type, available_metric -from ..enums import channels as channels_1 -from ..enums import cloud_storage_protocol -from ..enums import delivery_type as delivery_type_1 -from ..enums import distance_unit -from ..enums import exclusivity as exclusivity_1 -from ..enums import geo_level -from ..enums import match_type as match_type_1 -from ..enums import ( - metro_system, - pricing_structure, - response_type, - social_placement_surface, - sponsored_placement_type, -) -from ..enums import transport_mode as transport_mode_1 -from ..enums import travel_time_unit, video_placement_type -from . import audience_evidence_requirements as audience_evidence_requirements_1 -from . import brand_ref, canonical_format_kind -from . import ext as ext_1 -from . import format_id, format_option_ref, media_buy_features, performance_standard, postal_area -from . import signal_id as signal_id_1 -from . import signal_ref as signal_ref_1 -from . import vendor_metric_id - - -class PricingCurrency(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{3}$'} - _json_schema_extra = {'description': "ISO 4217 currency code (e.g., 'USD', 'EUR', 'GBP')"} - - -class BudgetRange(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - min: Annotated[StrictFloat | None, Field(description='Minimum budget amount', ge=0.0)] = None - max: Annotated[StrictFloat | None, Field(description='Maximum budget amount', ge=0.0)] = None - currency: Annotated[ - str, - Field( - description="ISO 4217 currency code (e.g., 'USD', 'EUR', 'GBP')", pattern='^[A-Z]{3}$' - ), - ] - - -class Country(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{2}$'} - - -class Region(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{2}-[A-Z0-9]+$'} - - -class Metro(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - system: Annotated[ - metro_system.MetroAreaSystem, Field(description='Metro classification system') - ] - code: Annotated[ - str, Field(description="Metro code within the system (e.g., '501' for NYC DMA)") - ] - - -class Provider(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[AnyUrl, Field(description="Provider's agent URL from the registry.")] - context_match: Annotated[ - StrictBool | None, - Field(description='When true, require this provider to support context match.'), - ] = None - identity_match: Annotated[ - StrictBool | None, - Field(description='When true, require this provider to support identity match.'), - ] = None - - -class AudienceActivationMethods(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - pattern: Literal['sync_audiences'] = 'sync_audiences' - - -class BuyerAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: AnyUrl - - -class AudienceActivationMethods1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - pattern: Literal['tmp_identity_match'] = 'tmp_identity_match' - buyer_agent: Annotated[ - BuyerAgent | None, - Field( - description='Require this buyer agent on tmp_identity_match entries. Buyers typically filter on their own agent_url.' - ), - ] = None - - -class Direction(StrEnum): - buyer_to_seller = 'buyer_to_seller' - seller_to_buyer = 'seller_to_buyer' - - -class RequiredGeoTargetingItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - level: Annotated[ - geo_level.GeographicTargetingLevel, - Field(description='Geographic targeting level (country, region, metro, postal_area)'), - ] - country: Annotated[ - str | None, - Field( - description='ISO 3166-1 alpha-2 country code. Required for native postal_area system filters; not applicable to country, region, or metro filters.', - pattern='^[A-Z]{2}$', - ), - ] = None - system: Annotated[ - str | None, - Field( - description="Optional classification system within the level. Use for a specific metro system (e.g., 'nielsen_dma'), native postal_area system (e.g., 'zip' with country 'US'), or deprecated legacy postal alias (e.g., 'us_zip'). Not applicable for country/region which use ISO standards." - ), - ] = None - - -class TargetingMode(StrEnum): - include = 'include' - exclude = 'exclude' - - -class SignalTargetingItem4(AdCPBaseModel): - targeting_mode: Annotated[ - TargetingMode | None, - Field( - description="Desired use of this signal on the product. 'include' requires the product option to allow use in an any group. 'exclude' requires the product option to allow use in a none group." - ), - ] = TargetingMode.include - - -class Radius(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - value: Annotated[StrictFloat, Field(description='Radius distance', gt=0.0)] - unit: Annotated[distance_unit.DistanceUnit, Field(description='Distance unit')] - - -class Type(StrEnum): - Polygon = 'Polygon' - MultiPolygon = 'MultiPolygon' - - -class Geometry(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Annotated[Type, Field(description='GeoJSON geometry type')] - coordinates: Annotated[list[Any], Field(description='GeoJSON coordinates array')] - - -class Keyword(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - keyword: Annotated[str, Field(description='The keyword to target', min_length=1)] - match_type: match_type_1.MatchType | None = match_type_1.MatchType.broad - - -class TrustedMatch(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - providers: Annotated[ - list[Provider] | None, - Field( - description='Filter to products with specific TMP providers and match types. Each entry identifies a provider by agent_url and optionally requires specific match capabilities. Products must match at least one entry.', - min_length=1, - ), - ] = None - response_types: Annotated[ - list[response_type.TmpResponseType] | None, - Field( - description="Filter to products supporting specific TMP response types (e.g., 'activation', 'creative', 'catalog_items'). Products must support at least one of the listed types.", - min_length=1, - ), - ] = None - - -class SignalTargetingItem1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - signal_ref_1.SignalRef | None, - Field(description='The signal to target. New targeting constraints SHOULD use signal_ref.'), - ] = None - signal_id: Annotated[ - signal_id_1.SignalId | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - ), - ] = None - value_type: Annotated[Literal['binary'], Field(description='Discriminator for binary signals')] = 'binary' - value: Annotated[ - StrictBool, - Field( - description='Whether to include (true) or exclude (false) users matching this signal' - ), - ] - - -class SignalTargetingItem2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - signal_ref_1.SignalRef | None, - Field(description='The signal to target. New targeting constraints SHOULD use signal_ref.'), - ] = None - signal_id: Annotated[ - signal_id_1.SignalId | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - ), - ] = None - value_type: Annotated[ - Literal['categorical'], Field(description='Discriminator for categorical signals') - ] = 'categorical' - values: Annotated[ - list[str], - Field( - description='Values to target. Users with any of these values will be included.', - min_length=1, - ), - ] - - -class SignalTargetingItem3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - signal_ref_1.SignalRef | None, - Field(description='The signal to target. New targeting constraints SHOULD use signal_ref.'), - ] = None - signal_id: Annotated[ - signal_id_1.SignalId | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - ), - ] = None - value_type: Annotated[ - Literal['numeric'], Field(description='Discriminator for numeric signals') - ] = 'numeric' - min_value: Annotated[ - StrictFloat | None, - Field( - description="Minimum value (inclusive). Omit for no minimum. Must be <= max_value when both are provided. Should be >= signal's range.min if defined." - ), - ] = None - max_value: Annotated[ - StrictFloat | None, - Field( - description="Maximum value (inclusive). Omit for no maximum. Must be >= min_value when both are provided. Should be <= signal's range.max if defined." - ), - ] = None - - -class SignalTargetingItem5(SignalTargetingItem1, SignalTargetingItem4): - targeting_mode: Annotated[ - TargetingMode | None, - Field( - description="Desired use of this signal on the product. 'include' requires the product option to allow use in an any group. 'exclude' requires the product option to allow use in a none group." - ), - ] = TargetingMode.include - - -class SignalTargetingItem6(SignalTargetingItem2, SignalTargetingItem4): - targeting_mode: Annotated[ - TargetingMode | None, - Field( - description="Desired use of this signal on the product. 'include' requires the product option to allow use in an any group. 'exclude' requires the product option to allow use in a none group." - ), - ] = TargetingMode.include - - -class SignalTargetingItem7(SignalTargetingItem3, SignalTargetingItem4): - targeting_mode: Annotated[ - TargetingMode | None, - Field( - description="Desired use of this signal on the product. 'include' requires the product option to allow use in an any group. 'exclude' requires the product option to allow use in a none group." - ), - ] = TargetingMode.include - - -SignalTargetingItem = SignalTargetingItem5 | SignalTargetingItem6 | SignalTargetingItem7 - - -class TravelTime(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - value: Annotated[StrictFloat, Field(description='Travel time limit', ge=1.0)] - unit: travel_time_unit.TravelTimeUnit - - -class GeoProximityItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - lat: Annotated[ - StrictFloat | None, - Field(description='Latitude in decimal degrees (WGS 84)', ge=-90.0, le=90.0), - ] = None - lng: Annotated[ - StrictFloat | None, - Field(description='Longitude in decimal degrees (WGS 84)', ge=-180.0, le=180.0), - ] = None - label: Annotated[ - str | None, - Field(description="Human-readable label (e.g., 'Düsseldorf', 'Heathrow Airport')"), - ] = None - travel_time: Annotated[ - TravelTime | None, Field(description='Travel time limit for isochrone calculation') - ] = None - transport_mode: Annotated[ - transport_mode_1.TransportMode | None, - Field( - description='Transportation mode for isochrone calculation. Required when travel_time is provided.' - ), - ] = None - radius: Annotated[Radius | None, Field(description='Simple radius from the point')] = None - geometry: Annotated[ - Geometry | None, - Field(description='Pre-computed GeoJSON geometry defining the proximity boundary'), - ] = None - - -class AudienceActivationMethods2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - pattern: Literal['file_transfer'] = 'file_transfer' - transport: Annotated[ - cloud_storage_protocol.CloudStorageProtocol | None, - Field(description='Require this storage protocol (file_transfer only).'), - ] = None - directions: Annotated[ - list[Direction] | None, - Field( - description="Require a non-empty intersection with the method's declared directions (file_transfer only).", - min_length=1, - ), - ] = None - vendor: Annotated[ - brand_ref.BrandReference | None, - Field(description="Require the method's vendor to match this reference."), - ] = None - - -class AudienceActivationMethods3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - pattern: Literal['dataset_query'] = 'dataset_query' - vendor: Annotated[ - brand_ref.BrandReference | None, - Field(description="Require the method's vendor to match this reference."), - ] = None - - -class AudienceActivationMethods4(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - pattern: Literal['clean_room'] = 'clean_room' - vendor: Annotated[ - brand_ref.BrandReference | None, - Field(description="Require the method's vendor to match this reference."), - ] = None - - -class AudienceActivationMethods5(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - pattern: Literal['platform_distribution'] = 'platform_distribution' - vendor: Annotated[ - brand_ref.BrandReference | None, - Field(description="Require the method's vendor to match this reference."), - ] = None - - -class RequiredVendorMetric(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - brand_ref.BrandReference | None, - Field( - description='Pin to a specific vendor. Optional — omit to match any vendor offering this `metric_id`.' - ), - ] = None - metric_id: Annotated[ - vendor_metric_id.VendorMetricId | None, - Field( - description="Pin to an exact metric identifier within a vendor's vocabulary. Optional — omit to match any metric from the pinned vendor. Cross-vendor identifier matching only works after vendors converge on identifiers (typically post-standardization)." - ), - ] = None - - -class ProductFilters(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - delivery_type: delivery_type_1.DeliveryType | None = None - exclusivity: Annotated[ - exclusivity_1.Exclusivity | None, - Field( - description="Filter by exclusivity level. Returns products matching the specified exclusivity (e.g., 'exclusive' returns only sole-sponsorship products)." - ), - ] = None - is_fixed_price: Annotated[ - StrictBool | None, - Field( - description='Legacy filter for fixed versus auction pricing availability. true returns options with fixed_price; false returns auction options whose price is established through bid_price. Contingent options such as revenue_share match neither value and MUST be omitted whenever this filter is present. Use pricing_structures to discover contingent pricing. Products with both fixed and auction options match both true and false, but sellers MUST return only entries matching the requested structure.' - ), - ] = None - pricing_structures: Annotated[ - list[pricing_structure.PricingStructure] | None, - Field( - description='Filter by how the payable price is determined. fixed selects options with fixed_price, auction selects options established through bid_price, and contingent selects options calculated from a measured business outcome after delivery (currently revenue_share). Products match when at least one pricing option has a requested structure. Sellers MUST return only matching pricing_options entries. When combined with is_fixed_price, both filters apply and the returned entries must satisfy both.', - min_length=1, - ), - ] = None - pricing_currencies: Annotated[ - list[PricingCurrency] | None, - Field( - description='Filter by currencies the buyer can use for the media product transaction, using ISO 4217 currency codes. Products match when they offer at least one product-level pricing_options entry in one of the requested currencies and any seller-applied or otherwise mandatory product-scoped signal charges are satisfiable in one of those currencies or have no incremental price. Mandatory custom signal pricing without currency is not satisfiable for this filter unless the seller can truthfully treat it as having no incremental price. Sellers MUST return only product pricing_options entries whose currency is in this list so buyers can select deterministically from discovery. This filter does not require pruning optional signal or vendor add-on pricing; buyers should avoid optional add-ons priced only in unsupported currencies.', - min_length=1, - ), - ] = None - format_ids: Annotated[ - list[format_id.FormatReferenceStructuredObject] | None, - Field( - deprecated=True, - description='Deprecated in AdCP 3.2; removed in AdCP 4.0. Filter by legacy named-format references. Use `format_kinds` or `format_option_refs`.', - min_length=1, - ), - ] = None - format_kinds: Annotated[ - list[canonical_format_kind.CanonicalFormatKind] | None, - Field( - description='Filter to products accepting any of these canonical format kinds.', - min_length=1, - ), - ] = None - format_option_refs: Annotated[ - list[format_option_ref.FormatOptionReference] | None, - Field( - description='Filter to products accepting any of these exact publisher- or product-scoped canonical format options.', - min_length=1, - ), - ] = None - standard_formats_only: Annotated[ - StrictBool | None, Field(description='Only return products accepting IAB standard formats') - ] = None - min_exposures: Annotated[ - SchemaInt | None, - Field(description='Minimum exposures/impressions needed for measurement validity', ge=1), - ] = None - start_date: Annotated[ - date | None, - Field( - description='Campaign start date (ISO 8601 date format: YYYY-MM-DD) for availability checks' - ), - ] = None - end_date: Annotated[ - date | None, - Field( - description='Campaign end date (ISO 8601 date format: YYYY-MM-DD) for availability checks' - ), - ] = None - budget_range: Annotated[ - BudgetRange | None, Field(description='Budget range to filter appropriate products') - ] = None - countries: Annotated[ - list[Country] | None, - Field( - deprecated=True, - description='DEPRECATED legacy coverage filter. On compact discovery tasks use criteria.offer_filters.countries. Return products whose inventory covers at least one requested area; this does not impose delivery targeting or require selectable targeting support. Retained get_products handlers MUST preserve the original coverage predicate.', - min_length=1, - ), - ] = None - regions: Annotated[ - list[Region] | None, - Field( - deprecated=True, - description='DEPRECATED legacy coverage filter. On compact discovery tasks use criteria.offer_filters.regions. Return products whose inventory covers at least one requested area; this does not impose delivery targeting or require selectable targeting support. Retained get_products handlers MUST preserve the original coverage predicate.', - min_length=1, - ), - ] = None - metros: Annotated[ - list[Metro] | None, - Field( - deprecated=True, - description='DEPRECATED legacy coverage filter. On compact discovery tasks use criteria.offer_filters.metros. Return products whose inventory covers at least one requested area; this does not impose delivery targeting or require selectable targeting support. Retained get_products handlers MUST preserve the original coverage predicate.', - min_length=1, - ), - ] = None - channels: Annotated[ - list[channels_1.MediaChannel] | None, - Field( - description="Filter by advertising channels (e.g., ['display', 'ctv', 'dooh'])", - min_length=1, - ), - ] = None - video_placement_types: Annotated[ - list[video_placement_type.VideoPlacementType] | None, - Field( - description='Filter product metadata by declared video placement types, using IAB Tech Lab/OpenRTB 2.6 video.plcmt definitions with AdCP-native names. A product matches when its declared array intersects the requested array. This is discovery classification only and does not promise delivery exclusively on a requested type; buyers needing exact placement inventory use targeting_overlay.placement_selection against targetable placements. This filter has set semantics for wholesale feed canonicalization.', - min_length=1, - ), - ] = None - audio_distribution_types: Annotated[ - list[audio_distribution_type.AudioDistributionType] | None, - Field( - description='Filter product metadata by declared audio distribution types, using IAB Tech Lab/OpenRTB 2.6 audio.feed definitions with AdCP-native names. A product matches when its declared array intersects the requested array. This is discovery classification only and does not promise delivery exclusively on a requested type. This filter has set semantics for wholesale feed canonicalization.', - min_length=1, - ), - ] = None - sponsored_placement_types: Annotated[ - list[sponsored_placement_type.SponsoredPlacementType] | None, - Field( - description='Filter retail-media product metadata by declared sponsored-placement types (sponsored search, sponsored display, or sponsored native). A product matches when its declared array intersects the requested array. This is discovery classification only and does not promise delivery exclusively on a requested type. This filter has set semantics for wholesale feed canonicalization.', - min_length=1, - ), - ] = None - social_placement_surfaces: Annotated[ - list[social_placement_surface.SocialPlacementSurface] | None, - Field( - description='Filter social-product metadata by declared placement surfaces (feed, stories, short_video, explore, or search). A product matches when its declared array intersects the requested array. This is discovery classification only and does not promise delivery exclusively on a requested surface; buyers needing an exact public placement use targeting_overlay.placement_selection. This filter has set semantics for wholesale feed canonicalization.', - min_length=1, - ), - ] = None - required_axe_integrations: Annotated[ - list[AnyUrl] | None, - Field( - deprecated=True, - description='Deprecated: Use trusted_match filter instead. Filter to products executable through specific agentic ad exchanges. URLs are canonical identifiers.', - min_length=1, - ), - ] = None - trusted_match: Annotated[ - TrustedMatch | None, - Field( - description='Filter products by Trusted Match Protocol capabilities. Only products with matching TMP support are returned.' - ), - ] = None - audience_activation_methods: Annotated[ - list[ - AudienceActivationMethods - | AudienceActivationMethods1 - | AudienceActivationMethods2 - | AudienceActivationMethods3 - | AudienceActivationMethods4 - | AudienceActivationMethods5 - ] - | None, - Field( - description="Filter to products whose audience_activation.methods matches at least one requested entry (OR across entries). Within an entry every specified field must match (AND); omitted optional fields are wildcards; directions matches on non-empty intersection, and a method that omits directions matches any requested directions; vendor matches on domain, plus brand_id when specified. Fields inapplicable to the requested pattern (e.g. transport on a clean_room entry) make that entry unsatisfiable. Sellers MUST exclude products with no audience_activation declaration when this filter is present; exclusions MAY be reported via filter_diagnostics.excluded_by. Experimental: part of the media_buy.audience_activation surface — buyers SHOULD check the seller's experimental_features before filtering on it; sellers that do not list the feature ignore this filter.", - min_length=1, - ), - ] = None - required_features: Annotated[ - media_buy_features.MediaBuyFeatures | None, - Field( - description='Filter to products from sellers supporting specific protocol features. Only features set to true are used for filtering.' - ), - ] = None - required_geo_targeting: Annotated[ - list[RequiredGeoTargetingItem] | None, - Field( - deprecated=True, - description='DEPRECATED. Use get_products.required_overlay_support, which is product-scoped and applies consistently to geographic and non-geographic targeting dimensions.', - min_length=1, - ), - ] = None - signal_targeting: Annotated[ - list[SignalTargetingItem] | None, - Field( - deprecated=True, - description='DEPRECATED legacy signal-option eligibility filter. Retained get_products handlers MUST preserve the signal identity, value predicate, and requested include/exclude capability. This does not activate delivery targeting. Native buyers use criteria.targeting_overlay.signal_targeting_groups only for concrete delivery selections, preserving exclusion through group operators; copying targeting_mode into targeting_overlay.signal_targeting does not preserve it.', - min_length=1, - ), - ] = None - postal_areas: Annotated[ - list[postal_area.PostalArea] | None, - Field( - deprecated=True, - description='DEPRECATED legacy coverage filter. On compact discovery tasks use criteria.offer_filters.postal_areas. Return products whose inventory covers at least one requested area; this does not impose delivery targeting or require selectable targeting support. Retained get_products handlers MUST preserve the original coverage predicate.', - min_length=1, - ), - ] = None - geo_proximity: Annotated[ - list[GeoProximityItem] | None, - Field( - deprecated=True, - description='DEPRECATED legacy coverage filter. On compact discovery tasks use criteria.offer_filters.geo_proximity. Return products whose inventory covers at least one requested area; this does not impose delivery targeting or require selectable targeting support. Retained get_products handlers MUST preserve the original coverage predicate.', - min_length=1, - ), - ] = None - required_performance_standards: Annotated[ - list[performance_standard.PerformanceStandard] | None, - Field( - description="Filter to products that can meet the buyer's performance standard requirements. Each entry specifies a metric, minimum threshold, and optionally a required vendor and standard. Products that cannot meet these thresholds or do not support the specified vendors are excluded. Use this to tell the seller upfront: 'I need DoubleVerify for viewability at 70% MRC.'", - min_length=1, - ), - ] = None - required_metrics: Annotated[ - list[available_metric.AvailableMetric] | None, - Field( - description="Filter to products whose `reporting_capabilities.available_metrics` is a superset of these metrics — i.e., products that commit to reporting all listed metrics in delivery responses. Use this for capability-level discovery (e.g., 'I need products that report `completed_views` for a CTV CPCV buy'); guarantee-level requirements with thresholds belong in `required_performance_standards` and `measurement_terms`. Sellers MUST silently exclude products that cannot meet this list (filter-not-fail; do not return an error). Under the container-subsumption rule in `enums/available-metric.json`, `viewability` satisfies numeric leaves such as `viewable_rate`; structured distributions require explicit `viewed_seconds_percentiles` or `viewed_seconds_histogram` declarations. The product's declared `available_metrics` becomes the binding reporting contract carried into the resulting media buy — the same metric vocabulary is used to compute `missing_metrics` on `get_media_buy_delivery`.", - examples=[ - ['completed_views'], - ['completed_views', 'completion_rate'], - ['impressions', 'spend', 'engagements'], - ], - min_length=1, - ), - ] = None - required_vendor_metrics: Annotated[ - list[RequiredVendorMetric] | None, - Field( - description="Filter to products whose `reporting_capabilities.vendor_metrics` matches these criteria. Each entry pins a `vendor` (matches any metric from that vendor), a `metric_id` (matches the metric across any vendor that uses that identifier), or both (specific vendor's specific metric). A product matches if its declared `vendor_metrics` covers ALL listed entries (AND across entries; pins within an entry are conjunctive). Cross-vendor discovery (e.g., 'I need attention measurement from any vendor that does it') is the buyer agent's responsibility — the agent resolves which vendors offer a category via the vendors' `brand.json` records, then enumerates them as filter entries. AdCP does not carry vendor-side metric metadata (category, methodology, standard alignment) in the filter surface; that lives at the vendor and is queried out-of-band. Sellers MUST silently exclude non-matching products (filter-not-fail; do not return an error) — same convention as the other `required_*` filters.", - examples=[ - [{'vendor': {'domain': 'attentionvendor.example'}}], - [ - { - 'vendor': {'domain': 'panelmeasurement.example'}, - 'metric_id': 'demographic_reach', - } - ], - [ - {'vendor': {'domain': 'attentionvendor.example'}}, - {'vendor': {'domain': 'secondattentionvendor.example'}}, - ], - ], - min_length=1, - ), - ] = None - keywords: Annotated[ - list[Keyword] | None, - Field( - deprecated=True, - description='DEPRECATED legacy product eligibility filter. Retained get_products handlers MUST preserve the requested keyword eligibility and match_type (default broad). This is not an instruction to add package keyword targeting. Native buyers use criteria.targeting_overlay.keyword_targets only when they intend a delivery constraint, or criteria.required_overlay_support.keyword_targets for future selectability.', - min_length=1, - ), - ] = None - audience_evidence_requirements: Annotated[ - audience_evidence_requirements_1.AudienceEvidenceRequirements | None, - Field( - description='Buyer policy for evaluating Product.audience_evidence. In required mode, sellers MUST apply the evidence_presence and admissibility semantics and exclude non-matching products; they MUST NOT ignore an unsupported hard requirement. In preferred mode, sellers use matches for ranking and explain the evidence selected. Buyers SHOULD inspect media_buy.audience_evidence capabilities before sending this object.' - ), - ] = None - ext: Annotated[ - ext_1.ExtensionObject | None, - Field( - description='Vendor-namespaced extension parameters for seller-specific filter criteria not covered by standard fields. Keys MUST be namespaced under a vendor or platform key (e.g., ext.gam, ext.platform_x). Sellers MUST treat all values as untrusted buyer input; do not interpolate into LLM prompts, SQL queries, or system commands without sanitization. Persistent use of an extension key across multiple buyers is a signal to propose standardization.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/product_format_declaration.py b/src/adcp/types/generated_poc/core/product_format_declaration.py deleted file mode 100644 index 7f0f65645..000000000 --- a/src/adcp/types/generated_poc/core/product_format_declaration.py +++ /dev/null @@ -1,1750 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/product_format_declaration.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, Field, StrictBool - -from ..enums import channels -from ..formats.canonical import ( - agent_placement, - audio_daast, - audio_hosted, - audio_vast, - coordinated_placements, - display_tag, - html5, - image, - image_carousel, - native_in_feed, - responsive_creative, - seller_rendered_stateful_display, - sponsored_placement, - video_hosted, - video_vast, -) -from . import creative_locale_policy, format_id, macro_resolution_capability, platform_extension_ref -from . import tracker_execution_contract as tracker_execution_contract_1 - - -class SellerPreference(StrEnum): - preferred = 'preferred' - accepted = 'accepted' - discouraged = 'discouraged' - - -class ProductFormatDeclaration1(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['image'] = 'image' - params: image.CanonicalFormatImage - - -class ProductFormatDeclaration2(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['html5'] = 'html5' - params: html5.CanonicalFormatHtml5Banner - - -class ProductFormatDeclaration3(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['display_tag'] = 'display_tag' - params: display_tag.CanonicalFormatDisplayTag - - -class ProductFormatDeclaration4(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['image_carousel'] = 'image_carousel' - params: image_carousel.CanonicalFormatImageCarousel - - -class ProductFormatDeclaration5(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['video_hosted'] = 'video_hosted' - params: video_hosted.CanonicalFormatHostedVideo - - -class ProductFormatDeclaration6(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['video_vast'] = 'video_vast' - params: video_vast.CanonicalFormatVastVideo - - -class ProductFormatDeclaration7(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['audio_hosted'] = 'audio_hosted' - params: audio_hosted.CanonicalFormatHostedAudio - - -class ProductFormatDeclaration8(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['audio_vast'] = 'audio_vast' - params: audio_vast.CanonicalFormatVastAudio - - -class ProductFormatDeclaration9(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['audio_daast'] = 'audio_daast' - params: audio_daast.CanonicalFormatDaastAudio - - -class ProductFormatDeclaration10(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['sponsored_placement'] = 'sponsored_placement' - params: sponsored_placement.CanonicalFormatSponsoredPlacementRetailMediaCatalogDriven - - -class ProductFormatDeclaration11(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['native_in_feed'] = 'native_in_feed' - params: native_in_feed.CanonicalFormatNativeInFeed - - -class ProductFormatDeclaration12(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['responsive_creative'] = 'responsive_creative' - params: responsive_creative.CanonicalFormatResponsiveCreative - - -class ProductFormatDeclaration13(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['agent_placement'] = 'agent_placement' - params: agent_placement.CanonicalFormatAgentPlacementAiSurfaceSponsoredPlacement - - -class ProductFormatDeclaration14(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['seller_rendered_stateful_display'] = 'seller_rendered_stateful_display' - params: seller_rendered_stateful_display.CanonicalFormatSellerRenderedStatefulDisplay - - -class ProductFormatDeclaration15(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['coordinated_placements'] = 'coordinated_placements' - params: coordinated_placements.CanonicalFormatCoordinatedPlacements - - -class ProductFormatDeclaration16(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['custom'] = 'custom' - params: Annotated[ - dict[str, Any], - Field( - description="Custom shape's params. Validated against the schema fetched from `format_schema.uri` at the cached `format_schema.digest`." - ), - ] - - -ProductFormatDeclaration = Annotated[ - ProductFormatDeclaration1 - | ProductFormatDeclaration2 - | ProductFormatDeclaration3 - | ProductFormatDeclaration4 - | ProductFormatDeclaration5 - | ProductFormatDeclaration6 - | ProductFormatDeclaration7 - | ProductFormatDeclaration8 - | ProductFormatDeclaration9 - | ProductFormatDeclaration10 - | ProductFormatDeclaration11 - | ProductFormatDeclaration12 - | ProductFormatDeclaration13 - | ProductFormatDeclaration14 - | ProductFormatDeclaration15 - | ProductFormatDeclaration16, - Field( - description='Inline format declaration on a product. The `format_kind` discriminator names which canonical format the product narrows; `params` carries only the canonical creative-shape parameters (slots, dimensions, durations, codecs, character limits, platform_extensions, etc.). Processing metadata such as `macro_resolution_capabilities` belongs at this declaration\'s top level rather than inside `params`. Optional `format_option_id` (stable identifier for routing when a product\'s `format_options` contains multiple declarations sharing the same `format_kind`), optional `publisher_domain` (namespace for the format option when it comes from a publisher adagents.json catalog), `display_name` (seller-controlled human-readable label for dashboard and catalog UIs), and `applies_to_channels` (subset of the product\'s declared channels this declaration applies to — lets a multi-channel product carry distinct format_options per channel). Discriminated-union shape generates clean tagged unions in TypeScript and Pydantic codegen. Replaces v1\'s named-format pattern (where products referenced a separately-defined format file via compound `format_id`). Canonical product-bound declarations are the AdCP 3.2 authoring path; v1 named formats remain only as deprecated 3.x compatibility projections.\n\n**Closed-set semantics (normative).** `format_options[]` is the closed set of accepted formats for this product. Sellers MUST reject `create_media_buy` requests targeting any `format_kind` (or format option reference) not present in this list — typically with `UNSUPPORTED_FEATURE` or a seller-specific code; the rejection is structural, not negotiable. `seller_preference` modulates *within* the accepted set (a soft ranking hint between equally-acceptable options), it is NOT an enforcement axis. A product wanting to say \'this format is the only one that works\' lists exactly that one entry in `format_options[]`; everything else falls outside the set and is rejected by the closed-set rule.\n\n**Format matching vs satisfaction (normative).** Legacy named formats MUST be normalized to canonical declarations before comparison; do not exact-match raw `(agent_url, id)` pairs once a `format_id` has been projected through `canonical`, `v1_format_ref`, or the canonical mapping registry. Equivalence matching can treat a legacy fixed-size display ID and `format_kind: "image"` with matching `width`/`height` as the same underlying shape. Product satisfaction is stricter and directional: when this declaration specifies fixed constraints such as `width`, `height`, `duration_ms_exact`, or `duration_ms_range`, a buyer request or creative manifest MUST declare and satisfy those constraints. A broad request with no dimensions or duration does not satisfy a fixed-size or fixed-duration product; a broad product MAY accept a more specific creative unless another product constraint excludes it. Duration precedence is `duration_ms_exact` > `duration_ms_range`. Range constraints use containment: a range-based request satisfies this declaration only when every value it permits falls within this declaration\'s accepted range; overlap alone is insufficient. An exact value satisfies a range when the exact value falls inside the accepted interval. For hosted audio/video, a null range endpoint is unbounded: [null, 60000] means up to 60s, and [15000, null] means at least 15s; [null, null] is invalid because at least one endpoint must be bounded.\n\n**Custom format_kind** (`format_kind: "custom"`): for adopter-defined shapes that don\'t fit the 15 canonicals (branded content, cross-screen sponsorship, sponsorship lockup, newsletter sponsorship, AR lens, playable, live event sponsorship). When `format_kind` is `custom`, the declaration MUST carry `format_shape` (recognized global pattern from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json)) AND `format_schema` (URI+digest reference to a fetchable schema describing the actual `params` and `slots`). Buyer agents fetch the schema, validate manifests structurally, and reason about manifests without per-seller integration code. See [adcp#3666](https://github.com/adcontextprotocol/adcp/issues/3666) for the canonical promotion queue.', - discriminator='format_kind', - examples=[ - { - 'description': 'Meta Reels — narrows video_hosted (vertical orientation)', - 'data': { - 'format_kind': 'video_hosted', - 'params': { - 'orientation': 'vertical', - 'aspect_ratio': '9:16', - 'duration_ms_range': [3000, 90000], - 'min_width': 1080, - 'min_height': 1920, - 'max_file_size_mb': 200, - 'video_codecs': ['h264'], - 'audio_codecs': ['aac'], - 'headline_max_chars': 25, - 'primary_text_max_chars': 72, - 'captions': 'recommended', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'DOWNLOAD', 'SIGN_UP'], - 'composition_model': 'deterministic', - 'platform_extensions': [ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - }, - }, - }, - { - 'description': 'IAB Medium Rectangle (300x250) — narrows image', - 'data': { - 'format_kind': 'image', - 'params': { - 'width': 300, - 'height': 250, - 'max_file_size_kb': 200, - 'image_formats': ['jpg', 'png', 'gif'], - 'ssl_required': True, - 'composition_model': 'deterministic', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'GET_OFFER'], - }, - }, - }, - { - 'description': 'Québec French-only display image — same image shape with a seller-enforced creative locale constraint', - 'data': { - 'format_kind': 'image', - 'format_option_id': 'quebec_display_image', - 'display_name': 'Québec display image — French creative', - 'canonical_formats_only': True, - 'locale_policy': {'accepted_language_ranges': ['fr']}, - 'params': { - 'width': 300, - 'height': 250, - 'max_file_size_kb': 200, - 'image_formats': ['jpg', 'png'], - }, - }, - }, - { - 'description': "Podcast 30s host-read — narrows audio_hosted with a `script` slot the seller's host reads verbatim. No separate `inputs` map; the script lives in the manifest's `assets` like any other text asset.", - 'data': { - 'format_kind': 'audio_hosted', - 'params': { - 'duration_ms_exact': 30000, - 'audio_codecs': ['mp3', 'aac'], - 'audio_sample_rates': [44100, 48000], - 'audio_channels': ['stereo'], - 'loudness_lufs': -16, - 'asset_source': 'publisher_host_recorded', - 'buyer_asset_acceptance': 'rejected', - 'composition_model': 'deterministic', - 'slots': [ - { - 'asset_group_id': 'script', - 'required': True, - 'asset_type': 'text', - 'max_chars': 800, - }, - { - 'asset_group_id': 'offering_ref', - 'required': False, - 'asset_type': 'text', - }, - ], - 'production_window_business_days': 7, - }, - }, - }, - { - 'description': "StreamHaus Branded Feature — a still-custom branded-content format with format_schema pointing at the fictional seller's hosted schema. Buyer agents fetch the schema by uri@digest (cached, immutable) and validate the manifest structurally. `canonical_formats_only: true` is required for custom declarations because no v1 named format expresses this bespoke composition.", - 'data': { - 'format_kind': 'custom', - 'canonical_formats_only': True, - 'format_shape': 'branded_content', - 'format_schema': { - 'uri': 'https://ads.streamhaus.example/schemas/formats/branded_feature_v1', - 'digest': 'sha256:e1d4f6a9c2b5e8d1f4a7c0b3e6d9f2a5c8b1e4d7f0a3c6b9e2d5f8a1c4b7e0a3', - }, - 'format_option_id': 'streamhaus_branded_feature', - 'display_name': 'StreamHaus branded feature', - 'applies_to_channels': ['display'], - 'params': { - 'components': [ - {'placement_type': 'feature_header', 'required': True}, - {'placement_type': 'sponsored_story_card', 'required': True}, - {'placement_type': 'sponsorship_lockup', 'required': True}, - ], - 'exclusivity_window_hours': 12, - 'ssl_required': True, - }, - }, - }, - ], - title='Product Format Declaration', - ), -] diff --git a/src/adcp/types/generated_poc/core/product_identity.py b/src/adcp/types/generated_poc/core/product_identity.py deleted file mode 100644 index 1741ddb12..000000000 --- a/src/adcp/types/generated_poc/core/product_identity.py +++ /dev/null @@ -1,29 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/product_identity.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictBool - - -class ProductIdentity(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - persistent_identifier: Annotated[ - StrictBool, - Field( - description='Whether delivery on this product has a persistent per-entity identifier suitable for identifier-backed reach, frequency, and frequency-cap enforcement. false does not prohibit modeled individuals or households measurement.' - ), - ] - reach_methodology: Annotated[ - str | None, - Field( - description="Human-readable methodology for this product's custom reach unit. Required when the product reports reach or frequency with reach_unit custom, including frequency-only reporting.", - min_length=1, - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/product_offer_filters.py b/src/adcp/types/generated_poc/core/product_offer_filters.py deleted file mode 100644 index fafb8596e..000000000 --- a/src/adcp/types/generated_poc/core/product_offer_filters.py +++ /dev/null @@ -1,324 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/product_offer_filters.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from datetime import date -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarStr -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat - -from ..enums import audio_distribution_type, available_metric -from ..enums import channels as channels_1 -from ..enums import delivery_type as delivery_type_1 -from ..enums import distance_unit -from ..enums import exclusivity as exclusivity_1 -from ..enums import ( - metro_system, - performance_standard_metric, - pricing_structure, - response_type, - social_placement_surface, - sponsored_placement_type, -) -from ..enums import transport_mode as transport_mode_1 -from ..enums import travel_time_unit, video_placement_type, viewability_standard -from . import brand_key -from . import budget_range as budget_range_1 -from . import canonical_format_kind, canonical_media_buy_features -from . import ext as ext_1 -from . import ( - format_option_ref, - postal_area, - product_audience_evidence_requirements, - property_list_ref, - vendor_metric_id, -) - - -class PricingCurrency(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{3}$'} - - -class AvailabilityHorizon(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - start_time: Annotated[ - AwareDatetime, - Field(description='Inclusive horizon start (RFC 3339 date-time with timezone offset).'), - ] - end_time: Annotated[ - AwareDatetime, - Field( - description='Exclusive horizon end (RFC 3339 date-time with timezone offset). MUST be after start_time.' - ), - ] - - -class Country(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{2}$'} - - -class Region(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{2}-[A-Z0-9]{1,3}$'} - - -class Metro(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - system: Annotated[ - metro_system.MetroAreaSystem, Field(description='Metro classification system') - ] - code: Annotated[ - str, Field(description="Metro code within the system (e.g., '501' for NYC DMA)") - ] - - -class Radius(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - value: Annotated[StrictFloat, Field(description='Radius distance.', gt=0.0)] - unit: Annotated[distance_unit.DistanceUnit, Field(description='Distance unit.')] - - -class Type(StrEnum): - Polygon = 'Polygon' - MultiPolygon = 'MultiPolygon' - - -class Geometry(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Annotated[Type, Field(description='GeoJSON geometry type.')] - coordinates: Annotated[ - list[Any], - Field( - description='GeoJSON coordinates array. For Polygon: array of linear rings. For MultiPolygon: array of polygons.' - ), - ] - - -class Provider(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: AnyUrl - context_match: StrictBool | None = None - identity_match: StrictBool | None = None - - -class TravelTime(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - value: Annotated[StrictFloat, Field(description='Travel time limit.', ge=1.0)] - unit: travel_time_unit.TravelTimeUnit - - -class GeoProximityItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - lat: Annotated[ - StrictFloat | None, - Field( - description='Latitude in decimal degrees (WGS 84). Required for travel_time and radius methods.', - ge=-90.0, - le=90.0, - ), - ] = None - lng: Annotated[ - StrictFloat | None, - Field( - description='Longitude in decimal degrees (WGS 84). Required for travel_time and radius methods.', - ge=-180.0, - le=180.0, - ), - ] = None - label: Annotated[ - str | None, - Field( - description="Human-readable label for this entry (e.g., 'Düsseldorf', 'Heathrow Airport', 'Primary trade area')." - ), - ] = None - travel_time: Annotated[ - TravelTime | None, - Field( - description='Travel time limit for isochrone calculation. The platform resolves this to a geographic boundary based on actual transportation networks.' - ), - ] = None - transport_mode: Annotated[ - transport_mode_1.TransportMode | None, - Field( - description='Transportation mode for isochrone calculation. Required when travel_time is provided.' - ), - ] = None - radius: Annotated[ - Radius | None, - Field( - description='Simple radius from the point. The platform draws a circle of this distance around the coordinates.' - ), - ] = None - geometry: Annotated[ - Geometry | None, - Field( - description='Pre-computed GeoJSON geometry defining the proximity boundary. Use when the buyer has already calculated isochrones (via TravelTime, Mapbox, etc.) or has custom boundaries. When geometry is provided, lat/lng are not required.' - ), - ] = None - ext: ext_1.ExtensionObject | None = None - - -class TrustedMatch(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - providers: Annotated[list[Provider] | None, Field(min_length=1)] = None - response_types: Annotated[list[response_type.TmpResponseType] | None, Field(min_length=1)] = ( - None - ) - - -class RequiredPerformanceStandard(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - metric: performance_standard_metric.PerformanceStandardMetric - threshold: Annotated[StrictFloat, Field(ge=0.0, le=1.0)] - standard: viewability_standard.ViewabilityStandard | None = None - vendor: brand_key.BrandKey - - -class RequiredVendorMetric(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: brand_key.BrandKey | None = None - metric_id: vendor_metric_id.VendorMetricId | None = None - - -class ProductOfferFilters(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - delivery_type: delivery_type_1.DeliveryType | None = None - exclusivity: exclusivity_1.Exclusivity | None = None - is_fixed_price: Annotated[ - StrictBool | None, - Field( - description='Filter fixed-price versus auction offers. Contingent pricing matches neither value.' - ), - ] = None - pricing_structures: Annotated[ - list[pricing_structure.PricingStructure] | None, Field(min_length=1) - ] = None - pricing_currencies: Annotated[list[PricingCurrency] | None, Field(min_length=1)] = None - format_kinds: Annotated[ - list[canonical_format_kind.CanonicalFormatKind] | None, Field(min_length=1) - ] = None - format_option_refs: Annotated[ - list[format_option_ref.FormatOptionReference] | None, Field(min_length=1) - ] = None - standard_formats_only: StrictBool | None = None - min_exposures: Annotated[SchemaInt | None, Field(ge=1)] = None - start_date: Annotated[ - date | None, - Field( - description='Fixed-flight availability filter: with end_date, declares the exact flight the buyer intends to run. Returned products MUST be able to serve that flight, and pricing and forecasts are scoped to it. Mutually exclusive with availability_horizon.' - ), - ] = None - end_date: Annotated[ - date | None, - Field( - description='Fixed-flight availability filter end. See start_date. Mutually exclusive with availability_horizon.' - ), - ] = None - availability_horizon: Annotated[ - AvailabilityHorizon | None, - Field( - description="Flexible-window availability discovery: the buyer is open to any bookable window inside [start_time, end_time) and asks the seller to describe when the returned inventory can run, instead of filtering to one exact flight. Sellers that support this field partition the horizon into time-dimensioned forecast rows (forecast-dimension-time) carrying availability_status; sellers that cannot cover the full horizon signal the gap via the response's incomplete[] mechanism. Unlike start_date/end_date this is not an eligibility filter — products remain returnable when only part of the horizon is open. The resulting availability is a snapshot bounded by the forecast's valid_until, never a hold. Mutually exclusive with start_date and end_date, which declare a fixed flight; buyers that already know their dates use those instead." - ), - ] = None - budget_range: budget_range_1.BudgetRange | None = None - countries: Annotated[ - list[Country] | None, - Field( - description="Filter by country coverage using ISO 3166-1 alpha-2 codes (e.g., ['US', 'CA', 'GB']). Returns products whose geographic coverage includes at least one of the specified countries. This is a product attribute filter, not a delivery-targeting instruction.", - min_length=1, - ), - ] = None - regions: Annotated[ - list[Region] | None, - Field( - description='Inventory coverage in the specified ISO 3166-2 subdivisions: OR within this field, AND across offer filters. Does not add delivery targeting, require targeting support, or rescope pricing or forecasts.', - min_length=1, - ), - ] = None - metros: Annotated[ - list[Metro] | None, - Field( - description='Inventory coverage in the specified metro areas (system and code): OR within this field, AND across offer filters. Does not add delivery targeting, require targeting support, or rescope pricing or forecasts.', - min_length=1, - ), - ] = None - postal_areas: Annotated[ - list[postal_area.PostalArea] | None, - Field( - description='Inventory coverage in the specified postal areas: OR within this field, AND across offer filters. Does not add delivery targeting, require targeting support, or rescope pricing or forecasts.', - min_length=1, - ), - ] = None - geo_proximity: Annotated[ - list[GeoProximityItem] | None, - Field( - description='Inventory coverage in the specified proximity boundaries: OR within this field, AND across offer filters. Does not add delivery targeting, require targeting support, or rescope pricing or forecasts.', - min_length=1, - ), - ] = None - property_list: Annotated[ - property_list_ref.PropertyListReference | None, - Field( - description='Reference to an externally managed property list. When provided, the seller filters products to only those available on properties in the list. This narrows which publisher inventory is returned; it is a product attribute filter, not a delivery-targeting instruction.' - ), - ] = None - channels: Annotated[list[channels_1.MediaChannel] | None, Field(min_length=1)] = None - video_placement_types: Annotated[ - list[video_placement_type.VideoPlacementType] | None, Field(min_length=1) - ] = None - audio_distribution_types: Annotated[ - list[audio_distribution_type.AudioDistributionType] | None, Field(min_length=1) - ] = None - sponsored_placement_types: Annotated[ - list[sponsored_placement_type.SponsoredPlacementType] | None, Field(min_length=1) - ] = None - social_placement_surfaces: Annotated[ - list[social_placement_surface.SocialPlacementSurface] | None, Field(min_length=1) - ] = None - trusted_match: TrustedMatch | None = None - required_features: Annotated[ - canonical_media_buy_features.CanonicalMediaBuyFeatures | None, - Field(description='Canonical protocol features the seller must support.'), - ] = None - required_performance_standards: Annotated[ - list[RequiredPerformanceStandard] | None, Field(min_length=1) - ] = None - required_metrics: Annotated[ - list[available_metric.AvailableMetric] | None, Field(min_length=1) - ] = None - required_vendor_metrics: Annotated[list[RequiredVendorMetric] | None, Field(min_length=1)] = ( - None - ) - audience_evidence_requirements: ( - product_audience_evidence_requirements.ProductAudienceEvidenceRequirements | None - ) = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/product_signal_targeting_option.py b/src/adcp/types/generated_poc/core/product_signal_targeting_option.py deleted file mode 100644 index fe0551364..000000000 --- a/src/adcp/types/generated_poc/core/product_signal_targeting_option.py +++ /dev/null @@ -1,73 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/product_signal_targeting_option.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from pydantic import ConfigDict, Field, StrictBool - -from . import signal_ref, vendor_pricing_option -from .signal_listing import SignalListing - - -class ActivationStatus(StrEnum): - ready = 'ready' - requires_activation = 'requires_activation' - - -class AllowedTargetingMode(StrEnum): - include = 'include' - exclude = 'exclude' - - -class ProductSignalTargetingOption(SignalListing): - model_config = ConfigDict( - extra='allow', - ) - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the seller exposes a distinct runtime or activation handle that buyers must echo in packages[].targeting_overlay.signal_targeting_groups.groups[].signals[].signal_agent_segment_id. Buyers SHOULD echo this handle verbatim rather than reconstructing identity from categorical values; providers MAY namespace handles so cross-provider identity stays legible without a shared taxonomy registry.' - ), - ] = None - activation_status: Annotated[ - ActivationStatus | None, - Field( - description="Whether this signal option is ready to select on create_media_buy for the requesting account. 'ready' means the buyer can select it directly. 'requires_activation' means the buyer must activate the signal first or include an activation_key the seller accepts." - ), - ] = ActivationStatus.ready - allowed_targeting_modes: Annotated[ - list[AllowedTargetingMode] | None, - Field( - description="How this signal may be used when composing package-level signal targeting groups. 'include' means the signal may appear in an 'any' child group. 'exclude' means the signal may appear in a 'none' child group. Omit when the signal is include-only. This field declares the allowed buy-time group operator; binary package signal entries still use value=true in both include and exclude groups.", - min_length=1, - ), - ] = [AllowedTargetingMode.include] - default_selected: Annotated[ - StrictBool | None, - Field( - description="Whether the seller recommends or preselects this signal when composing this product. Buyers may remove it unless signal_targeting_rules.selection_mode is 'fixed'. When selection_mode is 'fixed', sellers apply default_selected signals even if the buyer omits signal_targeting_groups and MUST echo the applied entries on the resulting package state." - ), - ] = False - selection_group: Annotated[ - str | None, - Field( - description='Optional product-defined composability bucket for signal options, such as alternative audience tiers, a key-value targeting plane, or an audience-segment targeting plane. Signals in the same selection_group are expected to be OR-combinable inside one child group for a given targeting mode, subject to signal_targeting_rules. Use different selection_group values when the product requires separate ANDed clauses, such as signal sets backed by different platform targeting primitives that cannot be collapsed into one child group. selection_group is a product-option grouping key, not a reference to one child object in packages[].targeting_overlay.signal_targeting_groups.groups[]. Sellers can use signal_targeting_rules.max_selected_per_group and signal_targeting_rules.selection_group_rules with selection_group to guide and validate storefront composition.' - ), - ] = None - pricing_options: Annotated[ - list[vendor_pricing_option.VendorPricingOption] | None, - Field( - description='Signal pricing options available when this signal is selected on this product. Product-scoped pricing is authoritative for this product; if get_signals exposes a different default rate card, use this product-scoped price when composing the buy. Buyers pass the selected pricing_option_id in packages[].targeting_overlay.signal_targeting_groups.groups[].signals[].pricing_option_id. Omit when the signal is bundled into the product price or has no incremental cost.', - min_length=1, - ), - ] = None - signal_ref: Annotated[ - signal_ref.SignalRef, - Field( - description="Canonical signal reference. Use scope 'product' for a product-local signal defined by this listing; use scope 'data_provider' with data_provider_domain for a signal defined in a data provider's published adagents.json signals[]; use scope 'signal_source' with signal_source_url for a source-native signal." - ), - ] diff --git a/src/adcp/types/generated_poc/core/product_targeting_resolution.py b/src/adcp/types/generated_poc/core/product_targeting_resolution.py deleted file mode 100644 index 43d742ffc..000000000 --- a/src/adcp/types/generated_poc/core/product_targeting_resolution.py +++ /dev/null @@ -1,34 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/product_targeting_resolution.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from . import ext as ext_1 -from . import targeting_modification - - -class ProductTargetingResolution(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - modifications: Annotated[ - list[targeting_modification.TargetingModification], - Field( - description='Ordered changes applied to the requested targeting_overlay. Sellers apply entries in array order and MUST validate the complete resulting overlay before returning the product.', - min_length=1, - ), - ] - effective_targeting_digest: Annotated[ - str | None, - Field( - description='Optional digest of the canonical effective targeting overlay. Until AdCP defines a cross-implementation canonicalization algorithm, this value is an opaque seller audit handle and buyers MUST NOT attempt to reproduce or compare it across sellers.', - pattern='^sha256:[0-9a-f]{64}$', - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/property.py b/src/adcp/types/generated_poc/core/property.py deleted file mode 100644 index 76dbc6284..000000000 --- a/src/adcp/types/generated_poc/core/property.py +++ /dev/null @@ -1,68 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/property.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..enums import channels, identifier_types -from ..enums import property_type as property_type_1 -from . import property_id as property_id_1 -from . import property_tag - - -class Identifier(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - identifier_types.PropertyIdentifierTypes, - Field(description='Type of identifier for this property'), - ] - value: Annotated[ - str, - Field( - description="The identifier value. For domain type: 'example.com' matches base domain plus www and m subdomains; 'edition.example.com' matches that specific subdomain; '*.example.com' matches ALL subdomains but NOT base domain" - ), - ] - - -class Property(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - property_id: Annotated[ - property_id_1.PropertyId | None, - Field( - description='Unique identifier for this property (optional). Enables referencing properties by ID instead of repeating full objects.' - ), - ] = None - property_type: Annotated[ - property_type_1.PropertyType, Field(description='Type of advertising property') - ] - name: Annotated[str, Field(description='Human-readable property name')] - identifiers: Annotated[ - list[Identifier], Field(description='Array of identifiers for this property', min_length=1) - ] - tags: Annotated[ - list[property_tag.PropertyTag] | None, - Field( - description='Tags for categorization and grouping (e.g., network membership, content categories)' - ), - ] = None - supported_channels: Annotated[ - list[channels.MediaChannel] | None, - Field( - description="Advertising channels this property supports (e.g., ['display', 'olv', 'social']). Publishers declare which channels their inventory aligns with. Properties may support multiple channels. See the Media Channel Taxonomy for definitions." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description='Domain where adagents.json should be checked for authorization validation. Optional in adagents.json (file location implies domain).' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/property_delivery_metrics.py b/src/adcp/types/generated_poc/core/property_delivery_metrics.py deleted file mode 100644 index c6987ce92..000000000 --- a/src/adcp/types/generated_poc/core/property_delivery_metrics.py +++ /dev/null @@ -1,43 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/property_delivery_metrics.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from pydantic import Field - -from . import identifier as identifier_1 -from . import property_ref as property_ref_1 -from .delivery_metrics import DeliveryMetrics - - -class PropertyDeliveryMetrics(DeliveryMetrics): - publisher_domain: Annotated[ - str, - Field( - description='Publisher or platform authority that namespaces the operational identifier, including when the surface is not registered in adagents.json.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - identifier: Annotated[ - identifier_1.Identifier, - Field( - description='Primary operational identifier of the property that delivered. Required even when catalog enrichment is unavailable.' - ), - ] - property_ref: Annotated[ - property_ref_1.PropertyReference | None, - Field( - description="Canonical publisher-scoped catalog identity when the delivered property resolves to adagents.json. Its publisher_domain MUST equal the row's publisher_domain." - ), - ] = None - property_name: Annotated[ - str | None, - Field( - description='Current human-readable property name. Convenience metadata only; property_ref is stable identity.' - ), - ] = None - impressions: Any - spend: Any diff --git a/src/adcp/types/generated_poc/core/property_id.py b/src/adcp/types/generated_poc/core/property_id.py deleted file mode 100644 index 514c9e7ed..000000000 --- a/src/adcp/types/generated_poc/core/property_id.py +++ /dev/null @@ -1,17 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/property_id.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from adcp.types._scalar import ScalarStr - - -class PropertyId(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[a-z0-9_]+$'} - _json_schema_extra = { - 'description': 'Identifier for a publisher property. Must be lowercase alphanumeric with underscores only.', - 'examples': ['cnn_ctv_app', 'homepage', 'mobile_ios', 'instagram'], - 'title': 'Property ID', - } diff --git a/src/adcp/types/generated_poc/core/property_list_ref.py b/src/adcp/types/generated_poc/core/property_list_ref.py deleted file mode 100644 index 9373a7a11..000000000 --- a/src/adcp/types/generated_poc/core/property_list_ref.py +++ /dev/null @@ -1,26 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/property_list_ref.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - - -class PropertyListReference(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[AnyUrl, Field(description='URL of the agent managing the property list')] - list_id: Annotated[ - str, Field(description='Identifier for the property list within the agent', min_length=1) - ] - auth_token: Annotated[ - str | None, - Field( - description='JWT or other authorization token for accessing the list. Optional if the list is public or caller has implicit access.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/property_ref.py b/src/adcp/types/generated_poc/core/property_ref.py deleted file mode 100644 index 993790e32..000000000 --- a/src/adcp/types/generated_poc/core/property_ref.py +++ /dev/null @@ -1,29 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/property_ref.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from . import property_id as property_id_1 - - -class PropertyReference(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Annotated[ - str, - Field( - description='Domain where the adagents.json declaring this property is hosted.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - property_id: Annotated[ - property_id_1.PropertyId, - Field(description="Property ID from the publisher's adagents.json property catalog."), - ] diff --git a/src/adcp/types/generated_poc/core/property_tag.py b/src/adcp/types/generated_poc/core/property_tag.py deleted file mode 100644 index f8a83e99d..000000000 --- a/src/adcp/types/generated_poc/core/property_tag.py +++ /dev/null @@ -1,17 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/property_tag.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from adcp.types._scalar import ScalarStr - - -class PropertyTag(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[a-z0-9_]+$'} - _json_schema_extra = { - 'description': 'Tag for categorizing publisher properties. Must be lowercase alphanumeric with underscores only.', - 'examples': ['ctv', 'premium', 'news', 'sports', 'meta_network', 'social_media'], - 'title': 'Property Tag', - } diff --git a/src/adcp/types/generated_poc/core/proposal.py b/src/adcp/types/generated_poc/core/proposal.py deleted file mode 100644 index 04b10b2aa..000000000 --- a/src/adcp/types/generated_poc/core/proposal.py +++ /dev/null @@ -1,116 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/proposal.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat - -from ..enums import pacing as pacing_1 -from ..enums import proposal_status as proposal_status_1 -from . import budget_allocation as budget_allocation_1 -from . import delivery_forecast -from . import ext as ext_1 -from . import insertion_order as insertion_order_1 -from . import media_buy_frequency_cap, product_allocation - - -class TotalBudgetGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - min: Annotated[StrictFloat | None, Field(description='Minimum recommended budget', ge=0.0)] = ( - None - ) - recommended: Annotated[ - StrictFloat | None, Field(description='Recommended budget for optimal performance', ge=0.0) - ] = None - max: Annotated[ - StrictFloat | None, Field(description='Maximum budget before diminishing returns', ge=0.0) - ] = None - currency: Annotated[str | None, Field(description='ISO 4217 currency code')] = None - - -class Proposal(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - proposal_id: Annotated[ - str, - Field( - description='Unique identifier for this proposal. Used to finalize a draft proposal and to execute a committed proposal via create_media_buy.', - max_length=255, - ), - ] - name: Annotated[ - str, Field(description='Human-readable name for this media plan proposal', max_length=500) - ] - description: Annotated[ - str | None, - Field( - description='Explanation of the proposal strategy and what it achieves', max_length=2000 - ), - ] = None - allocations: Annotated[ - list[product_allocation.ProductAllocation], - Field( - description='Products and budget constraints in this plan. Fixed proposals require allocation_percentage on every entry and percentages MUST sum to 100. Seller-optimized proposals forbid exact allocation_percentage and may instead supply min_spend_target_percentage and max_spend_percentage, each only when the seller advertises the matching package-control capability (seller_optimized_min_spend_targets, seller_optimized_package_budgets). Publishers are responsible for validating cross-entry sums; buyers SHOULD validate them before execution.', - min_length=1, - ), - ] - budget_allocation: Annotated[ - budget_allocation_1.BudgetAllocation | None, - Field( - description='How the executed total budget is allocated across proposal products. Omit for legacy fixed proposals.' - ), - ] = None - pacing: Annotated[ - pacing_1.Pacing | None, - Field( - description='Recommended aggregate pacing for the executed media-buy budget. On a committed proposal this is part of the firm delivery terms.' - ), - ] = None - frequency_cap: Annotated[ - media_buy_frequency_cap.MediaBuyFrequencyCap | None, - Field( - description='Aggregate cap bound into this legacy proposal. It is authoritative when the proposal is executed and uses one counter across its packages.' - ), - ] = None - proposal_status: Annotated[ - proposal_status_1.ProposalStatus | None, - Field( - description="Lifecycle status of this proposal and the per-proposal source of truth for whether finalization is required before create_media_buy. When absent, the proposal is ready to buy (backward compatible). 'draft' means indicative pricing — finalize via refine before purchasing. 'committed' means firm pricing with inventory reserved until expires_at and executable via create_media_buy." - ), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field( - description='When this proposal expires and can no longer be executed. For draft proposals, indicates when indicative pricing becomes stale. For committed proposals, indicates when the inventory hold lapses — the buyer must call create_media_buy before this time.' - ), - ] = None - insertion_order: Annotated[ - insertion_order_1.InsertionOrder | None, - Field( - description='Formal insertion order attached to a committed proposal. Present when the seller requires a signed agreement before the media buy can proceed. The buyer references the io_id in io_acceptance on create_media_buy.' - ), - ] = None - total_budget_guidance: Annotated[ - TotalBudgetGuidance | None, Field(description='Optional budget guidance for this proposal') - ] = None - brief_alignment: Annotated[ - str | None, - Field( - description='Explanation of how this proposal aligns with the campaign brief', - max_length=2000, - ), - ] = None - forecast: Annotated[ - delivery_forecast.DeliveryForecast | None, - Field( - description='Aggregate forecasted delivery metrics for the entire proposal. When both proposal-level and allocation-level forecasts are present, the proposal-level forecast is authoritative for total delivery estimation.' - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/protocol_envelope.py b/src/adcp/types/generated_poc/core/protocol_envelope.py deleted file mode 100644 index 7f75dfd17..000000000 --- a/src/adcp/types/generated_poc/core/protocol_envelope.py +++ /dev/null @@ -1,90 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/protocol_envelope.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field, StrictBool - -from ..enums import task_status -from . import context as context_1 -from . import error -from . import push_notification_config as push_notification_config_1 - - -class ProtocolEnvelope(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - context_id: Annotated[ - str | None, - Field( - description='Transport-managed conversation identifier. On A2A, this maps to the native Message/Task `contextId` used to associate messages with a conversation; it is not carried inside the AdCP DataPart. On MCP, a request-body `context_id`, where admitted by the selected request schema, is a compatibility-only field: servers MUST ignore it, callers MUST NOT rely on it for continuity, and it MUST NOT select session state, identity, account, authorization, task continuation, or idempotency scope. MCP continuity, if provided, comes from the transport session. Distinct from `context` (per-request opaque echo, see below) and from `task_id` (AdCP operation tracking).' - ), - ] = None - context: Annotated[ - context_1.ContextObject | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (transport-managed A2A conversation correlation or MCP compatibility metadata) — `context` is caller-owned echo and never selects transport state. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.' - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - task_status.TaskStatus, - Field( - description='Current AdCP task state or structured outcome. Indicates whether the task completed, is in progress, was submitted for async processing, failed, requires user input, or returned a typed business rejection. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) normally emit `status: "completed"`; a task-specific rejection arm emits `status: "rejected"` without turning the transport into a failure. Async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative AdCP response state.' - ), - ] = task_status.TaskStatus.completed - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - StrictBool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. `replayed` appears only when the request actually resolved through the idempotency cache. Pure reads may ignore an optional `idempotency_key`; when a seller voluntarily caches keyed reads, those responses use the same replay indicator and full cache contract." - ), - ] = False - adcp_error: Annotated[ - error.Error | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures." - ), - ] = None - push_notification_config: Annotated[ - push_notification_config_1.PushNotificationConfig | None, - Field( - description='AdCP application-layer webhook configuration for async task updates over MCP, A2A, or REST. Echoed from the request to confirm webhook settings. It is distinct from transport-native progress or A2A TaskPushNotificationConfig delivery and can outlive the originating transport session.' - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description='Opaque authorization context issued only by an approved check_governance decision. Buyers attach it to governed requests across protocol roles (media buys, rights acquisitions, signal activations, creative services); receiving services persist it and forward it on subsequent execution and lifecycle checks. The context is the authoritative plan binding at service boundaries, so a service MUST NOT require a separate plan_id.\n\nGovernance agents MUST emit a compact JWS per the AdCP JWS profile. Verifiers validate standard authorization claims such as signature, issuer, audience, expiry, and replay protection, but intermediaries MUST NOT interpret embedded governance state for business logic. A conditions or denied verdict never carries an authorization context.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.', - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/provenance.py b/src/adcp/types/generated_poc/core/provenance.py deleted file mode 100644 index e24dc3e9c..000000000 --- a/src/adcp/types/generated_poc/core/provenance.py +++ /dev/null @@ -1,333 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/provenance.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat - -from ..enums import c2pa_watermark_action -from ..enums import digital_source_type as digital_source_type_1 -from ..enums import ( - disclosure_persistence, - disclosure_position, - embedded_provenance_method, - watermark_media_type, -) -from . import ext as ext_1 - - -class AiTool(AdCPBaseModel): - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class EmbeddedProvenanceItem(AdCPBaseModel): - method: Annotated[ - embedded_provenance_method.EmbeddedProvenanceMethod, - Field(description='How provenance data is carried within the content'), - ] - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class VerifyAgent18(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class RenderGuidance(AdCPBaseModel): - persistence: Annotated[ - disclosure_persistence.DisclosurePersistence | None, - Field( - description='How long the disclosure must persist during content playback or display' - ), - ] = None - min_duration_ms: Annotated[ - SchemaInt | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[disclosure_position.DisclosurePosition] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: ext_1.ExtensionObject | None = None - - -class Jurisdiction(AdCPBaseModel): - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - required: Annotated[ - StrictBool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - StrictFloat | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Watermark(AdCPBaseModel): - media_type: Annotated[ - watermark_media_type.WatermarkMediaType, - Field(description='Media category of the watermarked content'), - ] - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent18 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: Annotated[ - c2pa_watermark_action.C2PaWatermarkAction | None, - Field(description='C2PA action classification for this watermark'), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Provenance(AdCPBaseModel): - digital_source_type: Annotated[ - digital_source_type_1.DigitalSourceType | None, - Field( - description='IPTC-aligned classification of AI involvement in producing this content' - ), - ] = None - synthetic_depiction: Annotated[ - StrictBool | None, - Field( - description='Assessed declaration of whether the content synthetically depicts a real or fictional person performing or appearing in a way that was generated or materially manipulated rather than captured as depicted. `true` covers both a fully synthetic performer and material manipulation of a real performer; `false` is an assessed declaration that the content does not contain such a depiction. Absence means the content has not been assessed for synthetic depiction. This field does not claim consent, legality, or independent verification, and receivers MUST NOT derive it solely from `digital_source_type`.' - ), - ] = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/publisher_property_selector.py b/src/adcp/types/generated_poc/core/publisher_property_selector.py deleted file mode 100644 index 5b4cc5862..000000000 --- a/src/adcp/types/generated_poc/core/publisher_property_selector.py +++ /dev/null @@ -1,107 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/publisher_property_selector.json -# timestamp: 2026-10-04T00:53:27+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field - -from . import property_id, property_tag - - -class PublisherDomain(ScalarStr): - __slots__ = () - _constraints = { - 'pattern': '^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - } - - -class PublisherPropertySelector1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Annotated[ - str | None, - Field( - description="Domain where publisher's adagents.json is hosted (e.g., 'cnn.com'). XOR with `publisher_domains` — exactly one MUST be present on each `publisher_properties[]` entry; both-present and neither-present both fail validation.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - publisher_domains: Annotated[ - list[PublisherDomain] | None, - Field( - description="Compact form for fanning the same selector across many publishers (e.g., a managed network listing every publisher it represents). Each entry is the domain where that publisher's adagents.json is hosted. Each listed domain MUST be canonicalized to lowercase (the `pattern` already rejects uppercase). Mutually exclusive with `publisher_domain`. Each listed domain counts as explicitly scoped for the `managerdomain` fallback safety rule.", - min_length=1, - ), - ] = None - selection_type: Annotated[ - Literal['all'], - Field( - description='Discriminator indicating all properties from each addressed publisher are included' - ), - ] = 'all' - - -class PublisherPropertySelector2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Annotated[ - str, - Field( - description="Domain where publisher's adagents.json is hosted (e.g., 'cnn.com').", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - selection_type: Annotated[ - Literal['by_id'], - Field(description='Discriminator indicating selection by specific property IDs'), - ] = 'by_id' - property_ids: Annotated[ - list[property_id.PropertyId], - Field(description="Specific property IDs from the publisher's adagents.json", min_length=1), - ] - - -class PublisherPropertySelector3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Annotated[ - str | None, - Field( - description="Domain where publisher's adagents.json is hosted (e.g., 'cnn.com'). XOR with `publisher_domains` — exactly one MUST be present on each `publisher_properties[]` entry; both-present and neither-present both fail validation.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - publisher_domains: Annotated[ - list[PublisherDomain] | None, - Field( - description="Compact form for fanning the same tag predicate across many publishers (canonical managed-network shape). Each entry is the domain where that publisher's adagents.json is hosted. Each listed domain MUST be canonicalized to lowercase (the `pattern` already rejects uppercase). Mutually exclusive with `publisher_domain`. Each listed domain counts as explicitly scoped for the `managerdomain` fallback safety rule.", - min_length=1, - ), - ] = None - selection_type: Annotated[ - Literal['by_tag'], Field(description='Discriminator indicating selection by property tags') - ] = 'by_tag' - property_tags: Annotated[ - list[property_tag.PropertyTag], - Field( - description="Property tags resolved against each addressed publisher's adagents.json, OR against the parent file's top-level `properties[]` when those properties carry a `publisher_domain` matching the selector. Selector covers all properties carrying any of these tags.", - min_length=1, - ), - ] - - -PublisherPropertySelector = Annotated[ - PublisherPropertySelector1 | PublisherPropertySelector2 | PublisherPropertySelector3, - Field( - description="Selects properties from a publisher's adagents.json. Used for both product definitions and agent authorization. Supports three selection patterns: all properties, specific IDs, or by tags. Each selector targets one publisher via `publisher_domain` (string) or a fan-out across many publishers that share the same selector via `publisher_domains` (array). Exactly one of `publisher_domain` or `publisher_domains` MUST be present. When `publisher_domains` is used, the selector is logically equivalent to repeating the same entry once per listed domain.", - discriminator='selection_type', - title='Publisher Property Selector', - ), -] diff --git a/src/adcp/types/generated_poc/core/push_notification_config.py b/src/adcp/types/generated_poc/core/push_notification_config.py deleted file mode 100644 index 1dd3b1cd7..000000000 --- a/src/adcp/types/generated_poc/core/push_notification_config.py +++ /dev/null @@ -1,66 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/push_notification_config.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - -from ..enums import auth_scheme - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[auth_scheme.AuthenticationScheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. For 3.x schema compatibility the member remains optional, but a seller MUST reject a task that registers an AdCP webhook without it using `INVALID_REQUEST`; otherwise the required webhook envelope cannot be emitted.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - deprecated=True, - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).', - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/real_estate_item.py b/src/adcp/types/generated_poc/core/real_estate_item.py deleted file mode 100644 index 338963531..000000000 --- a/src/adcp/types/generated_poc/core/real_estate_item.py +++ /dev/null @@ -1,120 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/real_estate_item.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, ConfigDict, Field, StrictFloat - -from . import ext as ext_1 -from . import offering_asset_group -from . import price as price_1 - - -class Address(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - street: Annotated[str | None, Field(description='Street address.')] = None - city: Annotated[str | None, Field(description='City name.')] = None - region: Annotated[str | None, Field(description='State, province, or region.')] = None - postal_code: Annotated[str | None, Field(description='Postal or ZIP code.')] = None - country: Annotated[ - str | None, Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = None - - -class PropertyType(StrEnum): - house = 'house' - apartment = 'apartment' - condo = 'condo' - townhouse = 'townhouse' - land = 'land' - commercial = 'commercial' - - -class ListingType(StrEnum): - for_sale = 'for_sale' - for_rent = 'for_rent' - - -class Unit(StrEnum): - sqft = 'sqft' - sqm = 'sqm' - - -class Area(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - value: Annotated[StrictFloat, Field(description='Area value.', ge=0.0)] - unit: Annotated[Unit, Field(description='Area unit.')] - - -class Location(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - lat: Annotated[ - StrictFloat, Field(description='Latitude in decimal degrees (WGS 84).', ge=-90.0, le=90.0) - ] - lng: Annotated[ - StrictFloat, - Field(description='Longitude in decimal degrees (WGS 84).', ge=-180.0, le=180.0), - ] - - -class RealEstateItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - listing_id: Annotated[str, Field(description='Unique identifier for this property listing.')] - title: Annotated[ - str, Field(description="Listing title (e.g., 'Spacious 3BR Apartment in Jordaan').") - ] - address: Annotated[Address, Field(description='Property address.')] - price: Annotated[ - price_1.Price | None, - Field(description="Property price or rental rate. Use period 'month' for rentals."), - ] = None - property_type: Annotated[PropertyType | None, Field(description='Type of property.')] = None - listing_type: Annotated[ - ListingType | None, Field(description='Whether the property is for sale or rent.') - ] = None - bedrooms: Annotated[SchemaInt | None, Field(description='Number of bedrooms.', ge=0)] = None - bathrooms: Annotated[ - StrictFloat | None, - Field( - description='Number of bathrooms (e.g., 2.5 for two full and one half bath).', ge=0.0 - ), - ] = None - area: Annotated[Area | None, Field(description='Property size.')] = None - description: Annotated[str | None, Field(description='Property description.')] = None - location: Annotated[ - Location | None, Field(description='Geographic coordinates of the property.') - ] = None - image_url: Annotated[AnyUrl | None, Field(description='Primary property image URL.')] = None - url: Annotated[AnyUrl | None, Field(description='Listing page URL.')] = None - neighborhood: Annotated[str | None, Field(description='Neighborhood or area name.')] = None - year_built: Annotated[SchemaInt | None, Field(description='Year the property was built.')] = ( - None - ) - tags: Annotated[ - list[str] | None, - Field( - description="Tags for filtering (e.g., 'garden', 'parking', 'renovated', 'waterfront').", - min_length=1, - ), - ] = None - assets: Annotated[ - list[offering_asset_group.OfferingAssetGroup] | None, - Field( - description="Typed creative asset pools for this property listing. Uses the same OfferingAssetGroup structure as offering-type catalogs. Standard group IDs: 'images_landscape' (exterior/interior hero), 'images_vertical' (9:16 for Stories), 'images_square' (1:1). Enables formats to declare typed image requirements that map unambiguously to the right asset regardless of platform.", - min_length=1, - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/reference_asset.py b/src/adcp/types/generated_poc/core/reference_asset.py deleted file mode 100644 index 877e9bfa4..000000000 --- a/src/adcp/types/generated_poc/core/reference_asset.py +++ /dev/null @@ -1,42 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reference_asset.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - - -class Role(StrEnum): - style_reference = 'style_reference' - product_shot = 'product_shot' - mood_board = 'mood_board' - example_creative = 'example_creative' - logo = 'logo' - strategy_doc = 'strategy_doc' - storyboard = 'storyboard' - - -class ReferenceAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - url: Annotated[ - AnyUrl, Field(description='URL to the reference asset (image, video, or document)') - ] - role: Annotated[ - Role, - Field( - description='How the creative agent should use this asset. style_reference: match the visual style; product_shot: include this product; mood_board: overall look and feel; example_creative: example of a similar execution; logo: logo to use; strategy_doc: strategy or planning document for context; storyboard: sequential visual direction for video or multi-scene creative' - ), - ] - description: Annotated[ - str | None, - Field( - description='Human-readable description of the asset and how it should inform creative generation' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/reference_renderer.py b/src/adcp/types/generated_poc/core/reference_renderer.py deleted file mode 100644 index f9b6a454d..000000000 --- a/src/adcp/types/generated_poc/core/reference_renderer.py +++ /dev/null @@ -1,86 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reference_renderer.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, field_validator - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - source_repository: Annotated[ - AnyUrl, - Field( - description='Allowlisted source repository that the npm provenance attestation MUST identify.' - ), - ] - workflow_path: Annotated[ - str, - Field( - description='Repository-relative GitHub Actions workflow path that npm provenance buildDefinition.externalParameters.workflow.path MUST identify.', - pattern='^\\.github/workflows/[A-Za-z0-9._/-]+\\.ya?ml$', - ), - ] - - @field_validator('source_repository') - @classmethod - def _require_github_source_repository(cls, value: AnyUrl) -> AnyUrl: - if value.scheme != 'https' or value.host != 'github.com' or value.port != 443: - raise ValueError('source_repository must use https://github.com/') - if value.username is not None or value.password is not None: - raise ValueError('source_repository must not contain credentials') - return value - -class ReferenceRenderer(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - runtime: Annotated[ - Literal['browser-esm'], - Field( - description='Execution contract for the referenced package. browser-esm means a browser-safe ECMAScript module that accepts canonical manifest data and returns an inert presentation without Node.js APIs, ambient credentials, delivery tracking, or undeclared network access. Non-JavaScript clients use a hosted preview_creative provider or display the manifest.' - ), - ] = 'browser-esm' - package: Annotated[ - str, - Field( - description='npm package name, scoped or unscoped. The package is resolved from the npm registry; the AdCP registry does not proxy its executable contents.', - pattern='^(?:@[a-z0-9][a-z0-9._~-]*/)?[a-z0-9][a-z0-9._~-]*$', - ), - ] - version: Annotated[ - str, - Field( - description='Exact semantic version. Ranges and tags such as latest are forbidden so the registry entry is reproducible. Package semantic versioning identifies the pinned distribution artifact; it is independent of any one format revision because one package may expose renderers for multiple formats.', - pattern='^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$', - ), - ] - export: Annotated[ - str, - Field( - description="Named package export that implements the renderer contract for this enclosing format entry. Compatibility is bound at the export-to-entry edge, not to matching version labels: registry review and contract fixtures verify that the export implements the entry's input contract. When that input contract changes, the registry MUST rerun those fixtures and MAY retain the existing export and package pin when they still pass. One package version MAY expose different named exports for different formats or input contracts.", - min_length=1, - ), - ] - format_revision: Annotated[ - str | None, - Field( - deprecated=True, - description="Deprecated compatibility annotation retained for previously published registry entries. Renderer package versions and community format revisions have independent lifecycles, so consumers MUST NOT require this value to equal the enclosing entry's format_revision or use matching values as evidence of compatibility. Registry review and contract fixtures bind the named export to the enclosing format's input contract. New entries SHOULD omit this field.", - pattern='^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)$', - ), - ] = None - integrity: Annotated[ - str, - Field( - description='Subresource Integrity value for the exact npm package tarball. Consumers MUST compare this value before loading code, require the provenance subject digest to match the same tarball, and fail closed on mismatch.', - pattern='^(?:sha256-[A-Za-z0-9+/]{43}=|sha384-[A-Za-z0-9+/]{64}|sha512-[A-Za-z0-9+/]{86}==)$', - ), - ] - provenance: Provenance diff --git a/src/adcp/types/generated_poc/core/registry_event.py b/src/adcp/types/generated_poc/core/registry_event.py deleted file mode 100644 index 868572257..000000000 --- a/src/adcp/types/generated_poc/core/registry_event.py +++ /dev/null @@ -1,1345 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/registry_event.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal -from uuid import UUID - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarStr -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, RootModel, StrictBool - -from ..enums import adcp_protocol, collection_kind, distribution_identifier_type -from . import agent_signing_key, canonical_format_kind -from . import collection as collection_1 -from . import collection_selector, format_id, identifier -from . import property as property_1 -from . import property_id, property_tag, publisher_property_selector - - -class EventType(StrEnum): - property_created = 'property.created' - property_updated = 'property.updated' - property_merged = 'property.merged' - property_stale = 'property.stale' - property_reactivated = 'property.reactivated' - collection_created = 'collection.created' - collection_updated = 'collection.updated' - collection_merged = 'collection.merged' - collection_removed = 'collection.removed' - agent_discovered = 'agent.discovered' - agent_removed = 'agent.removed' - agent_profile_updated = 'agent.profile_updated' - agent_compliance_changed = 'agent.compliance_changed' - agent_verification_earned = 'agent.verification_earned' - agent_verification_lost = 'agent.verification_lost' - publisher_adagents_discovered = 'publisher.adagents_discovered' - publisher_adagents_changed = 'publisher.adagents_changed' - authorization_granted = 'authorization.granted' - authorization_revoked = 'authorization.revoked' - authorization_modified = 'authorization.modified' - - -class EntityType(StrEnum): - property = 'property' - collection = 'collection' - agent = 'agent' - publisher = 'publisher' - authorization = 'authorization' - - -class Payload(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - alias_rid: Annotated[ - UUID, Field(description='Retired property_rid that now aliases to canonical_rid.') - ] - canonical_rid: Annotated[ - UUID, Field(description='Canonical property_rid that consumers should retain.') - ] - evidence: Annotated[ - str | None, - Field( - description='Registry evidence source for the merge, such as adagents_json or manual_review.' - ), - ] = None - - -class RegistryEvent3(AdCPBaseModel): - event_id: Annotated[ - UUID, - Field( - description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' - ), - ] - event_type: Annotated[ - Literal['property.merged'], - Field(description='Discriminator. Determines the shape of payload.'), - ] = 'property.merged' - entity_type: Annotated[ - Literal['property'], Field(description='Entity class touched by this event.') - ] = 'property' - entity_id: Annotated[ - str, - Field( - description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' - ), - ] - payload: Annotated[ - Payload, - Field( - description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' - ), - ] - actor: Annotated[ - str, - Field( - description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' - ), - ] - created_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' - ), - ] - - -class Payload1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - property_rid: UUID - last_resolved_at: AwareDatetime | None = None - reason: str | None = None - - -class RegistryEvent4(AdCPBaseModel): - event_id: Annotated[ - UUID, - Field( - description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' - ), - ] - event_type: Annotated[ - Literal['property.stale'], - Field(description='Discriminator. Determines the shape of payload.'), - ] = 'property.stale' - entity_type: Annotated[ - Literal['property'], Field(description='Entity class touched by this event.') - ] = 'property' - entity_id: Annotated[ - str, - Field( - description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' - ), - ] - payload: Annotated[ - Payload1, - Field( - description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' - ), - ] - actor: Annotated[ - str, - Field( - description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' - ), - ] - created_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' - ), - ] - - -class Payload3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - alias_rid: Annotated[ - UUID, Field(description='Retired collection_rid that now aliases to canonical_rid.') - ] - canonical_rid: Annotated[ - UUID, Field(description='Canonical collection_rid that consumers should retain.') - ] - evidence: Annotated[ - str | None, - Field( - description='Registry evidence source for the merge, such as adagents_json or manual_review.' - ), - ] = None - - -class RegistryEvent8(AdCPBaseModel): - event_id: Annotated[ - UUID, - Field( - description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' - ), - ] - event_type: Annotated[ - Literal['collection.merged'], - Field(description='Discriminator. Determines the shape of payload.'), - ] = 'collection.merged' - entity_type: Annotated[ - Literal['collection'], Field(description='Entity class touched by this event.') - ] = 'collection' - entity_id: Annotated[ - str, - Field( - description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' - ), - ] - payload: Annotated[ - Payload3, - Field( - description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' - ), - ] - actor: Annotated[ - str, - Field( - description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' - ), - ] - created_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' - ), - ] - - -class Status(StrEnum): - active = 'active' - stale = 'stale' - removed = 'removed' - - -class Tracks(StrEnum): - pass_ = 'pass' # nosec B105: Fixed protocol compliance track verdict. - fail = 'fail' - partial = 'partial' - skip = 'skip' - silent = 'silent' - warning = 'warning' - unknown = 'unknown' - skipped = 'skipped' - - -class GradingProfile(StrEnum): - legacy = 'legacy' - spec = 'spec' - - -class AuthorizationType(StrEnum): - property_ids = 'property_ids' - property_tags = 'property_tags' - inline_properties = 'inline_properties' - publisher_properties = 'publisher_properties' - signal_ids = 'signal_ids' - signal_tags = 'signal_tags' - - -class DelegationType(StrEnum): - direct = 'direct' - delegated = 'delegated' - ad_network = 'ad_network' - - -class Evidence(StrEnum): - adagents_json = 'adagents_json' - agent_claim = 'agent_claim' - community = 'community' - override = 'override' - - -class Domain(ScalarStr): - __slots__ = () - _constraints = { - 'pattern': '^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - } - - -class BadgeRole(RootModel[adcp_protocol.AdcpProtocol]): - root: adcp_protocol.AdcpProtocol - - -class StringArray(RootModel[list[str]]): - root: list[str] - - -class ChangedFields(RootModel[list[str]]): - root: Annotated[ - list[str], - Field( - description='Advisory list of changed top-level field names from the source document. Example values for publisher.adagents_changed include "formats", "placements", and "authorized_agents"; for agent.profile_updated these are profile field names such as "channels" or "format_kinds". Producers SHOULD enumerate every changed semantic field, explicitly including formats and placements for catalog-only revisions (additive from 3.2). Consumers MAY use this for targeted index refresh but MUST re-resolve every depended-on field when reading an older retained event that lacks this list.', - min_length=1, - ), - ] - - -class Classification(StrEnum): - property = 'property' - ad_infra = 'ad_infra' - publisher_mask = 'publisher_mask' - network = 'network' - unclassified = 'unclassified' - - -class PropertySource(StrEnum): - authoritative = 'authoritative' - enriched = 'enriched' - contributed = 'contributed' - - -class CollectionIdentifier(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - publisher_domain: Annotated[ - Domain, - Field(description='Distribution platform domain, such as youtube.com or spotify.com.'), - ] - type: distribution_identifier_type.DistributionIdentifierType - value: str - - -class Type(StrEnum): - sales = 'sales' - creative = 'creative' - signals = 'signals' - governance = 'governance' - measurement = 'measurement' - unknown = 'unknown' - - -class Market(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{2}$'} - - -class AgentProfilePayload(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: AnyUrl | None = None - name: str | None = None - type: Type | None = None - channels: StringArray | None = None - property_types: StringArray | None = None - markets: list[Market] | None = None - categories: StringArray | None = None - category_taxonomy: str | None = None - format_ids: Annotated[ - list[format_id.FormatReferenceStructuredObject] | None, - Field( - deprecated=True, - description='Deprecated in AdCP 3.2; removed in AdCP 4.0. Named-format profile projection. Use canonical `format_kinds` and `format_option_refs`.', - ), - ] = None - format_kinds: list[canonical_format_kind.CanonicalFormatKind] | None = None - tags: StringArray | None = None - delivery_types: StringArray | None = None - property_count: Annotated[SchemaInt | None, Field(ge=0)] = None - publisher_count: Annotated[SchemaInt | None, Field(ge=0)] = None - has_tmp: StrictBool | None = None - updated_at: AwareDatetime | None = None - - -class Country(Market): - pass - - -class Countries(RootModel[list[Country]]): - root: Annotated[list[Country], Field(min_length=1)] - - -class ComplianceStatus(StrEnum): - passing = 'passing' - degraded = 'degraded' - failing = 'failing' - unknown = 'unknown' - - -class StoryboardStatus(StrEnum): - passing = 'passing' - failing = 'failing' - partial = 'partial' - untested = 'untested' - skipped = 'skipped' - not_selected = 'not_selected' - unknown = 'unknown' - - -class RegistryEvent10(AdCPBaseModel): - event_id: Annotated[ - UUID, - Field( - description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' - ), - ] - event_type: Annotated[ - Literal['agent.discovered'], - Field(description='Discriminator. Determines the shape of payload.'), - ] = 'agent.discovered' - entity_type: Annotated[ - Literal['agent'], Field(description='Entity class touched by this event.') - ] = 'agent' - entity_id: Annotated[ - str, - Field( - description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' - ), - ] - payload: Annotated[ - AgentProfilePayload, - Field( - description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' - ), - ] - actor: Annotated[ - str, - Field( - description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' - ), - ] - created_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' - ), - ] - - -class Payload5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: AnyUrl - removed_from_publishers: Annotated[list[Domain] | None, Field(min_length=1)] = None - - -class RegistryEvent11(AdCPBaseModel): - event_id: Annotated[ - UUID, - Field( - description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' - ), - ] - event_type: Annotated[ - Literal['agent.removed'], - Field(description='Discriminator. Determines the shape of payload.'), - ] = 'agent.removed' - entity_type: Annotated[ - Literal['agent'], Field(description='Entity class touched by this event.') - ] = 'agent' - entity_id: Annotated[ - str, - Field( - description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' - ), - ] - payload: Annotated[ - Payload5, - Field( - description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' - ), - ] - actor: Annotated[ - str, - Field( - description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' - ), - ] - created_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' - ), - ] - - -class Payload6(AgentProfilePayload): - changed_fields: ChangedFields | None = None - agent_url: Any - - -class RegistryEvent12(AdCPBaseModel): - event_id: Annotated[ - UUID, - Field( - description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' - ), - ] - event_type: Annotated[ - Literal['agent.profile_updated'], - Field(description='Discriminator. Determines the shape of payload.'), - ] = 'agent.profile_updated' - entity_type: Annotated[ - Literal['agent'], Field(description='Entity class touched by this event.') - ] = 'agent' - entity_id: Annotated[ - str, - Field( - description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' - ), - ] - payload: Annotated[ - Payload6, - Field( - description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' - ), - ] - actor: Annotated[ - str, - Field( - description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' - ), - ] - created_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' - ), - ] - - -class Storyboard(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - storyboard_id: str - status: StoryboardStatus - steps_passed: Annotated[SchemaInt | None, Field(ge=0)] = None - steps_total: Annotated[SchemaInt | None, Field(ge=0)] = None - - -class Payload7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: AnyUrl - previous_status: ComplianceStatus - current_status: ComplianceStatus - headline: str | None = None - tracks: Annotated[ - dict[str, Tracks], Field(description='Map of compliance track id to track status.') - ] - storyboards_passing: Annotated[SchemaInt, Field(ge=0)] - storyboards_total: Annotated[SchemaInt, Field(ge=0)] - storyboards: list[Storyboard] | None = None - - -class RegistryEvent13(AdCPBaseModel): - event_id: Annotated[ - UUID, - Field( - description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' - ), - ] - event_type: Annotated[ - Literal['agent.compliance_changed'], - Field(description='Discriminator. Determines the shape of payload.'), - ] = 'agent.compliance_changed' - entity_type: Annotated[ - Literal['agent'], Field(description='Entity class touched by this event.') - ] = 'agent' - entity_id: Annotated[ - str, - Field( - description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' - ), - ] - payload: Annotated[ - Payload7, - Field( - description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' - ), - ] - actor: Annotated[ - str, - Field( - description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' - ), - ] - created_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' - ), - ] - - -class Payload8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: AnyUrl - role: BadgeRole - verified_specialisms: Annotated[list[str], Field(min_length=1)] - adcp_version: str | None = None - grading_profile: Annotated[ - GradingProfile | None, - Field( - description='Grading profile that produced the badge. Historical events emitted before profile selection may omit this field and are Legacy.' - ), - ] = None - - -class RegistryEvent14(AdCPBaseModel): - event_id: Annotated[ - UUID, - Field( - description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' - ), - ] - event_type: Annotated[ - Literal['agent.verification_earned'], - Field(description='Discriminator. Determines the shape of payload.'), - ] = 'agent.verification_earned' - entity_type: Annotated[ - Literal['agent'], Field(description='Entity class touched by this event.') - ] = 'agent' - entity_id: Annotated[ - str, - Field( - description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' - ), - ] - payload: Annotated[ - Payload8, - Field( - description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' - ), - ] - actor: Annotated[ - str, - Field( - description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' - ), - ] - created_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' - ), - ] - - -class Payload9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: AnyUrl - role: BadgeRole - reason: str - adcp_version: str | None = None - grading_profile: Annotated[ - GradingProfile | None, - Field( - description='Grading profile in force when the badge was lost. Historical events emitted before profile selection may omit this field and are Legacy.' - ), - ] = None - - -class RegistryEvent15(AdCPBaseModel): - event_id: Annotated[ - UUID, - Field( - description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' - ), - ] - event_type: Annotated[ - Literal['agent.verification_lost'], - Field(description='Discriminator. Determines the shape of payload.'), - ] = 'agent.verification_lost' - entity_type: Annotated[ - Literal['agent'], Field(description='Entity class touched by this event.') - ] = 'agent' - entity_id: Annotated[ - str, - Field( - description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' - ), - ] - payload: Annotated[ - Payload9, - Field( - description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' - ), - ] - actor: Annotated[ - str, - Field( - description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' - ), - ] - created_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' - ), - ] - - -class Payload10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Domain | None = None - domain: Annotated[ - Domain | None, - Field( - deprecated=True, - description='Legacy alias for publisher_domain retained for early feed examples.', - ), - ] = None - properties_added: Annotated[SchemaInt | None, Field(ge=0)] = None - properties_removed: Annotated[SchemaInt | None, Field(ge=0)] = None - agents_added: list[AnyUrl] | None = None - agents_removed: list[AnyUrl] | None = None - agent_count: Annotated[SchemaInt | None, Field(ge=0)] = None - property_count: Annotated[SchemaInt | None, Field(ge=0)] = None - collection_count: Annotated[SchemaInt | None, Field(ge=0)] = None - format_count: Annotated[ - SchemaInt | None, - Field(description='Number of top-level formats[] declarations after this revision.', ge=0), - ] = None - placement_count: Annotated[ - SchemaInt | None, - Field( - description='Number of top-level placements[] declarations after this revision.', ge=0 - ), - ] = None - changed_fields: ChangedFields | None = None - discovery_method: str | None = None - manager_domain: str | None = None - source: str | None = None - - -class RegistryEvent16(AdCPBaseModel): - event_id: Annotated[ - UUID, - Field( - description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' - ), - ] - event_type: Annotated[ - Literal['publisher.adagents_discovered'], - Field(description='Discriminator. Determines the shape of payload.'), - ] = 'publisher.adagents_discovered' - entity_type: Annotated[ - Literal['publisher'], Field(description='Entity class touched by this event.') - ] = 'publisher' - entity_id: Annotated[ - str, - Field( - description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' - ), - ] - payload: Annotated[ - Payload10, - Field( - description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' - ), - ] - actor: Annotated[ - str, - Field( - description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' - ), - ] - created_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' - ), - ] - - -class Payload12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - id: Annotated[ - UUID | None, - Field( - description='Registry authorization row id when the event is backed by a materialized effective authorization row.' - ), - ] = None - agent_url: AnyUrl - agent_url_canonical: Annotated[ - str | None, - Field(description='Registry-canonicalized form of agent_url for equality checks.'), - ] = None - publisher_domain: Domain - authorization_type: Annotated[ - AuthorizationType | None, - Field( - description='When present, identifies the adagents.json authorization variant represented by this payload.' - ), - ] = None - authorized_for: str | None = None - property_ids: Annotated[list[property_id.PropertyId] | None, Field(min_length=1)] = None - property_tags: Annotated[list[property_tag.PropertyTag] | None, Field(min_length=1)] = None - properties: Annotated[list[property_1.Property] | None, Field(min_length=1)] = None - publisher_properties: Annotated[ - list[publisher_property_selector.PublisherPropertySelector] | None, Field(min_length=1) - ] = None - property_rid: Annotated[ - UUID | None, - Field( - description='Catalog property_rid for materialized per-property authorization rows. Null for publisher-wide rows.' - ), - ] = None - property_id_slug: Annotated[ - str | None, - Field( - description='Publisher-local property id for materialized per-property authorization rows.' - ), - ] = None - placement_ids: Annotated[list[str] | None, Field(min_length=1)] = None - placement_tags: Annotated[list[str] | None, Field(min_length=1)] = None - collections: Annotated[ - list[collection_selector.CollectionSelector] | None, - Field( - description='Collection constraints from authorized_agents[*].collections. When set, the event does NOT authorize the property unqualified — consumers building a local authorization index MUST scope the grant to these selectors and fail closed when a query carries no collection scope. A selector without collection_ids is a bulk grant for all collections declared at that publisher_domain.', - min_length=1, - ), - ] = None - countries: Countries | None = None - delegation_type: DelegationType | None = None - exclusive: StrictBool | None = None - signing_keys: Annotated[ - list[agent_signing_key.AgentSigningKey] | None, - Field( - description='Publisher-attested signing keys copied from adagents.json when the registry has them. Advisory in feed events; verifiers MUST re-fetch the authoritative publisher artifact before treating keys as a trust anchor.', - min_length=1, - ), - ] = None - effective_from: AwareDatetime | None = None - effective_until: AwareDatetime | None = None - evidence: Evidence | None = None - disputed: StrictBool | None = None - created_by: str | None = None - expires_at: AwareDatetime | None = None - created_at: AwareDatetime | None = None - updated_at: AwareDatetime | None = None - override_applied: StrictBool | None = None - override_reason: str | None = None - - -class RegistryEvent18(AdCPBaseModel): - event_id: Annotated[ - UUID, - Field( - description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' - ), - ] - event_type: Annotated[ - Literal['authorization.granted'], - Field(description='Discriminator. Determines the shape of payload.'), - ] = 'authorization.granted' - entity_type: Annotated[ - Literal['authorization'], Field(description='Entity class touched by this event.') - ] = 'authorization' - entity_id: Annotated[ - str, - Field( - description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' - ), - ] - payload: Annotated[ - Payload12, - Field( - description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' - ), - ] - actor: Annotated[ - str, - Field( - description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' - ), - ] - created_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' - ), - ] - - -class Payload13(Payload12): - pass - - -class RegistryEvent19(AdCPBaseModel): - event_id: Annotated[ - UUID, - Field( - description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' - ), - ] - event_type: Annotated[ - Literal['authorization.revoked'], - Field(description='Discriminator. Determines the shape of payload.'), - ] = 'authorization.revoked' - entity_type: Annotated[ - Literal['authorization'], Field(description='Entity class touched by this event.') - ] = 'authorization' - entity_id: Annotated[ - str, - Field( - description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' - ), - ] - payload: Annotated[ - Payload13, - Field( - description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' - ), - ] - actor: Annotated[ - str, - Field( - description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' - ), - ] - created_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' - ), - ] - - -class Payload14(Payload12): - pass - - -class RegistryEvent20(AdCPBaseModel): - event_id: Annotated[ - UUID, - Field( - description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' - ), - ] - event_type: Annotated[ - Literal['authorization.modified'], - Field(description='Discriminator. Determines the shape of payload.'), - ] = 'authorization.modified' - entity_type: Annotated[ - Literal['authorization'], Field(description='Entity class touched by this event.') - ] = 'authorization' - entity_id: Annotated[ - str, - Field( - description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' - ), - ] - payload: Annotated[ - Payload14, - Field( - description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' - ), - ] - actor: Annotated[ - str, - Field( - description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' - ), - ] - created_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' - ), - ] - - -class PublisherAdagentsPayload(Payload10): - pass - - -class PropertyPayload(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - property_rid: UUID | None = None - classification: Classification | None = None - source: PropertySource | None = None - identifiers: Annotated[list[identifier.Identifier] | None, Field(min_length=1)] = None - publisher_domain: Domain | None = None - property: Annotated[ - property_1.Property | None, - Field(description='Optional full post-change property object when available.'), - ] = None - changed_fields: ChangedFields | None = None - - -class AuthorizationPayload(Payload12): - pass - - -class CompliancePayload(Payload7): - pass - - -class RegistryEvent1(AdCPBaseModel): - event_id: Annotated[ - UUID, - Field( - description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' - ), - ] - event_type: Annotated[ - Literal['property.created'], - Field(description='Discriminator. Determines the shape of payload.'), - ] = 'property.created' - entity_type: Annotated[ - Literal['property'], Field(description='Entity class touched by this event.') - ] = 'property' - entity_id: Annotated[ - str, - Field( - description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' - ), - ] - payload: Annotated[ - PropertyPayload, - Field( - description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' - ), - ] - actor: Annotated[ - str, - Field( - description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' - ), - ] - created_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' - ), - ] - - -class RegistryEvent2(AdCPBaseModel): - event_id: Annotated[ - UUID, - Field( - description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' - ), - ] - event_type: Annotated[ - Literal['property.updated'], - Field(description='Discriminator. Determines the shape of payload.'), - ] = 'property.updated' - entity_type: Annotated[ - Literal['property'], Field(description='Entity class touched by this event.') - ] = 'property' - entity_id: Annotated[ - str, - Field( - description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' - ), - ] - payload: Annotated[ - PropertyPayload, - Field( - description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' - ), - ] - actor: Annotated[ - str, - Field( - description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' - ), - ] - created_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' - ), - ] - - -class Payload2(PropertyPayload): - reactivated_at: AwareDatetime | None = None - property_rid: Any - - -class RegistryEvent5(AdCPBaseModel): - event_id: Annotated[ - UUID, - Field( - description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' - ), - ] - event_type: Annotated[ - Literal['property.reactivated'], - Field(description='Discriminator. Determines the shape of payload.'), - ] = 'property.reactivated' - entity_type: Annotated[ - Literal['property'], Field(description='Entity class touched by this event.') - ] = 'property' - entity_id: Annotated[ - str, - Field( - description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' - ), - ] - payload: Annotated[ - Payload2, - Field( - description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' - ), - ] - actor: Annotated[ - str, - Field( - description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' - ), - ] - created_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' - ), - ] - - -class Payload11(PublisherAdagentsPayload): - properties_added: Annotated[SchemaInt | None, Field(ge=0)] = None - properties_removed: Annotated[SchemaInt | None, Field(ge=0)] = None - agents_added: list[AnyUrl] | None = None - agents_removed: list[AnyUrl] | None = None - changed_fields: ChangedFields | None = None - - -class RegistryEvent17(AdCPBaseModel): - event_id: Annotated[ - UUID, - Field( - description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' - ), - ] - event_type: Annotated[ - Literal['publisher.adagents_changed'], - Field(description='Discriminator. Determines the shape of payload.'), - ] = 'publisher.adagents_changed' - entity_type: Annotated[ - Literal['publisher'], Field(description='Entity class touched by this event.') - ] = 'publisher' - entity_id: Annotated[ - str, - Field( - description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' - ), - ] - payload: Annotated[ - Payload11, - Field( - description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' - ), - ] - actor: Annotated[ - str, - Field( - description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' - ), - ] - created_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' - ), - ] - - -class CollectionPayload(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - collection_rid: UUID | None = None - publisher_domain: Domain | None = None - collection_id: Annotated[ - str | None, - Field(description='Publisher-local collection_id from the authoritative adagents.json.'), - ] = None - name: str | None = None - kind: collection_kind.CollectionKind | None = None - source: PropertySource | None = None - status: Status | None = None - identifiers: Annotated[ - list[CollectionIdentifier] | None, - Field(description='Distribution identifiers that alias this collection across platforms.'), - ] = None - collection: Annotated[ - collection_1.Collection | None, - Field(description='Optional full post-change collection object when available.'), - ] = None - changed_fields: ChangedFields | None = None - - -class RegistryEvent6(AdCPBaseModel): - event_id: Annotated[ - UUID, - Field( - description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' - ), - ] - event_type: Annotated[ - Literal['collection.created'], - Field(description='Discriminator. Determines the shape of payload.'), - ] = 'collection.created' - entity_type: Annotated[ - Literal['collection'], Field(description='Entity class touched by this event.') - ] = 'collection' - entity_id: Annotated[ - str, - Field( - description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' - ), - ] - payload: Annotated[ - CollectionPayload, - Field( - description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' - ), - ] - actor: Annotated[ - str, - Field( - description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' - ), - ] - created_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' - ), - ] - - -class RegistryEvent7(AdCPBaseModel): - event_id: Annotated[ - UUID, - Field( - description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' - ), - ] - event_type: Annotated[ - Literal['collection.updated'], - Field(description='Discriminator. Determines the shape of payload.'), - ] = 'collection.updated' - entity_type: Annotated[ - Literal['collection'], Field(description='Entity class touched by this event.') - ] = 'collection' - entity_id: Annotated[ - str, - Field( - description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' - ), - ] - payload: Annotated[ - CollectionPayload, - Field( - description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' - ), - ] - actor: Annotated[ - str, - Field( - description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' - ), - ] - created_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' - ), - ] - - -class Payload4(CollectionPayload): - status: Literal['removed'] = 'removed' # type: ignore[assignment] - collection_rid: Any - publisher_domain: Any - - -class RegistryEvent9(AdCPBaseModel): - event_id: Annotated[ - UUID, - Field( - description='Stable logical event identifier and feed cursor. UUID v7 is REQUIRED so consumers can apply events in event_id order without relying on producer clocks.' - ), - ] - event_type: Annotated[ - Literal['collection.removed'], - Field(description='Discriminator. Determines the shape of payload.'), - ] = 'collection.removed' - entity_type: Annotated[ - Literal['collection'], Field(description='Entity class touched by this event.') - ] = 'collection' - entity_id: Annotated[ - str, - Field( - description='Primary identifier for the changed entity. For property.* events this is the property_rid; for agent.* events this is the agent_url; for publisher.adagents_changed this is the publisher domain; for authorization.* events this is the authorization row id or a stable agent/publisher composite.' - ), - ] - payload: Annotated[ - Payload4, - Field( - description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' - ), - ] - actor: Annotated[ - str, - Field( - description='Internal producer label for audit and debugging, such as pipeline:crawler or trigger:caa_emit_event. Consumers MUST treat this as informational and not as an authorization principal.' - ), - ] - created_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the registry emitted the event. Advisory only; consumers MUST order and cursor by event_id.' - ), - ] - - -RegistryEvent = Annotated[ - RegistryEvent1 - | RegistryEvent2 - | RegistryEvent3 - | RegistryEvent4 - | RegistryEvent5 - | RegistryEvent6 - | RegistryEvent7 - | RegistryEvent8 - | RegistryEvent9 - | RegistryEvent10 - | RegistryEvent11 - | RegistryEvent12 - | RegistryEvent13 - | RegistryEvent14 - | RegistryEvent15 - | RegistryEvent16 - | RegistryEvent17 - | RegistryEvent18 - | RegistryEvent19 - | RegistryEvent20, - Field( - description='A single cursor-ordered event from the AgenticAdvertising.org registry change feed. Events are denormalized so RegistrySync clients can update local property, agent, publisher, and authorization indexes without re-reading the full registry. The discriminator is event_type; each branch defines the event-specific payload shape. See specs/registry-change-feed.md.', - discriminator='event_type', - title='Registry Event', - ), -] diff --git a/src/adcp/types/generated_poc/core/registry_feed_response.py b/src/adcp/types/generated_poc/core/registry_feed_response.py deleted file mode 100644 index 7ec6e80bf..000000000 --- a/src/adcp/types/generated_poc/core/registry_feed_response.py +++ /dev/null @@ -1,62 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/registry_feed_response.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated -from uuid import UUID - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field, StrictBool - -from . import registry_event - - -class Freshness(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - generated_at: Annotated[ - AwareDatetime, Field(description='Server timestamp when this feed page was generated.') - ] - latest_event_created_at: Annotated[ - AwareDatetime | None, - Field( - description='Newest event creation timestamp currently visible in the feed for the requested type filter. Null when no matching event exists inside retention.' - ), - ] - lag_seconds: Annotated[ - SchemaInt | None, - Field( - description='Seconds between generated_at and latest_event_created_at. Null when no matching event exists.', - ge=0, - ), - ] - retention_days: Annotated[ - SchemaInt, - Field(description='Number of days the registry retains feed cursors and events.', ge=1), - ] - - -class RegistryFeedResponse(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - events: list[registry_event.RegistryEvent] - cursor: Annotated[ - UUID | None, - Field( - description='Pass this value as cursor on the next request to continue polling. Null only when the feed has no events and no prior cursor.' - ), - ] - has_more: Annotated[ - StrictBool, - Field(description='True when more events are immediately available after cursor.'), - ] - freshness: Annotated[ - Freshness, - Field( - description='Consumer-visible feed freshness metadata for the requested type filter.' - ), - ] diff --git a/src/adcp/types/generated_poc/core/reporting_adjustment.py b/src/adcp/types/generated_poc/core/reporting_adjustment.py deleted file mode 100644 index b0bc57966..000000000 --- a/src/adcp/types/generated_poc/core/reporting_adjustment.py +++ /dev/null @@ -1,82 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_adjustment.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - -from . import reporting_control_total - - -class ReasonCode(StrEnum): - invalid_traffic = 'invalid_traffic' - late_attribution = 'late_attribution' - source_correction = 'source_correction' - mapping_correction = 'mapping_correction' - commercial_adjustment = 'commercial_adjustment' - other = 'other' - - -class AccountingPeriod(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - start: AwareDatetime - end: AwareDatetime - - -class ReportingAdjustment(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - reporting_adjustment_id: Annotated[ - str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') - ] - adjusts_reporting_revision_id: Annotated[ - str, - Field( - description='Exact immutable official revision whose billing-purpose evidence/control totals are corrected. External billing systems MAY retain this identifier as supporting evidence.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] - reason_code: Annotated[ - ReasonCode, - Field(description='Stable machine-readable reason for the post-official correction.'), - ] - reason_detail: Annotated[ - str | None, - Field( - description='Human-readable explanation. Treat as untrusted data, never agent or LLM instructions.', - max_length=1024, - min_length=1, - ), - ] = None - accounting_period: Annotated[ - AccountingPeriod, - Field( - description='Period derived from the pinned billing calendar and correction policy. It is evidence metadata only: it does not authorize reopening books or altering invoices.' - ), - ] - control_total_deltas: Annotated[ - list[reporting_control_total.ReportingControlTotal], - Field( - description="Signed deltas to apply to the named official control totals. Names and units use the adjusted revision's pinned report definition. Names MUST be unique.", - min_length=1, - ), - ] - canonical_adjustment_sha256: Annotated[ - str | None, - Field( - description='SHA-256 of the RFC 8785 JCS serialization of this adjustment with canonical_adjustment_sha256 omitted. Reconciled Billing consumers recompute this digest before accepting or rejecting the adjustment.', - pattern='^[A-Fa-f0-9]{64}$', - ), - ] = None - correction_observed_at: AwareDatetime - created_at: AwareDatetime diff --git a/src/adcp/types/generated_poc/core/reporting_adjustment_receipt.py b/src/adcp/types/generated_poc/core/reporting_adjustment_receipt.py deleted file mode 100644 index 4efbdd996..000000000 --- a/src/adcp/types/generated_poc/core/reporting_adjustment_receipt.py +++ /dev/null @@ -1,59 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_adjustment_receipt.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import AwareDatetime, ConfigDict, Field - - -class Status(StrEnum): - accepted = 'accepted' - rejected = 'rejected' - - -class ReportingAdjustmentRejectionCode(ScalarStr): - __slots__ = () - _constraints = {'max_length': 128, 'min_length': 1, 'pattern': '^[A-Z][A-Z0-9_]*$'} - - -class ReportingAdjustmentReceipt(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - reporting_receipt_id: Annotated[ - str, Field(max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$') - ] - reporting_adjustment_id: Annotated[ - str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') - ] - adjusts_reporting_revision_id: Annotated[ - str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') - ] - supersedes_reporting_receipt_id: Annotated[ - str | None, - Field( - description='Optional immutable rejected receipt replaced by this new receipt for the same adjustment. Accepted current receipts are terminal.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] = None - status: Status - observed_adjustment_sha256: Annotated[ - str, - Field( - description='Digest recomputed from the adjustment using its canonical evidence rule.', - pattern='^[A-Fa-f0-9]{64}$', - ), - ] - rejection_codes: Annotated[ - list[ReportingAdjustmentRejectionCode] | None, Field(min_length=1) - ] = None - observed_at: AwareDatetime - received_at: AwareDatetime | None = None diff --git a/src/adcp/types/generated_poc/core/reporting_canonical_content_digest.py b/src/adcp/types/generated_poc/core/reporting_canonical_content_digest.py deleted file mode 100644 index 84d8f7272..000000000 --- a/src/adcp/types/generated_poc/core/reporting_canonical_content_digest.py +++ /dev/null @@ -1,27 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_canonical_content_digest.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - - -class ReportingCanonicalContentDigest(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - regex_engine="python-re", - ) - algorithm: Literal['sha256'] = 'sha256' - value: Annotated[str, Field(pattern='^[A-Fa-f0-9]{64}$')] - canonicalization_id: Annotated[str, Field(max_length=128, min_length=1)] - canonicalization_uri: Annotated[ - AnyUrl, - Field( - description='Location of the exact immutable canonicalization contract. Consumers verify canonicalization_sha256 before applying it.' - ), - ] - canonicalization_sha256: Annotated[str, Field(pattern='^[A-Fa-f0-9]{64}$')] diff --git a/src/adcp/types/generated_poc/core/reporting_canonicalization_contract.py b/src/adcp/types/generated_poc/core/reporting_canonicalization_contract.py deleted file mode 100644 index c33e43797..000000000 --- a/src/adcp/types/generated_poc/core/reporting_canonicalization_contract.py +++ /dev/null @@ -1,94 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_canonicalization_contract.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field - - -class EmptyReport(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - name: Annotated[str, Field(max_length=128, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,128}$')] - purpose: Literal['empty_report'] = 'empty_report' - input_rows: Annotated[list[Any], Field(max_length=0)] - canonical_utf8_base64: Annotated[ - Literal['W10='], Field(description='Base64 of the exact UTF-8 bytes for [].') - ] = 'W10=' - sha256: Literal['4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945'] = '4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945' - - -class OrderingEncoding(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - name: Annotated[str, Field(max_length=128, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,128}$')] - purpose: Literal['ordering_encoding'] = 'ordering_encoding' - input_rows: Annotated[list[dict[str, Any]], Field(min_length=2)] - canonical_utf8_base64: Annotated[ - str, Field(description='Base64 of the exact expected canonical UTF-8 bytes.', min_length=1) - ] - sha256: Annotated[str, Field(pattern='^[A-Fa-f0-9]{64}$')] - - -class AdditionalItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - name: Annotated[str, Field(max_length=128, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,128}$')] - purpose: Literal['additional'] = 'additional' - input_rows: list[dict[str, Any]] - canonical_utf8_base64: Annotated[ - str, Field(description='Base64 of the exact expected canonical UTF-8 bytes.', min_length=1) - ] - sha256: Annotated[str, Field(pattern='^[A-Fa-f0-9]{64}$')] - - -class GoldenVectors(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - empty_report: Annotated[EmptyReport, Field(title='EmptyReportGoldenVector')] - ordering_encoding: Annotated[OrderingEncoding, Field(title='OrderingEncodingGoldenVector')] - additional: list[AdditionalItem] | None = None - - -class ReportingPrimaryKey(ScalarStr): - __slots__ = () - _constraints = {'max_length': 128, 'min_length': 1} - _json_schema_extra = {'title': 'Reporting Primary Key'} - - -class ReportingCanonicalizationContract(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - contract_version: Literal['1.0'] = '1.0' - media_type: Literal['application/vnd.adcp.reporting-canonicalization+json'] = 'application/vnd.adcp.reporting-canonicalization+json' - algorithm: Literal['adcp_jcs_rows_v1'] = 'adcp_jcs_rows_v1' - schema_sha256: Annotated[ - str, - Field( - description='Digest of the exact row schema to which this contract applies.', - pattern='^[A-Fa-f0-9]{64}$', - ), - ] - primary_keys: Annotated[ - list[ReportingPrimaryKey], - Field( - description="Ordered scalar fields used to sort rows and reject duplicate logical rows. This MUST equal the offering's primary_keys.", - min_length=1, - ), - ] - golden_vectors: Annotated[ - GoldenVectors, - Field( - description='Named cross-language conformance cases: exactly one empty_report vector, exactly one ordering_encoding vector, and an optional list of additional vectors.' - ), - ] diff --git a/src/adcp/types/generated_poc/core/reporting_capabilities.py b/src/adcp/types/generated_poc/core/reporting_capabilities.py deleted file mode 100644 index 36f5a4fa2..000000000 --- a/src/adcp/types/generated_poc/core/reporting_capabilities.py +++ /dev/null @@ -1,219 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_capabilities.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictBool - -from ..enums import available_metric, reporting_frequency -from ..enums import vendor_relationship as vendor_relationship_1 -from . import ( - brand_ref, - demographic_reporting_capability, - geo_breakdown_support, - measurement_window, - reporting_delivery_offering_id, - spot_reporting_capability, - vendor_metric_id, -) - - -class DateRangeSupport(StrEnum): - date_range = 'date_range' - lifetime_only = 'lifetime_only' - - -class VendorMetric(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - brand_ref.BrandReference, - Field( - description="Vendor that defines and computes this metric. The vendor's `brand.json` is the discovery anchor for the measurement agent (entry with `type: 'measurement'` in the `agents[]` array); the metric's standard alignment, accreditations, and methodology live at that agent's `get_adcp_capabilities.measurement.metrics[]` and are not duplicated inline here." - ), - ] - metric_id: Annotated[ - vendor_metric_id.VendorMetricId, - Field( - description="Identifier for the metric within the vendor's vocabulary (e.g., `attention_units`, `gco2e_per_impression`, `demographic_reach`)." - ), - ] - vendor_relationship: Annotated[ - vendor_relationship_1.VendorRelationship | None, - Field( - description="Relationship between this product's seller and `vendor` — `first_party` (the seller or its own measurement agent produced the number), `affiliated` (distinct legal entity under common ownership or control: parent/subsidiary, controlling stake, joint venture, disclosed board overlap), or `third_party` (independent vendor). Defined on structural facts only — a commercial dependency does not make a vendor `affiliated`; see the enum. A property of the (seller, vendor) pair, which is why it is declared here rather than on the vendor's catalog. REQUIRED whenever `vendor` resolves to the seller's own `brand.json` or to an entity under common ownership or control with the seller — the self-measurement case is permitted only when it is disclosed, so a seller MUST NOT leave the field absent and let a buyer guess. RECOMMENDED for unrelated third-party vendors. Absence means *undeclared*: buyers MUST treat an absent value as unknown, never as `third_party`, and MAY exclude undeclared rows from reconciliation. This is a relationship disposition, not a trust ranking — quality and independence-of-incentive questions belong to the vendor catalog's `accreditations[]` and `standard_reference`, not to this field. Methodology (sensor, panel, model, instrument) is NOT declared here — it lives at the vendor catalog's `description`, `methodology_url`, `methodology_version`, `standard_reference`, and `accreditations[]`. MAY be echoed on `vendor_metric_values[].vendor_relationship` in delivery." - ), - ] = None - - -class ReportingCapabilities(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - available_reporting_frequencies: Annotated[ - list[reporting_frequency.ReportingFrequency], - Field(description='Supported reporting frequency options', min_length=1), - ] - expected_delay_minutes: Annotated[ - SchemaInt, - Field( - description='Expected delay in minutes before reporting data becomes available (e.g., 240 for 4-hour delay)', - examples=[240, 300, 1440], - ge=0, - ), - ] - timezone: Annotated[ - str, - Field( - description="Timezone for this product's reporting periods. Use 'UTC' or an IANA timezone (e.g., 'America/New_York'). This explicit reporting clock may equal Account.timezone or differ when the upstream platform reports on a separate boundary, so buyers MUST NOT infer it from Account.timezone. It is the reporting timezone for this product's delivery reporting: get_media_buy_delivery start_date, end_date, and daily_breakdown dates are calendar dates in it, and reporting_period boundaries and daily, weekly, or monthly windows fall on its calendar boundaries. Buyers MUST use this value for daily/monthly report alignment.", - examples=['UTC', 'America/New_York', 'Europe/London', 'America/Los_Angeles'], - ), - ] - supports_webhooks: Annotated[ - StrictBool, - Field(description='Whether this product supports webhook-based reporting notifications'), - ] - reporting_delivery_offering_ids: Annotated[ - list[reporting_delivery_offering_id.ReportingDeliveryOfferingId] | None, - Field( - description='Product-scoped subset of get_adcp_capabilities.media_buy.reporting_delivery.offerings[].offering_id that packages using this product can satisfy. This binds seller-wide managed-delivery offerings to product/package eligibility. An empty array explicitly declares no managed offering; absence means product-level applicability is unknown and MUST NOT be inferred from the seller-wide list. Account, seat, credential, or provider constraints may narrow support further during sync_accounts validation.' - ), - ] = None - available_metrics: Annotated[ - list[available_metric.AvailableMetric], - Field( - description="Metrics available in reporting. Impressions and spend are always implicitly included. When a creative format declares reported_metrics, buyers receive the intersection of these product-level metrics and the format's reported_metrics.", - examples=[ - ['impressions', 'spend', 'clicks', 'completed_views'], - ['impressions', 'spend', 'conversions'], - ], - ), - ] - vendor_metrics: Annotated[ - list[VendorMetric] | None, - Field( - description="Vendor-defined metrics this product can report, beyond the closed `available_metrics` enum. Each entry is a pointer (`{ vendor, metric_id }`) into the vendor's metric catalog — the canonical definition (standard alignment, accreditations, methodology, unit, human-readable description) lives at the vendor's `get_adcp_capabilities.measurement.metrics[]`, queried once per vendor when needed. Use this for proprietary metrics like attention scores, emissions, panel-based demographics, or platform-native social metrics not yet in the standard enum. Sellers populate values in delivery via `delivery-metrics.json#/properties/vendor_metric_values`. The metric is identified by the tuple `(vendor, metric_id)`; identifiers are namespaced by the vendor, so the same `metric_id` may mean different things in different vendors' vocabularies. Semantic uniqueness key is `(vendor.domain, vendor.brand_id, metric_id)`; sellers MUST de-duplicate before emission and MUST NOT declare the same vendor metric twice. Buyers MAY treat duplicate `(vendor, metric_id)` rows as a seller-side conformance bug. (JSON Schema `uniqueItems` is not used here because BrandRef carries optional fields whose absence/presence would defeat deep-equal — uniqueness is on the semantic key, enforced at build/validation time on the seller side.) Promotion path: when the industry converges on a metric via a published standard, the spec adds it to the closed `available_metrics` enum and the vendor extensions become historical aliases. The `vendor` MAY resolve to the selling party's own `brand.json` — a seller MAY be its own measurement vendor (DOOH sensor networks, retail-media closed loops, walled gardens) provided it publishes the metric in an `agents[type='measurement']` catalog like any other vendor and declares the relationship via `vendor_relationship`; the catalog contract is not relaxed for first-party measurement." - ), - ] = None - supports_creative_breakdown: Annotated[ - StrictBool | None, - Field( - description='Whether this product supports creative-level metric breakdowns in delivery reporting (by_creative within by_package)' - ), - ] = None - supports_format_breakdown: Annotated[ - StrictBool | None, - Field( - description='Whether this product supports canonical creative-format breakdowns in GET delivery reporting (by_format within by_package, keyed by format_kind). This is independent from supports_creative_breakdown because a seller may expose aggregate format-grain reporting without exposing individual creative performance.' - ), - ] = None - supports_keyword_breakdown: Annotated[ - StrictBool | None, - Field( - description='Whether this product supports keyword-level metric breakdowns in delivery reporting (by_keyword within by_package)' - ), - ] = None - supports_geo_breakdown: Annotated[ - geo_breakdown_support.GeographicBreakdownSupport | None, - Field( - description='Geographic breakdown support for this product. Declares which geo levels and systems are available for by_geo reporting within by_package.' - ), - ] = None - supports_device_type_breakdown: Annotated[ - StrictBool | None, - Field( - description='Whether this product supports device type breakdowns in delivery reporting (by_device_type within by_package)' - ), - ] = None - supports_device_platform_breakdown: Annotated[ - StrictBool | None, - Field( - description='Whether this product supports device platform breakdowns in delivery reporting (by_device_platform within by_package)' - ), - ] = None - supports_audience_breakdown: Annotated[ - StrictBool | None, - Field( - description='Whether this product supports audience segment breakdowns in delivery reporting (by_audience within by_package)' - ), - ] = None - supports_demographic_breakdown: Annotated[ - demographic_reporting_capability.DemographicReportingCapability | None, - Field( - description='Product-scoped demographic breakdown support for by_demographic reporting. Declares reportable age ranges and measurement systems independently from demographic targeting execution.' - ), - ] = None - supports_placement_breakdown: Annotated[ - StrictBool | None, - Field( - description='Whether this product supports placement breakdowns in delivery reporting (by_placement within by_package)' - ), - ] = None - supports_property_breakdown: Annotated[ - StrictBool | None, - Field( - description='Whether this product supports property breakdowns in delivery reporting (by_property within by_package).' - ), - ] = None - supports_collection_breakdown: Annotated[ - StrictBool | None, - Field( - description='Whether this product supports collection breakdowns in delivery reporting (by_collection within by_package).' - ), - ] = None - supports_installment_breakdown: Annotated[ - StrictBool | None, - Field( - description='Whether this product supports installment breakdowns in delivery reporting (by_installment within by_package).' - ), - ] = None - supports_collection_property_breakdown: Annotated[ - StrictBool | None, - Field( - description='Whether this product supports collection × property intersection reporting (by_collection_property within by_package).' - ), - ] = None - supports_installment_property_breakdown: Annotated[ - StrictBool | None, - Field( - description='Whether this product supports installment × property intersection reporting (by_installment_property within by_package).' - ), - ] = None - supports_placement_property_breakdown: Annotated[ - StrictBool | None, - Field( - description='Whether this product supports placement × property intersection reporting (by_placement_property within by_package).' - ), - ] = None - supports_spot_breakdown: Annotated[ - spot_reporting_capability.SpotReportingCapability | None, - Field( - description='Spot-level as-run airing-log support and metrics available at spot grain for broadcast TV, radio, and other scheduled inventory.' - ), - ] = None - date_range_support: Annotated[ - DateRangeSupport, - Field( - description="Whether delivery data can be filtered to arbitrary date ranges. 'date_range' means the platform supports start_date/end_date parameters. 'lifetime_only' means the platform returns campaign lifetime totals and date range parameters are not accepted." - ), - ] - windowed_pull_granularities: Annotated[ - list[reporting_frequency.ReportingFrequency] | None, - Field( - description='Granularities at which this product honors per-window pulls on get_media_buy_delivery (via request `time_granularity` + `include_window_breakdown: true`). Closes the GET-side half of the snapshot/log two-paths-parity contract for data-bearing events: a buyer who missed a webhook fire at any granularity listed here can reconstruct an identical payload by polling. Capability-scoped MUST — sellers MUST honor pulls at any granularity declared here, and MUST return UNSUPPORTED_GRANULARITY for pulls outside the set. Sellers MAY emit higher-frequency webhooks than they expose for pull (common where the webhook is a Kafka tap and historical reads go through a warehouse with coarser granularity); buyers see the gap up front via this capability and treat the webhook as primary for those frequencies. Absent or empty means the product only supports cumulative date-range pulls and full per-window recovery via GET is unavailable — see snapshot-and-log Rule 4.', - examples=[['daily'], ['hourly', 'daily'], ['hourly', 'daily', 'monthly']], - ), - ] = None - measurement_windows: Annotated[ - list[measurement_window.MeasurementWindow] | None, - Field( - description='Measurement maturation stages available for this product. Used by any channel where billing-grade data is produced in phases rather than arriving final on day one. Examples: broadcast/linear TV (Live → C3 → C7 DVR accumulation), DOOH (tentative plays → post-IVT/fraud-check final), digital with IVT filtering (raw → GIVT filtered → SIVT filtered), podcast (7-day downloads → 30-day downloads). Each window defines an accumulation period and expected data availability. When present, delivery reports reference a specific window_id. Sellers whose data is final on first delivery typically omit this.', - min_length=1, - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/reporting_consumer_status.py b/src/adcp/types/generated_poc/core/reporting_consumer_status.py deleted file mode 100644 index b8f56908b..000000000 --- a/src/adcp/types/generated_poc/core/reporting_consumer_status.py +++ /dev/null @@ -1,164 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_consumer_status.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field - - -class Period(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - start: AwareDatetime - end: AwareDatetime - source_timezone: Annotated[str, Field(min_length=1)] - - -class ConsumerStatus(StrEnum): - received = 'received' - obligation_missing = 'obligation_missing' - revision_missing = 'revision_missing' - unreadable = 'unreadable' - content_mismatch = 'content_mismatch' - - -class MismatchCode(StrEnum): - scope_media_buy_missing = 'scope_media_buy_missing' - coverage_short = 'coverage_short' - metric_missing = 'metric_missing' - schema_nonconformant = 'schema_nonconformant' - currency_mismatch = 'currency_mismatch' - period_mismatch = 'period_mismatch' - - -class FailureCode(StrEnum): - access_denied = 'access_denied' - resource_not_found = 'resource_not_found' - integrity_mismatch = 'integrity_mismatch' - reader_incompatible = 'reader_incompatible' - transport_failed = 'transport_failed' - - -class ReportingConsumerStatus(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - reporting_status_id: Annotated[ - str, - Field( - description='Consumer-issued immutable identity for this status statement. Exact retries reuse the ID and content; changed status uses a new ID and supersedes_reporting_status_id.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - supersedes_reporting_status_id: Annotated[ - str | None, - Field( - description="The authenticated consumer's current status leaf replaced by this statement. It must identify the same account, configuration generation, report definition, and period.", - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] = None - delivery_config_id: Annotated[ - str, Field(max_length=64, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,64}$') - ] - delivery_config_version: Annotated[SchemaInt, Field(ge=1)] - report_definition_id: Annotated[ - str, - Field( - description='Exact immutable report definition accepted with the configuration generation, preventing unlike reporting promises from sharing a status chain.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] - period: Annotated[ - Period, - Field( - description='Expected half-open reporting period derived from the accepted configuration generation. This identity works even when the seller omitted the corresponding obligation.' - ), - ] - reporting_obligation_id: Annotated[ - str | None, - Field( - description='Seller-issued obligation identity when one was visible. Omitted when the consumer is reporting a missing obligation.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - reporting_revision_id: Annotated[ - str | None, - Field( - description='Exact revision successfully consumed or found unreadable. Omitted when no required revision was available.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - observed_revision_content_sha256: Annotated[ - str | None, - Field( - description='revision_content_sha256 independently recomputed from the exact consumed Core revision binding. Required for received and content_mismatch, where it proves which exact revision content the consumer read; unlike a Reconciled Billing receipt it carries no materialization evidence, row totals, canonical digest, or billing acceptance.', - pattern='^[A-Fa-f0-9]{64}$', - ), - ] = None - consumer_status: Annotated[ - ConsumerStatus, - Field( - description='received means the exact revision content was successfully consumed; obligation_missing means the independently expected period was absent from the seller ledger; revision_missing means the obligation existed but no required revision was available after expected_at; unreadable means a named revision was advertised but its exact content could not be consumed; content_mismatch means the exact revision content was read but contradicts a fact the accepted configuration generation already fixed, named by the closed mismatch_code. None of these values reconciles billing evidence, and content_mismatch in particular is not a measurement dispute.' - ), - ] - status_as_of: Annotated[ - AwareDatetime, - Field( - description='When the consumer established this status. For received, this is when the named revision first became consumable to this consumer; sellers use it as buyer-attributed arrival evidence rather than silently substituting publication time.' - ), - ] - mismatch_code: Annotated[ - MismatchCode | None, - Field( - description="Closed reason the consumed revision contradicts the accepted configuration generation. Each value is decidable from the obligation, the pinned report definition, and the revision itself, with no reference to either party's own measurement. scope_media_buy_missing: a media buy frozen in the obligation's media_buy_ids denominator is absent from the revision and is not represented by an explicit zero row, so the revision cannot distinguish zero delivery from an omitted buy. coverage_short: the revision covers fewer packages than the obligation's frozen coverage.covered_package_ids claims. metric_missing: a metric named in the pinned report definition's metrics[].name is absent from the revision. schema_nonconformant: rows do not validate against the reporting profile's pinned schema_uri and schema_sha256. currency_mismatch: a value's unit disagrees with the unit the pinned report definition fixed for that metric, or a control total's unit disagrees with the profile-defined unit for that name. period_mismatch: the revision carries a time dimension declared by the pinned grain whose values fall outside the obligation's half-open period. Precedence when more than one applies: schema_nonconformant is used only when the failure is structural validation against the pinned schema; a metric that is simply absent uses metric_missing even when the pinned schema declares it required. Each names a contract fact already fixed by the accepted generation, never a difference of opinion about counts. Agents dispatch on this value, not on prose." - ), - ] = None - failure_code: Annotated[ - FailureCode | None, - Field( - description='Typed reason a named revision was unreadable. Agents dispatch on this value, not prose or provider response bodies.' - ), - ] = None - consumer_commit_ref: Annotated[ - str | None, - Field( - description='Optional opaque, non-secret consumer checkpoint, transaction, or load reference. It is operational evidence, not authorization, a credential, a URL, or instructions; receivers compare or display it as inert text and never dereference or execute it.', - max_length=512, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,512}$', - ), - ] = None - seller_ledger_snapshot_id: Annotated[ - str | None, - Field( - description='Optional seller-issued get_reporting_status snapshot on which this statement was based. It is evidence context, not consumer authority over that snapshot.', - max_length=255, - min_length=1, - ), - ] = None - seller_ledger_as_of: Annotated[ - AwareDatetime | None, - Field( - description='ledger_as_of echoed from seller_ledger_snapshot_id. Present if and only if seller_ledger_snapshot_id is present.' - ), - ] = None - recorded_at: Annotated[ - AwareDatetime | None, - Field(description='When the seller durably recorded this immutable statement.'), - ] = None diff --git a/src/adcp/types/generated_poc/core/reporting_control_total.py b/src/adcp/types/generated_poc/core/reporting_control_total.py deleted file mode 100644 index fdbd69365..000000000 --- a/src/adcp/types/generated_poc/core/reporting_control_total.py +++ /dev/null @@ -1,69 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_control_total.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - - -class ReportingControlTotal1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - name: Annotated[ - str, Field(max_length=128, min_length=1, pattern='^[A-Za-z][A-Za-z0-9_.:-]{0,127}$') - ] - value: Annotated[ - str, - Field( - description='Canonical base-10 integer with no exponent, grouping separator, decimal point, or insignificant leading zeroes.', - pattern='^-?(?:0|[1-9][0-9]*)$', - ), - ] - value_type: Literal['integer'] = 'integer' - unit: Annotated[ - str | None, - Field( - description='Profile-defined unit such as impressions or an ISO 4217 currency code.', - max_length=32, - min_length=1, - ), - ] = None - - -class ReportingControlTotal2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - name: Annotated[ - str, Field(max_length=128, min_length=1, pattern='^[A-Za-z][A-Za-z0-9_.:-]{0,127}$') - ] - value: Annotated[ - str, - Field( - description='Canonical base-10 decimal with no exponent, grouping separator, or insignificant leading zeroes.', - pattern='^-?(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?$', - ), - ] - value_type: Literal['decimal'] = 'decimal' - unit: Annotated[ - str | None, - Field( - description='Profile-defined unit such as impressions or an ISO 4217 currency code.', - max_length=32, - min_length=1, - ), - ] = None - - -ReportingControlTotal = Annotated[ - ReportingControlTotal1 | ReportingControlTotal2, - Field( - description='One profile-defined aggregate used to reconcile a reporting revision without rereading every row. Names and units are defined by the immutable report definition. Values use canonical strings so currency and large integer comparisons are exact across SDKs.', - title='Reporting Control Total', - ), -] diff --git a/src/adcp/types/generated_poc/core/reporting_coverage.py b/src/adcp/types/generated_poc/core/reporting_coverage.py deleted file mode 100644 index b3ada0b0c..000000000 --- a/src/adcp/types/generated_poc/core/reporting_coverage.py +++ /dev/null @@ -1,79 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_coverage.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import AwareDatetime, ConfigDict, Field - - -class Status(StrEnum): - full = 'full' - partial = 'partial' - none = 'none' - unknown = 'unknown' - - -class Reason(StrEnum): - offering_unsupported = 'offering_unsupported' - account_entitlement_unavailable = 'account_entitlement_unavailable' - credential_scope_insufficient = 'credential_scope_insufficient' - provider_limitation = 'provider_limitation' - capability_unknown = 'capability_unknown' - - -class ReportingMediaBuyId(ScalarStr): - __slots__ = () - _constraints = {'min_length': 1} - _json_schema_extra = {'title': 'Reporting Media Buy ID'} - - -class ReportingPackageId(ScalarStr): - __slots__ = () - _constraints = {'min_length': 1} - _json_schema_extra = {'title': 'Reporting Package ID'} - - -class Limitation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - reason: Reason - media_buy_id: ReportingMediaBuyId - package_ids: Annotated[list[ReportingPackageId] | None, Field(min_length=1)] = None - - -class ReportingCoverage(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - status: Status - evaluated_at: AwareDatetime - media_buy_ids: Annotated[ - list[ReportingMediaBuyId], - Field( - description='Exact media-buy denominator, including unsupported and unknown buys. An empty array is an explicitly evaluated zero-buy scope.' - ), - ] - fully_covered_media_buy_ids: list[ReportingMediaBuyId] - partially_covered_media_buy_ids: list[ReportingMediaBuyId] - unsupported_media_buy_ids: list[ReportingMediaBuyId] - unknown_media_buy_ids: list[ReportingMediaBuyId] - package_ids: Annotated[ - list[ReportingPackageId], - Field(description='Exact package denominator for the evaluated media buys.'), - ] - covered_package_ids: list[ReportingPackageId] - unsupported_package_ids: list[ReportingPackageId] - unknown_package_ids: list[ReportingPackageId] - limitations: Annotated[ - list[Limitation], - Field( - description='Stable reasons that some requested scope is not covered by the exact selected offering. These are capability facts, not delivery failures.' - ), - ] diff --git a/src/adcp/types/generated_poc/core/reporting_dataset_share_destination.py b/src/adcp/types/generated_poc/core/reporting_dataset_share_destination.py deleted file mode 100644 index c67138f1b..000000000 --- a/src/adcp/types/generated_poc/core/reporting_dataset_share_destination.py +++ /dev/null @@ -1,85 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_dataset_share_destination.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - - -class ReportingDatasetShareDestination1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - mode: Literal['existing'] = 'existing' - destination_ref: Annotated[ - str, - Field( - description='Seller-issued immutable recipient/destination-generation reference returned by sync_agent_configuration, an earlier sync, or bilateral setup.', - max_length=255, - min_length=1, - ), - ] - - -class Provider(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, Field(pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$') - ] - - -class ReportingCloud(StrEnum): - aws = 'aws' - azure = 'azure' - gcp = 'gcp' - - -class Recipient(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - identity: Annotated[str, Field(max_length=512, min_length=1)] - cloud: ReportingCloud | None = None - region: Annotated[str | None, Field(max_length=128, min_length=1)] = None - - -class ReportingDatasetShareDestination2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - mode: Literal['provision'] = 'provision' - provider: Annotated[ - Provider, - Field(description='Data-sharing platform, such as databricks.com or snowflake.com.'), - ] - access_mode: Annotated[ - str, - Field( - description='Provider access family, such as databricks_to_databricks, open_sharing, or secure_data_sharing.', - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_.-]*$', - ), - ] - recipient: Annotated[ - Recipient, - Field( - description='Intended buyer principal. The identity is interpreted by the provider and access mode; for example, a Databricks sharing identifier, Snowflake organization/account pair, or Open Sharing recipient email. It is an identifier, never a credential.' - ), - ] - - -ReportingDatasetShareDestination = Annotated[ - ReportingDatasetShareDestination1 | ReportingDatasetShareDestination2, - Field( - description="Recipient configuration for a producer-hosted reporting share. The caller either references an existing seller-issued immutable recipient/destination generation or asks the seller to provision one for the named recipient. A destination_ref is owned by the stable authenticated principal's relationship with this seller and may be reused across accounts; each account delivery configuration separately authorizes disclosure of its feed and scope. Changing proof-bound recipient coordinates or the accepted delivery contract produces a new destination_ref. No bearer profile, token, private key, password, or other credential may appear here.", - title='Reporting Dataset Share Destination', - ), -] diff --git a/src/adcp/types/generated_poc/core/reporting_delivery_capabilities.py b/src/adcp/types/generated_poc/core/reporting_delivery_capabilities.py deleted file mode 100644 index 6770cc64f..000000000 --- a/src/adcp/types/generated_poc/core/reporting_delivery_capabilities.py +++ /dev/null @@ -1,169 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_delivery_capabilities.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Any -from pydantic import SerializerFunctionWrapHandler, model_serializer, model_validator - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, ConfigDict, EmailStr, Field, StrictBool - -from . import reporting_delivery_offering, reporting_reliability_statistics - - -class OperationsContact(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - regex_engine="python-re", - ) - url: Annotated[ - AnyUrl | None, - Field( - description='HTTPS page a human uses to open or track a reporting issue, such as a support portal or status page. Same hardened origin shape as the offering document URIs: never an IP literal, userinfo URL, loopback host, AdCP task endpoint, webhook target, or credentialed link.' - ), - ] = None - email: Annotated[ - EmailStr | None, - Field( - description='Monitored operations mailbox for reporting escalations. A role address, not an individual.', - max_length=254, - ), - ] = None - - -class ReportingDeliveryCapabilities(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - supported: Literal[True] - reliable_reporting_version: Annotated[ - Literal['1.0'] | None, - Field( - description='Explicit adoption declaration for the proper-name AdCP 3.2 Reliable Reporting contract. Presence, together with supported: true and the media_buy.reporting_delivery experimental feature gate, is the affirmative machine-readable answer. Absence denotes the earlier experimental managed-reporting shape.' - ), - ] = None - managed_delivery: Annotated[ - StrictBool | None, - Field( - description='Tier flag: this seller supports managed file, dataset-share, or warehouse delivery. Offerings whose method names a delivery pattern require this tier. When false or absent, every offering is API-delivered and Core-only.' - ), - ] = None - reconciled_billing: Annotated[ - StrictBool | None, - Field( - description='Tier flag: this seller supports canonical-digest verification and authenticated consumer receipts for both report materializations and post-official adjustments through receipt_task. Offerings with reconciliation_mode consumer_receipt and billing-grade canonicalization require this tier.' - ), - ] = None - configuration_task: Literal['sync_accounts'] | None = None - status_task: Literal['get_reporting_status'] | None = None - consumer_status_task: Annotated[ - Literal['sync_reporting_status'] | None, - Field( - description='Opt-in consumer-status loop during the published migration window, becoming required Core in the next eligible minor after that window. Buyers call this seller-hosted task to record whether each expected reporting period was received, missing, or unreadable. Buyers expose no reverse endpoint, and the status is not a billing receipt.' - ), - ] = None - revision_content_task: Annotated[ - Literal['get_media_buy_delivery'] | None, - Field( - description='Reliable Reporting exact-content read: callers select reporting_revision_id and receive immutable revision metadata plus authoritative canonical reporting_rows.' - ), - ] = None - receipt_task: Annotated[ - Literal['sync_reporting_receipts'] | None, - Field( - description='Required when reconciled_billing is true: the task consumers call to submit and read back authenticated revision and adjustment receipts.' - ), - ] = None - readiness_notification: Annotated[ - Literal['reporting.delivery_ready'] | None, - Field( - description='Optional managed-delivery-only positive-readiness doorbell. It names a materialization at a destination, so Core sellers MUST omit it.' - ), - ] = None - status_notification: Annotated[ - Literal['reporting.status_changed'] | None, - Field( - description='Optional tier-independent invalidation doorbell for health transitions in either direction, including clock-driven waiting-to-delayed and delayed-to-action_required. Valid for Core: it names no destination. Polling status_task remains the authoritative recovery path whether or not this is offered.' - ), - ] = None - ledger_notification: Annotated[ - Literal['reporting.ledger_changed'] | None, - Field( - description='Optional tier-independent invalidation for every newly committed revision or post-official adjustment, even when health does not change. Receivers repair through get_reporting_status changes_after; polling remains authoritative.' - ), - ] = None - offerings: Annotated[ - list[reporting_delivery_offering.ReportingDeliveryOffering], - Field( - description='Atomic supported feed/profile/schedule/finality/method combinations. offering_id values MUST be unique.', - min_length=1, - ), - ] - automated_recovery_window_seconds: Annotated[ - SchemaInt, - Field( - description='Maximum late interval during which a due obligation may remain delayed while automated recovery continues before action_required.', - ge=0, - ), - ] - status_retention_days: Annotated[ - SchemaInt, - Field( - description='Minimum period for which obligation, revision, and materialization metadata remain queryable.', - ge=1, - ), - ] - consumer_mismatch_escalation_seconds: Annotated[ - SchemaInt | None, - Field( - description="Maximum interval after a CONSUMER_STATUS_MISMATCH issue's opened_at during which the seller may keep that issue at a non-escalated recommended_action. After it, the issue MUST be action_required with a contact_ recommended_action naming the diagnosed responsible_party. Declaring it requires operations_contact so the escalation has a destination. Absence means the seller publishes no escalation commitment; it never means an unbounded one.", - ge=0, - ), - ] = None - operations_contact: Annotated[ - OperationsContact | None, - Field( - description='Optional non-secret human escalation path for reporting issues the protocol cannot resolve. It is display metadata for an operator, not an AdCP endpoint: agents MUST NOT dereference, probe, or send protocol traffic to these values, and they carry no authorization. Required when consumer_mismatch_escalation_seconds is advertised.' - ), - ] = None - reliability_statistics: Annotated[ - list[reporting_reliability_statistics.ReportingReliabilityStatistics] | None, - Field( - description='Optional evidence-scoped observed performance for advertised offerings. offering_id values MUST be unique and name offerings in this capability block.' - ), - ] = None - resource_retention_days: Annotated[ - SchemaInt | None, - Field( - description='Minimum period after publication for which at least one verified exact materialization remains readable to every still-authorized intended consumer.', - ge=1, - ), - ] = None - supports_webhook_activity: StrictBool | None = None - authorization_revocation_seconds: Annotated[ - SchemaInt | None, - Field( - description="Maximum delay after caller/account authorization ends before seller-controlled transport access, provider grants, and write credentials are revoked. It cannot revoke a buyer's access to data already written into a buyer-owned destination.", - ge=0, - ), - ] = None - - @model_validator(mode='after') - def _validate_reporting_tiers(self) -> ReportingDeliveryCapabilities: - if self.reconciled_billing is True and self.managed_delivery is not True: - raise ValueError('reconciled_billing requires managed_delivery') - if self.readiness_notification is not None and self.managed_delivery is not True: - raise ValueError('readiness_notification requires managed_delivery') - if self.receipt_task is not None and self.reconciled_billing is not True: - raise ValueError('receipt_task requires reconciled_billing') - return self - - @model_serializer(mode='wrap') - def _omit_absent_reporting_promises( - self, handler: SerializerFunctionWrapHandler - ) -> dict[str, Any]: - return {key: value for key, value in handler(self).items() if value is not None} diff --git a/src/adcp/types/generated_poc/core/reporting_delivery_config.py b/src/adcp/types/generated_poc/core/reporting_delivery_config.py deleted file mode 100644 index 44ab8c951..000000000 --- a/src/adcp/types/generated_poc/core/reporting_delivery_config.py +++ /dev/null @@ -1,164 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_delivery_config.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from collections.abc import Mapping -from typing import Any -from pydantic import SerializerFunctionWrapHandler, model_serializer, model_validator - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field, StrictBool - -from ..enums import reporting_finality -from . import ( - reporting_coverage, - reporting_delivery_method, - reporting_delivery_offering, - reporting_reconciliation_mode, - reporting_schedule, -) - - -class Scope(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - all_media_buys: Literal[True] | None = None - media_buy_ids: Annotated[ - list[reporting_coverage.ReportingMediaBuyId] | None, Field(min_length=1) - ] = None - - @model_validator(mode='before') - @classmethod - def _select_reporting_scope(cls, value: Any) -> Any: - if not isinstance(value, Mapping): - return value - if 'all_media_buys' in value and 'media_buy_ids' in value: - raise ValueError('reporting scope requires exactly one selector') - if 'all_media_buys' in value and value['all_media_buys'] is not True: - raise ValueError('all_media_buys must be true') - if 'media_buy_ids' in value: - if value['media_buy_ids'] is None: - raise ValueError('media_buy_ids must be a nonempty unique list') - return value - # Preserve the typed empty-scope convenience without mutating its input. - return {**value, 'all_media_buys': True} - - @model_validator(mode='after') - def _unique_reporting_scope(self) -> Scope: - if self.media_buy_ids is not None: - if len(self.media_buy_ids) != len(set(self.media_buy_ids)): - raise ValueError('media_buy_ids must be unique') - return self - - @model_serializer(mode='wrap') - def _serialize_reporting_scope(self, handler: SerializerFunctionWrapHandler) -> dict[str, Any]: - value: dict[str, Any] = handler(self) - for name in ('all_media_buys', 'media_buy_ids'): - if value.get(name) is None: - value.pop(name, None) - return value - - -class CoverageRequirement(StrEnum): - full = 'full' - allow_partial = 'allow_partial' - - -class AuthoritativeParty(StrEnum): - seller = 'seller' - consumer = 'consumer' - - -class ReportingDeliveryConfiguration(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - delivery_config_id: Annotated[ - str, - Field( - description='Caller-selected stable identifier, unique within the authenticated caller and account.', - max_length=64, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,64}$', - ), - ] - delivery_config_version: Annotated[ - SchemaInt, - Field( - description='Caller-selected immutable semantic generation. Increment when feed/profile/scope/finality/schedule/method/destination changes; lifecycle fields may change in place.', - ge=1, - ), - ] - offering_id: Annotated[ - str, - Field( - description='Atomic reporting offering advertised by the seller that binds feed, profile, schedule, finality, and delivery support.', - max_length=128, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,128}$', - ), - ] - active: Annotated[ - StrictBool, - Field( - description='Whether new reporting obligations should use this configuration. Inactive configurations remain visible for historical resolution.' - ), - ] - feed_purpose: reporting_delivery_offering.ReportingFeedPurpose - report_definition_id: Annotated[ - str, - Field( - description='Exact immutable semantic definition selected from the offering. This makes the expected obligation identity independently derivable and prevents attribution, timezone, source-mapping, or restatement-policy drift behind a profile label.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] - reporting_profile: Annotated[ - str, - Field( - description='Versioned semantic profile for the aggregate report, such as media_buy_delivery_v1. It MUST match the selected offering.', - max_length=128, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,128}$', - ), - ] - scope: Annotated[Scope, Field(description='Media buys covered by this configuration.')] - coverage_requirement: Annotated[ - CoverageRequirement, - Field( - description='Whether every package in the resolved media-buy scope must support the exact selected offering. full fails closed when any package is unsupported or unknown. allow_partial permits publication only for the explicitly covered package denominator; every revision and status response still exposes partial coverage and MUST NOT present covered-subset totals as whole-buy totals.' - ), - ] - required_finality: Annotated[ - reporting_finality.ReportingFinality, - Field( - description='Finality the durable path must ultimately provide. Snapshot delivery may still precede an official requirement.' - ), - ] - reconciliation_mode: Annotated[ - reporting_reconciliation_mode.ReportingReconciliationMode, - Field( - description='Whether producer-side delivery evidence is sufficient or the selected consumer must submit an authenticated matching receipt. A seller-authoritative billing feed MUST use consumer_receipt.' - ), - ] - authoritative_party: Annotated[ - AuthoritativeParty | None, - Field( - description="Reserved: which party's count of this feed is authoritative. seller (the default, and the only value any 3.2 seller accepts) means the seller produces every revision and the consumer may only attest to what it consumed. consumer is reserved for the buyer-deposited billing revision task scoped to a later minor; until that task exists sellers MUST reject it with UNSUPPORTED_FEATURE. Reserving the field now keeps a future buyer-basis billing feed additive instead of breaking the billing-feed constraints. See https://github.com/adcontextprotocol/adcp/issues/7440." - ), - ] = AuthoritativeParty.seller - schedule: reporting_schedule.ReportingSchedule - method: reporting_delivery_method.ReportingDeliveryMethod | None = None - revocation_effective_at: Annotated[ - AwareDatetime | None, - Field( - description='Optional requested cutoff for deactivation. No new publication may begin after the applied cutoff; historical access is limited to the contracted recovery window.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/reporting_delivery_config_state.py b/src/adcp/types/generated_poc/core/reporting_delivery_config_state.py deleted file mode 100644 index dfe7b6cd4..000000000 --- a/src/adcp/types/generated_poc/core/reporting_delivery_config_state.py +++ /dev/null @@ -1,91 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_delivery_config_state.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - -from . import reporting_coverage, reporting_delivery_config, reporting_status_issue - - -class Action(StrEnum): - grant_access = 'grant_access' - activate_recipient = 'activate_recipient' - authorize_provider = 'authorize_provider' - repair_access = 'repair_access' - - -class Setup(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - action: Action - message: Annotated[ - str, - Field( - description='Untrusted display text only. SDKs and agents dispatch only on the closed action value and never execute embedded links or instructions.', - max_length=2000, - min_length=1, - ), - ] - url: AnyUrl | None = None - expires_at: AwareDatetime | None = None - - -class ReportingDeliveryConfigLifecycleState(StrEnum): - pending_validation = 'pending_validation' - pending_setup = 'pending_setup' - ready = 'ready' - action_required = 'action_required' - inactive = 'inactive' - - -class ReportingDeliveryConfigurationState(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - configuration: reporting_delivery_config.ReportingDeliveryConfiguration - state: ReportingDeliveryConfigLifecycleState - destination_ref: Annotated[ - str | None, - Field( - description='Seller-issued immutable destination-generation reference. It is caller-scoped and reusable across separately authorized account configurations; it is not itself account authority or a bearer grant. Present when and only when the configuration selects a managed-delivery offering; a Core (API-delivered) configuration becomes ready with no destination at all.', - max_length=255, - min_length=1, - ), - ] = None - validated_at: AwareDatetime | None = None - activated_at: AwareDatetime | None = None - deactivated_at: AwareDatetime | None = None - publication_stopped_at: Annotated[ - AwareDatetime | None, - Field( - description='Applied schedule boundary at or after deactivation. No obligation whose period starts at or after this cutoff is created; earlier obligations remain owed through their SLA and recovery lifecycle.' - ), - ] = None - seller_managed_access_ends_at: Annotated[ - AwareDatetime | None, - Field( - description='End of historical access to a producer-hosted share/resource for a still-authorized principal after voluntary deactivation. Inapplicable to data already written into a buyer-owned destination.' - ), - ] = None - current_coverage: Annotated[ - reporting_coverage.ReportingCoverage | None, - Field( - description='Current effective product/package coverage for the selected offering and resolved account. This setup-time view may change as media buys or provider capabilities change; each period obligation later freezes its own authoritative coverage.' - ), - ] = None - setup: Annotated[ - Setup | None, - Field( - description='Secret-free next step when provider-side authorization or recipient activation cannot be completed automatically.' - ), - ] = None - issues: Annotated[ - list[reporting_status_issue.ReportingStatusIssue] | None, Field(min_length=1) - ] = None diff --git a/src/adcp/types/generated_poc/core/reporting_delivery_method.py b/src/adcp/types/generated_poc/core/reporting_delivery_method.py deleted file mode 100644 index ab901db2a..000000000 --- a/src/adcp/types/generated_poc/core/reporting_delivery_method.py +++ /dev/null @@ -1,103 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_delivery_method.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from . import reporting_dataset_share_destination, reporting_write_destination - - -class Format(StrEnum): - jsonl = 'jsonl' - csv = 'csv' - parquet = 'parquet' - avro = 'avro' - orc = 'orc' - - -class ReportingOrchestration(StrEnum): - producer_managed = 'producer_managed' - consumer_managed = 'consumer_managed' - - -class ReportingDeliveryMethod1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - pattern: Annotated[ - Literal['file_transfer'], - Field( - description='Immutable file/object publication with a manifest-last commit boundary.' - ), - ] = 'file_transfer' - transport: Annotated[ - str, - Field( - description='Storage transport such as s3, gcs, azure_blob, or sftp.', - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_.-]*$', - ), - ] - orchestration: ReportingOrchestration - destination: reporting_write_destination.ReportingWriteDestination - format: Annotated[Format, Field(description='Physical file format.')] - - -class ReportingDeliveryMethod2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - pattern: Annotated[ - Literal['dataset_share'], - Field( - description="Producer-hosted relation or share read through the intended recipient's access path." - ), - ] = 'dataset_share' - transport: Annotated[ - str, - Field( - description='Sharing transport such as delta_sharing, snowflake_secure_sharing, or bigquery_authorized_view.', - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_.-]*$', - ), - ] - orchestration: ReportingOrchestration - destination: reporting_dataset_share_destination.ReportingDatasetShareDestination - - -class ReportingDeliveryMethod3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - pattern: Annotated[ - Literal['warehouse_materialization'], - Field(description='Exact-revision publication into a warehouse relation or partition.'), - ] = 'warehouse_materialization' - transport: Annotated[ - str, - Field( - description='Warehouse or transfer transport such as bigquery, snowflake, databricks_sql, or gam_bigquery_transfer.', - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_.-]*$', - ), - ] - orchestration: ReportingOrchestration - destination: reporting_write_destination.ReportingWriteDestination - - -ReportingDeliveryMethod = Annotated[ - ReportingDeliveryMethod1 | ReportingDeliveryMethod2 | ReportingDeliveryMethod3, - Field( - description='Provider-neutral durable reporting delivery method. The caller may request protocol-managed provisioning or reuse an existing seller-issued binding. Transport names are open so new platforms do not require an AdCP enum change. Credentials, bearer profiles, and private keys MUST NOT appear. Sellers implementing this schema MUST advertise media_buy.reporting_delivery in experimental_features.', - title='Reporting Delivery Method', - ), -] diff --git a/src/adcp/types/generated_poc/core/reporting_delivery_offering.py b/src/adcp/types/generated_poc/core/reporting_delivery_offering.py deleted file mode 100644 index 02ad6a866..000000000 --- a/src/adcp/types/generated_poc/core/reporting_delivery_offering.py +++ /dev/null @@ -1,221 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_delivery_offering.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - -from ..enums import reporting_finality -from . import ( - reporting_canonicalization_contract, - reporting_dataset_share_destination, - reporting_delivery_method, - reporting_reconciliation_mode, - reporting_resource, - reporting_schedule_offering, -) - - -class ReportingProfile(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - regex_engine="python-re", - ) - id: Annotated[str, Field(max_length=128, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,128}$')] - version: Annotated[str, Field(max_length=64, min_length=1)] - schema_uri: Annotated[ - AnyUrl, - Field( - description='Authenticated seller/provider or AdCP-registry HTTPS origin only; never an IP literal, userinfo URL, redirect target, or mutable validation authority.' - ), - ] - schema_sha256: Annotated[ - str, - Field( - description='Digest of the exact schema bytes. SDKs verify this before parsing and cache by digest.', - pattern='^[A-Fa-f0-9]{64}$', - ), - ] - schema_dialect: Annotated[ - Literal['https://json-schema.org/draft/2020-12/schema'], - Field( - description="Closed SDK-bundled dialect. The SDK never resolves a metaschema over the network, and the fetched document's $schema MUST equal this value." - ), - ] = 'https://json-schema.org/draft/2020-12/schema' - schema_ref_policy: Annotated[ - Literal['local_fragment_only'], - Field( - description='The fetched schema is a self-contained bundle. Every $ref is a local # fragment; remote and relative-document dependencies are forbidden.' - ), - ] = 'local_fragment_only' - grain: Annotated[ - str, - Field( - description='Stable description of what one logical row represents.', - max_length=128, - min_length=1, - ), - ] - primary_keys: Annotated[ - list[reporting_canonicalization_contract.ReportingPrimaryKey], Field(min_length=1) - ] - canonicalization_id: Annotated[ - str | None, - Field( - description='Rules for stable logical row ordering, value encoding, nulls, and schema used by canonical_content_digest.', - max_length=128, - min_length=1, - ), - ] = None - canonicalization_contract_version: Literal['1.0'] = '1.0' - canonicalization_media_type: Literal['application/vnd.adcp.reporting-canonicalization+json'] = ( - 'application/vnd.adcp.reporting-canonicalization+json' - ) - canonicalization_uri: Annotated[ - AnyUrl | None, - Field( - description='Retrievable exact canonicalization contract on the authenticated seller/provider or AdCP-registry origin. SDKs apply the same bounded, redirect-free SSRF controls as schema_uri and verify canonicalization_sha256 before use.' - ), - ] = None - canonicalization_sha256: Annotated[ - str | None, - Field( - description='Digest of the exact canonicalization contract identified by canonicalization_id.', - pattern='^[A-Fa-f0-9]{64}$', - ), - ] = None - - -class DestinationMode(StrEnum): - provision = 'provision' - existing = 'existing' - - -class Provider(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, Field(pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$') - ] - - -class Format(StrEnum): - jsonl = 'jsonl' - csv = 'csv' - parquet = 'parquet' - avro = 'avro' - orc = 'orc' - - -class ProducerIdentity(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - provider: Provider - identity: Annotated[str, Field(max_length=512, min_length=1)] - cloud: reporting_dataset_share_destination.ReportingCloud | None = None - region: Annotated[str | None, Field(max_length=128, min_length=1)] = None - - -class ReportingFeedPurpose(StrEnum): - pacing = 'pacing' - analytics = 'analytics' - billing = 'billing' - - -class ReportingDeliveryPattern(StrEnum): - file_transfer = 'file_transfer' - dataset_share = 'dataset_share' - warehouse_materialization = 'warehouse_materialization' - - -class Method(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - pattern: ReportingDeliveryPattern - transport: Annotated[str, Field(max_length=64, min_length=1, pattern='^[a-z][a-z0-9_.-]*$')] - orchestration: reporting_delivery_method.ReportingOrchestration - destination_modes: Annotated[list[DestinationMode], Field(min_length=1)] - provider: Provider | None = None - format: Format | None = None - access_mode: Annotated[ - str | None, Field(max_length=64, min_length=1, pattern='^[a-z][a-z0-9_.-]*$') - ] = None - producer_identity: Annotated[ - ProducerIdentity | None, - Field( - description='Seller principal a buyer grants access to for this exact buyer-hosted destination offering.' - ), - ] = None - reader_compatibility: list[reporting_resource.ReportingReaderCompatibilityItem] | None = None - - -class ReportingDeliveryOffering(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - regex_engine="python-re", - ) - offering_id: Annotated[ - str, Field(max_length=128, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,128}$') - ] - feed_purpose: ReportingFeedPurpose - report_definition_id: Annotated[ - str, - Field( - description='Immutable semantic definition for metric, grain, attribution, action-report-time, timezone/calendar, source/API mapping, and restatement/finality policy. Configurations and revisions MUST echo this exact value.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] - report_definition_uri: Annotated[ - AnyUrl, - Field( - description='Retrievable immutable reporting-report-definition.json document on the authenticated seller/provider or AdCP-registry origin.' - ), - ] - report_definition_sha256: Annotated[ - str, - Field( - description='Digest of the exact report-definition bytes. SDKs verify this before parsing and cache by digest.', - pattern='^[A-Fa-f0-9]{64}$', - ), - ] - reporting_profile: Annotated[ - ReportingProfile, - Field( - description='Machine-readable semantic and validation contract for delivered rows. The canonicalization_* fields describe the external-materialization canonical-digest contract and are required only for offerings under the reconciled_billing tier; Core and managed-delivery offerings omit those fields but every Core revision still carries the fixed RFC 8785/JCS revision-binding digest.' - ), - ] - schedule: Annotated[ - reporting_schedule_offering.ReportingScheduleOffering, - Field( - description='Period and availability SLA this offering can honor. For example, PT1H with snapshot finality explicitly advertises hourly provisional snapshots; a separate P1D official offering advertises daily finalized reporting.' - ), - ] - supported_finality: Annotated[ - list[reporting_finality.ReportingFinality], - Field( - description='Finality classes available under this exact report definition, schedule, and delivery method. snapshot is an explicit provisional capability, not inferred from poll frequency. Use separate atomic offerings when snapshot and official schedules or methods differ.', - min_length=1, - ), - ] - reconciliation_mode: Annotated[ - reporting_reconciliation_mode.ReportingReconciliationMode, - Field( - description='Receipt contract included in this atomic offering. Billing offerings MUST require consumer_receipt.' - ), - ] - method: Annotated[ - Method | None, - Field( - description='Managed delivery method for this offering. Omit for a Core (API-delivered) offering: rows flow through existing get_media_buy_delivery and reporting_webhook transports and no destination is involved. Present only when the seller advertises managed_delivery.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/reporting_delivery_offering_id.py b/src/adcp/types/generated_poc/core/reporting_delivery_offering_id.py deleted file mode 100644 index ea8629836..000000000 --- a/src/adcp/types/generated_poc/core/reporting_delivery_offering_id.py +++ /dev/null @@ -1,16 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_delivery_offering_id.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from adcp.types._scalar import ScalarStr - - -class ReportingDeliveryOfferingId(ScalarStr): - __slots__ = () - _constraints = {'max_length': 128, 'min_length': 1, 'pattern': '^[A-Za-z0-9_.:-]{1,128}$'} - _json_schema_extra = { - 'description': 'Identifier of one seller-advertised managed reporting-delivery offering, matching get_adcp_capabilities.media_buy.reporting_delivery.offerings[].offering_id. Shared by the legacy and canonical product reporting capabilities.', - 'title': 'Reporting Delivery Offering ID', - } diff --git a/src/adcp/types/generated_poc/core/reporting_delivery_ready_webhook.py b/src/adcp/types/generated_poc/core/reporting_delivery_ready_webhook.py deleted file mode 100644 index 5aa2ee923..000000000 --- a/src/adcp/types/generated_poc/core/reporting_delivery_ready_webhook.py +++ /dev/null @@ -1,63 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_delivery_ready_webhook.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field - -from ..enums import reporting_finality -from . import reporting_delivery_offering - - -class Readiness(StrEnum): - available = 'available' - delivered = 'delivered' - - -class ReportingDeliveryReadyWebhook(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - idempotency_key: Annotated[ - str, - Field( - description='Stable across transport retries of this fire; new for a later re-emission.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - notification_id: Annotated[ - str, - Field( - description='Stable for this logical materialization-ready event across re-emissions.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] - notification_type: Literal['reporting.delivery_ready'] = 'reporting.delivery_ready' - fired_at: AwareDatetime - subscriber_id: Annotated[ - str, Field(max_length=64, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,64}$') - ] - account_id: Annotated[str, Field(min_length=1)] - delivery_config_id: Annotated[ - str, Field(max_length=64, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,64}$') - ] - delivery_config_version: Annotated[SchemaInt, Field(ge=1)] - reporting_revision_id: Annotated[ - str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') - ] - reporting_materialization_id: Annotated[ - str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') - ] - readiness: Readiness - finality: reporting_finality.ReportingFinality - data_through: AwareDatetime | None - feed_purpose: reporting_delivery_offering.ReportingFeedPurpose diff --git a/src/adcp/types/generated_poc/core/reporting_file_compression.py b/src/adcp/types/generated_poc/core/reporting_file_compression.py deleted file mode 100644 index c36473583..000000000 --- a/src/adcp/types/generated_poc/core/reporting_file_compression.py +++ /dev/null @@ -1,14 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_file_compression.json -# timestamp: 2026-09-01T19:37:00+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class ReportingFileCompression(StrEnum): - none = 'none' - gzip = 'gzip' - zstd = 'zstd' - snappy = 'snappy' diff --git a/src/adcp/types/generated_poc/core/reporting_file_entry.py b/src/adcp/types/generated_poc/core/reporting_file_entry.py deleted file mode 100644 index e8e092cc5..000000000 --- a/src/adcp/types/generated_poc/core/reporting_file_entry.py +++ /dev/null @@ -1,24 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_file_entry.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field - -from . import reporting_file_object_ref, reporting_native_version_ref - - -class ReportingFileEntry(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - object_ref: reporting_file_object_ref.ReportingFileObjectReference - native_version_ref: reporting_native_version_ref.ReportingNativeVersionReference | None = None - size_bytes: Annotated[SchemaInt, Field(ge=0)] - sha256: Annotated[str, Field(pattern='^[A-Fa-f0-9]{64}$')] - row_count: Annotated[SchemaInt, Field(ge=0)] - partition: Annotated[dict[str, str] | None, Field(max_length=32)] = None diff --git a/src/adcp/types/generated_poc/core/reporting_file_manifest.py b/src/adcp/types/generated_poc/core/reporting_file_manifest.py deleted file mode 100644 index 6bc62f075..000000000 --- a/src/adcp/types/generated_poc/core/reporting_file_manifest.py +++ /dev/null @@ -1,55 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_file_manifest.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field - -from . import reporting_control_total, reporting_file_compression, reporting_file_entry - - -class Period(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - start: AwareDatetime - end: AwareDatetime - source_timezone: Annotated[str, Field(min_length=1)] - - -class Format(StrEnum): - jsonl = 'jsonl' - csv = 'csv' - parquet = 'parquet' - avro = 'avro' - orc = 'orc' - - -class ReportingFileManifest(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - manifest_version: Literal['1.0'] = '1.0' - complete: Literal[True] - reporting_revision_id: Annotated[ - str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') - ] - reporting_obligation_id: Annotated[ - str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') - ] - reporting_materialization_id: Annotated[ - str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') - ] - period: Period - format: Format - compression: reporting_file_compression.ReportingFileCompression - files: Annotated[list[reporting_file_entry.ReportingFileEntry], Field(min_length=1)] - total_size_bytes: Annotated[SchemaInt, Field(ge=0)] - row_count: Annotated[SchemaInt, Field(ge=0)] - control_totals: list[reporting_control_total.ReportingControlTotal] - created_at: AwareDatetime diff --git a/src/adcp/types/generated_poc/core/reporting_file_object_ref.py b/src/adcp/types/generated_poc/core/reporting_file_object_ref.py deleted file mode 100644 index 0f87cf664..000000000 --- a/src/adcp/types/generated_poc/core/reporting_file_object_ref.py +++ /dev/null @@ -1,16 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_file_object_ref.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from adcp.types._scalar import ScalarStr - - -class ReportingFileObjectReference(ScalarStr): - __slots__ = () - _constraints = {'max_length': 1024, 'min_length': 1} - _json_schema_extra = { - 'description': 'Credential-free, destination-relative reference to one reporting data object. For an object store, this is the decoded object key relative to the configured destination, not an absolute URI and not a URI with a version query parameter. Providers URI-encode this value only when constructing their own storage request. The 1024-character limit accommodates an S3 object key of up to 1024 UTF-8 bytes because every Unicode character occupies at least one UTF-8 byte; JSON Schema maxLength counts characters rather than bytes.', - 'title': 'Reporting File Object Reference', - } diff --git a/src/adcp/types/generated_poc/core/reporting_ledger_changed_webhook.py b/src/adcp/types/generated_poc/core/reporting_ledger_changed_webhook.py deleted file mode 100644 index 8976a6482..000000000 --- a/src/adcp/types/generated_poc/core/reporting_ledger_changed_webhook.py +++ /dev/null @@ -1,64 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_ledger_changed_webhook.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - -from ..enums import reporting_finality -from . import ext as ext_1 - - -class ChangeKind(StrEnum): - revision_published = 'revision_published' - adjustment_published = 'adjustment_published' - - -class ReportingLedgerChangedWebhook(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - idempotency_key: Annotated[ - str, - Field( - description='Random per distinct fire and stable across transport retries, scoped to the authenticated sender.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - notification_id: Annotated[ - str, - Field( - description='Stable per committed ledger record; re-emissions reuse it.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] - notification_type: Literal['reporting.ledger_changed'] = 'reporting.ledger_changed' - fired_at: AwareDatetime - subscriber_id: Annotated[ - str, Field(max_length=64, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,64}$') - ] - account_id: Annotated[str, Field(min_length=1)] - change_kind: ChangeKind - reporting_revision_id: Annotated[ - str | None, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') - ] = None - supersedes_reporting_revision_id: Annotated[ - str | None, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') - ] = None - finality: reporting_finality.ReportingFinality | None = None - reporting_adjustment_id: Annotated[ - str | None, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') - ] = None - adjusts_reporting_revision_id: Annotated[ - str | None, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/reporting_materialization.py b/src/adcp/types/generated_poc/core/reporting_materialization.py deleted file mode 100644 index 995f13884..000000000 --- a/src/adcp/types/generated_poc/core/reporting_materialization.py +++ /dev/null @@ -1,94 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_materialization.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field - -from . import reporting_delivery_offering, reporting_resource, reporting_verification - - -class Method(StrEnum): - file_transfer = 'file_transfer' - dataset_share = 'dataset_share' - warehouse_materialization = 'warehouse_materialization' - - -class Status(StrEnum): - pending = 'pending' - available = 'available' - delivered = 'delivered' - failed = 'failed' - - -class ReportingMaterialization(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - reporting_materialization_id: Annotated[ - str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') - ] - reporting_revision_id: Annotated[ - str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') - ] - reporting_obligation_id: Annotated[ - str, - Field( - description='Destination-specific obligation this materialization attempts to satisfy.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] - delivery_config_id: Annotated[ - str, - Field( - description='Durable configuration that requested this materialization.', - max_length=64, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,64}$', - ), - ] - delivery_config_version: Annotated[SchemaInt, Field(ge=1)] - destination_ref: Annotated[ - str, - Field( - description='Immutable caller-owned destination generation selected by the account-authorized obligation. It may be reused by the same caller across other independently authorized accounts.', - max_length=255, - min_length=1, - ), - ] - feed_purpose: reporting_delivery_offering.ReportingFeedPurpose - method: Method - transport: Annotated[ - str | None, Field(max_length=64, min_length=1, pattern='^[a-z][a-z0-9_.-]*$') - ] = None - attempt: Annotated[SchemaInt, Field(ge=1)] - status: Annotated[ - Status, - Field( - description='Lifecycle of this attempt. pending may transition once to available, delivered, or failed; terminal evidence is immutable. Staleness is evaluated in get_reporting_status health, not stored as a materialization state.' - ), - ] - ready_at: Annotated[ - AwareDatetime | None, - Field(description='When consumer-path or destination verification completed.'), - ] = None - failed_at: AwareDatetime | None = None - failure_code: Annotated[ - str | None, - Field( - description='Stable safe failure classification. MUST NOT include credentials or provider response bodies.', - max_length=128, - min_length=1, - pattern='^[A-Z][A-Z0-9_]*$', - ), - ] = None - resource: reporting_resource.ReportingResource | None = None - verification: reporting_verification.ReportingVerification | None = None - created_at: AwareDatetime diff --git a/src/adcp/types/generated_poc/core/reporting_native_version_ref.py b/src/adcp/types/generated_poc/core/reporting_native_version_ref.py deleted file mode 100644 index cc4aa8921..000000000 --- a/src/adcp/types/generated_poc/core/reporting_native_version_ref.py +++ /dev/null @@ -1,16 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_native_version_ref.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from adcp.types._scalar import ScalarStr - - -class ReportingNativeVersionReference(ScalarStr): - __slots__ = () - _constraints = {'max_length': 1024, 'min_length': 1} - _json_schema_extra = { - 'description': 'Credential-free, decoded provider-native immutable version reference, such as an S3 VersionId, GCS generation, table version, transaction, snapshot, manifest generation, job, or run. This is not URI-encoded or concatenated into an object_ref; URI-encode it only when constructing a provider request. The 1024-character limit accommodates a provider value of up to 1024 UTF-8 bytes because every Unicode character occupies at least one UTF-8 byte; JSON Schema maxLength counts characters rather than bytes.', - 'title': 'Reporting Native Version Reference', - } diff --git a/src/adcp/types/generated_poc/core/reporting_obligation.py b/src/adcp/types/generated_poc/core/reporting_obligation.py deleted file mode 100644 index bc2a9fa25..000000000 --- a/src/adcp/types/generated_poc/core/reporting_obligation.py +++ /dev/null @@ -1,195 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_obligation.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field - -from ..enums import reporting_finality, reporting_health -from . import ( - reporting_coverage, - reporting_delivery_offering, - reporting_reconciliation_mode, - reporting_schedule, - reporting_status_issue, -) - - -class Period(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - start: AwareDatetime - end: AwareDatetime - source_timezone: Annotated[str, Field(min_length=1)] - - -class ReconciliationStatus(StrEnum): - not_required = 'not_required' - pending = 'pending' - accepted = 'accepted' - rejected = 'rejected' - - -class ProductionStatus(StrEnum): - not_due = 'not_due' - pending = 'pending' - published = 'published' - failed = 'failed' - - -class ReportingObligation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - reporting_obligation_id: Annotated[ - str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') - ] - delivery_config_id: Annotated[ - str, Field(max_length=64, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,64}$') - ] - delivery_config_version: Annotated[SchemaInt, Field(ge=1)] - report_definition_id: Annotated[ - str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') - ] - feed_purpose: reporting_delivery_offering.ReportingFeedPurpose - reporting_profile: Annotated[str, Field(max_length=128, min_length=1)] - account_id: Annotated[str, Field(min_length=1)] - media_buy_ids: Annotated[ - list[reporting_coverage.ReportingMediaBuyId], - Field( - description='Exact frozen media-buy denominator resolved for this period, including buys with zero rows. An empty array is the definitive zero-buy set; omission is never used to mean all, empty, or unknown.' - ), - ] - scope_resolved_at: Annotated[ - AwareDatetime, - Field( - description='Instant at which the configured scope was resolved and frozen for this obligation. For all_media_buys, include every caller-authorized account media buy whose effective flight overlaps the half-open period and was known by this cutoff. Later-created or backdated buys do not rewrite this obligation.' - ), - ] - coverage: Annotated[ - reporting_coverage.ReportingCoverage, - Field( - description='Immutable effective coverage of the exact selected offering at this period boundary. Delivery health is evaluated separately over the covered denominator.' - ), - ] - period: Period - expected_at: AwareDatetime - schedule: Annotated[ - reporting_schedule.ReportingSchedule, - Field(description='Resolved immutable schedule generation that created this obligation.'), - ] - destination_ref: Annotated[ - str | None, - Field( - description='Immutable caller-owned destination generation selected by this account-authorized obligation. The account/configuration join—not possession of this reusable reference—authorizes disclosure. Present when and only when the obligation’s configuration selects a managed-delivery offering; Core (API-delivered) obligations omit every destination and materialization field.', - max_length=255, - min_length=1, - ), - ] = None - required_finality: reporting_finality.ReportingFinality - reconciliation_mode: reporting_reconciliation_mode.ReportingReconciliationMode - reconciliation_status: Annotated[ - ReconciliationStatus, - Field( - description='Consumer agreement state for the current required revision. A later superseding revision returns a receipt-required obligation to pending until that revision is accepted.' - ), - ] - health: reporting_health.ReportingHealth - production_status: Annotated[ - ProductionStatus, - Field( - description='Whether any revision has been produced for this obligation. published includes zero-row revisions.' - ), - ] - revision_count: Annotated[ - SchemaInt, - Field( - description='Number of revision records for this obligation in the consistent ledger snapshot.', - ge=0, - ), - ] - consumer_status_count: Annotated[ - SchemaInt | None, - Field( - description='Complete number of immutable authenticated consumer status statements associated with this obligation in the ledger snapshot, whether originally joined by reporting_obligation_id or by the exact configuration-generation, report-definition, and period key before the obligation existed. Core status-sync history is counted independently from Reconciled Billing receipts.', - ge=0, - ), - ] = None - current_consumer_status_id: Annotated[ - str | None, - Field( - description='Current unsuperseded consumer status statement associated with this obligation by seller ID or its exact logical period key. Omitted when consumer_status_count is zero.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] = None - adjustment_count: Annotated[ - SchemaInt | None, - Field( - description='Number of immutable post-official reporting adjustment records for this obligation in the consistent ledger snapshot.', - ge=0, - ), - ] = None - materialization_count: Annotated[ - SchemaInt | None, - Field( - description="Number of materialization records for this obligation's revisions in the consistent ledger snapshot. Present iff the obligation is managed-delivery (destination_ref present).", - ge=0, - ), - ] = None - successful_materialization_count: Annotated[ - SchemaInt | None, - Field( - description='Number of available/delivered verified materializations in the consistent ledger snapshot. Present iff the obligation is managed-delivery (destination_ref present).', - ge=0, - ), - ] = None - receipt_count: Annotated[ - SchemaInt | None, - Field( - description='Complete number of authenticated receipts associated with this obligation in the ledger snapshot. Present iff reconciliation_mode is consumer_receipt.', - ge=0, - ), - ] = None - accepted_receipt_count: Annotated[ - SchemaInt | None, - Field( - description='Number of accepted receipts. At most one current accepted receipt per consumer and revision contributes to reconciliation_status. Present iff reconciliation_mode is consumer_receipt.', - ge=0, - ), - ] = None - adjustment_receipt_count: Annotated[ - SchemaInt | None, - Field( - description="Number of authenticated receipts for adjustments targeting this obligation's official revision. Reconciled Billing only.", - ge=0, - ), - ] = None - accepted_adjustment_receipt_count: Annotated[ - SchemaInt | None, - Field( - description='Number of accepted adjustment receipts. Reconciled Billing buyers do not post an adjustment until their exact digest is accepted.', - ge=0, - ), - ] = None - pending_adjustment_count: Annotated[ - SchemaInt | None, - Field( - description='Number of applicable adjustments without an accepted receipt. Reconciled Billing complete/healthy requires zero.', - ge=0, - ), - ] = None - issues: list[reporting_status_issue.ReportingStatusIssue] - resource_retained_until: Annotated[ - AwareDatetime | None, - Field( - description='Minimum time through which at least one verified materialization for a completed obligation remains readable. Managed-delivery only; Core revisions are retained per status_retention_days and readable through the existing API transports.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/reporting_receipt.py b/src/adcp/types/generated_poc/core/reporting_receipt.py deleted file mode 100644 index 4deba8528..000000000 --- a/src/adcp/types/generated_poc/core/reporting_receipt.py +++ /dev/null @@ -1,78 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_receipt.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarStr -from pydantic import AwareDatetime, ConfigDict, Field - -from . import ( - reporting_canonical_content_digest, - reporting_control_total, - reporting_native_version_ref, - reporting_verification_profile, -) - - -class Status(StrEnum): - accepted = 'accepted' - rejected = 'rejected' - - -class RejectionCode(ScalarStr): - __slots__ = () - _constraints = {'max_length': 128, 'min_length': 1, 'pattern': '^[A-Z][A-Z0-9_]*$'} - - -class ReportingReceipt(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - reporting_receipt_id: Annotated[ - str, Field(max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$') - ] - reporting_obligation_id: Annotated[ - str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') - ] - reporting_revision_id: Annotated[ - str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') - ] - reporting_materialization_id: Annotated[ - str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') - ] - supersedes_reporting_receipt_id: Annotated[ - str | None, - Field( - description="Optional immutable rejected receipt replaced by this new receipt. It MUST name the caller's current rejected receipt for this obligation and revision; accepted current receipts are terminal.", - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] = None - status: Status - verification_profile: reporting_verification_profile.ReportingVerificationProfile - observed_row_count: Annotated[SchemaInt, Field(ge=0)] - observed_control_totals: list[reporting_control_total.ReportingControlTotal] - observed_canonical_content_digest: ( - reporting_canonical_content_digest.ReportingCanonicalContentDigest | None - ) = None - observed_manifest_sha256: Annotated[str | None, Field(pattern='^[A-Fa-f0-9]{64}$')] = None - observed_native_version_ref: ( - reporting_native_version_ref.ReportingNativeVersionReference | None - ) = None - consumer_commit_ref: Annotated[ - str | None, - Field( - description='Optional non-secret consumer checkpoint, transaction, or load identifier. It is evidence for operations, not authorization or a credential.', - max_length=512, - min_length=1, - ), - ] = None - rejection_codes: Annotated[list[RejectionCode] | None, Field(min_length=1)] = None - observed_at: AwareDatetime - received_at: AwareDatetime | None = None diff --git a/src/adcp/types/generated_poc/core/reporting_reconciliation_mode.py b/src/adcp/types/generated_poc/core/reporting_reconciliation_mode.py deleted file mode 100644 index d9738e503..000000000 --- a/src/adcp/types/generated_poc/core/reporting_reconciliation_mode.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_reconciliation_mode.json -# timestamp: 2026-09-01T19:37:00+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class ReportingReconciliationMode(StrEnum): - delivery_only = 'delivery_only' - consumer_receipt = 'consumer_receipt' diff --git a/src/adcp/types/generated_poc/core/reporting_reliability_statistics.py b/src/adcp/types/generated_poc/core/reporting_reliability_statistics.py deleted file mode 100644 index 53b9e1e70..000000000 --- a/src/adcp/types/generated_poc/core/reporting_reliability_statistics.py +++ /dev/null @@ -1,103 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_reliability_statistics.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - - -class AdjustmentMagnitudeItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - control_total_name: Annotated[str, Field(max_length=128, min_length=1)] - unit: Annotated[str, Field(max_length=32, min_length=1)] - sample_count: Annotated[SchemaInt, Field(ge=1)] - p50_absolute_delta: Annotated[str, Field(pattern='^(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?$')] - p95_absolute_delta: Annotated[str, Field(pattern='^(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?$')] - - -class Basis(StrEnum): - reporting_ledger = 'reporting_ledger' - third_party_attestation = 'third_party_attestation' - - -class Evidence(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - regex_engine="python-re", - ) - basis: Basis - generated_at: AwareDatetime - attestation_uri: AnyUrl | None = None - attestation_sha256: Annotated[str | None, Field(pattern='^[A-Fa-f0-9]{64}$')] = None - - -class LatencyPercentiles(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - p50: Annotated[SchemaInt, Field(ge=0)] - p95: Annotated[SchemaInt, Field(ge=0)] - - -class ReportingReliabilityMeasurementPeriod(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - start: AwareDatetime - end: AwareDatetime - - -class ReportingReliabilityStatistics(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - offering_id: Annotated[ - str, Field(max_length=128, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,128}$') - ] - measurement_period: ReportingReliabilityMeasurementPeriod - obligations_due: Annotated[ - SchemaInt, Field(description='Denominator for on-time performance.', ge=1) - ] - obligations_on_time: Annotated[ - SchemaInt, - Field( - description='Obligations whose required revision was published by expected_at. Divide by obligations_due for the on-time rate.', - ge=0, - ), - ] - official_revisions_published: Annotated[ - SchemaInt, Field(description='Denominator for the official-adjustment rate.', ge=0) - ] - official_revisions_adjusted: Annotated[ - SchemaInt, - Field( - description='Distinct official revisions receiving at least one adjustment. Divide by official_revisions_published for the adjustment rate.', - ge=0, - ), - ] - publication_latency_seconds: Annotated[ - LatencyPercentiles, - Field( - description='Observed seconds from period.end to publication of the required revision.' - ), - ] - adjustment_latency_seconds: Annotated[ - LatencyPercentiles | None, - Field( - description='Observed seconds from official finalized_at to the first adjustment, required when official_revisions_adjusted is nonzero.' - ), - ] = None - adjustment_magnitude: Annotated[ - list[AdjustmentMagnitudeItem] | None, - Field( - description='Optional absolute adjustment-magnitude percentiles for comparable control totals such as spend.' - ), - ] = None - evidence: Evidence diff --git a/src/adcp/types/generated_poc/core/reporting_report_definition.py b/src/adcp/types/generated_poc/core/reporting_report_definition.py deleted file mode 100644 index 29d118e15..000000000 --- a/src/adcp/types/generated_poc/core/reporting_report_definition.py +++ /dev/null @@ -1,179 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_report_definition.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field - - -class ContractVersion(StrEnum): - field_1_0 = '1.0' - field_1_1 = '1.1' - - -class Provider(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, Field(pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$') - ] - - -class Source(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - provider: Provider - system: Annotated[str, Field(max_length=128, min_length=1)] - api_version: Annotated[str, Field(max_length=128, min_length=1)] - query_semantics: Annotated[ - dict[str, Any], - Field( - description='Canonical JSON object containing every source option that can change the numbers, including attribution settings, action-report-time, filters, and mapping version.' - ), - ] - - -class Aggregation(StrEnum): - sum = 'sum' - count = 'count' # type: ignore[assignment] - min = 'min' - max = 'max' - average = 'average' - ratio = 'ratio' - last = 'last' - custom = 'custom' - - -class Metric(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - name: Annotated[str, Field(max_length=128, min_length=1)] - source_expression: Annotated[str, Field(max_length=2048, min_length=1)] - aggregation: Aggregation - unit: Annotated[str | None, Field(max_length=64, min_length=1)] = None - - -class Dimension(ScalarStr): - __slots__ = () - _constraints = {'max_length': 128, 'min_length': 1} - - -class RestatementPolicy(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - regex_engine="python-re", - ) - source_requery_duration: Annotated[ - str, - Field( - pattern='^P(?=\\d|T)(?=.*\\d)(?:\\d+Y)?(?:\\d+M)?(?:\\d+D)?(?:T(?=\\d)(?:\\d+H)?(?:\\d+M)?(?:\\d+S)?)?$' - ), - ] - emit_only_on_content_change: Literal[True] - official_correction_mode: Annotated[ - Literal['adjustments_only'], - Field( - description='Once an official revision is published it is immutable and cannot be superseded. Later source corrections are separate reporting-adjustment records applied to a later accounting period.' - ), - ] = 'adjustments_only' - - -class FinalityPolicies(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - finality_policy_id: Annotated[ - str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') - ] - basis: Literal['source_final'] = 'source_final' - source_signal: Annotated[str, Field(max_length=512, min_length=1)] - - -class FinalityPolicies1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - regex_engine="python-re", - ) - finality_policy_id: Annotated[ - str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') - ] - basis: Literal['contractual_cutoff'] = 'contractual_cutoff' - duration_after_period_end: Annotated[ - str, - Field( - pattern='^P(?=\\d|T)(?=.*\\d)(?:\\d+Y)?(?:\\d+M)?(?:\\d+D)?(?:T(?=\\d)(?:\\d+H)?(?:\\d+M)?(?:\\d+S)?)?$' - ), - ] - - -class FinalityPolicies2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - regex_engine="python-re", - ) - finality_policy_id: Annotated[ - str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') - ] - basis: Literal['stabilized'] = 'stabilized' - minimum_age: Annotated[ - str, - Field( - pattern='^P(?=\\d|T)(?=.*\\d)(?:\\d+Y)?(?:\\d+M)?(?:\\d+D)?(?:T(?=\\d)(?:\\d+H)?(?:\\d+M)?(?:\\d+S)?)?$' - ), - ] - unchanged_for: Annotated[ - str, - Field( - pattern='^P(?=\\d|T)(?=.*\\d)(?:\\d+Y)?(?:\\d+M)?(?:\\d+D)?(?:T(?=\\d)(?:\\d+H)?(?:\\d+M)?(?:\\d+S)?)?$' - ), - ] - - -class ReportCalendarTimezoneBasis(StrEnum): - utc = 'utc' - account_timezone = 'account_timezone' - schedule_timezone = 'schedule_timezone' - configured_timezone = 'configured_timezone' - - -class Calendar(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - timezone_basis: ReportCalendarTimezoneBasis - timezone: Annotated[str | None, Field(max_length=255, min_length=1)] = None - - -class ReportingReportDefinition(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - contract_version: Annotated[ - ContractVersion, - Field( - description='1.1 adds immutable official closes with adjustments_only correction semantics for Reliable Reporting 1.0. Version 1.0 remains accepted for compatibility with the preceding experimental managed-reporting contract.' - ), - ] - media_type: Literal['application/vnd.adcp.reporting-definition+json'] = 'application/vnd.adcp.reporting-definition+json' - report_definition_id: Annotated[ - str, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') - ] - reporting_profile: Annotated[str, Field(max_length=128, min_length=1)] - grain: Annotated[str, Field(max_length=128, min_length=1)] - source: Source - calendar: Calendar - metrics: Annotated[list[Metric], Field(min_length=1)] - dimensions: list[Dimension] - restatement_policy: RestatementPolicy - finality_policies: Annotated[ - list[FinalityPolicies | FinalityPolicies1 | FinalityPolicies2], Field(min_length=1) - ] diff --git a/src/adcp/types/generated_poc/core/reporting_resource.py b/src/adcp/types/generated_poc/core/reporting_resource.py deleted file mode 100644 index 8cd4072f7..000000000 --- a/src/adcp/types/generated_poc/core/reporting_resource.py +++ /dev/null @@ -1,85 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_resource.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import AwareDatetime, ConfigDict, Field - -from . import reporting_native_version_ref - - -class Kind(StrEnum): - manifest = 'manifest' - dataset = 'dataset' - warehouse_relation = 'warehouse_relation' - - -class Immutability(StrEnum): - immutable_location = 'immutable_location' - native_version = 'native_version' - - -class ReportingReaderCompatibilityItem(ScalarStr): - __slots__ = () - _constraints = {'max_length': 128, 'min_length': 1} - _json_schema_extra = {'title': 'Reporting Reader Compatibility Item'} - - -class ReportingResource(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - resource_ref: Annotated[ - str, - Field( - description='Seller-issued opaque reference to this exact authenticated resource descriptor.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] - kind: Annotated[ - Kind, Field(description='Shape through which the durable revision is consumed.') - ] - location: Annotated[ - str, - Field( - description='Non-secret provider-native object, relation, or share identifier. MUST NOT contain an activation URL, signed URL, bearer token, password, private key, or embedded credential.', - max_length=2048, - min_length=1, - ), - ] - native_version_ref: reporting_native_version_ref.ReportingNativeVersionReference | None = None - manifest_version: Annotated[ - Literal['1.0'], - Field(description='Version of reporting-file-manifest.json used by a manifest resource.'), - ] = '1.0' - manifest_sha256: Annotated[ - str | None, - Field( - description='SHA-256 over the exact manifest bytes. Consumers verify this before parsing the manifest.', - pattern='^[A-Fa-f0-9]{64}$', - ), - ] = None - immutability: Annotated[ - Immutability, - Field(description='How this descriptor selects the exact immutable materialization.'), - ] - expires_at: Annotated[ - AwareDatetime, - Field( - description='Mandatory finite lower-bound endpoint through which this exact resource remains resolvable; it cannot be earlier than the advertised retention contract.' - ), - ] - reader_compatibility: Annotated[ - list[ReportingReaderCompatibilityItem] | None, - Field( - description='Reader features or format constraints required to consume this resource. Readiness verification MUST use a representative supported reader.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/reporting_revision.py b/src/adcp/types/generated_poc/core/reporting_revision.py deleted file mode 100644 index fd3e216c6..000000000 --- a/src/adcp/types/generated_poc/core/reporting_revision.py +++ /dev/null @@ -1,169 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_revision.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - -from ..enums import reporting_finality -from . import reporting_canonical_content_digest, reporting_control_total, reporting_coverage - - -class Period(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - start: AwareDatetime - end: AwareDatetime - source_timezone: Annotated[str, Field(min_length=1)] - - -class FinalityBasis(StrEnum): - source_final = 'source_final' - contractual_cutoff = 'contractual_cutoff' - stabilized = 'stabilized' - - -class DataThroughPrecision(StrEnum): - exact = 'exact' - lower_bound = 'lower_bound' - unknown = 'unknown' - - -class ReportingRevision(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - regex_engine="python-re", - ) - reporting_revision_id: Annotated[ - str, - Field( - description='Portable AdCP identity for this immutable report publication. Distinct from package delivery_revision_id and provider-native versions.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] - revision_content_sha256: Annotated[ - str, - Field( - description='SHA-256 of the immutable RFC 8785 JCS binding object containing reporting_revision_id, row_count, control_totals, and reporting_rows. Reliable Reporting 1.0 Core revisions include it and exact reads return the identical value.', - pattern='^[A-Fa-f0-9]{64}$', - ), - ] - report_definition_id: Annotated[ - str, - Field( - description='Identity or canonical fingerprint of immutable metric, grain, attribution, breakdown, action-definition, profile, and calendar/timezone semantics.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] - report_definition_uri: AnyUrl - report_definition_sha256: Annotated[str, Field(pattern='^[A-Fa-f0-9]{64}$')] - reporting_profile: Annotated[str, Field(max_length=128, min_length=1)] - schema_version: Annotated[str, Field(max_length=64, min_length=1)] - schema_uri: Annotated[ - AnyUrl, - Field( - description='Machine-readable schema on the authenticated seller/provider or AdCP-registry origin.' - ), - ] - schema_sha256: Annotated[ - str, - Field( - description='Digest of the exact schema bytes used to validate this immutable revision.', - pattern='^[A-Fa-f0-9]{64}$', - ), - ] - schema_dialect: Annotated[ - Literal['https://json-schema.org/draft/2020-12/schema'], - Field(description='Closed SDK-bundled dialect; the metaschema is never network-fetched.'), - ] = 'https://json-schema.org/draft/2020-12/schema' - schema_ref_policy: Annotated[ - Literal['local_fragment_only'], - Field( - description='The fetched schema is self-contained and every $ref is a local # fragment.' - ), - ] = 'local_fragment_only' - account_id: Annotated[str, Field(min_length=1)] - media_buy_ids: Annotated[ - list[reporting_coverage.ReportingMediaBuyId], - Field( - description='Exact frozen media-buy denominator inherited from the obligation, including buys with zero rows. An empty array proves a zero-buy period rather than an unknown denominator.' - ), - ] - coverage: Annotated[ - reporting_coverage.ReportingCoverage, - Field( - description='Frozen product/package denominator represented by this logical content. The same coverage follows the revision to every destination.' - ), - ] - period: Annotated[ - Period, Field(description='Half-open reporting interval with its source calendar boundary.') - ] - finality: reporting_finality.ReportingFinality - finality_basis: Annotated[ - FinalityBasis | None, - Field( - description='Why an official revision is considered final: an authoritative source signal, a versioned contractual cutoff, or a versioned stabilization rule.' - ), - ] = None - finality_policy_id: Annotated[ - str | None, - Field( - description='Immutable policy/version reference that defines the selected finality basis. It MUST be bound by report_definition_id.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - finalized_at: Annotated[ - AwareDatetime | None, - Field( - description='When the producer applied the declared finality basis to this official revision.' - ), - ] = None - observed_at: Annotated[ - AwareDatetime, - Field(description='When the seller obtained or committed this source observation.'), - ] - data_through: Annotated[ - AwareDatetime | None, - Field( - description='Latest event time conservatively included, or null when precision is unknown.' - ), - ] - data_through_precision: DataThroughPrecision - supersedes_reporting_revision_id: Annotated[ - str | None, - Field( - description='Immediately superseded snapshot revision of the same logical slice. An official revision is terminal and MUST NOT be named here; later corrections use reporting-adjustment records.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - row_count: Annotated[ - SchemaInt, - Field( - description='Logical row count, including zero for a successfully evaluated empty report.', - ge=0, - ), - ] - control_totals: Annotated[ - list[reporting_control_total.ReportingControlTotal], - Field( - description='Profile-defined totals computed from the canonical logical revision. Names MUST be unique.' - ), - ] - canonical_content_digest: ( - reporting_canonical_content_digest.ReportingCanonicalContentDigest | None - ) = None - created_at: AwareDatetime diff --git a/src/adcp/types/generated_poc/core/reporting_schedule.py b/src/adcp/types/generated_poc/core/reporting_schedule.py deleted file mode 100644 index 8226a2ba3..000000000 --- a/src/adcp/types/generated_poc/core/reporting_schedule.py +++ /dev/null @@ -1,54 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_schedule.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - - -class ReportingScheduleAlignment(StrEnum): - utc = 'utc' - account_timezone = 'account_timezone' - source_timezone = 'source_timezone' - billing_cycle = 'billing_cycle' - - -class ReportingSchedule(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - regex_engine="python-re", - ) - period_duration: Annotated[ - str, - Field( - description='Strictly positive ISO 8601 duration of each reporting period, such as PT15M, P1D, or P1M.', - pattern='^P(?=.*[1-9])(?=\\d|T)(?:\\d+Y)?(?:\\d+M)?(?:\\d+D)?(?:T(?=\\d)(?:\\d+H)?(?:\\d+M)?(?:\\d+S)?)?$', - ), - ] - alignment: ReportingScheduleAlignment - period_anchor: Annotated[ - AwareDatetime | None, - Field( - description='Required for billing_cycle alignment. This immutable instant anchors the recurring half-open billing periods so producer and consumer derive the same month, quarter, or other contractual cycle.' - ), - ] = None - period_timezone: Annotated[ - str | None, - Field( - description='Required IANA timezone for source_timezone and billing_cycle calendar arithmetic. A numeric UTC offset is not sufficient because it does not define DST transitions.', - max_length=255, - min_length=1, - ), - ] = None - delivery_sla: Annotated[ - str, - Field( - description='Non-negative maximum time after period end before the required revision is due. PT0S means due at period close; expected_at equals the resolved period end plus this duration.', - pattern='^P(?=\\d|T)(?=.*\\d)(?:\\d+Y)?(?:\\d+M)?(?:\\d+D)?(?:T(?=\\d)(?:\\d+H)?(?:\\d+M)?(?:\\d+S)?)?$', - ), - ] diff --git a/src/adcp/types/generated_poc/core/reporting_schedule_offering.py b/src/adcp/types/generated_poc/core/reporting_schedule_offering.py deleted file mode 100644 index 26d0a633a..000000000 --- a/src/adcp/types/generated_poc/core/reporting_schedule_offering.py +++ /dev/null @@ -1,57 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_schedule_offering.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - -from . import reporting_schedule - - -class PeriodAnchorPolicy(StrEnum): - fixed = 'fixed' - configurable = 'configurable' - - -class PeriodTimezonePolicy(StrEnum): - fixed = 'fixed' - account_resolved = 'account_resolved' - - -class ReportingScheduleOffering(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - regex_engine="python-re", - ) - period_duration: Annotated[ - str, - Field( - pattern='^P(?=.*[1-9])(?=\\d|T)(?:\\d+Y)?(?:\\d+M)?(?:\\d+D)?(?:T(?=\\d)(?:\\d+H)?(?:\\d+M)?(?:\\d+S)?)?$' - ), - ] - alignment: reporting_schedule.ReportingScheduleAlignment - period_anchor_policy: Annotated[ - PeriodAnchorPolicy | None, - Field( - description='For billing_cycle only. fixed requires the advertised anchor and timezone; configurable lets each authorized account configuration select them.' - ), - ] = None - period_timezone_policy: Annotated[ - PeriodTimezonePolicy | None, - Field( - description="For source_timezone only. fixed advertises one exact upstream IANA timezone; account_resolved requires the seller to resolve and echo the account's upstream reporting timezone during configuration." - ), - ] = None - period_anchor: AwareDatetime | None = None - period_timezone: Annotated[str | None, Field(max_length=255, min_length=1)] = None - delivery_sla: Annotated[ - str, - Field( - pattern='^P(?=\\d|T)(?=.*\\d)(?:\\d+Y)?(?:\\d+M)?(?:\\d+D)?(?:T(?=\\d)(?:\\d+H)?(?:\\d+M)?(?:\\d+S)?)?$' - ), - ] diff --git a/src/adcp/types/generated_poc/core/reporting_status_changed_webhook.py b/src/adcp/types/generated_poc/core/reporting_status_changed_webhook.py deleted file mode 100644 index 3378c62cc..000000000 --- a/src/adcp/types/generated_poc/core/reporting_status_changed_webhook.py +++ /dev/null @@ -1,83 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_status_changed_webhook.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarStr -from pydantic import AwareDatetime, ConfigDict, Field - -from ..enums import reporting_health -from . import ext as ext_1 -from . import reporting_delivery_offering - - -class IssueId(ScalarStr): - __slots__ = () - _constraints = {'max_length': 255, 'min_length': 1, 'pattern': '^[A-Za-z0-9_.:-]{1,255}$'} - - -class ReportingStatusChangedWebhook(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - idempotency_key: Annotated[ - str, - Field( - description='Sender-generated key stable across retries of the same fire. Sellers MUST generate a cryptographically random value (UUID v4 recommended) per distinct fire and reuse it on every retry. Receivers MUST dedupe by this key, scoped to the authenticated sender identity.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - notification_id: Annotated[ - str, - Field( - description='Stable per logical health transition; re-emissions reuse it, and a later distinct transition receives a new id.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] - notification_type: Literal['reporting.status_changed'] = 'reporting.status_changed' - fired_at: AwareDatetime - subscriber_id: Annotated[ - str, - Field( - description='Identifies which account-level notification_configs[] entry is receiving this fire.', - max_length=64, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,64}$', - ), - ] - account_id: Annotated[str, Field(min_length=1)] - delivery_config_id: Annotated[ - str, Field(max_length=64, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,64}$') - ] - delivery_config_version: Annotated[SchemaInt, Field(ge=1)] - feed_purpose: reporting_delivery_offering.ReportingFeedPurpose - reporting_obligation_id: Annotated[ - str | None, - Field( - description='Present when the transition is obligation-scoped; absent for configuration-level transitions.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - health: Annotated[ - reporting_health.ReportingHealth, - Field(description='The health state after this transition.'), - ] - previous_health: reporting_health.ReportingHealth | None = None - issue_ids: Annotated[ - list[IssueId] | None, - Field( - description='Stable identifiers of the open issues that caused or survived this transition, matching issues[].issue_id on get_reporting_status, so consumers can project AdCP reporting issues into durable work items. Empty or absent on recovery transitions.', - max_length=16, - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/reporting_status_issue.py b/src/adcp/types/generated_poc/core/reporting_status_issue.py deleted file mode 100644 index 3c4a1b334..000000000 --- a/src/adcp/types/generated_poc/core/reporting_status_issue.py +++ /dev/null @@ -1,132 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_status_issue.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field - -from . import reporting_coverage, reporting_delivery_offering - - -class Code(StrEnum): - REPORT_OVERDUE = 'REPORT_OVERDUE' - PRODUCTION_FAILED = 'PRODUCTION_FAILED' - DELIVERY_FAILED = 'DELIVERY_FAILED' - ACCESS_REQUIRED = 'ACCESS_REQUIRED' - CONFIGURATION_REQUIRED = 'CONFIGURATION_REQUIRED' - REPORTING_COVERAGE_INCOMPLETE = 'REPORTING_COVERAGE_INCOMPLETE' - RESOURCE_EXPIRED = 'RESOURCE_EXPIRED' - READER_INCOMPATIBLE = 'READER_INCOMPATIBLE' - HISTORY_UNAVAILABLE = 'HISTORY_UNAVAILABLE' - RECEIPT_REQUIRED = 'RECEIPT_REQUIRED' - RECEIPT_REJECTED = 'RECEIPT_REJECTED' - ADJUSTMENT_RECEIPT_REQUIRED = 'ADJUSTMENT_RECEIPT_REQUIRED' - ADJUSTMENT_RECEIPT_REJECTED = 'ADJUSTMENT_RECEIPT_REJECTED' - CONSUMER_STATUS_MISMATCH = 'CONSUMER_STATUS_MISMATCH' - - -class IssueState(StrEnum): - open = 'open' - acknowledged = 'acknowledged' - resolved = 'resolved' - waived = 'waived' - - -class ResponsibleParty(StrEnum): - buyer = 'buyer' - seller = 'seller' - provider = 'provider' - - -class RecommendedAction(StrEnum): - wait_for_retry = 'wait_for_retry' - contact_buyer = 'contact_buyer' - contact_seller = 'contact_seller' - contact_provider = 'contact_provider' - repair_access = 'repair_access' - update_configuration = 'update_configuration' - change_reporting_scope = 'change_reporting_scope' - use_supported_reader = 'use_supported_reader' - - -class ReportingStatusSeverity(StrEnum): - delayed = 'delayed' - action_required = 'action_required' - - -class ReportingStatusIssue(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - issue_id: Annotated[ - str, - Field( - description='Seller-issued stable identifier for this logical issue: re-emissions and later polls of the same unresolved condition reuse it, and resolution retires it, so consumers can project AdCP reporting issues into durable work items. A recurrence after resolution receives a new id.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] - code: Code - severity: ReportingStatusSeverity - opened_at: Annotated[ - AwareDatetime | None, - Field( - description='When the seller first observed this logical condition, carried unchanged across every re-emission until the issue is retired. It anchors the escalation clock advertised as consumer_mismatch_escalation_seconds and lets a consumer age an issue without keeping its own first-seen table. Required when code is CONSUMER_STATUS_MISMATCH.' - ), - ] = None - issue_state: Annotated[ - IssueState | None, - Field( - description='Optional seller-maintained lifecycle for this issue_id. open is the default when omitted. acknowledged means a human on responsible_party has taken it up but the condition persists. resolved means the underlying condition no longer holds; a recurrence uses a new issue_id. waived means the parties agreed off-protocol to disregard this exact issue even though its underlying condition may still hold. Only open and acknowledged issues appear in issues[]; retiring an issue removes it from the projection rather than publishing it at resolved or waived, so a reader that treats a nonempty issues[] as degradation stays correct. A CONSUMER_STATUS_MISMATCH waiver follows the bilateral, exact-scope requirements in consumer_mismatch_lifecycle.' - ), - ] = None - external_ref: Annotated[ - str | None, - Field( - description="Optional opaque, non-secret correlation string for the party's own tracker — a ticket key, incident ID, or case number. Untrusted display text only. The character class excludes whitespace and the solidus, so the value cannot express a URL or a sentence; receivers compare, store, and display it as inert text and never dereference, resolve, or execute it. It confers no authorization and MUST NOT be used to look up state across accounts or callers.", - max_length=128, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,128}$', - ), - ] = None - responsible_party: ResponsibleParty - recommended_action: RecommendedAction - message: Annotated[ - str | None, - Field( - description='Untrusted display text only. SDKs and agents dispatch exclusively on closed code/recommended_action values and never execute embedded links or instructions.', - max_length=500, - ), - ] = None - reporting_obligation_id: Annotated[ - str | None, Field(max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$') - ] = None - reporting_status_id: Annotated[ - str | None, - Field( - description='Current authenticated consumer status statement that caused this mismatch.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] = None - delivery_config_id: Annotated[ - str | None, Field(max_length=64, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,64}$') - ] = None - delivery_config_version: Annotated[SchemaInt | None, Field(ge=1)] = None - feed_purpose: reporting_delivery_offering.ReportingFeedPurpose | None = None - media_buy_ids: Annotated[ - list[reporting_coverage.ReportingMediaBuyId] | None, Field(min_length=1) - ] = None - package_ids: Annotated[ - list[reporting_coverage.ReportingPackageId] | None, Field(min_length=1) - ] = None - period_start: AwareDatetime | None = None - period_end: AwareDatetime | None = None - expected_at: AwareDatetime | None = None diff --git a/src/adcp/types/generated_poc/core/reporting_verification.py b/src/adcp/types/generated_poc/core/reporting_verification.py deleted file mode 100644 index d10af955d..000000000 --- a/src/adcp/types/generated_poc/core/reporting_verification.py +++ /dev/null @@ -1,104 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_verification.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field - -from . import ( - reporting_canonical_content_digest, - reporting_control_total, - reporting_file_object_ref, - reporting_native_version_ref, - reporting_verification_profile, -) - - -class VerificationPath(StrEnum): - producer = 'producer' - representative_consumer = 'representative_consumer' - destination = 'destination' - - -class PhysicalChecksums(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - object_ref: reporting_file_object_ref.ReportingFileObjectReference - algorithm: Literal['sha256'] = 'sha256' - value: Annotated[str, Field(pattern='^[A-Fa-f0-9]{64}$')] - - -class PhysicalChecksums1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - object_ref: reporting_file_object_ref.ReportingFileObjectReference - algorithm: Literal['sha512'] = 'sha512' - value: Annotated[str, Field(pattern='^[A-Fa-f0-9]{128}$')] - - -class ObservedThrough(StrEnum): - representative_consumer = 'representative_consumer' - destination = 'destination' - - -class NativeCommitEvidence(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - native_version_ref: reporting_native_version_ref.ReportingNativeVersionReference - observed_through: ObservedThrough - - -class ReportingVerification(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - verified_at: Annotated[ - AwareDatetime, - Field( - description='When the producer completed verification through the claimed consumer/destination path.' - ), - ] - verification_path: Annotated[ - VerificationPath, - Field( - description='Path on which verification succeeded. dataset_share readiness requires representative_consumer; delivered warehouse state requires destination.' - ), - ] - verification_profile: reporting_verification_profile.ReportingVerificationProfile - row_count: Annotated[ - SchemaInt, - Field( - description='Verified row count. Zero explicitly distinguishes an empty committed revision from a missing revision.', - ge=0, - ), - ] - control_totals: Annotated[ - list[reporting_control_total.ReportingControlTotal], - Field( - description='Profile-defined totals recomputed through verification_path. Names MUST be unique.' - ), - ] - canonical_content_digest: ( - reporting_canonical_content_digest.ReportingCanonicalContentDigest | None - ) = None - physical_checksums: Annotated[ - list[PhysicalChecksums | PhysicalChecksums1] | None, - Field( - description='Method-specific byte/object checksums. Different encodings of the same logical revision normally have different values.', - min_length=1, - ), - ] = None - native_commit_evidence: Annotated[ - NativeCommitEvidence | None, - Field( - description='Provider-native immutable version evidence observed through the named consumer or destination path.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/reporting_verification_profile.py b/src/adcp/types/generated_poc/core/reporting_verification_profile.py deleted file mode 100644 index 2b6208222..000000000 --- a/src/adcp/types/generated_poc/core/reporting_verification_profile.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_verification_profile.json -# timestamp: 2026-09-01T19:37:00+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class ReportingVerificationProfile(StrEnum): - native_commit = 'native_commit' - manifest_checksums = 'manifest_checksums' - canonical_digest = 'canonical_digest' diff --git a/src/adcp/types/generated_poc/core/reporting_verification_profile_set.py b/src/adcp/types/generated_poc/core/reporting_verification_profile_set.py deleted file mode 100644 index 8b6ec2b01..000000000 --- a/src/adcp/types/generated_poc/core/reporting_verification_profile_set.py +++ /dev/null @@ -1,27 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_verification_profile_set.json -# timestamp: 2026-09-01T19:37:00+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from pydantic import Field, RootModel - - -class ReportingVerificationProfileSetEnum(StrEnum): - native_commit = 'native_commit' - manifest_checksums = 'manifest_checksums' - canonical_digest = 'canonical_digest' - - -class ReportingVerificationProfileSet(RootModel[list[ReportingVerificationProfileSetEnum]]): - root: Annotated[ - list[ReportingVerificationProfileSetEnum], - Field( - description="Verification profiles the destination can accept. native_commit requires provider-native transaction/version evidence plus counts and control totals; manifest_checksums requires a committed file manifest with cryptographic checksums; canonical_digest requires recomputation of the canonical logical-content digest. A reporting feed selects one profile from this allowed set according to the seller offering and the feed's strictness requirements.", - min_length=1, - title='Reporting Verification Profile Set', - ), - ] diff --git a/src/adcp/types/generated_poc/core/reporting_webhook.py b/src/adcp/types/generated_poc/core/reporting_webhook.py deleted file mode 100644 index f20d22909..000000000 --- a/src/adcp/types/generated_poc/core/reporting_webhook.py +++ /dev/null @@ -1,73 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_webhook.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - -from ..enums import auth_scheme, available_metric - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[auth_scheme.AuthenticationScheme], - Field( - description="Array of authentication schemes. ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD use the RFC 9421 webhook signing profile instead.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class ReportingFrequency(StrEnum): - hourly = 'hourly' - daily = 'daily' - monthly = 'monthly' - - -class ReportingWebhook(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - url: Annotated[AnyUrl, Field(description='Webhook endpoint URL for reporting notifications')] - token: Annotated[ - str | None, - Field( - description='Optional client-provided token for webhook validation. Echoed back in webhook payload to validate request authenticity.', - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication, - Field( - deprecated=True, - description="Legacy authentication configuration for webhook delivery (A2A-compatible). Opts the receiver into Bearer or HMAC-SHA256 signing. Both schemes are deprecated; the preferred signing profile for new integrations is RFC 9421, where the seller signs with a key published at its brand.json agents[] entry and the buyer verifies against the seller's JWKS — no shared secret crosses the wire (see docs/building/implementation/security.mdx#webhook-callbacks). This field is required in AdCP 3.x; the requirement is removed in AdCP 4.0 when the default RFC 9421 path becomes the only path.", - ), - ] - reporting_frequency: Annotated[ - ReportingFrequency, - Field( - description='Frequency for automated reporting delivery. Must be supported by all products in the media buy.' - ), - ] - requested_metrics: Annotated[ - list[available_metric.AvailableMetric] | None, - Field( - description="Optional list of metrics to include in webhook notifications. If omitted, all available metrics are included; an empty array has the same meaning as omission (it does not narrow to impressions and spend only). impressions and spend are always included regardless of this list. Must be a subset of the product's available_metrics. Subset evaluation and leaf resolution follow `enums/available-metric.json`: a numeric leaf may be covered by its container, while each structured distribution must be explicitly available. Requesting any nested identity selects its canonical carrier inside the payload's nested object. Same narrowing semantics as get_media_buy_delivery's requested_metrics (which additionally requires at least one entry when present)." - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/reporting_write_destination.py b/src/adcp/types/generated_poc/core/reporting_write_destination.py deleted file mode 100644 index e14248287..000000000 --- a/src/adcp/types/generated_poc/core/reporting_write_destination.py +++ /dev/null @@ -1,68 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/reporting_write_destination.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - - -class ReportingWriteDestination1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - mode: Literal['existing'] = 'existing' - destination_ref: Annotated[ - str, - Field( - description='Seller-issued immutable destination-generation reference returned by sync_agent_configuration, an earlier sync, or bilateral setup.', - max_length=255, - min_length=1, - ), - ] - - -class Provider(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, Field(pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$') - ] - - -class ReportingWriteDestination2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - mode: Literal['provision'] = 'provision' - provider: Annotated[Provider, Field(description='Platform hosting the destination.')] - location: Annotated[ - str, - Field( - description='Provider-native bucket, prefix, project/dataset, catalog/schema, or equivalent locator. It MUST NOT contain an embedded credential or signed URL.', - max_length=2048, - min_length=1, - ), - ] - access_mode: Annotated[ - str | None, - Field( - description='Optional provider access family used for capability matching.', - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_.-]*$', - ), - ] = None - - -ReportingWriteDestination = Annotated[ - ReportingWriteDestination1 | ReportingWriteDestination2, - Field( - description="Storage or warehouse destination for durable reporting. The caller either references an existing seller-issued immutable destination generation or asks the seller to validate and bind a provider-native location. A destination_ref is owned by the stable authenticated principal's relationship with this seller and may be reused across accounts; each account delivery configuration separately authorizes its feed and scope. Changing proof-bound coordinates or the accepted delivery contract produces a new destination_ref. Access grants name advertised producer identities; credentials never transit AdCP.", - title='Reporting Write Destination', - ), -] diff --git a/src/adcp/types/generated_poc/core/representation_destination.py b/src/adcp/types/generated_poc/core/representation_destination.py deleted file mode 100644 index b811381a9..000000000 --- a/src/adcp/types/generated_poc/core/representation_destination.py +++ /dev/null @@ -1,47 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/representation_destination.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..enums import daast_version, vast_version -from . import placement_ref, product_format_declaration - - -class RepresentationDestination(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - product_id: Annotated[ - str, Field(description='Product owned by the destination seller.', min_length=1) - ] - format_option: Annotated[ - product_format_declaration.ProductFormatDeclaration, - Field( - description='Exact effective destination declaration. The full declaration is carried so an option without format_option_id remains addressable. The destination seller verifies it against the current product and applicable placement/publisher narrowings.' - ), - ] - placement_refs: Annotated[ - list[placement_ref.PlacementReference] | None, - Field( - description='Optional placements the derived manifest must satisfy. Omission means every placement in the intended product route, not an unconstrained destination.', - min_length=1, - ), - ] = None - execution_vast_version: Annotated[ - vast_version.VastVersion | None, - Field( - description='Exact VAST execution version selected for seller-assembled first-class VAST trackers when no sibling exact-version VAST document supplies it. Must be accepted by format_option and is preserved in selection lineage.' - ), - ] = None - execution_daast_version: Annotated[ - daast_version.DaastVersion | None, - Field( - description='Exact DAAST execution version selected for seller-assembled first-class DAAST trackers when no sibling exact-version DAAST document supplies it. Must be accepted by format_option and is preserved in selection lineage.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/representation_rejection.py b/src/adcp/types/generated_poc/core/representation_rejection.py deleted file mode 100644 index 8af9d760f..000000000 --- a/src/adcp/types/generated_poc/core/representation_rejection.py +++ /dev/null @@ -1,32 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/representation_rejection.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - - -class Code(StrEnum): - incompatible_format_kind = 'incompatible_format_kind' - format_option_mismatch = 'format_option_mismatch' - unsupported_delivery_type = 'unsupported_delivery_type' - vast_version_mismatch = 'vast_version_mismatch' - macro_unsupported = 'macro_unsupported' - tracker_contract_mismatch = 'tracker_contract_mismatch' - asset_requirement_failed = 'asset_requirement_failed' - other = 'other' - - -class RepresentationRejection(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - representation_id: Annotated[str, Field(min_length=1)] - code: Code - message: Annotated[str, Field(min_length=1)] - details: dict[str, Any] | None = None diff --git a/src/adcp/types/generated_poc/core/representation_selection.py b/src/adcp/types/generated_poc/core/representation_selection.py deleted file mode 100644 index 0778984a0..000000000 --- a/src/adcp/types/generated_poc/core/representation_selection.py +++ /dev/null @@ -1,64 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/representation_selection.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..enums import daast_version, representation_selection_strategy, vast_version -from . import creative_revision_id - - -class ResolvedBy(StrEnum): - buyer = 'buyer' - seller = 'seller' - - -class RepresentationSelection(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - creative_id: Annotated[str, Field(min_length=1)] - revision_id: creative_revision_id.CreativeRevisionId - revision_content_digest: Annotated[ - str, - Field( - description='Verified digest from the complete source representation set. A downstream seller uses this value, not the selected manifest bytes alone, to enforce immutable revision reuse.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] - selected_representation_id: Annotated[str, Field(min_length=1)] - strategy: Annotated[ - representation_selection_strategy.RepresentationSelectionStrategy, - Field(description='Deterministic strategy applied after compatibility filtering.'), - ] - selected_output_digest: Annotated[ - str, - Field( - description='Digest of the derived seller-bound manifest projection, computed with RFC 8785 JCS after removing exactly top-level `$schema`, `representation_selection`, `creative_id`, `revision_id`, `name`, `tags`, `status`, `weight`, `placement_refs`, `placement_ids`, and `inputs`. Every other field, including unknown delivery fields, is included. The identical exclusion list applies to the returned CreativeManifest and its later CreativeAsset sync wrapper. A selected sync item cannot carry localization in this version. This is the review/execution fingerprint for the projection, distinct from revision_content_digest, which binds the complete source set.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] - execution_vast_version: Annotated[ - vast_version.VastVersion | None, - Field( - description='Exact VAST version verified for seller-assembled first-class VAST trackers. This is selection lineage and part of the execution review identity; it is not included in selected_output_digest.' - ), - ] = None - execution_daast_version: Annotated[ - daast_version.DaastVersion | None, - Field( - description='Exact DAAST version verified for seller-assembled first-class DAAST trackers. This is selection lineage and part of the execution review identity; it is not included in selected_output_digest.' - ), - ] = None - resolved_by: Annotated[ - ResolvedBy, - Field( - description='Who performed deterministic compatibility resolution. A buyer may resolve locally from seller discovery. Seller resolution is valid only on the destination sales agent when it advertised representation_resolution; an independent creative agent cannot claim seller-bound compatibility.' - ), - ] diff --git a/src/adcp/types/generated_poc/core/requirements/__init__.py b/src/adcp/types/generated_poc/core/requirements/__init__.py deleted file mode 100644 index 27a34857c..000000000 --- a/src/adcp/types/generated_poc/core/requirements/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# generated by datamodel-codegen: -# filename: .schema_temp -# timestamp: 2026-02-12T02:11:19+00:00 diff --git a/src/adcp/types/generated_poc/core/requirements/asset_requirements.py b/src/adcp/types/generated_poc/core/requirements/asset_requirements.py deleted file mode 100644 index 13a6f7d3d..000000000 --- a/src/adcp/types/generated_poc/core/requirements/asset_requirements.py +++ /dev/null @@ -1,44 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/requirements/asset_requirements.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import Field - -from . import ( - audio_asset_requirements, - css_asset_requirements, - daast_asset_requirements, - html_asset_requirements, - image_asset_requirements, - javascript_asset_requirements, - markdown_asset_requirements, - text_asset_requirements, - url_asset_requirements, - vast_asset_requirements, - video_asset_requirements, - webhook_asset_requirements, -) - - -AssetRequirements = Annotated[ - image_asset_requirements.ImageAssetRequirements - | video_asset_requirements.VideoAssetRequirements - | audio_asset_requirements.AudioAssetRequirements - | text_asset_requirements.TextAssetRequirements - | markdown_asset_requirements.MarkdownAssetRequirements - | html_asset_requirements.HtmlAssetRequirements - | css_asset_requirements.CssAssetRequirements - | javascript_asset_requirements.JavascriptAssetRequirements - | vast_asset_requirements.VastAssetRequirements - | daast_asset_requirements.DaastAssetRequirements - | url_asset_requirements.UrlAssetRequirements - | webhook_asset_requirements.WebhookAssetRequirements, - Field( - description='Technical requirements for creative assets. The applicable schema is determined by the sibling asset_type field.', - title='Asset Requirements', - ), -] diff --git a/src/adcp/types/generated_poc/core/requirements/audio_asset_requirements.py b/src/adcp/types/generated_poc/core/requirements/audio_asset_requirements.py deleted file mode 100644 index 673dd0b39..000000000 --- a/src/adcp/types/generated_poc/core/requirements/audio_asset_requirements.py +++ /dev/null @@ -1,72 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/requirements/audio_asset_requirements.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarInt -from pydantic import ConfigDict, Field, StrictFloat - - -class Format(StrEnum): - mp3 = 'mp3' - aac = 'aac' - wav = 'wav' - ogg = 'ogg' - flac = 'flac' - - -class SampleRate(ScalarInt): - __slots__ = () - _constraints = {'ge': 1} - - -class Channel(StrEnum): - mono = 'mono' - stereo = 'stereo' - - -class AudioAssetRequirements(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - min_duration_ms: Annotated[ - SchemaInt | None, Field(description='Minimum duration in milliseconds', ge=1) - ] = None - max_duration_ms: Annotated[ - SchemaInt | None, Field(description='Maximum duration in milliseconds', ge=1) - ] = None - formats: Annotated[list[Format] | None, Field(description='Accepted audio file formats')] = None - max_file_size_kb: Annotated[ - SchemaInt | None, Field(description='Maximum file size in kilobytes', ge=1) - ] = None - sample_rates: Annotated[ - list[SampleRate] | None, - Field(description='Accepted sample rates in Hz (e.g., [44100, 48000])'), - ] = None - channels: Annotated[ - list[Channel] | None, Field(description='Accepted audio channel configurations') - ] = None - min_bitrate_kbps: Annotated[ - SchemaInt | None, Field(description='Minimum audio bitrate in kilobits per second', ge=1) - ] = None - max_bitrate_kbps: Annotated[ - SchemaInt | None, Field(description='Maximum audio bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[ - StrictFloat | None, - Field( - description='Target integrated loudness in LUFS. LKFS is the equivalent unit name under ITU-R BS.1770; values expressed in LUFS and LKFS are directly comparable without conversion.' - ), - ] = None - loudness_tolerance_db: Annotated[ - StrictFloat | None, - Field(description='Acceptable deviation from the loudness_lufs target in dB.', ge=0.0), - ] = None - true_peak_dbfs: Annotated[ - StrictFloat | None, Field(description='Maximum true-peak level in dBFS.') - ] = None diff --git a/src/adcp/types/generated_poc/core/requirements/catalog_field_binding.py b/src/adcp/types/generated_poc/core/requirements/catalog_field_binding.py deleted file mode 100644 index c3d6bfcd6..000000000 --- a/src/adcp/types/generated_poc/core/requirements/catalog_field_binding.py +++ /dev/null @@ -1,144 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/requirements/catalog_field_binding.json -# timestamp: 2026-10-03T00:36:50+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from .. import ext as ext_1 - - -class ScalarBinding(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['scalar'] = 'scalar' - asset_id: Annotated[ - str, - Field( - description="The asset_id from the format's assets array. Identifies which individual template slot this binding applies to." - ), - ] - catalog_field: Annotated[ - str, - Field( - description="Dot-notation path to the field on the catalog item (e.g., 'name', 'price.amount', 'location.city')." - ), - ] - ext: ext_1.ExtensionObject | None = None - - -class AssetPoolBinding(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['asset_pool'] = 'asset_pool' - asset_id: Annotated[ - str, - Field( - description="The asset_id from the format's assets array. Identifies which individual template slot this binding applies to." - ), - ] - asset_group_id: Annotated[ - str, - Field( - description="The asset_group_id on the catalog item's assets array to pull from (e.g., 'images_landscape', 'images_vertical', 'logo')." - ), - ] - ext: ext_1.ExtensionObject | None = None - - -PerItemBindings = Annotated[ - ScalarBinding | AssetPoolBinding, - Field(discriminator='kind'), -] - - -class CatalogFieldBinding1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['catalog_group'] = 'catalog_group' - format_group_id: Annotated[ - str, - Field(description="The asset_group_id of a repeatable_group in the format's assets array."), - ] - catalog_item: Annotated[ - Literal[True], - Field( - description="Each repetition of the format's repeatable_group maps to one item from the catalog." - ), - ] - per_item_bindings: Annotated[ - list[PerItemBindings] | None, - Field( - description='Scalar and asset pool bindings that apply within each repetition of the group. Nested catalog_group bindings are not permitted.', - min_length=1, - ), - ] = None - ext: ext_1.ExtensionObject | None = None - - -CatalogFieldBinding = Annotated[ - ScalarBinding | AssetPoolBinding | CatalogFieldBinding1, - Field( - description="Maps a format template slot to a catalog item field or typed asset pool. The 'kind' field identifies the binding variant. All bindings are optional — agents can still infer mappings without them.", - discriminator='kind', - examples=[ - { - 'description': 'Scalar binding — hotel name to headline slot', - 'data': {'kind': 'scalar', 'asset_id': 'headline', 'catalog_field': 'name'}, - }, - { - 'description': 'Scalar binding — nested field (nightly rate)', - 'data': { - 'kind': 'scalar', - 'asset_id': 'price_badge', - 'catalog_field': 'price.amount', - }, - }, - { - 'description': 'Asset pool binding — hero image from landscape pool', - 'data': { - 'kind': 'asset_pool', - 'asset_id': 'hero_image', - 'asset_group_id': 'images_landscape', - }, - }, - { - 'description': 'Asset pool binding — Snap vertical background from vertical pool', - 'data': { - 'kind': 'asset_pool', - 'asset_id': 'snap_background', - 'asset_group_id': 'images_vertical', - }, - }, - { - 'description': 'Catalog group binding — carousel where each slide is one hotel', - 'data': { - 'kind': 'catalog_group', - 'format_group_id': 'slide', - 'catalog_item': True, - 'per_item_bindings': [ - {'kind': 'scalar', 'asset_id': 'title', 'catalog_field': 'name'}, - { - 'kind': 'scalar', - 'asset_id': 'price', - 'catalog_field': 'price.amount', - }, - { - 'kind': 'asset_pool', - 'asset_id': 'image', - 'asset_group_id': 'images_landscape', - }, - ], - }, - }, - ], - title='Catalog Field Binding', - ), -] diff --git a/src/adcp/types/generated_poc/core/requirements/catalog_requirements.py b/src/adcp/types/generated_poc/core/requirements/catalog_requirements.py deleted file mode 100644 index 81471366d..000000000 --- a/src/adcp/types/generated_poc/core/requirements/catalog_requirements.py +++ /dev/null @@ -1,72 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/requirements/catalog_requirements.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictBool - -from ...enums import catalog_type as catalog_type_1 -from ...enums import feed_format -from . import catalog_field_binding, offering_asset_constraint - - -class CatalogRequirements(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - catalog_type: Annotated[ - catalog_type_1.CatalogType, - Field(description='The catalog type this requirement applies to'), - ] - required: Annotated[ - StrictBool | None, - Field( - description='Whether this catalog type must be present. When true, creatives using this format must reference a synced catalog of this type.' - ), - ] = True - min_items: Annotated[ - SchemaInt | None, - Field( - description='Minimum number of items the catalog must contain for this format to render properly (e.g., a carousel might require at least 3 products)', - ge=1, - ), - ] = None - max_items: Annotated[ - SchemaInt | None, - Field( - description='Maximum number of items the format can render. Items beyond this limit are ignored. Useful for fixed-slot layouts (e.g., a 3-product card) or feed-size constraints.', - ge=1, - ), - ] = None - required_fields: Annotated[ - list[str] | None, - Field( - description="Fields that must be present and non-empty on every item in the catalog. Field names are catalog-type-specific (e.g., 'title', 'price', 'image_url' for product catalogs; 'store_id', 'quantity' for inventory feeds).", - min_length=1, - ), - ] = None - feed_formats: Annotated[ - list[feed_format.FeedFormat] | None, - Field( - description='Accepted feed formats for this catalog type. When specified, the synced catalog must use one of these formats. When omitted, any format is accepted.', - min_length=1, - ), - ] = None - offering_asset_constraints: Annotated[ - list[offering_asset_constraint.OfferingAssetConstraint] | None, - Field( - description="Per-item creative asset requirements. Declares what asset groups (headlines, images, videos) each catalog item must provide in its assets array, along with count bounds and per-asset technical constraints. Applicable to 'offering' and all vertical catalog types (hotel, flight, job, etc.) whose items carry typed assets.", - min_length=1, - ), - ] = None - field_bindings: Annotated[ - list[catalog_field_binding.CatalogFieldBinding] | None, - Field( - description='Explicit mappings from format template slots to catalog item fields or typed asset pools. Optional — creative agents can infer mappings without them, but bindings make the relationship self-describing and enable validation. Covers scalar fields (asset_id → catalog_field), asset pools (asset_id → asset_group_id on the catalog item), and repeatable groups that iterate over catalog items.', - min_length=1, - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/requirements/css_asset_requirements.py b/src/adcp/types/generated_poc/core/requirements/css_asset_requirements.py deleted file mode 100644 index 96bdaef76..000000000 --- a/src/adcp/types/generated_poc/core/requirements/css_asset_requirements.py +++ /dev/null @@ -1,19 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/requirements/css_asset_requirements.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field - - -class CssAssetRequirements(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - max_file_size_kb: Annotated[ - SchemaInt | None, Field(description='Maximum file size in kilobytes', ge=1) - ] = None diff --git a/src/adcp/types/generated_poc/core/requirements/daast_asset_requirements.py b/src/adcp/types/generated_poc/core/requirements/daast_asset_requirements.py deleted file mode 100644 index 1b591a10b..000000000 --- a/src/adcp/types/generated_poc/core/requirements/daast_asset_requirements.py +++ /dev/null @@ -1,30 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/requirements/daast_asset_requirements.json -# timestamp: 2026-08-28T20:03:29+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ...enums import daast_version as daast_version_1 -from .. import daast_tracker_constraints - - -class DaastAssetRequirements(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - daast_version: Annotated[ - daast_version_1.DaastVersion | None, - Field( - deprecated=True, - description='Deprecated one-element alias for `daast_versions`. Producers use either the singular legacy alias or the plural 3.2 field, never both.', - ), - ] = None - daast_versions: Annotated[ - daast_tracker_constraints.DaastVersions | None, - Field(description='Accepted DAAST version set for this asset requirement.'), - ] = None diff --git a/src/adcp/types/generated_poc/core/requirements/html_asset_requirements.py b/src/adcp/types/generated_poc/core/requirements/html_asset_requirements.py deleted file mode 100644 index 303810f4c..000000000 --- a/src/adcp/types/generated_poc/core/requirements/html_asset_requirements.py +++ /dev/null @@ -1,46 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/requirements/html_asset_requirements.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictBool - - -class Sandbox(StrEnum): - none = 'none' - iframe = 'iframe' - safeframe = 'safeframe' - fencedframe = 'fencedframe' - - -class HtmlAssetRequirements(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - max_file_size_kb: Annotated[ - SchemaInt | None, - Field(description='Maximum file size in kilobytes for the HTML asset', ge=1), - ] = None - sandbox: Annotated[ - Sandbox | None, - Field( - description="Sandbox environment the HTML must be compatible with. 'none' = direct DOM access, 'iframe' = standard iframe isolation, 'safeframe' = IAB SafeFrame container, 'fencedframe' = Privacy Sandbox fenced frame" - ), - ] = Sandbox.none - external_resources_allowed: Annotated[ - StrictBool | None, - Field( - description='Whether the HTML creative can load external resources (scripts, images, fonts, etc.). When false, all resources must be inlined or bundled.' - ), - ] = None - allowed_external_domains: Annotated[ - list[str] | None, - Field( - description='List of domains the HTML creative may reference for external resources. Only applicable when external_resources_allowed is true.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/requirements/image_asset_requirements.py b/src/adcp/types/generated_poc/core/requirements/image_asset_requirements.py deleted file mode 100644 index f0a643f4e..000000000 --- a/src/adcp/types/generated_poc/core/requirements/image_asset_requirements.py +++ /dev/null @@ -1,159 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/requirements/image_asset_requirements.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarFloat -from pydantic import ConfigDict, Field, StrictBool, StrictFloat - -from ...enums import dimension_unit - - -class PixelRatio(ScalarFloat): - __slots__ = () - _constraints = {'gt': 0.0} - - -class Format(StrEnum): - jpg = 'jpg' - jpeg = 'jpeg' - png = 'png' - gif = 'gif' - webp = 'webp' - svg = 'svg' - avif = 'avif' - tiff = 'tiff' - pdf = 'pdf' - eps = 'eps' - - -class Bleed(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - uniform: Annotated[StrictFloat, Field(description='Same bleed on all four sides', ge=0.0)] - - -class Bleed1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - top: Annotated[StrictFloat, Field(ge=0.0)] - right: Annotated[StrictFloat, Field(ge=0.0)] - bottom: Annotated[StrictFloat, Field(ge=0.0)] - left: Annotated[StrictFloat, Field(ge=0.0)] - - -class ColorSpace(StrEnum): - rgb = 'rgb' - cmyk = 'cmyk' - grayscale = 'grayscale' - - -class ImageAssetRequirements(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - min_width: Annotated[ - StrictFloat | None, - Field( - description='Minimum width. Interpretation depends on unit (default: pixels). For exact dimensions, set min_width = max_width.', - gt=0.0, - ), - ] = None - max_width: Annotated[ - StrictFloat | None, - Field( - description='Maximum width. Interpretation depends on unit (default: pixels). For exact dimensions, set min_width = max_width.', - gt=0.0, - ), - ] = None - min_height: Annotated[ - StrictFloat | None, - Field( - description='Minimum height. Interpretation depends on unit (default: pixels). For exact dimensions, set min_height = max_height.', - gt=0.0, - ), - ] = None - max_height: Annotated[ - StrictFloat | None, - Field( - description='Maximum height. Interpretation depends on unit (default: pixels). For exact dimensions, set min_height = max_height.', - gt=0.0, - ), - ] = None - pixel_ratios: Annotated[ - list[PixelRatio] | None, - Field( - description='Accepted intrinsic-pixel densities for this image slot. One submitted image matching any listed ratio satisfies the slot; the array does not require one asset per ratio. Absence imposes no density constraint beyond the existing dimension requirements.', - min_length=1, - ), - ] = None - parameters_from_format_id: Annotated[ - StrictBool | None, - Field( - description="When true on a legacy template format, logical width, logical height, and pixel ratio are supplied by the parameterized format_id. The image asset's intrinsic dimensions MUST equal logical dimensions multiplied by pixel_ratio (which defaults to 1 when omitted)." - ), - ] = None - unit: Annotated[ - dimension_unit.DimensionUnit | None, - Field( - description="Unit of measurement for width/height values. Defaults to 'px' when absent. Print formats use 'inches' or 'cm'." - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description="Required aspect ratio (e.g., '16:9', '1:1', '1.91:1')", - pattern='^\\d+(\\.\\d+)?:\\d+(\\.\\d+)?$', - ), - ] = None - formats: Annotated[list[Format] | None, Field(description='Accepted image file formats')] = None - min_dpi: Annotated[ - SchemaInt | None, - Field( - description='Minimum resolution in dots per inch. Always in DPI regardless of the dimension unit. Standard print requires 300 DPI, newspaper 150 DPI.', - ge=1, - ), - ] = None - bleed: Annotated[ - Bleed | Bleed1 | None, - Field( - description='Required bleed area beyond the trim size. The submitted image must be larger than the declared dimensions: total width = trim width + left bleed + right bleed, total height = trim height + top bleed + bottom bleed. For uniform bleed: total = trim + (2 * uniform). Uses the same unit as the parent dimensions.' - ), - ] = None - color_space: Annotated[ - ColorSpace | None, Field(description='Required color space. Print typically requires CMYK.') - ] = None - max_file_size_kb: Annotated[ - SchemaInt | None, Field(description='Maximum file size in kilobytes', ge=1) - ] = None - transparency_required: Annotated[ - StrictBool | None, - Field( - description='Whether the image must support transparency (requires PNG, WebP, or GIF)' - ), - ] = None - animation_allowed: Annotated[ - StrictBool | None, - Field(description='Whether animated images (GIF, animated WebP) are accepted'), - ] = None - max_animation_duration_ms: Annotated[ - SchemaInt | None, - Field( - description='Maximum animation duration in milliseconds (if animation_allowed is true)', - ge=0, - ), - ] = None - max_weight_grams: Annotated[ - SchemaInt | None, - Field( - description='Maximum weight in grams for the finished physical piece (print inserts, flyers). Affects postage calculations and production constraints. Only applicable to print channels.', - gt=0, - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/requirements/javascript_asset_requirements.py b/src/adcp/types/generated_poc/core/requirements/javascript_asset_requirements.py deleted file mode 100644 index a2757e7e9..000000000 --- a/src/adcp/types/generated_poc/core/requirements/javascript_asset_requirements.py +++ /dev/null @@ -1,46 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/requirements/javascript_asset_requirements.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictBool - - -class ModuleType(StrEnum): - script = 'script' - module = 'module' - iife = 'iife' - - -class JavascriptAssetRequirements(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - max_file_size_kb: Annotated[ - SchemaInt | None, - Field(description='Maximum file size in kilobytes for the JavaScript asset', ge=1), - ] = None - module_type: Annotated[ - ModuleType | None, - Field( - description="Required JavaScript module format. 'script' = classic script, 'module' = ES modules, 'iife' = immediately invoked function expression" - ), - ] = None - strict_mode_required: Annotated[ - StrictBool | None, Field(description='Whether the JavaScript must use strict mode') - ] = None - external_resources_allowed: Annotated[ - StrictBool | None, - Field(description='Whether the JavaScript can load external resources dynamically'), - ] = None - allowed_external_domains: Annotated[ - list[str] | None, - Field( - description='List of domains the JavaScript may reference for external resources. Only applicable when external_resources_allowed is true.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/requirements/markdown_asset_requirements.py b/src/adcp/types/generated_poc/core/requirements/markdown_asset_requirements.py deleted file mode 100644 index ff057afdc..000000000 --- a/src/adcp/types/generated_poc/core/requirements/markdown_asset_requirements.py +++ /dev/null @@ -1,19 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/requirements/markdown_asset_requirements.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field - - -class MarkdownAssetRequirements(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - max_length: Annotated[SchemaInt | None, Field(description='Maximum character length', ge=1)] = ( - None - ) diff --git a/src/adcp/types/generated_poc/core/requirements/offering_asset_constraint.py b/src/adcp/types/generated_poc/core/requirements/offering_asset_constraint.py deleted file mode 100644 index 752c31af6..000000000 --- a/src/adcp/types/generated_poc/core/requirements/offering_asset_constraint.py +++ /dev/null @@ -1,49 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/requirements/offering_asset_constraint.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictBool - -from ...enums import asset_content_type -from .. import ext as ext_1 -from . import asset_requirements as asset_requirements_1 - - -class OfferingAssetConstraint(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_group_id: Annotated[ - str, - Field( - description="The asset group this constraint applies to. Values are canonical-format vocabulary — each declaration chooses its own group IDs (e.g., 'headlines', 'images', 'videos'). Buyers discover them through Product.format_options[], publisher adagents.json formats[], or creative.supported_formats[] according to context." - ), - ] - asset_type: Annotated[ - asset_content_type.AssetContentType, - Field(description='The expected content type for this group.'), - ] - required: Annotated[ - StrictBool | None, - Field( - description='Whether this asset group must be present in each offering. Defaults to true.' - ), - ] = True - min_count: Annotated[ - SchemaInt | None, Field(description='Minimum number of items required in this group.', ge=1) - ] = None - max_count: Annotated[ - SchemaInt | None, Field(description='Maximum number of items allowed in this group.', ge=1) - ] = None - asset_requirements: Annotated[ - asset_requirements_1.AssetRequirements | None, - Field( - description='Technical requirements for each item in this group (e.g., max_length for text, min_width/aspect_ratio for images). Applies uniformly to all items in the group.' - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/requirements/text_asset_requirements.py b/src/adcp/types/generated_poc/core/requirements/text_asset_requirements.py deleted file mode 100644 index 9ae90b27e..000000000 --- a/src/adcp/types/generated_poc/core/requirements/text_asset_requirements.py +++ /dev/null @@ -1,44 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/requirements/text_asset_requirements.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field - - -class TextAssetRequirements(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - min_length: Annotated[SchemaInt | None, Field(description='Minimum character length', ge=0)] = ( - None - ) - max_length: Annotated[SchemaInt | None, Field(description='Maximum character length', ge=1)] = ( - None - ) - min_lines: Annotated[SchemaInt | None, Field(description='Minimum number of lines', ge=1)] = ( - None - ) - max_lines: Annotated[SchemaInt | None, Field(description='Maximum number of lines', ge=1)] = ( - None - ) - character_pattern: Annotated[ - str | None, - Field( - description="Regex pattern defining allowed characters (e.g., '^[a-zA-Z0-9 .,!?-]+$')" - ), - ] = None - prohibited_terms: Annotated[ - list[str] | None, Field(description='List of prohibited words or phrases') - ] = None - allowed_values: Annotated[ - list[str] | None, - Field( - description='Closed set of permitted string values for this text slot. When present, a conformant implementation MUST reject any submitted content not in this list with `CREATIVE_VALUE_NOT_ALLOWED` (echoing the offending field path in `error.field` and the allowed list in `error.details.allowed_values`). Matching is case-sensitive; producers MUST supply exact casing. If the submitted value contains unresolved template tokens (e.g., {{product_name}}), validation against allowed_values MUST be deferred until interpolation is complete. All declared constraints (allowed_values, character_pattern, max_length, etc.) are conjunctive — submitted content must satisfy every applicable constraint simultaneously.', - min_length=1, - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/requirements/url_asset_requirements.py b/src/adcp/types/generated_poc/core/requirements/url_asset_requirements.py deleted file mode 100644 index 32f541574..000000000 --- a/src/adcp/types/generated_poc/core/requirements/url_asset_requirements.py +++ /dev/null @@ -1,53 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/requirements/url_asset_requirements.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictBool - - -class Role(StrEnum): - clickthrough = 'clickthrough' - landing_page = 'landing_page' - impression_tracker = 'impression_tracker' - click_tracker = 'click_tracker' - viewability_tracker = 'viewability_tracker' - third_party_tracker = 'third_party_tracker' - - -class Protocol(StrEnum): - https = 'https' - http = 'http' - - -class UrlAssetRequirements(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - role: Annotated[ - Role | None, - Field( - description="Purpose this URL slot serves in the format — distinct from `url_type` on the manifest-side asset (which declares the receiver's invocation mechanism). A slot can be `click_tracker` (purpose) and accept a `tracker_pixel` (mechanism) URL, or `clickthrough` (purpose) and accept a `clickthrough` (mechanism) URL. Complements `asset_role` (human-readable label) by providing a machine-readable enum and serves as the receiver's fallback signal when a manifest URL asset omits `url_type`." - ), - ] = None - protocols: Annotated[ - list[Protocol] | None, - Field(description='Allowed URL protocols. HTTPS is recommended for all ad URLs.'), - ] = None - allowed_domains: Annotated[ - list[str] | None, Field(description='List of allowed domains for the URL') - ] = None - max_length: Annotated[ - SchemaInt | None, Field(description='Maximum URL length in characters', ge=1) - ] = None - macro_support: Annotated[ - StrictBool | None, - Field( - description="Coarse slot gate for macro-bearing URLs. `false` is a hard prohibition on macro tokens. `true` permits tokens but does not prove any dialect semantic, resolver, context, or encoding capability; exact support comes from the selected format option's macro_resolution_capabilities." - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/requirements/vast_asset_requirements.py b/src/adcp/types/generated_poc/core/requirements/vast_asset_requirements.py deleted file mode 100644 index f778813a2..000000000 --- a/src/adcp/types/generated_poc/core/requirements/vast_asset_requirements.py +++ /dev/null @@ -1,36 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/requirements/vast_asset_requirements.json -# timestamp: 2026-08-28T20:03:29+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ...enums import vast_version as vast_version_1 -from .. import vast_media_file_requirements - - -class VastAssetRequirements(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vast_version: Annotated[ - vast_version_1.VastVersion | None, - Field( - deprecated=True, - description='Deprecated one-element alias for `vast_versions`. Producers use either the singular legacy alias or the plural 3.2 field, never both.', - ), - ] = None - vast_versions: Annotated[ - list[vast_version_1.VastVersion] | None, - Field(description='Accepted VAST version set for this asset requirement.', min_length=1), - ] = None - media_file_requirements: Annotated[ - vast_media_file_requirements.VastMediafileRequirements | None, - Field( - description='Technical acceptance constraints for the alternative MediaFile renditions in each applicable resolved InLine linear creative.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/requirements/video_asset_requirements.py b/src/adcp/types/generated_poc/core/requirements/video_asset_requirements.py deleted file mode 100644 index 9bb6b1477..000000000 --- a/src/adcp/types/generated_poc/core/requirements/video_asset_requirements.py +++ /dev/null @@ -1,166 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/requirements/video_asset_requirements.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarFloat, ScalarInt -from pydantic import ConfigDict, Field, StrictBool, StrictFloat - -from ...enums import audio_channel_layout -from ...enums import frame_rate_type as frame_rate_type_1 -from ...enums import gop_type as gop_type_1 -from ...enums import moov_atom_position as moov_atom_position_1 -from ...enums import scan_type as scan_type_1 - - -class Container(StrEnum): - mp4 = 'mp4' - webm = 'webm' - mov = 'mov' - avi = 'avi' - mkv = 'mkv' - - -class Codec(StrEnum): - h264 = 'h264' - h265 = 'h265' - vp8 = 'vp8' - vp9 = 'vp9' - av1 = 'av1' - prores = 'prores' - - -class FrameRate(ScalarFloat): - __slots__ = () - _constraints = {'ge': 1.0} - - -class AudioCodec(StrEnum): - aac = 'aac' - pcm = 'pcm' - ac3 = 'ac3' - eac3 = 'eac3' - mp3 = 'mp3' - opus = 'opus' - vorbis = 'vorbis' - flac = 'flac' - - -class AudioSampleRate(ScalarInt): - __slots__ = () - _constraints = {'ge': 1} - - -class VideoAssetRequirements(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - min_width: Annotated[SchemaInt | None, Field(description='Minimum width in pixels', ge=1)] = ( - None - ) - max_width: Annotated[SchemaInt | None, Field(description='Maximum width in pixels', ge=1)] = ( - None - ) - min_height: Annotated[SchemaInt | None, Field(description='Minimum height in pixels', ge=1)] = ( - None - ) - max_height: Annotated[SchemaInt | None, Field(description='Maximum height in pixels', ge=1)] = ( - None - ) - aspect_ratio: Annotated[ - str | None, - Field(description="Required aspect ratio (e.g., '16:9', '9:16')", pattern='^\\d+:\\d+$'), - ] = None - min_duration_ms: Annotated[ - SchemaInt | None, Field(description='Minimum duration in milliseconds', ge=1) - ] = None - max_duration_ms: Annotated[ - SchemaInt | None, Field(description='Maximum duration in milliseconds', ge=1) - ] = None - containers: Annotated[ - list[Container] | None, Field(description='Accepted video container formats') - ] = None - codecs: Annotated[list[Codec] | None, Field(description='Accepted video codecs')] = None - max_file_size_kb: Annotated[ - SchemaInt | None, - Field(description='Maximum file size, where 1 KB is exactly 1,000 bytes', ge=1), - ] = None - min_bitrate_kbps: Annotated[ - SchemaInt | None, Field(description='Minimum video bitrate in kilobits per second', ge=1) - ] = None - max_bitrate_kbps: Annotated[ - SchemaInt | None, Field(description='Maximum video bitrate in kilobits per second', ge=1) - ] = None - frame_rates: Annotated[ - list[FrameRate] | None, - Field(description='Accepted frame rates in frames per second (e.g., [24, 30, 60])'), - ] = None - audio_required: Annotated[ - StrictBool | None, Field(description='Whether the video must include an audio track') - ] = None - frame_rate_type: Annotated[ - frame_rate_type_1.FrameRateType | None, - Field( - description='Required frame rate type. Broadcast and SSAI require constant frame rate for seamless splicing.' - ), - ] = None - scan_type: Annotated[ - scan_type_1.ScanType | None, - Field(description='Required scan type. Modern delivery requires progressive scan.'), - ] = None - gop_type: Annotated[ - gop_type_1.GopType | None, - Field( - description='Required GOP structure. SSAI and broadcast require closed GOPs for clean splice points.' - ), - ] = None - min_gop_interval_seconds: Annotated[ - StrictFloat | None, Field(description='Minimum keyframe interval in seconds', ge=0.0) - ] = None - max_gop_interval_seconds: Annotated[ - StrictFloat | None, - Field( - description='Maximum keyframe interval in seconds. SSAI typically requires 1-2 second intervals.', - ge=0.0, - ), - ] = None - moov_atom_position: Annotated[ - moov_atom_position_1.MoovAtomPosition | None, - Field( - description="Required moov atom position in MP4 container. 'start' enables progressive download without buffering the entire file." - ), - ] = None - audio_codecs: Annotated[ - list[AudioCodec] | None, - Field(description="Accepted audio codecs (e.g., ['aac', 'pcm', 'ac3'])"), - ] = None - audio_sample_rates: Annotated[ - list[AudioSampleRate] | None, - Field(description='Accepted audio sample rates in Hz (e.g., [44100, 48000])'), - ] = None - audio_channels: Annotated[ - list[audio_channel_layout.AudioChannelLayout] | None, - Field(description='Accepted audio channel configurations'), - ] = None - loudness_lufs: Annotated[ - StrictFloat | None, - Field( - description='Target integrated loudness in LUFS (e.g., -24 for broadcast, -16 for streaming)' - ), - ] = None - loudness_tolerance_db: Annotated[ - StrictFloat | None, - Field( - description='Acceptable deviation from loudness_lufs target in dB (e.g., 2 means -22 to -26 LUFS for a -24 target)', - ge=0.0, - ), - ] = None - true_peak_dbfs: Annotated[ - StrictFloat | None, - Field(description='Maximum true peak level in dBFS (e.g., -2 for broadcast)'), - ] = None diff --git a/src/adcp/types/generated_poc/core/requirements/webhook_asset_requirements.py b/src/adcp/types/generated_poc/core/requirements/webhook_asset_requirements.py deleted file mode 100644 index bd1e92bab..000000000 --- a/src/adcp/types/generated_poc/core/requirements/webhook_asset_requirements.py +++ /dev/null @@ -1,23 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/requirements/webhook_asset_requirements.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - - -class Method(StrEnum): - GET = 'GET' - POST = 'POST' - - -class WebhookAssetRequirements(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - methods: Annotated[list[Method] | None, Field(description='Allowed HTTP methods')] = None diff --git a/src/adcp/types/generated_poc/core/response.py b/src/adcp/types/generated_poc/core/response.py deleted file mode 100644 index 4898fb1a2..000000000 --- a/src/adcp/types/generated_poc/core/response.py +++ /dev/null @@ -1,29 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/response.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - - -class ProtocolResponse(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - message: Annotated[str, Field(description='Human-readable summary')] - context_id: Annotated[ - str | None, - Field( - description='Transport-managed conversation identifier. Maps to native contextId on A2A; compatibility metadata only on MCP and not a continuation or authorization mechanism.' - ), - ] = None - data: Annotated[ - Any | None, - Field( - description='AdCP task-specific response data (see individual task response schemas)' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/response_payload_jws_envelope.py b/src/adcp/types/generated_poc/core/response_payload_jws_envelope.py deleted file mode 100644 index 17e1f4f20..000000000 --- a/src/adcp/types/generated_poc/core/response_payload_jws_envelope.py +++ /dev/null @@ -1,87 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/response_payload_jws_envelope.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, ConfigDict, Field - - -class Task(StrEnum): - verify_brand_claim = 'verify_brand_claim' - verify_brand_claims = 'verify_brand_claims' - - -class ResponsePayload(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - typ: Annotated[ - Literal['adcp-response-payload+jws'], - Field(description='Type discriminator preventing cross-profile replay.'), - ] - task: Annotated[Task, Field(description='Designated task whose response payload is signed.')] - brand_domain: Annotated[ - str, - Field( - description='Brand tenant whose policy store produced the answer. The signer MUST derive this from server-side tenant resolution, not caller-supplied request fields.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - agent_url: Annotated[ - AnyUrl, - Field( - description='Canonical URL of the responding brand agent entry whose response-signing key verifies this envelope.' - ), - ] - request_hash: Annotated[ - str, - Field( - description='sha256: prefix plus unpadded base64url SHA-256 of the canonical request-binding object for this call.', - pattern='^sha256:[A-Za-z0-9_-]{43}$', - ), - ] - iat: Annotated[SchemaInt, Field(description='Issued-at time as Unix epoch seconds.', ge=0)] - exp: Annotated[ - SchemaInt, - Field( - description='Expiration time as Unix epoch seconds. Online verifiers reject envelopes after this time, allowing only implementation-defined clock skew.', - ge=0, - ), - ] - response: Annotated[ - dict[str, Any], - Field( - description='Canonical task-body success response payload being attested. Any unsigned task-body fields on the outer response, excluding signed_response and protocol/version envelope fields, MUST match this object.' - ), - ] - - -class ResponsePayloadJwsEnvelope(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - protected: Annotated[ - str, - Field( - description='Base64url-encoded JWS protected header. The decoded header MUST include alg, kid, and typ: adcp-response-payload+jws, and MUST NOT include the RFC 7797 b64 header. Verifiers enforce the key purpose by resolving kid to a JWK with adcp_use: response-signing.', - pattern='^[A-Za-z0-9_-]+$', - ), - ] - payload: Annotated[ - ResponsePayload, - Field( - description='Decoded signed payload. Signers compute the JWS payload bytes from the RFC 8785/JCS canonicalization of this object.' - ), - ] - signature: Annotated[ - str, - Field( - description='Base64url-encoded JWS signature over the protected header and canonicalized payload.', - pattern='^[A-Za-z0-9_-]+$', - ), - ] diff --git a/src/adcp/types/generated_poc/core/rights_attestation_evaluation.py b/src/adcp/types/generated_poc/core/rights_attestation_evaluation.py deleted file mode 100644 index ebd0bc596..000000000 --- a/src/adcp/types/generated_poc/core/rights_attestation_evaluation.py +++ /dev/null @@ -1,227 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/rights_attestation_evaluation.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - -from . import brand_ref -from . import ext as ext_1 -from .attestation_evaluation import AttestationEvaluation -from .attestation_reference import AttestationReference - - -class Subject(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Literal['agent'] = 'agent' - resource_type: Literal['https://adcontextprotocol.org/claims/subjects/rights-grant'] = 'https://adcontextprotocol.org/claims/subjects/rights-grant' - agent_url: Annotated[ - AnyUrl, Field(description='Canonical HTTPS endpoint of the agent the claim concerns.') - ] - ext: ext_1.ExtensionObject | None = None - - -class Subject19(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Literal['resource'] = 'resource' - resource_type: Annotated[ - Literal['https://adcontextprotocol.org/claims/subjects/rights-grant'], - Field( - description='Open, absolute URI naming the subject vocabulary, such as https://adcontextprotocol.org/claims/subjects/signal. AdCP does not maintain an exhaustive enum.' - ), - ] = 'https://adcontextprotocol.org/claims/subjects/rights-grant' - namespace: Annotated[ - AnyUrl, - Field( - description='Absolute URI identifying the namespace in which id is unique. This may be an AdCP agent endpoint, a catalog origin, or a domain-specific namespace URI.' - ), - ] - id: Annotated[ - str, - Field( - description='Stable identifier for the subject within namespace. It MUST NOT be compared without resource_type and namespace.', - max_length=1024, - min_length=1, - ), - ] - content_digest: Annotated[ - str, - Field( - description='Optional SHA-256 pin for the exact content or immutable snapshot identified by this resource subject. This is part of the complete typed subject identity and is distinct from AttestationReference.content_digest, which pins credential bytes.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] - ext: ext_1.ExtensionObject | None = None - - -class ActionBinding(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - action_type: Annotated[ - Literal['https://adcontextprotocol.org/actions/rights-grant-evaluation'], - Field( - description='Absolute URI naming the action vocabulary, such as an AdCP governance-check or audience-evidence snapshot type.' - ), - ] = 'https://adcontextprotocol.org/actions/rights-grant-evaluation' - action_id: Annotated[ - str, - Field( - description="Stable identifier of the consuming action within the domain consumer's namespace.", - max_length=1024, - min_length=1, - ), - ] - action_digest: Annotated[ - str, - Field( - description='SHA-256 digest of the domain-defined canonical action preimage.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] - - -class Evaluation(AttestationEvaluation): - action_binding: Annotated[ - ActionBinding, - Field( - description='Optional binding to the consuming action or readback. Domain consumers that rely on an evaluation MUST carry either an action id or an action digest so the result cannot be transplanted to an unrelated decision.' - ), - ] - - -class Issuer(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Annotated[ - Literal['brand'], Field(description='The issuer is identified by an AdCP BrandRef.') - ] = 'brand' - brand: brand_ref.BrandReference - ext: ext_1.ExtensionObject | None = None - - -class Issuer5(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Annotated[ - Literal['agent'], - Field( - description='The issuer is an AdCP agent identified by its canonical HTTPS endpoint.' - ), - ] = 'agent' - brand: brand_ref.BrandReference - agent_url: Annotated[ - AnyUrl, - Field( - description='Canonical HTTPS endpoint of the issuing agent. Evaluators compare it using AdCP URL canonicalization rules.' - ), - ] - ext: ext_1.ExtensionObject | None = None - - -class Issuer6(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Annotated[ - Literal['origin'], - Field( - description='The issuer is identified by a canonical HTTPS origin because no AdCP brand or agent identity applies.' - ), - ] = 'origin' - brand: brand_ref.BrandReference - origin: Annotated[ - AnyUrl, - Field( - description='Canonical HTTPS origin with no path, query, fragment, or userinfo. This identifies the issuer; it does not authorize a fetch.' - ), - ] - ext: ext_1.ExtensionObject | None = None - - -class Subject10(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Literal['brand'] = 'brand' - resource_type: Literal['https://adcontextprotocol.org/claims/subjects/rights-grant'] = 'https://adcontextprotocol.org/claims/subjects/rights-grant' - brand: brand_ref.BrandReference - ext: ext_1.ExtensionObject | None = None - - -class Reference(AttestationReference): - issuer: Annotated[ - Issuer | Issuer5 | Issuer6 | None, - Field( - description='Canonical identity of the party that issued an attestation credential. The discriminator selects an existing AdCP identity when one exists and falls back to an HTTPS origin for other attestors. This identity is a claim carried by the presentation; evaluators MUST match it against their configured trust policy and verify it from the resolved or embedded credential before relying on it.', - discriminator='type', - examples=[ - { - 'type': 'brand', - 'brand': {'domain': 'nova-brands.example', 'brand_id': 'nova_motors'}, - }, - {'type': 'agent', 'agent_url': 'https://attestor.example/adcp'}, - {'type': 'origin', 'origin': 'https://credentials.example'}, - ], - title='Attestation Issuer', - ), - ] = None - claim_type: Annotated[ - Literal['https://adcontextprotocol.org/claims/rights/grant'], - Field( - description='Open, absolute URI identifying the claim vocabulary. The URI is an identifier and need not be dereferenceable. AdCP does not maintain an enum of approved claims.' - ), - ] = 'https://adcontextprotocol.org/claims/rights/grant' - subject: Annotated[ - Subject10 | Subject | Subject19 | None, - Field( - description='Typed identity of the entity or object an attestation credential is about. Brand and agent subjects reuse canonical AdCP identities. Other resources use an open, URI-namespaced resource_type plus an identifier whose namespace is explicit. Evaluators MUST compare the resolved credential subject to this complete typed identity, not to id alone.', - discriminator='type', - examples=[ - { - 'type': 'brand', - 'brand': {'domain': 'nova-brands.example', 'brand_id': 'nova_motors'}, - }, - { - 'type': 'resource', - 'resource_type': 'https://adcontextprotocol.org/claims/subjects/signal', - 'namespace': 'https://signals.meridian.example/adcp', - 'id': 'signal_urban_commuters', - }, - ], - title='Attestation Subject', - ), - ] = None - - -class RightsAttestationEvaluation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - rights_id: Annotated[ - str, - Field( - description='Rights grant identifier. MUST equal reference.subject.id and evaluation.action_binding.action_id.', - min_length=1, - ), - ] - content_digest: Annotated[ - str, - Field( - description='Digest of the exact rights constraint evaluated. MUST equal reference.subject.content_digest and evaluation.action_binding.action_digest.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] - reference: Reference - evaluation: Evaluation - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/rights_constraint.py b/src/adcp/types/generated_poc/core/rights_constraint.py deleted file mode 100644 index d5838dd94..000000000 --- a/src/adcp/types/generated_poc/core/rights_constraint.py +++ /dev/null @@ -1,318 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/rights_constraint.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarStr -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictBool - -from ..enums import right_type as right_type_1 -from ..enums import right_use -from . import brand_ref -from . import ext as ext_1 -from .attestation_reference import AttestationReference - - -class RightsAgent(AdCPBaseModel): - url: Annotated[AnyUrl, Field(description='MCP endpoint URL of the rights agent')] - id: Annotated[str, Field(description='Agent identifier')] - - -class Country(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{2}$'} - - -class ExcludedCountry(Country): - pass - - -class ApprovalStatus(StrEnum): - pending = 'pending' - approved = 'approved' - rejected = 'rejected' - - -class GrantStatus(StrEnum): - active = 'active' - paused = 'paused' - revoked = 'revoked' - - -class Restriction(ScalarStr): - __slots__ = () - _constraints = {'min_length': 1} - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - required: StrictBool - text: Annotated[str | None, Field(min_length=1)] = None - - -class Subject(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Literal['agent'] = 'agent' - resource_type: Literal['https://adcontextprotocol.org/claims/subjects/rights-grant'] = 'https://adcontextprotocol.org/claims/subjects/rights-grant' - agent_url: Annotated[ - AnyUrl, Field(description='Canonical HTTPS endpoint of the agent the claim concerns.') - ] - ext: ext_1.ExtensionObject | None = None - - -class Subject29(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Literal['resource'] = 'resource' - resource_type: Annotated[ - Literal['https://adcontextprotocol.org/claims/subjects/rights-grant'], - Field( - description='Open, absolute URI naming the subject vocabulary, such as https://adcontextprotocol.org/claims/subjects/signal. AdCP does not maintain an exhaustive enum.' - ), - ] = 'https://adcontextprotocol.org/claims/subjects/rights-grant' - namespace: Annotated[ - AnyUrl, - Field( - description='Absolute URI identifying the namespace in which id is unique. This may be an AdCP agent endpoint, a catalog origin, or a domain-specific namespace URI.' - ), - ] - id: Annotated[ - str, - Field( - description='Stable identifier for the subject within namespace. It MUST NOT be compared without resource_type and namespace.', - max_length=1024, - min_length=1, - ), - ] - content_digest: Annotated[ - str, - Field( - description='Optional SHA-256 pin for the exact content or immutable snapshot identified by this resource subject. This is part of the complete typed subject identity and is distinct from AttestationReference.content_digest, which pins credential bytes.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] - ext: ext_1.ExtensionObject | None = None - - -class Issuer(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Annotated[ - Literal['brand'], Field(description='The issuer is identified by an AdCP BrandRef.') - ] = 'brand' - brand: brand_ref.BrandReference - ext: ext_1.ExtensionObject | None = None - - -class Issuer8(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Annotated[ - Literal['agent'], - Field( - description='The issuer is an AdCP agent identified by its canonical HTTPS endpoint.' - ), - ] = 'agent' - brand: brand_ref.BrandReference - agent_url: Annotated[ - AnyUrl, - Field( - description='Canonical HTTPS endpoint of the issuing agent. Evaluators compare it using AdCP URL canonicalization rules.' - ), - ] - ext: ext_1.ExtensionObject | None = None - - -class Issuer9(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Annotated[ - Literal['origin'], - Field( - description='The issuer is identified by a canonical HTTPS origin because no AdCP brand or agent identity applies.' - ), - ] = 'origin' - brand: brand_ref.BrandReference - origin: Annotated[ - AnyUrl, - Field( - description='Canonical HTTPS origin with no path, query, fragment, or userinfo. This identifies the issuer; it does not authorize a fetch.' - ), - ] - ext: ext_1.ExtensionObject | None = None - - -class Subject20(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Literal['brand'] = 'brand' - resource_type: Literal['https://adcontextprotocol.org/claims/subjects/rights-grant'] = 'https://adcontextprotocol.org/claims/subjects/rights-grant' - brand: brand_ref.BrandReference - ext: ext_1.ExtensionObject | None = None - - -class AttestationRef(AttestationReference): - issuer: Annotated[ - Issuer | Issuer8 | Issuer9 | None, - Field( - description='Canonical identity of the party that issued an attestation credential. The discriminator selects an existing AdCP identity when one exists and falls back to an HTTPS origin for other attestors. This identity is a claim carried by the presentation; evaluators MUST match it against their configured trust policy and verify it from the resolved or embedded credential before relying on it.', - discriminator='type', - examples=[ - { - 'type': 'brand', - 'brand': {'domain': 'nova-brands.example', 'brand_id': 'nova_motors'}, - }, - {'type': 'agent', 'agent_url': 'https://attestor.example/adcp'}, - {'type': 'origin', 'origin': 'https://credentials.example'}, - ], - title='Attestation Issuer', - ), - ] = None - claim_type: Annotated[ - Literal['https://adcontextprotocol.org/claims/rights/grant'], - Field( - description='Open, absolute URI identifying the claim vocabulary. The URI is an identifier and need not be dereferenceable. AdCP does not maintain an enum of approved claims.' - ), - ] = 'https://adcontextprotocol.org/claims/rights/grant' - subject: Annotated[ - Subject20 | Subject | Subject29 | None, - Field( - description='Typed identity of the entity or object an attestation credential is about. Brand and agent subjects reuse canonical AdCP identities. Other resources use an open, URI-namespaced resource_type plus an identifier whose namespace is explicit. Evaluators MUST compare the resolved credential subject to this complete typed identity, not to id alone.', - discriminator='type', - examples=[ - { - 'type': 'brand', - 'brand': {'domain': 'nova-brands.example', 'brand_id': 'nova_motors'}, - }, - { - 'type': 'resource', - 'resource_type': 'https://adcontextprotocol.org/claims/subjects/signal', - 'namespace': 'https://signals.meridian.example/adcp', - 'id': 'signal_urban_commuters', - }, - ], - title='Attestation Subject', - ), - ] = None - - -class RightsConstraint(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - rights_id: Annotated[ - str, Field(description='Rights grant identifier from the acquire_rights response') - ] - rights_agent: Annotated[RightsAgent, Field(description='The agent that granted these rights')] - rights_holder: Annotated[ - brand_ref.BrandReference | None, - Field( - description='Canonical BrandRef for the rights holder that issued the grant through an authorized rights agent. Required whenever attestation_refs is present. Evaluators first match this exact BrandRef against local issuer policy, then resolve the authoritative brand.json and bind the credential verification key to exactly one matching agents[] entry with type rights. Sibling-brand agents are never authority. Legacy unattested constraints may omit this field but remain machine-unverified.' - ), - ] = None - valid_from: Annotated[ - AwareDatetime | None, Field(description='Start of the rights validity period') - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='End of the rights validity period. Creative should not be served after this time.' - ), - ] = None - uses: Annotated[ - list[right_use.RightUse], - Field(description='Rights uses covered by this constraint', min_length=1), - ] - countries: Annotated[ - list[Country] | None, - Field( - description='Countries where this creative may be served under these rights (ISO 3166-1 alpha-2). If omitted, no country restriction. When both countries and excluded_countries are present, the effective set is countries minus excluded_countries.' - ), - ] = None - excluded_countries: Annotated[ - list[ExcludedCountry] | None, - Field( - description='Countries excluded from rights availability (ISO 3166-1 alpha-2). Use when the grant is worldwide except specific markets.' - ), - ] = None - impression_cap: Annotated[ - SchemaInt | None, - Field( - description='Maximum total impressions allowed for the full validity period (valid_from to valid_until). This is the absolute cap across all creatives using this rights grant, not a per-creative or per-period limit.', - ge=1, - ), - ] = None - right_type: Annotated[ - right_type_1.RightType | None, - Field( - description='Type of rights (talent, music, etc.). Helps identify constraints when a creative combines multiple rights types.' - ), - ] = None - approval_status: Annotated[ - ApprovalStatus | None, - Field( - description='Approval status from the rights holder at manifest creation time (snapshot, not a live value)' - ), - ] = None - grant_status: Annotated[ - GrantStatus | None, - Field( - description='Authoritative lifecycle state bound into the grant digest and credential status evidence. Only active can evaluate to verified. paused and revoked credentials are ineligible for serving; resumption issues a fresh active credential and reference.' - ), - ] = None - restrictions: Annotated[ - list[Restriction] | None, - Field( - description='Normalized enforceable content and usage restrictions issued with the grant. When present these values are part of content_digest and cannot be omitted or changed without invalidating the attestation.', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Disclosure obligation issued with the grant and bound by content_digest.' - ), - ] = None - creative_approval_required: Annotated[ - StrictBool | None, - Field( - description='Whether each creative produced under this grant requires holder approval before distribution. This enforceable term is bound by content_digest.' - ), - ] = None - verification_url: Annotated[ - AnyUrl | None, - Field( - deprecated=True, - description='DEPRECATED legacy informational locator. It is not proof of grant status, a revocation source, a credential locator, or permission to make a network request. Receivers MUST NOT fetch it during rights evaluation, and HTTP status codes have no AdCP authorization meaning. Rights authorization uses attestation_refs and a verifier-of-record evaluation under evaluator-owned policy.', - ), - ] = None - content_digest: Annotated[ - str | None, - Field( - description='SHA-256 of UTF8(JCS(rights_constraint minus content_digest, attestation_refs, and verification_url)), formatted as sha256:. This binds all serving-relevant grant fields, including future extension fields, while deliberately excluding the legacy non-authoritative URL.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - attestation_refs: Annotated[ - list[AttestationRef] | None, - Field( - description='Alternative portable presentations of a holder-issued rights-grant credential signed by its authorized rights agent. The buyer carries references only; the serving party remains verifier-of-record. At least one reference must evaluate to verified and match rights_holder, rights_id, rights_agent, content_digest, validity, revocation state, and local policy before this constraint can contribute to serving eligibility.', - max_length=4, - min_length=1, - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/seller_agent_ref.py b/src/adcp/types/generated_poc/core/seller_agent_ref.py deleted file mode 100644 index a7cf517a5..000000000 --- a/src/adcp/types/generated_poc/core/seller_agent_ref.py +++ /dev/null @@ -1,30 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/seller_agent_ref.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - - -class SellerAgentReference(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="The seller agent's API endpoint URL as declared in the property publisher's adagents.json `authorized_agents[].url`. MUST use the `https://` scheme. Receivers compare this URL against the `authorized_agents` list using the AdCP URL canonicalization rules — not byte-equality — and reject mismatches with `seller_not_authorized`. See docs/reference/url-canonicalization." - ), - ] - id: Annotated[ - str | None, - Field( - description='Reserved for a future registry-assigned stable seller identifier. Not used today — senders MUST NOT populate this field until a registry is defined. When a future release populates both `agent_url` and `id`, `agent_url` remains authoritative and `id` is advisory.', - min_length=1, - pattern='^[a-zA-Z0-9_-]+$', - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/signal_coverage_forecast.py b/src/adcp/types/generated_poc/core/signal_coverage_forecast.py deleted file mode 100644 index 41a01b7c3..000000000 --- a/src/adcp/types/generated_poc/core/signal_coverage_forecast.py +++ /dev/null @@ -1,182 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/signal_coverage_forecast.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Dict, Literal - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat - -from ..enums import forecast_method -from . import date_range as date_range_1 -from . import ext as ext_1 -from . import forecast_point, forecast_point_dimensions, forecast_range - - -class CoverageRate(forecast_point.CoverageRate): - low: Annotated[ - StrictFloat | None, - Field(description='Conservative (low-end) forecast value', ge=0.0, le=1.0), - ] = None - mid: Annotated[ - StrictFloat | None, - Field(description='Expected (most likely) forecast value', ge=0.0, le=1.0), - ] = None - high: Annotated[ - StrictFloat | None, - Field(description='Optimistic (high-end) forecast value', ge=0.0, le=1.0), - ] = None - - -class Metrics(forecast_point.Metrics): - __pydantic_extra__: Dict[str, forecast_range.ForecastRange] - model_config = ConfigDict( - extra='allow', - ) - audience_size: forecast_range.ForecastRange | None = None - reach: forecast_range.ForecastRange | None = None - frequency: forecast_range.ForecastRange | None = None - impressions: forecast_range.ForecastRange | None = None - clicks: forecast_range.ForecastRange | None = None - spend: forecast_range.ForecastRange | None = None - views: forecast_range.ForecastRange | None = None - completed_views: forecast_range.ForecastRange | None = None - grps: forecast_range.ForecastRange | None = None - engagements: forecast_range.ForecastRange | None = None - follows: forecast_range.ForecastRange | None = None - saves: forecast_range.ForecastRange | None = None - profile_visits: forecast_range.ForecastRange | None = None - measured_impressions: forecast_range.ForecastRange | None = None - downloads: forecast_range.ForecastRange | None = None - plays: forecast_range.ForecastRange | None = None - coverage_rate: Annotated[ - CoverageRate, - Field( - description="Share of the declared forecast scope represented by this point. For signal coverage forecasts, this is the point's count divided by the coverage_forecast.scope denominator. Range 0.0 to 1.0." - ), - ] - - - - -class Kind(StrEnum): - inventory = 'inventory' - product = 'product' - account = 'account' - custom = 'custom' - - -class Country(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{2}$'} - - -class Scope(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[Kind, Field(description='Denominator family for the coverage forecast.')] - label: Annotated[ - str, - Field( - description="Human-readable denominator label, such as 'network price-priority inventory'." - ), - ] - product_id: Annotated[ - str | None, Field(description="Product denominator when kind is 'product'.") - ] = None - countries: Annotated[ - list[Country] | None, - Field( - description='Countries included in the denominator, as ISO 3166-1 alpha-2 codes.', - min_length=1, - ), - ] = None - line_item_types: Annotated[ - list[str] | None, - Field( - description='Seller or ad-server line item types included in the denominator.', - min_length=1, - ), - ] = None - date_range: Annotated[ - date_range_1.DateRange | None, - Field(description='Historical or planned date window used to compute the denominator.'), - ] = None - - -class BucketSemantics(StrEnum): - exclusive = 'exclusive' - overlapping = 'overlapping' - - -class BucketCompleteness(StrEnum): - complete = 'complete' - partial = 'partial' - - -class Point(forecast_point.ForecastPoint): - dimensions: Annotated[ - forecast_point_dimensions.ForecastPointDimensions, - Field( - description='Dimension constraints represented by this forecast point, such as country, region, placement, device type, platform, audience, signal value, time window, or intersections such as placement x country or product x signal. Each item declares one dimension family; when multiple items are present, the point represents their intersection. Sellers MUST NOT emit more than one item for each `kind` on a point; consumers MUST NOT treat repeated kinds as OR semantics. Use multiple points with dimensions to expose country/placement/signal availability within one product, proposal, or signal coverage forecast without creating separate products solely for each dimension. Dimensions describe the forecast row and are independent of pricing_options.' - ), - ] - metrics: Annotated[ - Metrics, - Field( - description='Forecasted metric values. Keys are forecastable-metric enum values for delivery/engagement or event-type enum values for outcomes. Values are ForecastRange objects (low/mid/high). Use { "mid": value } for point estimates. When budget is present, these are the expected metrics at that spend level. When budget is omitted, these represent total available inventory — use spend to express the estimated cost. Additional keys beyond the documented properties are allowed for event-type values (purchase, lead, app_install, etc.).' - ), - ] - - -class SignalCoverageForecast(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - points: Annotated[ - list[Point], - Field( - description='Coverage or availability points. Each point reuses the standard ForecastPoint shape, MUST include a signal dimension, and MUST include metrics.coverage_rate. Use metrics.impressions for count denominators and metrics.coverage_rate for the fraction of the declared scope represented by the point.', - min_length=1, - ), - ] - forecast_range_unit: Annotated[ - Literal['availability'], - Field( - description="How to interpret the points array. Signal coverage forecasts always use 'availability' because the points describe available inventory or population coverage, not spend curves or temporal pacing." - ), - ] = 'availability' - method: Annotated[ - forecast_method.ForecastMethod, - Field(description='Method used to produce this coverage forecast.'), - ] - scope: Annotated[ - Scope, - Field( - description='Explicit denominator for the coverage forecast. This identifies the inventory, product, account, or custom universe that coverage_rate values are relative to. Additional seller-specific qualifiers are allowed for scopes such as line item type, ad server, inventory class, country, or flight window.' - ), - ] - bucket_semantics: Annotated[ - BucketSemantics, - Field( - description="'exclusive' means the returned signal-value buckets do not overlap with each other. 'overlapping' means one impression or user can appear in multiple returned buckets, so coverage_rate values may sum above 1.0. This field describes overlap among returned buckets; bucket_completeness declares whether the returned buckets cover the full denominator." - ), - ] - bucket_completeness: Annotated[ - BucketCompleteness, - Field( - description="'complete' means the returned buckets cover the declared denominator. For complete + exclusive forecasts, count metrics and coverage_rate values can be treated as a full partition, subject to metric additivity rules. 'partial' means omitted denominator share represents undisclosed, other, or unsupported buckets; buyers MUST NOT infer totals by summing returned points." - ), - ] - generated_at: Annotated[ - AwareDatetime | None, Field(description='When this coverage forecast was computed.') - ] = None - valid_until: Annotated[ - AwareDatetime | None, Field(description='When this coverage forecast expires.') - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/signal_definition.py b/src/adcp/types/generated_poc/core/signal_definition.py deleted file mode 100644 index 1aedabd51..000000000 --- a/src/adcp/types/generated_poc/core/signal_definition.py +++ /dev/null @@ -1,545 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/signal_definition.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarStr -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, StrictBool, StrictFloat - -from ..enums import audience_resolution_method, audience_subject_type -from ..enums import consent_basis as consent_basis_1 -from ..enums import restricted_attribute, signal_value_type -from . import demographic_predicate as demographic_predicate_1 -from . import signal_modeling_disclosure - - -class Tag(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[a-z0-9_-]+$'} - - -class Range(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - min: Annotated[StrictFloat, Field(description='Minimum value')] - max: Annotated[StrictFloat, Field(description='Maximum value')] - unit: Annotated[ - str | None, Field(description="Unit of measurement (e.g., 'score', 'dollars', 'years')") - ] = None - - -class Value(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - id: Annotated[str, Field(description='Taxonomy node identifier.', min_length=1)] - path: Annotated[ - str | None, - Field( - description='Optional human-readable or taxonomy-native path for display and review.' - ), - ] = None - modifiers: Annotated[ - list[str] | None, - Field(description='Optional taxonomy-specific modifiers that qualify the node.'), - ] = None - - -class ValueMapping(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - value: Annotated[str, Field(description='Categorical value from allowed_values[].')] - taxonomy_value_id: Annotated[ - str, Field(description='Taxonomy node identifier corresponding to this categorical value.') - ] - path: Annotated[ - str | None, - Field( - description='Optional human-readable or taxonomy-native path for display and review.' - ), - ] = None - modifiers: Annotated[ - list[str] | None, - Field(description='Optional taxonomy-specific modifiers that qualify the mapped value.'), - ] = None - - -class ParentMatchBehavior(StrEnum): - exact_only = 'exact_only' - descendants_supported = 'descendants_supported' - unknown = 'unknown' - - -class Taxonomy(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - ref: Annotated[ - AnyUrl, Field(description='URI identifying the taxonomy or taxonomy documentation.') - ] - version: Annotated[ - str | None, - Field( - description='Version identifier for the taxonomy when the taxonomy has versioned definitions.' - ), - ] = None - segtax: Annotated[ - SchemaInt | None, - Field( - description='OpenRTB segtax code when the taxonomy maps to an OpenRTB segment taxonomy.', - ge=1, - ), - ] = None - etag: Annotated[ - str | None, - Field( - description='Optional validator for custom taxonomy definitions so consumers can detect drift between signal publication and taxonomy resolution.' - ), - ] = None - values: Annotated[ - list[Value], - Field( - description='Taxonomy node values that describe this signal. These are meaning/discovery metadata for the signal definition, not the values a buyer submits in package targeting expressions.', - min_length=1, - ), - ] - value_mappings: Annotated[ - list[ValueMapping] | None, - Field( - description="For categorical signals, maps package-targeting allowed_values[] strings to stable taxonomy node identifiers. The allowed_values[] strings remain the wire values buyers submit; this mapping explains how those strings resolve into the published taxonomy. Each value_mappings[].value SHOULD match one of the signal's allowed_values[] entries; JSON Schema draft-07 cannot enforce this cross-array constraint.", - min_length=1, - ), - ] = None - parent_match_behavior: Annotated[ - ParentMatchBehavior | None, - Field( - description="Whether this signal definition supports treating a parent taxonomy node as matching descendant nodes for discovery/filtering or seller-side expansion. 'exact_only' means only explicitly listed node ids match. 'descendants_supported' means the seller can expand known, version-pinned parent nodes to descendants internally, typically by ORing the children in its execution system. 'unknown' means the provider has not declared parent-node behavior. This field is metadata about discovery/translation behavior, not a package targeting operator." - ), - ] = None - - -class DataSource(StrEnum): - app_behavior = 'app_behavior' - app_usage = 'app_usage' - web_usage = 'web_usage' - geo_location = 'geo_location' - email = 'email' - tv_ott_or_stb_device = 'tv_ott_or_stb_device' - panel = 'panel' - online_ecommerce = 'online_ecommerce' - credit_data = 'credit_data' - loyalty_card = 'loyalty_card' - transaction = 'transaction' - online_survey = 'online_survey' - offline_survey = 'offline_survey' - public_record_census = 'public_record_census' - public_record_voter_file = 'public_record_voter_file' - public_record_other = 'public_record_other' - offline_transaction = 'offline_transaction' - - -class Methodology(StrEnum): - observed = 'observed' - declared = 'declared' - derived = 'derived' - inferred = 'inferred' - modeled = 'modeled' - - -class RefreshCadence(StrEnum): - intra_day = 'intra_day' - daily = 'daily' - weekly = 'weekly' - monthly = 'monthly' - bi_monthly = 'bi_monthly' - quarterly = 'quarterly' - bi_annually = 'bi_annually' - annually = 'annually' - - -class MatchKey(StrEnum): - name = 'name' - address = 'address' - email = 'email' - postal = 'postal' - lat_long = 'lat_long' - mobile_id = 'mobile_id' - cookie_id = 'cookie_id' - ip = 'ip' - customer_id = 'customer_id' - phone = 'phone' - - -class PreOnboardingPrecisionLevel(StrEnum): - individual = 'individual' - household = 'household' - business = 'business' - geography = 'geography' - - -class Onboarder(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - match_keys: Annotated[list[MatchKey], Field(min_length=1)] - pre_onboarding_audience_expansion: StrictBool | None = None - pre_onboarding_device_expansion: StrictBool | None = None - pre_onboarding_precision_level: PreOnboardingPrecisionLevel | None = None - - -class IdType(StrEnum): - cookie = 'cookie' - mobile_id = 'mobile_id' - platform_id = 'platform_id' - user_enabled_id = 'user_enabled_id' - - -class AudienceScope(StrEnum): - single_domain = 'single_domain' - cross_domain_owned = 'cross_domain_owned' - cross_domain_unowned = 'cross_domain_unowned' - offline = 'offline' - - -class Country(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{2}$'} - - -class Art9Basis(StrEnum): - explicit_consent = 'explicit_consent' - manifestly_made_public = 'manifestly_made_public' - substantial_public_interest = 'substantial_public_interest' - vital_interests = 'vital_interests' - - -class Method(StrEnum): - lookalike = 'lookalike' - supervised = 'supervised' - embedding = 'embedding' - rules = 'rules' - - -class Type(StrEnum): - first_party_crm = 'first_party_crm' - panel = 'panel' - declared_survey = 'declared_survey' - transactional = 'transactional' - behavioral = 'behavioral' - - -class SeedSource(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Type - provider_signed: Annotated[ - StrictBool, - Field( - description="Whether the seed source carries a signed attestation under one of the provider's published signing keys. This is a forward-looking claim until the consumer can resolve and verify the provider's applicable signing keys through the AdCP signing profile; consumers MUST NOT treat this boolean alone as cryptographic proof." - ), - ] - - -class TrainingDataJurisdiction(Country): - pass - - -class AiActRiskClass(StrEnum): - minimal = 'minimal' - limited = 'limited' - high_risk = 'high_risk' - - -class Right(StrEnum): - access = 'access' - rectification = 'rectification' - erasure = 'erasure' - portability = 'portability' - objection = 'objection' - - -class Channel(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - rights: Annotated[ - list[Right], Field(description='Rights supported by this channel.', min_length=1) - ] - url: Annotated[ - AnyUrl | None, Field(description='HTTPS URL for submitting the rights request.') - ] = None - email: Annotated[ - EmailStr | None, Field(description='Email address for submitting the rights request.') - ] = None - languages: Annotated[ - list[str] | None, Field(description='BCP 47 language tags supported by this channel.') - ] = None - countries: Annotated[ - list[Country] | None, - Field( - description='ISO 3166-1 alpha-2 countries this channel serves, when the provider routes rights by country.' - ), - ] = None - - -class DataSubjectRights(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - upstream_source_domain: Annotated[ - str | None, - Field( - description='Domain of the upstream data source whose rights process these channels reach, when different from the publishing domain.', - max_length=253, - pattern='^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]{0,61}[A-Za-z0-9])$', - ), - ] = None - channels: Annotated[ - list[Channel], - Field( - description='Rights request channels and the rights each channel supports. At least one declared channel MUST support one or more of access, erasure, or objection; schema validators enforce this with draft-07 contains, and consumers whose SDK drops contains need an equivalent runtime check.', - min_length=1, - ), - ] - response_sla_days: Annotated[ - SchemaInt | None, - Field( - description="Maximum response time in days for rights requests handled through the declared rights channels. For provider-published signals, providers SHOULD avoid duplicating this field across every signal unless the value varies by signal or upstream source; consumers MAY also consult the provider's public privacy policy or registry disclosures when present.", - ge=1, - le=90, - ), - ] = None - ccpa_opt_out_url: Annotated[ - AnyUrl | None, - Field(description="US-specific 'Do Not Sell or Share' opt-out URL where required."), - ] = None - - -class Modeling(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - method: Method - seed_source: SeedSource - training_data_jurisdictions: Annotated[ - list[TrainingDataJurisdiction], - Field( - description="ISO 3166-1 alpha-2 country codes where the model's training data was collected.", - min_length=1, - ), - ] - ai_act_risk_class: Annotated[ - AiActRiskClass, - Field( - description='EU AI Act risk classification self-declared by the provider. Prohibited-risk modeled signals must not be published as AdCP signal definitions.' - ), - ] - disclosure: Annotated[ - signal_modeling_disclosure.SignalModelingDisclosure | None, - Field( - description='Signal/modeling-specific disclosure requirements and jurisdictional notes. This is not creative provenance render guidance.' - ), - ] = None - - -class SignalDefinition(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - id: Annotated[ - str, - Field( - description="Signal identifier within the publishing domain's adagents.json signals[]", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - name: Annotated[ - str, Field(description='Human-readable signal name', max_length=255, min_length=1) - ] - description: Annotated[ - str | None, - Field( - description="Detailed description of what this signal represents and how it's derived", - max_length=2000, - ), - ] = None - value_type: Annotated[ - signal_value_type.SignalValueType, - Field(description="The data type of this signal's values"), - ] - tags: Annotated[ - list[Tag] | None, - Field( - description="Tags for grouping and filtering this domain's published signal definitions" - ), - ] = None - allowed_values: Annotated[ - list[str] | None, - Field( - description='For categorical signals, the valid values users can be assigned', - min_length=1, - ), - ] = None - restricted_attributes: Annotated[ - list[restricted_attribute.RestrictedAttribute] | None, - Field( - description="Restricted attribute categories this signal touches. Data providers SHOULD declare these so governance agents can structurally match signals against a plan's restricted_attributes without relying on semantic inference from the signal name or description.", - min_length=1, - ), - ] = None - demographic_predicate: Annotated[ - demographic_predicate_1.DemographicPredicate | None, - Field( - description='Authoritative machine-readable demographic meaning for this signal. Signal names and taxonomy labels alone never establish exact demographic equivalence. Signals carrying this field MUST also declare restricted_attributes including age.' - ), - ] = None - policy_categories: Annotated[ - list[str] | None, - Field( - description="Policy categories this signal is sensitive for (e.g., a children's interest signal declares ['children_directed']). Governance agents match these against a plan's policy_categories to flag sensitive data usage.", - min_length=1, - ), - ] = None - range: Annotated[ - Range | None, Field(description='For numeric signals, the valid value range') - ] = None - taxonomy: Annotated[ - Taxonomy | None, - Field( - description='Optional taxonomy metadata describing what this signal means in an external audience, content, retail-media, or provider-owned taxonomy. Taxonomy metadata does not create a new value_type and does not change package targeting grammar: buyers still target the named signal according to value_type. When a taxonomy value is a parent node, parent/descendant expansion is seller behavior and must be declared through parent_match_behavior rather than assumed.' - ), - ] = None - segmentation_criteria: Annotated[ - str | None, - Field( - description='Rules governing inclusion of identifiers in the segment. Aligns with IAB Data Transparency Standard audience criteria disclosure.', - max_length=500, - ), - ] = None - criteria_url: Annotated[ - AnyUrl | None, - Field( - description='Optional URL to a longer-form methodology or criteria document. This is a disclosure pointer; buyers should not branch programmatically on the linked content.' - ), - ] = None - data_sources: Annotated[ - list[DataSource] | None, - Field( - description="Origin categories of the raw data used to compile the signal, aligned with IAB Data Transparency Standard source disclosure. Use 'panel' for respondent-panel or JIC-style audience sources. Offline and public-record sources require onboarder disclosure. Co-viewing projection and reconciliation of seller claims against a JIC or measurement vendor belong in measurement reporting/vendor metrics rather than package signal targeting.", - min_length=1, - ), - ] = None - methodology: Annotated[ - Methodology | None, - Field( - description="How the signal's audience membership or attribute was determined. 'modeled' requires the modeling block." - ), - ] = None - audience_expansion: Annotated[ - StrictBool | None, - Field( - description='Whether look-alike or similar-audience expansion was used to include additional identifiers. When true, modeling is required.' - ), - ] = None - device_expansion: Annotated[ - StrictBool | None, - Field( - description="Whether the signal was expanded deterministically across devices of the same user, household, or business. Probabilistic cross-device expansion is modeling and should use methodology 'modeled' or the modeling block." - ), - ] = None - refresh_cadence: Annotated[ - RefreshCadence | None, - Field( - description="Cadence at which the signal definition's underlying segment membership is refreshed." - ), - ] = None - lookback_window: Annotated[ - RefreshCadence | None, - Field(description='Time window in which a qualifying event can occur for inclusion.'), - ] = None - onboarder: Annotated[ - Onboarder | None, - Field( - description='Onboarder disclosure. Required when data_sources includes an offline_* or public_record_* source.' - ), - ] = None - subject_type: Annotated[ - audience_subject_type.AudienceSubjectType | None, - Field(description='What kind of subject this signal characterizes.'), - ] = None - resolution_method: Annotated[ - audience_resolution_method.AudienceResolutionMethod | None, - Field(description='How the subject is resolved at decision time.'), - ] = None - id_types: Annotated[ - list[IdType] | None, - Field( - description='Identifier currencies analyzed to determine audience membership or attributes.', - min_length=1, - ), - ] = None - audience_scope: Annotated[ - AudienceScope | None, - Field( - description="Context within which the audience attribute was determined. 'single_domain' requires originating_domain." - ), - ] = None - originating_domain: Annotated[ - str | None, - Field( - description="Domain of the digital property where the audience originates. Required when audience_scope is 'single_domain'.", - pattern='^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]{0,61}[A-Za-z0-9])$', - ), - ] = None - countries: Annotated[ - list[Country] | None, - Field( - description="ISO 3166-1 alpha-2 country codes where the signal is applicable. Sellers must not expose a signal for media buys in countries outside this list. Federating agents that surface a peer's signal MUST treat the peer-published list as an upper bound, re-check it against the buyer's intended deployment countries, and may apply only narrower local policy.", - min_length=1, - ), - ] = None - consent_basis: Annotated[ - list[consent_basis_1.ConsentBasis] | None, - Field( - description="Declared GDPR Article 6 lawful basis or consent basis under which this signal's data is processed. For non-GDPR regimes, use countries, policy_categories, and disclosure fields to describe jurisdiction-specific obligations unless a future enum value applies.", - min_length=1, - ), - ] = None - art9_basis: Annotated[ - Art9Basis | None, - Field( - description='GDPR Article 9 basis when restricted_attributes is non-empty and the signal is used in jurisdictions where Article 9 applies. Required by policy for applicable use cases rather than universally required at schema level because sensitivity and lawful basis are jurisdiction-relative.' - ), - ] = None - modeling: Annotated[ - Modeling | None, - Field( - description="Modeling disclosure for modeled data signals. Required when methodology is 'modeled' or audience_expansion is true. This describes data modeling and intentionally does not reuse creative provenance, which is content/render oriented." - ), - ] = None - data_subject_rights: Annotated[ - DataSubjectRights | None, - Field( - description='Per-signal data-subject-rights routing. Inline on the signal because upstream source, rights routing, and response commitments can differ by segment or may be unavailable from a public provider document for custom/private signals. This is a contact/routing reference, not a machine-callable AdCP API.' - ), - ] = None - last_updated: Annotated[ - AwareDatetime | None, - Field( - description='When this definition record was last updated. This indicates freshness of the definition record, not an attestation that the underlying data or model was refreshed at that time.' - ), - ] = None - dts_compliant_version: Annotated[ - str | None, - Field( - description='IAB Data Transparency Standard version this signal definition self-attests as satisfying, when applicable.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/signal_definition_enrichment.py b/src/adcp/types/generated_poc/core/signal_definition_enrichment.py deleted file mode 100644 index 2151a3a24..000000000 --- a/src/adcp/types/generated_poc/core/signal_definition_enrichment.py +++ /dev/null @@ -1,283 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/signal_definition_enrichment.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarStr -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, StrictBool - -from ..enums import consent_basis as consent_basis_1 -from ..enums import restricted_attribute -from . import demographic_predicate as demographic_predicate_1 -from . import signal_modeling_disclosure - - -class Value(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - id: Annotated[str, Field(min_length=1)] - path: str | None = None - modifiers: list[str] | None = None - - -class ValueMapping(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - value: str - taxonomy_value_id: str - path: str | None = None - modifiers: list[str] | None = None - - -class ParentMatchBehavior(StrEnum): - exact_only = 'exact_only' - descendants_supported = 'descendants_supported' - unknown = 'unknown' - - -class Taxonomy(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - ref: AnyUrl - version: str | None = None - segtax: Annotated[SchemaInt | None, Field(ge=1)] = None - etag: str | None = None - values: Annotated[list[Value], Field(min_length=1)] - value_mappings: Annotated[list[ValueMapping] | None, Field(min_length=1)] = None - parent_match_behavior: ParentMatchBehavior | None = None - - -class DataSource(StrEnum): - app_behavior = 'app_behavior' - app_usage = 'app_usage' - web_usage = 'web_usage' - geo_location = 'geo_location' - email = 'email' - tv_ott_or_stb_device = 'tv_ott_or_stb_device' - panel = 'panel' - online_ecommerce = 'online_ecommerce' - credit_data = 'credit_data' - loyalty_card = 'loyalty_card' - transaction = 'transaction' - online_survey = 'online_survey' - offline_survey = 'offline_survey' - public_record_census = 'public_record_census' - public_record_voter_file = 'public_record_voter_file' - public_record_other = 'public_record_other' - offline_transaction = 'offline_transaction' - - -class Methodology(StrEnum): - observed = 'observed' - declared = 'declared' - derived = 'derived' - inferred = 'inferred' - modeled = 'modeled' - - -class RefreshCadence(StrEnum): - intra_day = 'intra_day' - daily = 'daily' - weekly = 'weekly' - monthly = 'monthly' - bi_monthly = 'bi_monthly' - quarterly = 'quarterly' - bi_annually = 'bi_annually' - annually = 'annually' - - -class MatchKey(StrEnum): - name = 'name' - address = 'address' - email = 'email' - postal = 'postal' - lat_long = 'lat_long' - mobile_id = 'mobile_id' - cookie_id = 'cookie_id' - ip = 'ip' - customer_id = 'customer_id' - phone = 'phone' - - -class PreOnboardingPrecisionLevel(StrEnum): - individual = 'individual' - household = 'household' - business = 'business' - geography = 'geography' - - -class Onboarder(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - match_keys: Annotated[list[MatchKey], Field(min_length=1)] - pre_onboarding_audience_expansion: StrictBool | None = None - pre_onboarding_device_expansion: StrictBool | None = None - pre_onboarding_precision_level: PreOnboardingPrecisionLevel | None = None - - -class Country(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{2}$'} - - -class Art9Basis(StrEnum): - explicit_consent = 'explicit_consent' - manifestly_made_public = 'manifestly_made_public' - substantial_public_interest = 'substantial_public_interest' - vital_interests = 'vital_interests' - - -class Method(StrEnum): - lookalike = 'lookalike' - supervised = 'supervised' - embedding = 'embedding' - rules = 'rules' - - -class Type(StrEnum): - first_party_crm = 'first_party_crm' - panel = 'panel' - declared_survey = 'declared_survey' - transactional = 'transactional' - behavioral = 'behavioral' - - -class SeedSource(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Type - provider_signed: Annotated[ - StrictBool, - Field( - description='Provider assertion that the seed source carries a signed attestation. Consumers MUST NOT treat this boolean alone as cryptographic proof.' - ), - ] - - -class TrainingDataJurisdiction(Country): - pass - - -class AiActRiskClass(StrEnum): - minimal = 'minimal' - limited = 'limited' - high_risk = 'high_risk' - - -class Right(StrEnum): - access = 'access' - rectification = 'rectification' - erasure = 'erasure' - portability = 'portability' - objection = 'objection' - - -class Channel(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - rights: Annotated[list[Right], Field(min_length=1)] - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - countries: list[Country] | None = None - - -class DataSubjectRights(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - upstream_source_domain: Annotated[ - str | None, - Field( - max_length=253, - pattern='^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]{0,61}[A-Za-z0-9])$', - ), - ] = None - channels: Annotated[list[Channel], Field(min_length=1)] - response_sla_days: Annotated[SchemaInt | None, Field(ge=1, le=90)] = None - ccpa_opt_out_url: AnyUrl | None = None - - -class Modeling(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - method: Method - seed_source: SeedSource - training_data_jurisdictions: Annotated[list[TrainingDataJurisdiction], Field(min_length=1)] - ai_act_risk_class: AiActRiskClass - disclosure: signal_modeling_disclosure.SignalModelingDisclosure | None = None - - -class SignalDefinitionEnrichment(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - restricted_attributes: Annotated[ - list[restricted_attribute.RestrictedAttribute] | None, - Field(description='Restricted attribute categories this signal touches.', min_length=1), - ] = None - demographic_predicate: Annotated[ - demographic_predicate_1.DemographicPredicate | None, - Field( - description="Projected authoritative demographic meaning for the signal. When projected from another provider, this MUST match the provider's definition exactly. Signal names alone never establish demographic semantics." - ), - ] = None - policy_categories: Annotated[ - list[str] | None, - Field(description='Policy categories this signal is sensitive for.', min_length=1), - ] = None - taxonomy: Annotated[ - Taxonomy | None, - Field( - description='Optional taxonomy metadata describing what this signal means in an external audience, content, retail-media, or provider-owned taxonomy.' - ), - ] = None - segmentation_criteria: Annotated[str | None, Field(max_length=500)] = None - criteria_url: AnyUrl | None = None - data_sources: Annotated[list[DataSource] | None, Field(min_length=1)] = None - methodology: Methodology | None = None - audience_expansion: StrictBool | None = None - device_expansion: StrictBool | None = None - refresh_cadence: RefreshCadence | None = None - lookback_window: RefreshCadence | None = None - onboarder: Onboarder | None = None - countries: Annotated[list[Country] | None, Field(min_length=1)] = None - consent_basis: Annotated[ - list[consent_basis_1.ConsentBasis] | None, - Field( - description="Data provider's declared GDPR Article 6 lawful basis or consent basis for the underlying signal definition, projected into this get_signals response row when requested. Sellers and federating agents that pass through another provider's signal MUST NOT substitute their own processing basis for the provider-declared basis.", - min_length=1, - ), - ] = None - art9_basis: Annotated[ - Art9Basis | None, - Field( - description="Data provider's declared GDPR Article 9 basis for the underlying signal definition when special-category data is involved and Article 9 applies, projected into this get_signals response row when requested. Sellers and federating agents that pass through another provider's signal MUST NOT substitute their own Article 9 basis for the provider-declared basis." - ), - ] = None - modeling: Modeling | None = None - data_subject_rights: Annotated[ - DataSubjectRights | None, - Field( - description='Per-signal data-subject-rights routing. This is a contact/routing reference, not a machine-callable AdCP API.' - ), - ] = None - last_updated: Annotated[ - AwareDatetime | None, - Field( - description='When this definition record was last updated. This indicates freshness of the definition record, not an attestation that the underlying data or model was refreshed at that time.' - ), - ] = None - dts_compliant_version: str | None = None diff --git a/src/adcp/types/generated_poc/core/signal_filters.py b/src/adcp/types/generated_poc/core/signal_filters.py deleted file mode 100644 index 1e07697cd..000000000 --- a/src/adcp/types/generated_poc/core/signal_filters.py +++ /dev/null @@ -1,47 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/signal_filters.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictFloat - -from ..enums import signal_catalog_type -from . import ext as ext_1 - - -class SignalFilters(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - catalog_types: Annotated[ - list[signal_catalog_type.SignalAvailabilityType] | None, - Field(description='Filter by catalog type', min_length=1), - ] = None - data_providers: Annotated[ - list[str] | None, Field(description='Filter by specific data providers', min_length=1) - ] = None - max_cpm: Annotated[ - StrictFloat | None, - Field(description="Maximum CPM filter. Applies only to signals with model='cpm'.", ge=0.0), - ] = None - max_percent: Annotated[ - StrictFloat | None, - Field( - description='Maximum percent-of-media rate filter. Signals where all percent_of_media pricing options exceed this value are excluded. Does not account for max_cpm caps.', - ge=0.0, - le=100.0, - ), - ] = None - min_coverage_percentage: Annotated[ - StrictFloat | None, Field(description='Minimum coverage requirement', ge=0.0, le=100.0) - ] = None - ext: Annotated[ - ext_1.ExtensionObject | None, - Field( - description='Vendor-namespaced extension parameters for seller- or platform-specific signal filter criteria not covered by standard fields. Keys MUST be namespaced under a vendor or platform key (e.g., ext.gam, ext.platform_x). Sellers MUST treat all values as untrusted buyer input; avoid unbounded logging or labels, and do not interpolate values into caller-visible error strings, LLM prompts, SQL queries, or system commands without sanitization. Persistent use of an extension key across multiple buyers is a signal to propose standardization.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/signal_id.py b/src/adcp/types/generated_poc/core/signal_id.py deleted file mode 100644 index 85041b060..000000000 --- a/src/adcp/types/generated_poc/core/signal_id.py +++ /dev/null @@ -1,71 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/signal_id.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - - -class SignalId8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - source: Annotated[ - Literal['catalog'], - Field( - description="Discriminator indicating this signal is from a data provider's published adagents.json signals[]" - ), - ] = 'catalog' - data_provider_domain: Annotated[ - str, - Field( - description="Domain of the data provider that owns this signal (e.g., 'pinnacle-data.example'). The signal definition is published at this domain's /.well-known/adagents.json", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - id: Annotated[ - str, - Field( - description="Signal identifier within the data provider's catalog (e.g., 'likely_ev_buyers', 'income_100k_plus')", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalId9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - source: Annotated[ - Literal['agent'], - Field( - description="Discriminator indicating this signal is native to the signal source identified by agent_url, not from a data provider's published signal definitions." - ), - ] = 'agent' - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the signal source that provides this signal (e.g., 'https://signals.example/.well-known/adcp/signals')" - ), - ] - id: Annotated[ - str, - Field( - description="Signal identifier within the agent's signal set (e.g., 'custom_auto_intenders')", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -SignalId = Annotated[ - SignalId8 | SignalId9, - Field( - description="DEPRECATED. Use SignalRef for new discovery, activation, and media-buy signal identity surfaces. Legacy universal signal identifier used by older Signals Protocol clients. Uses 'source' as discriminator: 'catalog' for signals from a data provider's published catalog (verifiable), or 'agent' for signals native to a signal source identified by agent_url.", - discriminator='source', - title='Signal ID', - ), -] diff --git a/src/adcp/types/generated_poc/core/signal_listing.py b/src/adcp/types/generated_poc/core/signal_listing.py deleted file mode 100644 index 30ca559d8..000000000 --- a/src/adcp/types/generated_poc/core/signal_listing.py +++ /dev/null @@ -1,109 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/signal_listing.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictFloat, model_validator - -from ..enums import restricted_attribute, signal_value_type -from . import demographic_predicate as demographic_predicate_1 -from . import signal_id as signal_id_1 -from . import signal_ref as signal_ref_1 - - -class Range(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - min: Annotated[StrictFloat, Field(description='Minimum value, inclusive.')] - max: Annotated[StrictFloat, Field(description='Maximum value, inclusive.')] - - -class SignalListing(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - signal_ref_1.SignalRef | None, - Field( - description="Canonical signal reference. Use scope 'product' for a product-local signal defined by this listing; use scope 'data_provider' with data_provider_domain for a signal defined in a data provider's published adagents.json signals[]; use scope 'signal_source' with signal_source_url for a source-native signal." - ), - ] = None - signal_id: Annotated[ - signal_id_1.SignalId | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older Signals Protocol clients.', - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable signal name. Required when signal_ref.scope is 'product'. For data_provider and signal_source refs, this is optional contextual display text; the referenced definition or source remains authoritative." - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Detailed signal description. For data_provider and signal_source refs, this is optional contextual display text and MUST NOT replace the referenced definition.' - ), - ] = None - methodology_url: Annotated[ - AnyUrl | None, - Field( - description='Optional link to published methodology, media-kit, or data documentation. For data_provider and signal_source refs, this SHOULD match or supplement the referenced definition.' - ), - ] = None - last_updated: Annotated[ - AwareDatetime | None, - Field( - description='When this listing record was last updated. This indicates freshness of the listing record, not an attestation that the underlying data or model was refreshed at that time.' - ), - ] = None - value_type: Annotated[ - signal_value_type.SignalValueType | None, - Field( - description="The data type of this signal's values. Required when signal_ref.scope is 'product'." - ), - ] = None - categories: Annotated[ - list[str] | None, - Field( - description="Valid values for categorical signals. Present when value_type is 'categorical'.", - min_length=1, - ), - ] = None - range: Annotated[ - Range | None, - Field(description="Valid range for numeric signals. Present when value_type is 'numeric'."), - ] = None - restricted_attributes: Annotated[ - list[restricted_attribute.RestrictedAttribute] | None, - Field( - description='Restricted attribute categories this listing touches. Required with demographic_predicate and must include age. For referenced provider/source signals, any projected values must match the authoritative definition.', - min_length=1, - ), - ] = None - demographic_predicate: Annotated[ - demographic_predicate_1.DemographicPredicate | None, - Field( - description='Machine-readable demographic meaning. For product-local signals this listing is authoritative; for data-provider and signal-source refs, any projected value MUST match the referenced authoritative definition. Signal names alone never establish demographic semantics.' - ), - ] = None - - @model_validator(mode='after') - def _require_schema_required_group(self) -> SignalListing: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('signal_ref',), ('signal_id',),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'SignalListing requires at least one of these field groups: signal_ref | signal_id' - ) diff --git a/src/adcp/types/generated_poc/core/signal_modeling_disclosure.py b/src/adcp/types/generated_poc/core/signal_modeling_disclosure.py deleted file mode 100644 index 31be34976..000000000 --- a/src/adcp/types/generated_poc/core/signal_modeling_disclosure.py +++ /dev/null @@ -1,77 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/signal_modeling_disclosure.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, StrictBool - - -class Audience(StrEnum): - buyer = 'buyer' - data_subject = 'data_subject' - regulator = 'regulator' - public = 'public' - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - country: Annotated[ - str, Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] - region: Annotated[ - str | None, - Field( - description='Provider-defined sub-national region code or name when the obligation is regional. No global canonical format is implied.' - ), - ] = None - regulation: Annotated[ - str, - Field(description='Provider-supplied regulation identifier for the disclosure obligation.'), - ] - disclosure_text: Annotated[ - str | None, - Field( - description='Human-readable disclosure text or summary the provider expects buyers or reviewers to see.' - ), - ] = None - disclosure_url: Annotated[ - AnyUrl | None, - Field( - description="Optional URL to the provider's canonical disclosure or methodology page for this jurisdiction." - ), - ] = None - audience: Annotated[ - Audience | None, Field(description='Primary audience for this disclosure entry.') - ] = None - - -class SignalModelingDisclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - required: Annotated[ - StrictBool, - Field( - description="The provider's claim that a modeling or AI-use disclosure is required for this signal in at least one applicable jurisdiction. This is a declared compliance signal, not a protocol-level legal determination." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field( - description='Jurisdictions where a modeling or AI-use disclosure applies.', min_length=1 - ), - ] = None - notes: Annotated[ - str | None, - Field( - description='Optional provider notes on how the disclosure should be interpreted. Informational only; buyers should not branch programmatically on this text.', - max_length=2000, - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/signal_pricing.py b/src/adcp/types/generated_poc/core/signal_pricing.py deleted file mode 100644 index 2c35b6cc7..000000000 --- a/src/adcp/types/generated_poc/core/signal_pricing.py +++ /dev/null @@ -1,130 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/signal_pricing.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictFloat - -from . import ext as ext_1 - - -class VendorPricing1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['cpm'] = 'cpm' - cpm: Annotated[StrictFloat, Field(description='Cost per thousand impressions', ge=0.0)] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: ext_1.ExtensionObject | None = None - - -class VendorPricing2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['percent_of_media'] = 'percent_of_media' - percent: Annotated[ - StrictFloat, Field(description='Percentage of media spend, e.g. 15 = 15%', ge=0.0, le=100.0) - ] - max_cpm: Annotated[ - StrictFloat | None, - Field( - description='Optional CPM cap. When set, the effective charge is min(percent × media_spend_per_mille, max_cpm).', - ge=0.0, - ), - ] = None - currency: Annotated[ - str, - Field(description='ISO 4217 currency code for the resulting charge', pattern='^[A-Z]{3}$'), - ] - ext: ext_1.ExtensionObject | None = None - - -class Period(StrEnum): - monthly = 'monthly' - quarterly = 'quarterly' - annual = 'annual' - campaign = 'campaign' - - -class VendorPricing3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['flat_fee'] = 'flat_fee' - amount: Annotated[StrictFloat, Field(description='Fixed charge for the billing period', ge=0.0)] - period: Annotated[Period, Field(description='Billing period for the flat fee.')] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: ext_1.ExtensionObject | None = None - - -class VendorPricing4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['per_unit'] = 'per_unit' - unit: Annotated[ - str, - Field( - description="What is counted — e.g. 'format', 'image', 'token', 'variant', 'render', 'evaluation'." - ), - ] - unit_price: Annotated[StrictFloat, Field(description='Cost per one unit', ge=0.0)] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: ext_1.ExtensionObject | None = None - - -class Metadata(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - summary_for_operator: Annotated[ - str | None, - Field( - description="One or two sentences describing the pricing construct in plain language, displayed to the buyer's operator when requesting approval. Should not repeat the top-level `description` verbatim — summarize the charge mechanic instead (e.g., 'Base $12 CPM plus $0.50 per qualifying post-view conversion, capped at $45 CPM').", - min_length=1, - ), - ] = None - - -class VendorPricing5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['custom'] = 'custom' - description: Annotated[ - str, - Field( - description='Human-readable description of the custom pricing model. Buyers display this to the operator when requesting approval.', - min_length=1, - ), - ] - metadata: Annotated[ - Metadata, - Field( - description="Structured parameters for the custom model. Keys follow lowercase_snake_case. Values may be primitives, arrays, or nested objects. Must be sufficient for a human to understand the pricing basis and for a downstream system to reconstruct the charge. Vendors SHOULD include a `summary_for_operator` string (one or two sentences, suitable for display in a buyer's operator-review UI) so reviewers across vendors see a consistent prompt. Required operator-review fields (approver role, dollar threshold for automatic approval, escalation contact) MAY be surfaced via additional keys the buyer's review surface recognizes." - ), - ] - currency: Annotated[ - str | None, - Field( - description='ISO 4217 currency code. Present when the pricing resolves to a monetary charge in a specific currency.', - pattern='^[A-Z]{3}$', - ), - ] = None - ext: ext_1.ExtensionObject | None = None - - -VendorPricing = Annotated[ - VendorPricing1 | VendorPricing2 | VendorPricing3 | VendorPricing4 | VendorPricing5, - Field( - description="Pricing model for a vendor service. Discriminated by model: 'cpm' (fixed CPM), 'percent_of_media' (percentage of spend with optional CPM cap), 'flat_fee' (fixed charge per reporting period), 'per_unit' (fixed price per unit of work), or 'custom' (escape hatch for models not covered by the enumerated forms — requires a description and structured metadata).", - discriminator='model', - title='Vendor Pricing', - ), -] diff --git a/src/adcp/types/generated_poc/core/signal_pricing_option.py b/src/adcp/types/generated_poc/core/signal_pricing_option.py deleted file mode 100644 index d42f10c56..000000000 --- a/src/adcp/types/generated_poc/core/signal_pricing_option.py +++ /dev/null @@ -1,21 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/signal_pricing_option.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import Field, RootModel - -from . import vendor_pricing_option - - -class SignalPricingOption(RootModel[vendor_pricing_option.VendorPricingOption]): - root: Annotated[ - vendor_pricing_option.VendorPricingOption, - Field( - description='Deprecated — use vendor-pricing-option.json for new implementations. This alias is retained for backward compatibility.', - title='Signal Pricing Option', - ), - ] diff --git a/src/adcp/types/generated_poc/core/signal_ref.py b/src/adcp/types/generated_poc/core/signal_ref.py deleted file mode 100644 index 888ba70ba..000000000 --- a/src/adcp/types/generated_poc/core/signal_ref.py +++ /dev/null @@ -1,87 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/signal_ref.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - - -class SignalRef1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['product'], - Field( - description="Discriminator indicating the signal resolves through the selected product's included_signals or signal_targeting_options." - ), - ] = 'product' - signal_id: Annotated[ - str, - Field( - description='Product-local signal identifier. For local signals exposed on both get_signals and get_products, this MUST match get_signals.signals[].signal_ref.signal_id for the same signal.', - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalRef2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['data_provider'], - Field( - description="Discriminator indicating the signal resolves through a data provider's published adagents.json signals[]." - ), - ] = 'data_provider' - data_provider_domain: Annotated[ - str, - Field( - description='Domain that publishes the signal definition in its adagents.json signals[].', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - signal_id: Annotated[ - str, - Field( - description="Signal identifier within the data provider's published adagents.json signals[].", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalRef3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['signal_source'], - Field( - description='Discriminator indicating the signal resolves through the issuing signal source.' - ), - ] = 'signal_source' - signal_source_url: Annotated[ - AnyUrl, Field(description='URL of the signal source that issues this source-native signal.') - ] - signal_id: Annotated[ - str, - Field( - description="Signal identifier within the issuing signal source's signal set.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -SignalRef = Annotated[ - SignalRef1 | SignalRef2 | SignalRef3, - Field( - description="Reference to a named signal definition. Uses scope as discriminator: 'data_provider' for a signal resolved through published adagents.json signals[], 'signal_source' for a source-native signal resolved through the issuing signal source, or 'product' for a product-local signal option. Scope is the resolution path, not provenance; authoritative enrichment lives on the seller, signal source, or data-provider signal definition, not on this reference.", - discriminator='scope', - title='Signal Ref', - ), -] diff --git a/src/adcp/types/generated_poc/core/signal_selection_group_rule.py b/src/adcp/types/generated_poc/core/signal_selection_group_rule.py deleted file mode 100644 index 830ae4f84..000000000 --- a/src/adcp/types/generated_poc/core/signal_selection_group_rule.py +++ /dev/null @@ -1,57 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/signal_selection_group_rule.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field - - -class TargetingMode(StrEnum): - include = 'include' - exclude = 'exclude' - - -class SelectionMode(StrEnum): - optional = 'optional' - required = 'required' - fixed = 'fixed' - - -class SignalSelectionGroupRule(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - selection_group: Annotated[ - str, - Field( - description='ProductSignalTargetingOption.selection_group value this rule applies to.' - ), - ] - targeting_mode: Annotated[ - TargetingMode | None, - Field( - description="How options in this selection_group are intended to be used in signal_targeting_groups. 'include' maps to child groups with operator 'any'. 'exclude' maps to child groups with operator 'none'. Omit when options in the group may be used according to each option's allowed_targeting_modes." - ), - ] = None - selection_mode: Annotated[ - SelectionMode | None, - Field( - description="Selection behavior for this selection_group. 'required' means at least min_selected_signals, or 1 when omitted. 'fixed' means default_selected options in this group are seller-applied and read-only." - ), - ] = None - min_selected_signals: Annotated[ - SchemaInt | None, - Field( - description="Minimum selected options from this selection_group. If selection_mode is 'required' and omitted, sellers MUST treat the minimum as 1.", - ge=0, - ), - ] = None - max_selected_signals: Annotated[ - SchemaInt | None, - Field(description='Maximum selected options from this selection_group.', ge=1), - ] = None diff --git a/src/adcp/types/generated_poc/core/signal_targeting.py b/src/adcp/types/generated_poc/core/signal_targeting.py deleted file mode 100644 index d9de5babe..000000000 --- a/src/adcp/types/generated_poc/core/signal_targeting.py +++ /dev/null @@ -1,106 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/signal_targeting.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictBool, StrictFloat - -from . import signal_id as signal_id_1 -from . import signal_ref as signal_ref_1 - - -class SignalTargeting1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - signal_ref_1.SignalRef | None, - Field(description='The signal to target. New targeting constraints SHOULD use signal_ref.'), - ] = None - signal_id: Annotated[ - signal_id_1.SignalId | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - ), - ] = None - value_type: Annotated[Literal['binary'], Field(description='Discriminator for binary signals')] = 'binary' - value: Annotated[ - StrictBool, - Field( - description='Whether to include (true) or exclude (false) users matching this signal' - ), - ] - - -class SignalTargeting2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - signal_ref_1.SignalRef | None, - Field(description='The signal to target. New targeting constraints SHOULD use signal_ref.'), - ] = None - signal_id: Annotated[ - signal_id_1.SignalId | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - ), - ] = None - value_type: Annotated[ - Literal['categorical'], Field(description='Discriminator for categorical signals') - ] = 'categorical' - values: Annotated[ - list[str], - Field( - description='Values to target. Users with any of these values will be included.', - min_length=1, - ), - ] - - -class SignalTargeting3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - signal_ref_1.SignalRef | None, - Field(description='The signal to target. New targeting constraints SHOULD use signal_ref.'), - ] = None - signal_id: Annotated[ - signal_id_1.SignalId | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - ), - ] = None - value_type: Annotated[ - Literal['numeric'], Field(description='Discriminator for numeric signals') - ] = 'numeric' - min_value: Annotated[ - StrictFloat | None, - Field( - description="Minimum value (inclusive). Omit for no minimum. Must be <= max_value when both are provided. Should be >= signal's range.min if defined." - ), - ] = None - max_value: Annotated[ - StrictFloat | None, - Field( - description="Maximum value (inclusive). Omit for no maximum. Must be >= min_value when both are provided. Should be <= signal's range.max if defined." - ), - ] = None - - -SignalTargeting = Annotated[ - SignalTargeting1 | SignalTargeting2 | SignalTargeting3, - Field( - description='Targeting constraint for a specific signal. Uses value_type as discriminator to determine the targeting expression format.', - discriminator='value_type', - title='Signal Targeting', - ), -] diff --git a/src/adcp/types/generated_poc/core/signal_targeting_expression.py b/src/adcp/types/generated_poc/core/signal_targeting_expression.py deleted file mode 100644 index 93a9e8048..000000000 --- a/src/adcp/types/generated_poc/core/signal_targeting_expression.py +++ /dev/null @@ -1,75 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/signal_targeting_expression.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictFloat - -from . import signal_ref as signal_ref_1 - - -class SignalTargetingExpression1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[signal_ref_1.SignalRef, Field(description='Named signal being targeted.')] - value_type: Annotated[Literal['binary'], Field(description='Discriminator for binary signals.')] = 'binary' - value: Annotated[ - Literal[True], - Field( - description='Binary package signal entries match users for whom the signal is true. Use the parent group operator for include/exclude.' - ), - ] - - -class SignalTargetingExpression2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[signal_ref_1.SignalRef, Field(description='Named signal being targeted.')] - value_type: Annotated[ - Literal['categorical'], Field(description='Discriminator for categorical signals.') - ] = 'categorical' - values: Annotated[ - list[str], - Field( - description='Values to target. Users with any of these values match the expression.', - min_length=1, - ), - ] - - -class SignalTargetingExpression3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[signal_ref_1.SignalRef, Field(description='Named signal being targeted.')] - value_type: Annotated[ - Literal['numeric'], Field(description='Discriminator for numeric signals.') - ] = 'numeric' - min_value: Annotated[ - StrictFloat | None, - Field( - description="Minimum value, inclusive. Omit for no minimum. Should be within the signal definition's range when declared." - ), - ] = None - max_value: Annotated[ - StrictFloat | None, - Field( - description="Maximum value, inclusive. Omit for no maximum. Should be within the signal definition's range when declared." - ), - ] = None - - -SignalTargetingExpression = Annotated[ - SignalTargetingExpression1 | SignalTargetingExpression2 | SignalTargetingExpression3, - Field( - description='Predicate over a named signal definition. Signals are typed dimensions, similar to feature values: binary signals match true, categorical signals match one of a set of values, and numeric signals match a range. In package signal targeting groups, include/exclude semantics are controlled by the parent group operator, not by negating the expression.', - discriminator='value_type', - title='Signal Targeting Expression', - ), -] diff --git a/src/adcp/types/generated_poc/core/signal_targeting_rules.py b/src/adcp/types/generated_poc/core/signal_targeting_rules.py deleted file mode 100644 index 4d9d6d7be..000000000 --- a/src/adcp/types/generated_poc/core/signal_targeting_rules.py +++ /dev/null @@ -1,84 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/signal_targeting_rules.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field - -from . import signal_selection_group_rule - - -class ResolutionModel(StrEnum): - direct_targeting = 'direct_targeting' - seller_planned = 'seller_planned' - - -class SelectionMode(StrEnum): - optional = 'optional' - required = 'required' - fixed = 'fixed' - - -class SignalTargetingRules(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - resolution_model: Annotated[ - ResolutionModel | None, - Field( - description="How selected signal_targeting_options are resolved against the product's inventory. 'direct_targeting' means selected signals are applied as targeting predicates to the package inventory. 'seller_planned' means selected signals are planning inputs that the seller resolves against product-specific inventory, timing, availability, reach, or pacing constraints; buyers SHOULD NOT attempt to decompose the signal selection into lower-level inventory or schedule decisions. Use 'seller_planned' for products such as linear broadcast schedules where the audience definition may be portable but the audience-to-avails plan is seller-resolved." - ), - ] = ResolutionModel.direct_targeting - selection_mode: Annotated[ - SelectionMode | None, - Field( - description="Default selection behavior for selectable signals on this product. 'optional' means the buyer may select zero or more signals. 'required' means the buyer must select at least min_selected_signals, or 1 when min_selected_signals is omitted. 'fixed' means the seller applies the default_selected signals and the buyer cannot add or remove them; buyers SHOULD render those entries as read-only and sellers MUST echo them in package targeting_overlay.signal_targeting_groups. Use selection_group_rules for product-scoped products that need different behavior for different groups, such as fixed suppressions plus a required include tier." - ), - ] = SelectionMode.optional - min_selected_signals: Annotated[ - SchemaInt | None, - Field( - description="Minimum number of signals the buyer must select when selection_mode is 'required'. If selection_mode is 'required' and this field is omitted, sellers MUST treat the minimum as 1. Defaults to 0 for optional selection.", - ge=0, - ), - ] = None - max_selected_signals: Annotated[ - SchemaInt | None, - Field( - description='Maximum number of signals the buyer may select for a package. Omit when there is no declared limit beyond the available options.', - ge=1, - ), - ] = None - max_selected_per_group: Annotated[ - SchemaInt | None, - Field( - description='Maximum number of signal_targeting_options the buyer may select from the same ProductSignalTargetingOption.selection_group. Use 1 for mutually exclusive alternatives within each option group. This limit applies to product option grouping, not to the number of child groups in packages[].targeting_overlay.signal_targeting_groups.', - ge=1, - ), - ] = None - max_signal_targeting_groups: Annotated[ - SchemaInt | None, - Field( - description='Maximum number of child groups allowed in packages[].targeting_overlay.signal_targeting_groups.groups. Omit when the seller has no declared limit beyond product terms.', - ge=1, - ), - ] = None - max_signals_per_targeting_group: Annotated[ - SchemaInt | None, - Field( - description='Maximum number of signals allowed in each packages[].targeting_overlay.signal_targeting_groups.groups[].signals array. Omit when the seller has no declared limit beyond product terms.', - ge=1, - ), - ] = None - selection_group_rules: Annotated[ - list[signal_selection_group_rule.SignalSelectionGroupRule] | None, - Field( - description='Optional product-scoped overrides for specific ProductSignalTargetingOption.selection_group values. Use this when one product has mixed behavior, such as fixed seller-applied suppressions, a required pick-one include tier, optional buyer-selected exclusions, or heterogeneous targeting planes that must be represented as separate ANDed clauses. Rules apply only to options whose selection_group matches. When selection_group_rules are present, each packages[].targeting_overlay.signal_targeting_groups child group MUST contain signals from exactly one selection_group and one targeting_mode, and buyers MUST send at most one child group for each (selection_group, targeting_mode) pair. Sellers MUST reject duplicate, mixed, or collapsed groups that combine distinct selection_group_rules into the same child group.', - min_length=1, - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/sla_window.py b/src/adcp/types/generated_poc/core/sla_window.py deleted file mode 100644 index f6545809d..000000000 --- a/src/adcp/types/generated_poc/core/sla_window.py +++ /dev/null @@ -1,33 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/sla_window.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - - -class SlaWindow(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - regex_engine="python-re", - ) - response_max: Annotated[ - str | None, - Field( - description='Maximum elapsed time from when the buyer issues the action to when the seller acknowledges receipt (mode-appropriate: synchronous response for self_serve, tolerance decision for conditional_self_serve, or queue acknowledgement for seller_managed and legacy requires_approval). Sellers include weekends and non-working periods in the maximum. ISO 8601 duration.', - examples=['PT5M', 'PT4H', 'P1D'], - pattern='^P(?!$)(\\d+Y)?(\\d+M)?(\\d+D)?(T(\\d+H)?(\\d+M)?(\\d+S)?)?$', - ), - ] = None - completion_max: Annotated[ - str | None, - Field( - description='Maximum elapsed time from buyer issuing the action to the seller completing it (mutation applied, proposal finalized, or seller-managed decision resolved). Sellers include weekends and non-working periods in the maximum. ISO 8601 duration.', - examples=['PT1H', 'PT24H', 'P2D'], - pattern='^P(?!$)(\\d+Y)?(\\d+M)?(\\d+D)?(T(\\d+H)?(\\d+M)?(\\d+S)?)?$', - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/special.py b/src/adcp/types/generated_poc/core/special.py deleted file mode 100644 index c2b96e54d..000000000 --- a/src/adcp/types/generated_poc/core/special.py +++ /dev/null @@ -1,31 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/special.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - -from ..enums import special_category - - -class Special(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, Field(description="Name of the event (e.g., 'Olympics 2028', 'Super Bowl LXI')") - ] - category: Annotated[ - special_category.SpecialCategory | None, Field(description='Category of the event') - ] = None - starts: Annotated[ - AwareDatetime | None, Field(description='When the event starts (ISO 8601)') - ] = None - ends: Annotated[ - AwareDatetime | None, - Field(description='When the event ends (ISO 8601). Omit for single-day events.'), - ] = None diff --git a/src/adcp/types/generated_poc/core/spot_reporting_capability.py b/src/adcp/types/generated_poc/core/spot_reporting_capability.py deleted file mode 100644 index 597e24863..000000000 --- a/src/adcp/types/generated_poc/core/spot_reporting_capability.py +++ /dev/null @@ -1,24 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/spot_reporting_capability.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..enums import available_metric - - -class SpotReportingCapability(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - available_metrics: Annotated[ - list[available_metric.AvailableMetric], - Field( - description='Metrics available at spot grain. A declared metric may be absent from a provisional measurement window and appear when a later window supersedes it. A metric omitted from this declaration is not promised at spot grain even when it is available at package grain.' - ), - ] diff --git a/src/adcp/types/generated_poc/core/start_timing.py b/src/adcp/types/generated_poc/core/start_timing.py deleted file mode 100644 index 3992e2387..000000000 --- a/src/adcp/types/generated_poc/core/start_timing.py +++ /dev/null @@ -1,17 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/start_timing.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from pydantic import AwareDatetime, Field - - -StartTiming = Annotated[ - Literal['asap'] | AwareDatetime, - Field( - description="Campaign start timing: 'asap' or ISO 8601 date-time", title='Start Timing' - ), -] diff --git a/src/adcp/types/generated_poc/core/store_item.py b/src/adcp/types/generated_poc/core/store_item.py deleted file mode 100644 index a93a4eb49..000000000 --- a/src/adcp/types/generated_poc/core/store_item.py +++ /dev/null @@ -1,98 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/store_item.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, StrictFloat - -from . import catchment -from . import ext as ext_1 - - -class Location(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - lat: Annotated[ - StrictFloat, Field(description='Latitude in decimal degrees (WGS 84).', ge=-90.0, le=90.0) - ] - lng: Annotated[ - StrictFloat, - Field(description='Longitude in decimal degrees (WGS 84).', ge=-180.0, le=180.0), - ] - - -class Address(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - street: Annotated[str | None, Field(description="Street address (e.g., '123 Main St').")] = None - city: Annotated[str | None, Field(description='City name.')] = None - region: Annotated[ - str | None, - Field( - description="State, province, or region. ISO 3166-2 subdivision code preferred (e.g., 'NL-NH', 'US-CA')." - ), - ] = None - postal_code: Annotated[str | None, Field(description='Postal or ZIP code.')] = None - country: Annotated[ - str | None, Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = None - - -class StoreItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - store_id: Annotated[ - str, - Field( - description='Unique identifier for this store. Used to reference specific stores in targeting, inventory feeds, and creative templates.' - ), - ] - name: Annotated[ - str, - Field( - description="Human-readable store name (e.g., 'Amsterdam Flagship', 'Brooklyn Heights')." - ), - ] - location: Annotated[Location, Field(description='Geographic coordinates of the store.')] - address: Annotated[ - Address | None, Field(description='Structured address for display and geocoding fallback.') - ] = None - catchments: Annotated[ - list[catchment.Catchment] | None, - Field( - description='Catchment areas for this store. Each defines a reachable area using travel time (isochrone), simple radius, or pre-computed GeoJSON. Multiple catchments allow different modes — e.g., 15-minute drive AND 10-minute walk.', - min_length=1, - ), - ] = None - phone: Annotated[ - str | None, Field(description="Store phone number in E.164 format (e.g., '+31201234567').") - ] = None - url: Annotated[ - AnyUrl | None, - Field(description='Store-specific page URL (e.g., store locator detail page).'), - ] = None - hours: Annotated[ - dict[ - Literal['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'], - str, - ] - | None, - Field( - description='Operating hours. Keys are ISO day names (monday–sunday), values are time ranges.' - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description="Tags for filtering stores in targeting and creative selection (e.g., 'flagship', 'pickup', 'pharmacy').", - min_length=1, - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/talent.py b/src/adcp/types/generated_poc/core/talent.py deleted file mode 100644 index 5607dfb08..000000000 --- a/src/adcp/types/generated_poc/core/talent.py +++ /dev/null @@ -1,29 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/talent.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - -from ..enums import talent_role - - -class Talent(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - role: Annotated[ - talent_role.TalentRole, - Field(description='Role of this person on the collection or installment'), - ] - name: Annotated[str, Field(description="Person's name as credited on the collection")] - brand_url: Annotated[ - AnyUrl | None, - Field( - description="URL to this person's brand.json entry. Enables buyer agents to evaluate the talent's brand identity and associations." - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/targeting.py b/src/adcp/types/generated_poc/core/targeting.py deleted file mode 100644 index 17707ed15..000000000 --- a/src/adcp/types/generated_poc/core/targeting.py +++ /dev/null @@ -1,476 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/targeting.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from collections.abc import Sequence -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, StrictBool, StrictFloat - -from ..enums import age_verification_method, browser_family -from ..enums import device_platform as device_platform_1 -from ..enums import device_type as device_type_1 -from ..enums import distance_unit -from ..enums import match_type as match_type_1 -from ..enums import metro_system -from ..enums import transport_mode as transport_mode_1 -from ..enums import travel_time_unit -from . import collection_list_ref -from . import collection_selection as collection_selection_1 -from . import daypart_target, demographic_targeting_intent -from . import ext as ext_1 -from . import frequency_cap as frequency_cap_1 -from . import ( - geo_metro, - geo_place_area, - locale_tag, - negative_keyword, - package_signal_targeting_groups, -) -from . import placement_selection as placement_selection_1 -from . import postal_area, property_list_ref -from . import signal_targeting as signal_targeting_1 - - -class GeoCountry(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{2}$'} - - -class GeoCountriesExcludeItem(GeoCountry): - pass - - -class GeoRegion(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{2}-[A-Z0-9]{1,3}$'} - - -class GeoRegionsExcludeItem(GeoRegion): - pass - - -class GeoMetrosExcludeItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - system: Annotated[ - metro_system.MetroAreaSystem, - Field(description="Metro area classification system (e.g., 'nielsen_dma', 'uk_itl2')"), - ] - values: Annotated[ - list[str], - Field( - description="Metro codes to exclude within the system (e.g., ['501', '602'] for Nielsen DMAs)", - min_length=1, - ), - ] - - -class AgeRestriction(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - min: Annotated[SchemaInt, Field(description='Minimum age required', ge=13, le=99)] - verification_required: Annotated[ - StrictBool | None, - Field(description='Whether verified age (not inferred) is required for compliance'), - ] = False - accepted_methods: Annotated[ - list[age_verification_method.AgeVerificationMethod] | None, - Field( - description='Accepted verification methods. If omitted, any method the platform supports is acceptable.', - min_length=1, - ), - ] = None - - -class StoreCatchment(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - catalog_id: Annotated[ - str, Field(description='Synced store-type catalog ID from sync_catalogs.') - ] - store_ids: Annotated[ - list[str] | None, - Field( - description='Filter to specific stores within the catalog. Omit to target all stores.', - min_length=1, - ), - ] = None - catchment_ids: Annotated[ - list[str] | None, - Field( - description="Catchment zone IDs to target (e.g., 'walk', 'drive'). Omit to target all catchment zones.", - min_length=1, - ), - ] = None - - -class Radius(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - value: Annotated[StrictFloat, Field(description='Radius distance.', gt=0.0)] - unit: Annotated[distance_unit.DistanceUnit, Field(description='Distance unit.')] - - -class Type(StrEnum): - Polygon = 'Polygon' - MultiPolygon = 'MultiPolygon' - - -class Geometry(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Annotated[Type, Field(description='GeoJSON geometry type.')] - coordinates: Annotated[ - list[Any], - Field( - description='GeoJSON coordinates array. For Polygon: array of linear rings. For MultiPolygon: array of polygons.' - ), - ] - - -class KeywordTarget(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - keyword: Annotated[str, Field(description='The keyword to target', min_length=1)] - match_type: match_type_1.MatchType - bid_price: Annotated[ - StrictFloat | None, - Field( - description="Per-keyword bid price, denominated in the same currency as the package's pricing option. Overrides the package-level bid_price for this keyword. Inherits the max_bid interpretation from the pricing option: when max_bid is true, this is the keyword's bid ceiling; when false, this is the exact bid. If omitted, the package bid_price applies.", - ge=0.0, - ), - ] = None - - -class TravelTime(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - value: Annotated[StrictFloat, Field(description='Travel time limit.', ge=1.0)] - unit: travel_time_unit.TravelTimeUnit - - -class GeoProximityItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - lat: Annotated[ - StrictFloat | None, - Field( - description='Latitude in decimal degrees (WGS 84). Required for travel_time and radius methods.', - ge=-90.0, - le=90.0, - ), - ] = None - lng: Annotated[ - StrictFloat | None, - Field( - description='Longitude in decimal degrees (WGS 84). Required for travel_time and radius methods.', - ge=-180.0, - le=180.0, - ), - ] = None - label: Annotated[ - str | None, - Field( - description="Human-readable label for this entry (e.g., 'Düsseldorf', 'Heathrow Airport', 'Primary trade area')." - ), - ] = None - travel_time: Annotated[ - TravelTime | None, - Field( - description='Travel time limit for isochrone calculation. The platform resolves this to a geographic boundary based on actual transportation networks.' - ), - ] = None - transport_mode: Annotated[ - transport_mode_1.TransportMode | None, - Field( - description='Transportation mode for isochrone calculation. Required when travel_time is provided.' - ), - ] = None - radius: Annotated[ - Radius | None, - Field( - description='Simple radius from the point. The platform draws a circle of this distance around the coordinates.' - ), - ] = None - geometry: Annotated[ - Geometry | None, - Field( - description='Pre-computed GeoJSON geometry defining the proximity boundary. Use when the buyer has already calculated isochrones (via TravelTime, Mapbox, etc.) or has custom boundaries. When geometry is provided, lat/lng are not required.' - ), - ] = None - ext: ext_1.ExtensionObject | None = None - - -class TargetingOverlay(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - geo_countries: Annotated[ - list[GeoCountry] | None, - Field( - description="Restrict delivery to specific countries. ISO 3166-1 alpha-2 codes (e.g., 'US', 'GB', 'DE').", - min_length=1, - ), - ] = None - geo_countries_exclude: Annotated[ - Sequence[GeoCountriesExcludeItem] | None, - Field( - description="Exclude specific countries from delivery. ISO 3166-1 alpha-2 codes (e.g., 'US', 'GB', 'DE').", - min_length=1, - ), - ] = None - geo_regions: Annotated[ - list[GeoRegion] | None, - Field( - description='Restrict delivery to exact canonical ISO 3166-2 subdivisions (states, provinces, regions, departments, or other subdivision categories). Unknown identifiers are invalid. At create or update, sellers MUST reject unsupported identifiers and MUST NOT silently widen, drop, or partially apply the list. During get_products, a seller may instead return a sparse, buyer-reviewable targeting_resolution modification for a valid but unsupported requested outcome. Exact internal translation preserves accepted identifiers in package readback.', - min_length=1, - ), - ] = None - geo_regions_exclude: Annotated[ - Sequence[GeoRegionsExcludeItem] | None, - Field( - description='Exclude exact canonical ISO 3166-2 subdivisions. Support is independent from geo_regions inclusion support. Unknown identifiers and values also present in geo_regions are invalid. At create or update, sellers MUST reject unsupported identifiers and partial application; during get_products, a seller may instead return a sparse, buyer-reviewable targeting_resolution modification for a valid but unsupported requested outcome.', - min_length=1, - ), - ] = None - geo_metros: Annotated[ - list[geo_metro.GeoMetro] | None, - Field( - description='Restrict delivery to specific metro areas. Each entry specifies the classification system and target values. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - title='Targeting Geo Metros', - ), - ] = None - geo_metros_exclude: Annotated[ - Sequence[GeoMetrosExcludeItem] | None, - Field( - description='Exclude specific metro areas from delivery. Each entry specifies the classification system and excluded values. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - geo_postal_areas: Annotated[ - list[postal_area.PostalArea] | None, - Field( - description='Restrict delivery to specific postal areas. Prefer the native country + postal system form. The deprecated legacy country-fused postal-system tokens remain accepted for compatibility. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - geo_postal_areas_exclude: Annotated[ - Sequence[postal_area.PostalArea] | None, - Field( - description='Exclude specific postal areas from delivery. Prefer the native country + postal system form. The deprecated legacy country-fused postal-system tokens remain accepted for compatibility. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - geo_places: Annotated[ - list[geo_place_area.GeographicPlaceArea] | None, - Field( - description='Restrict delivery to catalog-backed named places. Values MUST be stable identifiers in the declared system, not display names. Sellers must declare supported systems, countries, and place types in get_adcp_capabilities and reject unsupported entries rather than silently dropping them.', - min_length=1, - ), - ] = None - geo_places_exclude: Annotated[ - list[geo_place_area.GeographicPlaceArea] | None, - Field( - description='Exclude catalog-backed named places. Uses the same identifier-based shape as geo_places. Sellers MUST reject overlap with geo_places for the same country, system, place_type, and value.', - min_length=1, - ), - ] = None - daypart_targets: Annotated[ - list[daypart_target.DaypartTarget] | None, - Field( - description='Restrict delivery to specific time windows. Each entry specifies days of week, an hour range, and an optional timezone that defaults to inventory_local. A concrete IANA zone uses one shared civil-time clock, while inventory_local evaluates each inventory unit in its seller-assigned local timezone. Entries are independent and MAY use different clocks.', - min_length=1, - ), - ] = None - axe_include_segment: Annotated[ - str | None, - Field( - deprecated=True, - description='Deprecated: Use TMP provider fields instead. AXE segment ID to include for targeting.', - ), - ] = None - axe_exclude_segment: Annotated[ - str | None, - Field( - deprecated=True, - description='Deprecated: Use TMP provider fields instead. AXE segment ID to exclude from targeting.', - ), - ] = None - audience_include: Annotated[ - list[str] | None, - Field( - description='Restrict delivery to members of these first-party CRM audiences. Only users present in the uploaded lists are eligible. References audience_id values from sync_audiences on the same seller account — audience IDs are not portable across sellers. Not for lookalike expansion — express that intent in the campaign brief. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - audience_exclude: Annotated[ - list[str] | None, - Field( - description='Suppress delivery to members of these first-party CRM audiences. Matched users are excluded regardless of other targeting. References audience_id values from sync_audiences on the same seller account — audience IDs are not portable across sellers. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - signal_targeting_groups: Annotated[ - package_signal_targeting_groups.PackageSignalTargetingGroups | None, - Field( - description="Basic Boolean grouping for seller-offered signals. v1 supports a required top-level operator 'all' and child groups with operator 'any' for include groups or 'none' for exclusion groups. Example semantics: group 1 any(A, B) plus group 2 none(C, D) means (A OR B) AND NOT (C OR D). Signal entries reference named signal definitions with signal_ref scope 'product' for product-local signal options or scope 'data_provider' for external signals published in adagents.json signals[]. For simple include-only targeting, send one child group with operator 'any'. Sellers SHOULD reject entries that are not available for the product through inline signal_targeting_options or get_signals, are not active for the account, or exceed the product's signal_targeting_allowed/signal_targeting_rules/product terms. Signal targeting limits are product-scoped, not declared in get_adcp_capabilities, because products may be backed by different ad servers. Sellers MUST echo applied signal_targeting_groups on the resulting package state, including fixed/default selections. Sellers MAY return REQUOTE_REQUIRED when a targeting mutation changes commercial terms.", - title='Targeting Signal Groups', - ), - ] = None - signal_targeting: Annotated[ - list[signal_targeting_1.SignalTargeting] | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_targeting_groups for package-level signal targeting. Legacy flat signal_targeting remains accepted during the SignalRef migration window but cannot express grouped include/exclude composition or product-scoped pricing.', - min_length=1, - ), - ] = None - demographics: Annotated[ - demographic_targeting_intent.DemographicTargetingIntent | None, - Field( - description='Canonical demographic audience targeting intent with optional constraints on how age may be determined. This is distinct from age_restriction: demographics selects an audience, while age_restriction expresses a legal eligibility or verification floor. Fresh create/update targeting MUST compile exactly or be rejected. During get_products, a seller may offer a different configured predicate only through sparse targeting_resolution modifications on a distinguishable product_id; selecting that product accepts the alternative. Sellers never silently broaden, narrow, default, drop, or substitute the basis.' - ), - ] = None - frequency_cap: Annotated[ - frequency_cap_1.FrequencyCap | None, Field(title='Targeting Frequency Cap') - ] = None - property_list: Annotated[ - property_list_ref.PropertyListReference | None, - Field( - description="Reference to a property list for targeting specific properties within this product. The package runs on the intersection of the product's publisher_properties and this list. Sellers SHOULD return a validation error if the product has property_targeting_allowed: false.", - title='Targeting Property List', - ), - ] = None - property_list_exclude: Annotated[ - property_list_ref.PropertyListReference | None, - Field( - description="Reference to a property list whose properties must not carry the buyer's ads. Matched properties are removed from delivery. Use for brand-safety do-not-run lists (apps, sites). Exclude wins on overlap with property_list, and applies regardless of the product's property_targeting_allowed flag. Seller must declare support in get_adcp_capabilities." - ), - ] = None - collection_list: Annotated[ - collection_list_ref.CollectionListReference | None, - Field( - description='Reference to a collection list for including specific collections (programs, publications, channels) within this product. The package runs on the intersection of matched collections and this list. Use for inclusion-based collection targeting. Seller must declare support in get_adcp_capabilities.', - title='Targeting Collection List', - ), - ] = None - collection_list_exclude: Annotated[ - collection_list_ref.CollectionListReference | None, - Field( - description="Reference to a collection list for excluding specific collections (programs, publications, channels) from this product. Matched collections must not carry the buyer's ads. Use for brand safety do-not-air lists. Seller must declare support in get_adcp_capabilities." - ), - ] = None - placement_selection: Annotated[ - placement_selection_1.PlacementSelection | None, - Field( - description='Purchased placement selection within the product. This constrains package inventory; it is distinct from creative_assignments[].placement_refs, which only route individual creatives within the purchased set. On create, mode selected supplies the complete selected set and mode default uses the product default. In request-side Targeting Input, a non-null value replaces this dimension, omission preserves or inherits it, and null clears it when the product permits that broader inventory set.' - ), - ] = None - collection_selection: Annotated[ - collection_selection_1.CollectionSelection | None, - Field( - description="Purchased collection selection within the product. On create, mode selected supplies the complete selected set and mode default uses the product's full bundle. On package readback this is the committed selection sellers MUST echo as concrete selectors, materializing any collection_list composition; collection_list fields remain the buyer-managed list mechanism. In request-side Targeting Input, a non-null value replaces this dimension, omission preserves or inherits it, and null clears it when the product permits that broader inventory set.", - title='Targeting Collection Selection', - ), - ] = None - age_restriction: Annotated[ - AgeRestriction | None, - Field( - description='Age restriction for compliance. Use for legal requirements (alcohol, gambling), not audience targeting.' - ), - ] = None - device_platform: Annotated[ - list[device_platform_1.DevicePlatform] | None, - Field( - description='Restrict to specific platforms. Use for technical compatibility (app only works on iOS). Values from Sec-CH-UA-Platform standard, extended for CTV.', - min_length=1, - ), - ] = None - device_platform_exclude: Annotated[ - list[device_platform_1.DevicePlatform] | None, - Field( - description='Exclude specific operating-system platforms from delivery. When a platform appears in both device_platform and device_platform_exclude, exclusion wins. Sellers MUST reject a request they cannot enforce rather than silently dropping the exclusion.', - min_length=1, - ), - ] = None - device_type: Annotated[ - list[device_type_1.DeviceType] | None, - Field( - description='Restrict to specific device form factors. Use for campaigns targeting hardware categories rather than operating systems (e.g., mobile-only promotions, CTV campaigns).', - min_length=1, - ), - ] = None - device_type_exclude: Annotated[ - list[device_type_1.DeviceType] | None, - Field( - description='Exclude specific device form factors from delivery (e.g., exclude CTV for app-install campaigns).', - min_length=1, - ), - ] = None - browser: Annotated[ - list[browser_family.BrowserFamily] | None, - Field( - description='Restrict delivery to specific canonical browser families in the impression delivery and rendering environment, not the post-click landing-page browser. Values MUST NOT be inferred solely from operating system, device, web/mobile-web inventory, or placement. Values in this array use OR semantics. When browser is supplied, families not listed are ineligible: other includes a seller-recognized family that is not explicitly enumerated, while unknown includes a browser the seller cannot classify into a recognized family. When the same family appears in browser and browser_exclude, exclusion wins. Browser and device constraints intersect; a seller that cannot enforce the exact combination MUST exclude or explicitly reconfigure the product during discovery and MUST reject it at create or update rather than silently widening delivery. Browser versions and seller-native IDs are intentionally unsupported.', - min_length=1, - ), - ] = None - browser_exclude: Annotated[ - list[browser_family.BrowserFamily] | None, - Field( - description='Exclude specific canonical browser families from delivery. other excludes seller-recognized families that are not explicitly enumerated; unknown excludes browsers the seller cannot classify into a recognized family. When the same family appears in browser and browser_exclude, exclusion wins. Sellers MUST reject a request they cannot enforce rather than silently dropping the exclusion.', - min_length=1, - ), - ] = None - store_catchments: Annotated[ - list[StoreCatchment] | None, - Field( - description='Target users within store catchment areas from a synced store catalog. Each entry references a store-type catalog and optionally narrows to specific stores or catchment zones.', - min_length=1, - ), - ] = None - geo_proximity: Annotated[ - list[GeoProximityItem] | None, - Field( - description='Target users within travel time, distance, or a custom boundary around arbitrary geographic points. Multiple entries use OR semantics — a user within range of any listed point is eligible. For campaigns targeting 10+ locations, consider using store_catchments with a location catalog instead. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - language: Annotated[ - list[locale_tag.LanguageTag] | None, - Field( - description="Restrict to users with specific language preferences using canonical BCP 47 language ranges. Each buyer range is evaluated against a user's language-preference tag with RFC 4647 section 3.3.1 Basic Filtering: 'fr' matches 'fr', 'fr-CA', and 'fr-FR', while 'fr-CA' matches 'fr-CA' and more-specific descendants but not 'fr' or 'fr-FR'. Values use OR logic.", - min_length=1, - title='Targeting Languages', - ), - ] = None - keyword_targets: Annotated[ - list[KeywordTarget] | None, - Field( - description='Keyword targeting for search and retail media platforms. Restricts delivery to queries matching the specified keywords. Each keyword is identified by the tuple (keyword, match_type) — the same keyword string with different match types are distinct targets. Sellers SHOULD reject duplicate (keyword, match_type) pairs within a single request. Seller must declare support in get_adcp_capabilities.', - min_length=1, - title='Targeting Keywords', - ), - ] = None - negative_keywords: Annotated[ - list[negative_keyword.NegativeKeyword] | None, - Field( - description='Keywords to exclude from delivery. Queries matching these keywords will not trigger the ad. Each negative keyword is identified by the tuple (keyword, match_type). Seller must declare support in get_adcp_capabilities.', - min_length=1, - title='Targeting Negative Keywords', - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/targeting_input.py b/src/adcp/types/generated_poc/core/targeting_input.py deleted file mode 100644 index a86a60833..000000000 --- a/src/adcp/types/generated_poc/core/targeting_input.py +++ /dev/null @@ -1,409 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/targeting_input.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, StrictFloat - -from ..enums import browser_family -from ..enums import device_platform as device_platform_1 -from ..enums import device_type as device_type_1 -from ..enums import distance_unit -from ..enums import match_type as match_type_1 -from ..enums import metro_system -from ..enums import transport_mode as transport_mode_1 -from ..enums import travel_time_unit -from . import collection_list_ref -from . import collection_selection as collection_selection_1 -from . import daypart_target, demographic_targeting_intent -from . import ext as ext_1 -from . import frequency_cap as frequency_cap_1 -from . import ( - geo_metro, - geo_place_area, - locale_tag, - negative_keyword, - package_signal_targeting_groups, -) -from . import placement_selection as placement_selection_1 -from . import postal_area, property_list_ref -from . import signal_targeting as signal_targeting_1 -from . import targeting - - -class GeoCountry(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{2}$'} - - -class GeoCountriesExcludeItem(GeoCountry): - pass - - -class GeoRegion(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{2}-[A-Z0-9]{1,3}$'} - - -class GeoRegionsExcludeItem(GeoRegion): - pass - - -class GeoMetrosExcludeItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - system: Annotated[ - metro_system.MetroAreaSystem, - Field(description="Metro area classification system (e.g., 'nielsen_dma', 'uk_itl2')"), - ] - values: Annotated[ - list[str], - Field( - description="Metro codes to exclude within the system (e.g., ['501', '602'] for Nielsen DMAs)", - min_length=1, - ), - ] - - -class StoreCatchment(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - catalog_id: Annotated[ - str, Field(description='Synced store-type catalog ID from sync_catalogs.') - ] - store_ids: Annotated[ - list[str] | None, - Field( - description='Filter to specific stores within the catalog. Omit to target all stores.', - min_length=1, - ), - ] = None - catchment_ids: Annotated[ - list[str] | None, - Field( - description="Catchment zone IDs to target (e.g., 'walk', 'drive'). Omit to target all catchment zones.", - min_length=1, - ), - ] = None - - -class Radius(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - value: Annotated[StrictFloat, Field(description='Radius distance.', gt=0.0)] - unit: Annotated[distance_unit.DistanceUnit, Field(description='Distance unit.')] - - -class Type(StrEnum): - Polygon = 'Polygon' - MultiPolygon = 'MultiPolygon' - - -class Geometry(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Annotated[Type, Field(description='GeoJSON geometry type.')] - coordinates: Annotated[ - list[Any], - Field( - description='GeoJSON coordinates array. For Polygon: array of linear rings. For MultiPolygon: array of polygons.' - ), - ] - - -class KeywordTarget(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - keyword: Annotated[str, Field(description='The keyword to target', min_length=1)] - match_type: match_type_1.MatchType - bid_price: Annotated[ - StrictFloat | None, - Field( - description="Per-keyword bid price, denominated in the same currency as the package's pricing option. Overrides the package-level bid_price for this keyword. Inherits the max_bid interpretation from the pricing option: when max_bid is true, this is the keyword's bid ceiling; when false, this is the exact bid. If omitted, the package bid_price applies.", - ge=0.0, - ), - ] = None - - -class TravelTime(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - value: Annotated[StrictFloat, Field(description='Travel time limit.', ge=1.0)] - unit: travel_time_unit.TravelTimeUnit - - -class GeoProximityItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - lat: Annotated[ - StrictFloat | None, - Field( - description='Latitude in decimal degrees (WGS 84). Required for travel_time and radius methods.', - ge=-90.0, - le=90.0, - ), - ] = None - lng: Annotated[ - StrictFloat | None, - Field( - description='Longitude in decimal degrees (WGS 84). Required for travel_time and radius methods.', - ge=-180.0, - le=180.0, - ), - ] = None - label: Annotated[ - str | None, - Field( - description="Human-readable label for this entry (e.g., 'Düsseldorf', 'Heathrow Airport', 'Primary trade area')." - ), - ] = None - travel_time: Annotated[ - TravelTime | None, - Field( - description='Travel time limit for isochrone calculation. The platform resolves this to a geographic boundary based on actual transportation networks.' - ), - ] = None - transport_mode: Annotated[ - transport_mode_1.TransportMode | None, - Field( - description='Transportation mode for isochrone calculation. Required when travel_time is provided.' - ), - ] = None - radius: Annotated[ - Radius | None, - Field( - description='Simple radius from the point. The platform draws a circle of this distance around the coordinates.' - ), - ] = None - geometry: Annotated[ - Geometry | None, - Field( - description='Pre-computed GeoJSON geometry defining the proximity boundary. Use when the buyer has already calculated isochrones (via TravelTime, Mapbox, etc.) or has custom boundaries. When geometry is provided, lat/lng are not required.' - ), - ] = None - ext: ext_1.ExtensionObject | None = None - - -class TargetingOverlayInput(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - geo_countries: Annotated[ - list[GeoCountry] | None, - Field( - description="Restrict delivery to specific countries. ISO 3166-1 alpha-2 codes (e.g., 'US', 'GB', 'DE').", - min_length=1, - ), - ] = None - geo_countries_exclude: Annotated[ - list[GeoCountriesExcludeItem] | None, - Field( - description="Exclude specific countries from delivery. ISO 3166-1 alpha-2 codes (e.g., 'US', 'GB', 'DE').", - min_length=1, - ), - ] = None - geo_regions: Annotated[ - list[GeoRegion] | None, - Field( - description='Restrict delivery to exact canonical ISO 3166-2 subdivisions (states, provinces, regions, departments, or other subdivision categories). Unknown identifiers are invalid. At create or update, sellers MUST reject unsupported identifiers and MUST NOT silently widen, drop, or partially apply the list. During get_products, a seller may instead return a sparse, buyer-reviewable targeting_resolution modification for a valid but unsupported requested outcome. Exact internal translation preserves accepted identifiers in package readback.', - min_length=1, - ), - ] = None - geo_regions_exclude: Annotated[ - list[GeoRegionsExcludeItem] | None, - Field( - description='Exclude exact canonical ISO 3166-2 subdivisions. Support is independent from geo_regions inclusion support. Unknown identifiers and values also present in geo_regions are invalid. At create or update, sellers MUST reject unsupported identifiers and partial application; during get_products, a seller may instead return a sparse, buyer-reviewable targeting_resolution modification for a valid but unsupported requested outcome.', - min_length=1, - ), - ] = None - geo_metros: Annotated[ - list[geo_metro.GeoMetro] | None, - Field( - description='Restrict delivery to specific metro areas. Each entry specifies the classification system and target values. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - title='Targeting Geo Metros', - ), - ] = None - geo_metros_exclude: Annotated[ - list[GeoMetrosExcludeItem] | None, - Field( - description='Exclude specific metro areas from delivery. Each entry specifies the classification system and excluded values. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - geo_postal_areas: Annotated[ - list[postal_area.PostalArea] | None, - Field( - description='Restrict delivery to specific postal areas. Prefer the native country + postal system form. The deprecated legacy country-fused postal-system tokens remain accepted for compatibility. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - geo_postal_areas_exclude: Annotated[ - list[postal_area.PostalArea] | None, - Field( - description='Exclude specific postal areas from delivery. Prefer the native country + postal system form. The deprecated legacy country-fused postal-system tokens remain accepted for compatibility. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - geo_places: Annotated[ - list[geo_place_area.GeographicPlaceArea] | None, - Field( - description='Restrict delivery to catalog-backed named places. Values MUST be stable identifiers in the declared system, not display names. Sellers must declare supported systems, countries, and place types in get_adcp_capabilities and reject unsupported entries rather than silently dropping them.', - min_length=1, - ), - ] = None - geo_places_exclude: Annotated[ - list[geo_place_area.GeographicPlaceArea] | None, - Field( - description='Exclude catalog-backed named places. Uses the same identifier-based shape as geo_places. Sellers MUST reject overlap with geo_places for the same country, system, place_type, and value.', - min_length=1, - ), - ] = None - daypart_targets: Annotated[ - list[daypart_target.DaypartTarget] | None, - Field( - description='Restrict delivery to specific time windows. Each entry specifies days of week, an hour range, and an optional timezone that defaults to inventory_local. A concrete IANA zone uses one shared civil-time clock, while inventory_local evaluates each inventory unit in its seller-assigned local timezone. Entries are independent and MAY use different clocks.', - min_length=1, - ), - ] = None - axe_include_segment: Annotated[ - str | None, - Field( - deprecated=True, - description='Deprecated: Use TMP provider fields instead. AXE segment ID to include for targeting.', - ), - ] = None - axe_exclude_segment: Annotated[ - str | None, - Field( - deprecated=True, - description='Deprecated: Use TMP provider fields instead. AXE segment ID to exclude from targeting.', - ), - ] = None - audience_include: Annotated[ - list[str] | None, - Field( - description='Restrict delivery to members of these first-party CRM audiences. Only users present in the uploaded lists are eligible. References audience_id values from sync_audiences on the same seller account — audience IDs are not portable across sellers. Not for lookalike expansion — express that intent in the campaign brief. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - audience_exclude: Annotated[ - list[str] | None, - Field( - description='Suppress delivery to members of these first-party CRM audiences. Matched users are excluded regardless of other targeting. References audience_id values from sync_audiences on the same seller account — audience IDs are not portable across sellers. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - signal_targeting_groups: package_signal_targeting_groups.PackageSignalTargetingGroups | None = ( - None - ) - signal_targeting: Annotated[ - list[signal_targeting_1.SignalTargeting] | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_targeting_groups for package-level signal targeting. Legacy flat signal_targeting remains accepted during the SignalRef migration window but cannot express grouped include/exclude composition or product-scoped pricing.', - min_length=1, - ), - ] = None - demographics: demographic_targeting_intent.DemographicTargetingIntent | None = None - frequency_cap: frequency_cap_1.FrequencyCap | None = None - property_list: property_list_ref.PropertyListReference | None = None - property_list_exclude: property_list_ref.PropertyListReference | None = None - collection_list: collection_list_ref.CollectionListReference | None = None - collection_list_exclude: collection_list_ref.CollectionListReference | None = None - placement_selection: placement_selection_1.PlacementSelection | None = None - collection_selection: collection_selection_1.CollectionSelection | None = None - age_restriction: targeting.AgeRestriction | None = None - device_platform: Annotated[ - list[device_platform_1.DevicePlatform] | None, - Field( - description='Restrict to specific platforms. Use for technical compatibility (app only works on iOS). Values from Sec-CH-UA-Platform standard, extended for CTV.', - min_length=1, - ), - ] = None - device_platform_exclude: Annotated[ - list[device_platform_1.DevicePlatform] | None, - Field( - description='Exclude specific operating-system platforms from delivery. When a platform appears in both device_platform and device_platform_exclude, exclusion wins. Sellers MUST reject a request they cannot enforce rather than silently dropping the exclusion.', - min_length=1, - ), - ] = None - device_type: Annotated[ - list[device_type_1.DeviceType] | None, - Field( - description='Restrict to specific device form factors. Use for campaigns targeting hardware categories rather than operating systems (e.g., mobile-only promotions, CTV campaigns).', - min_length=1, - ), - ] = None - device_type_exclude: Annotated[ - list[device_type_1.DeviceType] | None, - Field( - description='Exclude specific device form factors from delivery (e.g., exclude CTV for app-install campaigns).', - min_length=1, - ), - ] = None - browser: Annotated[ - list[browser_family.BrowserFamily] | None, - Field( - description='Restrict delivery to specific canonical browser families in the impression delivery and rendering environment, not the post-click landing-page browser. Values MUST NOT be inferred solely from operating system, device, web/mobile-web inventory, or placement. Values in this array use OR semantics. When browser is supplied, families not listed are ineligible: other includes a seller-recognized family that is not explicitly enumerated, while unknown includes a browser the seller cannot classify into a recognized family. When the same family appears in browser and browser_exclude, exclusion wins. Browser and device constraints intersect; a seller that cannot enforce the exact combination MUST exclude or explicitly reconfigure the product during discovery and MUST reject it at create or update rather than silently widening delivery. Browser versions and seller-native IDs are intentionally unsupported.', - min_length=1, - ), - ] = None - browser_exclude: Annotated[ - list[browser_family.BrowserFamily] | None, - Field( - description='Exclude specific canonical browser families from delivery. other excludes seller-recognized families that are not explicitly enumerated; unknown excludes browsers the seller cannot classify into a recognized family. When the same family appears in browser and browser_exclude, exclusion wins. Sellers MUST reject a request they cannot enforce rather than silently dropping the exclusion.', - min_length=1, - ), - ] = None - store_catchments: Annotated[ - list[StoreCatchment] | None, - Field( - description='Target users within store catchment areas from a synced store catalog. Each entry references a store-type catalog and optionally narrows to specific stores or catchment zones.', - min_length=1, - ), - ] = None - geo_proximity: Annotated[ - list[GeoProximityItem] | None, - Field( - description='Target users within travel time, distance, or a custom boundary around arbitrary geographic points. Multiple entries use OR semantics — a user within range of any listed point is eligible. For campaigns targeting 10+ locations, consider using store_catchments with a location catalog instead. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - language: Annotated[ - list[locale_tag.LanguageTag] | None, - Field( - description="Restrict to users with specific language preferences using canonical BCP 47 language ranges. Each buyer range is evaluated against a user's language-preference tag with RFC 4647 section 3.3.1 Basic Filtering: 'fr' matches 'fr', 'fr-CA', and 'fr-FR', while 'fr-CA' matches 'fr-CA' and more-specific descendants but not 'fr' or 'fr-FR'. Values use OR logic.", - min_length=1, - title='Targeting Languages', - ), - ] = None - keyword_targets: Annotated[ - list[KeywordTarget] | None, - Field( - description='Keyword targeting for search and retail media platforms. Restricts delivery to queries matching the specified keywords. Each keyword is identified by the tuple (keyword, match_type) — the same keyword string with different match types are distinct targets. Sellers SHOULD reject duplicate (keyword, match_type) pairs within a single request. Seller must declare support in get_adcp_capabilities.', - min_length=1, - title='Targeting Keywords', - ), - ] = None - negative_keywords: Annotated[ - list[negative_keyword.NegativeKeyword] | None, - Field( - description='Keywords to exclude from delivery. Queries matching these keywords will not trigger the ad. Each negative keyword is identified by the tuple (keyword, match_type). Seller must declare support in get_adcp_capabilities.', - min_length=1, - title='Targeting Negative Keywords', - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/targeting_modification.py b/src/adcp/types/generated_poc/core/targeting_modification.py deleted file mode 100644 index 086fbe0e2..000000000 --- a/src/adcp/types/generated_poc/core/targeting_modification.py +++ /dev/null @@ -1,99 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/targeting_modification.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from . import ext as ext_1 - - -class TargetingModification1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - operation: Literal['replace'] = 'replace' - path: Annotated[ - str, - Field( - description='RFC 6901 JSON Pointer to one complete targeting value relative to targeting_overlay, for example /demographics/age. Array indexes are forbidden by protocol semantics even if syntactically valid JSON Pointer; target a stable whole dimension instead.', - pattern='^(?:/(?:[^~/]|~[01])*)+$', - ), - ] - applied: Annotated[ - Any, - Field( - description='Complete replacement value. It MUST validate against targeting.json at path; the seller then validates the complete effective overlay. This operation may narrow or broaden only as an explicit buyer-visible proposal.' - ), - ] - reason: Annotated[str, Field(min_length=1)] - ext: ext_1.ExtensionObject | None = None - - -class Path(StrEnum): - field_geo_countries = '/geo_countries' - field_geo_countries_exclude = '/geo_countries_exclude' - field_geo_regions = '/geo_regions' - field_geo_regions_exclude = '/geo_regions_exclude' - field_geo_postal_areas = '/geo_postal_areas' - field_geo_postal_areas_exclude = '/geo_postal_areas_exclude' - field_audience_include = '/audience_include' - field_audience_exclude = '/audience_exclude' - field_device_platform = '/device_platform' - field_device_platform_exclude = '/device_platform_exclude' - field_device_type = '/device_type' - field_device_type_exclude = '/device_type_exclude' - field_browser = '/browser' - field_browser_exclude = '/browser_exclude' - field_language = '/language' - - -class Selector(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - country: Annotated[str, Field(pattern='^[A-Z]{2}$')] - system: Annotated[str, Field(min_length=1)] - - -class TargetingModification2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - operation: Literal['remove_values'] = 'remove_values' - path: Annotated[ - Path, - Field( - description='Supported targeting field with string-set semantics. Postal-area paths identify their group with selector; direct top-level sets omit selector.' - ), - ] - selector: Annotated[ - Selector | None, - Field( - description='Stable identity of exactly one postal-area group in the requested array. Required only for postal-area paths. Zero or multiple matches make the modification invalid.' - ), - ] = None - values: Annotated[ - list[str], - Field( - description='Values removed from the requested set. Values absent from that set are invalid rather than no-ops.', - min_length=1, - ), - ] - reason: Annotated[str, Field(min_length=1)] - ext: ext_1.ExtensionObject | None = None - - -TargetingModification = Annotated[ - TargetingModification1 | TargetingModification2, - Field( - description='Sparse, buyer-reviewable change from targeting requested in get_products to targeting bound to a returned configured product. Entries are applied in array order to the original targeting_overlay; after every entry, the complete result MUST validate against targeting.json. A seller MUST NOT emit two replace operations for the same path or combine replace and remove_values on the same path. Selecting the configured product_id accepts the ordered result.', - discriminator='operation', - title='Targeting Modification', - ), -] diff --git a/src/adcp/types/generated_poc/core/targeting_overlay_requirements.py b/src/adcp/types/generated_poc/core/targeting_overlay_requirements.py deleted file mode 100644 index 9ae9b0230..000000000 --- a/src/adcp/types/generated_poc/core/targeting_overlay_requirements.py +++ /dev/null @@ -1,168 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/targeting_overlay_requirements.json -# timestamp: 2026-10-03T00:35:15+00:00 - -from __future__ import annotations - -from typing import Any, Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel - -from ..enums import browser_family, daypart_timezone_mode, match_type, metro_system, transport_mode -from . import ext as ext_1 -from . import ( - frequency_cap_requirements, - geo_place_requirement, - geo_region_requirement, - iana_timezone, - positive_postal_area_support, -) - - -class Required(RootModel[Literal[True]]): - root: Literal[True] - - -class MetroRequirement1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - systems: Annotated[list[metro_system.MetroAreaSystem], Field(min_length=1)] - - -class MetroRequirement(RootModel[Required | MetroRequirement1]): - root: Required | MetroRequirement1 - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - - - -class KeywordRequirement1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - supported_match_types: Annotated[list[match_type.MatchType], Field(min_length=1)] - - -class KeywordRequirement(RootModel[Required | KeywordRequirement1]): - root: Required | KeywordRequirement1 - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - - - -class BrowserRequirement1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - families: Annotated[list[browser_family.BrowserFamily], Field(min_length=1)] - - -class BrowserRequirement(RootModel[Required | BrowserRequirement1]): - root: Required | BrowserRequirement1 - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - - - -class DaypartRequirement1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - timezone_modes: Annotated[list[daypart_timezone_mode.DaypartTimezoneMode], Field(min_length=1)] - iana_timezones: Annotated[ - list[iana_timezone.IanaTimezoneIdentifier] | None, - Field( - description='Exact concrete IANA timezone identifiers that must remain selectable after discovery. Valid only when timezone_modes includes iana.', - min_length=1, - ), - ] = None - - -class DaypartRequirement(RootModel[Required | DaypartRequirement1]): - root: Required | DaypartRequirement1 - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - - - -class GeoProximity(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - radius: Required | None = None - travel_time: Required | None = None - geometry: Required | None = None - transport_modes: Annotated[list[transport_mode.TransportMode] | None, Field(min_length=1)] = ( - None - ) - - -class Demographics(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - age: Required - - -class TargetingOverlayRequirements(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - geo_countries: Required | None = None - geo_countries_exclude: Required | None = None - geo_regions: Required | geo_region_requirement.GeographicRegionRequirement | None = None - geo_regions_exclude: Required | geo_region_requirement.GeographicRegionRequirement | None = None - geo_metros: MetroRequirement | None = None - geo_metros_exclude: MetroRequirement | None = None - geo_places: geo_place_requirement.GeographicPlaceRequirement | None = None - geo_places_exclude: geo_place_requirement.GeographicPlaceRequirement | None = None - geo_postal_areas: Required | positive_postal_area_support.PositivePostalAreaSupport | None = ( - None - ) - geo_postal_areas_exclude: ( - Required | positive_postal_area_support.PositivePostalAreaSupport | None - ) = None - geo_proximity: Required | GeoProximity | None = None - daypart_targets: DaypartRequirement | None = None - audience_include: Required | None = None - audience_exclude: Required | None = None - signal_targeting_groups: Required | None = None - demographics: Required | Demographics | None = None - frequency_cap: Required | None = None - frequency_cap_support: Annotated[ - frequency_cap_requirements.FrequencyCapRequirements | None, - Field( - description='Minimum structured package-cap support. Matches a broad legacy frequency_cap: true or a containing frequency_cap_support object. Mutually exclusive with a frequency_cap: true requirement, which would exclude every constrained product.' - ), - ] = None - property_list: Required | None = None - property_list_exclude: Required | None = None - collection_list: Required | None = None - collection_list_exclude: Required | None = None - placement_selection: Required | None = None - age_restriction: Required | None = None - device_platform: Required | None = None - device_platform_exclude: Required | None = None - device_type: Required | None = None - device_type_exclude: Required | None = None - browser: BrowserRequirement | None = None - browser_exclude: BrowserRequirement | None = None - store_catchments: Required | None = None - language: Required | None = None - keyword_targets: KeywordRequirement | None = None - negative_keywords: KeywordRequirement | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/targeting_overlay_support.py b/src/adcp/types/generated_poc/core/targeting_overlay_support.py deleted file mode 100644 index 045645c96..000000000 --- a/src/adcp/types/generated_poc/core/targeting_overlay_support.py +++ /dev/null @@ -1,278 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/targeting_overlay_support.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Any, Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, RootModel, StringConstraints - -from ..enums import browser_family, daypart_timezone_mode, match_type, metro_system, transport_mode -from . import ext as ext_1 -from . import ( - frequency_cap_constraints, - geo_place_system, - geo_place_type, - geo_region_support, - iana_timezone, - positive_postal_area_support, -) - - -class PlacementSelection(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - max_values_per_package: Annotated[SchemaInt | None, Field(ge=1)] = None - max_packages: Annotated[SchemaInt | None, Field(ge=1)] = None - ext: ext_1.ExtensionObject | None = None - - -class Supported(RootModel[Literal[True]]): - root: Literal[True] - - -class CountrySupport1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - max_values_per_package: Annotated[ - SchemaInt, - Field( - description='Maximum number of country values accepted in this targeting field on one package.', - ge=1, - ), - ] - ext: ext_1.ExtensionObject | None = None - - -class CountrySupport(RootModel[Supported | CountrySupport1]): - root: Supported | CountrySupport1 - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - - - -class MetroSupport1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - systems: Annotated[list[metro_system.MetroAreaSystem], Field(min_length=1)] - max_values_per_package: Annotated[SchemaInt | None, Field(ge=1)] = None - max_packages: Annotated[ - SchemaInt | None, - Field( - description='Optional maximum number of independently targeted packages the seller will create from this configured product.', - ge=1, - ), - ] = None - ext: ext_1.ExtensionObject | None = None - - -class MetroSupport(RootModel[Supported | MetroSupport1]): - root: Supported | MetroSupport1 - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - - - -class SystemVersion(ScalarStr): - __slots__ = () - _constraints = {'min_length': 1} - - -class PlaceCatalogSupport(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - countries: Annotated[ - dict[Annotated[str, StringConstraints(pattern=r'^[A-Z]{2}$')], list[geo_place_type.GeographicPlaceType]], - Field(min_length=1), - ] - current_version: Annotated[ - str, - Field( - description='Version applied when a later package overlay omits system_version. Must be present in system_versions.', - min_length=1, - ), - ] - system_versions: Annotated[ - list[SystemVersion], - Field( - description='Exact catalog versions selectable on later package overlays.', min_length=1 - ), - ] - ext: ext_1.ExtensionObject | None = None - - -class PlaceSupport(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - systems: Annotated[ - dict[geo_place_system.GeographicPlaceIdentifierSystem, PlaceCatalogSupport], - Field(min_length=1), - ] - max_values_per_package: Annotated[SchemaInt | None, Field(ge=1)] = None - max_packages: Annotated[ - SchemaInt | None, - Field( - description='Optional maximum number of independently place-targeted packages the seller will create from this configured product.', - ge=1, - ), - ] = None - ext: ext_1.ExtensionObject | None = None - - -class KeywordSupport1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - supported_match_types: Annotated[list[match_type.MatchType], Field(min_length=1)] - ext: ext_1.ExtensionObject | None = None - - -class KeywordSupport(RootModel[Supported | KeywordSupport1]): - root: Supported | KeywordSupport1 - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - - - -class BrowserSupport1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - families: Annotated[list[browser_family.BrowserFamily], Field(min_length=1)] - ext: ext_1.ExtensionObject | None = None - - -class BrowserSupport(RootModel[Supported | BrowserSupport1]): - root: Supported | BrowserSupport1 - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - - - -class IanaTimezones(RootModel[list[iana_timezone.IanaTimezoneIdentifier]]): - root: Annotated[ - list[iana_timezone.IanaTimezoneIdentifier], - Field( - description="Concrete IANA timezone identifiers accepted by this product. true means every identifier valid in the seller's supported IANA TZDB; an array is the exact supported subset. Required when timezone_modes includes iana and forbidden otherwise.", - min_length=1, - ), - ] - - -class DaypartSupport1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - timezone_modes: Annotated[list[daypart_timezone_mode.DaypartTimezoneMode], Field(min_length=1)] - iana_timezones: Annotated[ - Literal[True] | IanaTimezones | None, - Field( - description="Concrete IANA timezone identifiers accepted by this product. true means every identifier valid in the seller's supported IANA TZDB; an array is the exact supported subset. Required when timezone_modes includes iana and forbidden otherwise." - ), - ] = None - ext: ext_1.ExtensionObject | None = None - - -class DaypartSupport(RootModel[Literal[True] | DaypartSupport1]): - root: Literal[True] | DaypartSupport1 - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - - - -class GeoProximity(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - radius: Supported | None = None - travel_time: Supported | None = None - geometry: Supported | None = None - transport_modes: Annotated[list[transport_mode.TransportMode] | None, Field(min_length=1)] = ( - None - ) - max_values_per_package: Annotated[ - SchemaInt | None, - Field(description='Maximum number of proximity entries accepted on one package.', ge=1), - ] = None - ext: ext_1.ExtensionObject | None = None - - -class Demographics(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - age: Supported - ext: ext_1.ExtensionObject | None = None - - -class TargetingOverlaySupport(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - geo_countries: CountrySupport | None = None - geo_countries_exclude: CountrySupport | None = None - geo_regions: Supported | geo_region_support.GeographicRegionSupport | None = None - geo_regions_exclude: Supported | geo_region_support.GeographicRegionSupport | None = None - geo_metros: MetroSupport | None = None - geo_metros_exclude: MetroSupport | None = None - geo_places: PlaceSupport | None = None - geo_places_exclude: PlaceSupport | None = None - geo_postal_areas: Supported | positive_postal_area_support.PositivePostalAreaSupport | None = ( - None - ) - geo_postal_areas_exclude: ( - Supported | positive_postal_area_support.PositivePostalAreaSupport | None - ) = None - geo_proximity: Supported | GeoProximity | None = None - daypart_targets: DaypartSupport | None = None - audience_include: Supported | None = None - audience_exclude: Supported | None = None - signal_targeting_groups: Supported | None = None - demographics: Supported | Demographics | None = None - frequency_cap: Supported | None = None - frequency_cap_support: Annotated[ - frequency_cap_constraints.FrequencyCapConstraints | None, - Field( - description='Independently positive constrained package-cap support. Mutually exclusive with legacy frequency_cap: a product declares one form or the other.' - ), - ] = None - property_list: Supported | None = None - property_list_exclude: Supported | None = None - collection_list: Supported | None = None - collection_list_exclude: Supported | None = None - placement_selection: Supported | PlacementSelection | None = None - age_restriction: Supported | None = None - device_platform: Supported | None = None - device_platform_exclude: Supported | None = None - device_type: Supported | None = None - device_type_exclude: Supported | None = None - browser: BrowserSupport | None = None - browser_exclude: BrowserSupport | None = None - store_catchments: Supported | None = None - language: Supported | None = None - keyword_targets: KeywordSupport | None = None - negative_keywords: KeywordSupport | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/targeting_unknown_age_eligibility_constraint.py b/src/adcp/types/generated_poc/core/targeting_unknown_age_eligibility_constraint.py deleted file mode 100644 index b28b93d9e..000000000 --- a/src/adcp/types/generated_poc/core/targeting_unknown_age_eligibility_constraint.py +++ /dev/null @@ -1,19 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/targeting_unknown_age_eligibility_constraint.json -# timestamp: 2026-09-14T14:16:02+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from pydantic import Field, RootModel - - -class TargetingUnknownAgeEligibilityConstraint(RootModel[Any]): - root: Annotated[ - Any, - Field( - description='Unknown-age delivery cannot satisfy a minimum-age eligibility policy. When demographic audience targeting and age_restriction are both present, include_unknown must be false.', - title='Targeting Unknown Age Eligibility Constraint', - ), - ] diff --git a/src/adcp/types/generated_poc/core/targeting_verified_age_basis_constraint.py b/src/adcp/types/generated_poc/core/targeting_verified_age_basis_constraint.py deleted file mode 100644 index 61a615b76..000000000 --- a/src/adcp/types/generated_poc/core/targeting_verified_age_basis_constraint.py +++ /dev/null @@ -1,19 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/targeting_verified_age_basis_constraint.json -# timestamp: 2026-09-14T14:16:02+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from pydantic import Field, RootModel - - -class TargetingVerifiedAgeBasisConstraint(RootModel[Any]): - root: Annotated[ - Any, - Field( - description='A legal verification requirement always narrows demographic targeting. When the buyer supplies accepted_bases and age_restriction requires verification, verified must be accepted; otherwise the constraints have an empty intersection and the request is invalid.', - title='Targeting Verified Age Basis Constraint', - ), - ] diff --git a/src/adcp/types/generated_poc/core/tasks_get_request.py b/src/adcp/types/generated_poc/core/tasks_get_request.py deleted file mode 100644 index 59654efce..000000000 --- a/src/adcp/types/generated_poc/core/tasks_get_request.py +++ /dev/null @@ -1,41 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/tasks_get_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import ConfigDict, Field, StrictBool - -from . import account_ref -from . import context as context_1 -from . import ext as ext_1 -from .version_envelope import AdcpVersionEnvelope - - -class TasksGetRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - task_id: Annotated[str, Field(description='Unique identifier of the task to retrieve')] - account: Annotated[ - account_ref.AccountReference | None, - Field( - description='Account scope for the task lookup. Sellers MUST return REFERENCE_NOT_FOUND for a task_id that exists only under a different account or principal. When omitted, the seller MAY use the credential-bound singleton account, but multi-account credentials SHOULD require an explicit account.' - ), - ] = None - include_history: Annotated[ - StrictBool | None, - Field( - description='Include full conversation history for this task (may increase response size)' - ), - ] = False - include_result: Annotated[ - StrictBool | None, - Field( - description="Include the task's canonical terminal result payload when one exists. Defaults to false for lightweight status-only polls. When true, sellers MUST include result for completed, failed, or rejected terminal tasks when that task produced a terminal artifact; canceled tasks may have no result. The legacy singular error field remains a convenience for failed tasks but does not replace the canonical terminal result." - ), - ] = False - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/tasks_get_response.py b/src/adcp/types/generated_poc/core/tasks_get_response.py deleted file mode 100644 index 99e8c8f85..000000000 --- a/src/adcp/types/generated_poc/core/tasks_get_response.py +++ /dev/null @@ -1,122 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/tasks_get_response.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat - -from ..enums import adcp_protocol, task_status -from ..enums import task_type as task_type_1 -from . import context as context_1 -from . import ext as ext_1 -from .protocol_envelope import ProtocolEnvelope -from .version_envelope import AdcpVersionEnvelope - - -class Progress(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - percentage: Annotated[ - StrictFloat | None, Field(description='Completion percentage (0-100)', ge=0.0, le=100.0) - ] = None - current_step: Annotated[ - str | None, Field(description='Current step or phase of the operation') - ] = None - total_steps: Annotated[ - SchemaInt | None, Field(description='Total number of steps in the operation', ge=1) - ] = None - step_number: Annotated[SchemaInt | None, Field(description='Current step number', ge=1)] = None - - -class Details(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - protocol: Annotated[ - adcp_protocol.AdcpProtocol | None, Field(description='AdCP protocol where error occurred') - ] = None - operation: Annotated[str | None, Field(description='Specific operation that failed')] = None - specific_context: Annotated[ - dict[str, Any] | None, Field(description='Domain-specific error context') - ] = None - - -class Error(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[str, Field(description='Error code for programmatic handling')] - message: Annotated[str, Field(description='Detailed error message')] - details: Annotated[Details | None, Field(description='Additional error context')] = None - - -class Type(StrEnum): - request = 'request' - response = 'response' - - -class HistoryItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - timestamp: Annotated[AwareDatetime, Field(description='When this exchange occurred (ISO 8601)')] - type: Annotated[ - Type, Field(description='Whether this was a request from client or response from server') - ] - data: Annotated[dict[str, Any], Field(description='The full request or response payload')] - - -class TasksGetResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - task_id: Annotated[str, Field(description='Unique identifier for this task')] - task_type: Annotated[task_type_1.TaskType, Field(description='Type of AdCP operation')] - protocol: Annotated[ - adcp_protocol.AdcpProtocol, Field(description='AdCP protocol this task belongs to') - ] - status: Annotated[task_status.TaskStatus, Field(description='Current task status')] - created_at: Annotated[ - AwareDatetime, Field(description='When the task was initially created (ISO 8601)') - ] - updated_at: Annotated[ - AwareDatetime, Field(description='When the task was last updated (ISO 8601)') - ] - completed_at: Annotated[ - AwareDatetime | None, - Field( - description='When the task completed (ISO 8601, only for completed/failed/canceled tasks)' - ), - ] = None - has_webhook: Annotated[ - StrictBool | None, Field(description='Whether this task has webhook configuration') - ] = None - progress: Annotated[ - Progress | None, Field(description='Progress information for long-running tasks') - ] = None - error: Annotated[ - Error | None, - Field( - description='Convenience summary for failed tasks. When include_result was true and the canonical terminal result is also present, this error MUST agree with the canonical fatal error in result. A legacy poll carrying only this singular summary proves failure status but not equivalence to a richer terminal webhook artifact.' - ), - ] = None - history: Annotated[ - list[HistoryItem] | None, - Field( - description='Complete conversation history for this task (only included if include_history was true in request)' - ), - ] = None - result: Annotated[ - dict[str, Any] | None, - Field( - description='Canonical task-specific terminal payload. Present when include_result was true and a completed, failed, or rejected task produced a terminal artifact; canceled tasks may omit it. For failed tasks, the singular error field is a convenience summary and MUST agree with the canonical fatal error represented here. Consumers and sellers MUST resolve and validate the exact schema through manifest.task_result_resolution: use terminal_schema_overrides[task_type] when present, otherwise tools[task_type].response_schema. The polling envelope keeps this field generic so tasks/get does not embed every task response schema.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/tasks_list_request.py b/src/adcp/types/generated_poc/core/tasks_list_request.py deleted file mode 100644 index 2cb1b573d..000000000 --- a/src/adcp/types/generated_poc/core/tasks_list_request.py +++ /dev/null @@ -1,117 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/tasks_list_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field, StrictBool - -from ..enums import adcp_protocol, sort_direction, task_status -from ..enums import task_type as task_type_1 -from . import account_ref -from . import context as context_1 -from . import ext as ext_1 -from . import pagination_request -from .version_envelope import AdcpVersionEnvelope - - -class Filters(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - protocol: Annotated[ - adcp_protocol.AdcpProtocol | None, Field(description='Filter by single AdCP protocol') - ] = None - protocols: Annotated[ - list[adcp_protocol.AdcpProtocol] | None, - Field(description='Filter by multiple AdCP protocols', min_length=1), - ] = None - status: Annotated[ - task_status.TaskStatus | None, Field(description='Filter by single task status') - ] = None - statuses: Annotated[ - list[task_status.TaskStatus] | None, - Field(description='Filter by multiple task statuses', min_length=1), - ] = None - task_type: Annotated[ - task_type_1.TaskType | None, Field(description='Filter by single task type') - ] = None - task_types: Annotated[ - list[task_type_1.TaskType] | None, - Field(description='Filter by multiple task types', min_length=1), - ] = None - created_after: Annotated[ - AwareDatetime | None, Field(description='Filter tasks created after this date (ISO 8601)') - ] = None - created_before: Annotated[ - AwareDatetime | None, Field(description='Filter tasks created before this date (ISO 8601)') - ] = None - updated_after: Annotated[ - AwareDatetime | None, - Field(description='Filter tasks last updated after this date (ISO 8601)'), - ] = None - updated_before: Annotated[ - AwareDatetime | None, - Field(description='Filter tasks last updated before this date (ISO 8601)'), - ] = None - task_ids: Annotated[ - list[str] | None, - Field(description='Filter by specific task IDs', max_length=100, min_length=1), - ] = None - context_contains: Annotated[ - str | None, - Field( - description='Filter tasks where context contains this text (searches media_buy_id, signal_id, etc.)' - ), - ] = None - has_webhook: Annotated[ - StrictBool | None, - Field(description='Filter tasks that have webhook configuration when true'), - ] = None - - -class Field1(StrEnum): - created_at = 'created_at' - updated_at = 'updated_at' - status = 'status' - task_type = 'task_type' - protocol = 'protocol' - - -class Sort(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - field: Annotated[Field1 | None, Field(description='Field to sort by')] = Field1.created_at - direction: Annotated[ - sort_direction.SortDirection | None, Field(description='Sort direction') - ] = sort_direction.SortDirection.desc - - -class TasksListRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - account: Annotated[ - account_ref.AccountReference | None, - Field( - description="Account scope for task reconciliation. Sellers MUST only return tasks created for the caller's authenticated account + principal pair. When omitted, the seller MAY use the credential-bound singleton account, but multi-account credentials SHOULD require an explicit account." - ), - ] = None - filters: Annotated[Filters | None, Field(description='Filter criteria for querying tasks')] = ( - None - ) - sort: Annotated[Sort | None, Field(description='Sorting parameters')] = None - pagination: pagination_request.PaginationRequest | None = None - include_history: Annotated[ - StrictBool | None, - Field( - description='Include full conversation history for each task (may significantly increase response size)' - ), - ] = False - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/tasks_list_response.py b/src/adcp/types/generated_poc/core/tasks_list_response.py deleted file mode 100644 index 0f5b7e550..000000000 --- a/src/adcp/types/generated_poc/core/tasks_list_response.py +++ /dev/null @@ -1,117 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/tasks_list_response.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field, StrictBool - -from ..enums import task_status -from ..enums import task_type as task_type_1 -from . import context as context_1 -from . import ext as ext_1 -from . import pagination_response -from .protocol_envelope import ProtocolEnvelope -from .version_envelope import AdcpVersionEnvelope - - -class DomainBreakdown(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_buy: Annotated[ - SchemaInt | None, - Field(alias='media-buy', description='Number of media-buy tasks in results', ge=0), - ] = None - signals: Annotated[ - SchemaInt | None, Field(description='Number of signals tasks in results', ge=0) - ] = None - creative: Annotated[ - SchemaInt | None, Field(description='Number of creative tasks in results', ge=0) - ] = None - - -class Direction(StrEnum): - asc = 'asc' - desc = 'desc' - - -class SortApplied(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - field: str - direction: Direction - - -class QuerySummary(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - total_matching: Annotated[ - SchemaInt | None, - Field(description='Total number of tasks matching filters (across all pages)', ge=0), - ] = None - returned: Annotated[ - SchemaInt | None, Field(description='Number of tasks returned in this response', ge=0) - ] = None - domain_breakdown: Annotated[ - DomainBreakdown | None, Field(description='Count of tasks by domain') - ] = None - status_breakdown: Annotated[ - dict[str, SchemaInt] | None, Field(description='Count of tasks by status') - ] = None - filters_applied: Annotated[ - list[str] | None, Field(description='List of filters that were applied to the query') - ] = None - sort_applied: Annotated[ - SortApplied | None, Field(description='Sort order that was applied') - ] = None - - -class Domain(StrEnum): - media_buy = 'media-buy' - signals = 'signals' - creative = 'creative' - - -class Task(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - task_id: Annotated[str, Field(description='Unique identifier for this task')] - task_type: Annotated[task_type_1.TaskType, Field(description='Type of AdCP operation')] - domain: Annotated[Domain, Field(description='AdCP domain this task belongs to')] - status: Annotated[task_status.TaskStatus, Field(description='Current task status')] - created_at: Annotated[ - AwareDatetime, Field(description='When the task was initially created (ISO 8601)') - ] - updated_at: Annotated[ - AwareDatetime, Field(description='When the task was last updated (ISO 8601)') - ] - completed_at: Annotated[ - AwareDatetime | None, - Field( - description='When the task completed (ISO 8601, only for completed/failed/canceled tasks)' - ), - ] = None - has_webhook: Annotated[ - StrictBool | None, Field(description='Whether this task has webhook configuration') - ] = None - - -class TasksListResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - query_summary: Annotated[ - QuerySummary, Field(description='Summary of the query that was executed') - ] - tasks: Annotated[list[Task], Field(description='Array of tasks matching the query criteria')] - pagination: pagination_response.PaginationResponse - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/tracker_execution_contract.py b/src/adcp/types/generated_poc/core/tracker_execution_contract.py deleted file mode 100644 index d779cdaaf..000000000 --- a/src/adcp/types/generated_poc/core/tracker_execution_contract.py +++ /dev/null @@ -1,30 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/tracker_execution_contract.json -# timestamp: 2026-10-03T15:09:44+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictBool - -from . import tracker_execution_selector - - -class TrackerExecutionContract(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - complete: Annotated[ - StrictBool, - Field( - description='Whether honored is the complete set. complete:true with an empty honored array explicitly supports no buyer-supplied trackers.' - ), - ] - honored: Annotated[ - list[tracker_execution_selector.TrackerExecutionSelector], - Field( - description='Exact tracker/event selectors the seller accepts and initiates once per manifest instance for each logical event occurrence. selector_id and structural selector identity are each unique within this array.' - ), - ] diff --git a/src/adcp/types/generated_poc/core/tracker_execution_selector.py b/src/adcp/types/generated_poc/core/tracker_execution_selector.py deleted file mode 100644 index c3b58ceed..000000000 --- a/src/adcp/types/generated_poc/core/tracker_execution_selector.py +++ /dev/null @@ -1,109 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/tracker_execution_selector.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..enums import pixel_tracking_event, tracker_execution_actor, tracker_firing_path -from . import daast_tracker_constraints, vast_tracker_constraints -from .daast_tracker_constraints import DaastTrackerConstraints -from .vast_tracker_constraints import VastTrackerConstraints - - -class TrackerExecutionSelector1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - selector_id: Annotated[ - str, - Field( - description='Stable identifier unique within the materialized execution contract.', - min_length=1, - ), - ] - asset_type: Literal['pixel_tracker'] = 'pixel_tracker' - event: pixel_tracking_event.PixelTrackingEvent - method: Annotated[ - Literal['img'], - Field( - description='AdCP 3.2 execution contracts cover image-pixel initiation only; JavaScript response evaluation is outside this contract version.' - ), - ] = 'img' - custom_event_name: Annotated[ - str | None, Field(description='Required only when event is custom.', min_length=1) - ] = None - execution_actor: tracker_execution_actor.TrackerExecutionActor - firing_paths: Annotated[ - list[tracker_firing_path.TrackerFiringPath], - Field( - description='Complete set of permitted initiation environments. Exactly one path is selected for each logical event occurrence.', - min_length=1, - ), - ] - - -class TrackerExecutionSelector3(DaastTrackerConstraints): - model_config = ConfigDict( - extra='forbid', - ) - selector_id: Annotated[ - str, - Field( - description='Stable identifier unique within the materialized execution contract.', - min_length=1, - ), - ] - asset_type: Literal['daast_tracker'] = 'daast_tracker' - daast_versions: daast_tracker_constraints.DaastVersions - daast_event: daast_tracker_constraints.DaastEvent - target: daast_tracker_constraints.DaastTarget - offset: daast_tracker_constraints.DaastOffset | None = None - execution_actor: tracker_execution_actor.TrackerExecutionActor - firing_paths: Annotated[ - list[tracker_firing_path.TrackerFiringPath], - Field( - description='Complete set of permitted initiation environments. Exactly one path is selected for each logical event occurrence.', - min_length=1, - ), - ] - - -class TrackerExecutionSelector2(VastTrackerConstraints): - model_config = ConfigDict( - extra='forbid', - ) - selector_id: Annotated[ - str, - Field( - description='Stable identifier unique within the materialized execution contract.', - min_length=1, - ), - ] - asset_type: Literal['vast_tracker'] = 'vast_tracker' - vast_versions: vast_tracker_constraints.VastVersions - vast_event: vast_tracker_constraints.VastEvent - target: vast_tracker_constraints.VastTarget - offset: vast_tracker_constraints.VastOffset | None = None - execution_actor: tracker_execution_actor.TrackerExecutionActor - firing_paths: Annotated[ - list[tracker_firing_path.TrackerFiringPath], - Field( - description='Complete set of permitted initiation environments. Exactly one path is selected for each logical event occurrence.', - min_length=1, - ), - ] - - -TrackerExecutionSelector = Annotated[ - TrackerExecutionSelector1 | TrackerExecutionSelector2 | TrackerExecutionSelector3, - Field( - description='One exact first-class manifest tracker selector in a seller production execution contract. AdCP 3.2 supports pixel_tracker, vast_tracker, and daast_tracker only; URL-slot selectors are not part of this union.', - discriminator='asset_type', - title='Tracker Execution Selector', - ), -] diff --git a/src/adcp/types/generated_poc/core/transformer.py b/src/adcp/types/generated_poc/core/transformer.py deleted file mode 100644 index f19273a11..000000000 --- a/src/adcp/types/generated_poc/core/transformer.py +++ /dev/null @@ -1,1874 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/transformer.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarStr -from pydantic import AnyUrl, ConfigDict, Field, StrictBool, model_validator - -from ..enums import channels -from ..formats.canonical import ( - agent_placement, - audio_daast, - audio_hosted, - audio_vast, - coordinated_placements, - display_tag, - html5, - image, - image_carousel, - native_in_feed, - responsive_creative, - seller_rendered_stateful_display, - sponsored_placement, - video_hosted, - video_vast, -) -from . import creative_locale_policy, format_id, macro_resolution_capability, platform_extension_ref -from . import tracker_execution_contract as tracker_execution_contract_1 -from . import transformer_param, vendor_pricing_option - - -class BrandAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - url: Annotated[AnyUrl, Field(description='MCP endpoint URL of the brand agent.')] - id: Annotated[str, Field(description='Agent identifier.')] - - -class VoiceSynthesisRefItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - brand_agent: Annotated[ - BrandAgent, - Field(description='Brand agent that exposed the referenced voice_synthesis entry.'), - ] - voice_id: Annotated[str, Field(description='voice_synthesis.voice_id from the brand agent.')] - rights_id: Annotated[ - str | None, - Field( - description='Optional rights offering or buyer-specific grant identifier associated with this provisioned voice. Brand-side voice_synthesis uses rights_offering_id for the configuration-time offering anchor. This transformer field may carry that offering ID or a grant ID after provisioning; it remains provenance metadata only, not a build_creative rights token.' - ), - ] = None - - -class SellerPreference(StrEnum): - preferred = 'preferred' - accepted = 'accepted' - discouraged = 'discouraged' - - -class InputFormat17(AdCPBaseModel): - pass - - -class OutputCapabilityId(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[a-zA-Z0-9_-]+$'} - - -class VariantDimension(StrEnum): - voice = 'voice' - theme = 'theme' - best_of_n = 'best_of_n' - transformer_config = 'transformer_config' - custom = 'custom' - - -class Multiplicity(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supports_catalog_fanout: Annotated[ - StrictBool | None, Field(description='Whether this transformer accepts max_creatives.') - ] = False - max_creatives_limit: Annotated[ - SchemaInt | None, - Field(description='Per-transformer ceiling on max_creatives (≤ the agent ceiling).', ge=1), - ] = None - supports_variants: Annotated[ - StrictBool | None, - Field(description='Whether this transformer accepts max_variants > 1 / variant_axis.'), - ] = False - max_variants_limit: Annotated[ - SchemaInt | None, - Field(description='Per-transformer ceiling on max_variants (≤ the agent ceiling).', ge=1), - ] = None - variant_dimensions: Annotated[ - list[VariantDimension] | None, - Field(description="Variant axis dimensions this transformer supports (⊆ the agent's)."), - ] = None - - -class InputFormat1(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['image'] = 'image' - params: image.CanonicalFormatImage - - -class InputFormat2(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['html5'] = 'html5' - params: html5.CanonicalFormatHtml5Banner - - -class InputFormat3(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['display_tag'] = 'display_tag' - params: display_tag.CanonicalFormatDisplayTag - - -class InputFormat4(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['image_carousel'] = 'image_carousel' - params: image_carousel.CanonicalFormatImageCarousel - - -class InputFormat5(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['video_hosted'] = 'video_hosted' - params: video_hosted.CanonicalFormatHostedVideo - - -class InputFormat6(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['video_vast'] = 'video_vast' - params: video_vast.CanonicalFormatVastVideo - - -class InputFormat7(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['audio_hosted'] = 'audio_hosted' - params: audio_hosted.CanonicalFormatHostedAudio - - -class InputFormat8(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['audio_vast'] = 'audio_vast' - params: audio_vast.CanonicalFormatVastAudio - - -class InputFormat9(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['audio_daast'] = 'audio_daast' - params: audio_daast.CanonicalFormatDaastAudio - - -class InputFormat10(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['sponsored_placement'] = 'sponsored_placement' - params: sponsored_placement.CanonicalFormatSponsoredPlacementRetailMediaCatalogDriven - - -class InputFormat11(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['native_in_feed'] = 'native_in_feed' - params: native_in_feed.CanonicalFormatNativeInFeed - - -class InputFormat12(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['responsive_creative'] = 'responsive_creative' - params: responsive_creative.CanonicalFormatResponsiveCreative - - -class InputFormat13(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['agent_placement'] = 'agent_placement' - params: agent_placement.CanonicalFormatAgentPlacementAiSurfaceSponsoredPlacement - - -class InputFormat14(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['seller_rendered_stateful_display'] = 'seller_rendered_stateful_display' - params: seller_rendered_stateful_display.CanonicalFormatSellerRenderedStatefulDisplay - - -class InputFormat15(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['coordinated_placements'] = 'coordinated_placements' - params: coordinated_placements.CanonicalFormatCoordinatedPlacements - - -class InputFormat16(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this declaration within its namespace. REQUIRED when a product contains multiple declarations with the same format_kind and SHOULD be set on every entry. Publisher-backed options pair it with publisher_domain; product-local options omit publisher_domain. When a single declaration has a unique format_kind and no ID, buyers author canonically with format_kind plus params; they MUST NOT fall back to deprecated format_ids merely because this optional ID is absent. 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 - tracker_execution_contract: Annotated[ - tracker_execution_contract_1.TrackerExecutionContract | None, - Field( - description='Seller- or publisher-authored commitment describing which first-class manifest trackers the selected format option accepts and initiates in production. The seller-returned Product declaration is binding; publisher and placement declarations are upstream inputs that the seller resolves into that effective contract. Presence requires a stable format_option_id. Creative-agent capability projections, transformer inputs, and deprecated canonical_parameters MUST reject this seller-authority field rather than copying it.' - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Binding format-option processing capabilities for exact macro dialect identities, semantics, operations, actors, contexts, and encodings. Absence means undeclared, not supported on the opt-in declared-token path. Seller-wide capabilities are only a ceiling. This field does not claim that a buyer tracker asset is honored or fired.', - min_length=1, - ), - ] = None - technical_requirements_complete: Annotated[ - StrictBool | None, - Field( - description='Completeness assertion for technical creative acceptance constraints in this declaration. When true, the declaring party asserts that every technical constraint within its authority is expressed by this declaration (including fetched custom-format and platform-extension schemas), and every omitted technical field means no constraint at that layer. A creative that satisfies the complete effective technical contract MUST NOT later be rejected for an undisclosed technical constraint. When false or absent, omitted technical constraints are undeclared: consumers MUST NOT interpret omission as support, no constraint, or a prose/default value. The effective product/placement contract is complete only when every applicable product, publisher, and placement declaration asserts true. This assertion is limited to technical acceptance; it does not waive policy, legal, security, malware, transport/fetch, corrupted-content, or materially misdeclared-asset checks. Creative size fields ending in `_kb` use exactly 1,000 bytes per KB and fields ending in `_mb` use exactly 1,000,000 bytes per MB.' - ), - ] = 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 - sample_render_url: Annotated[ - AnyUrl | None, - Field( - description='Optional public HTTPS page where a human can inspect a sample render of this declaration using assets chosen by the party publishing the enclosing declaration. Consumers MUST identify that source correctly: publisher or community mirror for `adagents.json` `formats[]`, seller for product or inline-placement declarations, and creative agent for `creative.supported_formats`. Informational only: this is not a renderer endpoint, buyer-asset preview, validation result, creative approval, proof of publisher acceptance, or guarantee of live delivery. Declaring parties SHOULD keep the URL stable while the declaration is active.' - ), - ] = 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 - locale_policy: Annotated[ - creative_locale_policy.CreativeLocalePolicy | None, - Field( - description='Optional seller-enforced creative-locale constraint for this format option. This is product/placement eligibility, not a new format kind or synthetic locale-specific format ID. Because legacy format_ids cannot preserve this constraint, declarations carrying locale_policy MUST set canonical_formats_only to true and MUST NOT carry v1_format_ref.' - ), - ] = None - canonical_formats_only: Annotated[ - StrictBool | 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[ - StrictBool | None, - Field( - description="When true, this seller's specific canonical declaration may not work as declared even if the underlying canonical is stable. Buyers SHOULD preflight it with validate_input or in a sandbox before routing production budget and SHOULD filter it from default views unless the caller opts in. Experimental status never makes the deprecated named-format path preferable. This field is independent of the canonical's own experimental flag and replaces the earlier runtime_status enum." - ), - ] = 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) (`branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values are 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 may promote it to a first-class canonical. Retired vocabulary entries `multi_state_display` and `multi_placement_takeover` remain temporarily recognizable for migration; new declarations MUST use their promoted canonicals and validators SHOULD emit `FORMAT_SHAPE_PROMOTED`. `roadblock` remains an inventory/exclusivity classifier and is not a promoted creative format.' - ), - ] = 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/` + `id: ` (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/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — 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.' - ), - ] = None - format_kind: Literal['custom'] = 'custom' - params: Annotated[ - dict[str, Any], - Field( - description="Custom shape's params. Validated against the schema fetched from `format_schema.uri` at the cached `format_schema.digest`." - ), - ] - - -class InputFormat18(InputFormat1, InputFormat17): - pass - - -class InputFormat19(InputFormat2, InputFormat17): - pass - - -class InputFormat20(InputFormat3, InputFormat17): - pass - - -class InputFormat21(InputFormat4, InputFormat17): - pass - - -class InputFormat22(InputFormat5, InputFormat17): - pass - - -class InputFormat23(InputFormat6, InputFormat17): - pass - - -class InputFormat24(InputFormat7, InputFormat17): - pass - - -class InputFormat25(InputFormat8, InputFormat17): - pass - - -class InputFormat26(InputFormat9, InputFormat17): - pass - - -class InputFormat27(InputFormat10, InputFormat17): - pass - - -class InputFormat28(InputFormat11, InputFormat17): - pass - - -class InputFormat29(InputFormat12, InputFormat17): - pass - - -class InputFormat30(InputFormat13, InputFormat17): - pass - - -class InputFormat31(InputFormat14, InputFormat17): - pass - - -class InputFormat32(InputFormat15, InputFormat17): - pass - - -class InputFormat33(InputFormat16, InputFormat17): - pass - - -InputFormat = ( - InputFormat18 - | InputFormat19 - | InputFormat20 - | InputFormat21 - | InputFormat22 - | InputFormat23 - | InputFormat24 - | InputFormat25 - | InputFormat26 - | InputFormat27 - | InputFormat28 - | InputFormat29 - | InputFormat30 - | InputFormat31 - | InputFormat32 - | InputFormat33 -) - - -class Transformer(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - transformer_id: Annotated[ - str, - Field( - description='Stable identifier for this transformer within the agent. Pass to build_creative `transformer_id` to select it.' - ), - ] - name: Annotated[ - str, - Field( - description="Human-readable transformer name (e.g. 'Voiceover — Isaac', 'Veo 3 text-to-video')." - ), - ] - description: Annotated[ - str | None, - Field(description='Plain-text explanation of what this transformer produces and how.'), - ] = None - metadata: Annotated[ - dict[str, Any] | None, - Field( - description='Transformer-specific attributes a buyer can filter or display (e.g. provider, modality, language).' - ), - ] = None - voice_synthesis_ref: Annotated[ - list[VoiceSynthesisRefItem] | None, - Field( - description='Optional discovery/audit anchors for voice transformers provisioned from brand-agent voice_synthesis entries. Informational only: these references help buyers match a discovered transformer to brand/rights-agent provenance, but they do not assert build-time authorization or require the creative agent to perform rights-token validation.', - min_length=1, - ), - ] = None - input_format_ids: Annotated[ - list[format_id.FormatReferenceStructuredObject] | None, - Field( - deprecated=True, - description='**DEPRECATED in 3.2.** Legacy named formats this transformer accepts as input. Use input_formats with canonical declarations.', - ), - ] = None - output_format_ids: Annotated[ - list[format_id.FormatReferenceStructuredObject] | None, - Field( - deprecated=True, - description='**DEPRECATED in 3.2.** Legacy named formats this transformer can produce. Use output_capability_ids.', - min_length=1, - ), - ] = None - input_formats: Annotated[ - list[InputFormat] | None, - Field( - description='Canonical format declarations this transformer accepts as input. Omitted means it builds from a brief or raw assets rather than transforming an existing creative. Compatibility uses canonical constraint satisfaction, not identifier equality. Transformer self-description has no seller production authority, so tracker_execution_contract and tracker_execution_contract_digest are forbidden.', - min_length=1, - ), - ] = None - output_capability_ids: Annotated[ - list[OutputCapabilityId] | None, - Field( - description="Canonical output capabilities this transformer can produce. Every value MUST match this agent's get_adcp_capabilities `creative.supported_formats[].capability_id`. A build_creative request's target_capability_id(s) MUST be a subset of this array.", - min_length=1, - ), - ] = None - params: Annotated[ - list[transformer_param.TransformerParam] | None, - Field( - description="Configuration knobs this transformer exposes. The buyer supplies values in build_creative `config`, keyed by each param's `field`. Enumerable param values (e.g. account-specific voices) are returned only when requested via list_transformers `expand_params`." - ), - ] = None - pricing_options: Annotated[ - list[vendor_pricing_option.VendorPricingOption] | None, - Field( - description='Per-account rate-card options for using this transformer. Present when the list_transformers request set include_pricing=true with an account. The applied option is echoed back per-leaf on the build_creative response and reconciled via report_usage.', - min_length=1, - ), - ] = None - multiplicity: Annotated[ - Multiplicity | None, - Field( - description="Optional per-transformer fan-out limits that NARROW the agent-level get_adcp_capabilities `creative.multiplicity`. Same shape as the agent-level object. When present, this transformer's authoritative; its ceilings (max_creatives_limit / max_variants_limit) MUST NOT exceed the agent ceilings, and its variant_dimensions MUST be a subset of the agent's. Omit to inherit the agent-level capability unchanged." - ), - ] = None - - @model_validator(mode='after') - def _require_output_format_declaration(self) -> Transformer: - """At least one output declaration is required by the schema.""" - # Read Pydantic's stored values directly so validation itself does not - # emit a deprecation warning for the still-supported legacy field. - if ( - self.__dict__.get('output_capability_ids') is None - and self.__dict__.get('output_format_ids') is None - ): - raise ValueError( - 'one of output_capability_ids or deprecated output_format_ids is required' - ) - return self - - @model_validator(mode='after') - def _require_schema_required_group(self) -> Transformer: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('output_capability_ids',), ('output_format_ids',),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'Transformer requires at least one of these field groups: output_capability_ids | output_format_ids' - ) diff --git a/src/adcp/types/generated_poc/core/transformer_param.py b/src/adcp/types/generated_poc/core/transformer_param.py deleted file mode 100644 index 2651bd37c..000000000 --- a/src/adcp/types/generated_poc/core/transformer_param.py +++ /dev/null @@ -1,111 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/transformer_param.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictBool, StrictFloat - - -class Type(StrEnum): - string = 'string' - number = 'number' - integer = 'integer' - boolean = 'boolean' - - -class ValueSource(StrEnum): - inline = 'inline' - range = 'range' - enumerable = 'enumerable' - free_text = 'free_text' - - -class Option(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - value: Annotated[ - Any, Field(description='The value the buyer passes in `config` for this field.') - ] - label: Annotated[str | None, Field(description='Human-readable label for this option.')] = None - metadata: Annotated[ - dict[str, Any] | None, - Field( - description='Option-specific attributes the buyer can filter or display (e.g. for a voice: language, gender, provider, custom).' - ), - ] = None - - -class TransformerParam(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - field: Annotated[ - str, - Field( - description='The config key. Buyers set the value under this exact key in build_creative `config`.' - ), - ] - type: Annotated[ - Type, Field(description='JSON type of the value the buyer supplies for this field.') - ] - value_source: Annotated[ - ValueSource, - Field( - description='Where the legal values come from. `inline` — a small closed set listed in `allowed_values` (e.g. mastering_preset). `range` — a numeric interval bounded by `minimum`/`maximum` (e.g. speaking_rate). `enumerable` — an account-scoped, dynamic set (e.g. voices, including custom/cloned ones) resolved per-credential; values appear in `options[]` only when expanded. `free_text` — an open buyer-authored string with no closed/enumerable set (e.g. a negative_prompt or style note for a generative agent); `type` MUST be `string` and `allowed_values`/`minimum`/`maximum`/`options`/`options_cursor` MUST be absent. NOTE: a transformer-param MUST NOT be a generation-count knob (sample_count/n/num_images/count) — output count is owned by `max_variants`/`max_creatives`, never config.' - ), - ] - max_length: Annotated[ - SchemaInt | None, - Field( - description='Optional maximum character length for a `free_text` param. Omit for no declared limit.', - ge=1, - ), - ] = None - allowed_values: Annotated[ - list[Any] | None, - Field( - description='The closed set of legal values. Present when `value_source` is `inline`.', - min_length=1, - ), - ] = None - minimum: Annotated[ - StrictFloat | None, - Field(description='Inclusive lower bound. Present when `value_source` is `range`.'), - ] = None - maximum: Annotated[ - StrictFloat | None, - Field(description='Inclusive upper bound. Present when `value_source` is `range`.'), - ] = None - options: Annotated[ - list[Option] | None, - Field( - description="Account-scoped legal values for an `enumerable` param. Populated ONLY when this param's `field` was named in the list_transformers `expand_params` request — otherwise omitted (the buyer enumerates on demand). Brief-filtered and paginated; use `options_cursor` for the next page." - ), - ] = None - options_cursor: Annotated[ - str | None, - Field( - description="Opaque pagination cursor for this param's `options[]`. Present when more option values are available than were returned. Pass back to list_transformers (scoped to this transformer + field) to fetch the next page." - ), - ] = None - default: Annotated[ - Any | None, - Field( - description='The value applied when the buyer omits this field from `config`. Type matches `type`.' - ), - ] = None - required: Annotated[ - StrictBool | None, - Field( - description='Whether the buyer MUST supply this field in `config`. When false and no `default` is declared, the agent chooses.' - ), - ] = False - description: Annotated[ - str | None, Field(description='Human-readable explanation of what this knob does.') - ] = None diff --git a/src/adcp/types/generated_poc/core/truncation_sentinel.py b/src/adcp/types/generated_poc/core/truncation_sentinel.py deleted file mode 100644 index e83010fec..000000000 --- a/src/adcp/types/generated_poc/core/truncation_sentinel.py +++ /dev/null @@ -1,48 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/truncation_sentinel.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field - - -class FieldTruncation(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - original_size_bytes: Annotated[ - SchemaInt, - Field( - description='Size of the untruncated original content in bytes, measured before any encoding overhead. Lets the receiver decide whether to fetch the full value via a payload-bearing surface (when one exists) or proceed with the preview.', - ge=0, - ), - ] - preview: Annotated[ - str | None, - Field( - description='Optional bounded human-readable excerpt of the original content. Sellers SHOULD keep previews under 8 KiB and SHOULD truncate on a UTF-8 codepoint boundary. Absent when the seller cannot safely surface even a preview (e.g., the original content is binary and not text-decodable, or seller policy forbids any payload surfacing). Receivers MUST NOT parse `preview` as a complete representation of the original value.' - ), - ] = None - preview_format: Annotated[ - str | None, - Field( - description='Hint for how to render `preview`. Common values: `text` (plain UTF-8), `json` (JSON fragment), `base64` (base64-encoded binary), `xml`, `html`. The list is open — adopters MAY use other MIME-derived shorthands. Receivers SHOULD treat unknown values as `text` and SHOULD NOT reject the sentinel when the value is unfamiliar.' - ), - ] = None - - -class TruncationSentinel(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - field_truncation: Annotated[ - FieldTruncation, - Field( - alias='_truncation', - description='Truncation envelope. The leading underscore is a deliberate signal that this property is a control marker and not a payload field — the natural value being surfaced will not have a `_truncation` key. `additionalProperties: true` so future revisions of this contract can add classification fields without a forward-compat break.', - ), - ] diff --git a/src/adcp/types/generated_poc/core/user_match.py b/src/adcp/types/generated_poc/core/user_match.py deleted file mode 100644 index 7fc0a30b7..000000000 --- a/src/adcp/types/generated_poc/core/user_match.py +++ /dev/null @@ -1,72 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/user_match.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, model_validator - -from ..enums import uid_type -from . import ext as ext_1 - - -class Uid(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[uid_type.UidType, Field(description='Universal ID type')] - value: Annotated[str, Field(description='Universal ID value')] - - -class UserMatch(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - uids: Annotated[ - list[Uid] | None, Field(description='Universal ID values for user matching', min_length=1) - ] = None - hashed_email: Annotated[ - str | None, - Field( - description='SHA-256 hash of lowercase, trimmed email address. Buyer must normalize before hashing: lowercase, trim whitespace. Pseudonymous PII, not anonymous — the email namespace is small enough that an unsalted SHA-256 is recoverable via precomputed dictionaries. Treat as PII for retention, consent, and access-control purposes. See docs/reference/privacy-considerations#unsalted-hashed-identifiers-are-pseudonymous-not-anonymous.', - pattern='^[a-f0-9]{64}$', - ), - ] = None - hashed_phone: Annotated[ - str | None, - Field( - description='SHA-256 hash of E.164-formatted phone number (e.g. +12065551234). Buyer must normalize to E.164 before hashing. Pseudonymous PII, not anonymous — the E.164 namespace is small enough that an unsalted SHA-256 is recoverable via precomputed dictionaries. Treat as PII for retention, consent, and access-control purposes. See docs/reference/privacy-considerations#unsalted-hashed-identifiers-are-pseudonymous-not-anonymous.', - pattern='^[a-f0-9]{64}$', - ), - ] = None - click_id: Annotated[ - str | None, - Field(description='Platform click identifier (fbclid, gclid, ttclid, ScCid, etc.)'), - ] = None - click_id_type: Annotated[ - str | None, - Field(description='Type of click identifier (e.g. fbclid, gclid, ttclid, msclkid, ScCid)'), - ] = None - client_ip: Annotated[ - str | None, Field(description='Client IP address for probabilistic matching') - ] = None - client_user_agent: Annotated[ - str | None, Field(description='Client user agent string for probabilistic matching') - ] = None - ext: ext_1.ExtensionObject | None = None - - @model_validator(mode='after') - def _require_schema_required_group(self) -> UserMatch: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('uids',), ('hashed_email',), ('hashed_phone',), ('click_id',), ('client_ip', 'client_user_agent'),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'UserMatch requires at least one of these field groups: uids | hashed_email | hashed_phone | click_id | client_ip+client_user_agent' - ) diff --git a/src/adcp/types/generated_poc/core/vast_media_file_requirements.py b/src/adcp/types/generated_poc/core/vast_media_file_requirements.py deleted file mode 100644 index 25dd654bb..000000000 --- a/src/adcp/types/generated_poc/core/vast_media_file_requirements.py +++ /dev/null @@ -1,97 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/vast_media_file_requirements.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field - -from ..enums import vast_media_delivery_method - - -class MimeType(ScalarStr): - __slots__ = () - _constraints = { - 'pattern': '^[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]*/[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]*$', - } - - -class Container(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[a-z0-9][a-z0-9._+-]*$'} - - -class Codec(ScalarStr): - __slots__ = () - _constraints = {'min_length': 1} - - -class VastMediafileRequirements(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - delivery_methods: Annotated[ - list[vast_media_delivery_method.VastMediaDeliveryMethod] | None, - Field( - description='Accepted values of the required `MediaFile@delivery` attribute. `progressive` identifies a directly downloadable media file; `streaming` identifies a streaming media resource.', - min_length=1, - ), - ] = None - mime_types: Annotated[ - list[MimeType] | None, - Field( - description='Accepted MIME types from `MediaFile@type`, compared case-insensitively after trimming optional whitespace. Parameters are not accepted in this field. Examples: `video/mp4`, `video/webm`, `audio/mpeg`, `audio/aac`.', - min_length=1, - ), - ] = None - containers: Annotated[ - list[Container] | None, - Field( - description='Accepted normalized container identifiers, such as `mp4`, `webm`, or `mpeg_ts`. A receiver MUST determine the container from authoritative metadata or safe byte inspection and MUST NOT guess it only from the media URI suffix.', - min_length=1, - ), - ] = None - codecs: Annotated[ - list[Codec] | None, - Field( - description='Accepted codec identifiers for `MediaFile@codec`, using the codec syntax referenced by the applicable VAST version. A missing codec attribute does not satisfy a declared codec constraint unless safe inspection establishes the codec.', - min_length=1, - ), - ] = None - min_width: Annotated[ - SchemaInt | None, Field(description='Minimum accepted `MediaFile@width` in pixels.', ge=1) - ] = None - max_width: Annotated[ - SchemaInt | None, Field(description='Maximum accepted `MediaFile@width` in pixels.', ge=1) - ] = None - min_height: Annotated[ - SchemaInt | None, Field(description='Minimum accepted `MediaFile@height` in pixels.', ge=1) - ] = None - max_height: Annotated[ - SchemaInt | None, Field(description='Maximum accepted `MediaFile@height` in pixels.', ge=1) - ] = None - min_bitrate_kbps: Annotated[ - SchemaInt | None, - Field( - description='Minimum accepted MediaFile bitrate in kilobits per second. A fixed `MediaFile@bitrate` must be at least this value. For adaptive/streaming media, the declared `MediaFile@minBitrate` must be at least this value. Safe byte inspection may establish the value when metadata is absent.', - ge=1, - ), - ] = None - max_bitrate_kbps: Annotated[ - SchemaInt | None, - Field( - description='Maximum accepted MediaFile bitrate in kilobits per second. A fixed `MediaFile@bitrate` must not exceed this value. For adaptive/streaming media, the declared `MediaFile@maxBitrate` must not exceed this value. Safe byte inspection may establish the value when metadata is absent.', - ge=1, - ), - ] = None - max_file_size_bytes: Annotated[ - SchemaInt | None, - Field( - description='Maximum accepted MediaFile size in exact bytes. `MediaFile@fileSize`, when present, is expressed in bytes; a receiver MAY verify it against safely fetched media bytes and MUST use the verified byte count if the values disagree.', - ge=1, - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/vast_tracker_constraints.py b/src/adcp/types/generated_poc/core/vast_tracker_constraints.py deleted file mode 100644 index 94f3664d0..000000000 --- a/src/adcp/types/generated_poc/core/vast_tracker_constraints.py +++ /dev/null @@ -1,66 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/vast_tracker_constraints.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import Field, RootModel - -from ..enums import vast_version - - -class VastEvent(StrEnum): - creativeView = 'creativeView' - loaded = 'loaded' - start = 'start' - firstQuartile = 'firstQuartile' - midpoint = 'midpoint' - thirdQuartile = 'thirdQuartile' - complete = 'complete' - mute = 'mute' - unmute = 'unmute' - pause = 'pause' - resume = 'resume' - rewind = 'rewind' - skip = 'skip' - playerExpand = 'playerExpand' - playerCollapse = 'playerCollapse' - fullscreen = 'fullscreen' - exitFullscreen = 'exitFullscreen' - progress = 'progress' - acceptInvitation = 'acceptInvitation' - adExpand = 'adExpand' - adCollapse = 'adCollapse' - minimize = 'minimize' - overlayViewDuration = 'overlayViewDuration' - otherAdInteraction = 'otherAdInteraction' - interactiveStart = 'interactiveStart' - close = 'close' - closeLinear = 'closeLinear' - - -class VastTarget(StrEnum): - linear = 'linear' - non_linear = 'non_linear' - companion = 'companion' - - -class VastOffset(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$'} - - -class VastVersions(RootModel[list[vast_version.VastVersion]]): - root: Annotated[list[vast_version.VastVersion], Field(min_length=1)] - - -class VastTrackerConstraints(AdCPBaseModel): - vast_event: VastEvent | None = None - target: VastTarget | None = VastTarget.linear - offset: VastOffset | None = None - vast_versions: VastVersions | None = None diff --git a/src/adcp/types/generated_poc/core/vehicle_item.py b/src/adcp/types/generated_poc/core/vehicle_item.py deleted file mode 100644 index b3c6b9bcf..000000000 --- a/src/adcp/types/generated_poc/core/vehicle_item.py +++ /dev/null @@ -1,115 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/vehicle_item.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, ConfigDict, Field, StrictFloat - -from . import ext as ext_1 -from . import offering_asset_group -from . import price as price_1 - - -class Condition(StrEnum): - new = 'new' - used = 'used' - certified_pre_owned = 'certified_pre_owned' - - -class Unit(StrEnum): - km = 'km' - mi = 'mi' - - -class Mileage(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - value: Annotated[StrictFloat, Field(description='Mileage value.', ge=0.0)] - unit: Annotated[Unit, Field(description='Distance unit.')] - - -class BodyStyle(StrEnum): - sedan = 'sedan' - suv = 'suv' - truck = 'truck' - coupe = 'coupe' - convertible = 'convertible' - wagon = 'wagon' - van = 'van' - hatchback = 'hatchback' - - -class Transmission(StrEnum): - automatic = 'automatic' - manual = 'manual' - cvt = 'cvt' - - -class FuelType(StrEnum): - gasoline = 'gasoline' - diesel = 'diesel' - electric = 'electric' - hybrid = 'hybrid' - plug_in_hybrid = 'plug_in_hybrid' - - -class Location(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - lat: Annotated[ - StrictFloat, Field(description='Latitude in decimal degrees (WGS 84).', ge=-90.0, le=90.0) - ] - lng: Annotated[ - StrictFloat, - Field(description='Longitude in decimal degrees (WGS 84).', ge=-180.0, le=180.0), - ] - - -class VehicleItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vehicle_id: Annotated[str, Field(description='Unique identifier for this vehicle listing.')] - title: Annotated[str, Field(description="Listing title (e.g., '2024 Honda Civic EX Sedan').")] - make: Annotated[str, Field(description="Vehicle manufacturer (e.g., 'Honda', 'Ford', 'BMW').")] - model: Annotated[str, Field(description="Vehicle model (e.g., 'Civic', 'F-150', 'X5').")] - year: Annotated[SchemaInt, Field(description='Model year.', ge=1900)] - price: Annotated[price_1.Price | None, Field(description='Vehicle price.')] = None - condition: Annotated[Condition | None, Field(description='Vehicle condition.')] = None - vin: Annotated[ - str | None, Field(description='Vehicle Identification Number (17-character VIN).') - ] = None - trim: Annotated[ - str | None, Field(description="Trim level (e.g., 'EX', 'Limited', 'Sport').") - ] = None - mileage: Annotated[Mileage | None, Field(description='Odometer reading.')] = None - body_style: Annotated[BodyStyle | None, Field(description='Vehicle body style.')] = None - transmission: Annotated[Transmission | None, Field(description='Transmission type.')] = None - fuel_type: Annotated[FuelType | None, Field(description='Fuel or powertrain type.')] = None - exterior_color: Annotated[str | None, Field(description='Exterior color.')] = None - interior_color: Annotated[str | None, Field(description='Interior color.')] = None - location: Annotated[Location | None, Field(description='Dealer or vehicle location.')] = None - image_url: Annotated[AnyUrl | None, Field(description='Primary vehicle image URL.')] = None - url: Annotated[AnyUrl | None, Field(description='Vehicle listing page URL.')] = None - tags: Annotated[ - list[str] | None, - Field( - description="Tags for filtering (e.g., 'low-mileage', 'one-owner', 'dealer-certified').", - min_length=1, - ), - ] = None - assets: Annotated[ - list[offering_asset_group.OfferingAssetGroup] | None, - Field( - description="Typed creative asset pools for this vehicle. Uses the same OfferingAssetGroup structure as offering-type catalogs. Standard group IDs: 'images_landscape' (exterior hero), 'images_vertical' (9:16 for Stories), 'images_square' (1:1). Enables formats to declare typed image requirements that map unambiguously to the right asset regardless of platform.", - min_length=1, - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/vendor_metric_id.py b/src/adcp/types/generated_poc/core/vendor_metric_id.py deleted file mode 100644 index 4f8897ff1..000000000 --- a/src/adcp/types/generated_poc/core/vendor_metric_id.py +++ /dev/null @@ -1,17 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/vendor_metric_id.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from adcp.types._scalar import ScalarStr - - -class VendorMetricId(ScalarStr): - __slots__ = () - _constraints = {'max_length': 64, 'min_length': 1, 'pattern': '^[a-z][a-z0-9_]*$'} - _json_schema_extra = { - 'description': "Identifier for a vendor-defined metric within the vendor's vocabulary. Stable lookup key; the vendor publishes the canonical list (with category, methodology, and standard alignment) in `brand.json` `agents[type='measurement']`. Lowercase with underscores so a future enum promotion into `available-metric.json` is a literal string lift. Identifier is namespaced by the vendor — the same `metric_id` may mean different things in different vendors' vocabularies.", - 'examples': ['attention_units', 'gco2e_per_impression', 'demographic_reach', 'co_view_index', 'incremental_lift_percent'], - 'title': 'Vendor Metric ID', - } diff --git a/src/adcp/types/generated_poc/core/vendor_metric_optimization.py b/src/adcp/types/generated_poc/core/vendor_metric_optimization.py deleted file mode 100644 index 2698a7fba..000000000 --- a/src/adcp/types/generated_poc/core/vendor_metric_optimization.py +++ /dev/null @@ -1,24 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/vendor_metric_optimization.json -# timestamp: 2026-05-30T17:30:49+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from . import vendor_metric_optimization_supported_metric - - -class VendorMetricOptimization(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supported_metrics: Annotated[ - list[vendor_metric_optimization_supported_metric.VendorMetricOptimizationSupportedMetric], - Field( - description="Vendor-defined metrics this product can steer delivery toward. Each entry pairs a vendor identity (BrandRef anchored on the vendor's `brand.json` `agents[type='measurement']`) with a `metric_id` from that vendor's published `measurement.metrics[]` catalog, plus the target kinds the seller supports for the pair. Semantic uniqueness key is `(vendor.domain, vendor.brand_id, metric_id)`; sellers MUST de-duplicate before publication. JSON Schema `uniqueItems` blocks exact-object duplicates; semantic deduplication on the BrandRef-domain key is a seller obligation." - ), - ] diff --git a/src/adcp/types/generated_poc/core/vendor_metric_optimization_supported_metric.py b/src/adcp/types/generated_poc/core/vendor_metric_optimization_supported_metric.py deleted file mode 100644 index 613c76d8c..000000000 --- a/src/adcp/types/generated_poc/core/vendor_metric_optimization_supported_metric.py +++ /dev/null @@ -1,42 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/vendor_metric_optimization_supported_metric.json -# timestamp: 2026-05-30T17:30:49+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from . import brand_ref, vendor_metric_id - - -class SupportedTarget(StrEnum): - cost_per = 'cost_per' - threshold_rate = 'threshold_rate' - - -class VendorMetricOptimizationSupportedMetric(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - brand_ref.BrandReference, - Field( - description="Vendor that defines and computes this metric. The vendor's `brand.json` is the discovery anchor for the measurement agent (entry with `type: 'measurement'` in the `agents[]` array); the metric's definition, methodology, and unit live at that agent's `get_adcp_capabilities.measurement.metrics[]` and are not duplicated inline here. Same shape as the `vendor` field on `reporting_capabilities.vendor_metrics` for symmetry across optimization and reporting capability declarations." - ), - ] - metric_id: Annotated[ - vendor_metric_id.VendorMetricId, - Field( - description="Identifier for the metric within the vendor's vocabulary (e.g., `attention_score`, `attention_seconds`, `gco2e_per_impression`, `awareness_lift`). MUST be present in the vendor's published `measurement.metrics[]` catalog." - ), - ] - supported_targets: Annotated[ - list[SupportedTarget] | None, - Field( - description='Target kinds available for `vendor_metric` goals against this `(vendor, metric_id)` pair. Values match `target.kind` on the optimization goal. `cost_per` — target cost per metric unit (e.g., $0.05 per attention-second). `threshold_rate` — minimum per-impression value (e.g., attention_score ≥ 70). Only these target kinds are accepted — goals with unlisted target kinds will be rejected. A goal without a target implicitly maximizes the metric within budget — no declaration needed for that mode. When omitted, buyers can still set target-less vendor_metric goals.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/vendor_metric_value.py b/src/adcp/types/generated_poc/core/vendor_metric_value.py deleted file mode 100644 index 8729dcfd3..000000000 --- a/src/adcp/types/generated_poc/core/vendor_metric_value.py +++ /dev/null @@ -1,98 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/vendor_metric_value.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictFloat - -from ..enums import attribution_methodology as attribution_methodology_1 -from ..enums import completion_source as completion_source_1 -from ..enums import lift_dimension as lift_dimension_1 -from ..enums import vendor_relationship as vendor_relationship_1 -from ..enums import viewability_standard as viewability_standard_1 -from . import brand_ref, duration, vendor_metric_id - - -class Qualifier(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - viewability_standard: viewability_standard_1.ViewabilityStandard | None = None - completion_source: completion_source_1.CompletionSource | None = None - attribution_methodology: attribution_methodology_1.AttributionMethodology | None = None - attribution_window: duration.Duration | None = None - lift_dimension: lift_dimension_1.LiftDimension | None = None - - -class VendorMetricValue(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - brand_ref.BrandReference, - Field( - description='Vendor that produced this value. Matches a `vendor_metrics[].vendor` declaration on the product.' - ), - ] - metric_id: Annotated[ - vendor_metric_id.VendorMetricId, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a `vendor_metrics[].metric_id` declaration on the product." - ), - ] - value: Annotated[ - StrictFloat, - Field( - description="The reported value. Unit semantics are vendor-defined — see `unit` field below and the vendor's `brand.json` measurement-agent documentation." - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate the heterogeneity of vendor metrics (e.g., `score`, `seconds`, `persons`, `gCO2e`, `USD`, `lift_percent`). When the value is monetary, use the ISO 4217 code (e.g., `USD`, `EUR`); for non-monetary, use whatever the vendor publishes. Optional on every row — the canonical unit lives at the vendor's measurement-agent metric definition (`brand.json` `agents[type='measurement']`). When sellers populate it inline they SHOULD match the vendor's published unit; buyers MAY resolve from the vendor's measurement agent when this field is absent.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - StrictFloat | None, - Field( - description='Number of impressions in this reporting period that the vendor was able to measure. Coverage denominator — buyers compute coverage rate as `measurable_impressions / impressions`. When absent, coverage is unspecified — buyers MUST NOT compute a coverage rate or assume full coverage. When the vendor measured zero impressions but is integrated, set to 0 explicitly. When the entry is omitted from `vendor_metric_values` entirely, the buyer infers no measurement happened (no integration). This pattern parallels `viewability.measurable_impressions` (`delivery-metrics.json#/properties/viewability`), which has handled vendor coverage in the IAS/DV/MRC ecosystem for over a decade — same convention: absence is unknown, not full. For channels where the atomic observation unit is a play or screen-second rather than an impression (DOOH, cinema, place-based), report `measurable_plays` or `measurable_play_seconds` instead — `impressions` there is itself a modelled figure (`plays × audience multiplier`), so `measurable_impressions / impressions` divides a measured count by a model output and has no interpretation.', - ge=0.0, - ), - ] = None - measurable_plays: Annotated[ - StrictFloat | None, - Field( - description='Number of plays (loop plays / spots aired) in this reporting period that the vendor was able to measure. Coverage denominator for channels where a play, not an impression, is the atomic observation unit — DOOH, cinema, place-based audio. Buyers compute coverage as `measurable_plays / plays` (top-level `plays` on `delivery-metrics.json`). Same absence semantics as `measurable_impressions`: absent means coverage is unspecified and buyers MUST NOT compute a rate; 0 means the vendor is integrated but measured nothing. A row SHOULD carry exactly one coverage denominator — the one matching the unit the vendor actually observes.', - ge=0.0, - ), - ] = None - measurable_play_seconds: Annotated[ - StrictFloat | None, - Field( - description="Play-seconds — seconds of creative playout summed across endpoints (screens, speakers, players) — in this reporting period that the vendor was able to measure. Medium-neutral on purpose: place-based audio has plays and duration but no screen. Coverage denominator when the vendor meters exposure duration rather than discrete plays. On screen networks buyers compute coverage as `measurable_play_seconds / dooh_metrics.screen_time_seconds`; on other place-based media, against the seller's reported playout seconds for the period. Same absence semantics as `measurable_impressions`.", - ge=0.0, - ), - ] = None - vendor_relationship: Annotated[ - vendor_relationship_1.VendorRelationship | None, - Field( - description="Optional echo of the product's `reporting_capabilities.vendor_metrics[].vendor_relationship` for this `(vendor, metric_id)`, so the delivery row is self-describing without joining back to the product (same reasoning as `viewability.vendor`). When present it MUST equal the declared value; buyers MAY resolve from the product declaration when absent. Absence on the row is *undeclared*, never `third_party`. A relationship disposition, not a trust ranking. Deliberately not a `qualifier` key: the relationship is constant across every row for a (seller, vendor) pair and does not partition rows, so it stays out of the `(vendor, metric_id, qualifier)` reconciliation join." - ), - ] = None - qualifier: Annotated[ - Qualifier | None, - Field( - description='Optional qualifier disambiguating this row from sibling rows for the same (vendor, metric_id) — e.g., the same vendor outcome metric reported under 7-day and 30-day attribution windows. Same closed key set as `committed-metric`. When the matching `committed_metrics` entry carries a qualifier, this row MUST carry the identical qualifier so reconciliation joins on `(vendor, metric_id, qualifier)`.' - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that don't fit a single scalar — panel demographic breakouts, co-view audience composition, incremental reach + frequency + lift decompositions. Free-form; the keys and value semantics are defined by the vendor (see the vendor's `brand.json` measurement-agent docs). Buyers MUST treat this object as opaque without consulting the vendor's documentation. Vendors place any fields beyond the standard envelope (e.g., confidence intervals, panel sizes) inside this object rather than at the top level." - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/vendor_pricing_option.py b/src/adcp/types/generated_poc/core/vendor_pricing_option.py deleted file mode 100644 index e4f5e35ac..000000000 --- a/src/adcp/types/generated_poc/core/vendor_pricing_option.py +++ /dev/null @@ -1,184 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/vendor_pricing_option.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, StrictFloat - -from . import ext as ext_1 -from . import format_id - - -class AppliesToOutputCapabilityId(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[a-zA-Z0-9_-]+$'} - - -class VendorPricingOption1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['cpm'] = 'cpm' - cpm: Annotated[StrictFloat, Field(description='Cost per thousand impressions', ge=0.0)] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: ext_1.ExtensionObject | None = None - - -class VendorPricingOption2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['percent_of_media'] = 'percent_of_media' - percent: Annotated[ - StrictFloat, Field(description='Percentage of media spend, e.g. 15 = 15%', ge=0.0, le=100.0) - ] - max_cpm: Annotated[ - StrictFloat | None, - Field( - description='Optional CPM cap. When set, the effective charge is min(percent × media_spend_per_mille, max_cpm).', - ge=0.0, - ), - ] = None - currency: Annotated[ - str, - Field(description='ISO 4217 currency code for the resulting charge', pattern='^[A-Z]{3}$'), - ] - ext: ext_1.ExtensionObject | None = None - - -class Period(StrEnum): - monthly = 'monthly' - quarterly = 'quarterly' - annual = 'annual' - campaign = 'campaign' - - -class VendorPricingOption3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['flat_fee'] = 'flat_fee' - amount: Annotated[StrictFloat, Field(description='Fixed charge for the billing period', ge=0.0)] - period: Annotated[Period, Field(description='Billing period for the flat fee.')] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: ext_1.ExtensionObject | None = None - - -class VendorPricingOption4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['per_unit'] = 'per_unit' - unit: Annotated[ - str, - Field( - description="What is counted — e.g. 'format', 'image', 'token', 'variant', 'render', 'evaluation'." - ), - ] - unit_price: Annotated[StrictFloat, Field(description='Cost per one unit', ge=0.0)] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: ext_1.ExtensionObject | None = None - - -class Metadata(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - summary_for_operator: Annotated[ - str | None, - Field( - description="One or two sentences describing the pricing construct in plain language, displayed to the buyer's operator when requesting approval. Should not repeat the top-level `description` verbatim — summarize the charge mechanic instead (e.g., 'Base $12 CPM plus $0.50 per qualifying post-view conversion, capped at $45 CPM').", - min_length=1, - ), - ] = None - - -class VendorPricingOption5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['custom'] = 'custom' - description: Annotated[ - str, - Field( - description='Human-readable description of the custom pricing model. Buyers display this to the operator when requesting approval.', - min_length=1, - ), - ] - metadata: Annotated[ - Metadata, - Field( - description="Structured parameters for the custom model. Keys follow lowercase_snake_case. Values may be primitives, arrays, or nested objects. Must be sufficient for a human to understand the pricing basis and for a downstream system to reconstruct the charge. Vendors SHOULD include a `summary_for_operator` string (one or two sentences, suitable for display in a buyer's operator-review UI) so reviewers across vendors see a consistent prompt. Required operator-review fields (approver role, dollar threshold for automatic approval, escalation contact) MAY be surfaced via additional keys the buyer's review surface recognizes." - ), - ] - currency: Annotated[ - str | None, - Field( - description='ISO 4217 currency code. Present when the pricing resolves to a monetary charge in a specific currency.', - pattern='^[A-Z]{3}$', - ), - ] = None - ext: ext_1.ExtensionObject | None = None - - -class VendorPricingOption6(AdCPBaseModel): - pricing_option_id: Annotated[ - str, - Field( - description='Opaque identifier for this pricing option, unique within the vendor agent. Pass this in report_usage to identify which pricing option was applied.' - ), - ] - applies_to_output_format_ids: Annotated[ - list[format_id.FormatReferenceStructuredObject] | None, - Field( - deprecated=True, - description='**DEPRECATED in 3.2.** Legacy named-format pricing scope. Use applies_to_output_capability_ids.', - min_length=1, - ), - ] = None - applies_to_output_capability_ids: Annotated[ - list[AppliesToOutputCapabilityId] | None, - Field( - description='Creative transformers only: scopes this pricing option to canonical output capabilities advertised in creative.supported_formats[].capability_id. When absent, the option is the default for any output. A build targeting an output that matches no scoped option and has no unscoped default is rejected with UNPRICEABLE_OUTPUT.', - min_length=1, - ), - ] = None - - -class VendorPricingOption7(VendorPricingOption1, VendorPricingOption6): - pass - - -class VendorPricingOption8(VendorPricingOption2, VendorPricingOption6): - pass - - -class VendorPricingOption9(VendorPricingOption3, VendorPricingOption6): - pass - - -class VendorPricingOption10(VendorPricingOption4, VendorPricingOption6): - pass - - -class VendorPricingOption11(VendorPricingOption5, VendorPricingOption6): - pass - - -VendorPricingOption = Annotated[ - VendorPricingOption7 - | VendorPricingOption8 - | VendorPricingOption9 - | VendorPricingOption10 - | VendorPricingOption11, - Field( - description='A pricing option offered by a vendor agent (signals, creative, governance). Combines pricing_option_id with the pricing model fields. Pass pricing_option_id in report_usage for billing verification. All vendor discovery responses return pricing_options as an array — vendors may offer multiple options (volume tiers, context-specific rates, different models per product line).', - title='Vendor Pricing Option', - ), -] diff --git a/src/adcp/types/generated_poc/core/verification_token_claims.py b/src/adcp/types/generated_poc/core/verification_token_claims.py deleted file mode 100644 index c1f781f67..000000000 --- a/src/adcp/types/generated_poc/core/verification_token_claims.py +++ /dev/null @@ -1,60 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/verification_token_claims.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - - -class Role(StrEnum): - media_buy = 'media-buy' - creative = 'creative' - signals = 'signals' - governance = 'governance' - brand = 'brand' - sponsored_intelligence = 'sponsored-intelligence' - - -class VerificationTokenMode(StrEnum): - spec = 'spec' - live = 'live' - - -class VerificationTokenGradingProfile(StrEnum): - legacy = 'legacy' - spec = 'spec' - - -class AgenticadvertisingOrgVerificationTokenClaims(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - iss: Literal['https://aao.org'] = 'https://aao.org' - sub: AnyUrl - aud: Literal['aao-verification'] = 'aao-verification' - jti: Annotated[str, Field(min_length=1)] - iat: Annotated[SchemaInt, Field(ge=0)] - exp: Annotated[SchemaInt, Field(ge=0)] - agent_url: AnyUrl - role: Role - verified_specialisms: Annotated[list[str], Field(min_length=1)] - verification_modes: Annotated[list[VerificationTokenMode], Field(min_length=1)] - grading_profile: Annotated[ - VerificationTokenGradingProfile | None, - Field( - description='Grading policy that produced the badge. Absence on a historical token means Legacy; verification_modes remains an independent evidence axis.' - ), - ] = None - first_failing_spec_at: Annotated[ - AwareDatetime | None, - Field( - description='Start of the current Strict Spec failure episode. Omitted when no Strict Spec failure clock is active. The registry remains authoritative for real-time status.' - ), - ] = None - adcp_version: Annotated[str | None, Field(pattern='^[1-9][0-9]*\\.[0-9]+$')] = None - protocol_version: str | None = None diff --git a/src/adcp/types/generated_poc/core/version_envelope.py b/src/adcp/types/generated_poc/core/version_envelope.py deleted file mode 100644 index d75760373..000000000 --- a/src/adcp/types/generated_poc/core/version_envelope.py +++ /dev/null @@ -1,30 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/version_envelope.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import Field - - -class AdcpVersionEnvelope(AdCPBaseModel): - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?)?$', - ), - ] = None - adcp_major_version: Annotated[ - SchemaInt | None, - Field( - deprecated=True, - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/warning.py b/src/adcp/types/generated_poc/core/warning.py deleted file mode 100644 index a233f4469..000000000 --- a/src/adcp/types/generated_poc/core/warning.py +++ /dev/null @@ -1,42 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/warning.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..enums import warning_code -from . import ext as ext_1 -from . import warning_resource - - -class Warning(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: warning_code.WarningCode - message: Annotated[ - str, - Field( - description='Short human-readable explanation. Treat as untrusted seller text and do not require buyers to parse it for routing.', - max_length=2000, - min_length=1, - ), - ] - affected_resource: Annotated[ - warning_resource.WarningAffectedResource, - Field( - description='Resource or relationship affected by this warning. Required so multi-package and multi-creative responses remain machine-joinable.' - ), - ] - details: Annotated[ - dict[str, Any] | None, - Field( - description='Optional seller-specific structured diagnostics. AdCP 3.2 defines interoperability through code and affected_resource only; buyers MUST NOT require portable keys inside details. Seller extensions that are not direct diagnostics belong in ext.' - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/warning_resource.py b/src/adcp/types/generated_poc/core/warning_resource.py deleted file mode 100644 index 11a11e41a..000000000 --- a/src/adcp/types/generated_poc/core/warning_resource.py +++ /dev/null @@ -1,27 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/warning_resource.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict - - -class ResourceType(StrEnum): - media_buy = 'media_buy' - package = 'package' - creative = 'creative' - creative_assignment = 'creative_assignment' - - -class WarningAffectedResource(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - resource_type: ResourceType - media_buy_id: str | None = None - package_id: str | None = None - creative_id: str | None = None diff --git a/src/adcp/types/generated_poc/core/webhook_activity_record.py b/src/adcp/types/generated_poc/core/webhook_activity_record.py deleted file mode 100644 index 9ac54ddb8..000000000 --- a/src/adcp/types/generated_poc/core/webhook_activity_record.py +++ /dev/null @@ -1,128 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/webhook_activity_record.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - -from ..enums import notification_type as notification_type_1 -from . import ext as ext_1 - - -class Status(StrEnum): - success = 'success' - failed = 'failed' - timeout = 'timeout' - connection_error = 'connection_error' - pending = 'pending' - - -class WebhookActivityRecord(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - idempotency_key: Annotated[ - str, - Field( - description='Equals the `idempotency_key` carried in the webhook payload itself (see docs/building/by-layer/L3/webhooks.mdx § Dedup by `idempotency_key`). Stable across retry attempts of the same logical fire — retries with `attempt` > 1 reuse this key. Buyers correlate this surface with their own endpoint logs via this exact field; the spec deliberately reuses the payload key rather than minting a parallel `delivery_id` so callers do not need a join table. Format is sender-defined; callers MUST treat as opaque.' - ), - ] - notification_id: Annotated[ - str | None, - Field( - description='Optional event-layer identifier copied verbatim from the webhook payload. For fires whose payload carries `notification_id`, sellers SHOULD populate this field on newly recorded attempts and, when populated, MUST copy the payload value without transformation. The field remains optional in AdCP 3.2 so sellers can return retained activity records created before they persisted event identity. Point-in-time delivery-report fires do not define `notification_id` and omit this field. Re-emissions of the same logical event have different `idempotency_key` values but reuse `notification_id`, allowing buyers to distinguish a re-emission from a transport retry. Callers MUST treat the value as opaque.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - subscriber_id: Annotated[ - str | None, - Field( - description='Identifies which registered webhook subscriber received this fire. **Required on records from account-anchored notification channels** (`notification_configs[]` registered via `sync_accounts`) — every subscriber has a `subscriber_id` at registration time, the seller MUST echo it on every fire and every activity record. **Optional on records from per-resource push channels** (`push_notification_config` on a media buy or task) — the calling principal is unambiguous in single-subscriber configurations and the field MAY be omitted; sellers MUST populate it once `reporting_webhook` adopts multi-subscriber (per #3009 in AdCP 4.0). Buyers MUST NOT use absence as a signal that no other subscribers exist; that information is not exposed by this surface.' - ), - ] = None - fired_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the seller initiated the HTTP request for this attempt.' - ), - ] - completed_at: Annotated[ - AwareDatetime | None, - Field( - description="ISO 8601 timestamp when the seller observed the response (or terminal timeout / connection error — for `timeout` and `connection_error` outcomes, `completed_at` is set to the moment the seller declared the attempt terminal). Explicitly `null` when the attempt is still in flight or queued for retry (status `pending`); MUST be set as `null` rather than omitted so callers can distinguish 'still in flight' from 'field missing'." - ), - ] = None - notification_type: Annotated[ - notification_type_1.NotificationType, - Field( - description='Notification type carried by this fire, verbatim from the webhook payload. Includes delivery-report types (`scheduled`, `final`, `delayed`, `adjusted`, `window_update`), health notifications (`impairment`), and account-anchored indicator or creative-assignment invalidations from the shared registry. All share the same persistent-channel webhook contract and the same buyer-side debug need.' - ), - ] - sequence_number: Annotated[ - SchemaInt | None, - Field( - description='Sequence number from the webhook payload. Surfaced here so the buyer can spot stale-sequence drops and gaps without correlating against their own endpoint log. Absent for notification types that do not carry a sequence number.', - ge=0, - ), - ] = None - attempt: Annotated[ - SchemaInt, - Field( - description='1-indexed retry counter for this logical fire. Initial fire is attempt=1; retries increment. Sellers MUST emit one record per attempt, so a successful first-attempt fire appears as a single record with `attempt: 1` and a 3-attempt retry trail appears as three records sharing `idempotency_key`.', - ge=1, - ), - ] - status: Annotated[ - Status, - Field( - description="Outcome of this attempt. `success` — response received with 2xx (`http_status_code` populated). `failed` — response received with non-2xx (`http_status_code` populated). `timeout` — no response within the seller's configured timeout (`http_status_code` null). `connection_error` — DNS / TLS / socket failure before any HTTP response (`http_status_code` null). `pending` — attempt is in flight or queued for retry (`completed_at` null, `http_status_code` null). The `timeout` / `connection_error` split is intentional and operationally distinct: `timeout` typically signals a slow / overloaded buyer endpoint, `connection_error` typically signals it is unreachable or misconfigured." - ), - ] - url: Annotated[ - AnyUrl, - Field( - description='Target URL for this fire. Query string and fragment MUST be stripped before surfacing — buyers commonly stash bearer tokens in the query string and sellers MUST NOT echo those back through this debug surface. Sellers SHOULD additionally redact path segments matching obvious secret patterns (e.g., a path segment that is high-entropy random material or matches a UUID / token format). Buyers matching this against their own configured URL should compare by origin + path; query strings will not match and that mismatch is expected.' - ), - ] - http_status_code: Annotated[ - SchemaInt | None, - Field( - description="HTTP status code returned by the buyer's endpoint. Explicitly `null` when no HTTP response was received (status `timeout`, `connection_error`, or `pending`); MUST be set as `null` rather than omitted.", - ge=100, - le=599, - ), - ] = None - response_time_ms: Annotated[ - SchemaInt | None, - Field( - description='Wall-clock latency between request send and response receipt, in milliseconds. Explicitly `null` when the attempt did not complete (`timeout`, `connection_error`, `pending`); MUST be set as `null` rather than omitted.', - ge=0, - ), - ] = None - payload_size_bytes: Annotated[ - SchemaInt | None, - Field( - description="Size of the request body the seller sent, in bytes. Useful for diagnosing oversized-payload rejections from the buyer's gateway.", - ge=0, - ), - ] = None - error_message: Annotated[ - str | None, - Field( - description='Short human-readable server-side classification of why this attempt did not succeed (e.g., `connection refused`, `TLS handshake timeout`, `HTTP 503 Service Unavailable`). Explicitly `null` for `success` (MUST be set as `null` rather than omitted). Sellers MUST NOT include request headers, request body content, or response body content in this field — payload surfacing is reserved for a future `include_webhook_payloads` extension and is subject to stricter access controls. Sellers SHOULD also avoid including buyer-endpoint internal hostnames, stack traces, or other implementation detail leaked by the response — keep it a stable classification string.', - max_length=500, - ), - ] = None - ext: Annotated[ - ext_1.ExtensionObject | None, - Field( - description='Resource-specific extension slot. Adopters MAY surface a resource-specific cross-reference (e.g., `creative_id` on a creative-lifecycle record, `media_buy_id` on a record nested inside an account-level read) under `ext` rather than adding top-level fields — the canonical record shape stays uniform across resources and the `ext` envelope absorbs per-resource needs. Top-level extensions are not permitted (`additionalProperties: false`).' - ), - ] = None diff --git a/src/adcp/types/generated_poc/core/webhook_challenge.py b/src/adcp/types/generated_poc/core/webhook_challenge.py deleted file mode 100644 index 5823f7f0e..000000000 --- a/src/adcp/types/generated_poc/core/webhook_challenge.py +++ /dev/null @@ -1,91 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/webhook_challenge.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - -from ..enums import notification_type - - -class Mode(StrEnum): - rfc9421 = 'rfc9421' - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class DeliveryAuth(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - mode: Annotated[ - Mode, - Field( - description='Future webhook delivery authentication mode. `rfc9421` is used when notification_configs[].authentication is absent; Bearer and HMAC-SHA256 are the deprecated legacy modes selected by notification_configs[].authentication.' - ), - ] - credential_fingerprint: Annotated[ - str | None, - Field( - description='SHA-256 hex fingerprint of the exact legacy credential string supplied in notification_configs[].authentication.credentials. Required when mode is Bearer or HMAC-SHA256; absent for rfc9421.', - pattern='^[a-f0-9]{64}$', - ), - ] = None - - -class WebhookChallenge(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Annotated[ - Literal['webhook.challenge'], - Field(description='Discriminator for endpoint proof-of-control challenges.'), - ] = 'webhook.challenge' - challenge: Annotated[ - str, - Field( - description='Opaque, cryptographically random value that the receiver must echo in the response body. Recommended encoding: base64url without padding.', - max_length=255, - min_length=32, - pattern='^[A-Za-z0-9_.:-]{32,255}$', - ), - ] - account_id: Annotated[ - str, - Field( - description='Seller account identifier for the account whose notification_configs[] entry is being challenged.' - ), - ] - subscriber_id: Annotated[ - str, - Field( - description='Buyer-supplied subscriber identifier from the notification_configs[] entry being challenged.', - max_length=64, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,64}$', - ), - ] - seller_agent_url: Annotated[ - AnyUrl, - Field( - description='Exact seller agent URL whose RFC 9421 webhook profile key signs this challenge and that will send subsequent webhooks.' - ), - ] - delivery_auth: Annotated[ - DeliveryAuth, - Field( - description='Authentication/signing mode the seller will use for subsequent webhooks delivered to this notification config.' - ), - ] - event_types: Annotated[ - list[notification_type.NotificationType], - Field( - description='Normalized notification types requested by the subscriber at the time of the challenge. Part of the endpoint proof scope; changing event_types[] requires a fresh challenge before the new set can become active.', - min_length=1, - ), - ] diff --git a/src/adcp/types/generated_poc/core/webhook_challenge_response.py b/src/adcp/types/generated_poc/core/webhook_challenge_response.py deleted file mode 100644 index 00e1dfba7..000000000 --- a/src/adcp/types/generated_poc/core/webhook_challenge_response.py +++ /dev/null @@ -1,47 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/webhook_challenge_response.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, model_validator - - -class WebhookChallengeResponse(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - challenge: Annotated[ - str | None, - Field( - description='Echo of the challenge value supplied by the seller.', - max_length=255, - min_length=32, - pattern='^[A-Za-z0-9_.:-]{32,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description='Backward-compatible alias for `challenge`. Receivers SHOULD prefer `challenge`; sellers MUST accept either field.', - max_length=255, - min_length=32, - pattern='^[A-Za-z0-9_.:-]{32,255}$', - ), - ] = None - - @model_validator(mode='after') - def _require_schema_required_group(self) -> WebhookChallengeResponse: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('challenge',), ('token',),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'WebhookChallengeResponse requires at least one of these field groups: challenge | token' - ) diff --git a/src/adcp/types/generated_poc/core/wholesale_feed_event.py b/src/adcp/types/generated_poc/core/wholesale_feed_event.py deleted file mode 100644 index 2d23ce13f..000000000 --- a/src/adcp/types/generated_poc/core/wholesale_feed_event.py +++ /dev/null @@ -1,688 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/wholesale_feed_event.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from collections.abc import Sequence -from typing import Annotated, Literal -from uuid import UUID - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat - -from ..enums import signal_catalog_type, signal_value_type -from . import canonical_pricing_option -from . import canonical_product as canonical_product_1 -from . import deployment, pricing_option -from . import product as product_1 -from . import signal_coverage_forecast -from . import signal_id as signal_id_1 -from . import signal_ref as signal_ref_1 -from . import vendor_pricing_option -from .signal_listing import SignalListing - - -class EventType(StrEnum): - product_created = 'product.created' - product_updated = 'product.updated' - product_priced = 'product.priced' - product_removed = 'product.removed' - signal_created = 'signal.created' - signal_updated = 'signal.updated' - signal_priced = 'signal.priced' - signal_removed = 'signal.removed' - wholesale_feed_bulk_change = 'wholesale_feed.bulk_change' - - -class EntityType(StrEnum): - product = 'product' - signal = 'signal' - feed = 'feed' - - -class AffectedEntityType(StrEnum): - product = 'product' - signal = 'signal' - - -class AppliesTo1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Literal['public'] = 'public' - - -class AppliesTo2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Literal['account'] = 'account' - account_ids: Annotated[ - list[str] | None, - Field( - description="Optional. When present, names the accounts whose overlays are affected. When omitted, subscribers infer that their own principal's overlay is affected (they received the event because the per-subscriber scope filter routed it to them).", - min_length=1, - ), - ] = None - - -AppliesTo = Annotated[ - AppliesTo1 | AppliesTo2, - Field( - description='Declares which cache layer this event invalidates. See specs/wholesale-feed-webhooks.md §Cache layering and event scoping.', - discriminator='scope', - title='Cache layer scope', - ), -] - - -class RemovalReason(StrEnum): - withdrawn = 'withdrawn' - cancellation = 'cancellation' - expired = 'expired' - depublication = 'depublication' - policy_takedown = 'policy_takedown' - - -class Range(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - min: StrictFloat - max: StrictFloat - - -class Payload(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - product_id: str - pricing_options: Annotated[ - list[pricing_option.PricingOption] | None, - Field( - description='Full post-change pricing_options array. NOT a delta — consumers replace the prior options with this list.', - min_length=1, - ), - ] = None - canonical_pricing_options: Annotated[ - list[canonical_pricing_option.CanonicalPricingOption] | None, - Field( - description='Full post-change canonical pricing array for list_products mirrors.', - min_length=1, - ), - ] = None - previous_pricing_option_ids: Annotated[ - list[str] | None, - Field(description='Optional. Lets consumers detect which pricing_option_ids were retired.'), - ] = None - effective_at: Annotated[ - AwareDatetime | None, - Field( - description='When the price change takes effect. A value in the future is a pre-announcement: consumers MAY warm caches but MUST NOT bind decisions until the effective time has passed. See specs/wholesale-feed-webhooks.md §`effective_at` and pre-announce.' - ), - ] = None - retracts_event_id: Annotated[ - UUID | None, - Field( - description="Optional. When this event retracts a prior pre-announced *.priced (per the §`effective_at` and pre-announce rules), set this to the event_id of the announcement being retracted. Lets consumers detect retraction deterministically by event_id lookup rather than maintaining per-entity 'latest-event' state. When present, the retraction MUST carry effective_at <= the retracted event's effective_at." - ), - ] = None - applies_to: AppliesTo - - -class WholesaleFeedEvent3(AdCPBaseModel): - event_id: Annotated[ - UUID, - Field( - description='Stable logical event identifier for this wholesale feed change. MUST equal the enclosing webhook notification_id. UUID v7 is RECOMMENDED so receivers can detect obvious out-of-order delivery; missed or distrusted pushes are repaired through list_products / get_signals.' - ), - ] - event_type: Annotated[ - Literal['product.priced'], - Field(description='Discriminator. Determines the shape of `payload`.'), - ] = 'product.priced' - entity_type: Annotated[ - Literal['product'], - Field( - description="Entity class. 'product' for product.* events, 'signal' for signal.* events, 'feed' for wholesale_feed.bulk_change." - ), - ] = 'product' - entity_id: Annotated[ - str, - Field( - description='Entity identifier. For product.* events, the product_id. For signal.* events, the signal_agent_segment_id. For wholesale_feed.bulk_change, an agent-defined operation id.' - ), - ] - created_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the agent emitted the event. Advisory — consumers MUST order by event_id (UUID v7), not by created_at, to avoid clock-skew artifacts.' - ), - ] - payload: Annotated[ - Payload, - Field( - description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' - ), - ] - - -class Payload20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - product_id: str - removal_reason: RemovalReason | None = None - applies_to: AppliesTo - - -class WholesaleFeedEvent4(AdCPBaseModel): - event_id: Annotated[ - UUID, - Field( - description='Stable logical event identifier for this wholesale feed change. MUST equal the enclosing webhook notification_id. UUID v7 is RECOMMENDED so receivers can detect obvious out-of-order delivery; missed or distrusted pushes are repaired through list_products / get_signals.' - ), - ] - event_type: Annotated[ - Literal['product.removed'], - Field(description='Discriminator. Determines the shape of `payload`.'), - ] = 'product.removed' - entity_type: Annotated[ - Literal['product'], - Field( - description="Entity class. 'product' for product.* events, 'signal' for signal.* events, 'feed' for wholesale_feed.bulk_change." - ), - ] = 'product' - entity_id: Annotated[ - str, - Field( - description='Entity identifier. For product.* events, the product_id. For signal.* events, the signal_agent_segment_id. For wholesale_feed.bulk_change, an agent-defined operation id.' - ), - ] - created_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the agent emitted the event. Advisory — consumers MUST order by event_id (UUID v7), not by created_at, to avoid clock-skew artifacts.' - ), - ] - payload: Annotated[ - Payload20, - Field( - description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' - ), - ] - - -class Payload23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_agent_segment_id: str - signal_ref: signal_ref_1.SignalRef | None = None - pricing_options: Annotated[ - list[vendor_pricing_option.VendorPricingOption], - Field(description='Full post-change pricing_options array. NOT a delta.', min_length=1), - ] - previous_pricing_option_ids: list[str] | None = None - effective_at: Annotated[ - AwareDatetime | None, - Field( - description='When the price change takes effect. A value in the future is a pre-announcement: consumers MAY warm caches but MUST NOT bind decisions until the effective time has passed. See specs/wholesale-feed-webhooks.md §`effective_at` and pre-announce.' - ), - ] = None - retracts_event_id: Annotated[ - UUID | None, - Field( - description='Optional. When this event retracts a prior pre-announced *.priced, set this to the event_id of the announcement being retracted. See product.priced for the full retraction contract.' - ), - ] = None - applies_to: AppliesTo - - -class WholesaleFeedEvent7(AdCPBaseModel): - event_id: Annotated[ - UUID, - Field( - description='Stable logical event identifier for this wholesale feed change. MUST equal the enclosing webhook notification_id. UUID v7 is RECOMMENDED so receivers can detect obvious out-of-order delivery; missed or distrusted pushes are repaired through list_products / get_signals.' - ), - ] - event_type: Annotated[ - Literal['signal.priced'], - Field(description='Discriminator. Determines the shape of `payload`.'), - ] = 'signal.priced' - entity_type: Annotated[ - Literal['signal'], - Field( - description="Entity class. 'product' for product.* events, 'signal' for signal.* events, 'feed' for wholesale_feed.bulk_change." - ), - ] = 'signal' - entity_id: Annotated[ - str, - Field( - description='Entity identifier. For product.* events, the product_id. For signal.* events, the signal_agent_segment_id. For wholesale_feed.bulk_change, an agent-defined operation id.' - ), - ] - created_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the agent emitted the event. Advisory — consumers MUST order by event_id (UUID v7), not by created_at, to avoid clock-skew artifacts.' - ), - ] - payload: Annotated[ - Payload23, - Field( - description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' - ), - ] - - -class Payload24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_agent_segment_id: str - signal_ref: signal_ref_1.SignalRef | None = None - removal_reason: RemovalReason | None = None - applies_to: AppliesTo - - -class WholesaleFeedEvent8(AdCPBaseModel): - event_id: Annotated[ - UUID, - Field( - description='Stable logical event identifier for this wholesale feed change. MUST equal the enclosing webhook notification_id. UUID v7 is RECOMMENDED so receivers can detect obvious out-of-order delivery; missed or distrusted pushes are repaired through list_products / get_signals.' - ), - ] - event_type: Annotated[ - Literal['signal.removed'], - Field(description='Discriminator. Determines the shape of `payload`.'), - ] = 'signal.removed' - entity_type: Annotated[ - Literal['signal'], - Field( - description="Entity class. 'product' for product.* events, 'signal' for signal.* events, 'feed' for wholesale_feed.bulk_change." - ), - ] = 'signal' - entity_id: Annotated[ - str, - Field( - description='Entity identifier. For product.* events, the product_id. For signal.* events, the signal_agent_segment_id. For wholesale_feed.bulk_change, an agent-defined operation id.' - ), - ] - created_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the agent emitted the event. Advisory — consumers MUST order by event_id (UUID v7), not by created_at, to avoid clock-skew artifacts.' - ), - ] - payload: Annotated[ - Payload24, - Field( - description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' - ), - ] - - -class Payload25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - summary: Annotated[ - str, - Field( - description="Human-readable description of the bulk operation (e.g., 'Q3 2026 rate card refresh')." - ), - ] - affected_count: Annotated[ - SchemaInt, Field(description='Approximate count of affected entities.', ge=1) - ] - recommendation: Annotated[ - Literal['wholesale_resync'] | None, - Field( - description='Advisory recommendation. Consumers SHOULD repair by re-reading the affected feed named by affected_entity_type; the recommendation is not a synchronized trigger. Modeled as a single-value enum (not a boolean) so future minor versions can extend the recommendation vocabulary without a breaking shape change. Consumers MUST be prepared to ignore unknown recommendation values.' - ), - ] = None - applies_to: AppliesTo - affected_entity_type: Annotated[ - AffectedEntityType, - Field( - description='Which wholesale feed this bulk operation touched. A bulk operation that changes both products and signals MUST emit one webhook per feed so each envelope carries the correct post-change wholesale_feed_version.' - ), - ] - - -class WholesaleFeedEvent9(AdCPBaseModel): - event_id: Annotated[ - UUID, - Field( - description='Stable logical event identifier for this wholesale feed change. MUST equal the enclosing webhook notification_id. UUID v7 is RECOMMENDED so receivers can detect obvious out-of-order delivery; missed or distrusted pushes are repaired through list_products / get_signals.' - ), - ] - event_type: Annotated[ - Literal['wholesale_feed.bulk_change'], - Field(description='Discriminator. Determines the shape of `payload`.'), - ] = 'wholesale_feed.bulk_change' - entity_type: Annotated[ - Literal['feed'], - Field( - description="Entity class. 'product' for product.* events, 'signal' for signal.* events, 'feed' for wholesale_feed.bulk_change." - ), - ] = 'feed' - entity_id: Annotated[ - str, - Field( - description='Entity identifier. For product.* events, the product_id. For signal.* events, the signal_agent_segment_id. For wholesale_feed.bulk_change, an agent-defined operation id.' - ), - ] - created_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the agent emitted the event. Advisory — consumers MUST order by event_id (UUID v7), not by created_at, to avoid clock-skew artifacts.' - ), - ] - payload: Annotated[ - Payload25, - Field( - description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' - ), - ] - - -class Signal(SignalListing): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - signal_ref_1.SignalRef | None, - Field( - description='Canonical signal reference for this wholesale signal. New events SHOULD use signal_ref.' - ), - ] = None - signal_id: Annotated[ - signal_id_1.SignalId | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - ), - ] = None - signal_agent_segment_id: Annotated[ - str, - Field(description='Opaque activation handle returned by the signals agent.', min_length=1), - ] - name: Annotated[str, Field(description='Human-readable signal name', min_length=1)] - description: Annotated[str, Field(description='Detailed signal description', min_length=1)] - value_type: signal_value_type.SignalValueType | None = None - categories: Annotated[list[str] | None, Field(min_length=1)] = None - range: Range | None = None - signal_type: signal_catalog_type.SignalAvailabilityType - data_provider: Annotated[str | None, Field(min_length=1)] = None - coverage_percentage: Annotated[ - StrictFloat | None, - Field( - deprecated=True, - description='DEPRECATED for detailed planning. Optional legacy scalar percentage of audience coverage retained only as a fallback for clients that do not consume coverage_forecast. When coverage_forecast is present, coverage_forecast is authoritative for signal-level discovery and coverage_percentage is fallback-only.', - ge=0.0, - le=100.0, - ), - ] = None - coverage_forecast: Annotated[ - signal_coverage_forecast.SignalCoverageForecast | None, - Field( - description='Optional forecast-shaped signal availability guidance using the same wire shape as get_signals.signals[].coverage_forecast. When present, this is authoritative for signal-level discovery coverage.' - ), - ] = None - deployments: Annotated[Sequence[deployment.Deployment], Field(min_length=1)] - pricing_options: Annotated[ - list[vendor_pricing_option.VendorPricingOption] | None, Field(min_length=1) - ] = None - - -class Payload21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_agent_segment_id: str - signal_ref: signal_ref_1.SignalRef | None = None - applies_to: Annotated[ - AppliesTo, - Field( - description="REQUIRED. Agents MUST declare the cache layer explicitly on every *.created event. When introducing a signal that exists only in an account overlay (e.g., a custom segment for a single account), the agent MUST emit { scope: 'account', account_ids: [...] } to prevent leak into every consumer's public-layer cache. For public-layer additions, declare { scope: 'public' } explicitly rather than relying on a default — schema-required declaration prevents the quiet-failure path." - ), - ] - signal: Annotated[Signal, Field(description='Full post-change signal object.')] - - -class WholesaleFeedEvent5(AdCPBaseModel): - event_id: Annotated[ - UUID, - Field( - description='Stable logical event identifier for this wholesale feed change. MUST equal the enclosing webhook notification_id. UUID v7 is RECOMMENDED so receivers can detect obvious out-of-order delivery; missed or distrusted pushes are repaired through list_products / get_signals.' - ), - ] - event_type: Annotated[ - Literal['signal.created'], - Field(description='Discriminator. Determines the shape of `payload`.'), - ] = 'signal.created' - entity_type: Annotated[ - Literal['signal'], - Field( - description="Entity class. 'product' for product.* events, 'signal' for signal.* events, 'feed' for wholesale_feed.bulk_change." - ), - ] = 'signal' - entity_id: Annotated[ - str, - Field( - description='Entity identifier. For product.* events, the product_id. For signal.* events, the signal_agent_segment_id. For wholesale_feed.bulk_change, an agent-defined operation id.' - ), - ] - created_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the agent emitted the event. Advisory — consumers MUST order by event_id (UUID v7), not by created_at, to avoid clock-skew artifacts.' - ), - ] - payload: Annotated[ - Payload21, - Field( - description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' - ), - ] - - -class Payload22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_agent_segment_id: str - signal_ref: signal_ref_1.SignalRef | None = None - changed_fields: Annotated[ - list[str] | None, - Field( - description='Advisory list of changed top-level field names. Consumers MAY use for fine-grained re-render, but the signal object is the source of truth for this webhook payload.' - ), - ] = None - applies_to: AppliesTo - signal: Annotated[ - Signal, - Field( - description='Full post-change signal object. Consumers replace the prior signal mirror entry with this object.' - ), - ] - - -class WholesaleFeedEvent6(AdCPBaseModel): - event_id: Annotated[ - UUID, - Field( - description='Stable logical event identifier for this wholesale feed change. MUST equal the enclosing webhook notification_id. UUID v7 is RECOMMENDED so receivers can detect obvious out-of-order delivery; missed or distrusted pushes are repaired through list_products / get_signals.' - ), - ] - event_type: Annotated[ - Literal['signal.updated'], - Field(description='Discriminator. Determines the shape of `payload`.'), - ] = 'signal.updated' - entity_type: Annotated[ - Literal['signal'], - Field( - description="Entity class. 'product' for product.* events, 'signal' for signal.* events, 'feed' for wholesale_feed.bulk_change." - ), - ] = 'signal' - entity_id: Annotated[ - str, - Field( - description='Entity identifier. For product.* events, the product_id. For signal.* events, the signal_agent_segment_id. For wholesale_feed.bulk_change, an agent-defined operation id.' - ), - ] - created_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the agent emitted the event. Advisory — consumers MUST order by event_id (UUID v7), not by created_at, to avoid clock-skew artifacts.' - ), - ] - payload: Annotated[ - Payload22, - Field( - description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' - ), - ] - - -class Payload17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - product_id: str - product: Annotated[ - product_1.Product | None, - Field( - description='Legacy 3.x post-change Product. Subscribers using get_products consume this form.' - ), - ] = None - canonical_product: Annotated[ - canonical_product_1.CanonicalProduct | None, - Field( - description='Canonical post-change Product for subscribers that negotiated lifecycle_tools.list_products. Consumers replace the matching canonical mirror entry directly.' - ), - ] = None - applies_to: Annotated[ - AppliesTo, - Field( - description="REQUIRED. Sellers MUST declare the cache layer explicitly on every *.created event. When introducing an entity that exists only in an account overlay (e.g., a custom product for a single account), the seller MUST emit { scope: 'account', account_ids: [...] } to prevent the entity from leaking into every consumer's public-layer cache. For public-layer additions, declare { scope: 'public' } explicitly rather than relying on a default — schema-required declaration prevents the quiet-failure path where a forgotten applies_to leaks an account-only entity to all consumers." - ), - ] - - -class WholesaleFeedEvent1(AdCPBaseModel): - event_id: Annotated[ - UUID, - Field( - description='Stable logical event identifier for this wholesale feed change. MUST equal the enclosing webhook notification_id. UUID v7 is RECOMMENDED so receivers can detect obvious out-of-order delivery; missed or distrusted pushes are repaired through list_products / get_signals.' - ), - ] - event_type: Annotated[ - Literal['product.created'], - Field(description='Discriminator. Determines the shape of `payload`.'), - ] = 'product.created' - entity_type: Annotated[ - Literal['product'], - Field( - description="Entity class. 'product' for product.* events, 'signal' for signal.* events, 'feed' for wholesale_feed.bulk_change." - ), - ] = 'product' - entity_id: Annotated[ - str, - Field( - description='Entity identifier. For product.* events, the product_id. For signal.* events, the signal_agent_segment_id. For wholesale_feed.bulk_change, an agent-defined operation id.' - ), - ] - created_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the agent emitted the event. Advisory — consumers MUST order by event_id (UUID v7), not by created_at, to avoid clock-skew artifacts.' - ), - ] - payload: Annotated[ - Payload17, - Field( - description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' - ), - ] - - -class Payload18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - product_id: str - product: Annotated[ - product_1.Product | None, - Field(description='Legacy 3.x post-change Product for get_products mirrors.'), - ] = None - canonical_product: Annotated[ - canonical_product_1.CanonicalProduct | None, - Field(description='Canonical post-change Product for list_products mirrors.'), - ] = None - changed_fields: Annotated[ - list[str] | None, - Field( - description="Advisory list of changed top-level field names (e.g., ['format_ids', 'performance_standards']). Consumers MAY use for fine-grained re-render, but the product object is the source of truth for this webhook payload." - ), - ] = None - applies_to: AppliesTo - - -class WholesaleFeedEvent2(AdCPBaseModel): - event_id: Annotated[ - UUID, - Field( - description='Stable logical event identifier for this wholesale feed change. MUST equal the enclosing webhook notification_id. UUID v7 is RECOMMENDED so receivers can detect obvious out-of-order delivery; missed or distrusted pushes are repaired through list_products / get_signals.' - ), - ] - event_type: Annotated[ - Literal['product.updated'], - Field(description='Discriminator. Determines the shape of `payload`.'), - ] = 'product.updated' - entity_type: Annotated[ - Literal['product'], - Field( - description="Entity class. 'product' for product.* events, 'signal' for signal.* events, 'feed' for wholesale_feed.bulk_change." - ), - ] = 'product' - entity_id: Annotated[ - str, - Field( - description='Entity identifier. For product.* events, the product_id. For signal.* events, the signal_agent_segment_id. For wholesale_feed.bulk_change, an agent-defined operation id.' - ), - ] - created_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the agent emitted the event. Advisory — consumers MUST order by event_id (UUID v7), not by created_at, to avoid clock-skew artifacts.' - ), - ] - payload: Annotated[ - Payload18, - Field( - description='Event-type-specific payload. Shape is determined by event_type per the oneOf below.' - ), - ] - - -WholesaleFeedEvent = Annotated[ - WholesaleFeedEvent1 - | WholesaleFeedEvent2 - | WholesaleFeedEvent3 - | WholesaleFeedEvent4 - | WholesaleFeedEvent5 - | WholesaleFeedEvent6 - | WholesaleFeedEvent7 - | WholesaleFeedEvent8 - | WholesaleFeedEvent9, - Field( - description="A single change event emitted by an AdCP agent's wholesale product feed or wholesale signals feed and delivered inside wholesale-feed-webhook payloads. Events are denormalized so consumers can update local state without polling list_products / get_signals. This is distinct from buyer-provided feeds managed by sync_catalogs. The discriminator is event_type; each branch defines the payload shape.", - discriminator='event_type', - title='Wholesale Feed Event', - ), -] diff --git a/src/adcp/types/generated_poc/core/wholesale_feed_webhook.py b/src/adcp/types/generated_poc/core/wholesale_feed_webhook.py deleted file mode 100644 index 6a78e73dd..000000000 --- a/src/adcp/types/generated_poc/core/wholesale_feed_webhook.py +++ /dev/null @@ -1,116 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/wholesale_feed_webhook.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated -from uuid import UUID - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - -from . import ext as ext_1 -from . import wholesale_feed_event - - -class NotificationType(StrEnum): - product_created = 'product.created' - product_updated = 'product.updated' - product_priced = 'product.priced' - product_removed = 'product.removed' - signal_created = 'signal.created' - signal_updated = 'signal.updated' - signal_priced = 'signal.priced' - signal_removed = 'signal.removed' - wholesale_feed_bulk_change = 'wholesale_feed.bulk_change' - - -class ProductPayloadView(StrEnum): - canonical = 'canonical' - legacy = 'legacy' - - -class CacheScope(StrEnum): - public = 'public' - account = 'account' - - -class WholesaleFeedWebhook(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - idempotency_key: Annotated[ - str, - Field( - description='Sender-generated key stable across retries of the same webhook fire. Receivers MUST dedupe by this key, scoped to the authenticated sender identity.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - notification_id: Annotated[ - UUID, - Field( - description='Stable identifier for this logical wholesale feed event. MUST equal event.event_id. Re-emissions of the same logical event reuse this value under a new idempotency_key.' - ), - ] - notification_type: Annotated[ - NotificationType, - Field( - description='Wholesale feed notification type discriminator. MUST match event.event_type.' - ), - ] - fired_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the seller initiated this webhook fire. Distinct from event.created_at, which is when the seller observed or recorded the feed change.' - ), - ] - subscriber_id: Annotated[ - str, - Field( - description='Identifies which notification_configs[] entry is receiving this fire. Echoed from the registered subscriber_id.', - max_length=64, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,64}$', - ), - ] - account_id: Annotated[ - str, - Field( - description='Seller account identifier for the account scope that registered this webhook through sync_accounts.accounts[].notification_configs[]. Required because wholesale feed webhooks are account-anchored notifications.' - ), - ] - wholesale_feed_version: Annotated[ - str, - Field( - description='Opaque post-change version token for the affected wholesale feed. Store it only after applying the event. A stale mirror repairs with its last applied version; uncertain or bulk repair omits the conditional token.' - ), - ] - product_payload_view: Annotated[ - ProductPayloadView | None, - Field( - description='Product representation selected by the receiving notification config. Present on product.* fires; canonical uses canonical_product/canonical_pricing_options and legacy uses product/pricing_options.' - ), - ] = None - previous_wholesale_feed_version: Annotated[ - str | None, - Field( - description='Opaque version token for the affected wholesale feed before this change, when the seller can cheaply provide it. Receivers MAY use this to detect obvious gaps, but MUST NOT require it.' - ), - ] = None - cache_scope: Annotated[ - CacheScope, - Field( - description='Cache layer affected by this change. MUST equal event.payload.applies_to.scope. Mirrors the cache_scope returned by list_products / get_signals for the affected wholesale feed.' - ), - ] - event: Annotated[ - wholesale_feed_event.WholesaleFeedEvent, - Field( - description='The actual product, signal, or bulk-change event. Consumers MAY apply this payload to their local mirror. Before any binding action, or when ordering/gap checks fail, consumers MUST reconcile through list_products / get_signals.' - ), - ] - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/core/x_entity_types.py b/src/adcp/types/generated_poc/core/x_entity_types.py deleted file mode 100644 index d1865421d..000000000 --- a/src/adcp/types/generated_poc/core/x_entity_types.py +++ /dev/null @@ -1,87 +0,0 @@ -# generated by datamodel-codegen: -# filename: core/x_entity_types.json -# timestamp: 2026-09-24T08:59:48+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class XEntityTypes(StrEnum): - advertiser_brand = 'advertiser_brand' - rights_holder_brand = 'rights_holder_brand' - rights_grant = 'rights_grant' - account = 'account' - operator = 'operator' - operator_unit = 'operator_unit' - media_buy = 'media_buy' - package = 'package' - product = 'product' - proposal = 'proposal' - opportunity = 'opportunity' - placement = 'placement' - product_pricing_option = 'product_pricing_option' - vendor_pricing_option = 'vendor_pricing_option' - creative = 'creative' - creative_revision = 'creative_revision' - creative_representation = 'creative_representation' - macro_declaration = 'macro_declaration' - tracker_execution_selector = 'tracker_execution_selector' - creative_locale_variant = 'creative_locale_variant' - creative_format = 'creative_format' - transformer = 'transformer' - evaluator = 'evaluator' - creative_evaluation = 'creative_evaluation' - build_variant = 'build_variant' - served_variant = 'served_variant' - audience = 'audience' - audience_evidence = 'audience_evidence' - audience_evidence_snapshot = 'audience_evidence_snapshot' - signal = 'signal' - signal_activation_id = 'signal_activation_id' - demographic_interval_id = 'demographic_interval_id' - spot_airing = 'spot_airing' - event_source = 'event_source' - impairment = 'impairment' - collection = 'collection' - installment = 'installment' - collection_list = 'collection_list' - property_list = 'property_list' - catalog = 'catalog' - catalog_generation = 'catalog_generation' - catalog_item = 'catalog_item' - property = 'property' - media_plan = 'media_plan' - governance_plan = 'governance_plan' - governance_registry_policy = 'governance_registry_policy' - governance_policy_category = 'governance_policy_category' - governance_policy_category_facet = 'governance_policy_category_facet' - acceptance_policy_profile = 'acceptance_policy_profile' - acceptance_policy_rule = 'acceptance_policy_rule' - media_buy_change_term = 'media_buy_change_term' - governance_inline_policy = 'governance_inline_policy' - governance_check = 'governance_check' - governance_delivery_statement = 'governance_delivery_statement' - governance_delivery_observation = 'governance_delivery_observation' - governance_outcome = 'governance_outcome' - governance_adjustment = 'governance_adjustment' - governance_adjustment_evidence = 'governance_adjustment_evidence' - seller_adjustment = 'seller_adjustment' - content_standards = 'content_standards' - task = 'task' - attestation_credential = 'attestation_credential' - si_session = 'si_session' - offering = 'offering' - vendor_metric = 'vendor_metric' - reporting_destination = 'reporting_destination' - reporting_offering = 'reporting_offering' - reporting_delivery_config = 'reporting_delivery_config' - reporting_definition = 'reporting_definition' - reporting_obligation = 'reporting_obligation' - reporting_revision = 'reporting_revision' - reporting_adjustment = 'reporting_adjustment' - reporting_materialization = 'reporting_materialization' - reporting_receipt = 'reporting_receipt' - reporting_consumer_status = 'reporting_consumer_status' - reporting_resource = 'reporting_resource' - identity_relying_party = 'identity_relying_party' diff --git a/src/adcp/types/generated_poc/creative/__init__.py b/src/adcp/types/generated_poc/creative/__init__.py deleted file mode 100644 index 5a3cca36b..000000000 --- a/src/adcp/types/generated_poc/creative/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# generated by datamodel-codegen: -# filename: .schema_temp -# timestamp: 2025-11-22T15:23:24+00:00 diff --git a/src/adcp/types/generated_poc/creative/audit_observation.py b/src/adcp/types/generated_poc/creative/audit_observation.py deleted file mode 100644 index ebe0e57b5..000000000 --- a/src/adcp/types/generated_poc/creative/audit_observation.py +++ /dev/null @@ -1,107 +0,0 @@ -# generated by datamodel-codegen: -# filename: creative/audit_observation.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, StrictBool, StrictFloat - -from ..core import ext as ext_1 - - -class HumanOversight(StrEnum): - edited = 'edited' - directed = 'directed' - - -class ClaimedValue(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - human_oversight: Annotated[ - HumanOversight, - Field(description='Human oversight level declared by the creative provenance.'), - ] - disclosure_required: Annotated[ - Literal[False], - Field(description='Disclosure-required claim declared by the creative provenance.'), - ] - - -class Details(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, Field(description='Governance agent URL that produced the observation.') - ] - feature_id: Annotated[ - str | None, Field(description='Feature or policy check that produced the observation.') - ] = None - claimed_value: Annotated[ - ClaimedValue, - Field( - description='Compact object of claimed provenance values that triggered the observation.' - ), - ] - observed_value: Annotated[ - StrictBool | StrictFloat | str | None, - Field(description='Verifier observation relevant to the claim, when applicable.'), - ] = None - confidence: Annotated[ - StrictFloat | None, - Field(description='Confidence score for the observation, when applicable.', ge=0.0, le=1.0), - ] = None - substituted_for: Annotated[ - AnyUrl | None, - Field( - description='Buyer-nominated verifier URL when the seller or orchestrator used a different on-list governance agent.' - ), - ] = None - - -class CreativeAuditObservation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - code: Annotated[ - Literal['OVERSIGHT_DISCLOSURE_CARVEOUT_CLAIMED'], - Field( - description='Machine-readable observation code. `OVERSIGHT_DISCLOSURE_CARVEOUT_CLAIMED` means provenance declares `human_oversight` as `edited` or `directed` while also declaring `disclosure.required: false`; the verifier is surfacing the carve-out claim for audit, not adjudicating it.' - ), - ] = 'OVERSIGHT_DISCLOSURE_CARVEOUT_CLAIMED' - severity: Annotated[ - Literal['audit-worthy'], - Field( - description='Routing severity. `audit-worthy` means the observation should be retained and may be routed to human or downstream audit review, but it is not a protocol rejection signal.' - ), - ] = 'audit-worthy' - recovery: Annotated[ - Literal['informational'], - Field( - description='Caller recovery category for audit observations, distinct from the canonical error-code recovery enum. `informational` means the creative can continue through the normal flow; the observation is audit context rather than a required correction.' - ), - ] = 'informational' - field: Annotated[ - str, - Field( - description='Resolved creative manifest path for the risky claim side of the observation, for example `creative_manifest.provenance.disclosure.required`. Some observations are triggered by a combination of fields; `field` anchors the primary claim, not necessarily every field in the trigger condition.' - ), - ] - message: Annotated[ - str, - Field( - description='Human-readable summary suitable for an audit queue. Do not include PII, cross-tenant data, or vendor-only report details.' - ), - ] - details: Annotated[ - Details, - Field( - description='Audit-safe structured details. Mirrors the safe allowlist keys used for `PROVENANCE_CLAIM_CONTRADICTED`; value shapes remain observation-specific. Top-level `ext` remains the standard protocol extension point, but details do not allow arbitrary verifier response fields.' - ), - ] - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/creative/creative_assignment_changed_webhook.py b/src/adcp/types/generated_poc/creative/creative_assignment_changed_webhook.py deleted file mode 100644 index 5f4cc9518..000000000 --- a/src/adcp/types/generated_poc/creative/creative_assignment_changed_webhook.py +++ /dev/null @@ -1,49 +0,0 @@ -# generated by datamodel-codegen: -# filename: creative/creative_assignment_changed_webhook.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - -from ..core import ext as ext_1 - - -class ChangeKind(StrEnum): - assigned = 'assigned' - unassigned = 'unassigned' - approval_changed = 'approval_changed' - - -class CreativeAssignmentChangedWebhook(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - idempotency_key: Annotated[ - str, Field(max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$') - ] - notification_id: Annotated[ - str, - Field( - description='Stable identity for this logical assignment change across re-emissions.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] - notification_type: Literal['creative.assignment_changed'] = 'creative.assignment_changed' - fired_at: AwareDatetime - subscriber_id: Annotated[ - str, Field(max_length=64, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,64}$') - ] - account_id: str - media_buy_id: str - package_id: str - creative_id: str - change_kind: ChangeKind - observed_at: AwareDatetime - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/creative/creative_feature_result.py b/src/adcp/types/generated_poc/creative/creative_feature_result.py deleted file mode 100644 index 51210c409..000000000 --- a/src/adcp/types/generated_poc/creative/creative_feature_result.py +++ /dev/null @@ -1,61 +0,0 @@ -# generated by datamodel-codegen: -# filename: creative/creative_feature_result.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat - -from ..core import ext as ext_1 - - -class CreativeFeatureResult(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - feature_id: Annotated[ - str, - Field( - description="The feature that was evaluated (e.g., 'auto_redirect', 'brand_consistency'). Features prefixed with 'registry:' reference standardized policies from the shared policy registry (e.g., 'registry:eu_ai_act_article_50'). Unprefixed feature IDs are agent-defined." - ), - ] - value: Annotated[ - StrictBool | StrictFloat | str, - Field( - description='The feature value. Type depends on feature definition: boolean for binary, number for quantitative, string for categorical.' - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of measurement for quantitative values (e.g., 'percentage', 'score')" - ), - ] = None - confidence: Annotated[ - StrictFloat | None, - Field(description='Confidence score for this value (0-1)', ge=0.0, le=1.0), - ] = None - measured_at: Annotated[ - AwareDatetime | None, Field(description='When this feature was evaluated') - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field(description='When this evaluation expires and should be refreshed'), - ] = None - methodology_version: Annotated[ - str | None, Field(description='Version of the methodology used to evaluate this feature') - ] = None - details: Annotated[ - dict[str, Any] | None, - Field(description='Additional vendor-specific details about this evaluation'), - ] = None - policy_id: Annotated[ - str | None, - Field( - description='Optional attribution — when this feature was evaluated for the purpose of a specific policy, policy_id references the authorizing PolicyEntry. Creative agents and sellers populate when the measurement was motivated by a specific policy; do NOT populate when the feature is a generic measurement (carbon score, brand consistency) unrelated to any policy. See /docs/governance/policy-attribution.' - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/creative/creative_purged_webhook.py b/src/adcp/types/generated_poc/creative/creative_purged_webhook.py deleted file mode 100644 index 340bc8e66..000000000 --- a/src/adcp/types/generated_poc/creative/creative_purged_webhook.py +++ /dev/null @@ -1,108 +0,0 @@ -# generated by datamodel-codegen: -# filename: creative/creative_purged_webhook.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - -from ..core import ext as ext_1 -from ..enums import creative_event_reason_code - - -class PurgeKind(StrEnum): - soft = 'soft' - hard = 'hard' - - -class Initiator(StrEnum): - seller = 'seller' - system = 'system' - - -class CreativePurgedWebhook(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - idempotency_key: Annotated[ - str, - Field( - description='Sender-generated key stable across retries of the same fire.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - notification_id: Annotated[ - str, - Field( - description='Stable identifier for this purge event. Receivers correlate fires by this id; a single creative can only be purged once, so a new id implies a different creative or (rare) a re-emission of the same purge after retention-driven snapshot loss.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] - notification_type: Annotated[ - Literal['creative.purged'], Field(description='Fixed notification type discriminator.') - ] = 'creative.purged' - fired_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the seller initiated this fire. Sellers MUST fire `creative.purged` immediately after purge — coalescence is not permitted on this notification type.' - ), - ] - subscriber_id: Annotated[ - str, - Field( - description='Identifies which `notification_configs[]` entry on the recipient account is receiving this fire.', - max_length=64, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,64}$', - ), - ] - account_id: Annotated[ - str, - Field(description="Seller's identifier for the account that owned the purged creative."), - ] - creative_id: Annotated[ - str, - Field( - description="Seller's identifier for the purged creative. After a `soft` purge this id resolves on `list_creatives` (with `include_purged: true`) for the seller's tombstone retention window; after a `hard` purge no record remains." - ), - ] - purge_kind: Annotated[ - PurgeKind, - Field( - description="`soft` — tombstone retained on `list_creatives` (with `include_purged: true`) for the seller's retention window (default 30 days, MUST match the `webhook_activity[]` retention rule). `hard` — no tombstone; the seller MUST NOT retain or echo the asset content elsewhere. `hard` is reserved for legal erasure or equivalent compelled destruction; sellers SHOULD default to `soft`." - ), - ] - purged_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the seller observed the destruction. Distinct from `fired_at`.' - ), - ] - reason_code: Annotated[ - creative_event_reason_code.CreativeEventReasonCode, - Field( - description='Categorical reason for the purge. Typical values: `retention_expired`, `legal_erasure`, `takedown_request`. Receivers MUST treat unknown reason codes as forward-compatible additions.' - ), - ] - reason_detail: Annotated[ - str | None, - Field( - description='Human-readable supplement. Sellers MUST NOT include third-party PII or the asset content in this field — the field is meant for operational context, not asset preservation.', - max_length=500, - ), - ] = None - initiator: Annotated[ - Initiator, - Field( - description='Who initiated the purge. `seller` — explicit decision (legal team, content policy, takedown handler). `system` — automated retention sweep, storage policy enforcement. `buyer` never appears on this event — buyer-initiated destruction (if and when supported) is acknowledged on the `sync_creatives` response path.' - ), - ] - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/creative/creative_status_changed_webhook.py b/src/adcp/types/generated_poc/creative/creative_status_changed_webhook.py deleted file mode 100644 index 21e563e53..000000000 --- a/src/adcp/types/generated_poc/creative/creative_status_changed_webhook.py +++ /dev/null @@ -1,136 +0,0 @@ -# generated by datamodel-codegen: -# filename: creative/creative_status_changed_webhook.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - -from ..core import creative_revision_id -from ..core import ext as ext_1 -from ..enums import creative_event_reason_code, creative_status - - -class From(StrEnum): - processing = 'processing' - pending_review = 'pending_review' - approved = 'approved' - suspended = 'suspended' - - -class Transition(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - from_: Annotated[ - From, - Field( - alias='from', - description='Prior status — restricted to the states from which a seller/system-initiated transition can fire. For initial review outcomes the prior status is `pending_review`; for processing outcomes it is `processing`; for seller re-review, post-approval revocation, recoverable suspension, and seller-initiated archive it is `approved`; for recovery from a dependency/authorization outage or terminal escalation after a suspension it is `suspended`.', - ), - ] - to: Annotated[creative_status.CreativeStatus, Field(description='New status.')] - observed_at: Annotated[ - AwareDatetime, - Field( - description="ISO 8601 timestamp when the seller observed the transition. Distinct from `fired_at` — `fired_at` reflects when the webhook was emitted, which may lag `observed_at` by up to the seller's coalescence window. **Ordering with `media-buy.impairment`:** when a creative transition also causes a media-buy impairment (e.g., `approved → suspended` or `approved → rejected` while assignments exist), the `creative.status_changed` and `impairment` fires are not ordered — buyers MUST NOT assume one arrives before the other. Reconcile via the snapshot (`list_creatives` and `get_media_buys`) when the two fires reference the same `creative_id`." - ), - ] - - -class Initiator(StrEnum): - seller = 'seller' - system = 'system' - - -class CreativeStatusChangedWebhook(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - idempotency_key: Annotated[ - str, - Field( - description='Sender-generated key stable across retries of the same fire. Sellers MUST generate a cryptographically random value (UUID v4 recommended) per distinct fire and reuse it on every retry of the same fire. Receivers MUST dedupe by this key, scoped to the authenticated sender identity. Distinct from `notification_id` — same `notification_id` under two different `idempotency_key` values is a re-emission signal (snapshot-and-log Rule 1).', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - notification_id: Annotated[ - str, - Field( - description="Stable identifier for this logical transition event, used by buyers to correlate fires to current snapshot state. Stable across re-emissions of the same transition (e.g., the seller re-fires after the buyer's endpoint was unreachable); a fresh transition on the same creative receives a new id. Charset matches `idempotency_key` so the value is safe to log, embed in dashboard URLs, and pass into LLM prompts without escaping.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] - notification_type: Annotated[ - Literal['creative.status_changed'], - Field( - description="Fixed notification type discriminator. Matches the value registered on the subscriber's `event_types`." - ), - ] = 'creative.status_changed' - fired_at: Annotated[ - AwareDatetime, - Field( - description="ISO 8601 timestamp when the seller initiated this fire. Distinct from when the transition was observed (`transition.observed_at`) — fires MAY be coalesced or delayed up to the seller's declared coalescence window for `creative.status_changed`." - ), - ] - subscriber_id: Annotated[ - str, - Field( - description="Identifies which `notification_configs[]` entry on the recipient account is receiving this fire. Echoed verbatim from the entry's `subscriber_id`. Required so multi-subscriber accounts can route by endpoint.", - max_length=64, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,64}$', - ), - ] - account_id: Annotated[ - str, - Field( - description="Seller's identifier for the account this creative belongs to. Echoed so multi-account buyers can route without re-resolving via `list_accounts`." - ), - ] - creative_id: Annotated[ - str, - Field( - description="Seller's identifier for the creative whose status changed. References the same id space as `list_creatives` / `sync_creatives` responses." - ), - ] - revision_id: Annotated[ - creative_revision_id.CreativeRevisionId | None, - Field( - description='Buyer-authored input revision to which this transition applies. Required when the reviewed state has revision identity and the seller advertises creative.supports_revisions; it need not be the current list revision when the fire is received. Omitted for unversioned current content. A stale review outcome MUST NOT emit a fire as though it applied to a newer current revision.' - ), - ] = None - transition: Annotated[ - Transition, - Field( - description='The status transition that triggered this fire. Valid `from` values are restricted to the prior states from which a seller/system-initiated transition can fire (`processing` for processing outcomes, `pending_review` for initial review outcomes, `approved` for re-review/revocation/seller-archive/recoverable suspension, `suspended` for seller-observed recovery or terminal escalation). The post-terminal states `rejected` and `archived` MUST NOT appear as `from` — those would require a buyer-initiated unblock (`sync_creatives` resubmit / unarchive), which does not fire this event.' - ), - ] - reason_code: Annotated[ - creative_event_reason_code.CreativeEventReasonCode, - Field( - description='Categorical reason for the transition. Per-transition valid subsets are constrained by impairment.coherence-style narrative rules — see docs/creative/creative-lifecycle-webhooks.mdx. Receivers MUST treat unknown reason codes as forward-compatible additions and not reject the fire.' - ), - ] - reason_detail: Annotated[ - str | None, - Field( - description='Human-readable supplement to `reason_code`. Free text from the seller. Sellers MUST NOT include third-party PII in this field.', - max_length=500, - ), - ] = None - initiator: Annotated[ - Initiator, - Field( - description='Who initiated the transition. `seller` — explicit decision by the seller (human reviewer, policy operator, takedown handler). `system` — automated seller-side process (processing pipeline, retention sweep, inactivity scan). `buyer` never appears on this event — buyer-initiated transitions are acknowledged on the `sync_creatives` response path and MUST NOT fire `creative.status_changed`.' - ), - ] - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/creative/get_creative_delivery_request.py b/src/adcp/types/generated_poc/creative/get_creative_delivery_request.py deleted file mode 100644 index 75aa17ad8..000000000 --- a/src/adcp/types/generated_poc/creative/get_creative_delivery_request.py +++ /dev/null @@ -1,84 +0,0 @@ -# generated by datamodel-codegen: -# filename: creative/get_creative_delivery_request.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import SchemaInt -from pydantic import ConfigDict, Field, model_validator - -from ..core import account_ref -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core import pagination_request -from ..core.version_envelope import AdcpVersionEnvelope - - -class GetCreativeDeliveryRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - account: Annotated[ - account_ref.AccountReference | None, - Field( - description='Account for routing and scoping. Limits results to creatives within this account.' - ), - ] = None - media_buy_ids: Annotated[ - list[str] | None, - Field( - description='Filter to specific media buys by publisher ID. If omitted, returns creative delivery across all matching media buys.', - min_length=1, - ), - ] = None - creative_ids: Annotated[ - list[str] | None, - Field( - description='Filter to specific creatives by ID. If omitted, returns delivery for all creatives matching the other filters.', - min_length=1, - ), - ] = None - start_date: Annotated[ - str | None, - Field( - description="Start date for delivery period (YYYY-MM-DD). Interpreted in the platform's reporting timezone.", - pattern='^\\d{4}-\\d{2}-\\d{2}$', - ), - ] = None - end_date: Annotated[ - str | None, - Field( - description="End date for delivery period (YYYY-MM-DD). Interpreted in the platform's reporting timezone.", - pattern='^\\d{4}-\\d{2}-\\d{2}$', - ), - ] = None - max_variants: Annotated[ - SchemaInt | None, - Field( - description='Maximum number of variants to return per creative. When omitted, the agent returns all variants. Use this to limit response size for generative creatives that may produce large numbers of variants.', - ge=1, - ), - ] = None - pagination: Annotated[ - pagination_request.PaginationRequest | None, - Field( - description='Pagination parameters for the creatives array in the response. Uses cursor-based pagination consistent with other list operations.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - @model_validator(mode='after') - def _require_schema_required_group(self) -> GetCreativeDeliveryRequest: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('media_buy_ids',), ('creative_ids',),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'GetCreativeDeliveryRequest requires at least one of these field groups: media_buy_ids | creative_ids' - ) diff --git a/src/adcp/types/generated_poc/creative/get_creative_delivery_response.py b/src/adcp/types/generated_poc/creative/get_creative_delivery_response.py deleted file mode 100644 index 857e5c7f9..000000000 --- a/src/adcp/types/generated_poc/creative/get_creative_delivery_response.py +++ /dev/null @@ -1,150 +0,0 @@ -# generated by datamodel-codegen: -# filename: creative/get_creative_delivery_response.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from collections.abc import Sequence -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field, StrictBool - -from ..core import canonical_format_kind -from ..core import context as context_1 -from ..core import creative_variant, delivery_metrics, error -from ..core import ext as ext_1 -from ..core import format_id as format_id_1 -from ..core import format_option_ref as format_option_ref_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope - - -class ReportingPeriod(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - start: Annotated[AwareDatetime, Field(description='ISO 8601 start timestamp')] - end: Annotated[AwareDatetime, Field(description='ISO 8601 end timestamp')] - timezone: Annotated[ - str | None, - Field( - description="IANA timezone identifier for the reporting period (e.g., 'America/New_York', 'UTC'). Platforms report in their native timezone." - ), - ] = None - - -class Pagination(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - limit: Annotated[SchemaInt, Field(description='Maximum number of creatives requested', ge=1)] - offset: Annotated[SchemaInt, Field(description='Number of creatives skipped', ge=0)] - has_more: Annotated[ - StrictBool, Field(description='Whether more creatives are available beyond this page') - ] - total_count: Annotated[ - SchemaInt | None, - Field( - description='Total number of creatives matching the request filters. Canonical field name (matches `PaginationResponse.total_count`). Sellers SHOULD populate this and the deprecated `total` field identically until 4.0; buyers SHOULD prefer this field.', - ge=0, - ), - ] = None - total: Annotated[ - SchemaInt | None, - Field( - deprecated=True, - description='**Deprecated** — use `total_count` instead. Retained as a legacy alias for 3.x backward compatibility; removed in AdCP 4.0. Sellers populating this field MUST also populate `total_count` with the same value.', - ge=0, - ), - ] = None - - -class Creative(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - creative_id: Annotated[str, Field(description='Creative identifier')] - media_buy_id: Annotated[ - str | None, - Field( - description="Publisher's media buy identifier for this creative. Present when the request spanned multiple media buys, so the buyer can correlate each creative to its media buy." - ), - ] = None - format_id: Annotated[ - format_id_1.FormatReferenceStructuredObject | None, - Field( - deprecated=True, - description='**DEPRECATED in 3.2.** Legacy named format of this creative. New responses use `format_kind` and optional `format_option_ref`.', - ), - ] = None - format_kind: Annotated[ - canonical_format_kind.CanonicalFormatKind | str | None, - Field( - description='Canonical format kind delivered for this creative.', - union_mode='left_to_right', - ), - ] = None - format_option_ref: Annotated[ - format_option_ref_1.FormatOptionReference | None, - Field( - description='Portable reference to the product or publisher format option used for delivery, when disambiguation is required.' - ), - ] = None - totals: Annotated[ - delivery_metrics.DeliveryMetrics | None, - Field(description='Aggregate delivery metrics across all variants of this creative'), - ] = None - variant_count: Annotated[ - SchemaInt | None, - Field( - description='Total number of agent-unique variant_id rows for this creative. When max_variants was specified in the request, this may exceed the number of items in the variants array.', - ge=0, - ), - ] = None - variants: Annotated[ - list[creative_variant.CreativeVariant], - Field( - description='Variant-level delivery breakdown. Each agent-unique variant_id identifies one immutable served execution and each row includes metrics from exactly one source revision and, for localized delivery, exactly one locale variant. A distinct revision, locale, or rendered manifest receives a distinct variant_id; metrics MUST NOT cross those boundaries. For standard creatives, contains one row per source revision and locale represented in the reporting period. For asset group optimization, one per combination, source revision, and locale. For generative creative, one per generated execution, source revision, and locale. Empty when a creative has no variants yet.' - ), - ] - - -class GetCreativeDeliveryResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - account_id: Annotated[ - str | None, - Field( - description='Account identifier. Present when the response spans or is scoped to a specific account.' - ), - ] = None - media_buy_id: Annotated[ - str | None, - Field( - description="Publisher's media buy identifier. Present when the request was scoped to a single media buy." - ), - ] = None - currency: Annotated[ - str, - Field( - description="ISO 4217 currency code for monetary values in this response (e.g., 'USD', 'EUR')", - pattern='^[A-Z]{3}$', - ), - ] - reporting_period: Annotated[ReportingPeriod, Field(description='Date range for the report.')] - creatives: Annotated[ - Sequence[Creative], Field(description='Creative delivery data with variant breakdowns') - ] - pagination: Annotated[ - Pagination | None, - Field( - description='Pagination information. Present when the request included pagination parameters. **Note:** `get_creative_delivery` uses page-based pagination (`limit`/`offset`) for historical reasons, distinct from the cursor-based [`PaginationResponse`](/schemas/v3/core/pagination-response.json) used by `list_*` tools. Field naming aligned with `PaginationResponse.total_count` in 3.1; the legacy `total` field is retained as a deprecated alias until 4.0. Sellers MUST populate both fields identically; buyers SHOULD prefer `total_count` (the canonical name) and ignore `total` if both are present.' - ), - ] = None - errors: Annotated[ - list[error.Error] | None, Field(description='Task-specific errors and warnings') - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/creative/get_creative_features_async_response_submitted.py b/src/adcp/types/generated_poc/creative/get_creative_features_async_response_submitted.py deleted file mode 100644 index e941d061b..000000000 --- a/src/adcp/types/generated_poc/creative/get_creative_features_async_response_submitted.py +++ /dev/null @@ -1,48 +0,0 @@ -# generated by datamodel-codegen: -# filename: creative/get_creative_features_async_response_submitted.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..core import context as context_1 -from ..core import ext as ext_1 - - -class GetCreativeFeaturesSubmitted(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Literal['submitted'], - Field( - description='Task-level status literal that discriminates this acknowledgement from terminal success and error responses.' - ), - ] = 'submitted' - task_id: Annotated[ - str, - Field( - description='AdCP task handle used to poll get_task_status or correlate terminal webhook delivery. Distinct from evaluation_id.', - min_length=1, - ), - ] - evaluation_id: Annotated[ - str | None, - Field( - description='Provider-generated identity allocated when the evaluation is accepted. Optional in AdCP 3.x and required in AdCP 4.0. Providers SHOULD emit it in 3.x; when present, exact replays and the terminal result MUST carry this same value. Distinct from task_id and idempotency_key.', - min_length=1, - ), - ] = None - message: Annotated[ - str | None, - Field( - description='Optional human-readable explanation of why the evaluation is submitted. Plain text only; callers treat it as untrusted provider input.', - max_length=2000, - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/creative/get_creative_features_request.py b/src/adcp/types/generated_poc/creative/get_creative_features_request.py deleted file mode 100644 index 82e813fcd..000000000 --- a/src/adcp/types/generated_poc/creative/get_creative_features_request.py +++ /dev/null @@ -1,58 +0,0 @@ -# generated by datamodel-codegen: -# filename: creative/get_creative_features_request.json -# timestamp: 2026-10-02T23:10:32+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import ConfigDict, Field - -from ..core import account_ref -from ..core import context as context_1 -from ..core import creative_manifest as creative_manifest_1 -from ..core import ext as ext_1 -from ..core import push_notification_config as push_notification_config_1 -from ..core.version_envelope import AdcpVersionEnvelope - - -class GetCreativeFeaturesRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - idempotency_key: Annotated[ - str | None, - Field( - description='Optional in AdCP 3.x for wire compatibility; clients SHOULD send a unique key for every logical evaluation. When supplied to a provider that advertises adcp.idempotency.supported: true, exact retries with the same canonical payload MUST return the cached initial response with replayed: true and MUST NOT dispatch a second provider evaluation or record a second consumption or cost event. Reusing the key with a different creative_manifest, account, or feature_ids payload returns IDEMPOTENCY_CONFLICT. The provider MUST retain the replay record for at least 24 hours. Keys MUST be unique per (provider, request) pair to prevent cross-provider correlation. This field becomes required in AdCP 4.0; use a fresh UUID v4 for each logical evaluation.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] = None - creative_manifest: Annotated[ - creative_manifest_1.CreativeManifest, - Field( - description='The canonical creative manifest to evaluate. In 3.2 it contains `format_kind`, optional `format_option_ref`, and typed assets. The deprecated named `format_id` branch remains available only for older 3.x peers.' - ), - ] - feature_ids: Annotated[ - list[str] | None, - Field( - description='Optional filter to specific features. If omitted, returns all available features.', - min_length=1, - ), - ] = None - account: Annotated[ - account_ref.AccountReference | None, - Field( - description='Account for billing this evaluation. Required when the governance agent charges per evaluation.' - ), - ] = None - push_notification_config: Annotated[ - push_notification_config_1.PushNotificationConfig | None, - Field( - description='Optional webhook configuration for async terminal completion/failure notifications. A submitted acknowledgement remains pollable through get_task_status whether or not this field is present. If the provider accepts this field and returns status: submitted, it MUST deliver at least the terminal completion or failure notification to the configured URL. If it cannot honor the webhook channel, it MUST reject the request instead of silently downgrading delivery.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/creative/get_creative_features_response.py b/src/adcp/types/generated_poc/creative/get_creative_features_response.py deleted file mode 100644 index fb917fcca..000000000 --- a/src/adcp/types/generated_poc/creative/get_creative_features_response.py +++ /dev/null @@ -1,49 +0,0 @@ -# generated by datamodel-codegen: -# filename: creative/get_creative_features_response.json -# timestamp: preserved-by-post-generate-fixes - -from __future__ import annotations - -from typing import Annotated, Literal, TypeAlias - -from pydantic import ConfigDict, StringConstraints - -from ..core.version_envelope import AdcpVersionEnvelope -from ..core import context as context_1 -from ..core import error as error_1 -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..enums import task_status as task_status_1 - - -class GetCreativeFeaturesResponse1(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - - -class GetCreativeFeaturesResponse2(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - evaluation_id: Annotated[str, StringConstraints(min_length=1)] | None = None - errors: list[error_1.Error] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class GetCreativeFeaturesResponse3(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow', validate_default=True) - status: Literal[task_status_1.TaskStatus.submitted] = task_status_1.TaskStatus.submitted - task_id: Annotated[str, StringConstraints(min_length=1)] - evaluation_id: Annotated[str, StringConstraints(min_length=1)] | None = None - message: Annotated[str, StringConstraints(max_length=2000)] | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -GetCreativeFeaturesResponse: TypeAlias = GetCreativeFeaturesResponse1 | GetCreativeFeaturesResponse2 | GetCreativeFeaturesResponse3 - - -__all__ = [ - 'GetCreativeFeaturesResponse', - 'GetCreativeFeaturesResponse1', - 'GetCreativeFeaturesResponse2', - 'GetCreativeFeaturesResponse3', -] diff --git a/src/adcp/types/generated_poc/creative/get_creative_features_terminal_success.py b/src/adcp/types/generated_poc/creative/get_creative_features_terminal_success.py deleted file mode 100644 index a834279b9..000000000 --- a/src/adcp/types/generated_poc/creative/get_creative_features_terminal_success.py +++ /dev/null @@ -1,66 +0,0 @@ -# generated by datamodel-codegen: -# filename: creative/get_creative_features_terminal_success.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, StrictFloat - -from ..core import context as context_1 -from ..core import creative_consumption -from ..core import ext as ext_1 -from . import audit_observation, creative_feature_result - - -class GetCreativeFeaturesSuccess(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - evaluation_id: Annotated[ - str | None, - Field( - description='Provider-generated identity for this evaluation. Optional in AdCP 3.x and required in AdCP 4.0. Providers SHOULD emit it in 3.x. When present, it MUST be stable across exact response replays and, for async work whose submitted acknowledgement included evaluation_id, MUST be identical to that value. Used for result provenance, consumption and provider-cost reconciliation, and support; it is not an idempotency key or task identity.', - min_length=1, - ), - ] = None - results: Annotated[ - list[creative_feature_result.CreativeFeatureResult], - Field(description='Feature values for the evaluated creative'), - ] - detail_url: Annotated[ - AnyUrl | None, - Field( - description="URL to the vendor's full assessment report. The vendor controls what information is disclosed and access control." - ), - ] = None - audit_observations: Annotated[ - list[audit_observation.CreativeAuditObservation] | None, - Field( - description='Non-blocking audit observations from the governance agent. Observations surface audit-worthy claims that are not verifier refutations and are not rejection grounds by themselves.' - ), - ] = None - pricing_option_id: Annotated[ - str | None, - Field( - description='Which rate card pricing option was applied for this evaluation. Present when the governance agent charges for evaluations and account was provided in the request.' - ), - ] = None - vendor_cost: Annotated[ - StrictFloat | None, - Field(description='Cost incurred for this evaluation, denominated in currency.', ge=0.0), - ] = None - currency: Annotated[ - str | None, - Field(description='ISO 4217 currency code for vendor_cost.', pattern='^[A-Z]{3}$'), - ] = None - consumption: Annotated[ - creative_consumption.CreativeConsumption | None, - Field( - description='Structured consumption details for this evaluation. Informational — lets the buyer verify that vendor_cost is consistent with the rate card. vendor_cost is the billing source of truth.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/creative/list_creative_formats_request.py b/src/adcp/types/generated_poc/creative/list_creative_formats_request.py deleted file mode 100644 index e66098bd1..000000000 --- a/src/adcp/types/generated_poc/creative/list_creative_formats_request.py +++ /dev/null @@ -1,141 +0,0 @@ -# generated by datamodel-codegen: -# filename: creative/list_creative_formats_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import SchemaInt -from pydantic import ConfigDict, Field, StrictBool - -from ..core import account_ref -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core import format_id, pagination_request -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import asset_content_type -from ..enums import disclosure_persistence as disclosure_persistence_1 -from ..enums import disclosure_position -from ..enums import wcag_level as wcag_level_1 - - -class Type(StrEnum): - audio = 'audio' - video = 'video' - display = 'display' - dooh = 'dooh' - - -class ListCreativeFormatsRequestCreativeAgent(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - format_ids: Annotated[ - list[format_id.FormatReferenceStructuredObject] | None, - Field( - deprecated=True, - description='Deprecated in AdCP 3.2; removed in AdCP 4.0. Return only these specific named-format IDs. Use canonical-format discovery in 4.0.', - min_length=1, - ), - ] = None - type: Annotated[ - Type | None, - Field( - description='Filter by format type (technical categories with distinct requirements)' - ), - ] = None - asset_types: Annotated[ - list[asset_content_type.AssetContentType] | None, - Field( - description="Filter to formats that include these asset types. For third-party tags, search for 'html' or 'javascript'. For published-post reference formats, search for 'published_post'. E.g., ['image', 'text'] returns formats with images and text, ['javascript'] returns formats accepting JavaScript tags.", - min_length=1, - ), - ] = None - max_width: Annotated[ - SchemaInt | None, - Field( - description='Maximum width in pixels (inclusive). Returns formats with width <= this value. Omit for responsive/fluid formats.' - ), - ] = None - max_height: Annotated[ - SchemaInt | None, - Field( - description='Maximum height in pixels (inclusive). Returns formats with height <= this value. Omit for responsive/fluid formats.' - ), - ] = None - min_width: Annotated[ - SchemaInt | None, - Field( - description='Minimum width in pixels (inclusive). Returns formats with width >= this value.' - ), - ] = None - min_height: Annotated[ - SchemaInt | None, - Field( - description='Minimum height in pixels (inclusive). Returns formats with height >= this value.' - ), - ] = None - is_responsive: Annotated[ - StrictBool | None, - Field( - description='Filter for responsive formats that adapt to container size. When true, returns formats without fixed dimensions.' - ), - ] = None - name_search: Annotated[ - str | None, Field(description='Search for formats by name (case-insensitive partial match)') - ] = None - wcag_level: Annotated[ - wcag_level_1.WcagLevel | None, - Field( - description='Filter to formats that meet at least this WCAG conformance level (A < AA < AAA)' - ), - ] = None - disclosure_positions: Annotated[ - list[disclosure_position.DisclosurePosition] | None, - Field( - description="Filter to formats that support all of these disclosure positions. When a format has disclosure_capabilities, match against those positions. Otherwise fall back to supported_disclosure_positions. Use to find formats compatible with a brief's compliance requirements.", - min_length=1, - ), - ] = None - disclosure_persistence: Annotated[ - list[disclosure_persistence_1.DisclosurePersistence] | None, - Field( - description='Filter to formats where each requested persistence mode is supported by at least one position in disclosure_capabilities. Different positions may satisfy different modes. Use to find formats compatible with jurisdiction-specific persistence requirements (e.g., continuous for EU AI Act).', - min_length=1, - ), - ] = None - output_format_ids: Annotated[ - list[format_id.FormatReferenceStructuredObject] | None, - Field( - deprecated=True, - description='**DEPRECATED in 3.1.** Discover build capability via `list_transformers` (filter its `output_format_ids`) instead — build capability is a property of transformers, not a relationship between formats. *Legacy:* filter to formats whose `output_format_ids` includes any of these format IDs.', - min_length=1, - ), - ] = None - input_format_ids: Annotated[ - list[format_id.FormatReferenceStructuredObject] | None, - Field( - deprecated=True, - description='**DEPRECATED in 3.1.** Discover build capability via `list_transformers` (filter its `input_format_ids`) instead. *Legacy:* filter to formats whose `input_format_ids` includes any of these format IDs.', - min_length=1, - ), - ] = None - include_pricing: Annotated[ - StrictBool | None, - Field( - deprecated=True, - description='**DEPRECATED in 3.1. Removed at 4.0.** Use `list_transformers` with `include_pricing` and `account` for transformation and generation pricing. *Legacy 3.x behavior:* include `pricing_options` on each returned format. Requires `account`; when false or omitted, pricing is not computed. Agents supporting the 3.1 format-pricing surface MUST continue to honor this field through 3.x.', - ), - ] = False - account: Annotated[ - account_ref.AccountReference | None, - Field( - deprecated=True, - description='**DEPRECATED for `list_creative_formats` in 3.1. Removed at 4.0.** Use the account-scoped `list_transformers` request instead. *Legacy 3.x behavior:* identifies the rate card used when `include_pricing` is true.', - ), - ] = None - pagination: pagination_request.PaginationRequest | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/creative/list_creative_formats_response.py b/src/adcp/types/generated_poc/creative/list_creative_formats_response.py deleted file mode 100644 index cbe0dfeee..000000000 --- a/src/adcp/types/generated_poc/creative/list_creative_formats_response.py +++ /dev/null @@ -1,60 +0,0 @@ -# generated by datamodel-codegen: -# filename: creative/list_creative_formats_response.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 -from ..core import format, pagination_response -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import creative_agent_capability - - -class CreativeAgent(AdCPBaseModel): - agent_url: Annotated[ - AnyUrl, - Field( - description="Base URL for the creative agent (e.g., 'https://reference.example.com', 'https://dco.example.com')." - ), - ] - agent_name: Annotated[ - str | None, Field(description='Human-readable name for the creative agent') - ] = None - capabilities: Annotated[ - list[creative_agent_capability.CreativeAgentCapability] | None, - Field(description='Capabilities this creative agent provides'), - ] = None - - -class ListCreativeFormatsResponseCreativeAgent(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - formats: Annotated[ - list[format.Format], - Field( - deprecated=True, - description='Deprecated named-format definitions projected for older 3.x callers. Canonical creative capabilities are declared by get_adcp_capabilities creative.supported_formats[].', - ), - ] - creative_agents: Annotated[ - list[CreativeAgent] | None, - Field( - deprecated=True, - description='Deprecated recursive discovery projection retained only for historical 3.x responses. New buyers use registry reverse lookup and direct get_adcp_capabilities confirmation.', - ), - ] = None - errors: Annotated[ - list[error.Error] | None, Field(description='Task-specific errors and warnings') - ] = None - pagination: pagination_response.PaginationResponse | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/creative/list_creatives_request.py b/src/adcp/types/generated_poc/creative/list_creatives_request.py deleted file mode 100644 index 5728a2bab..000000000 --- a/src/adcp/types/generated_poc/creative/list_creatives_request.py +++ /dev/null @@ -1,142 +0,0 @@ -# generated by datamodel-codegen: -# filename: creative/list_creatives_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictBool - -from ..core import account_ref -from ..core import context as context_1 -from ..core import creative_filters -from ..core import ext as ext_1 -from ..core import pagination_request -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import creative_sort_field, sort_direction - - -class AssignmentProjection(StrEnum): - all = 'all' - matching = 'matching' - - -class Field1(StrEnum): - creative_id = 'creative_id' - name = 'name' - format_id = 'format_id' - format_kind = 'format_kind' - format_option_ref = 'format_option_ref' - assets = 'assets' - status = 'status' - created_date = 'created_date' - updated_date = 'updated_date' - tags = 'tags' - rights = 'rights' - rights_attestation_evaluations = 'rights_attestation_evaluations' - localization = 'localization' - localization_unavailable = 'localization_unavailable' - assignments = 'assignments' - snapshot = 'snapshot' - items = 'items' - variables = 'variables' - concept = 'concept' - pricing_options = 'pricing_options' - - -class Sort(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - field: Annotated[ - creative_sort_field.CreativeSortField | None, Field(description='Field to sort by') - ] = creative_sort_field.CreativeSortField.created_date - direction: Annotated[ - sort_direction.SortDirection | None, Field(description='Sort direction') - ] = sort_direction.SortDirection.desc - - -class ListCreativesRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - filters: creative_filters.CreativeFilters | None = None - sort: Annotated[Sort | None, Field(description='Sorting parameters')] = None - pagination: pagination_request.PaginationRequest | None = None - include_assignments: Annotated[ - StrictBool | None, Field(description='Include package assignment information in response') - ] = True - assignment_projection: Annotated[ - AssignmentProjection | None, - Field( - description='Controls nested assigned_packages projection when assignments are included. all returns up to assignment_limit active assignments per creative. matching returns only assignments matching filters.indicator_types and requires that filter. Use matching for compact indicator discovery; get_media_buys remains the complete authoritative repair path when assignments_truncated is true.' - ), - ] = AssignmentProjection.all - assignment_limit: Annotated[ - SchemaInt | None, - Field( - description='Maximum assigned_packages rows returned per creative. Sellers MUST set assignments.assignments_truncated when additional qualifying rows exist.', - ge=1, - le=200, - ), - ] = 50 - include_snapshot: Annotated[ - StrictBool | None, - Field( - description='Include a lightweight delivery snapshot per creative (lifetime impressions and last-served date). For detailed performance analytics, use get_creative_delivery.' - ), - ] = False - include_items: Annotated[ - StrictBool | None, - Field(description='Include items for multi-asset formats like carousels and native ads'), - ] = False - include_variables: Annotated[ - StrictBool | None, - Field( - description='Include dynamic content variable definitions (DCO slots) for each creative' - ), - ] = False - include_pricing: Annotated[ - StrictBool | None, - Field( - description='Include pricing_options on each creative. Requires account to be provided. When false or omitted, pricing is not computed.' - ), - ] = False - include_purged: Annotated[ - StrictBool | None, - Field( - description="Include soft-purged creative tombstones in the result set. When true, creatives destroyed via `creative.purged` with `purge_kind: soft` surface as tombstone records carrying `purged: true`, `purged_at`, and the purge reason — within the seller's webhook activity retention window (30 days from `purged_at`, MUST match `webhook-activity-record` retention). Hard-purged creatives MUST NOT appear regardless of this flag. When false or omitted, the result set excludes all purged creatives — same default as today." - ), - ] = False - include_webhook_activity: Annotated[ - StrictBool | None, - Field( - description='Include recent webhook activity per creative. When true, each returned creative carries a `webhook_activity[]` array of the most recent fires scoped to that creative — `creative.status_changed` and `creative.purged` deliveries. Adoption of the `webhook_activity[]` pattern per `snapshot-and-log.mdx § Webhook activity log pattern`. Retention is 30 days from `completed_at` (MUST). Three-state presence applies: omitted = seller does not surface; `[]` = persists but no recent fires; non-empty = actual records.' - ), - ] = False - webhook_activity_limit: Annotated[ - SchemaInt | None, - Field( - description="Maximum number of `webhook_activity[]` records to return per creative. Only meaningful when `include_webhook_activity: true`. Sellers MUST respect the cap; structural enforcement is provided by the response schema's `maxItems: 200` on the array.", - ge=1, - le=200, - ), - ] = 50 - account: Annotated[ - account_ref.AccountReference | None, - Field( - description="Account reference for pricing and access. When provided with include_pricing, the agent returns pricing_options from this account's rate card on each creative." - ), - ] = None - fields: Annotated[ - list[Field1] | None, - Field( - description="Specific fields to include in response (omit for all fields). The 'concept' value returns both concept_id and concept_name. `format_id` is a deprecated 3.x compatibility projection; new integrations request `format_kind` and `format_option_ref`. Selecting localization automatically includes creative_id, status, assets, the selected format identity, and localization_unavailable when applicable. Selecting rights_attestation_evaluations automatically includes rights so each seller-produced result can be reconciled with the exact retained constraint and reference.", - min_length=1, - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/creative/list_creatives_response.py b/src/adcp/types/generated_poc/creative/list_creatives_response.py deleted file mode 100644 index f6b5ba91b..000000000 --- a/src/adcp/types/generated_poc/creative/list_creatives_response.py +++ /dev/null @@ -1,481 +0,0 @@ -# generated by datamodel-codegen: -# filename: creative/list_creatives_response.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from collections.abc import Sequence -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field, RootModel, StrictBool, StringConstraints, model_validator - -from ..core import account as account_1 -from ..core import canonical_format_kind -from ..core import context as context_1 -from ..core import ( - creative_approval_scope, - creative_assets, - creative_item, - creative_localization_readback, - creative_revision_id, - creative_variable, - error, -) -from ..core import ext as ext_1 -from ..core import format_id as format_id_1 -from ..core import format_option_ref as format_option_ref_1 -from ..core import indicator_scope, pagination_response -from ..core import representation_selection as representation_selection_1 -from ..core import ( - rights_attestation_evaluation, - rights_constraint, - vendor_pricing_option, - webhook_activity_record, -) -from ..core.assets import asset_union -from ..core.indicator_bearing import IndicatorBearingResourceState -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import creative_approval_status, creative_event_reason_code, creative_status -from ..enums import snapshot_unavailable_reason as snapshot_unavailable_reason_1 -from ..enums import sort_direction - - -class LocalizationUnavailable(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - errors: Annotated[list[error.Error], Field(min_length=1)] - retryable: Annotated[ - StrictBool, - Field( - description='Whether a later list_creatives call may recover without buyer mutation.' - ), - ] - - -class IndicatorTypesEvaluatedEnum(StrEnum): - creative_fatigue = 'creative_fatigue' - creative_quality_opportunity = 'creative_quality_opportunity' - creative_diversity_low = 'creative_diversity_low' - audience_saturation = 'audience_saturation' - inventory_shortfall_forecast = 'inventory_shortfall_forecast' - pacing_risk = 'pacing_risk' - budget_constrained = 'budget_constrained' - creative_fatigue_1 = 'creative_fatigue' - creative_quality_opportunity_1 = 'creative_quality_opportunity' - - -class Indicator(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - IndicatorTypesEvaluatedEnum, - Field( - description="A seller-asserted material risk or optimization opportunity that currently warrants buyer attention on an AdCP resource relationship. AdCP standardizes the broad meaning of each value for the protocol release; it does not standardize or version the seller's detection methodology or prescribe a universal remedy.", - title='Indicator Type', - ), - ] - detected_at: Annotated[ - AwareDatetime | None, - Field( - description='When the seller first detected the current uninterrupted occurrence of this indicator. Keep this value stable while the condition remains present. If the condition clears and is later detected again, use the new detection time. Optional because some upstream platforms expose current assessments without an original detection timestamp.' - ), - ] = None - scope: Annotated[ - list[indicator_scope.IndicatorScope] | None, - Field( - description='Optional narrower publisher or placement scope within the enclosing media buy, package, or package–creative assignment. Omit only when the seller evaluated and asserts the indicator across the whole enclosing resource/relationship. When partial indicators_evaluated_scope is declared, every returned indicator MUST include scope and every entry MUST fall within that coverage. This is scope, not source: the responding seller remains the source.', - min_length=1, - ), - ] = None - ext: Annotated[ - ext_1.ExtensionObject | None, - Field( - description='Seller- or provider-specific detail such as scores, thresholds, evaluation windows, methodology identifiers, or upstream attribution. Core consumers must not need ext to understand the broad meaning of type.' - ), - ] = None - - -class Snapshot(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - as_of: Annotated[ - AwareDatetime, Field(description='When this snapshot was captured by the platform') - ] - staleness_seconds: Annotated[ - SchemaInt, - Field( - description='Maximum age of this data in seconds. For example, 3600 means the data may be up to 1 hour old.', - ge=0, - ), - ] - impressions: Annotated[ - SchemaInt, - Field( - description='Lifetime impressions across all assignments. Not scoped to any date range.', - ge=0, - ), - ] - last_served: Annotated[ - AwareDatetime | None, - Field( - description='Last time this creative served an impression. Absent when the creative has never served.' - ), - ] = None - - -class Purge(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[ - Literal['soft'], - Field(description='Always `soft` on tombstones — hard purges do not surface on this read.'), - ] = 'soft' - at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the creative was destroyed. Matches the `purged_at` field on the corresponding `creative.purged` webhook fire.' - ), - ] - reason_code: Annotated[ - creative_event_reason_code.CreativeEventReasonCode, - Field( - description='Categorical reason for the purge. Matches the value the seller emitted on the corresponding `creative.purged` webhook fire.' - ), - ] - - -class StatusSummary(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - processing: Annotated[ - SchemaInt | None, Field(description='Number of creatives being processed', ge=0) - ] = None - approved: Annotated[ - SchemaInt | None, Field(description='Number of approved creatives', ge=0) - ] = None - pending_review: Annotated[ - SchemaInt | None, Field(description='Number of creatives pending review', ge=0) - ] = None - rejected: Annotated[ - SchemaInt | None, Field(description='Number of rejected creatives', ge=0) - ] = None - archived: Annotated[ - SchemaInt | None, Field(description='Number of archived creatives', ge=0) - ] = None - - -class SortApplied(AdCPBaseModel): - field: str | None = None - direction: sort_direction.SortDirection | None = None - - -class QuerySummary(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - total_matching: Annotated[ - SchemaInt, - Field(description='Total number of creatives matching filters (across all pages)', ge=0), - ] - returned: Annotated[ - SchemaInt, Field(description='Number of creatives returned in this response', ge=0) - ] - filters_applied: Annotated[ - list[str] | None, Field(description='List of filters that were applied to the query') - ] = None - sort_applied: Annotated[ - SortApplied | None, Field(description='Sort order that was applied') - ] = None - - -class AssignedPackage(IndicatorBearingResourceState): - model_config = ConfigDict( - extra='allow', - ) - indicator_types_evaluated: Annotated[ - list[IndicatorTypesEvaluatedEnum] | None, - Field( - description='Indicator types covered by this snapshot. Required whenever indicators is present. Types omitted from this list remain unknown even when indicators is empty. Every returned indicator.type MUST appear in this list.', - min_length=1, - ), - ] = None - indicators: Annotated[ - list[Indicator] | None, - Field( - description='Current seller assertions for the indicator types and publisher/placement coverage named by the sibling evaluation fields. Omitted means unknown or not evaluated. A present empty array means evaluated with no current assertion for indicator_types_evaluated in the evaluated scope.' - ), - ] = None - package_id: Annotated[str, Field(description='Package identifier')] - media_buy_id: Annotated[ - str | None, - Field( - description='Media buy containing this package. A seller advertising list_creatives in media_buy.relationship_notifications.projection_tasks MUST include this field on every assignment row, including rows where indicators is omitted as unknown, so buyers can key and reread the relationship unambiguously when package IDs are reused across media buys.' - ), - ] = None - assigned_date: Annotated[AwareDatetime, Field(description='When this assignment was created')] - approval_status: Annotated[ - creative_approval_status.CreativeApprovalStatus | None, - Field( - description='Aggregate approval state for this creative in this package assignment. This mirrors the same relationship in get_media_buys. Sellers advertising list_creatives as an indicator projection task MUST include it; partially_approved requires approval_scopes.' - ), - ] = None - rejection_reason: Annotated[ - str | None, - Field( - description='Human-readable explanation when approval_status is rejected. Mirrors get_media_buys for the same relationship.' - ), - ] = None - approval_scopes: Annotated[ - list[creative_approval_scope.ScopedCreativeApproval] | None, - Field( - description='Complete, disjoint publisher/placement approval partition when approval_status is partially_approved. A normalized scope appears once. For one publisher, use either one publisher-wide row or placement-specific rows, never both. Omit when one approval_status applies uniformly to the whole assignment. The same scoped outcomes are mirrored on get_media_buys.', - min_length=2, - ), - ] = None - indicators_as_of: Annotated[ - AwareDatetime | None, - Field( - description='When the seller last completed the evaluation represented by indicators for this relationship. Required whenever indicators is present, including an empty array.' - ), - ] = None - indicators_evaluated_scope: Annotated[ - list[indicator_scope.IndicatorScope] | None, - Field( - description='Optional publisher or placement scopes covered by this evaluation. Omit when indicators covers the whole package–creative assignment. When present, scopes not listed remain unknown; every returned indicator.scope entry MUST be contained by this set.', - min_length=1, - ), - ] = None - - -class Assignments(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - assignment_count: Annotated[ - SchemaInt, Field(description='Total number of active package assignments', ge=0) - ] - returned_assignment_count: Annotated[ - SchemaInt | None, - Field( - description='Number of rows present in assigned_packages for this response.', - ge=0, - le=200, - ), - ] = None - matching_assignment_count: Annotated[ - SchemaInt | None, - Field( - description='Total active assignments matching filters.indicator_types. MUST be present exactly when assignment_projection was matching. May exceed returned_assignment_count.', - ge=0, - ), - ] = None - assignments_truncated: Annotated[ - StrictBool | None, - Field( - description='True exactly when more qualifying assignments exist than were returned under assignment_limit. Qualifying means assignment_count for the all projection and matching_assignment_count for the matching projection. Buyers needing complete state repair through get_media_buys.' - ), - ] = None - assigned_packages: Annotated[ - list[AssignedPackage] | None, - Field( - description='Bounded package assignment projection. Under assignment_projection: matching, contains only assignments carrying a requested indicator type; otherwise contains active assignments up to assignment_limit. The response ceiling is enforced via verifier_constraints rather than maxItems, so payloads from 3.1 sellers remain schema-valid.' - ), - ] = None - - -class Assets(RootModel[list[asset_union.AssetVariant]]): - root: Annotated[list[asset_union.AssetVariant], Field(min_length=1)] - - -class Creative(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - creative_id: Annotated[str, Field(description='Unique identifier for the creative')] - revision_id: Annotated[ - creative_revision_id.CreativeRevisionId | None, - Field( - description='Current buyer-authored input revision for this creative. Present when the current effective content has revision identity and the seller advertises creative.supports_revisions; omitted after an accepted content-bearing legacy update without revision_id.' - ), - ] = None - representation_selection: Annotated[ - representation_selection_1.RepresentationSelection | None, - Field( - description='Exact source-set and selected-output lineage retained when the current stored creative was resolved from a CreativeRepresentationSet.' - ), - ] = None - account: Annotated[ - account_1.Account | None, Field(description='Account that owns this creative') - ] = None - name: Annotated[str, Field(description='Human-readable creative name')] - format_id: Annotated[ - format_id_1.FormatReferenceStructuredObject | None, - Field( - deprecated=True, - description='**DEPRECATED in 3.2.** Legacy named-format path. New listed creatives use `format_kind` and optional `format_option_ref`.', - ), - ] = None - format_kind: Annotated[ - canonical_format_kind.CanonicalFormatKind | None, - Field( - description='Canonical 3.2 path. The canonical format kind this creative targets. Mutually exclusive with deprecated `format_id`.' - ), - ] = None - format_option_ref: Annotated[ - format_option_ref_1.FormatOptionReference | None, - Field( - description='Optional reference to the concrete canonical format option this creative targets. Required when `format_kind` alone is ambiguous in the enclosing product context.' - ), - ] = None - status: Annotated[ - creative_status.CreativeStatus, Field(description='Current approval status of the creative') - ] - created_date: Annotated[AwareDatetime, Field(description='When the creative was created')] - updated_date: Annotated[AwareDatetime, Field(description='When the creative was last modified')] - assets: Annotated[ - dict[Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], asset_union.AssetVariant | Assets] | None, - Field( - description='Assets for this creative, keyed by asset_id. Each slot value is either a single asset object or an array of asset objects (for slots with `min`/`max > 1`). Each asset value carries an `asset_type` discriminator that selects the matching asset schema.' - ), - ] = None - component_assets: Annotated[ - dict[Annotated[str, StringConstraints(pattern=r'^[a-z][a-z0-9_]*$')], creative_assets.CreativeAssets] | None, - Field( - description='Preserved component-addressed asset maps for `coordinated_placements`, keyed by coordinated component ID.' - ), - ] = None - localization: Annotated[ - creative_localization_readback.CreativeLocalizationReadback | None, - Field( - description='Authoritative exact materialized locale-variant state. Present for localized creatives when complete. The enclosing creative status is the single review lifecycle for all variants.' - ), - ] = None - localization_unavailable: Annotated[ - LocalizationUnavailable | None, - Field( - description='Per-creative fail-closed state returned instead of localization when the seller knows the creative is localized but cannot construct complete exact readback. The creative remains in this page and counts toward query_summary.returned and pagination; buyers may continue using the base creative fields but MUST NOT infer locale eligibility.' - ), - ] = None - tags: Annotated[ - list[str] | None, Field(description='User-defined tags for organization and searchability') - ] = None - rights: Annotated[ - list[rights_constraint.RightsConstraint] | None, - Field( - description="Exact rights constraints retained from the buyer's creative submission. Presence is presentation readback, not proof that the seller accepted or verified the rights.", - min_length=1, - ), - ] = None - rights_attestation_evaluations: Annotated[ - list[rights_attestation_evaluation.RightsAttestationEvaluation] | None, - Field( - description="Complete seller-produced verifier-of-record results for retained rights references. Buyers MUST ignore any evaluation they originally supplied and rely only on this seller readback for this seller's eligibility decision. This array has no independent item ceiling because rights is not capped; under a required policy every applicable retained constraint needs a corresponding current verified result.", - min_length=1, - ), - ] = None - concept_id: Annotated[ - str | None, - Field( - description='Creative concept this creative belongs to. Concepts group related creatives across sizes and formats.' - ), - ] = None - concept_name: Annotated[str | None, Field(description='Human-readable concept name')] = None - variables: Annotated[ - list[creative_variable.CreativeVariable] | None, - Field( - description='Dynamic content variables (DCO slots) for this creative. Included when include_variables=true.' - ), - ] = None - assignments: Annotated[ - Assignments | None, - Field(description='Current package assignments (included when include_assignments=true)'), - ] = None - snapshot: Annotated[ - Snapshot | None, - Field( - description='Lightweight delivery snapshot (included when include_snapshot=true). For detailed performance analytics, use get_creative_delivery.' - ), - ] = None - snapshot_unavailable_reason: Annotated[ - snapshot_unavailable_reason_1.SnapshotUnavailableReason | None, - Field( - description='Machine-readable reason the snapshot is omitted. Present only when include_snapshot was true and snapshot data is unavailable for this creative.' - ), - ] = None - items: Annotated[ - list[creative_item.CreativeItem] | None, - Field( - description='Items for multi-asset formats like carousels and native ads (included when include_items=true)' - ), - ] = None - pricing_options: Annotated[ - list[vendor_pricing_option.VendorPricingOption] | None, - Field( - description='Pricing options for using this creative (serving, delivery). Used by ad servers and library agents. Transformation agents expose build pricing on canonical transformer.pricing_options entries from list_transformers instead. Present when include_pricing=true and account provided. The buyer passes the applied pricing_option_id in report_usage.', - min_length=1, - ), - ] = None - purge: Annotated[ - Purge | None, - Field( - description="Tombstone block — present only when this record is a soft-purged creative surfaced via `include_purged: true`. The record's `status` field reflects the last status before purge (frozen — buyers MUST treat the creative as gone; assignments, snapshot, and serving operations no longer apply). Tombstones surface for the seller's webhook activity retention window (30 days from `purge.at`). Hard purges (`purge_kind: hard` on the webhook) do not surface on this read — the [`creative.purged`](https://adcontextprotocol.org/schemas/v3/creative/creative-purged-webhook.json) webhook is the only signal." - ), - ] = None - webhook_activity: Annotated[ - list[webhook_activity_record.WebhookActivityRecord] | None, - Field( - description='Recent webhook fires scoped to this creative — creative.status_changed, creative.purged, creative.assignment_changed, and assignment-level indicators.changed deliveries. Present only when include_webhook_activity is true. Account-anchored records include subscriber_id; the parent creative_id disambiguates the record. Retention: 30 days from completed_at. See snapshot-and-log.mdx § Webhook activity log pattern.', - max_length=200, - ), - ] = None - - - @model_validator(mode='after') - def _validate_format_reference_xor(self) -> Creative: - if (self.format_id is None) == (self.format_kind is None): - raise ValueError('exactly one of format_id and format_kind is required') - return self - - -Creatives = Creative -Creatives1 = Creative - - -class ListCreativesResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - query_summary: Annotated[ - QuerySummary, Field(description='Summary of the query that was executed') - ] - pagination: pagination_response.PaginationResponse - creatives: Annotated[ - Sequence[Creative], Field(description='Array of creative assets matching the query') - ] - format_summary: Annotated[ - dict[Annotated[str, StringConstraints(pattern=r'^[a-zA-Z0-9_-]+$')], SchemaInt] | None, - Field( - description='Breakdown of creatives by canonical format kind. Keys SHOULD be `format_kind` values; an implementation may append a stable option suffix when separate product or publisher options must be distinguished.' - ), - ] = None - status_summary: Annotated[ - StatusSummary | None, Field(description='Breakdown of creatives by status') - ] = None - errors: Annotated[ - list[error.Error] | None, - Field(description='Task-specific errors (e.g., invalid filters, account not found)'), - ] = None - sandbox: Annotated[ - StrictBool | None, - Field(description='When true, this response contains simulated data from sandbox mode.'), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/creative/list_transformers_request.py b/src/adcp/types/generated_poc/creative/list_transformers_request.py deleted file mode 100644 index ef82144c3..000000000 --- a/src/adcp/types/generated_poc/creative/list_transformers_request.py +++ /dev/null @@ -1,112 +0,0 @@ -# generated by datamodel-codegen: -# filename: creative/list_transformers_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, StrictBool - -from ..core import account_ref, canonical_format_kind -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core import format_id, pagination_request -from ..core.version_envelope import AdcpVersionEnvelope - - -class OutputCapabilityId(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[a-zA-Z0-9_-]+$'} - - -class ExpandPaginationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - transformer_id: Annotated[ - str, Field(description='The transformer whose param options to page.') - ] - field: Annotated[str, Field(description='The param `field` to page.')] - options_cursor: Annotated[ - str, Field(description="Opaque cursor from that param's prior `params[].options_cursor`.") - ] - - -class ListTransformersRequestCreativeAgent(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - transformer_ids: Annotated[ - list[str] | None, - Field(description='Return only these specific transformer IDs.', min_length=1), - ] = None - input_format_ids: Annotated[ - list[format_id.FormatReferenceStructuredObject] | None, - Field( - deprecated=True, - description='**DEPRECATED in 3.2.** Filter by legacy named input formats. Use input_format_kinds.', - min_length=1, - ), - ] = None - output_format_ids: Annotated[ - list[format_id.FormatReferenceStructuredObject] | None, - Field( - deprecated=True, - description='**DEPRECATED in 3.2.** Filter by legacy named output formats. Use output_capability_ids.', - min_length=1, - ), - ] = None - input_format_kinds: Annotated[ - list[canonical_format_kind.CanonicalFormatKind] | None, - Field( - description='Filter to transformers whose canonical input_formats include any of these canonical format kinds.', - min_length=1, - ), - ] = None - output_capability_ids: Annotated[ - list[OutputCapabilityId] | None, - Field( - description='Filter to transformers that can produce any of these canonical creative.supported_formats capability IDs.', - min_length=1, - ), - ] = None - name_search: Annotated[ - str | None, - Field(description='Search transformers by name (case-insensitive partial match).'), - ] = None - brief: Annotated[ - str | None, - Field( - description="Natural-language brief used to rank and filter transformers (and their enumerable option values when expanded) — e.g. 'warm female Spanish-language voiceover'. Curates to intent rather than returning the full set, the way get_products curates inventory." - ), - ] = None - expand_params: Annotated[ - list[str] | None, - Field( - description="Param `field` names for which to return the FIRST page of account-scoped option VALUES inline on each transformer's `params[].options[]`. Omit to return param descriptors without enumerated values (the lean default). When a param's options are truncated, its `params[].options_cursor` is set — fetch the next page via `expand_pagination` (below).", - min_length=1, - ), - ] = None - expand_pagination: Annotated[ - list[ExpandPaginationItem] | None, - Field( - description="Fetch the NEXT page of a specific param's account-scoped options, using the `options_cursor` a prior response returned for that `(transformer, param)`. Scoped per `(transformer_id, field)` so multiple params can be paged independently. Use this instead of `expand_params` once you hold a cursor.", - min_length=1, - ), - ] = None - include_pricing: Annotated[ - StrictBool | None, - Field(description='Include `pricing_options` on each transformer. Requires `account`.'), - ] = False - account: Annotated[ - account_ref.AccountReference | None, - Field( - description='Account reference. Transformers are account-scoped — the returned set, the enumerable option values, and (with include_pricing) the rate card are all resolved for this credential.' - ), - ] = None - pagination: pagination_request.PaginationRequest | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/creative/list_transformers_response.py b/src/adcp/types/generated_poc/creative/list_transformers_response.py deleted file mode 100644 index 2f8ebb440..000000000 --- a/src/adcp/types/generated_poc/creative/list_transformers_response.py +++ /dev/null @@ -1,32 +0,0 @@ -# generated by datamodel-codegen: -# filename: creative/list_transformers_response.json -# timestamp: 2026-06-04T19:44:00+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import ConfigDict, Field - -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 -from ..core import pagination_response, transformer -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope - - -class ListTransformersResponseCreativeAgent(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - transformers: Annotated[ - list[transformer.Transformer], - Field(description='Transformer descriptors matching the query.'), - ] - errors: Annotated[ - list[error.Error] | None, Field(description='Task-specific errors and warnings.') - ] = None - pagination: pagination_response.PaginationResponse | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/creative/preview_creative_request.py b/src/adcp/types/generated_poc/creative/preview_creative_request.py deleted file mode 100644 index ab5159fbf..000000000 --- a/src/adcp/types/generated_poc/creative/preview_creative_request.py +++ /dev/null @@ -1,210 +0,0 @@ -# generated by datamodel-codegen: -# filename: creative/preview_creative_request.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictBool - -from ..core import context as context_1 -from ..core import creative_manifest as creative_manifest_1 -from ..core import ext as ext_1 -from ..core import format_id as format_id_1 -from ..core import push_notification_config as push_notification_config_1 -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import creative_quality, preview_output_format - - -class RequestType(StrEnum): - single = 'single' - batch = 'batch' - variant = 'variant' - - -class Input(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Human-readable name for this input set (e.g., 'Sunny morning on mobile', 'Evening podcast ad', 'Desktop dark mode')" - ), - ] - macros: Annotated[ - dict[str, str] | None, - Field( - description="Macro values to use for this preview. Supports all universal macros from the format's supported_macros list." - ), - ] = None - context_description: Annotated[ - str | None, - Field( - description="Natural language description of the context for AI-generated content (e.g., 'User just searched for running shoes', 'Podcast discussing weather patterns')" - ), - ] = None - - -class Input10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[str, Field(description='Human-readable name for this input set')] - macros: Annotated[ - dict[str, str] | None, Field(description='Macro values to use for this preview') - ] = None - context_description: Annotated[ - str | None, - Field(description='Natural language description of the context for AI-generated content'), - ] = None - - -class Request(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - target_capability_id: Annotated[ - str | None, - Field( - description='Canonical preview-operation selector for this batch item. Overrides the batch-level target_capability_id and MUST identify an advertised capability whose operations contains preview. If neither item nor batch supplies one, renderer inference is permitted only for a unique compatible preview capability.', - pattern='^[a-zA-Z0-9_-]+$', - ), - ] = None - format_id: Annotated[ - format_id_1.FormatReferenceStructuredObject | None, - Field( - deprecated=True, - description='**DEPRECATED in 3.2.** Legacy named-format preview route. Use target_capability_id plus the canonical identity in creative_manifest.', - ), - ] = None - creative_manifest: Annotated[ - creative_manifest_1.CreativeManifest | None, - Field(description='Complete creative manifest with all required assets.'), - ] = None - creative_id: Annotated[ - str | None, - Field( - description='Creative-library identifier. Use instead of creative_manifest to preview a stored canonical creative.' - ), - ] = None - inputs: Annotated[ - list[Input10] | None, - Field( - description='Array of input sets for generating multiple preview variants', min_length=1 - ), - ] = None - template_id: Annotated[ - str | None, Field(description='Specific template ID for custom format rendering') - ] = None - quality: Annotated[ - creative_quality.CreativeQuality | None, - Field(description='Render quality for this preview. Overrides batch-level default.'), - ] = None - output_format: Annotated[ - preview_output_format.PreviewOutputFormat | None, - Field(description='Output format for this preview. Overrides batch-level default.'), - ] = preview_output_format.PreviewOutputFormat.url - item_limit: Annotated[ - SchemaInt | None, - Field(description='Maximum number of catalog items to render in this preview.', ge=1), - ] = None - - -class PreviewCreativeRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - request_type: Annotated[ - RequestType, - Field( - description="Preview mode. 'single' previews one creative manifest. 'batch' previews multiple creatives in one call. 'variant' replays a post-flight variant by ID." - ), - ] - creative_manifest: Annotated[ - creative_manifest_1.CreativeManifest | None, - Field( - description='Complete creative manifest with all required assets for the format. In single mode, provide exactly one of creative_manifest or creative_id. Also accepted per item in batch mode.' - ), - ] = None - target_capability_id: Annotated[ - str | None, - Field( - description="Canonical preview-operation selector. Identifies one get_adcp_capabilities creative.supported_formats[].capability_id entry whose operations contains preview. In single mode it selects the renderer for this request; in batch mode it is the default for items that omit their own target_capability_id. When omitted, the agent MAY resolve the renderer only if exactly one advertised preview capability satisfies the manifest's canonical declaration; zero matches or multiple matches MUST be rejected with FORMAT_NOT_SUPPORTED rather than choosing nondeterministically. Mutually exclusive with deprecated format_id.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] = None - format_id: Annotated[ - format_id_1.FormatReferenceStructuredObject | None, - Field( - deprecated=True, - description='**DEPRECATED in 3.2.** Legacy named-format preview route. New requests select an advertised preview renderer with target_capability_id and carry portable format identity in creative_manifest_1.format_kind plus optional creative_manifest_1.format_option_ref.', - ), - ] = None - inputs: Annotated[ - list[Input] | None, - Field( - description='Array of input sets for generating multiple preview variants. Each input set defines macros and context values for one preview rendering. Used in single mode.', - min_length=1, - ), - ] = None - template_id: Annotated[ - str | None, - Field(description='Specific template ID for custom format rendering. Used in single mode.'), - ] = None - quality: Annotated[ - creative_quality.CreativeQuality | None, - Field( - description="Render quality. 'draft' produces fast, lower-fidelity renderings. 'production' produces full-quality renderings. In batch mode, sets the default for all requests (individual items can override)." - ), - ] = None - output_format: Annotated[ - preview_output_format.PreviewOutputFormat | None, - Field( - description="Output format. 'url' returns preview_url (iframe-embeddable URL), 'html' returns preview_html (raw HTML). In batch mode, sets the default for all requests (individual items can override). Default: 'url'." - ), - ] = preview_output_format.PreviewOutputFormat.url - item_limit: Annotated[ - SchemaInt | None, - Field( - description='Maximum number of catalog items to render per preview variant. Used in single mode. Creative agents SHOULD default to a reasonable sample when omitted and the catalog is large.', - ge=1, - ), - ] = None - requests: Annotated[ - list[Request] | None, - Field( - description="Array of preview requests (1-50 items). Required when request_type is 'batch'. Each item follows the single request structure.", - max_length=50, - min_length=1, - ), - ] = None - variant_id: Annotated[ - str | None, - Field( - description="Agent-assigned AdCP served-execution identifier from get_creative_delivery. Required when request_type is 'variant'. It is agent-unique when the source agent advertises creative.supports_revisions; for legacy agents the published scope remains agent plus creative, and callers SHOULD also send creative_id to disambiguate reused values." - ), - ] = None - creative_id: Annotated[ - str | None, - Field( - description='Creative-library identifier. In single mode, previews the stored canonical creative without requiring the caller to reconstruct its manifest. Also available as context in variant mode.' - ), - ] = None - allow_async: Annotated[ - StrictBool | None, - Field( - description="Opt in to an asynchronous preview response. When true, the creative agent MAY return status 'submitted' with a task_id only when rendering has been handed to a queue or external renderer and will continue after the request connection is released. Active processing on an open connection uses working progress instead. The buyer polls get_task_status for completion. When false or absent, the agent MUST return a synchronous preview response or a terminal protocol error; it MUST NOT return the submitted shape. This field applies to preview_creative only; build_creative already defines its own async lifecycle." - ), - ] = False - push_notification_config: Annotated[ - push_notification_config_1.PushNotificationConfig | None, - Field( - description='Optional webhook configuration for terminal completion/failure notifications when allow_async is true and preview_creative returns a submitted task envelope. Submitted tasks remain pollable through get_task_status whether or not this field is present. If the agent accepts this configuration and returns submitted, it MUST deliver at least the terminal notification; if it cannot honor the webhook, it MUST return a structured error. Presence of this field alone MUST NOT cause asynchronous execution.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/creative/preview_creative_response.py b/src/adcp/types/generated_poc/creative/preview_creative_response.py deleted file mode 100644 index c565a5f68..000000000 --- a/src/adcp/types/generated_poc/creative/preview_creative_response.py +++ /dev/null @@ -1,129 +0,0 @@ -# generated by datamodel-codegen: -# filename: creative/preview_creative_response.json -# timestamp: preserved-by-post-generate-fixes - -from __future__ import annotations - -from typing import Annotated, Literal, TypeAlias - -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StringConstraints - -from ..core.version_envelope import AdcpVersionEnvelope -from . import preview_render as preview_render_1 -from ..core import context as context_1 -from ..core import creative_manifest as creative_manifest_1 -from ..core import error as error_1 -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..enums import creative_quality as creative_quality_1 -from ..enums import task_status as task_status_1 - - -class Input(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - name: str - macros: dict[str, str] | None = None - context_description: str | None = None - - -class Preview(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - preview_id: str - renders: Annotated[list[preview_render_1.PreviewRender], Field(min_length=1)] - input: Input - - -class Input2(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - name: str - macros: dict[str, str] | None = None - context_description: str | None = None - - -class Preview2(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - preview_id: str - renders: Annotated[list[preview_render_1.PreviewRender], Field(min_length=1)] - input: Input2 - - -class Response(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - previews: Annotated[list[Preview2], Field(min_length=1)] - interactive_url: AnyUrl | None = None - expires_at: AwareDatetime | None = None - - -class Result(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - success: bool - creative_id: str - quality_used: creative_quality_1.CreativeQuality | None = None - response: Response | None = None - errors: Annotated[list[error_1.Error], Field(min_length=1)] | None = None - - -class Preview3(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - preview_id: str - renders: Annotated[list[preview_render_1.PreviewRender], Field(min_length=1)] - - -class PreviewCreativeResponse1(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - response_type: Literal['single'] = 'single' - previews: Annotated[list[Preview], Field(min_length=1)] - quality_used: creative_quality_1.CreativeQuality | None = None - interactive_url: AnyUrl | None = None - expires_at: AwareDatetime | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class PreviewCreativeResponse2(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - response_type: Literal['batch'] = 'batch' - results: Annotated[list[Result], Field(min_length=1)] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class PreviewCreativeResponse3(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - response_type: Literal['variant'] = 'variant' - variant_id: str - creative_id: str | None = None - previews: Annotated[list[Preview3], Field(min_length=1)] - manifest: creative_manifest_1.CreativeManifest | None = None - expires_at: AwareDatetime | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class PreviewCreativeResponse4(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow', validate_default=True) - response_type: Literal['submitted'] = 'submitted' - status: Literal[task_status_1.TaskStatus.submitted] = task_status_1.TaskStatus.submitted - task_id: str - message: Annotated[str, StringConstraints(max_length=2000)] | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -PreviewCreativeResponse: TypeAlias = PreviewCreativeResponse1 | PreviewCreativeResponse2 | PreviewCreativeResponse3 | PreviewCreativeResponse4 - - -__all__ = [ - 'PreviewCreativeResponse', - 'PreviewCreativeResponse1', - 'PreviewCreativeResponse2', - 'PreviewCreativeResponse3', - 'PreviewCreativeResponse4', - 'Input', - 'Input2', - 'Preview', - 'Preview2', - 'Preview3', - 'Response', - 'Result', -] diff --git a/src/adcp/types/generated_poc/creative/preview_render.py b/src/adcp/types/generated_poc/creative/preview_render.py deleted file mode 100644 index 30d08fbbb..000000000 --- a/src/adcp/types/generated_poc/creative/preview_render.py +++ /dev/null @@ -1,165 +0,0 @@ -# generated by datamodel-codegen: -# filename: creative/preview_render.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, StrictBool, StrictFloat - -from ..core import preview_renderer_metadata - - -class Dimensions(AdCPBaseModel): - width: Annotated[StrictFloat, Field(ge=0.0)] - height: Annotated[StrictFloat, Field(ge=0.0)] - - -class Embedding(AdCPBaseModel): - recommended_sandbox: Annotated[ - Literal[''], - Field( - description='Empty iframe sandbox token set. Provider metadata MUST NOT grant scripts, same-origin, navigation, popups, forms, downloads, or other capabilities.' - ), - ] = '' - requires_https: Annotated[ - StrictBool | None, - Field(description='Whether this output requires HTTPS for secure embedding'), - ] = None - supports_fullscreen: Annotated[ - StrictBool | None, Field(description='Whether this output supports fullscreen mode') - ] = None - csp_policy: Annotated[ - str | None, Field(description='Content Security Policy requirements for embedding') - ] = None - - -class PreviewRender1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - render_id: Annotated[ - str, Field(description='Unique identifier for this rendered piece within the variant') - ] - output_format: Annotated[ - Literal['url'], Field(description='Discriminator indicating preview_url is provided') - ] = 'url' - preview_url: Annotated[ - AnyUrl, - Field( - description='Untrusted URL to an HTML page that renders this piece. Consumers MUST load it only in a cross-origin iframe with an empty sandbox token set and a caller-enforced restrictive CSP. Provider embedding metadata is advisory and MUST NOT loosen that policy.' - ), - ] - role: Annotated[ - str, - Field( - description="Semantic role of this rendered piece. Use 'primary' for main content, 'companion' for associated banners, descriptive strings for device variants or custom roles." - ), - ] - dimensions: Annotated[ - Dimensions | None, Field(description='Dimensions for this rendered piece') - ] = None - embedding: Annotated[ - Embedding | None, - Field(description='Optional security and embedding metadata for safe iframe integration'), - ] = None - renderer: Annotated[ - preview_renderer_metadata.PreviewRendererMetadata | None, - Field( - description='Optional renderer implementation and safety metadata for audit and reproducibility. Authority is still resolved from capability discovery and placement delegation.' - ), - ] = None - - -class PreviewRender2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - render_id: Annotated[ - str, Field(description='Unique identifier for this rendered piece within the variant') - ] - output_format: Annotated[ - Literal['html'], Field(description='Discriminator indicating preview_html is provided') - ] = 'html' - preview_html: Annotated[ - str, - Field( - description='Untrusted HTML. Consumers MUST NOT inject it into the host DOM. Render only as iframe srcdoc with an empty sandbox token set and a caller-enforced restrictive CSP; provider embedding metadata is advisory and MUST NOT loosen that policy.' - ), - ] - role: Annotated[ - str, - Field( - description="Semantic role of this rendered piece. Use 'primary' for main content, 'companion' for associated banners, descriptive strings for device variants or custom roles." - ), - ] - dimensions: Annotated[ - Dimensions | None, Field(description='Dimensions for this rendered piece') - ] = None - embedding: Annotated[ - Embedding | None, Field(description='Optional security and embedding metadata') - ] = None - renderer: Annotated[ - preview_renderer_metadata.PreviewRendererMetadata | None, - Field( - description='Optional renderer implementation and safety metadata for audit and reproducibility. Authority is still resolved from capability discovery and placement delegation.' - ), - ] = None - - -class PreviewRender3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - render_id: Annotated[ - str, Field(description='Unique identifier for this rendered piece within the variant') - ] - output_format: Annotated[ - Literal['both'], - Field( - description='Discriminator indicating both preview_url and preview_html are provided' - ), - ] = 'both' - preview_url: Annotated[ - AnyUrl, - Field( - description='Untrusted URL to an HTML page that renders this piece. Consumers MUST load it only in a cross-origin iframe with an empty sandbox token set and a caller-enforced restrictive CSP. Provider embedding metadata is advisory and MUST NOT loosen that policy.' - ), - ] - preview_html: Annotated[ - str, - Field( - description='Untrusted HTML. Consumers MUST NOT inject it into the host DOM. Render only as iframe srcdoc with an empty sandbox token set and a caller-enforced restrictive CSP; provider embedding metadata is advisory and MUST NOT loosen that policy.' - ), - ] - role: Annotated[ - str, - Field( - description="Semantic role of this rendered piece. Use 'primary' for main content, 'companion' for associated banners, descriptive strings for device variants or custom roles." - ), - ] - dimensions: Annotated[ - Dimensions | None, Field(description='Dimensions for this rendered piece') - ] = None - embedding: Annotated[ - Embedding | None, - Field(description='Optional security and embedding metadata for safe iframe integration'), - ] = None - renderer: Annotated[ - preview_renderer_metadata.PreviewRendererMetadata | None, - Field( - description='Optional renderer implementation and safety metadata for audit and reproducibility. Authority is still resolved from capability discovery and placement delegation.' - ), - ] = None - - -PreviewRender = Annotated[ - PreviewRender1 | PreviewRender2 | PreviewRender3, - Field( - description='A single rendered piece of a creative preview with discriminated output format', - discriminator='output_format', - title='Preview Render', - ), -] diff --git a/src/adcp/types/generated_poc/creative/sync_creatives_async_response_input_required.py b/src/adcp/types/generated_poc/creative/sync_creatives_async_response_input_required.py deleted file mode 100644 index 64c70998f..000000000 --- a/src/adcp/types/generated_poc/creative/sync_creatives_async_response_input_required.py +++ /dev/null @@ -1,31 +0,0 @@ -# generated by datamodel-codegen: -# filename: creative/sync_creatives_async_response_input_required.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..core import context as context_1 -from ..core import ext as ext_1 - - -class Reason(StrEnum): - APPROVAL_REQUIRED = 'APPROVAL_REQUIRED' - ASSET_CONFIRMATION = 'ASSET_CONFIRMATION' - FORMAT_CLARIFICATION = 'FORMAT_CLARIFICATION' - - -class SyncCreativesInputRequired(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - reason: Annotated[ - Reason | None, Field(description='Reason code indicating why buyer input is needed') - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/creative/sync_creatives_async_response_submitted.py b/src/adcp/types/generated_poc/creative/sync_creatives_async_response_submitted.py deleted file mode 100644 index 15a54bc6d..000000000 --- a/src/adcp/types/generated_poc/creative/sync_creatives_async_response_submitted.py +++ /dev/null @@ -1,47 +0,0 @@ -# generated by datamodel-codegen: -# filename: creative/sync_creatives_async_response_submitted.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 - - -class SyncCreativesSubmitted(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Literal['submitted'], - Field( - description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, whose creatives array carries per-item approval state via CreativeStatus. See task-status.json for the full task-status enum.' - ), - ] = 'submitted' - task_id: Annotated[ - str, - Field( - description='Task handle the buyer uses with get_task_status (or the legacy AdCP tasks/get alias), and that the seller references on push-notification callbacks. The creatives array is issued on the completion artifact, not here. This AdCP application-layer handle remains the snake_case task_id in every transport payload and is distinct from any transport-native A2A Task id.' - ), - ] - message: Annotated[ - str | None, - Field( - description="Optional human-readable explanation of why the task is submitted — e.g., 'Batch ingestion queued; typical turnaround 15-30 minutes.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", - max_length=2000, - ), - ] = None - errors: Annotated[ - list[error.Error] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/creative/sync_creatives_async_response_working.py b/src/adcp/types/generated_poc/creative/sync_creatives_async_response_working.py deleted file mode 100644 index 24172dac3..000000000 --- a/src/adcp/types/generated_poc/creative/sync_creatives_async_response_working.py +++ /dev/null @@ -1,37 +0,0 @@ -# generated by datamodel-codegen: -# filename: creative/sync_creatives_async_response_working.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictFloat - -from ..core import context as context_1 -from ..core import ext as ext_1 - - -class SyncCreativesWorking(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - percentage: Annotated[ - StrictFloat | None, Field(description='Completion percentage (0-100)', ge=0.0, le=100.0) - ] = None - current_step: Annotated[ - str | None, Field(description='Current step or phase of the operation') - ] = None - total_steps: Annotated[ - SchemaInt | None, Field(description='Total number of steps in the operation', ge=1) - ] = None - step_number: Annotated[SchemaInt | None, Field(description='Current step number', ge=1)] = None - creatives_processed: Annotated[ - SchemaInt | None, Field(description='Number of creatives processed so far', ge=0) - ] = None - creatives_total: Annotated[ - SchemaInt | None, Field(description='Total number of creatives to process', ge=0) - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/creative/sync_creatives_request.py b/src/adcp/types/generated_poc/creative/sync_creatives_request.py deleted file mode 100644 index ef53ce4ed..000000000 --- a/src/adcp/types/generated_poc/creative/sync_creatives_request.py +++ /dev/null @@ -1,192 +0,0 @@ -# generated by datamodel-codegen: -# filename: creative/sync_creatives_request.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, StrictBool, StrictFloat, model_validator - -from ..core import account_ref -from ..core import context as context_1 -from ..core import creative_localization, creative_revision_id -from ..core import ext as ext_1 -from ..core import push_notification_config as push_notification_config_1 -from ..core.creative_asset import CreativeAsset -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import validation_mode as validation_mode_1 - - -class Assignment(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - creative_id: Annotated[str, Field(description='ID of the creative to assign')] - package_id: Annotated[str, Field(description='ID of the package to assign the creative to')] - weight: Annotated[ - StrictFloat | None, - Field( - description='Relative delivery weight (0-100). When multiple creatives are assigned to the same package, weights determine impression distribution proportionally. When omitted, the creative receives equal rotation with other unweighted creatives. A weight of 0 means the creative is assigned but paused (receives no delivery).', - ge=0.0, - le=100.0, - ), - ] = None - placement_ids: Annotated[ - list[str] | None, - Field( - description='Restrict this creative to specific placements within the package. When omitted, the creative is eligible for all placements.', - min_length=1, - ), - ] = None - - -class PlacementId(ScalarStr): - __slots__ = () - _constraints = {'min_length': 1} - - -class AssignmentOperations1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - operation: Literal['assign'] = 'assign' - creative_id: Annotated[str, Field(min_length=1)] - package_id: Annotated[str, Field(min_length=1)] - weight: Annotated[StrictFloat | None, Field(ge=0.0, le=100.0)] = None - placement_ids: Annotated[list[PlacementId] | None, Field(min_length=1)] = None - - -class AssignmentOperations2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - operation: Literal['unassign'] = 'unassign' - creative_id: Annotated[str, Field(min_length=1)] - package_id: Annotated[str, Field(min_length=1)] - - -class AssignmentOperations3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - operation: Literal['replace'] = 'replace' - creative_id: Annotated[str, Field(description='Replacement creative ID.', min_length=1)] - replaces_creative_id: Annotated[ - str, Field(description='Currently assigned creative ID to remove atomically.', min_length=1) - ] - package_id: Annotated[str, Field(min_length=1)] - weight: Annotated[StrictFloat | None, Field(ge=0.0, le=100.0)] = None - placement_ids: Annotated[list[PlacementId] | None, Field(min_length=1)] = None - - -AssignmentOperations = Annotated[ - AssignmentOperations1 | AssignmentOperations2 | AssignmentOperations3, - Field(discriminator='operation'), -] - - -class Creative(CreativeAsset): - revision_id: Annotated[ - creative_revision_id.CreativeRevisionId | None, - Field( - description='Optional buyer-assigned identity for this exact input-content state. Buyers may send it to any 3.2 peer; creative.supports_revisions gates reliance on immutability, echo, readback, and delivery correlation, not whether the open request shape may carry the field. Within a supporting seller, a revision_id is immutable under one creative_id: reusing it with different canonical revision content MUST fail with CREATIVE_REVISION_CONTENT_MISMATCH. Metadata and assignment fields excluded from revision content may change without minting a new revision.' - ), - ] = None - localization: Annotated[ - creative_localization.CreativeLocalization | None, - Field( - description='Sync-only materialized-localization mutation. The top-level assets are the source variant; optional locale_fallbacks declare buyer-approved language-family substitutions, and default_locale_variant_id selects the final serving fallback. An object transactionally replaces the source assets and complete locale set, omission preserves existing localization only when the top-level source assets are unchanged, and null removes localization. This field never requests translation or generation. Receivers MUST advertise get_adcp_capabilities creative.localization before accepting it.' - ), - ] = None - - -class SyncCreativesRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - account: Annotated[ - account_ref.AccountReference, Field(description='Account that owns these creatives.') - ] - creatives: Annotated[ - list[Creative] | None, - Field( - description='Array of creative assets to sync (create or update)', - max_length=100, - min_length=1, - ), - ] = None - creative_ids: Annotated[ - list[str] | None, - Field( - description='Optional filter to limit sync scope to specific creative IDs. When provided, only these creatives will be created/updated. Other creatives in the library are unaffected. Useful for partial updates and error recovery.', - max_length=100, - min_length=1, - ), - ] = None - assignments: Annotated[ - list[Assignment] | None, - Field( - deprecated=True, - description='Deprecated additive assignment shorthand. Each entry upserts one creative-to-package assignment. Use assignment_operations for explicit assign, unassign, and replace semantics. Standalone creative agents that do not manage media buys ignore this field.', - min_length=1, - ), - ] = None - assignment_operations: Annotated[ - list[AssignmentOperations] | None, - Field( - description='Explicit, ordered assignment mutations. These operations may be sent without creatives to traffic existing creative IDs independently from MediaBuy commercial control. The entire request is atomic under idempotency_key and therefore requires strict validation; lenient partial processing is not permitted.', - max_length=500, - min_length=1, - ), - ] = None - idempotency_key: Annotated[ - str, - Field( - description='Client-generated idempotency key for safe retries. If a sync fails without a response, resending with the same idempotency_key guarantees at-most-once execution. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - delete_missing: Annotated[ - StrictBool | None, - Field( - description='When true, creatives not included in this sync will be archived. Use with caution for full library replacement. Invalid when creative_ids is provided — delete_missing applies to the entire library scope, not a filtered subset.' - ), - ] = False - dry_run: Annotated[ - StrictBool | None, - Field( - description="When true, rehearse this sync_creatives operation without applying it. Validates the actual trafficking request in the seller's current context, including library upsert semantics, creative IDs, assignments, account-scoped gates, and seller policies, then returns what would be created/updated/deleted. This is distinct from validate_input, which only validates manifest structure against canonical/product format targets." - ), - ] = False - validation_mode: Annotated[ - validation_mode_1.ValidationMode | None, - Field( - description="Validation strictness. 'strict' fails entire sync on any validation error. 'lenient' processes valid creatives and reports errors." - ), - ] = validation_mode_1.ValidationMode.strict - push_notification_config: Annotated[ - push_notification_config_1.PushNotificationConfig | None, - Field( - description='Optional webhook configuration for async sync notifications. The agent will send a webhook when sync completes if the operation takes longer than immediate response time (typically for large bulk operations or manual approval/HITL).' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - @model_validator(mode='after') - def _require_schema_required_group(self) -> SyncCreativesRequest: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('creatives',), ('assignments',), ('assignment_operations',),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'SyncCreativesRequest requires at least one of these field groups: creatives | assignments | assignment_operations' - ) diff --git a/src/adcp/types/generated_poc/creative/sync_creatives_response.py b/src/adcp/types/generated_poc/creative/sync_creatives_response.py deleted file mode 100644 index cebca0004..000000000 --- a/src/adcp/types/generated_poc/creative/sync_creatives_response.py +++ /dev/null @@ -1,79 +0,0 @@ -# generated by datamodel-codegen: -# filename: creative/sync_creatives_response.json -# timestamp: preserved-by-post-generate-fixes - -from __future__ import annotations - -from typing import Annotated, Literal, TypeAlias - -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StringConstraints - -from ..core.version_envelope import AdcpVersionEnvelope -from ..core import account as account_1 -from ..core import context as context_1 -from ..core import creative_localization_readback as creative_localization_readback_1 -from ..core import creative_revision_id as creative_revision_id_1 -from ..core import error as error_1 -from ..core import ext as ext_1 -from ..core import macro_resolution_result as macro_resolution_result_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..enums import creative_action as creative_action_1 -from ..enums import creative_status as creative_status_1 -from ..enums import task_status as task_status_1 - - -class Creative(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - creative_id: str - revision_id: creative_revision_id_1.CreativeRevisionId | None = None - account: account_1.Account | None = None - action: creative_action_1.CreativeAction - status: creative_status_1.CreativeStatus | None = None - platform_id: str | None = None - localization: creative_localization_readback_1.CreativeLocalizationReadback | None = None - changes: list[str] | None = None - errors: list[error_1.Error] | None = None - warnings: list[str] | None = None - macro_resolution_results: list[macro_resolution_result_1.MacroResolutionResult] | None = None - preview_url: AnyUrl | None = None - expires_at: AwareDatetime | None = None - assigned_to: list[str] | None = None - assignment_errors: dict[Annotated[str, StringConstraints(pattern='^[a-zA-Z0-9_-]+$')], str] | None = None - - -class SyncCreativesResponse1(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - dry_run: bool | None = None - creatives: list[Creative] - sandbox: bool | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class SyncCreativesResponse2(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - errors: Annotated[list[error_1.Error], Field(min_length=1)] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class SyncCreativesResponse3(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow', validate_default=True) - status: Literal[task_status_1.TaskStatus.submitted] = task_status_1.TaskStatus.submitted - task_id: str - message: Annotated[str, StringConstraints(max_length=2000)] | None = None - errors: list[error_1.Error] | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -SyncCreativesResponse: TypeAlias = SyncCreativesResponse1 | SyncCreativesResponse2 | SyncCreativesResponse3 - - -__all__ = [ - 'SyncCreativesResponse', - 'SyncCreativesResponse1', - 'SyncCreativesResponse2', - 'SyncCreativesResponse3', - 'Creative', -] diff --git a/src/adcp/types/generated_poc/creative/validate_input_request.py b/src/adcp/types/generated_poc/creative/validate_input_request.py deleted file mode 100644 index aa0df0a23..000000000 --- a/src/adcp/types/generated_poc/creative/validate_input_request.py +++ /dev/null @@ -1,88 +0,0 @@ -# generated by datamodel-codegen: -# filename: creative/validate_input_request.json -# timestamp: 2026-10-03T00:36:50+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - -from ..core import account_ref, brand_ref, creative_manifest - - -class Targets1(AdCPBaseModel): - kind: Literal['canonical'] = 'canonical' - id: Annotated[ - str, - Field( - description="Canonical format name from `canonical-format-kind.json` (e.g., `image`, `video_hosted`, `audio_daast`). Validators check the manifest against the canonical's parameter schema." - ), - ] - - -class Targets2(AdCPBaseModel): - kind: Literal['product'] = 'product' - id: Annotated[ - str, - Field( - description="Product ID. Validators check the manifest against the product's inline `ProductFormatDeclaration` narrowing of the canonical (parameter constraints, slot requirements, platform extensions)." - ), - ] - - -class Targets3(AdCPBaseModel): - kind: Literal['third_party_format'] = 'third_party_format' - id: Annotated[ - AnyUrl, - Field( - description='URI-form format identifier referencing a third-party format definition (e.g., `https://creativevendor.example/formats/image_300x250@sha256:...`). Validators fetch the definition (digest-pinned, cached) and validate the manifest against it.' - ), - ] - - -class Targets4(AdCPBaseModel): - kind: Literal['capability'] = 'capability' - id: Annotated[ - str, - Field( - description='Agent-local capability_id from get_adcp_capabilities creative.supported_formats. The selected entry MUST include validate in operations; unknown IDs or entries without validate are rejected with FORMAT_NOT_SUPPORTED.', - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -Targets = Annotated[ - Targets1 | Targets2 | Targets3 | Targets4, - Field(discriminator='kind'), -] - - -class ValidateInputRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - account: Annotated[ - account_ref.AccountReference | None, - Field( - description='Optional account scope for seller-specific product validation. Required by sellers that route product declarations by buyer account.' - ), - ] = None - brand: Annotated[ - brand_ref.BrandReference | None, - Field( - description='Optional brand scope when account is omitted or the seller keys sandbox validation by brand identity.' - ), - ] = None - manifest: Annotated[ - creative_manifest.CreativeManifest, Field(description='Creative manifest to validate.') - ] - targets: Annotated[ - list[Targets] | None, - Field( - description="Discriminated list of validation targets. Each entry mirrors the `target` shape on `validate-input-result.json` so the request/response wire shapes match exactly. Multi-target requests enable universal-creative scenarios where one manifest targets multiple sellers' format declarations in a single round-trip; the response carries one result per target in the same order.", - max_length=50, - min_length=1, - ), - ] = None diff --git a/src/adcp/types/generated_poc/creative/validate_input_response.py b/src/adcp/types/generated_poc/creative/validate_input_response.py deleted file mode 100644 index 39eb942d9..000000000 --- a/src/adcp/types/generated_poc/creative/validate_input_response.py +++ /dev/null @@ -1,23 +0,0 @@ -# generated by datamodel-codegen: -# filename: creative/validate_input_response.json -# timestamp: 2026-05-22T13:15:12+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import ConfigDict, Field - -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from . import validate_input_result - - -class ValidateInputResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - results: Annotated[ - list[validate_input_result.ValidateInputResult], - Field(description='Per-target validation results.'), - ] diff --git a/src/adcp/types/generated_poc/creative/validate_input_result.py b/src/adcp/types/generated_poc/creative/validate_input_result.py deleted file mode 100644 index c7b4fbbf7..000000000 --- a/src/adcp/types/generated_poc/creative/validate_input_result.py +++ /dev/null @@ -1,110 +0,0 @@ -# generated by datamodel-codegen: -# filename: creative/validate_input_result.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..core import macro_resolution_result - - -class Kind(StrEnum): - canonical = 'canonical' - product = 'product' - third_party_format = 'third_party_format' - capability = 'capability' - - -class Target(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Kind - id: Annotated[ - str, - Field( - description="Canonical format name (e.g., 'image'), product_id, URI-form third-party format identifier, or agent-local creative capability_id." - ), - ] - - -class ResultKind(StrEnum): - validated_pass = 'validated_pass' - validated_fail = 'validated_fail' - unvalidatable_nondeterministic = 'unvalidatable_nondeterministic' - - -class Violation(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - rule: Annotated[ - str, - Field( - description="Rule name (e.g., 'duration_ms_range', 'aspect_ratio', 'max_file_size_kb')." - ), - ] - expected: Annotated[ - Any | None, Field(description="Expected value or range (e.g., '28000-32000', '9:16', 200).") - ] = None - predicted: Annotated[ - Any | None, - Field( - description="Platform's pre-flight estimate for this field (NOT the actual output — there is no protocol state for orphaned out-of-spec artifacts). For TTS, this might be the predicted audio duration from text-length analysis. Helps the buyer fix the input before committing to a build." - ), - ] = None - field: Annotated[ - str, - Field(description="Path to the violating field (e.g., 'assets.video_main.duration_ms')."), - ] - retry_with: Annotated[ - dict[str, Any] | None, - Field( - description='Optional advisory adjustment hint. Platforms MAY suggest a corrected input shape; buyers MUST treat this as advisory, not authoritative.' - ), - ] = None - - -class Warning(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - rule: str - expected: Any | None = None - predicted: Any | None = None - - -class ValidateInputResult(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - target: Target - result_kind: Annotated[ - ResultKind, - Field( - description="Discriminator for the validation outcome. See schema description for the three states. Replaces the earlier boolean `ok` to distinguish 'failed validation' from 'platform is nondeterministic, can't pre-validate'." - ), - ] - violations: Annotated[ - list[Violation] | None, - Field( - description="When `result_kind` is `validated_fail`, the specific constraints the manifest fails to meet. MUST be absent (or empty) for `validated_pass` and `unvalidatable_nondeterministic` — neither has constraint violations to enumerate (`unvalidatable_nondeterministic` doesn't validate at all; `validated_pass` has nothing to fail)." - ), - ] = None - warnings: Annotated[ - list[Warning] | None, - Field( - description='Non-blocking observations (e.g. LEAN policy advisories such as hover-triggered expansion or non-user-initiated entry into overlay anchoring) that do not affect `result_kind`. MAY be present alongside `validated_pass`, `validated_fail`, or `unvalidatable_nondeterministic`. Same item shape as `violations`.' - ), - ] = None - macro_resolution_results: Annotated[ - list[macro_resolution_result.MacroResolutionResult] | None, - Field( - description='Per-token compatibility against the selected target. Required or unsafe `unsupported`/`ambiguous` results make the target `validated_fail`; deliberate preservation for the declared downstream resolver may still pass.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/creative/video_brief.py b/src/adcp/types/generated_poc/creative/video_brief.py deleted file mode 100644 index bee1c4e05..000000000 --- a/src/adcp/types/generated_poc/creative/video_brief.py +++ /dev/null @@ -1,48 +0,0 @@ -# generated by datamodel-codegen: -# filename: creative/video_brief.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field - - -class Segment(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - order: Annotated[ - SchemaInt, - Field(description='1-indexed sequence position of this segment in the final video.', ge=1), - ] - duration_ms: Annotated[ - SchemaInt, Field(description='Duration of this segment in milliseconds.', ge=1) - ] - prompt: Annotated[ - str, - Field( - description='Text prompt fed to the synthesis pipeline for this segment (subject, action, setting, mood). Renamed from earlier `description` to make explicit that this is a generation prompt — not a description-of-finished-content.' - ), - ] - vo: Annotated[str | None, Field(description='Voiceover line for this segment (optional).')] = ( - None - ) - caption: Annotated[ - str | None, Field(description='On-screen caption text for this segment (optional).') - ] = None - - -class VideoBrief(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - segments: Annotated[ - list[Segment], - Field( - description="Ordered list of per-segment prompts that compose the generated video. The sum of `duration_ms` across segments should match the target video duration declared by the format declaration's `duration_ms_exact` or `duration_ms_range`.", - min_length=1, - ), - ] diff --git a/src/adcp/types/generated_poc/enums/__init__.py b/src/adcp/types/generated_poc/enums/__init__.py deleted file mode 100644 index 5a3cca36b..000000000 --- a/src/adcp/types/generated_poc/enums/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# generated by datamodel-codegen: -# filename: .schema_temp -# timestamp: 2025-11-22T15:23:24+00:00 diff --git a/src/adcp/types/generated_poc/enums/account_currency_mode.py b/src/adcp/types/generated_poc/enums/account_currency_mode.py deleted file mode 100644 index 3898a54be..000000000 --- a/src/adcp/types/generated_poc/enums/account_currency_mode.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/account_currency_mode.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class AccountCurrencyMode(StrEnum): - fixed = 'fixed' - per_media_buy = 'per_media_buy' diff --git a/src/adcp/types/generated_poc/enums/account_scope.py b/src/adcp/types/generated_poc/enums/account_scope.py deleted file mode 100644 index b1afad2d1..000000000 --- a/src/adcp/types/generated_poc/enums/account_scope.py +++ /dev/null @@ -1,14 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/account_scope.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class AccountScope(StrEnum): - operator = 'operator' - brand = 'brand' - operator_brand = 'operator_brand' - agent = 'agent' diff --git a/src/adcp/types/generated_poc/enums/account_status.py b/src/adcp/types/generated_poc/enums/account_status.py deleted file mode 100644 index 0bfcb3958..000000000 --- a/src/adcp/types/generated_poc/enums/account_status.py +++ /dev/null @@ -1,16 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/account_status.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class AccountStatus(StrEnum): - active = 'active' - pending_approval = 'pending_approval' - rejected = 'rejected' - payment_required = 'payment_required' - suspended = 'suspended' - closed = 'closed' diff --git a/src/adcp/types/generated_poc/enums/action_not_allowed_reason.py b/src/adcp/types/generated_poc/enums/action_not_allowed_reason.py deleted file mode 100644 index 5ed94529f..000000000 --- a/src/adcp/types/generated_poc/enums/action_not_allowed_reason.py +++ /dev/null @@ -1,15 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/action_not_allowed_reason.json -# timestamp: 2026-08-28T20:03:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class ActionNotAllowedReason(StrEnum): - wrong_status = 'wrong_status' - not_supported_on_product = 'not_supported_on_product' - not_supported_on_buy = 'not_supported_on_buy' - mode_mismatch = 'mode_mismatch' - condition_unresolved = 'condition_unresolved' diff --git a/src/adcp/types/generated_poc/enums/action_source.py b/src/adcp/types/generated_poc/enums/action_source.py deleted file mode 100644 index e5297e5ae..000000000 --- a/src/adcp/types/generated_poc/enums/action_source.py +++ /dev/null @@ -1,19 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/action_source.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class ActionSource(StrEnum): - website = 'website' - app = 'app' - offline = 'offline' - phone_call = 'phone_call' - chat = 'chat' - email = 'email' - in_store = 'in_store' - system_generated = 'system_generated' - other = 'other' diff --git a/src/adcp/types/generated_poc/enums/activation_method.py b/src/adcp/types/generated_poc/enums/activation_method.py deleted file mode 100644 index 11c82d10c..000000000 --- a/src/adcp/types/generated_poc/enums/activation_method.py +++ /dev/null @@ -1,16 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/activation_method.json -# timestamp: 2026-08-24T04:35:32+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class CreativeActivationMethod(StrEnum): - qr_code = 'qr_code' - deep_link = 'deep_link' - push_notification = 'push_notification' - email = 'email' - tune_in = 'tune_in' - text_message = 'text_message' diff --git a/src/adcp/types/generated_poc/enums/adcp_protocol.py b/src/adcp/types/generated_poc/enums/adcp_protocol.py deleted file mode 100644 index fd0bb879b..000000000 --- a/src/adcp/types/generated_poc/enums/adcp_protocol.py +++ /dev/null @@ -1,17 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/adcp_protocol.json -# timestamp: 2026-05-22T13:15:12+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class AdcpProtocol(StrEnum): - media_buy = 'media-buy' - signals = 'signals' - governance = 'governance' - creative = 'creative' - brand = 'brand' - sponsored_intelligence = 'sponsored-intelligence' - measurement = 'measurement' diff --git a/src/adcp/types/generated_poc/enums/adjustment_kind.py b/src/adcp/types/generated_poc/enums/adjustment_kind.py deleted file mode 100644 index 16a766fe5..000000000 --- a/src/adcp/types/generated_poc/enums/adjustment_kind.py +++ /dev/null @@ -1,14 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/adjustment_kind.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class PriceAdjustmentKind(StrEnum): - fee = 'fee' - discount = 'discount' - commission = 'commission' - settlement = 'settlement' diff --git a/src/adcp/types/generated_poc/enums/advertiser_industry.py b/src/adcp/types/generated_poc/enums/advertiser_industry.py deleted file mode 100644 index 3273ed0d3..000000000 --- a/src/adcp/types/generated_poc/enums/advertiser_industry.py +++ /dev/null @@ -1,91 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/advertiser_industry.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class AdvertiserIndustry(StrEnum): - automotive = 'automotive' - automotive_electric_vehicles = 'automotive.electric_vehicles' - automotive_parts_accessories = 'automotive.parts_accessories' - automotive_luxury = 'automotive.luxury' - beauty_cosmetics = 'beauty_cosmetics' - beauty_cosmetics_skincare = 'beauty_cosmetics.skincare' - beauty_cosmetics_fragrance = 'beauty_cosmetics.fragrance' - beauty_cosmetics_haircare = 'beauty_cosmetics.haircare' - cannabis = 'cannabis' - cpg = 'cpg' - cpg_personal_care = 'cpg.personal_care' - cpg_household = 'cpg.household' - dating = 'dating' - education = 'education' - education_higher_education = 'education.higher_education' - education_online_learning = 'education.online_learning' - education_k12 = 'education.k12' - energy_utilities = 'energy_utilities' - energy_utilities_renewable = 'energy_utilities.renewable' - fashion_apparel = 'fashion_apparel' - fashion_apparel_luxury = 'fashion_apparel.luxury' - fashion_apparel_sportswear = 'fashion_apparel.sportswear' - finance = 'finance' - finance_banking = 'finance.banking' - finance_insurance = 'finance.insurance' - finance_investment = 'finance.investment' - finance_cryptocurrency = 'finance.cryptocurrency' - food_beverage = 'food_beverage' - food_beverage_alcohol = 'food_beverage.alcohol' - food_beverage_restaurants = 'food_beverage.restaurants' - food_beverage_packaged_goods = 'food_beverage.packaged_goods' - gambling_betting = 'gambling_betting' - gambling_betting_sports_betting = 'gambling_betting.sports_betting' - gambling_betting_casino = 'gambling_betting.casino' - gaming = 'gaming' - gaming_mobile = 'gaming.mobile' - gaming_console_pc = 'gaming.console_pc' - gaming_esports = 'gaming.esports' - government_nonprofit = 'government_nonprofit' - government_nonprofit_political = 'government_nonprofit.political' - government_nonprofit_charity = 'government_nonprofit.charity' - healthcare = 'healthcare' - healthcare_pharmaceutical = 'healthcare.pharmaceutical' - healthcare_medical_devices = 'healthcare.medical_devices' - healthcare_wellness = 'healthcare.wellness' - home_garden = 'home_garden' - home_garden_furniture = 'home_garden.furniture' - home_garden_home_improvement = 'home_garden.home_improvement' - media_entertainment = 'media_entertainment' - media_entertainment_podcasts = 'media_entertainment.podcasts' - media_entertainment_music = 'media_entertainment.music' - media_entertainment_film_tv = 'media_entertainment.film_tv' - media_entertainment_publishing = 'media_entertainment.publishing' - media_entertainment_live_events = 'media_entertainment.live_events' - pets = 'pets' - professional_services = 'professional_services' - professional_services_legal = 'professional_services.legal' - professional_services_consulting = 'professional_services.consulting' - real_estate = 'real_estate' - real_estate_residential = 'real_estate.residential' - real_estate_commercial = 'real_estate.commercial' - recruitment_hr = 'recruitment_hr' - retail = 'retail' - retail_ecommerce = 'retail.ecommerce' - retail_department_stores = 'retail.department_stores' - sports_fitness = 'sports_fitness' - sports_fitness_equipment = 'sports_fitness.equipment' - sports_fitness_teams_leagues = 'sports_fitness.teams_leagues' - technology = 'technology' - technology_software = 'technology.software' - technology_hardware = 'technology.hardware' - technology_ai_ml = 'technology.ai_ml' - telecom = 'telecom' - telecom_mobile_carriers = 'telecom.mobile_carriers' - telecom_internet_providers = 'telecom.internet_providers' - transportation_logistics = 'transportation_logistics' - travel_hospitality = 'travel_hospitality' - travel_hospitality_airlines = 'travel_hospitality.airlines' - travel_hospitality_hotels = 'travel_hospitality.hotels' - travel_hospitality_cruise = 'travel_hospitality.cruise' - travel_hospitality_tourism = 'travel_hospitality.tourism' diff --git a/src/adcp/types/generated_poc/enums/age_determination_basis.py b/src/adcp/types/generated_poc/enums/age_determination_basis.py deleted file mode 100644 index 901598216..000000000 --- a/src/adcp/types/generated_poc/enums/age_determination_basis.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/age_determination_basis.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class AgeDeterminationBasis(StrEnum): - verified = 'verified' - declared = 'declared' - inferred = 'inferred' diff --git a/src/adcp/types/generated_poc/enums/age_verification_method.py b/src/adcp/types/generated_poc/enums/age_verification_method.py deleted file mode 100644 index 2f76bb152..000000000 --- a/src/adcp/types/generated_poc/enums/age_verification_method.py +++ /dev/null @@ -1,15 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/age_verification_method.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class AgeVerificationMethod(StrEnum): - facial_age_estimation = 'facial_age_estimation' - id_document = 'id_document' - digital_id = 'digital_id' - credit_card = 'credit_card' - world_id = 'world_id' diff --git a/src/adcp/types/generated_poc/enums/assessment_status.py b/src/adcp/types/generated_poc/enums/assessment_status.py deleted file mode 100644 index e7af61416..000000000 --- a/src/adcp/types/generated_poc/enums/assessment_status.py +++ /dev/null @@ -1,14 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/assessment_status.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class AssessmentStatus(StrEnum): - insufficient = 'insufficient' - minimum = 'minimum' - good = 'good' - excellent = 'excellent' diff --git a/src/adcp/types/generated_poc/enums/asset_content_type.py b/src/adcp/types/generated_poc/enums/asset_content_type.py deleted file mode 100644 index 401a0706c..000000000 --- a/src/adcp/types/generated_poc/enums/asset_content_type.py +++ /dev/null @@ -1,26 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/asset_content_type.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class AssetContentType(StrEnum): - image = 'image' - video = 'video' - audio = 'audio' - text = 'text' - markdown = 'markdown' - html = 'html' - css = 'css' - javascript = 'javascript' - zip = 'zip' - vast = 'vast' - daast = 'daast' - url = 'url' - webhook = 'webhook' - brief = 'brief' - catalog = 'catalog' - published_post = 'published_post' diff --git a/src/adcp/types/generated_poc/enums/attestation_claim.py b/src/adcp/types/generated_poc/enums/attestation_claim.py deleted file mode 100644 index bb8a183c9..000000000 --- a/src/adcp/types/generated_poc/enums/attestation_claim.py +++ /dev/null @@ -1,15 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/attestation_claim.json -# timestamp: 2026-06-12T11:05:47+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class AttestationClaim(StrEnum): - unique_human = 'unique_human' - age_over_13 = 'age_over_13' - age_over_16 = 'age_over_16' - age_over_18 = 'age_over_18' - age_over_21 = 'age_over_21' diff --git a/src/adcp/types/generated_poc/enums/attribution_methodology.py b/src/adcp/types/generated_poc/enums/attribution_methodology.py deleted file mode 100644 index 345dac1fd..000000000 --- a/src/adcp/types/generated_poc/enums/attribution_methodology.py +++ /dev/null @@ -1,14 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/attribution_methodology.json -# timestamp: 2026-05-22T13:15:12+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class AttributionMethodology(StrEnum): - deterministic_purchase = 'deterministic_purchase' - probabilistic = 'probabilistic' - panel_based = 'panel_based' - modeled = 'modeled' diff --git a/src/adcp/types/generated_poc/enums/attribution_model.py b/src/adcp/types/generated_poc/enums/attribution_model.py deleted file mode 100644 index 02b0940a9..000000000 --- a/src/adcp/types/generated_poc/enums/attribution_model.py +++ /dev/null @@ -1,15 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/attribution_model.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class AttributionModel(StrEnum): - last_touch = 'last_touch' - first_touch = 'first_touch' - linear = 'linear' - time_decay = 'time_decay' - data_driven = 'data_driven' diff --git a/src/adcp/types/generated_poc/enums/audience_evidence_methodology.py b/src/adcp/types/generated_poc/enums/audience_evidence_methodology.py deleted file mode 100644 index d266dd758..000000000 --- a/src/adcp/types/generated_poc/enums/audience_evidence_methodology.py +++ /dev/null @@ -1,16 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/audience_evidence_methodology.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class AudienceEvidenceMethodology(StrEnum): - observed = 'observed' - declared = 'declared' - derived = 'derived' - inferred = 'inferred' - modeled = 'modeled' - projected = 'projected' diff --git a/src/adcp/types/generated_poc/enums/audience_resolution_method.py b/src/adcp/types/generated_poc/enums/audience_resolution_method.py deleted file mode 100644 index c56c31c19..000000000 --- a/src/adcp/types/generated_poc/enums/audience_resolution_method.py +++ /dev/null @@ -1,16 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/audience_resolution_method.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class AudienceResolutionMethod(StrEnum): - deterministic_id = 'deterministic_id' - probabilistic_device = 'probabilistic_device' - browser = 'browser' - geographic = 'geographic' - content_signal = 'content_signal' - mixed = 'mixed' diff --git a/src/adcp/types/generated_poc/enums/audience_source.py b/src/adcp/types/generated_poc/enums/audience_source.py deleted file mode 100644 index b0b04f9ce..000000000 --- a/src/adcp/types/generated_poc/enums/audience_source.py +++ /dev/null @@ -1,16 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/audience_source.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class AudienceSource(StrEnum): - synced = 'synced' - platform = 'platform' - third_party = 'third_party' - lookalike = 'lookalike' - retargeting = 'retargeting' - unknown = 'unknown' diff --git a/src/adcp/types/generated_poc/enums/audience_status.py b/src/adcp/types/generated_poc/enums/audience_status.py deleted file mode 100644 index 3694385a6..000000000 --- a/src/adcp/types/generated_poc/enums/audience_status.py +++ /dev/null @@ -1,14 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/audience_status.json -# timestamp: 2026-05-22T13:15:12+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class AudienceStatus(StrEnum): - processing = 'processing' - ready = 'ready' - too_small = 'too_small' - suspended = 'suspended' diff --git a/src/adcp/types/generated_poc/enums/audience_subject_type.py b/src/adcp/types/generated_poc/enums/audience_subject_type.py deleted file mode 100644 index f8bc03e6e..000000000 --- a/src/adcp/types/generated_poc/enums/audience_subject_type.py +++ /dev/null @@ -1,15 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/audience_subject_type.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class AudienceSubjectType(StrEnum): - individual = 'individual' - household = 'household' - business = 'business' - contextual = 'contextual' - none = 'none' diff --git a/src/adcp/types/generated_poc/enums/audio_channel_layout.py b/src/adcp/types/generated_poc/enums/audio_channel_layout.py deleted file mode 100644 index 39048830c..000000000 --- a/src/adcp/types/generated_poc/enums/audio_channel_layout.py +++ /dev/null @@ -1,14 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/audio_channel_layout.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class AudioChannelLayout(StrEnum): - mono = 'mono' - stereo = 'stereo' - field_5_1 = '5.1' - field_7_1 = '7.1' diff --git a/src/adcp/types/generated_poc/enums/audio_distribution_type.py b/src/adcp/types/generated_poc/enums/audio_distribution_type.py deleted file mode 100644 index aa3a02490..000000000 --- a/src/adcp/types/generated_poc/enums/audio_distribution_type.py +++ /dev/null @@ -1,17 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/audio_distribution_type.json -# timestamp: 2026-06-12T11:05:47+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class AudioDistributionType(StrEnum): - music_streaming_service = 'music_streaming_service' - fm_am_broadcast = 'fm_am_broadcast' - podcast = 'podcast' - catch_up_radio = 'catch_up_radio' - web_radio = 'web_radio' - video_game = 'video_game' - text_to_speech = 'text_to_speech' diff --git a/src/adcp/types/generated_poc/enums/auth_scheme.py b/src/adcp/types/generated_poc/enums/auth_scheme.py deleted file mode 100644 index fff14236c..000000000 --- a/src/adcp/types/generated_poc/enums/auth_scheme.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/auth_scheme.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class AuthenticationScheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' diff --git a/src/adcp/types/generated_poc/enums/availability_status.py b/src/adcp/types/generated_poc/enums/availability_status.py deleted file mode 100644 index 8bcf698a6..000000000 --- a/src/adcp/types/generated_poc/enums/availability_status.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/availability_status.json -# timestamp: 2026-08-21T04:35:18+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class AvailabilityStatus(StrEnum): - available = 'available' - unavailable = 'unavailable' diff --git a/src/adcp/types/generated_poc/enums/available_metric.py b/src/adcp/types/generated_poc/enums/available_metric.py deleted file mode 100644 index ceba1cdd3..000000000 --- a/src/adcp/types/generated_poc/enums/available_metric.py +++ /dev/null @@ -1,59 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/available_metric.json -# timestamp: 2026-08-28T20:03:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class AvailableMetric(StrEnum): - impressions = 'impressions' - spend = 'spend' - clicks = 'clicks' - ctr = 'ctr' - views = 'views' - completed_views = 'completed_views' - completion_rate = 'completion_rate' - conversions = 'conversions' - conversion_value = 'conversion_value' - commissionable_value = 'commissionable_value' - roas = 'roas' - cost_per_acquisition = 'cost_per_acquisition' - new_to_brand_rate = 'new_to_brand_rate' - leads = 'leads' - reach = 'reach' - frequency = 'frequency' - grps = 'grps' - engagements = 'engagements' - engagement_rate = 'engagement_rate' - follows = 'follows' - saves = 'saves' - profile_visits = 'profile_visits' - viewability = 'viewability' - viewable_rate = 'viewable_rate' - viewable_impressions = 'viewable_impressions' - measurable_impressions = 'measurable_impressions' - viewed_seconds = 'viewed_seconds' - viewed_seconds_percentiles = 'viewed_seconds_percentiles' - viewed_seconds_histogram = 'viewed_seconds_histogram' - quartile_data = 'quartile_data' - quartile_25 = 'quartile_25' - quartile_50 = 'quartile_50' - quartile_75 = 'quartile_75' - quartile_100 = 'quartile_100' - time_based_views = 'time_based_views' - dooh_metrics = 'dooh_metrics' - ooh_metrics = 'ooh_metrics' - cost_per_click = 'cost_per_click' - cost_per_completed_view = 'cost_per_completed_view' - cpm = 'cpm' - downloads = 'downloads' - units_sold = 'units_sold' - new_to_brand_units = 'new_to_brand_units' - plays = 'plays' - incremental_sales_lift = 'incremental_sales_lift' - brand_lift = 'brand_lift' - foot_traffic = 'foot_traffic' - conversion_lift = 'conversion_lift' - brand_search_lift = 'brand_search_lift' diff --git a/src/adcp/types/generated_poc/enums/billing_party.py b/src/adcp/types/generated_poc/enums/billing_party.py deleted file mode 100644 index 9a7e4faca..000000000 --- a/src/adcp/types/generated_poc/enums/billing_party.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/billing_party.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class BillingParty(StrEnum): - operator = 'operator' - agent = 'agent' - advertiser = 'advertiser' diff --git a/src/adcp/types/generated_poc/enums/binary_verdict.py b/src/adcp/types/generated_poc/enums/binary_verdict.py deleted file mode 100644 index 29b19dfed..000000000 --- a/src/adcp/types/generated_poc/enums/binary_verdict.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/binary_verdict.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class BinaryVerdict(StrEnum): - pass_ = 'pass' - fail = 'fail' diff --git a/src/adcp/types/generated_poc/enums/brand_agent_type.py b/src/adcp/types/generated_poc/enums/brand_agent_type.py deleted file mode 100644 index 3a5194d47..000000000 --- a/src/adcp/types/generated_poc/enums/brand_agent_type.py +++ /dev/null @@ -1,18 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/brand_agent_type.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class BrandAgentType(StrEnum): - brand = 'brand' - rights = 'rights' - measurement = 'measurement' - governance = 'governance' - creative = 'creative' - sales = 'sales' - buying = 'buying' - signals = 'signals' diff --git a/src/adcp/types/generated_poc/enums/browser_family.py b/src/adcp/types/generated_poc/enums/browser_family.py deleted file mode 100644 index 9ccf55b28..000000000 --- a/src/adcp/types/generated_poc/enums/browser_family.py +++ /dev/null @@ -1,19 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/browser_family.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class BrowserFamily(StrEnum): - chrome = 'chrome' - safari = 'safari' - firefox = 'firefox' - edge = 'edge' - opera = 'opera' - samsung_internet = 'samsung_internet' - android_webview = 'android_webview' - other = 'other' - unknown = 'unknown' diff --git a/src/adcp/types/generated_poc/enums/c2pa_watermark_action.py b/src/adcp/types/generated_poc/enums/c2pa_watermark_action.py deleted file mode 100644 index 604064394..000000000 --- a/src/adcp/types/generated_poc/enums/c2pa_watermark_action.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/c2pa_watermark_action.json -# timestamp: 2026-05-22T13:15:12+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class C2PaWatermarkAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' diff --git a/src/adcp/types/generated_poc/enums/canceled_by.py b/src/adcp/types/generated_poc/enums/canceled_by.py deleted file mode 100644 index fa1d979c9..000000000 --- a/src/adcp/types/generated_poc/enums/canceled_by.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/canceled_by.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class CanceledBy(StrEnum): - buyer = 'buyer' - seller = 'seller' diff --git a/src/adcp/types/generated_poc/enums/canonical_media_buy_action.py b/src/adcp/types/generated_poc/enums/canonical_media_buy_action.py deleted file mode 100644 index a492b33b0..000000000 --- a/src/adcp/types/generated_poc/enums/canonical_media_buy_action.py +++ /dev/null @@ -1,36 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/canonical_media_buy_action.json -# timestamp: 2026-09-14T14:16:02+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class CanonicalMediaBuyActionName(StrEnum): - pause = 'pause' - resume = 'resume' - cancel = 'cancel' - extend_flight = 'extend_flight' - shorten_flight = 'shorten_flight' - update_flight_dates = 'update_flight_dates' - increase_budget = 'increase_budget' - decrease_budget = 'decrease_budget' - reallocate_budget = 'reallocate_budget' - update_budget_allocation = 'update_budget_allocation' - update_targeting = 'update_targeting' - update_pacing = 'update_pacing' - update_bidding = 'update_bidding' - update_frequency_caps = 'update_frequency_caps' - update_media_buy_frequency_cap = 'update_media_buy_frequency_cap' - update_catalog_assignments = 'update_catalog_assignments' - update_keywords = 'update_keywords' - update_optimization_goals = 'update_optimization_goals' - update_impression_goal = 'update_impression_goal' - update_spend_target = 'update_spend_target' - update_reporting_webhook = 'update_reporting_webhook' - replace_creative = 'replace_creative' - update_creative_assignments = 'update_creative_assignments' - remove_creative = 'remove_creative' - add_packages = 'add_packages' - remove_packages = 'remove_packages' diff --git a/src/adcp/types/generated_poc/enums/canonical_media_buy_action_mode.py b/src/adcp/types/generated_poc/enums/canonical_media_buy_action_mode.py deleted file mode 100644 index 0a216203d..000000000 --- a/src/adcp/types/generated_poc/enums/canonical_media_buy_action_mode.py +++ /dev/null @@ -1,14 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/canonical_media_buy_action_mode.json -# timestamp: 2026-08-28T20:03:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class CanonicalMediaBuyActionMode(StrEnum): - self_serve = 'self_serve' - conditional_self_serve = 'conditional_self_serve' - seller_managed = 'seller_managed' - requires_approval = 'requires_approval' diff --git a/src/adcp/types/generated_poc/enums/catalog_action.py b/src/adcp/types/generated_poc/enums/catalog_action.py deleted file mode 100644 index 3114de50f..000000000 --- a/src/adcp/types/generated_poc/enums/catalog_action.py +++ /dev/null @@ -1,15 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/catalog_action.json -# timestamp: 2026-05-22T13:15:12+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class CatalogAction(StrEnum): - created = 'created' - updated = 'updated' - unchanged = 'unchanged' - failed = 'failed' - deleted = 'deleted' diff --git a/src/adcp/types/generated_poc/enums/catalog_item_status.py b/src/adcp/types/generated_poc/enums/catalog_item_status.py deleted file mode 100644 index ddfd17b96..000000000 --- a/src/adcp/types/generated_poc/enums/catalog_item_status.py +++ /dev/null @@ -1,15 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/catalog_item_status.json -# timestamp: 2026-05-22T13:15:12+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class CatalogItemStatus(StrEnum): - approved = 'approved' - pending = 'pending' - rejected = 'rejected' - warning = 'warning' - withdrawn = 'withdrawn' diff --git a/src/adcp/types/generated_poc/enums/catalog_type.py b/src/adcp/types/generated_poc/enums/catalog_type.py deleted file mode 100644 index 6ffea75bb..000000000 --- a/src/adcp/types/generated_poc/enums/catalog_type.py +++ /dev/null @@ -1,23 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/catalog_type.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class CatalogType(StrEnum): - offering = 'offering' - product = 'product' - inventory = 'inventory' - store = 'store' - promotion = 'promotion' - hotel = 'hotel' - flight = 'flight' - job = 'job' - vehicle = 'vehicle' - real_estate = 'real_estate' - education = 'education' - destination = 'destination' - app = 'app' diff --git a/src/adcp/types/generated_poc/enums/channels.py b/src/adcp/types/generated_poc/enums/channels.py deleted file mode 100644 index 16da34188..000000000 --- a/src/adcp/types/generated_poc/enums/channels.py +++ /dev/null @@ -1,30 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/channels.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class MediaChannel(StrEnum): - display = 'display' - olv = 'olv' - social = 'social' - search = 'search' - ctv = 'ctv' - linear_tv = 'linear_tv' - radio = 'radio' - streaming_audio = 'streaming_audio' - podcast = 'podcast' - dooh = 'dooh' - ooh = 'ooh' - print = 'print' - cinema = 'cinema' - email = 'email' - gaming = 'gaming' - retail_media = 'retail_media' - influencer = 'influencer' - affiliate = 'affiliate' - product_placement = 'product_placement' - sponsored_intelligence = 'sponsored_intelligence' diff --git a/src/adcp/types/generated_poc/enums/cloud_storage_protocol.py b/src/adcp/types/generated_poc/enums/cloud_storage_protocol.py deleted file mode 100644 index 7d1041594..000000000 --- a/src/adcp/types/generated_poc/enums/cloud_storage_protocol.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/cloud_storage_protocol.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class CloudStorageProtocol(StrEnum): - s3 = 's3' - gcs = 'gcs' - azure_blob = 'azure_blob' diff --git a/src/adcp/types/generated_poc/enums/co_branding_requirement.py b/src/adcp/types/generated_poc/enums/co_branding_requirement.py deleted file mode 100644 index 0f4353626..000000000 --- a/src/adcp/types/generated_poc/enums/co_branding_requirement.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/co_branding_requirement.json -# timestamp: 2026-06-05T16:54:33+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class CoBrandingRequirement(StrEnum): - required = 'required' - optional = 'optional' - none = 'none' diff --git a/src/adcp/types/generated_poc/enums/collection_cadence.py b/src/adcp/types/generated_poc/enums/collection_cadence.py deleted file mode 100644 index 73ee3a19c..000000000 --- a/src/adcp/types/generated_poc/enums/collection_cadence.py +++ /dev/null @@ -1,16 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/collection_cadence.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class CollectionCadence(StrEnum): - daily = 'daily' - weekly = 'weekly' - monthly = 'monthly' - seasonal = 'seasonal' - event = 'event' - irregular = 'irregular' diff --git a/src/adcp/types/generated_poc/enums/collection_kind.py b/src/adcp/types/generated_poc/enums/collection_kind.py deleted file mode 100644 index 08cb77701..000000000 --- a/src/adcp/types/generated_poc/enums/collection_kind.py +++ /dev/null @@ -1,15 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/collection_kind.json -# timestamp: 2026-09-01T19:37:00+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class CollectionKind(StrEnum): - series = 'series' - publication = 'publication' - event_series = 'event_series' - rotation = 'rotation' - channel = 'channel' diff --git a/src/adcp/types/generated_poc/enums/collection_relationship.py b/src/adcp/types/generated_poc/enums/collection_relationship.py deleted file mode 100644 index 051942b52..000000000 --- a/src/adcp/types/generated_poc/enums/collection_relationship.py +++ /dev/null @@ -1,15 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/collection_relationship.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class CollectionRelationship(StrEnum): - spinoff = 'spinoff' - companion = 'companion' - sequel = 'sequel' - prequel = 'prequel' - crossover = 'crossover' diff --git a/src/adcp/types/generated_poc/enums/collection_status.py b/src/adcp/types/generated_poc/enums/collection_status.py deleted file mode 100644 index 70e6faa09..000000000 --- a/src/adcp/types/generated_poc/enums/collection_status.py +++ /dev/null @@ -1,14 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/collection_status.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class CollectionStatus(StrEnum): - active = 'active' - hiatus = 'hiatus' - ended = 'ended' - upcoming = 'upcoming' diff --git a/src/adcp/types/generated_poc/enums/completion_source.py b/src/adcp/types/generated_poc/enums/completion_source.py deleted file mode 100644 index 73a4e9342..000000000 --- a/src/adcp/types/generated_poc/enums/completion_source.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/completion_source.json -# timestamp: 2026-05-22T13:15:12+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class CompletionSource(StrEnum): - seller_attested = 'seller_attested' - vendor_attested = 'vendor_attested' diff --git a/src/adcp/types/generated_poc/enums/consent_basis.py b/src/adcp/types/generated_poc/enums/consent_basis.py deleted file mode 100644 index e8e746026..000000000 --- a/src/adcp/types/generated_poc/enums/consent_basis.py +++ /dev/null @@ -1,14 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/consent_basis.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class ConsentBasis(StrEnum): - consent = 'consent' - legitimate_interest = 'legitimate_interest' - contract = 'contract' - legal_obligation = 'legal_obligation' diff --git a/src/adcp/types/generated_poc/enums/content_id_type.py b/src/adcp/types/generated_poc/enums/content_id_type.py deleted file mode 100644 index b8f2b0806..000000000 --- a/src/adcp/types/generated_poc/enums/content_id_type.py +++ /dev/null @@ -1,22 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/content_id_type.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class ContentIdType(StrEnum): - sku = 'sku' - gtin = 'gtin' - offering_id = 'offering_id' - job_id = 'job_id' - hotel_id = 'hotel_id' - flight_id = 'flight_id' - vehicle_id = 'vehicle_id' - listing_id = 'listing_id' - store_id = 'store_id' - program_id = 'program_id' - destination_id = 'destination_id' - app_id = 'app_id' diff --git a/src/adcp/types/generated_poc/enums/content_rating_system.py b/src/adcp/types/generated_poc/enums/content_rating_system.py deleted file mode 100644 index 88e0d1b7b..000000000 --- a/src/adcp/types/generated_poc/enums/content_rating_system.py +++ /dev/null @@ -1,21 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/content_rating_system.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class ContentRatingSystem(StrEnum): - tv_parental = 'tv_parental' - mpaa = 'mpaa' - podcast = 'podcast' - esrb = 'esrb' - bbfc = 'bbfc' - fsk = 'fsk' - acb = 'acb' - chvrs = 'chvrs' - csa = 'csa' - pegi = 'pegi' - custom = 'custom' diff --git a/src/adcp/types/generated_poc/enums/creative_action.py b/src/adcp/types/generated_poc/enums/creative_action.py deleted file mode 100644 index d8f6a55e6..000000000 --- a/src/adcp/types/generated_poc/enums/creative_action.py +++ /dev/null @@ -1,15 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/creative_action.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class CreativeAction(StrEnum): - created = 'created' - updated = 'updated' - unchanged = 'unchanged' - failed = 'failed' - deleted = 'deleted' diff --git a/src/adcp/types/generated_poc/enums/creative_agent_capability.py b/src/adcp/types/generated_poc/enums/creative_agent_capability.py deleted file mode 100644 index 2eadea814..000000000 --- a/src/adcp/types/generated_poc/enums/creative_agent_capability.py +++ /dev/null @@ -1,15 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/creative_agent_capability.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class CreativeAgentCapability(StrEnum): - validation = 'validation' - assembly = 'assembly' - generation = 'generation' - preview = 'preview' - delivery = 'delivery' diff --git a/src/adcp/types/generated_poc/enums/creative_approval_status.py b/src/adcp/types/generated_poc/enums/creative_approval_status.py deleted file mode 100644 index a37c6ed81..000000000 --- a/src/adcp/types/generated_poc/enums/creative_approval_status.py +++ /dev/null @@ -1,14 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/creative_approval_status.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class CreativeApprovalStatus(StrEnum): - pending_review = 'pending_review' - approved = 'approved' - partially_approved = 'partially_approved' - rejected = 'rejected' diff --git a/src/adcp/types/generated_poc/enums/creative_event_reason_code.py b/src/adcp/types/generated_poc/enums/creative_event_reason_code.py deleted file mode 100644 index 53f4bb36f..000000000 --- a/src/adcp/types/generated_poc/enums/creative_event_reason_code.py +++ /dev/null @@ -1,27 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/creative_event_reason_code.json -# timestamp: 2026-06-04T19:44:00+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class CreativeEventReasonCode(StrEnum): - review_passed = 'review_passed' - review_failure = 'review_failure' - processing_failure = 'processing_failure' - seller_rereview = 'seller_rereview' - policy_revocation = 'policy_revocation' - content_drift = 'content_drift' - identity_authorization_revoked = 'identity_authorization_revoked' - identity_authorization_expired = 'identity_authorization_expired' - source_private = 'source_private' - source_deleted = 'source_deleted' - takedown_request = 'takedown_request' - advertiser_request = 'advertiser_request' - seller_archive = 'seller_archive' - account_closed = 'account_closed' - account_suspended = 'account_suspended' - retention_expired = 'retention_expired' - legal_erasure = 'legal_erasure' diff --git a/src/adcp/types/generated_poc/enums/creative_identifier_type.py b/src/adcp/types/generated_poc/enums/creative_identifier_type.py deleted file mode 100644 index 29e633a68..000000000 --- a/src/adcp/types/generated_poc/enums/creative_identifier_type.py +++ /dev/null @@ -1,14 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/creative_identifier_type.json -# timestamp: 2026-06-04T19:44:00+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class CreativeIdentifierType(StrEnum): - ad_id = 'ad_id' - isci = 'isci' - clearcast_clock = 'clearcast_clock' - idcrea = 'idcrea' diff --git a/src/adcp/types/generated_poc/enums/creative_quality.py b/src/adcp/types/generated_poc/enums/creative_quality.py deleted file mode 100644 index 38cdf5afe..000000000 --- a/src/adcp/types/generated_poc/enums/creative_quality.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/creative_quality.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class CreativeQuality(StrEnum): - draft = 'draft' - production = 'production' diff --git a/src/adcp/types/generated_poc/enums/creative_selection_strategy.py b/src/adcp/types/generated_poc/enums/creative_selection_strategy.py deleted file mode 100644 index cb558038c..000000000 --- a/src/adcp/types/generated_poc/enums/creative_selection_strategy.py +++ /dev/null @@ -1,16 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/creative_selection_strategy.json -# timestamp: 2026-06-04T19:44:00+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class CreativeSelectionStrategy(StrEnum): - audience_relevance = 'audience_relevance' - contextual_fit = 'contextual_fit' - performance = 'performance' - proximity = 'proximity' - inventory_priority = 'inventory_priority' - random = 'random' diff --git a/src/adcp/types/generated_poc/enums/creative_sort_field.py b/src/adcp/types/generated_poc/enums/creative_sort_field.py deleted file mode 100644 index c5baee2a2..000000000 --- a/src/adcp/types/generated_poc/enums/creative_sort_field.py +++ /dev/null @@ -1,15 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/creative_sort_field.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class CreativeSortField(StrEnum): - created_date = 'created_date' - updated_date = 'updated_date' - name = 'name' - status = 'status' - assignment_count = 'assignment_count' diff --git a/src/adcp/types/generated_poc/enums/creative_status.py b/src/adcp/types/generated_poc/enums/creative_status.py deleted file mode 100644 index 56b7bb52e..000000000 --- a/src/adcp/types/generated_poc/enums/creative_status.py +++ /dev/null @@ -1,16 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/creative_status.json -# timestamp: 2026-06-04T19:44:00+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class CreativeStatus(StrEnum): - processing = 'processing' - pending_review = 'pending_review' - approved = 'approved' - suspended = 'suspended' - rejected = 'rejected' - archived = 'archived' diff --git a/src/adcp/types/generated_poc/enums/ctv_ad_experience.py b/src/adcp/types/generated_poc/enums/ctv_ad_experience.py deleted file mode 100644 index 18bc4f0e9..000000000 --- a/src/adcp/types/generated_poc/enums/ctv_ad_experience.py +++ /dev/null @@ -1,16 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/ctv_ad_experience.json -# timestamp: 2026-08-24T04:35:32+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class CtvAdExperience(StrEnum): - menu = 'menu' - pause = 'pause' - screensaver = 'screensaver' - overlay = 'overlay' - squeezeback = 'squeezeback' - in_scene = 'in_scene' diff --git a/src/adcp/types/generated_poc/enums/daast_tracking_event.py b/src/adcp/types/generated_poc/enums/daast_tracking_event.py deleted file mode 100644 index 6ccaa07d9..000000000 --- a/src/adcp/types/generated_poc/enums/daast_tracking_event.py +++ /dev/null @@ -1,33 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/daast_tracking_event.json -# timestamp: 2026-05-22T13:15:12+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class DaastTrackingEvent(StrEnum): - impression = 'impression' - creativeView = 'creativeView' - start = 'start' - firstQuartile = 'firstQuartile' - midpoint = 'midpoint' - thirdQuartile = 'thirdQuartile' - complete = 'complete' - mute = 'mute' - unmute = 'unmute' - pause = 'pause' - resume = 'resume' - rewind = 'rewind' - skip = 'skip' - progress = 'progress' - clickTracking = 'clickTracking' - customClick = 'customClick' - close = 'close' - error = 'error' - viewable = 'viewable' - notViewable = 'notViewable' - viewUndetermined = 'viewUndetermined' - measurableImpression = 'measurableImpression' - viewableImpression = 'viewableImpression' diff --git a/src/adcp/types/generated_poc/enums/daast_version.py b/src/adcp/types/generated_poc/enums/daast_version.py deleted file mode 100644 index 7a4cd61ce..000000000 --- a/src/adcp/types/generated_poc/enums/daast_version.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/daast_version.json -# timestamp: 2026-05-19T21:40:22+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class DaastVersion(StrEnum): - field_1_0 = '1.0' - field_1_1 = '1.1' diff --git a/src/adcp/types/generated_poc/enums/day_of_week.py b/src/adcp/types/generated_poc/enums/day_of_week.py deleted file mode 100644 index 6cb8e69d1..000000000 --- a/src/adcp/types/generated_poc/enums/day_of_week.py +++ /dev/null @@ -1,17 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/day_of_week.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class DayOfWeek(StrEnum): - monday = 'monday' - tuesday = 'tuesday' - wednesday = 'wednesday' - thursday = 'thursday' - friday = 'friday' - saturday = 'saturday' - sunday = 'sunday' diff --git a/src/adcp/types/generated_poc/enums/daypart_timezone_mode.py b/src/adcp/types/generated_poc/enums/daypart_timezone_mode.py deleted file mode 100644 index f1ae4ccc4..000000000 --- a/src/adcp/types/generated_poc/enums/daypart_timezone_mode.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/daypart_timezone_mode.json -# timestamp: 2026-09-03T18:44:01+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class DaypartTimezoneMode(StrEnum): - inventory_local = 'inventory_local' - iana = 'iana' diff --git a/src/adcp/types/generated_poc/enums/delegation_authority.py b/src/adcp/types/generated_poc/enums/delegation_authority.py deleted file mode 100644 index d75356d18..000000000 --- a/src/adcp/types/generated_poc/enums/delegation_authority.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/delegation_authority.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class DelegationAuthority(StrEnum): - full = 'full' - execute_only = 'execute_only' - propose_only = 'propose_only' diff --git a/src/adcp/types/generated_poc/enums/delivery_recipient_cloud.py b/src/adcp/types/generated_poc/enums/delivery_recipient_cloud.py deleted file mode 100644 index 2890eed5b..000000000 --- a/src/adcp/types/generated_poc/enums/delivery_recipient_cloud.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/delivery_recipient_cloud.json -# timestamp: 2026-09-01T19:37:00+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class DeliveryRecipientCloud(StrEnum): - aws = 'aws' - azure = 'azure' - gcp = 'gcp' diff --git a/src/adcp/types/generated_poc/enums/delivery_status.py b/src/adcp/types/generated_poc/enums/delivery_status.py deleted file mode 100644 index 8c31d99f7..000000000 --- a/src/adcp/types/generated_poc/enums/delivery_status.py +++ /dev/null @@ -1,16 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/delivery_status.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class DeliveryStatus(StrEnum): - delivering = 'delivering' - not_delivering = 'not_delivering' - completed = 'completed' - budget_exhausted = 'budget_exhausted' - flight_ended = 'flight_ended' - goal_met = 'goal_met' diff --git a/src/adcp/types/generated_poc/enums/delivery_type.py b/src/adcp/types/generated_poc/enums/delivery_type.py deleted file mode 100644 index 451f4a0b3..000000000 --- a/src/adcp/types/generated_poc/enums/delivery_type.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/delivery_type.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class DeliveryType(StrEnum): - guaranteed = 'guaranteed' - non_guaranteed = 'non_guaranteed' diff --git a/src/adcp/types/generated_poc/enums/demographic_system.py b/src/adcp/types/generated_poc/enums/demographic_system.py deleted file mode 100644 index 098be4165..000000000 --- a/src/adcp/types/generated_poc/enums/demographic_system.py +++ /dev/null @@ -1,17 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/demographic_system.json -# timestamp: 2026-08-28T20:03:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class DemographicSystem(StrEnum): - nielsen = 'nielsen' - nielsen_audio = 'nielsen_audio' - barb = 'barb' - agf = 'agf' - oztam = 'oztam' - mediametrie = 'mediametrie' - custom = 'custom' diff --git a/src/adcp/types/generated_poc/enums/derivative_type.py b/src/adcp/types/generated_poc/enums/derivative_type.py deleted file mode 100644 index 687eed621..000000000 --- a/src/adcp/types/generated_poc/enums/derivative_type.py +++ /dev/null @@ -1,15 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/derivative_type.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class DerivativeType(StrEnum): - clip = 'clip' - highlight = 'highlight' - recap = 'recap' - trailer = 'trailer' - bonus = 'bonus' diff --git a/src/adcp/types/generated_poc/enums/device_platform.py b/src/adcp/types/generated_poc/enums/device_platform.py deleted file mode 100644 index 3c9df30bf..000000000 --- a/src/adcp/types/generated_poc/enums/device_platform.py +++ /dev/null @@ -1,22 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/device_platform.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class DevicePlatform(StrEnum): - ios = 'ios' - android = 'android' - windows = 'windows' - macos = 'macos' - linux = 'linux' - chromeos = 'chromeos' - tvos = 'tvos' - tizen = 'tizen' - webos = 'webos' - fire_os = 'fire_os' - roku_os = 'roku_os' - unknown = 'unknown' diff --git a/src/adcp/types/generated_poc/enums/device_type.py b/src/adcp/types/generated_poc/enums/device_type.py deleted file mode 100644 index 625d9c7f4..000000000 --- a/src/adcp/types/generated_poc/enums/device_type.py +++ /dev/null @@ -1,16 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/device_type.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class DeviceType(StrEnum): - desktop = 'desktop' - mobile = 'mobile' - tablet = 'tablet' - ctv = 'ctv' - dooh = 'dooh' - unknown = 'unknown' diff --git a/src/adcp/types/generated_poc/enums/digital_source_type.py b/src/adcp/types/generated_poc/enums/digital_source_type.py deleted file mode 100644 index 5d0756beb..000000000 --- a/src/adcp/types/generated_poc/enums/digital_source_type.py +++ /dev/null @@ -1,19 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/digital_source_type.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' diff --git a/src/adcp/types/generated_poc/enums/dimension_unit.py b/src/adcp/types/generated_poc/enums/dimension_unit.py deleted file mode 100644 index 78dfa62f5..000000000 --- a/src/adcp/types/generated_poc/enums/dimension_unit.py +++ /dev/null @@ -1,16 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/dimension_unit.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class DimensionUnit(StrEnum): - px = 'px' - dp = 'dp' - inches = 'inches' - cm = 'cm' - mm = 'mm' - pt = 'pt' diff --git a/src/adcp/types/generated_poc/enums/disclosure_persistence.py b/src/adcp/types/generated_poc/enums/disclosure_persistence.py deleted file mode 100644 index e8fdecfa4..000000000 --- a/src/adcp/types/generated_poc/enums/disclosure_persistence.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/disclosure_persistence.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' diff --git a/src/adcp/types/generated_poc/enums/disclosure_position.py b/src/adcp/types/generated_poc/enums/disclosure_position.py deleted file mode 100644 index 1ddf30646..000000000 --- a/src/adcp/types/generated_poc/enums/disclosure_position.py +++ /dev/null @@ -1,18 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/disclosure_position.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' diff --git a/src/adcp/types/generated_poc/enums/distance_unit.py b/src/adcp/types/generated_poc/enums/distance_unit.py deleted file mode 100644 index 4539bfd49..000000000 --- a/src/adcp/types/generated_poc/enums/distance_unit.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/distance_unit.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class DistanceUnit(StrEnum): - km = 'km' - mi = 'mi' - m = 'm' diff --git a/src/adcp/types/generated_poc/enums/distribution_identifier_type.py b/src/adcp/types/generated_poc/enums/distribution_identifier_type.py deleted file mode 100644 index 672e88bd5..000000000 --- a/src/adcp/types/generated_poc/enums/distribution_identifier_type.py +++ /dev/null @@ -1,34 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/distribution_identifier_type.json -# timestamp: 2026-09-01T19:37:00+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class DistributionIdentifierType(StrEnum): - apple_podcast_id = 'apple_podcast_id' - spotify_collection_id = 'spotify_collection_id' - rss_url = 'rss_url' - podcast_guid = 'podcast_guid' - amazon_music_id = 'amazon_music_id' - iheart_id = 'iheart_id' - podcast_index_id = 'podcast_index_id' - youtube_channel_id = 'youtube_channel_id' - youtube_channel_handle = 'youtube_channel_handle' - youtube_channel_url = 'youtube_channel_url' - youtube_playlist_id = 'youtube_playlist_id' - amazon_title_id = 'amazon_title_id' - platform_channel_id = 'platform_channel_id' - roku_channel_id = 'roku_channel_id' - pluto_channel_id = 'pluto_channel_id' - tubi_id = 'tubi_id' - peacock_id = 'peacock_id' - tiktok_id = 'tiktok_id' - twitch_channel = 'twitch_channel' - imdb_id = 'imdb_id' - gracenote_id = 'gracenote_id' - eidr_id = 'eidr_id' - domain = 'domain' - substack_id = 'substack_id' diff --git a/src/adcp/types/generated_poc/enums/dooh_motion_type.py b/src/adcp/types/generated_poc/enums/dooh_motion_type.py deleted file mode 100644 index 81b1cb59a..000000000 --- a/src/adcp/types/generated_poc/enums/dooh_motion_type.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/dooh_motion_type.json -# timestamp: 2026-09-01T19:37:00+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class DoohMotionType(StrEnum): - full_motion = 'full_motion' - partial_motion = 'partial_motion' - static = 'static' diff --git a/src/adcp/types/generated_poc/enums/embedded_provenance_method.py b/src/adcp/types/generated_poc/enums/embedded_provenance_method.py deleted file mode 100644 index 89f8eeda5..000000000 --- a/src/adcp/types/generated_poc/enums/embedded_provenance_method.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/embedded_provenance_method.json -# timestamp: 2026-05-22T13:15:12+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class EmbeddedProvenanceMethod(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' diff --git a/src/adcp/types/generated_poc/enums/error_code.py b/src/adcp/types/generated_poc/enums/error_code.py deleted file mode 100644 index 463df271b..000000000 --- a/src/adcp/types/generated_poc/enums/error_code.py +++ /dev/null @@ -1,130 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/error_code.json -# timestamp: 2026-09-24T08:59:48+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class ErrorCode(StrEnum): - INVALID_REQUEST = 'INVALID_REQUEST' - AUTH_REQUIRED = 'AUTH_REQUIRED' - AUTH_MISSING = 'AUTH_MISSING' - AUTH_INVALID = 'AUTH_INVALID' - AUTHORIZATION_REQUIRED = 'AUTHORIZATION_REQUIRED' - RATE_LIMITED = 'RATE_LIMITED' - SERVICE_UNAVAILABLE = 'SERVICE_UNAVAILABLE' - CONFIGURATION_ERROR = 'CONFIGURATION_ERROR' - POLICY_VIOLATION = 'POLICY_VIOLATION' - PRODUCT_NOT_FOUND = 'PRODUCT_NOT_FOUND' - PRODUCT_UNAVAILABLE = 'PRODUCT_UNAVAILABLE' - PROPOSAL_EXPIRED = 'PROPOSAL_EXPIRED' - BUDGET_TOO_LOW = 'BUDGET_TOO_LOW' - CREATIVE_REJECTED = 'CREATIVE_REJECTED' - CREATIVE_SIZE_MISMATCH = 'CREATIVE_SIZE_MISMATCH' - CREATIVE_MISSING_CLICK_URL = 'CREATIVE_MISSING_CLICK_URL' - CREATIVE_VALIDATION_FAILED_GENERIC = 'CREATIVE_VALIDATION_FAILED_GENERIC' - CREATIVE_LOCALE_NOT_ACCEPTED = 'CREATIVE_LOCALE_NOT_ACCEPTED' - CREATIVE_VALUE_NOT_ALLOWED = 'CREATIVE_VALUE_NOT_ALLOWED' - CREATIVE_REVISION_CONTENT_MISMATCH = 'CREATIVE_REVISION_CONTENT_MISMATCH' - UNSUPPORTED_FEATURE = 'UNSUPPORTED_FEATURE' - UNPRICEABLE_OUTPUT = 'UNPRICEABLE_OUTPUT' - UNSUPPORTED_GRANULARITY = 'UNSUPPORTED_GRANULARITY' - UNSUPPORTED_PROVISIONING = 'UNSUPPORTED_PROVISIONING' - AUDIENCE_TOO_SMALL = 'AUDIENCE_TOO_SMALL' - ACCOUNT_REQUIRED = 'ACCOUNT_REQUIRED' - ACCOUNT_NOT_FOUND = 'ACCOUNT_NOT_FOUND' - ACCOUNT_MOVED = 'ACCOUNT_MOVED' - ACCOUNT_IDENTITY_CONFLICT = 'ACCOUNT_IDENTITY_CONFLICT' - ACCOUNT_SETUP_REQUIRED = 'ACCOUNT_SETUP_REQUIRED' - ACCOUNT_AMBIGUOUS = 'ACCOUNT_AMBIGUOUS' - ACCOUNT_PAYMENT_REQUIRED = 'ACCOUNT_PAYMENT_REQUIRED' - ACCOUNT_SUSPENDED = 'ACCOUNT_SUSPENDED' - COMPLIANCE_UNSATISFIED = 'COMPLIANCE_UNSATISFIED' - GOVERNANCE_DENIED = 'GOVERNANCE_DENIED' - BUDGET_EXHAUSTED = 'BUDGET_EXHAUSTED' - BUDGET_EXCEEDED = 'BUDGET_EXCEEDED' - BUDGET_CAP_REACHED = 'BUDGET_CAP_REACHED' - CONFLICT = 'CONFLICT' - COMMITTED_RESOURCE_PURGED = 'COMMITTED_RESOURCE_PURGED' - IDEMPOTENCY_CONFLICT = 'IDEMPOTENCY_CONFLICT' - IDEMPOTENCY_EXPIRED = 'IDEMPOTENCY_EXPIRED' - IDEMPOTENCY_IN_FLIGHT = 'IDEMPOTENCY_IN_FLIGHT' - CURSOR_EXPIRED = 'CURSOR_EXPIRED' - CREATIVE_DEADLINE_EXCEEDED = 'CREATIVE_DEADLINE_EXCEEDED' - CREATIVE_INACCESSIBLE = 'CREATIVE_INACCESSIBLE' - INVALID_STATE = 'INVALID_STATE' - MEDIA_BUY_NOT_FOUND = 'MEDIA_BUY_NOT_FOUND' - NOT_CANCELLABLE = 'NOT_CANCELLABLE' - PACKAGE_NOT_FOUND = 'PACKAGE_NOT_FOUND' - PLACE_TARGET_UNAVAILABLE = 'PLACE_TARGET_UNAVAILABLE' - CREATIVE_NOT_FOUND = 'CREATIVE_NOT_FOUND' - SIGNAL_NOT_FOUND = 'SIGNAL_NOT_FOUND' - SIGNAL_TARGETING_INCOMPATIBLE = 'SIGNAL_TARGETING_INCOMPATIBLE' - SESSION_NOT_FOUND = 'SESSION_NOT_FOUND' - PLAN_NOT_FOUND = 'PLAN_NOT_FOUND' - REFERENCE_NOT_FOUND = 'REFERENCE_NOT_FOUND' - SESSION_TERMINATED = 'SESSION_TERMINATED' - VALIDATION_ERROR = 'VALIDATION_ERROR' - PRODUCT_EXPIRED = 'PRODUCT_EXPIRED' - PROPOSAL_NOT_COMMITTED = 'PROPOSAL_NOT_COMMITTED' - PROPOSAL_NOT_FOUND = 'PROPOSAL_NOT_FOUND' - MULTI_FINALIZE_UNSUPPORTED = 'MULTI_FINALIZE_UNSUPPORTED' - IO_REQUIRED = 'IO_REQUIRED' - TERMS_REJECTED = 'TERMS_REJECTED' - BIDDING_PLACEMENT_CONFLICT = 'BIDDING_PLACEMENT_CONFLICT' - AMBIGUOUS_BIDDING_POLICY = 'AMBIGUOUS_BIDDING_POLICY' - CONFLICTING_SELECTORS = 'CONFLICTING_SELECTORS' - REQUOTE_REQUIRED = 'REQUOTE_REQUIRED' - VERSION_UNSUPPORTED = 'VERSION_UNSUPPORTED' - CAMPAIGN_SUSPENDED = 'CAMPAIGN_SUSPENDED' - GOVERNANCE_UNAVAILABLE = 'GOVERNANCE_UNAVAILABLE' - GOVERNANCE_AGENT_NOT_ACCEPTED = 'GOVERNANCE_AGENT_NOT_ACCEPTED' - PERMISSION_DENIED = 'PERMISSION_DENIED' - SCOPE_INSUFFICIENT = 'SCOPE_INSUFFICIENT' - READ_ONLY_SCOPE = 'READ_ONLY_SCOPE' - FIELD_NOT_PERMITTED = 'FIELD_NOT_PERMITTED' - PROVENANCE_REQUIRED = 'PROVENANCE_REQUIRED' - PROVENANCE_DIGITAL_SOURCE_TYPE_MISSING = 'PROVENANCE_DIGITAL_SOURCE_TYPE_MISSING' - PROVENANCE_SYNTHETIC_DEPICTION_MISSING = 'PROVENANCE_SYNTHETIC_DEPICTION_MISSING' - PROVENANCE_DISCLOSURE_MISSING = 'PROVENANCE_DISCLOSURE_MISSING' - PROVENANCE_EMBEDDED_MISSING = 'PROVENANCE_EMBEDDED_MISSING' - PROVENANCE_VERIFIER_NOT_ACCEPTED = 'PROVENANCE_VERIFIER_NOT_ACCEPTED' - PROVENANCE_CLAIM_CONTRADICTED = 'PROVENANCE_CLAIM_CONTRADICTED' - EVALUATOR_AGENT_NOT_ACCEPTED = 'EVALUATOR_AGENT_NOT_ACCEPTED' - BILLING_NOT_SUPPORTED = 'BILLING_NOT_SUPPORTED' - BILLING_NOT_PERMITTED_FOR_AGENT = 'BILLING_NOT_PERMITTED_FOR_AGENT' - BILLING_OUT_OF_BAND = 'BILLING_OUT_OF_BAND' - PAYMENT_TERMS_NOT_SUPPORTED = 'PAYMENT_TERMS_NOT_SUPPORTED' - BRAND_REQUIRED = 'BRAND_REQUIRED' - AGENT_SUSPENDED = 'AGENT_SUSPENDED' - AGENT_BLOCKED = 'AGENT_BLOCKED' - CREDENTIAL_IN_ARGS = 'CREDENTIAL_IN_ARGS' - ACTION_NOT_ALLOWED = 'ACTION_NOT_ALLOWED' - PRIVATE_FIELD_IN_PUBLIC_PLACEMENT = 'PRIVATE_FIELD_IN_PUBLIC_PLACEMENT' - FORMAT_PROJECTION_FAILED = 'FORMAT_PROJECTION_FAILED' - FORMAT_DECLARATION_DIVERGENT = 'FORMAT_DECLARATION_DIVERGENT' - FORMAT_SHAPE_PROMOTED = 'FORMAT_SHAPE_PROMOTED' - FORMAT_DECLARATION_V1_AMBIGUOUS = 'FORMAT_DECLARATION_V1_AMBIGUOUS' - FORMAT_OPTION_UNRESOLVED = 'FORMAT_OPTION_UNRESOLVED' - FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE = 'FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE' - FORMAT_NOT_SUPPORTED = 'FORMAT_NOT_SUPPORTED' - PIXEL_TRACKER_LOSSY_DOWNGRADE = 'PIXEL_TRACKER_LOSSY_DOWNGRADE' - PIXEL_TRACKER_UPGRADE_INFERRED = 'PIXEL_TRACKER_UPGRADE_INFERRED' - STALE_RESPONSE = 'STALE_RESPONSE' - FEED_FETCH_FAILED = 'FEED_FETCH_FAILED' - SOURCE_ACCESS_FAILED = 'SOURCE_ACCESS_FAILED' - INVALID_FEED_FORMAT = 'INVALID_FEED_FORMAT' - ITEM_VALIDATION_FAILED = 'ITEM_VALIDATION_FAILED' - CATALOG_LIMIT_EXCEEDED = 'CATALOG_LIMIT_EXCEEDED' - INVALID_PRICING_OPTION = 'INVALID_PRICING_OPTION' - INVALID_USAGE_DATA = 'INVALID_USAGE_DATA' - SIGNED_RESPONSE_ENVELOPE_EXPIRED = 'SIGNED_RESPONSE_ENVELOPE_EXPIRED' - SIGNED_RESPONSE_REQUEST_HASH_MISMATCH = 'SIGNED_RESPONSE_REQUEST_HASH_MISMATCH' - SIGNED_RESPONSE_TENANT_MISMATCH = 'SIGNED_RESPONSE_TENANT_MISMATCH' - VAST_PARSE_FAILED = 'VAST_PARSE_FAILED' - VAST_VERSION_MISMATCH = 'VAST_VERSION_MISMATCH' - VAST_WRAPPER_DEPTH_EXCEEDED = 'VAST_WRAPPER_DEPTH_EXCEEDED' - CREATIVE_REPRESENTATION_UNRESOLVED = 'CREATIVE_REPRESENTATION_UNRESOLVED' - MACRO_RESOLUTION_FAILED = 'MACRO_RESOLUTION_FAILED' diff --git a/src/adcp/types/generated_poc/enums/error_scope.py b/src/adcp/types/generated_poc/enums/error_scope.py deleted file mode 100644 index c61b362ac..000000000 --- a/src/adcp/types/generated_poc/enums/error_scope.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/error_scope.json -# timestamp: 2026-05-22T13:15:12+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class ErrorScope(StrEnum): - capability = 'capability' - account = 'account' - agent = 'agent' diff --git a/src/adcp/types/generated_poc/enums/escalation_severity.py b/src/adcp/types/generated_poc/enums/escalation_severity.py deleted file mode 100644 index e93e4bde0..000000000 --- a/src/adcp/types/generated_poc/enums/escalation_severity.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/escalation_severity.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class EscalationSeverity(StrEnum): - info = 'info' - warning = 'warning' - critical = 'critical' diff --git a/src/adcp/types/generated_poc/enums/event_type.py b/src/adcp/types/generated_poc/enums/event_type.py deleted file mode 100644 index 8ad76c33c..000000000 --- a/src/adcp/types/generated_poc/enums/event_type.py +++ /dev/null @@ -1,41 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/event_type.json -# timestamp: 2026-06-01T00:32:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class EventType(StrEnum): - page_view = 'page_view' - view_content = 'view_content' - select_content = 'select_content' - select_item = 'select_item' - search = 'search' - share = 'share' - add_to_cart = 'add_to_cart' - remove_from_cart = 'remove_from_cart' - viewed_cart = 'viewed_cart' - add_to_wishlist = 'add_to_wishlist' - initiate_checkout = 'initiate_checkout' - add_payment_info = 'add_payment_info' - purchase = 'purchase' - refund = 'refund' - lead = 'lead' - qualify_lead = 'qualify_lead' - close_convert_lead = 'close_convert_lead' - disqualify_lead = 'disqualify_lead' - complete_registration = 'complete_registration' - subscribe = 'subscribe' - follow = 'follow' - content_view = 'content_view' - watch_milestone = 'watch_milestone' - start_trial = 'start_trial' - app_install = 'app_install' - app_launch = 'app_launch' - contact = 'contact' - schedule = 'schedule' - donate = 'donate' - submit_application = 'submit_application' - custom = 'custom' diff --git a/src/adcp/types/generated_poc/enums/exclusivity.py b/src/adcp/types/generated_poc/enums/exclusivity.py deleted file mode 100644 index 14a926862..000000000 --- a/src/adcp/types/generated_poc/enums/exclusivity.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/exclusivity.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class Exclusivity(StrEnum): - none = 'none' - category = 'category' - exclusive = 'exclusive' diff --git a/src/adcp/types/generated_poc/enums/feature_check_status.py b/src/adcp/types/generated_poc/enums/feature_check_status.py deleted file mode 100644 index 94b6c78a6..000000000 --- a/src/adcp/types/generated_poc/enums/feature_check_status.py +++ /dev/null @@ -1,14 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/feature_check_status.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class FeatureCheckStatus(StrEnum): - passed = 'passed' - failed = 'failed' - warning = 'warning' - unevaluated = 'unevaluated' diff --git a/src/adcp/types/generated_poc/enums/feed_format.py b/src/adcp/types/generated_poc/enums/feed_format.py deleted file mode 100644 index 2164a7cee..000000000 --- a/src/adcp/types/generated_poc/enums/feed_format.py +++ /dev/null @@ -1,18 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/feed_format.json -# timestamp: 2026-06-04T19:44:00+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class FeedFormat(StrEnum): - google_merchant_center = 'google_merchant_center' - facebook_catalog = 'facebook_catalog' - shopify = 'shopify' - linkedin_jobs = 'linkedin_jobs' - tiktok_shop = 'tiktok_shop' - pinterest_catalog = 'pinterest_catalog' - openai_product_feed = 'openai_product_feed' - custom = 'custom' diff --git a/src/adcp/types/generated_poc/enums/feedback_source.py b/src/adcp/types/generated_poc/enums/feedback_source.py deleted file mode 100644 index 7e163d1b2..000000000 --- a/src/adcp/types/generated_poc/enums/feedback_source.py +++ /dev/null @@ -1,14 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/feedback_source.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class FeedbackSource(StrEnum): - buyer_attribution = 'buyer_attribution' - third_party_measurement = 'third_party_measurement' - platform_analytics = 'platform_analytics' - verification_partner = 'verification_partner' diff --git a/src/adcp/types/generated_poc/enums/forecast_method.py b/src/adcp/types/generated_poc/enums/forecast_method.py deleted file mode 100644 index 0bd062137..000000000 --- a/src/adcp/types/generated_poc/enums/forecast_method.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/forecast_method.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class ForecastMethod(StrEnum): - estimate = 'estimate' - modeled = 'modeled' - guaranteed = 'guaranteed' diff --git a/src/adcp/types/generated_poc/enums/forecast_range_unit.py b/src/adcp/types/generated_poc/enums/forecast_range_unit.py deleted file mode 100644 index 1df752c19..000000000 --- a/src/adcp/types/generated_poc/enums/forecast_range_unit.py +++ /dev/null @@ -1,18 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/forecast_range_unit.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class ForecastRangeUnit(StrEnum): - spend = 'spend' - availability = 'availability' - reach_freq = 'reach_freq' - weekly = 'weekly' - daily = 'daily' - clicks = 'clicks' - conversions = 'conversions' - package = 'package' diff --git a/src/adcp/types/generated_poc/enums/forecastable_metric.py b/src/adcp/types/generated_poc/enums/forecastable_metric.py deleted file mode 100644 index 5285d3688..000000000 --- a/src/adcp/types/generated_poc/enums/forecastable_metric.py +++ /dev/null @@ -1,27 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/forecastable_metric.json -# timestamp: 2026-05-28T10:34:10+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class ForecastableMetric(StrEnum): - audience_size = 'audience_size' - reach = 'reach' - frequency = 'frequency' - impressions = 'impressions' - clicks = 'clicks' - spend = 'spend' - views = 'views' - completed_views = 'completed_views' - grps = 'grps' - engagements = 'engagements' - follows = 'follows' - saves = 'saves' - profile_visits = 'profile_visits' - measured_impressions = 'measured_impressions' - downloads = 'downloads' - plays = 'plays' - coverage_rate = 'coverage_rate' diff --git a/src/adcp/types/generated_poc/enums/format_id_parameter.py b/src/adcp/types/generated_poc/enums/format_id_parameter.py deleted file mode 100644 index 29a761618..000000000 --- a/src/adcp/types/generated_poc/enums/format_id_parameter.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/format_id_parameter.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class FormatIdParameter(StrEnum): - dimensions = 'dimensions' - duration = 'duration' - pixel_ratio = 'pixel_ratio' diff --git a/src/adcp/types/generated_poc/enums/frame_rate_type.py b/src/adcp/types/generated_poc/enums/frame_rate_type.py deleted file mode 100644 index cfaebdd75..000000000 --- a/src/adcp/types/generated_poc/enums/frame_rate_type.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/frame_rate_type.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class FrameRateType(StrEnum): - constant = 'constant' - variable = 'variable' diff --git a/src/adcp/types/generated_poc/enums/frequency_cap_control_mode.py b/src/adcp/types/generated_poc/enums/frequency_cap_control_mode.py deleted file mode 100644 index 79e5015d6..000000000 --- a/src/adcp/types/generated_poc/enums/frequency_cap_control_mode.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/frequency_cap_control_mode.json -# timestamp: 2026-09-14T14:16:02+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class FrequencyCapControlMode(StrEnum): - max_impressions = 'max_impressions' - suppress = 'suppress' - max_impressions_and_suppress = 'max_impressions_and_suppress' diff --git a/src/adcp/types/generated_poc/enums/frequency_cap_mutable_field.py b/src/adcp/types/generated_poc/enums/frequency_cap_mutable_field.py deleted file mode 100644 index aab069855..000000000 --- a/src/adcp/types/generated_poc/enums/frequency_cap_mutable_field.py +++ /dev/null @@ -1,14 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/frequency_cap_mutable_field.json -# timestamp: 2026-09-14T14:16:02+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class FrequencyCapMutableField(StrEnum): - max_impressions = 'max_impressions' - per = 'per' - window = 'window' - suppress = 'suppress' diff --git a/src/adcp/types/generated_poc/enums/frequency_cap_scope.py b/src/adcp/types/generated_poc/enums/frequency_cap_scope.py deleted file mode 100644 index ff99c43fe..000000000 --- a/src/adcp/types/generated_poc/enums/frequency_cap_scope.py +++ /dev/null @@ -1,16 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/frequency_cap_scope.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from pydantic import Field, RootModel - - -class FrequencyCapScope(RootModel[Literal['package']]): - root: Annotated[ - Literal['package'], - Field(description='Scope for frequency cap application', title='Frequency Cap Scope'), - ] = 'package' diff --git a/src/adcp/types/generated_poc/enums/genre_taxonomy.py b/src/adcp/types/generated_poc/enums/genre_taxonomy.py deleted file mode 100644 index 6b6e10411..000000000 --- a/src/adcp/types/generated_poc/enums/genre_taxonomy.py +++ /dev/null @@ -1,19 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/genre_taxonomy.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class GenreTaxonomy(StrEnum): - iab_content_3_0 = 'iab_content_3.0' - iab_content_2_2 = 'iab_content_2.2' - gracenote = 'gracenote' - eidr = 'eidr' - apple_genres = 'apple_genres' - google_genres = 'google_genres' - roku = 'roku' - amazon_genres = 'amazon_genres' - custom = 'custom' diff --git a/src/adcp/types/generated_poc/enums/geo_level.py b/src/adcp/types/generated_poc/enums/geo_level.py deleted file mode 100644 index 59d181519..000000000 --- a/src/adcp/types/generated_poc/enums/geo_level.py +++ /dev/null @@ -1,14 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/geo_level.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class GeographicTargetingLevel(StrEnum): - country = 'country' - region = 'region' - metro = 'metro' - postal_area = 'postal_area' diff --git a/src/adcp/types/generated_poc/enums/gop_type.py b/src/adcp/types/generated_poc/enums/gop_type.py deleted file mode 100644 index 046f84f52..000000000 --- a/src/adcp/types/generated_poc/enums/gop_type.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/gop_type.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class GopType(StrEnum): - closed = 'closed' - open = 'open' diff --git a/src/adcp/types/generated_poc/enums/governance_decision.py b/src/adcp/types/generated_poc/enums/governance_decision.py deleted file mode 100644 index e514adc93..000000000 --- a/src/adcp/types/generated_poc/enums/governance_decision.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/governance_decision.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class GovernanceDecision(StrEnum): - approved = 'approved' - denied = 'denied' - conditions = 'conditions' diff --git a/src/adcp/types/generated_poc/enums/governance_domain.py b/src/adcp/types/generated_poc/enums/governance_domain.py deleted file mode 100644 index 6ff6fe057..000000000 --- a/src/adcp/types/generated_poc/enums/governance_domain.py +++ /dev/null @@ -1,14 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/governance_domain.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class GovernanceDomain(StrEnum): - campaign = 'campaign' - property = 'property' - creative = 'creative' - content_standards = 'content_standards' diff --git a/src/adcp/types/generated_poc/enums/governance_mode.py b/src/adcp/types/generated_poc/enums/governance_mode.py deleted file mode 100644 index c10cc406b..000000000 --- a/src/adcp/types/generated_poc/enums/governance_mode.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/governance_mode.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class GovernanceMode(StrEnum): - audit = 'audit' - advisory = 'advisory' - enforce = 'enforce' diff --git a/src/adcp/types/generated_poc/enums/governance_phase.py b/src/adcp/types/generated_poc/enums/governance_phase.py deleted file mode 100644 index cac3da2ff..000000000 --- a/src/adcp/types/generated_poc/enums/governance_phase.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/governance_phase.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class GovernancePhase(StrEnum): - purchase = 'purchase' - modification = 'modification' - delivery = 'delivery' diff --git a/src/adcp/types/generated_poc/enums/history_entry_type.py b/src/adcp/types/generated_poc/enums/history_entry_type.py deleted file mode 100644 index 54e41d952..000000000 --- a/src/adcp/types/generated_poc/enums/history_entry_type.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/history_entry_type.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class HistoryEntryType(StrEnum): - request = 'request' - response = 'response' diff --git a/src/adcp/types/generated_poc/enums/http_method.py b/src/adcp/types/generated_poc/enums/http_method.py deleted file mode 100644 index 37703d3e3..000000000 --- a/src/adcp/types/generated_poc/enums/http_method.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/http_method.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class HttpMethod(StrEnum): - GET = 'GET' - POST = 'POST' diff --git a/src/adcp/types/generated_poc/enums/identifier_types.py b/src/adcp/types/generated_poc/enums/identifier_types.py deleted file mode 100644 index ed39b9bb3..000000000 --- a/src/adcp/types/generated_poc/enums/identifier_types.py +++ /dev/null @@ -1,31 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/identifier_types.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class PropertyIdentifierTypes(StrEnum): - domain = 'domain' - subdomain = 'subdomain' - network_id = 'network_id' - ios_bundle = 'ios_bundle' - android_package = 'android_package' - apple_app_store_id = 'apple_app_store_id' - google_play_id = 'google_play_id' - roku_store_id = 'roku_store_id' - fire_tv_asin = 'fire_tv_asin' - samsung_app_id = 'samsung_app_id' - apple_tv_bundle = 'apple_tv_bundle' - bundle_id = 'bundle_id' - venue_id = 'venue_id' - screen_id = 'screen_id' - openooh_venue_type = 'openooh_venue_type' - rss_url = 'rss_url' - apple_podcast_id = 'apple_podcast_id' - spotify_collection_id = 'spotify_collection_id' - podcast_guid = 'podcast_guid' - station_id = 'station_id' - facility_id = 'facility_id' diff --git a/src/adcp/types/generated_poc/enums/impairment_offline_state.py b/src/adcp/types/generated_poc/enums/impairment_offline_state.py deleted file mode 100644 index 68af7bd2b..000000000 --- a/src/adcp/types/generated_poc/enums/impairment_offline_state.py +++ /dev/null @@ -1,15 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/impairment_offline_state.json -# timestamp: 2026-05-22T13:15:12+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class ImpairmentOfflineState(StrEnum): - suspended = 'suspended' - rejected = 'rejected' - withdrawn = 'withdrawn' - insufficient = 'insufficient' - depublished = 'depublished' diff --git a/src/adcp/types/generated_poc/enums/impairment_reason_code.py b/src/adcp/types/generated_poc/enums/impairment_reason_code.py deleted file mode 100644 index a0127595a..000000000 --- a/src/adcp/types/generated_poc/enums/impairment_reason_code.py +++ /dev/null @@ -1,21 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/impairment_reason_code.json -# timestamp: 2026-06-04T19:44:00+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class ImpairmentReasonCode(StrEnum): - policy_violation = 'policy_violation' - consent_expired = 'consent_expired' - ttl_expired = 'ttl_expired' - pii_audit_failed = 'pii_audit_failed' - seller_removed = 'seller_removed' - content_rejected = 'content_rejected' - identity_authorization_revoked = 'identity_authorization_revoked' - identity_authorization_expired = 'identity_authorization_expired' - source_private = 'source_private' - source_offline = 'source_offline' - property_depublished = 'property_depublished' diff --git a/src/adcp/types/generated_poc/enums/indicator_type.py b/src/adcp/types/generated_poc/enums/indicator_type.py deleted file mode 100644 index 3d224cd44..000000000 --- a/src/adcp/types/generated_poc/enums/indicator_type.py +++ /dev/null @@ -1,17 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/indicator_type.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class IndicatorType(StrEnum): - creative_fatigue = 'creative_fatigue' - creative_quality_opportunity = 'creative_quality_opportunity' - creative_diversity_low = 'creative_diversity_low' - audience_saturation = 'audience_saturation' - inventory_shortfall_forecast = 'inventory_shortfall_forecast' - pacing_risk = 'pacing_risk' - budget_constrained = 'budget_constrained' diff --git a/src/adcp/types/generated_poc/enums/installment_status.py b/src/adcp/types/generated_poc/enums/installment_status.py deleted file mode 100644 index 86a59a009..000000000 --- a/src/adcp/types/generated_poc/enums/installment_status.py +++ /dev/null @@ -1,17 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/installment_status.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class InstallmentStatus(StrEnum): - scheduled = 'scheduled' - tentative = 'tentative' - live = 'live' - postponed = 'postponed' - cancelled = 'cancelled' - aired = 'aired' - published = 'published' diff --git a/src/adcp/types/generated_poc/enums/javascript_module_type.py b/src/adcp/types/generated_poc/enums/javascript_module_type.py deleted file mode 100644 index 54cc6b94a..000000000 --- a/src/adcp/types/generated_poc/enums/javascript_module_type.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/javascript_module_type.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class JavascriptModuleType(StrEnum): - esm = 'esm' - commonjs = 'commonjs' - script = 'script' diff --git a/src/adcp/types/generated_poc/enums/landing_page_requirement.py b/src/adcp/types/generated_poc/enums/landing_page_requirement.py deleted file mode 100644 index f04267a0b..000000000 --- a/src/adcp/types/generated_poc/enums/landing_page_requirement.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/landing_page_requirement.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class LandingPageRequirement(StrEnum): - any = 'any' - retailer_site_only = 'retailer_site_only' - must_include_retailer = 'must_include_retailer' diff --git a/src/adcp/types/generated_poc/enums/legacy_postal_system.py b/src/adcp/types/generated_poc/enums/legacy_postal_system.py deleted file mode 100644 index 441f3cd49..000000000 --- a/src/adcp/types/generated_poc/enums/legacy_postal_system.py +++ /dev/null @@ -1,21 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/legacy_postal_system.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class CountryFusedPostalCodeSystem(StrEnum): - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' diff --git a/src/adcp/types/generated_poc/enums/lift_dimension.py b/src/adcp/types/generated_poc/enums/lift_dimension.py deleted file mode 100644 index dc261a6bc..000000000 --- a/src/adcp/types/generated_poc/enums/lift_dimension.py +++ /dev/null @@ -1,15 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/lift_dimension.json -# timestamp: 2026-05-22T13:15:12+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class LiftDimension(StrEnum): - awareness = 'awareness' - consideration = 'consideration' - favorability = 'favorability' - purchase_intent = 'purchase_intent' - ad_recall = 'ad_recall' diff --git a/src/adcp/types/generated_poc/enums/logo_slot.py b/src/adcp/types/generated_poc/enums/logo_slot.py deleted file mode 100644 index 3a0eb2444..000000000 --- a/src/adcp/types/generated_poc/enums/logo_slot.py +++ /dev/null @@ -1,23 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/logo_slot.json -# timestamp: 2026-05-29T23:56:01+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class LogoSlot(StrEnum): - logo_card_light = 'logo_card_light' - logo_card_dark = 'logo_card_dark' - profile_mark = 'profile_mark' - favicon = 'favicon' - app_icon = 'app_icon' - social_profile_mark = 'social_profile_mark' - nav_header = 'nav_header' - footer = 'footer' - email_header = 'email_header' - watermark = 'watermark' - ad_end_card = 'ad_end_card' - co_brand_lockup = 'co_brand_lockup' - marketplace_listing = 'marketplace_listing' diff --git a/src/adcp/types/generated_poc/enums/macro_dialect.py b/src/adcp/types/generated_poc/enums/macro_dialect.py deleted file mode 100644 index 073f3a429..000000000 --- a/src/adcp/types/generated_poc/enums/macro_dialect.py +++ /dev/null @@ -1,15 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/macro_dialect.json -# timestamp: 2026-08-28T20:03:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class MacroDialectFamily(StrEnum): - adcp = 'adcp' - iab_vast = 'iab_vast' - iab_daast = 'iab_daast' - vendor = 'vendor' - unknown = 'unknown' diff --git a/src/adcp/types/generated_poc/enums/macro_mapping_status.py b/src/adcp/types/generated_poc/enums/macro_mapping_status.py deleted file mode 100644 index e07a5f952..000000000 --- a/src/adcp/types/generated_poc/enums/macro_mapping_status.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/macro_mapping_status.json -# timestamp: 2026-08-28T20:03:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class MacroMappingStatus(StrEnum): - verified_universal = 'verified_universal' - dialect_defined = 'dialect_defined' - unresolved = 'unresolved' diff --git a/src/adcp/types/generated_poc/enums/macro_processing_operation.py b/src/adcp/types/generated_poc/enums/macro_processing_operation.py deleted file mode 100644 index 2751d9928..000000000 --- a/src/adcp/types/generated_poc/enums/macro_processing_operation.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/macro_processing_operation.json -# timestamp: 2026-08-28T20:03:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class MacroProcessingOperation(StrEnum): - translate_to_native = 'translate_to_native' - resolve_value = 'resolve_value' - preserve = 'preserve' diff --git a/src/adcp/types/generated_poc/enums/macro_resolution_reason.py b/src/adcp/types/generated_poc/enums/macro_resolution_reason.py deleted file mode 100644 index 0c642fbac..000000000 --- a/src/adcp/types/generated_poc/enums/macro_resolution_reason.py +++ /dev/null @@ -1,22 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/macro_resolution_reason.json -# timestamp: 2026-08-28T20:03:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class MacroResolutionReason(StrEnum): - capability_match = 'capability_match' - preserved_unknown = 'preserved_unknown' - preserved_for_downstream = 'preserved_for_downstream' - dialect_unsupported = 'dialect_unsupported' - namespace_mismatch = 'namespace_mismatch' - revision_mismatch = 'revision_mismatch' - semantic_unsupported = 'semantic_unsupported' - operation_unsupported = 'operation_unsupported' - resolver_mismatch = 'resolver_mismatch' - context_unsupported = 'context_unsupported' - encoding_unsupported = 'encoding_unsupported' - ambiguous_mapping = 'ambiguous_mapping' diff --git a/src/adcp/types/generated_poc/enums/macro_resolver.py b/src/adcp/types/generated_poc/enums/macro_resolver.py deleted file mode 100644 index eecdd661a..000000000 --- a/src/adcp/types/generated_poc/enums/macro_resolver.py +++ /dev/null @@ -1,15 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/macro_resolver.json -# timestamp: 2026-08-28T20:03:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class MacroProcessingActor(StrEnum): - buyer = 'buyer' - creative_agent = 'creative_agent' - seller = 'seller' - request_executor = 'request_executor' - source_ad_server = 'source_ad_server' diff --git a/src/adcp/types/generated_poc/enums/macro_value_context.py b/src/adcp/types/generated_poc/enums/macro_value_context.py deleted file mode 100644 index ae277cad7..000000000 --- a/src/adcp/types/generated_poc/enums/macro_value_context.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/macro_value_context.json -# timestamp: 2026-08-28T20:03:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class MacroValueContext(StrEnum): - url_query_value = 'url_query_value' - url_path_segment = 'url_path_segment' - opaque = 'opaque' diff --git a/src/adcp/types/generated_poc/enums/makegood_remedy.py b/src/adcp/types/generated_poc/enums/makegood_remedy.py deleted file mode 100644 index a001cf603..000000000 --- a/src/adcp/types/generated_poc/enums/makegood_remedy.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/makegood_remedy.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class MakegoodRemedy(StrEnum): - additional_delivery = 'additional_delivery' - credit = 'credit' - invoice_adjustment = 'invoice_adjustment' diff --git a/src/adcp/types/generated_poc/enums/markdown_flavor.py b/src/adcp/types/generated_poc/enums/markdown_flavor.py deleted file mode 100644 index be75bdad4..000000000 --- a/src/adcp/types/generated_poc/enums/markdown_flavor.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/markdown_flavor.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class MarkdownFlavor(StrEnum): - commonmark = 'commonmark' - gfm = 'gfm' diff --git a/src/adcp/types/generated_poc/enums/match_id_type.py b/src/adcp/types/generated_poc/enums/match_id_type.py deleted file mode 100644 index d5afd91cd..000000000 --- a/src/adcp/types/generated_poc/enums/match_id_type.py +++ /dev/null @@ -1,19 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/match_id_type.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class MatchIdType(StrEnum): - hashed_email = 'hashed_email' - hashed_phone = 'hashed_phone' - rampid = 'rampid' - id5 = 'id5' - uid2 = 'uid2' - euid = 'euid' - pairid = 'pairid' - maid = 'maid' - other = 'other' diff --git a/src/adcp/types/generated_poc/enums/match_type.py b/src/adcp/types/generated_poc/enums/match_type.py deleted file mode 100644 index 7ced75155..000000000 --- a/src/adcp/types/generated_poc/enums/match_type.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/match_type.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class MatchType(StrEnum): - broad = 'broad' - phrase = 'phrase' - exact = 'exact' diff --git a/src/adcp/types/generated_poc/enums/media_buy_action_mode.py b/src/adcp/types/generated_poc/enums/media_buy_action_mode.py deleted file mode 100644 index cf2916d44..000000000 --- a/src/adcp/types/generated_poc/enums/media_buy_action_mode.py +++ /dev/null @@ -1,14 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/media_buy_action_mode.json -# timestamp: 2026-08-28T20:03:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class MediaBuyActionMode(StrEnum): - self_serve = 'self_serve' - conditional_self_serve = 'conditional_self_serve' - seller_managed = 'seller_managed' - requires_approval = 'requires_approval' diff --git a/src/adcp/types/generated_poc/enums/media_buy_frequency_cap_control_mode.py b/src/adcp/types/generated_poc/enums/media_buy_frequency_cap_control_mode.py deleted file mode 100644 index 3947843bb..000000000 --- a/src/adcp/types/generated_poc/enums/media_buy_frequency_cap_control_mode.py +++ /dev/null @@ -1,19 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/media_buy_frequency_cap_control_mode.json -# timestamp: 2026-09-14T14:16:02+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from pydantic import Field, RootModel - - -class MediaBuyFrequencyCapControlMode(RootModel[Literal['max_impressions']]): - root: Annotated[ - Literal['max_impressions'], - Field( - description='The aggregate MediaBuy frequency-cap control shape supported in 3.2.', - title='Media Buy Frequency Cap Control Mode', - ), - ] = 'max_impressions' diff --git a/src/adcp/types/generated_poc/enums/media_buy_health.py b/src/adcp/types/generated_poc/enums/media_buy_health.py deleted file mode 100644 index 15777e585..000000000 --- a/src/adcp/types/generated_poc/enums/media_buy_health.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/media_buy_health.json -# timestamp: 2026-05-22T13:15:12+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class MediaBuyHealth(StrEnum): - ok = 'ok' - impaired = 'impaired' diff --git a/src/adcp/types/generated_poc/enums/media_buy_status.py b/src/adcp/types/generated_poc/enums/media_buy_status.py deleted file mode 100644 index d6acb6292..000000000 --- a/src/adcp/types/generated_poc/enums/media_buy_status.py +++ /dev/null @@ -1,17 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/media_buy_status.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class MediaBuyStatus(StrEnum): - pending_creatives = 'pending_creatives' - pending_start = 'pending_start' - active = 'active' - paused = 'paused' - completed = 'completed' - rejected = 'rejected' - canceled = 'canceled' diff --git a/src/adcp/types/generated_poc/enums/media_buy_valid_action.py b/src/adcp/types/generated_poc/enums/media_buy_valid_action.py deleted file mode 100644 index 6de1c892b..000000000 --- a/src/adcp/types/generated_poc/enums/media_buy_valid_action.py +++ /dev/null @@ -1,34 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/media_buy_valid_action.json -# timestamp: 2026-08-21T04:35:18+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class MediaBuyValidAction(StrEnum): - pause = 'pause' - resume = 'resume' - cancel = 'cancel' - update_name = 'update_name' - extend_flight = 'extend_flight' - shorten_flight = 'shorten_flight' - update_flight_dates = 'update_flight_dates' - increase_budget = 'increase_budget' - decrease_budget = 'decrease_budget' - reallocate_budget = 'reallocate_budget' - update_budget_allocation = 'update_budget_allocation' - update_targeting = 'update_targeting' - update_pacing = 'update_pacing' - update_bidding = 'update_bidding' - update_frequency_caps = 'update_frequency_caps' - replace_creative = 'replace_creative' - update_creative_assignments = 'update_creative_assignments' - remove_creative = 'remove_creative' - add_packages = 'add_packages' - remove_packages = 'remove_packages' - update_budget = 'update_budget' - update_dates = 'update_dates' - update_packages = 'update_packages' - sync_creatives = 'sync_creatives' diff --git a/src/adcp/types/generated_poc/enums/metric_scope.py b/src/adcp/types/generated_poc/enums/metric_scope.py deleted file mode 100644 index 4e42b951e..000000000 --- a/src/adcp/types/generated_poc/enums/metric_scope.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/metric_scope.json -# timestamp: 2026-05-22T13:15:12+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class MetricScope(StrEnum): - standard = 'standard' - vendor = 'vendor' diff --git a/src/adcp/types/generated_poc/enums/metric_type.py b/src/adcp/types/generated_poc/enums/metric_type.py deleted file mode 100644 index 13b275fe5..000000000 --- a/src/adcp/types/generated_poc/enums/metric_type.py +++ /dev/null @@ -1,18 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/metric_type.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class MetricType(StrEnum): - overall_performance = 'overall_performance' - conversion_rate = 'conversion_rate' - brand_lift = 'brand_lift' - click_through_rate = 'click_through_rate' - completion_rate = 'completion_rate' - viewability = 'viewability' - brand_safety = 'brand_safety' - cost_efficiency = 'cost_efficiency' diff --git a/src/adcp/types/generated_poc/enums/metro_system.py b/src/adcp/types/generated_poc/enums/metro_system.py deleted file mode 100644 index 3fbfb4889..000000000 --- a/src/adcp/types/generated_poc/enums/metro_system.py +++ /dev/null @@ -1,15 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/metro_system.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class MetroAreaSystem(StrEnum): - nielsen_dma = 'nielsen_dma' - uk_itl1 = 'uk_itl1' - uk_itl2 = 'uk_itl2' - eurostat_nuts2 = 'eurostat_nuts2' - custom = 'custom' diff --git a/src/adcp/types/generated_poc/enums/moov_atom_position.py b/src/adcp/types/generated_poc/enums/moov_atom_position.py deleted file mode 100644 index aedc3e89b..000000000 --- a/src/adcp/types/generated_poc/enums/moov_atom_position.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/moov_atom_position.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class MoovAtomPosition(StrEnum): - start = 'start' - end = 'end' diff --git a/src/adcp/types/generated_poc/enums/motion_level.py b/src/adcp/types/generated_poc/enums/motion_level.py deleted file mode 100644 index 2e7595e3a..000000000 --- a/src/adcp/types/generated_poc/enums/motion_level.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/motion_level.json -# timestamp: 2026-08-24T04:35:32+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class CreativeMotionLevel(StrEnum): - static = 'static' - limited_motion = 'limited_motion' - full_motion = 'full_motion' diff --git a/src/adcp/types/generated_poc/enums/notification_type.py b/src/adcp/types/generated_poc/enums/notification_type.py deleted file mode 100644 index e3cd266b2..000000000 --- a/src/adcp/types/generated_poc/enums/notification_type.py +++ /dev/null @@ -1,36 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/notification_type.json -# timestamp: 2026-09-05T11:09:57+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class NotificationType(StrEnum): - scheduled = 'scheduled' - final = 'final' - delayed = 'delayed' - adjusted = 'adjusted' - window_update = 'window_update' - impairment = 'impairment' - creative_status_changed = 'creative.status_changed' - creative_assignment_changed = 'creative.assignment_changed' - indicators_changed = 'indicators.changed' - creative_purged = 'creative.purged' - account_status_changed = 'account.status_changed' - account_change_recorded = 'account.change_recorded' - product_created = 'product.created' - product_updated = 'product.updated' - product_priced = 'product.priced' - product_removed = 'product.removed' - signal_created = 'signal.created' - signal_updated = 'signal.updated' - signal_priced = 'signal.priced' - signal_removed = 'signal.removed' - wholesale_feed_bulk_change = 'wholesale_feed.bulk_change' - capabilities_changed = 'capabilities.changed' - reporting_delivery_ready = 'reporting.delivery_ready' - reporting_status_changed = 'reporting.status_changed' - reporting_ledger_changed = 'reporting.ledger_changed' - principal_changed = 'principal.changed' diff --git a/src/adcp/types/generated_poc/enums/offering_availability_status.py b/src/adcp/types/generated_poc/enums/offering_availability_status.py deleted file mode 100644 index 205d00dbc..000000000 --- a/src/adcp/types/generated_poc/enums/offering_availability_status.py +++ /dev/null @@ -1,16 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/offering_availability_status.json -# timestamp: 2026-06-04T19:44:00+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class OfferingAvailabilityStatus(StrEnum): - available = 'available' - limited = 'limited' - sold_out = 'sold_out' - expired = 'expired' - region_restricted = 'region_restricted' - inactive = 'inactive' diff --git a/src/adcp/types/generated_poc/enums/outcome_target_cost_strength.py b/src/adcp/types/generated_poc/enums/outcome_target_cost_strength.py deleted file mode 100644 index 0af7a9c4a..000000000 --- a/src/adcp/types/generated_poc/enums/outcome_target_cost_strength.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/outcome_target_cost_strength.json -# timestamp: 2026-10-01T07:57:26+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class OutcomeTargetCostStrength(StrEnum): - cap = 'cap' - target = 'target' diff --git a/src/adcp/types/generated_poc/enums/outcome_type.py b/src/adcp/types/generated_poc/enums/outcome_type.py deleted file mode 100644 index d109bfc00..000000000 --- a/src/adcp/types/generated_poc/enums/outcome_type.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/outcome_type.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class OutcomeType(StrEnum): - completed = 'completed' - failed = 'failed' - delivery = 'delivery' diff --git a/src/adcp/types/generated_poc/enums/pacing.py b/src/adcp/types/generated_poc/enums/pacing.py deleted file mode 100644 index 36aac63f6..000000000 --- a/src/adcp/types/generated_poc/enums/pacing.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/pacing.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class Pacing(StrEnum): - even = 'even' - asap = 'asap' - front_loaded = 'front_loaded' diff --git a/src/adcp/types/generated_poc/enums/payment_terms.py b/src/adcp/types/generated_poc/enums/payment_terms.py deleted file mode 100644 index c5f80d72f..000000000 --- a/src/adcp/types/generated_poc/enums/payment_terms.py +++ /dev/null @@ -1,16 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/payment_terms.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class PaymentTerms(StrEnum): - net_15 = 'net_15' - net_30 = 'net_30' - net_45 = 'net_45' - net_60 = 'net_60' - net_90 = 'net_90' - prepay = 'prepay' diff --git a/src/adcp/types/generated_poc/enums/performance_baseline.py b/src/adcp/types/generated_poc/enums/performance_baseline.py deleted file mode 100644 index 1c1ac0ed5..000000000 --- a/src/adcp/types/generated_poc/enums/performance_baseline.py +++ /dev/null @@ -1,16 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/performance_baseline.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class PerformanceBaseline(StrEnum): - campaign_target = 'campaign_target' - control_group = 'control_group' - seller_history = 'seller_history' - buyer_portfolio = 'buyer_portfolio' - market_benchmark = 'market_benchmark' - other = 'other' diff --git a/src/adcp/types/generated_poc/enums/performance_standard_metric.py b/src/adcp/types/generated_poc/enums/performance_standard_metric.py deleted file mode 100644 index f111a9130..000000000 --- a/src/adcp/types/generated_poc/enums/performance_standard_metric.py +++ /dev/null @@ -1,15 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/performance_standard_metric.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class PerformanceStandardMetric(StrEnum): - viewability = 'viewability' - ivt = 'ivt' - completion_rate = 'completion_rate' - brand_safety = 'brand_safety' - attention_score = 'attention_score' diff --git a/src/adcp/types/generated_poc/enums/pixel_tracking_event.py b/src/adcp/types/generated_poc/enums/pixel_tracking_event.py deleted file mode 100644 index 75933b160..000000000 --- a/src/adcp/types/generated_poc/enums/pixel_tracking_event.py +++ /dev/null @@ -1,17 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/pixel_tracking_event.json -# timestamp: 2026-08-28T20:03:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class PixelTrackingEvent(StrEnum): - impression = 'impression' - viewable_mrc_50 = 'viewable_mrc_50' - viewable_mrc_100 = 'viewable_mrc_100' - viewable_video_50 = 'viewable_video_50' - audible_video_complete = 'audible_video_complete' - click = 'click' - custom = 'custom' diff --git a/src/adcp/types/generated_poc/enums/policy_category.py b/src/adcp/types/generated_poc/enums/policy_category.py deleted file mode 100644 index 41c116f65..000000000 --- a/src/adcp/types/generated_poc/enums/policy_category.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/policy_category.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class PolicyCategory(StrEnum): - regulation = 'regulation' - standard = 'standard' diff --git a/src/adcp/types/generated_poc/enums/policy_enforcement.py b/src/adcp/types/generated_poc/enums/policy_enforcement.py deleted file mode 100644 index c1078b37d..000000000 --- a/src/adcp/types/generated_poc/enums/policy_enforcement.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/policy_enforcement.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class PolicyEnforcementLevel(StrEnum): - must = 'must' - should = 'should' - may = 'may' diff --git a/src/adcp/types/generated_poc/enums/postal_system.py b/src/adcp/types/generated_poc/enums/postal_system.py deleted file mode 100644 index 27cbd459f..000000000 --- a/src/adcp/types/generated_poc/enums/postal_system.py +++ /dev/null @@ -1,33 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/postal_system.json -# timestamp: 2026-06-07T22:46:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class PostalCodeSystem(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' diff --git a/src/adcp/types/generated_poc/enums/preview_output_format.py b/src/adcp/types/generated_poc/enums/preview_output_format.py deleted file mode 100644 index b1095e5e2..000000000 --- a/src/adcp/types/generated_poc/enums/preview_output_format.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/preview_output_format.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class PreviewOutputFormat(StrEnum): - url = 'url' - html = 'html' diff --git a/src/adcp/types/generated_poc/enums/pricing_model.py b/src/adcp/types/generated_poc/enums/pricing_model.py deleted file mode 100644 index 62c33a56b..000000000 --- a/src/adcp/types/generated_poc/enums/pricing_model.py +++ /dev/null @@ -1,20 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/pricing_model.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class PricingModel(StrEnum): - cpm = 'cpm' - vcpm = 'vcpm' - cpc = 'cpc' - cpcv = 'cpcv' - cpv = 'cpv' - cpp = 'cpp' - cpa = 'cpa' - revenue_share = 'revenue_share' - flat_rate = 'flat_rate' - time = 'time' diff --git a/src/adcp/types/generated_poc/enums/pricing_structure.py b/src/adcp/types/generated_poc/enums/pricing_structure.py deleted file mode 100644 index a77aecf79..000000000 --- a/src/adcp/types/generated_poc/enums/pricing_structure.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/pricing_structure.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class PricingStructure(StrEnum): - fixed = 'fixed' - auction = 'auction' - contingent = 'contingent' diff --git a/src/adcp/types/generated_poc/enums/principal_kind.py b/src/adcp/types/generated_poc/enums/principal_kind.py deleted file mode 100644 index 571ebec33..000000000 --- a/src/adcp/types/generated_poc/enums/principal_kind.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/principal_kind.json -# timestamp: 2026-09-01T19:37:00+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class PrincipalKind(StrEnum): - buyer_agent = 'buyer_agent' - operator = 'operator' diff --git a/src/adcp/types/generated_poc/enums/production_quality.py b/src/adcp/types/generated_poc/enums/production_quality.py deleted file mode 100644 index 31c74a261..000000000 --- a/src/adcp/types/generated_poc/enums/production_quality.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/production_quality.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class ProductionQuality(StrEnum): - professional = 'professional' - prosumer = 'prosumer' - ugc = 'ugc' diff --git a/src/adcp/types/generated_poc/enums/property_type.py b/src/adcp/types/generated_poc/enums/property_type.py deleted file mode 100644 index f15bd4d77..000000000 --- a/src/adcp/types/generated_poc/enums/property_type.py +++ /dev/null @@ -1,20 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/property_type.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class PropertyType(StrEnum): - website = 'website' - mobile_app = 'mobile_app' - ctv_app = 'ctv_app' - desktop_app = 'desktop_app' - dooh = 'dooh' - podcast = 'podcast' - radio = 'radio' - linear_tv = 'linear_tv' - streaming_audio = 'streaming_audio' - ai_assistant = 'ai_assistant' diff --git a/src/adcp/types/generated_poc/enums/proposal_decline_reason.py b/src/adcp/types/generated_poc/enums/proposal_decline_reason.py deleted file mode 100644 index e14501351..000000000 --- a/src/adcp/types/generated_poc/enums/proposal_decline_reason.py +++ /dev/null @@ -1,20 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/proposal_decline_reason.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class ProposalDeclineReason(StrEnum): - price = 'price' - inventory_fit = 'inventory_fit' - audience_fit = 'audience_fit' - creative_unsupported = 'creative_unsupported' - measurement_unsupported = 'measurement_unsupported' - policy = 'policy' - timing = 'timing' - budget_changed = 'budget_changed' - selected_alternative = 'selected_alternative' - other = 'other' diff --git a/src/adcp/types/generated_poc/enums/proposal_refinement_reason.py b/src/adcp/types/generated_poc/enums/proposal_refinement_reason.py deleted file mode 100644 index e46f7e7b6..000000000 --- a/src/adcp/types/generated_poc/enums/proposal_refinement_reason.py +++ /dev/null @@ -1,18 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/proposal_refinement_reason.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class ProposalRefinementReason(StrEnum): - commercially_declined = 'commercially_declined' - constraint_unsatisfiable = 'constraint_unsatisfiable' - unsupported_dimension = 'unsupported_dimension' - uninterpreted = 'uninterpreted' - alternatives_unavailable = 'alternatives_unavailable' - source_unavailable = 'source_unavailable' - hold_unavailable = 'hold_unavailable' - batch_aborted = 'batch_aborted' diff --git a/src/adcp/types/generated_poc/enums/proposal_status.py b/src/adcp/types/generated_poc/enums/proposal_status.py deleted file mode 100644 index 753eb76b0..000000000 --- a/src/adcp/types/generated_poc/enums/proposal_status.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/proposal_status.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class ProposalStatus(StrEnum): - draft = 'draft' - committed = 'committed' - accepted = 'accepted' diff --git a/src/adcp/types/generated_poc/enums/publisher_identifier_types.py b/src/adcp/types/generated_poc/enums/publisher_identifier_types.py deleted file mode 100644 index e06cabb10..000000000 --- a/src/adcp/types/generated_poc/enums/publisher_identifier_types.py +++ /dev/null @@ -1,15 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/publisher_identifier_types.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class PublisherIdentifierTypes(StrEnum): - tag_id = 'tag_id' - duns = 'duns' - lei = 'lei' - seller_id = 'seller_id' - gln = 'gln' diff --git a/src/adcp/types/generated_poc/enums/purchase_type.py b/src/adcp/types/generated_poc/enums/purchase_type.py deleted file mode 100644 index 4363c77f0..000000000 --- a/src/adcp/types/generated_poc/enums/purchase_type.py +++ /dev/null @@ -1,14 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/purchase_type.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class PurchaseType(StrEnum): - media_buy = 'media_buy' - rights_license = 'rights_license' - signal_activation = 'signal_activation' - creative_services = 'creative_services' diff --git a/src/adcp/types/generated_poc/enums/reach_aggregation.py b/src/adcp/types/generated_poc/enums/reach_aggregation.py deleted file mode 100644 index b9f262347..000000000 --- a/src/adcp/types/generated_poc/enums/reach_aggregation.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/reach_aggregation.json -# timestamp: 2026-09-05T11:09:57+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class ReachAggregation(StrEnum): - deduplicated = 'deduplicated' - sum_of_constituent_reach = 'sum_of_constituent_reach' diff --git a/src/adcp/types/generated_poc/enums/reach_unit.py b/src/adcp/types/generated_poc/enums/reach_unit.py deleted file mode 100644 index 54eab6f36..000000000 --- a/src/adcp/types/generated_poc/enums/reach_unit.py +++ /dev/null @@ -1,16 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/reach_unit.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class ReachUnit(StrEnum): - individuals = 'individuals' - households = 'households' - devices = 'devices' - accounts = 'accounts' - cookies = 'cookies' - custom = 'custom' diff --git a/src/adcp/types/generated_poc/enums/reporting_destination_setup_state.py b/src/adcp/types/generated_poc/enums/reporting_destination_setup_state.py deleted file mode 100644 index e872982b0..000000000 --- a/src/adcp/types/generated_poc/enums/reporting_destination_setup_state.py +++ /dev/null @@ -1,15 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/reporting_destination_setup_state.json -# timestamp: 2026-09-01T19:37:00+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class ReportingDestinationSetupState(StrEnum): - validating = 'validating' - ready = 'ready' - action_required = 'action_required' - inactive = 'inactive' - rejected = 'rejected' diff --git a/src/adcp/types/generated_poc/enums/reporting_finality.py b/src/adcp/types/generated_poc/enums/reporting_finality.py deleted file mode 100644 index d441cb4eb..000000000 --- a/src/adcp/types/generated_poc/enums/reporting_finality.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/reporting_finality.json -# timestamp: 2026-09-01T19:37:00+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class ReportingFinality(StrEnum): - snapshot = 'snapshot' - official = 'official' diff --git a/src/adcp/types/generated_poc/enums/reporting_frequency.py b/src/adcp/types/generated_poc/enums/reporting_frequency.py deleted file mode 100644 index 3c3275ed3..000000000 --- a/src/adcp/types/generated_poc/enums/reporting_frequency.py +++ /dev/null @@ -1,16 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/reporting_frequency.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class ReportingFrequency(StrEnum): - hourly = 'hourly' - daily = 'daily' - weekly = 'weekly' - monthly = 'monthly' - quarterly = 'quarterly' - post_campaign = 'post_campaign' diff --git a/src/adcp/types/generated_poc/enums/reporting_health.py b/src/adcp/types/generated_poc/enums/reporting_health.py deleted file mode 100644 index 969682e14..000000000 --- a/src/adcp/types/generated_poc/enums/reporting_health.py +++ /dev/null @@ -1,15 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/reporting_health.json -# timestamp: 2026-09-01T19:37:00+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class ReportingHealth(StrEnum): - healthy = 'healthy' - waiting = 'waiting' - delayed = 'delayed' - action_required = 'action_required' - complete = 'complete' diff --git a/src/adcp/types/generated_poc/enums/representation_selection_strategy.py b/src/adcp/types/generated_poc/enums/representation_selection_strategy.py deleted file mode 100644 index 968e15026..000000000 --- a/src/adcp/types/generated_poc/enums/representation_selection_strategy.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/representation_selection_strategy.json -# timestamp: 2026-08-28T20:03:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class RepresentationSelectionStrategy(StrEnum): - representation_order = 'representation_order' - highest_compatible_vast = 'highest_compatible_vast' diff --git a/src/adcp/types/generated_poc/enums/request_signing_error_code.py b/src/adcp/types/generated_poc/enums/request_signing_error_code.py deleted file mode 100644 index c1d63a65b..000000000 --- a/src/adcp/types/generated_poc/enums/request_signing_error_code.py +++ /dev/null @@ -1,39 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/request_signing_error_code.json -# timestamp: 2026-09-24T08:59:48+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class RequestSigningErrorCode(StrEnum): - request_signature_brand_json_url_missing = 'request_signature_brand_json_url_missing' - request_signature_capabilities_unreachable = 'request_signature_capabilities_unreachable' - request_signature_brand_json_unreachable = 'request_signature_brand_json_unreachable' - request_signature_brand_json_malformed = 'request_signature_brand_json_malformed' - request_signature_brand_origin_mismatch = 'request_signature_brand_origin_mismatch' - request_signature_agent_not_in_brand_json = 'request_signature_agent_not_in_brand_json' - request_signature_brand_json_ambiguous = 'request_signature_brand_json_ambiguous' - request_signature_key_origin_mismatch = 'request_signature_key_origin_mismatch' - request_signature_key_origin_missing = 'request_signature_key_origin_missing' - request_signature_required = 'request_signature_required' - request_target_uri_malformed = 'request_target_uri_malformed' - request_signature_header_malformed = 'request_signature_header_malformed' - request_signature_params_incomplete = 'request_signature_params_incomplete' - request_signature_tag_invalid = 'request_signature_tag_invalid' - request_signature_alg_not_allowed = 'request_signature_alg_not_allowed' - request_signature_window_invalid = 'request_signature_window_invalid' - request_signature_components_incomplete = 'request_signature_components_incomplete' - request_signature_components_unexpected = 'request_signature_components_unexpected' - request_signature_key_unknown = 'request_signature_key_unknown' - request_signature_key_purpose_invalid = 'request_signature_key_purpose_invalid' - request_signature_key_revoked = 'request_signature_key_revoked' - request_signature_revocation_stale = 'request_signature_revocation_stale' - request_signature_invalid = 'request_signature_invalid' - request_signature_digest_mismatch = 'request_signature_digest_mismatch' - request_body_malformed = 'request_body_malformed' - request_signature_replayed = 'request_signature_replayed' - request_signature_rate_abuse = 'request_signature_rate_abuse' - request_signature_jwks_unavailable = 'request_signature_jwks_unavailable' - request_signature_jwks_untrusted = 'request_signature_jwks_untrusted' diff --git a/src/adcp/types/generated_poc/enums/response_type.py b/src/adcp/types/generated_poc/enums/response_type.py deleted file mode 100644 index 3e2ebea84..000000000 --- a/src/adcp/types/generated_poc/enums/response_type.py +++ /dev/null @@ -1,14 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/response_type.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class TmpResponseType(StrEnum): - activation = 'activation' - catalog_items = 'catalog_items' - creative = 'creative' - deal = 'deal' diff --git a/src/adcp/types/generated_poc/enums/restricted_attribute.py b/src/adcp/types/generated_poc/enums/restricted_attribute.py deleted file mode 100644 index 4a48f5ccf..000000000 --- a/src/adcp/types/generated_poc/enums/restricted_attribute.py +++ /dev/null @@ -1,20 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/restricted_attribute.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class RestrictedAttribute(StrEnum): - racial_ethnic_origin = 'racial_ethnic_origin' - political_opinions = 'political_opinions' - religious_beliefs = 'religious_beliefs' - trade_union_membership = 'trade_union_membership' - health_data = 'health_data' - sex_life_sexual_orientation = 'sex_life_sexual_orientation' - genetic_data = 'genetic_data' - biometric_data = 'biometric_data' - age = 'age' - familial_status = 'familial_status' diff --git a/src/adcp/types/generated_poc/enums/right_type.py b/src/adcp/types/generated_poc/enums/right_type.py deleted file mode 100644 index 30c4b50d6..000000000 --- a/src/adcp/types/generated_poc/enums/right_type.py +++ /dev/null @@ -1,15 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/right_type.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class RightType(StrEnum): - talent = 'talent' - character = 'character' - brand_ip = 'brand_ip' - music = 'music' - stock_media = 'stock_media' diff --git a/src/adcp/types/generated_poc/enums/right_use.py b/src/adcp/types/generated_poc/enums/right_use.py deleted file mode 100644 index 388eca848..000000000 --- a/src/adcp/types/generated_poc/enums/right_use.py +++ /dev/null @@ -1,22 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/right_use.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class RightUse(StrEnum): - likeness = 'likeness' - voice = 'voice' - name = 'name' - endorsement = 'endorsement' - motion_capture = 'motion_capture' - signature = 'signature' - catchphrase = 'catchphrase' - sync = 'sync' - background_music = 'background_music' - editorial = 'editorial' - commercial = 'commercial' - ai_generated_image = 'ai_generated_image' diff --git a/src/adcp/types/generated_poc/enums/rights_billing_period.py b/src/adcp/types/generated_poc/enums/rights_billing_period.py deleted file mode 100644 index 38f770b48..000000000 --- a/src/adcp/types/generated_poc/enums/rights_billing_period.py +++ /dev/null @@ -1,16 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/rights_billing_period.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class RightsBillingPeriod(StrEnum): - daily = 'daily' - weekly = 'weekly' - monthly = 'monthly' - quarterly = 'quarterly' - annual = 'annual' - one_time = 'one_time' diff --git a/src/adcp/types/generated_poc/enums/scan_type.py b/src/adcp/types/generated_poc/enums/scan_type.py deleted file mode 100644 index a3f30f723..000000000 --- a/src/adcp/types/generated_poc/enums/scan_type.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/scan_type.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class ScanType(StrEnum): - progressive = 'progressive' - interlaced = 'interlaced' diff --git a/src/adcp/types/generated_poc/enums/seller_policy_decline_reason.py b/src/adcp/types/generated_poc/enums/seller_policy_decline_reason.py deleted file mode 100644 index 143895b99..000000000 --- a/src/adcp/types/generated_poc/enums/seller_policy_decline_reason.py +++ /dev/null @@ -1,17 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/seller_policy_decline_reason.json -# timestamp: 2026-09-24T08:59:48+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class SellerPolicyDeclineReason(StrEnum): - inventory = 'inventory' - share_of_voice = 'share_of_voice' - minimum_commitment = 'minimum_commitment' - notice_period = 'notice_period' - contract_terms = 'contract_terms' - frequency_cap = 'frequency_cap' - other = 'other' diff --git a/src/adcp/types/generated_poc/enums/si_session_status.py b/src/adcp/types/generated_poc/enums/si_session_status.py deleted file mode 100644 index e118ebaff..000000000 --- a/src/adcp/types/generated_poc/enums/si_session_status.py +++ /dev/null @@ -1,14 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/si_session_status.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class SiSessionStatus(StrEnum): - active = 'active' - pending_handoff = 'pending_handoff' - complete = 'complete' - terminated = 'terminated' diff --git a/src/adcp/types/generated_poc/enums/signal_catalog_type.py b/src/adcp/types/generated_poc/enums/signal_catalog_type.py deleted file mode 100644 index b4dda7ca5..000000000 --- a/src/adcp/types/generated_poc/enums/signal_catalog_type.py +++ /dev/null @@ -1,16 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/signal_catalog_type.json -# timestamp: 2026-05-29T23:56:01+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class SignalAvailabilityType(StrEnum): - marketplace = 'marketplace' - custom = 'custom' - owned = 'owned' - - -SignalCatalogType = SignalAvailabilityType diff --git a/src/adcp/types/generated_poc/enums/signal_source.py b/src/adcp/types/generated_poc/enums/signal_source.py deleted file mode 100644 index a31ccc875..000000000 --- a/src/adcp/types/generated_poc/enums/signal_source.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/signal_source.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class SignalSource(StrEnum): - catalog = 'catalog' - agent = 'agent' diff --git a/src/adcp/types/generated_poc/enums/signal_value_type.py b/src/adcp/types/generated_poc/enums/signal_value_type.py deleted file mode 100644 index 2fa0d1f83..000000000 --- a/src/adcp/types/generated_poc/enums/signal_value_type.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/signal_value_type.json -# timestamp: 2026-09-05T19:58:21+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class SignalValueType(StrEnum): - binary = 'binary' - categorical = 'categorical' - numeric = 'numeric' diff --git a/src/adcp/types/generated_poc/enums/snapshot_unavailable_reason.py b/src/adcp/types/generated_poc/enums/snapshot_unavailable_reason.py deleted file mode 100644 index 1021f2317..000000000 --- a/src/adcp/types/generated_poc/enums/snapshot_unavailable_reason.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/snapshot_unavailable_reason.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class SnapshotUnavailableReason(StrEnum): - SNAPSHOT_UNSUPPORTED = 'SNAPSHOT_UNSUPPORTED' - SNAPSHOT_TEMPORARILY_UNAVAILABLE = 'SNAPSHOT_TEMPORARILY_UNAVAILABLE' - SNAPSHOT_PERMISSION_DENIED = 'SNAPSHOT_PERMISSION_DENIED' diff --git a/src/adcp/types/generated_poc/enums/social_placement_surface.py b/src/adcp/types/generated_poc/enums/social_placement_surface.py deleted file mode 100644 index b54a8a2ee..000000000 --- a/src/adcp/types/generated_poc/enums/social_placement_surface.py +++ /dev/null @@ -1,15 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/social_placement_surface.json -# timestamp: 2026-06-04T19:44:00+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class SocialPlacementSurface(StrEnum): - feed = 'feed' - stories = 'stories' - short_video = 'short_video' - explore = 'explore' - search = 'search' diff --git a/src/adcp/types/generated_poc/enums/sort_direction.py b/src/adcp/types/generated_poc/enums/sort_direction.py deleted file mode 100644 index a5a21dfe9..000000000 --- a/src/adcp/types/generated_poc/enums/sort_direction.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/sort_direction.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class SortDirection(StrEnum): - asc = 'asc' - desc = 'desc' diff --git a/src/adcp/types/generated_poc/enums/sort_metric.py b/src/adcp/types/generated_poc/enums/sort_metric.py deleted file mode 100644 index 3b2030364..000000000 --- a/src/adcp/types/generated_poc/enums/sort_metric.py +++ /dev/null @@ -1,47 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/sort_metric.json -# timestamp: 2026-08-24T04:35:32+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class SortMetric(StrEnum): - impressions = 'impressions' - spend = 'spend' - clicks = 'clicks' - ctr = 'ctr' - views = 'views' - completed_views = 'completed_views' - completion_rate = 'completion_rate' - conversions = 'conversions' - conversion_value = 'conversion_value' - roas = 'roas' - cost_per_acquisition = 'cost_per_acquisition' - new_to_brand_rate = 'new_to_brand_rate' - leads = 'leads' - grps = 'grps' - reach = 'reach' - frequency = 'frequency' - engagements = 'engagements' - follows = 'follows' - saves = 'saves' - profile_visits = 'profile_visits' - engagement_rate = 'engagement_rate' - cost_per_click = 'cost_per_click' - commissionable_value = 'commissionable_value' - plays = 'plays' - cost_per_completed_view = 'cost_per_completed_view' - cpm = 'cpm' - downloads = 'downloads' - units_sold = 'units_sold' - new_to_brand_units = 'new_to_brand_units' - viewable_rate = 'viewable_rate' - viewable_impressions = 'viewable_impressions' - measurable_impressions = 'measurable_impressions' - viewed_seconds = 'viewed_seconds' - quartile_25 = 'quartile_25' - quartile_50 = 'quartile_50' - quartile_75 = 'quartile_75' - quartile_100 = 'quartile_100' diff --git a/src/adcp/types/generated_poc/enums/special_category.py b/src/adcp/types/generated_poc/enums/special_category.py deleted file mode 100644 index 83ae1b443..000000000 --- a/src/adcp/types/generated_poc/enums/special_category.py +++ /dev/null @@ -1,22 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/special_category.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class SpecialCategory(StrEnum): - awards = 'awards' - championship = 'championship' - concert = 'concert' - conference = 'conference' - election = 'election' - festival = 'festival' - gala = 'gala' - holiday = 'holiday' - premiere = 'premiere' - product_launch = 'product_launch' - reunion = 'reunion' - tribute = 'tribute' diff --git a/src/adcp/types/generated_poc/enums/specialism.py b/src/adcp/types/generated_poc/enums/specialism.py deleted file mode 100644 index 4a8192ee1..000000000 --- a/src/adcp/types/generated_poc/enums/specialism.py +++ /dev/null @@ -1,42 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/specialism.json -# timestamp: 2026-09-24T08:59:48+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class AdcpSpecialism(StrEnum): - audience_sync = 'audience-sync' - brand_rights = 'brand-rights' - buyer_activation = 'buyer-activation' - buyer_discovery = 'buyer-discovery' - buyer_monitoring = 'buyer-monitoring' - buyer_negotiation = 'buyer-negotiation' - buyer_recovery = 'buyer-recovery' - collection_lists = 'collection-lists' - content_standards = 'content-standards' - creative_ad_server = 'creative-ad-server' - creative_generative = 'creative-generative' - creative_template = 'creative-template' - creative_transformers = 'creative-transformers' - governance_aware_seller = 'governance-aware-seller' - governance_delivery_monitor = 'governance-delivery-monitor' - governance_spend_authority = 'governance-spend-authority' - property_lists = 'property-lists' - sales_broadcast_tv = 'sales-broadcast-tv' - sales_catalog_driven = 'sales-catalog-driven' - sales_dooh = 'sales-dooh' - sales_exchange = 'sales-exchange' - sales_guaranteed = 'sales-guaranteed' - sales_non_guaranteed = 'sales-non-guaranteed' - sales_proposal_mode = 'sales-proposal-mode' - sales_retail_media = 'sales-retail-media' - sales_streaming_tv = 'sales-streaming-tv' - sales_social = 'sales-social' - signal_marketplace = 'signal-marketplace' - orchestrator_multi_agent = 'orchestrator-multi-agent' - signal_owned = 'signal-owned' - signed_requests = 'signed-requests' - sponsored_intelligence = 'sponsored-intelligence' diff --git a/src/adcp/types/generated_poc/enums/sponsored_placement_type.py b/src/adcp/types/generated_poc/enums/sponsored_placement_type.py deleted file mode 100644 index f74718ca3..000000000 --- a/src/adcp/types/generated_poc/enums/sponsored_placement_type.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/sponsored_placement_type.json -# timestamp: 2026-06-04T19:44:00+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class SponsoredPlacementType(StrEnum): - sponsored_search = 'sponsored_search' - sponsored_display = 'sponsored_display' - sponsored_native = 'sponsored_native' diff --git a/src/adcp/types/generated_poc/enums/talent_role.py b/src/adcp/types/generated_poc/enums/talent_role.py deleted file mode 100644 index 3916bb8a9..000000000 --- a/src/adcp/types/generated_poc/enums/talent_role.py +++ /dev/null @@ -1,19 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/talent_role.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class TalentRole(StrEnum): - host = 'host' - guest = 'guest' - creator = 'creator' - cast = 'cast' - narrator = 'narrator' - producer = 'producer' - correspondent = 'correspondent' - commentator = 'commentator' - analyst = 'analyst' diff --git a/src/adcp/types/generated_poc/enums/task_status.py b/src/adcp/types/generated_poc/enums/task_status.py deleted file mode 100644 index e2481b00c..000000000 --- a/src/adcp/types/generated_poc/enums/task_status.py +++ /dev/null @@ -1,19 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/task_status.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class TaskStatus(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' diff --git a/src/adcp/types/generated_poc/enums/task_type.py b/src/adcp/types/generated_poc/enums/task_type.py deleted file mode 100644 index db25bf474..000000000 --- a/src/adcp/types/generated_poc/enums/task_type.py +++ /dev/null @@ -1,48 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/task_type.json -# timestamp: 2026-09-24T08:59:48+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class TaskType(StrEnum): - create_media_buy = 'create_media_buy' - update_media_buy = 'update_media_buy' - buy_products = 'buy_products' - accept_proposal = 'accept_proposal' - control_media_buy = 'control_media_buy' - media_buy_delivery = 'media_buy_delivery' - sync_creatives = 'sync_creatives' - build_creative = 'build_creative' - preview_creative = 'preview_creative' - get_creative_features = 'get_creative_features' - activate_signal = 'activate_signal' - get_products = 'get_products' - request_proposals = 'request_proposals' - refine_proposals = 'refine_proposals' - decline_proposals = 'decline_proposals' - get_signals = 'get_signals' - create_property_list = 'create_property_list' - update_property_list = 'update_property_list' - get_property_list = 'get_property_list' - list_property_lists = 'list_property_lists' - delete_property_list = 'delete_property_list' - sync_accounts = 'sync_accounts' - get_account_financials = 'get_account_financials' - get_creative_delivery = 'get_creative_delivery' - sync_event_sources = 'sync_event_sources' - sync_audiences = 'sync_audiences' - sync_catalogs = 'sync_catalogs' - log_event = 'log_event' - get_brand_identity = 'get_brand_identity' - search_brands = 'search_brands' - get_rights = 'get_rights' - acquire_rights = 'acquire_rights' - update_rights = 'update_rights' - sync_agent_notification_configs = 'sync_agent_notification_configs' - sync_principal = 'sync_principal' - get_principal = 'get_principal' - sync_reporting_status = 'sync_reporting_status' - sync_reporting_receipts = 'sync_reporting_receipts' diff --git a/src/adcp/types/generated_poc/enums/tracker_execution_actor.py b/src/adcp/types/generated_poc/enums/tracker_execution_actor.py deleted file mode 100644 index 14fb4e47b..000000000 --- a/src/adcp/types/generated_poc/enums/tracker_execution_actor.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/tracker_execution_actor.json -# timestamp: 2026-08-28T20:03:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class TrackerExecutionActor(StrEnum): - seller = 'seller' - request_executor = 'request_executor' diff --git a/src/adcp/types/generated_poc/enums/tracker_firing_path.py b/src/adcp/types/generated_poc/enums/tracker_firing_path.py deleted file mode 100644 index 227d8b47d..000000000 --- a/src/adcp/types/generated_poc/enums/tracker_firing_path.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/tracker_firing_path.json -# timestamp: 2026-08-28T20:03:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class TrackerFiringPath(StrEnum): - client = 'client' - server = 'server' diff --git a/src/adcp/types/generated_poc/enums/transport_mode.py b/src/adcp/types/generated_poc/enums/transport_mode.py deleted file mode 100644 index f547a4804..000000000 --- a/src/adcp/types/generated_poc/enums/transport_mode.py +++ /dev/null @@ -1,14 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/transport_mode.json -# timestamp: 2026-06-04T19:44:00+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class TransportMode(StrEnum): - walking = 'walking' - cycling = 'cycling' - driving = 'driving' - public_transport = 'public_transport' diff --git a/src/adcp/types/generated_poc/enums/travel_time_unit.py b/src/adcp/types/generated_poc/enums/travel_time_unit.py deleted file mode 100644 index 440fd83e0..000000000 --- a/src/adcp/types/generated_poc/enums/travel_time_unit.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/travel_time_unit.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class TravelTimeUnit(StrEnum): - min = 'min' - hr = 'hr' diff --git a/src/adcp/types/generated_poc/enums/uid_type.py b/src/adcp/types/generated_poc/enums/uid_type.py deleted file mode 100644 index 6a1f44a01..000000000 --- a/src/adcp/types/generated_poc/enums/uid_type.py +++ /dev/null @@ -1,21 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/uid_type.json -# timestamp: 2026-06-12T11:05:47+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class UidType(StrEnum): - rampid = 'rampid' - rampid_derived = 'rampid_derived' - id5 = 'id5' - uid2 = 'uid2' - euid = 'euid' - pairid = 'pairid' - maid = 'maid' - hashed_email = 'hashed_email' - publisher_first_party = 'publisher_first_party' - world_id_nullifier = 'world_id_nullifier' - other = 'other' diff --git a/src/adcp/types/generated_poc/enums/universal_macro.py b/src/adcp/types/generated_poc/enums/universal_macro.py deleted file mode 100644 index f479de8f6..000000000 --- a/src/adcp/types/generated_poc/enums/universal_macro.py +++ /dev/null @@ -1,85 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/universal_macro.json -# timestamp: 2026-06-04T19:44:00+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class UniversalMacro(StrEnum): - MEDIA_BUY_ID = 'MEDIA_BUY_ID' - PACKAGE_ID = 'PACKAGE_ID' - CREATIVE_ID = 'CREATIVE_ID' - CACHEBUSTER = 'CACHEBUSTER' - TIMESTAMP = 'TIMESTAMP' - CLICK_URL = 'CLICK_URL' - GDPR = 'GDPR' - GDPR_CONSENT = 'GDPR_CONSENT' - US_PRIVACY = 'US_PRIVACY' - GPP_STRING = 'GPP_STRING' - GPP_SID = 'GPP_SID' - IP_ADDRESS = 'IP_ADDRESS' - LIMIT_AD_TRACKING = 'LIMIT_AD_TRACKING' - DEVICE_TYPE = 'DEVICE_TYPE' - OS = 'OS' - OS_VERSION = 'OS_VERSION' - DEVICE_MAKE = 'DEVICE_MAKE' - DEVICE_MODEL = 'DEVICE_MODEL' - USER_AGENT = 'USER_AGENT' - APP_BUNDLE = 'APP_BUNDLE' - APP_NAME = 'APP_NAME' - COUNTRY = 'COUNTRY' - REGION = 'REGION' - CITY = 'CITY' - ZIP = 'ZIP' - DMA = 'DMA' - LAT = 'LAT' - LONG = 'LONG' - DEVICE_ID = 'DEVICE_ID' - DEVICE_ID_TYPE = 'DEVICE_ID_TYPE' - DOMAIN = 'DOMAIN' - PAGE_URL = 'PAGE_URL' - REFERRER = 'REFERRER' - KEYWORDS = 'KEYWORDS' - PLACEMENT_ID = 'PLACEMENT_ID' - FOLD_POSITION = 'FOLD_POSITION' - AD_WIDTH = 'AD_WIDTH' - AD_HEIGHT = 'AD_HEIGHT' - VIDEO_ID = 'VIDEO_ID' - VIDEO_TITLE = 'VIDEO_TITLE' - VIDEO_DURATION = 'VIDEO_DURATION' - VIDEO_CATEGORY = 'VIDEO_CATEGORY' - CONTENT_GENRE = 'CONTENT_GENRE' - CONTENT_RATING = 'CONTENT_RATING' - PLAYER_WIDTH = 'PLAYER_WIDTH' - PLAYER_HEIGHT = 'PLAYER_HEIGHT' - POD_POSITION = 'POD_POSITION' - POD_SIZE = 'POD_SIZE' - AD_BREAK_ID = 'AD_BREAK_ID' - STATION_ID = 'STATION_ID' - COLLECTION_NAME = 'COLLECTION_NAME' - INSTALLMENT_ID = 'INSTALLMENT_ID' - AUDIO_DURATION = 'AUDIO_DURATION' - TMPX = 'TMPX' - IMPRESSION_ID = 'IMPRESSION_ID' - AXEM = 'AXEM' - CATALOG_ID = 'CATALOG_ID' - SKU = 'SKU' - GTIN = 'GTIN' - OFFERING_ID = 'OFFERING_ID' - JOB_ID = 'JOB_ID' - HOTEL_ID = 'HOTEL_ID' - FLIGHT_ID = 'FLIGHT_ID' - VEHICLE_ID = 'VEHICLE_ID' - LISTING_ID = 'LISTING_ID' - STORE_ID = 'STORE_ID' - PROGRAM_ID = 'PROGRAM_ID' - DESTINATION_ID = 'DESTINATION_ID' - CREATIVE_VARIANT_ID = 'CREATIVE_VARIANT_ID' - APP_ITEM_ID = 'APP_ITEM_ID' - ITEM_NAME = 'ITEM_NAME' - ITEM_DESCRIPTION = 'ITEM_DESCRIPTION' - ITEM_TAGLINE = 'ITEM_TAGLINE' - ITEM_PRICE = 'ITEM_PRICE' - ITEM_PRICE_CURRENCY = 'ITEM_PRICE_CURRENCY' diff --git a/src/adcp/types/generated_poc/enums/update_frequency.py b/src/adcp/types/generated_poc/enums/update_frequency.py deleted file mode 100644 index 59f2b3b9e..000000000 --- a/src/adcp/types/generated_poc/enums/update_frequency.py +++ /dev/null @@ -1,14 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/update_frequency.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class UpdateFrequency(StrEnum): - realtime = 'realtime' - hourly = 'hourly' - daily = 'daily' - weekly = 'weekly' diff --git a/src/adcp/types/generated_poc/enums/url_asset_type.py b/src/adcp/types/generated_poc/enums/url_asset_type.py deleted file mode 100644 index fdbda4f21..000000000 --- a/src/adcp/types/generated_poc/enums/url_asset_type.py +++ /dev/null @@ -1,14 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/url_asset_type.json -# timestamp: 2026-08-28T20:03:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class UrlAssetType(StrEnum): - clickthrough = 'clickthrough' - ad_request = 'ad_request' - tracker_pixel = 'tracker_pixel' - tracker_script = 'tracker_script' diff --git a/src/adcp/types/generated_poc/enums/validation_mode.py b/src/adcp/types/generated_poc/enums/validation_mode.py deleted file mode 100644 index 998f56cae..000000000 --- a/src/adcp/types/generated_poc/enums/validation_mode.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/validation_mode.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class ValidationMode(StrEnum): - strict = 'strict' - lenient = 'lenient' diff --git a/src/adcp/types/generated_poc/enums/vast_media_delivery_method.py b/src/adcp/types/generated_poc/enums/vast_media_delivery_method.py deleted file mode 100644 index ea2885107..000000000 --- a/src/adcp/types/generated_poc/enums/vast_media_delivery_method.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/vast_media_delivery_method.json -# timestamp: 2026-08-28T20:03:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class VastMediaDeliveryMethod(StrEnum): - progressive = 'progressive' - streaming = 'streaming' diff --git a/src/adcp/types/generated_poc/enums/vast_tracking_event.py b/src/adcp/types/generated_poc/enums/vast_tracking_event.py deleted file mode 100644 index 9757c99e4..000000000 --- a/src/adcp/types/generated_poc/enums/vast_tracking_event.py +++ /dev/null @@ -1,46 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/vast_tracking_event.json -# timestamp: 2026-05-22T13:15:12+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class VastTrackingEvent(StrEnum): - impression = 'impression' - creativeView = 'creativeView' - loaded = 'loaded' - start = 'start' - firstQuartile = 'firstQuartile' - midpoint = 'midpoint' - thirdQuartile = 'thirdQuartile' - complete = 'complete' - mute = 'mute' - unmute = 'unmute' - pause = 'pause' - resume = 'resume' - rewind = 'rewind' - skip = 'skip' - playerExpand = 'playerExpand' - playerCollapse = 'playerCollapse' - fullscreen = 'fullscreen' - exitFullscreen = 'exitFullscreen' - progress = 'progress' - acceptInvitation = 'acceptInvitation' - adExpand = 'adExpand' - adCollapse = 'adCollapse' - minimize = 'minimize' - overlayViewDuration = 'overlayViewDuration' - otherAdInteraction = 'otherAdInteraction' - interactiveStart = 'interactiveStart' - clickTracking = 'clickTracking' - customClick = 'customClick' - close = 'close' - closeLinear = 'closeLinear' - error = 'error' - viewable = 'viewable' - notViewable = 'notViewable' - viewUndetermined = 'viewUndetermined' - measurableImpression = 'measurableImpression' - viewableImpression = 'viewableImpression' diff --git a/src/adcp/types/generated_poc/enums/vast_version.py b/src/adcp/types/generated_poc/enums/vast_version.py deleted file mode 100644 index d650021b7..000000000 --- a/src/adcp/types/generated_poc/enums/vast_version.py +++ /dev/null @@ -1,16 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/vast_version.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class VastVersion(StrEnum): - field_2_0 = '2.0' - field_3_0 = '3.0' - field_4_0 = '4.0' - field_4_1 = '4.1' - field_4_2 = '4.2' - field_4_3 = '4.3' diff --git a/src/adcp/types/generated_poc/enums/vendor_relationship.py b/src/adcp/types/generated_poc/enums/vendor_relationship.py deleted file mode 100644 index e73cbcf28..000000000 --- a/src/adcp/types/generated_poc/enums/vendor_relationship.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/vendor_relationship.json -# timestamp: 2026-09-05T11:09:57+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class VendorRelationship(StrEnum): - first_party = 'first_party' - affiliated = 'affiliated' - third_party = 'third_party' diff --git a/src/adcp/types/generated_poc/enums/video_placement_type.py b/src/adcp/types/generated_poc/enums/video_placement_type.py deleted file mode 100644 index aeaef22b1..000000000 --- a/src/adcp/types/generated_poc/enums/video_placement_type.py +++ /dev/null @@ -1,14 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/video_placement_type.json -# timestamp: 2026-05-29T23:56:01+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class VideoPlacementType(StrEnum): - instream = 'instream' - accompanying_content = 'accompanying_content' - interstitial = 'interstitial' - standalone = 'standalone' diff --git a/src/adcp/types/generated_poc/enums/view_threshold_basis.py b/src/adcp/types/generated_poc/enums/view_threshold_basis.py deleted file mode 100644 index 549d9a180..000000000 --- a/src/adcp/types/generated_poc/enums/view_threshold_basis.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/view_threshold_basis.json -# timestamp: 2026-08-24T04:35:32+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class ViewThresholdBasis(StrEnum): - play_time = 'play_time' - in_view = 'in_view' diff --git a/src/adcp/types/generated_poc/enums/viewability_standard.py b/src/adcp/types/generated_poc/enums/viewability_standard.py deleted file mode 100644 index cc6f86eec..000000000 --- a/src/adcp/types/generated_poc/enums/viewability_standard.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/viewability_standard.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class ViewabilityStandard(StrEnum): - mrc = 'mrc' - groupm = 'groupm' diff --git a/src/adcp/types/generated_poc/enums/warning_code.py b/src/adcp/types/generated_poc/enums/warning_code.py deleted file mode 100644 index 81879868e..000000000 --- a/src/adcp/types/generated_poc/enums/warning_code.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/warning_code.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class WarningCode(StrEnum): - inventory_shortfall_forecast = 'inventory_shortfall_forecast' - flight_change_creates_pacing_risk = 'flight_change_creates_pacing_risk' - fields_ignored_due_to_precedence = 'fields_ignored_due_to_precedence' diff --git a/src/adcp/types/generated_poc/enums/watermark_media_type.py b/src/adcp/types/generated_poc/enums/watermark_media_type.py deleted file mode 100644 index 8db3747b7..000000000 --- a/src/adcp/types/generated_poc/enums/watermark_media_type.py +++ /dev/null @@ -1,14 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/watermark_media_type.json -# timestamp: 2026-06-04T19:44:00+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class WatermarkMediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' diff --git a/src/adcp/types/generated_poc/enums/wcag_level.py b/src/adcp/types/generated_poc/enums/wcag_level.py deleted file mode 100644 index 013eafdc4..000000000 --- a/src/adcp/types/generated_poc/enums/wcag_level.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/wcag_level.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class WcagLevel(StrEnum): - A = 'A' - AA = 'AA' - AAA = 'AAA' diff --git a/src/adcp/types/generated_poc/enums/webhook_response_type.py b/src/adcp/types/generated_poc/enums/webhook_response_type.py deleted file mode 100644 index 11a82949d..000000000 --- a/src/adcp/types/generated_poc/enums/webhook_response_type.py +++ /dev/null @@ -1,14 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/webhook_response_type.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class WebhookResponseType(StrEnum): - html = 'html' - json = 'json' - xml = 'xml' - javascript = 'javascript' diff --git a/src/adcp/types/generated_poc/enums/webhook_security_method.py b/src/adcp/types/generated_poc/enums/webhook_security_method.py deleted file mode 100644 index d0ab2901b..000000000 --- a/src/adcp/types/generated_poc/enums/webhook_security_method.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: enums/webhook_security_method.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class WebhookSecurityMethod(StrEnum): - hmac_sha256 = 'hmac_sha256' - api_key = 'api_key' - none = 'none' diff --git a/src/adcp/types/generated_poc/error_details/__init__.py b/src/adcp/types/generated_poc/error_details/__init__.py deleted file mode 100644 index bcbe04734..000000000 --- a/src/adcp/types/generated_poc/error_details/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# generated by datamodel-codegen: -# filename: .schema_temp -# timestamp: 2026-04-18T19:31:26+00:00 diff --git a/src/adcp/types/generated_poc/error_details/accessibility_violation.py b/src/adcp/types/generated_poc/error_details/accessibility_violation.py deleted file mode 100644 index dd15f47d1..000000000 --- a/src/adcp/types/generated_poc/error_details/accessibility_violation.py +++ /dev/null @@ -1,106 +0,0 @@ -# generated by datamodel-codegen: -# filename: error_details/accessibility_violation.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, StrictBool - - -class FailureKind(StrEnum): - alt_text_missing = 'alt_text_missing' - contrast_ratio_insufficient = 'contrast_ratio_insufficient' - captions_missing = 'captions_missing' - transcript_missing = 'transcript_missing' - keyboard_navigation_failure = 'keyboard_navigation_failure' - focus_order_invalid = 'focus_order_invalid' - aria_label_missing = 'aria_label_missing' - other = 'other' - - -class FailureKind1(ScalarStr): - __slots__ = () - _constraints = {'max_length': 64, 'pattern': '^x_[a-z0-9]+(?:_[a-z0-9]+)*$'} - _json_schema_extra = { - 'description': 'Diagnostic failure category. Canonical values cover common portable cases. Vendor-local extensions use an x_ prefix. Portable recovery dispatches on criterion_source plus criterion after validating this details schema; failure_kind may refine diagnostics but does not select the recovery class.', - } - - -class Violation(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description='RFC 6901 JSON Pointer rooted at the complete task request payload, not at the nested creative manifest. For example, inline build_creative uses /creative_manifest/assets/hero_image and a sync_creatives batch uses /creatives/0/assets/hero_image. If build_creative resolves a stored manifest from creative_id, use /creative_id because the failing asset is not present in the request.', - max_length=256, - pattern='^(?:/(?:[^~/\\u0000-\\u001F\\u007F]|~0|~1)*)*$', - ), - ] - criterion: Annotated[ - str, - Field( - description='Stable criterion identifier within criterion_source, for example 1.4.3. Open string so external standards can evolve without an AdCP enum update.', - max_length=32, - min_length=1, - pattern='^[^\\u0000-\\u001F\\u007F]+$', - ), - ] - criterion_source: Annotated[ - str, - Field( - description='Standard or policy namespace defining the criterion, for example WCAG21, WCAG22, or EN301549.', - max_length=64, - min_length=1, - pattern='^[A-Za-z][A-Za-z0-9._:-]{0,63}$', - ), - ] - required_level: Annotated[ - str, - Field( - description="Conformance level or policy threshold that applied, for example AA. Kept open because not every accessibility standard uses WCAG's A/AA/AAA vocabulary.", - max_length=64, - min_length=1, - pattern='^[^\\u0000-\\u001F\\u007F]+$', - ), - ] - failure_kind: Annotated[ - FailureKind | FailureKind1, - Field( - description='Diagnostic failure category. Canonical values cover common portable cases. Vendor-local extensions use an x_ prefix. Portable recovery dispatches on criterion_source plus criterion after validating this details schema; failure_kind may refine diagnostics but does not select the recovery class.' - ), - ] - remediation: Annotated[ - str | None, - Field( - description='Optional human-readable correction guidance. Plain text only; consumers treat it as untrusted agent input.', - max_length=256, - pattern='^[^\\u0000-\\u001F\\u007F]*$', - ), - ] = None - - -class AccessibilityViolationDetails(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - violations: Annotated[ - list[Violation], - Field( - description='Accessibility requirements the creative or its assets failed. This is distinct from error.issues, which carries JSON Schema validator failures.', - max_length=4, - min_length=1, - ), - ] - truncated: Annotated[ - StrictBool | None, - Field( - description='True when the producer omitted additional violations or remediation text to keep the complete serialized error within the 4096-byte transport safety limit. Omission means false.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/error_details/account_moved.py b/src/adcp/types/generated_poc/error_details/account_moved.py deleted file mode 100644 index 49548a938..000000000 --- a/src/adcp/types/generated_poc/error_details/account_moved.py +++ /dev/null @@ -1,30 +0,0 @@ -# generated by datamodel-codegen: -# filename: error_details/account_moved.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field - -from ..core import canonical_account_ref - - -class AccountMovedDetails(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - current_account: Annotated[ - canonical_account_ref.CanonicalAccountReference, - Field( - description='Current canonical reference. Buyer-declared account sellers return the complete current natural key; account-id namespaces may return account_id.' - ), - ] - revision: Annotated[ - SchemaInt | None, - Field( - description='Current account revision when the seller exposes account revisions.', ge=1 - ), - ] = None diff --git a/src/adcp/types/generated_poc/error_details/account_setup_required.py b/src/adcp/types/generated_poc/error_details/account_setup_required.py deleted file mode 100644 index 4b7b5875b..000000000 --- a/src/adcp/types/generated_poc/error_details/account_setup_required.py +++ /dev/null @@ -1,22 +0,0 @@ -# generated by datamodel-codegen: -# filename: error_details/account_setup_required.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - - -class AccountSetupRequiredDetails(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - setup_url: Annotated[ - AnyUrl | None, Field(description='URL where account setup can be completed') - ] = None - setup_steps: Annotated[ - list[str] | None, Field(description='Steps remaining before the account is ready') - ] = None diff --git a/src/adcp/types/generated_poc/error_details/action_not_allowed.py b/src/adcp/types/generated_poc/error_details/action_not_allowed.py deleted file mode 100644 index fad18a7d1..000000000 --- a/src/adcp/types/generated_poc/error_details/action_not_allowed.py +++ /dev/null @@ -1,43 +0,0 @@ -# generated by datamodel-codegen: -# filename: error_details/action_not_allowed.json -# timestamp: 2026-09-24T08:59:48+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..core import media_buy_available_action, media_buy_available_action_id -from ..enums import action_not_allowed_reason, seller_policy_decline_reason - - -class ActionNotAllowedDetails(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - attempted_action: Annotated[ - media_buy_available_action_id.MediaBuyAvailableActionId, - Field( - description='The action the request mapped to, per the normative action-to-field mapping in update_media_buy task-reference.' - ), - ] - reason: Annotated[ - action_not_allowed_reason.ActionNotAllowedReason, - Field( - description='Why the action was rejected. Buyer SDKs branch on this to choose a recovery path.' - ), - ] - currently_available_actions: Annotated[ - list[media_buy_available_action.MediaBuyAvailableAction] | None, - Field( - description="Echo of the buy's resolved `available_actions[]` at rejection time. Buyer SDKs render this to the caller as the recovery option set." - ), - ] = None - decline_reason: Annotated[ - seller_policy_decline_reason.SellerPolicyDeclineReason | None, - Field( - description='Coarse seller-policy dimension behind a buy-specific denial. Sellers SHOULD populate this for `reason: not_supported_on_buy` when they have a structured reason and disclosure does not expose private thresholds or enforcement controls. It SHOULD be omitted for structural reasons such as `wrong_status`, `not_supported_on_product`, `mode_mismatch`, and `condition_unresolved`. When present, it MUST be consistent with the enclosing error.recovery classification.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/error_details/agent_permission_denied.py b/src/adcp/types/generated_poc/error_details/agent_permission_denied.py deleted file mode 100644 index 12b2351ee..000000000 --- a/src/adcp/types/generated_poc/error_details/agent_permission_denied.py +++ /dev/null @@ -1,28 +0,0 @@ -# generated by datamodel-codegen: -# filename: error_details/agent_permission_denied.json -# timestamp: 2026-05-22T13:15:12+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - - -class AgentPermissionDeniedDetails(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['agent'], - Field( - description='Discriminator that this `PERMISSION_DENIED` was triggered by a per-buyer-agent provisioning gate (as opposed to a generic credential-shaped failure). Registered subset of `enums/error-scope.json` — sellers MUST set this field exactly to `"agent"` for this shape, and MUST omit `scope` entirely when responding on the unauthenticated/unestablished-identity path required by the cross-tenant oracle clamp. The `AGENT_SUSPENDED` / `AGENT_BLOCKED` codes (per-agent commercial status) do NOT carry an explicit `scope` field — the code itself is the discriminator on those paths, mirroring `BILLING_NOT_PERMITTED_FOR_AGENT`.' - ), - ] = 'agent' - reason: Annotated[ - Literal['sandbox_only'], - Field( - description='Registered per-agent provisioning gate that fired. `"sandbox_only"` — the agent is provisioned for sandbox traffic only and the request was against a non-sandbox account. New `reason` values MUST be added here (the enum is closed by `additionalProperties: false`) so cross-language SDKs can dispatch without parsing prose.' - ), - ] = 'sandbox_only' diff --git a/src/adcp/types/generated_poc/error_details/audience_too_small.py b/src/adcp/types/generated_poc/error_details/audience_too_small.py deleted file mode 100644 index 0ea9a883e..000000000 --- a/src/adcp/types/generated_poc/error_details/audience_too_small.py +++ /dev/null @@ -1,20 +0,0 @@ -# generated by datamodel-codegen: -# filename: error_details/audience_too_small.json -# timestamp: 2026-10-03T15:09:44+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictFloat - - -class AudienceTooSmallDetails(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - minimum_size: Annotated[ - StrictFloat | None, Field(description='Minimum audience size required') - ] = None - current_size: Annotated[StrictFloat | None, Field(description='Current audience size')] = None diff --git a/src/adcp/types/generated_poc/error_details/authorization_required.py b/src/adcp/types/generated_poc/error_details/authorization_required.py deleted file mode 100644 index b49400dab..000000000 --- a/src/adcp/types/generated_poc/error_details/authorization_required.py +++ /dev/null @@ -1,49 +0,0 @@ -# generated by datamodel-codegen: -# filename: error_details/authorization_required.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - -from ..core import downstream_connection_requirement - - -class AuthorizationRequiredDetails(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_connections: Annotated[ - list[downstream_connection_requirement.DownstreamConnectionRequirement] | None, - Field( - description='Complete set of downstream connections known to be required for the relevant product, format, or request.' - ), - ] = None - missing_connections: Annotated[ - list[downstream_connection_requirement.DownstreamConnectionRequirement] | None, - Field( - description='Subset of downstream connections that blocked the current request. Sellers SHOULD populate this array when the caller needs to route a human through a connections flow. Entries with `status` of `missing`, `pending`, `expired`, or `revoked` MUST include either `provider` or `authorization_url` so the buyer can route the remediation unambiguously.' - ), - ] = None - authorization_url: Annotated[ - AnyUrl | None, - Field( - description='General recovery URL when there is a single obvious authorization step or when the seller has its own connection-management page.' - ), - ] = None - authorization_instructions: Annotated[ - str | None, - Field( - description='Human-readable recovery instructions. Use `missing_connections[].authorization_instructions` when instructions differ per downstream connection.' - ), - ] = None - reference_authorization: Annotated[ - dict[str, Any] | None, - Field( - deprecated=True, - description='Legacy or provider-specific authorization hint for the referenced object. Prefer `missing_connections[]` for new implementations.', - ), - ] = None diff --git a/src/adcp/types/generated_poc/error_details/billing_not_permitted_for_agent.py b/src/adcp/types/generated_poc/error_details/billing_not_permitted_for_agent.py deleted file mode 100644 index 113dae0a8..000000000 --- a/src/adcp/types/generated_poc/error_details/billing_not_permitted_for_agent.py +++ /dev/null @@ -1,30 +0,0 @@ -# generated by datamodel-codegen: -# filename: error_details/billing_not_permitted_for_agent.json -# timestamp: 2026-05-22T13:15:12+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..enums import billing_party - - -class BillingNotPermittedForAgentDetails(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - rejected_billing: Annotated[ - billing_party.BillingParty, - Field( - description='The `billing` value the seller rejected — echoed verbatim from the request for caller-side reconciliation.' - ), - ] - suggested_billing: Annotated[ - billing_party.BillingParty | None, - Field( - description="A single billing value the calling buyer agent MAY retry with autonomously. Typically `operator` for passthrough-only agents. Sellers MAY omit this field when no retryable value exists for this agent's commercial relationship — in that case the rejection is terminal-pending-onboarding and the buyer MUST complete offline payments-relationship onboarding before any value other than the original request will succeed. Sellers MUST NOT populate this field with the full subset of values the agent may use; the field surfaces at most one canonical retry." - ), - ] = None diff --git a/src/adcp/types/generated_poc/error_details/billing_not_supported.py b/src/adcp/types/generated_poc/error_details/billing_not_supported.py deleted file mode 100644 index ca1042ad1..000000000 --- a/src/adcp/types/generated_poc/error_details/billing_not_supported.py +++ /dev/null @@ -1,37 +0,0 @@ -# generated by datamodel-codegen: -# filename: error_details/billing_not_supported.json -# timestamp: 2026-05-22T13:15:12+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..enums import billing_party - - -class Scope(StrEnum): - capability = 'capability' - account = 'account' - - -class BillingNotSupportedDetails(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Scope | None, - Field( - description='Which gate fired. `"capability"` — the seller\'s `supported_billing` capability does not include the requested value (advice: choose from `supported_billing` and retry). `"account"` — the seller\'s capability accepts the value generally but not for the specific operator on this account (advice: try the next-most-permissive value the capability allows). Registered subset of the shared discriminator vocabulary in `enums/error-scope.json` — the per-agent scope is reserved for `BILLING_NOT_PERMITTED_FOR_AGENT` (which carries no explicit `scope` field) and for `PERMISSION_DENIED` with `error-details/agent-permission-denied.json`. Sellers MUST omit this field when the response is being returned to a caller whose agent identity has not been established (signed-request derivation or credential-to-agent mapping); see the uniform-response requirement in error-handling.mdx Billing and Account Setup.' - ), - ] = None - supported_billing: Annotated[ - list[billing_party.BillingParty] | None, - Field( - description='Echoed copy of the seller\'s `supported_billing` capability for the caller\'s convenience — saves a `get_adcp_capabilities` round-trip when `scope` is `"capability"`. Sellers MAY omit this field; callers MUST treat absence as "call `get_adcp_capabilities`" rather than "any value is permitted."', - min_length=1, - ), - ] = None diff --git a/src/adcp/types/generated_poc/error_details/budget_too_low.py b/src/adcp/types/generated_poc/error_details/budget_too_low.py deleted file mode 100644 index bc2f8d718..000000000 --- a/src/adcp/types/generated_poc/error_details/budget_too_low.py +++ /dev/null @@ -1,20 +0,0 @@ -# generated by datamodel-codegen: -# filename: error_details/budget_too_low.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictFloat - - -class BudgetTooLowDetails(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - minimum_budget: Annotated[ - StrictFloat | None, Field(description="Seller's minimum budget for this product") - ] = None - currency: Annotated[str | None, Field(description='ISO 4217 currency code')] = None diff --git a/src/adcp/types/generated_poc/error_details/conflict.py b/src/adcp/types/generated_poc/error_details/conflict.py deleted file mode 100644 index 4095d020c..000000000 --- a/src/adcp/types/generated_poc/error_details/conflict.py +++ /dev/null @@ -1,26 +0,0 @@ -# generated by datamodel-codegen: -# filename: error_details/conflict.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictFloat - - -class ConflictDetails(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - resource_id: Annotated[ - str | None, Field(description='Identifier of the conflicting resource') - ] = None - expected_version: Annotated[ - StrictFloat | str | None, - Field(description='Version or ETag the client was operating against'), - ] = None - current_version: Annotated[ - StrictFloat | str | None, Field(description='Current version or ETag on the server') - ] = None diff --git a/src/adcp/types/generated_poc/error_details/creative_rejected.py b/src/adcp/types/generated_poc/error_details/creative_rejected.py deleted file mode 100644 index 458eb442c..000000000 --- a/src/adcp/types/generated_poc/error_details/creative_rejected.py +++ /dev/null @@ -1,23 +0,0 @@ -# generated by datamodel-codegen: -# filename: error_details/creative_rejected.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - - -class CreativeRejectedDetails(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - policy_id: Annotated[str | None, Field(description='Identifier for the violated policy')] = None - policy_url: Annotated[ - AnyUrl | None, Field(description='URL where the full policy can be reviewed') - ] = None - reasons: Annotated[ - list[str] | None, Field(description='Specific reasons the creative was rejected') - ] = None diff --git a/src/adcp/types/generated_poc/error_details/creative_representation_unresolved.py b/src/adcp/types/generated_poc/error_details/creative_representation_unresolved.py deleted file mode 100644 index 176022483..000000000 --- a/src/adcp/types/generated_poc/error_details/creative_representation_unresolved.py +++ /dev/null @@ -1,21 +0,0 @@ -# generated by datamodel-codegen: -# filename: error_details/creative_representation_unresolved.json -# timestamp: 2026-08-28T20:03:29+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..core import representation_rejection - - -class CreativeRepresentationUnresolvedDetails(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - representation_rejections: Annotated[ - list[representation_rejection.RepresentationRejection], Field(min_length=1) - ] diff --git a/src/adcp/types/generated_poc/error_details/creative_revision_content_mismatch.py b/src/adcp/types/generated_poc/error_details/creative_revision_content_mismatch.py deleted file mode 100644 index c1ddbba96..000000000 --- a/src/adcp/types/generated_poc/error_details/creative_revision_content_mismatch.py +++ /dev/null @@ -1,20 +0,0 @@ -# generated by datamodel-codegen: -# filename: error_details/creative_revision_content_mismatch.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..core import creative_revision_id - - -class CreativeRevisionContentMismatchDetails(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - creative_id: Annotated[str, Field(min_length=1)] - revision_id: creative_revision_id.CreativeRevisionId diff --git a/src/adcp/types/generated_poc/error_details/execution_requirement_unmet.py b/src/adcp/types/generated_poc/error_details/execution_requirement_unmet.py deleted file mode 100644 index decc9f70e..000000000 --- a/src/adcp/types/generated_poc/error_details/execution_requirement_unmet.py +++ /dev/null @@ -1,54 +0,0 @@ -# generated by datamodel-codegen: -# filename: error_details/execution_requirement_unmet.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..core import product_execution_requirement - - -class Reason(StrEnum): - not_bound = 'not_bound' - not_found = 'not_found' - ineligible = 'ineligible' - - -class UnmetRequirement(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - product_id: Annotated[ - str, Field(description='Product whose requirement is unmet.', min_length=1) - ] - field: Annotated[ - str, - Field( - description='Request path where the resource binds, in the same JSONPath-lite form as `error.field`: `packages[i].optimization_goals` or `packages[i].catalogs` on `create_media_buy`; `new_packages[i].…` or `packages[i].…` on `update_media_buy`. For `not_found` and `ineligible`, sellers SHOULD point at the offending entry (for example `packages[0].optimization_goals[1].event_sources[0]` or `packages[0].catalogs[0]`).', - min_length=1, - ), - ] - reason: Annotated[ - Reason, - Field( - description='`not_bound`: the package does not reference a resource of this kind. `not_found`: the referenced resource is not available to the account (for example an `event_source_id` that was never registered through `sync_event_sources`); register or sync it, then retry. `ineligible`: the referenced resource exists but does not satisfy the requirement (for example an event type or catalog type the requirement does not list).' - ), - ] - requirement: Annotated[ - product_execution_requirement.ProductExecutionRequirement, - Field( - description="The product's declared requirement, echoed so the buyer can fix the request without another `get_products` call." - ), - ] - - -class ExecutionRequirementUnmetDetails(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - unmet_requirements: Annotated[list[UnmetRequirement], Field(min_length=1)] diff --git a/src/adcp/types/generated_poc/error_details/governance_agent_not_accepted.py b/src/adcp/types/generated_poc/error_details/governance_agent_not_accepted.py deleted file mode 100644 index 5b6d9c9fd..000000000 --- a/src/adcp/types/generated_poc/error_details/governance_agent_not_accepted.py +++ /dev/null @@ -1,51 +0,0 @@ -# generated by datamodel-codegen: -# filename: error_details/governance_agent_not_accepted.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - -from ..governance import accepted_governance_agents as accepted_governance_agents_1 - - -class GovernanceAgentNotAcceptedDetails1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - disclosure: Literal['disclosed'] = 'disclosed' - attempted_agent_origin: Annotated[ - AnyUrl, - Field( - description='Parsed HTTPS origin only. The seller MUST strip userinfo, path, query, and fragment and MUST NOT echo the raw candidate URL.' - ), - ] - accepted_governance_agents: accepted_governance_agents_1.AcceptedGovernanceAgents - - -class GovernanceAgentNotAcceptedDetails2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - disclosure: Literal['opaque'] = 'opaque' - rejection_ref: Annotated[ - str | None, - Field( - description='Optional seller-local correlation reference. It does not identify or reveal an acceptance rule.', - max_length=128, - pattern='^[A-Za-z0-9_.:-]+$', - ), - ] = None - - -GovernanceAgentNotAcceptedDetails = Annotated[ - GovernanceAgentNotAcceptedDetails1 | GovernanceAgentNotAcceptedDetails2, - Field( - description='Per-account rejection details for a governance-agent binding. Sellers choose either the disclosed or opaque arm; callers must not infer undisclosed criteria.', - discriminator='disclosure', - title='Governance Agent Not Accepted Details', - ), -] diff --git a/src/adcp/types/generated_poc/error_details/macro_resolution_failed.py b/src/adcp/types/generated_poc/error_details/macro_resolution_failed.py deleted file mode 100644 index 8f038bdd5..000000000 --- a/src/adcp/types/generated_poc/error_details/macro_resolution_failed.py +++ /dev/null @@ -1,21 +0,0 @@ -# generated by datamodel-codegen: -# filename: error_details/macro_resolution_failed.json -# timestamp: 2026-08-28T20:03:29+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..core import macro_resolution_result - - -class MacroResolutionFailedDetails(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - macro_resolution_results: Annotated[ - list[macro_resolution_result.MacroResolutionResult], Field(min_length=1) - ] diff --git a/src/adcp/types/generated_poc/error_details/policy_violation.py b/src/adcp/types/generated_poc/error_details/policy_violation.py deleted file mode 100644 index 4e1dd159f..000000000 --- a/src/adcp/types/generated_poc/error_details/policy_violation.py +++ /dev/null @@ -1,55 +0,0 @@ -# generated by datamodel-codegen: -# filename: error_details/policy_violation.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - - -class Origin(StrEnum): - buyer_plan = 'buyer_plan' - registry = 'registry' - seller = 'seller' - - -class PolicyViolationDetails(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - origin: Annotated[ - Origin | None, - Field( - description='Whose policy produced the rejection. Optional so undisclosed seller policies can return only the typed POLICY_VIOLATION code if even origin would reveal sensitive information.' - ), - ] = None - policy_id: Annotated[ - str | None, - Field( - description='Shared policy-registry identifier for the violated policy. Use seller_policy_ref instead when the seller is not disclosing a registry policy.' - ), - ] = None - seller_policy_ref: Annotated[ - str | None, - Field( - description='Opaque seller-scoped reference that lets support and audit records correlate an undisclosed policy without publishing its rules. This is not a shared policy-registry ID.', - min_length=1, - ), - ] = None - policy_url: Annotated[ - AnyUrl | None, Field(description='URL where the full policy can be reviewed') - ] = None - violated_rules: Annotated[ - list[str] | None, Field(description='Specific rules that were violated') - ] = None - category: Annotated[ - str | None, - Field( - description="Optional coarse policy category disclosed at the seller's discretion.", - pattern='^[a-z][a-z0-9_]*$', - ), - ] = None diff --git a/src/adcp/types/generated_poc/error_details/rate_limited.py b/src/adcp/types/generated_poc/error_details/rate_limited.py deleted file mode 100644 index 16ef25ce8..000000000 --- a/src/adcp/types/generated_poc/error_details/rate_limited.py +++ /dev/null @@ -1,33 +0,0 @@ -# generated by datamodel-codegen: -# filename: error_details/rate_limited.json -# timestamp: 2026-10-03T15:09:44+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictFloat - - -class Scope(StrEnum): - account = 'account' - tool = 'tool' - global_ = 'global' - - -class RateLimitedDetails(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - limit: Annotated[ - StrictFloat | None, Field(description='Maximum requests allowed in the window') - ] = None - remaining: Annotated[ - StrictFloat | None, Field(description='Requests remaining in the current window') - ] = None - window_seconds: Annotated[ - StrictFloat | None, Field(description='Duration of the rate-limit window in seconds') - ] = None - scope: Annotated[Scope | None, Field(description='What the limit applies to')] = None diff --git a/src/adcp/types/generated_poc/error_details/requote_required.py b/src/adcp/types/generated_poc/error_details/requote_required.py deleted file mode 100644 index 3eb8c2608..000000000 --- a/src/adcp/types/generated_poc/error_details/requote_required.py +++ /dev/null @@ -1,61 +0,0 @@ -# generated by datamodel-codegen: -# filename: error_details/requote_required.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, RootModel - -from ..core import media_buy_change_term_id -from ..enums import seller_policy_decline_reason - - -class EnvelopeField(ScalarStr): - __slots__ = () - _constraints = {'min_length': 1} - _json_schema_extra = { - 'description': 'Buyer-visible request field path or paths that breached the accepted commercial envelope. Values MUST NOT name seller-internal objects or identifiers. New emitters SHOULD use the array form even for one path; the scalar form remains valid for compatibility with existing 3.x sellers.', - } - - -class EnvelopeField1Item(ScalarStr): - __slots__ = () - _constraints = {'min_length': 1} - - -class EnvelopeField1(RootModel[list[EnvelopeField1Item]]): - root: Annotated[ - list[EnvelopeField1Item], - Field( - description='Buyer-visible request field path or paths that breached the accepted commercial envelope. Values MUST NOT name seller-internal objects or identifiers. New emitters SHOULD use the array form even for one path; the scalar form remains valid for compatibility with existing 3.x sellers.', - min_length=1, - ), - ] - - -class RequoteRequiredDetails(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - envelope_field: Annotated[ - EnvelopeField | EnvelopeField1 | None, - Field( - description='Buyer-visible request field path or paths that breached the accepted commercial envelope. Values MUST NOT name seller-internal objects or identifiers. New emitters SHOULD use the array form even for one path; the scalar form remains valid for compatibility with existing 3.x sellers.' - ), - ] = None - change_term_id: Annotated[ - media_buy_change_term_id.MediaBuyChangeTermId | None, - Field( - description='Accepted proposal change term whose typed constraint the request exceeded.' - ), - ] = None - decline_reason: Annotated[ - seller_policy_decline_reason.SellerPolicyDeclineReason | None, - Field( - description='Coarse seller-policy dimension that declined the requested shape. Sellers SHOULD populate this when they have a structured reason and disclosure does not expose private thresholds or enforcement controls. When present, it MUST be consistent with the enclosing error.recovery classification.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/error_details/stale_response.py b/src/adcp/types/generated_poc/error_details/stale_response.py deleted file mode 100644 index 77b47bdd8..000000000 --- a/src/adcp/types/generated_poc/error_details/stale_response.py +++ /dev/null @@ -1,84 +0,0 @@ -# generated by datamodel-codegen: -# filename: error_details/stale_response.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, ConfigDict, Field - - -class Upstream(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - url: Annotated[ - AnyUrl | None, - Field( - description='URL of the unreachable upstream, when the upstream is HTTP-addressable. Sellers MUST omit when the upstream is an internal (non-URL) dependency.' - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable identifier for the upstream, e.g., 'creative-agent-foo' or 'inventory-service'. Used when the upstream is internal or when the URL is not informative on its own." - ), - ] = None - - -class OriginalError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str | None, - Field( - description="Error code from the underlying failure, e.g., SERVICE_UNAVAILABLE, or a transport-level identifier like 'TIMEOUT' / 'CONNECTION_REFUSED' when the underlying call did not produce an AdCP error code." - ), - ] = None - message: Annotated[ - str | None, - Field( - description="Short human-readable description of the underlying failure (e.g., 'Connection timeout after 20s'). MUST NOT include credentials, internal hostnames, or stack traces." - ), - ] = None - - -class StaleResponseDetails(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - served_from_cache: Annotated[ - Literal[True], - Field( - description='Always true for STALE_RESPONSE. Acts as a positive shape sentinel so consumers can validate the details payload matches the code.' - ), - ] - cache_age_seconds: Annotated[ - SchemaInt, - Field( - description='Age of the cached payload in seconds at the time of the response. Informational — buyer agents MAY use this to decide whether to immediately retry for fresh data or accept the cached value.', - ge=0, - ), - ] - freshness_target_seconds: Annotated[ - SchemaInt | None, - Field( - description="The seller's freshness target for this surface, in seconds. `cache_age_seconds - freshness_target_seconds` is how far beyond target the cached entry is. Optional — sellers MAY omit when no public freshness contract is declared.", - ge=0, - ), - ] = None - upstream: Annotated[ - Upstream | None, - Field( - description='Identifies the upstream or sub-agent whose fetch failed and triggered cache fallback. When N upstreams are stale, the seller emits N separate STALE_RESPONSE entries (one per upstream) rather than aggregating into a single entry — mirrors the per-asset advisory precedent set by PIXEL_TRACKER_LOSSY_DOWNGRADE.' - ), - ] = None - original_error: Annotated[ - OriginalError | None, - Field( - description='Minimal subset of the underlying failure that triggered cache fallback. Sellers MUST NOT include internal stack traces, credentials, or connection strings.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/error_details/unsupported_refinement_dimension.py b/src/adcp/types/generated_poc/error_details/unsupported_refinement_dimension.py deleted file mode 100644 index 313c96ee9..000000000 --- a/src/adcp/types/generated_poc/error_details/unsupported_refinement_dimension.py +++ /dev/null @@ -1,35 +0,0 @@ -# generated by datamodel-codegen: -# filename: error_details/unsupported_refinement_dimension.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field - - -class SupportedDimension(ScalarStr): - __slots__ = () - _constraints = {'min_length': 1} - - -class UnsupportedRefinementDimensionDetails(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - unsupported_dimension: Annotated[ - str, - Field( - description='The first typed revision dimension in the request that the seller does not support, named with the supported_dimensions vocabulary.', - min_length=1, - ), - ] - supported_dimensions: Annotated[ - list[SupportedDimension], - Field( - description="The seller's complete supported_dimensions declaration, echoed so the buyer can reconstruct a valid request. An empty array means ask-only refinement." - ), - ] diff --git a/src/adcp/types/generated_poc/error_details/vast_version_mismatch.py b/src/adcp/types/generated_poc/error_details/vast_version_mismatch.py deleted file mode 100644 index 1d19e8d17..000000000 --- a/src/adcp/types/generated_poc/error_details/vast_version_mismatch.py +++ /dev/null @@ -1,161 +0,0 @@ -# generated by datamodel-codegen: -# filename: error_details/vast_version_mismatch.json -# timestamp: 2026-10-04T00:56:30+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..core import format_option_ref as format_option_ref_1 -from ..enums import vast_version - - -class MismatchReason(StrEnum): - asset_outside_acceptance = 'asset_outside_acceptance' - document_version_mismatch = 'document_version_mismatch' - - -class DocumentRole(StrEnum): - submitted = 'submitted' - wrapper = 'wrapper' - terminal_inline = 'terminal_inline' - - -class VastVersionMismatchDetails1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - mismatch_reason: Annotated[ - Literal['asset_outside_acceptance'], - Field(description='Machine-readable modern mismatch branch.'), - ] = 'asset_outside_acceptance' - asset_vast_version: vast_version.VastVersion - observed_document_vast_version: Annotated[ - str | None, - Field( - description='Raw version observed on the inspected VAST document whose root is being validated. `null` means the required version attribute was missing. This field is intentionally not limited to the governed supported-version enum so diagnostics can report future or invalid document values.', - min_length=1, - ), - ] = None - document_role: Annotated[ - DocumentRole | None, - Field( - description='Role of the inspected document. Only the submitted document is required to equal the asset declaration; wrapper and terminal documents are checked against the applicable acceptance sets.' - ), - ] = None - product_vast_versions: Annotated[list[vast_version.VastVersion], Field(min_length=1)] - seller_vast_versions: Annotated[list[vast_version.VastVersion], Field(min_length=1)] - format_option_ref: Annotated[ - format_option_ref_1.FormatOptionReference | None, - Field( - description='Selected option reference when the product option is addressable. Omitted for a unique id-less product option.' - ), - ] = None - supported_versions: Annotated[ - list[vast_version.VastVersion] | None, - Field( - deprecated=True, - description='Deprecated 3.x compatibility field. For an acceptance mismatch, this is the effective product/seller intersection when known. Modern emitters use the typed sets and mismatch_reason.', - min_length=1, - ), - ] = None - - -class VastVersionMismatchDetails2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - mismatch_reason: Annotated[ - Literal['document_version_mismatch'], - Field(description='Machine-readable modern mismatch branch.'), - ] = 'document_version_mismatch' - asset_vast_version: vast_version.VastVersion - observed_document_vast_version: Annotated[ - str | None, - Field( - description='Raw version observed on the inspected VAST document whose root is being validated. `null` means the required version attribute was missing. This field is intentionally not limited to the governed supported-version enum so diagnostics can report future or invalid document values.', - min_length=1, - ), - ] - document_role: Annotated[ - DocumentRole, - Field( - description='Role of the inspected document. Only the submitted document is required to equal the asset declaration; wrapper and terminal documents are checked against the applicable acceptance sets.' - ), - ] - product_vast_versions: Annotated[list[vast_version.VastVersion] | None, Field(min_length=1)] = ( - None - ) - seller_vast_versions: Annotated[list[vast_version.VastVersion] | None, Field(min_length=1)] = ( - None - ) - format_option_ref: Annotated[ - format_option_ref_1.FormatOptionReference | None, - Field( - description='Selected option reference when the product option is addressable. Omitted for a unique id-less product option.' - ), - ] = None - supported_versions: Annotated[ - list[vast_version.VastVersion] | None, - Field( - deprecated=True, - description='Deprecated 3.x compatibility field. For an acceptance mismatch, this is the effective product/seller intersection when known. Modern emitters use the typed sets and mismatch_reason.', - min_length=1, - ), - ] = None - - -class VastVersionMismatchDetails3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - mismatch_reason: Annotated[ - MismatchReason | None, Field(description='Machine-readable modern mismatch branch.') - ] = None - asset_vast_version: vast_version.VastVersion | None = None - observed_document_vast_version: Annotated[ - str | None, - Field( - description='Raw version observed on the inspected VAST document whose root is being validated. `null` means the required version attribute was missing. This field is intentionally not limited to the governed supported-version enum so diagnostics can report future or invalid document values.', - min_length=1, - ), - ] = None - document_role: Annotated[ - DocumentRole | None, - Field( - description='Role of the inspected document. Only the submitted document is required to equal the asset declaration; wrapper and terminal documents are checked against the applicable acceptance sets.' - ), - ] = None - product_vast_versions: Annotated[list[vast_version.VastVersion] | None, Field(min_length=1)] = ( - None - ) - seller_vast_versions: Annotated[list[vast_version.VastVersion] | None, Field(min_length=1)] = ( - None - ) - format_option_ref: Annotated[ - format_option_ref_1.FormatOptionReference | None, - Field( - description='Selected option reference when the product option is addressable. Omitted for a unique id-less product option.' - ), - ] = None - supported_versions: Annotated[ - list[vast_version.VastVersion], - Field( - deprecated=True, - description='Deprecated 3.x compatibility field. For an acceptance mismatch, this is the effective product/seller intersection when known. Modern emitters use the typed sets and mismatch_reason.', - min_length=1, - ), - ] - - -VastVersionMismatchDetails = Annotated[ - VastVersionMismatchDetails1 | VastVersionMismatchDetails2 | VastVersionMismatchDetails3, - Field( - description='Required compatibility details for VAST_VERSION_MISMATCH. Modern emitters discriminate an asset-versus-acceptance mismatch from an inspected-document mismatch. The legacy supported_versions-only arm remains accepted for older 3.x peers.', - title='VAST Version Mismatch Details', - ), -] diff --git a/src/adcp/types/generated_poc/error_details/vendor_error_codes.py b/src/adcp/types/generated_poc/error_details/vendor_error_codes.py deleted file mode 100644 index 284bcee04..000000000 --- a/src/adcp/types/generated_poc/error_details/vendor_error_codes.py +++ /dev/null @@ -1,38 +0,0 @@ -# generated by datamodel-codegen: -# filename: error_details/vendor_error_codes.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, Field - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Codes(AdCPBaseModel): - description: str - recovery: Recovery - - -class Vendors(AdCPBaseModel): - name: Annotated[str, Field(description='Full vendor name')] - url: Annotated[AnyUrl | None, Field(description='Vendor website or documentation URL')] = None - codes: Annotated[ - dict[str, Codes], - Field(description='Map of code suffix to description and recovery classification'), - ] - - -class VendorErrorCodeRegistry(AdCPBaseModel): - vendors: Annotated[ - dict[str, Vendors] | None, - Field(description='Map of vendor prefix to vendor metadata and codes'), - ] = None diff --git a/src/adcp/types/generated_poc/error_details/version_unsupported.py b/src/adcp/types/generated_poc/error_details/version_unsupported.py deleted file mode 100644 index dbfa92cfa..000000000 --- a/src/adcp/types/generated_poc/error_details/version_unsupported.py +++ /dev/null @@ -1,59 +0,0 @@ -# generated by datamodel-codegen: -# filename: error_details/version_unsupported.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from adcp.types._scalar import ScalarInt, ScalarStr - -from typing import Annotated - -from pydantic import ConfigDict, Field - -from ..core.version_envelope import AdcpVersionEnvelope - - -class SupportedVersion(ScalarStr): - __slots__ = () - _constraints = { - 'pattern': '^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?)?$', - } - - -class SupportedMajor(ScalarInt): - __slots__ = () - _constraints = {'ge': 1} - - -class VersionUnsupportedDetails(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - supported_versions: Annotated[ - list[SupportedVersion], - Field( - description='Release-precision versions the seller speaks. Authoritative — buyers SHOULD select a value from this list and retry.', - examples=[['3.0', '3.1']], - min_length=1, - ), - ] - supported_majors: Annotated[ - list[SupportedMajor] | None, - Field( - deprecated=True, - description='DEPRECATED in favor of `supported_versions`. Major versions the seller supports. Servers SHOULD emit both through 3.x; removed in 4.0.', - ), - ] = None - build_version: Annotated[ - str | None, - Field( - description="Optional advisory: full semver build identifier of the seller's deployment (MAJOR.MINOR.PATCH plus optional pre-release and build-metadata segments per semver §9–§10), for incident triage. Buyers MUST NOT use this field for negotiation.", - examples=[ - '3.1.2', - '3.1.0-beta.3', - '3.1.2+scope3.deploy.4821', - '3.1.0-beta.3+sha.a1b2c3d', - ], - pattern='^\\d+\\.\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?(\\+[a-zA-Z0-9.-]+)?$', - ), - ] = None diff --git a/src/adcp/types/generated_poc/extensions/__init__.py b/src/adcp/types/generated_poc/extensions/__init__.py deleted file mode 100644 index 5334de51e..000000000 --- a/src/adcp/types/generated_poc/extensions/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# generated by datamodel-codegen: -# filename: .schema_temp -# timestamp: 2026-01-14T17:08:13+00:00 diff --git a/src/adcp/types/generated_poc/extensions/extension_meta.py b/src/adcp/types/generated_poc/extensions/extension_meta.py deleted file mode 100644 index 6aa395838..000000000 --- a/src/adcp/types/generated_poc/extensions/extension_meta.py +++ /dev/null @@ -1,50 +0,0 @@ -# generated by datamodel-codegen: -# filename: extensions/extension_meta.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, Field - - -class AdcpExtensionFileSchema(AdCPBaseModel): - field_schema: Annotated[ - Literal['http://json-schema.org/draft-07/schema#'], Field(alias='$schema') - ] = 'http://json-schema.org/draft-07/schema#' - title: Annotated[str, Field(description='Human-readable title for the extension')] - description: Annotated[str, Field(description='Description of what this extension provides')] - valid_from: Annotated[ - str, - Field( - description="Minimum AdCP version this extension is compatible with (e.g., '2.5'). Extension will be included in all versioned schema builds >= this version.", - pattern='^\\d+\\.\\d+$', - ), - ] - valid_until: Annotated[ - str | None, - Field( - description="Last AdCP version this extension is compatible with (e.g., '3.0'). Omit if extension is still valid for current and future versions.", - pattern='^\\d+\\.\\d+$', - ), - ] = None - docs_url: Annotated[ - AnyUrl | None, Field(description='URL to documentation for implementors of this extension') - ] = None - type: Annotated[ - Literal['object'], - Field(description='Extensions must be objects (data within ext.{namespace})'), - ] = 'object' - properties: Annotated[ - dict[str, Any], - Field(description='Schema properties defining the structure of ext.{namespace} data'), - ] - required: Annotated[ - list[str] | None, Field(description='Required properties within the extension data') - ] = None - additionalProperties: Annotated[ - Any | None, - Field(description='Whether additional properties are allowed in the extension data'), - ] = None diff --git a/src/adcp/types/generated_poc/formats/__init__.py b/src/adcp/types/generated_poc/formats/__init__.py deleted file mode 100644 index 8930ceaf3..000000000 --- a/src/adcp/types/generated_poc/formats/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# generated by datamodel-codegen: -# filename: .schema_temp -# timestamp: 2026-05-22T13:15:12+00:00 diff --git a/src/adcp/types/generated_poc/formats/canonical/__init__.py b/src/adcp/types/generated_poc/formats/canonical/__init__.py deleted file mode 100644 index 8930ceaf3..000000000 --- a/src/adcp/types/generated_poc/formats/canonical/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# generated by datamodel-codegen: -# filename: .schema_temp -# timestamp: 2026-05-22T13:15:12+00:00 diff --git a/src/adcp/types/generated_poc/formats/canonical/_base.py b/src/adcp/types/generated_poc/formats/canonical/_base.py deleted file mode 100644 index 11adf76c7..000000000 --- a/src/adcp/types/generated_poc/formats/canonical/_base.py +++ /dev/null @@ -1,224 +0,0 @@ -# generated by datamodel-codegen: -# filename: formats/canonical/_base.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarFloat -from pydantic import ConfigDict, Field, StrictBool - -from ...core import downstream_connection_requirement, platform_extension_ref -from ...enums import logo_slot - - -class CompositionModel(StrEnum): - deterministic = 'deterministic' - algorithmic = 'algorithmic' - - -class AssetType(StrEnum): - image = 'image' - video = 'video' - audio = 'audio' - text = 'text' - markdown = 'markdown' - url = 'url' - html = 'html' - css = 'css' - javascript = 'javascript' - vast = 'vast' - daast = 'daast' - display_tag = 'display_tag' - webhook = 'webhook' - brief = 'brief' - catalog = 'catalog' - published_post = 'published_post' - zip = 'zip' - card = 'card' - object = 'object' - pixel_tracker = 'pixel_tracker' - vast_tracker = 'vast_tracker' - daast_tracker = 'daast_tracker' - - -class PixelRatio(ScalarFloat): - __slots__ = () - _constraints = {'gt': 0.0} - - -class RequiredPixelRatio(PixelRatio): - pass - - -class ReferenceMutability(StrEnum): - immutable_snapshot = 'immutable_snapshot' - mutable_requires_reapproval = 'mutable_requires_reapproval' - mutable_auto_recheck = 'mutable_auto_recheck' - - -class Slot(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_group_id: Annotated[ - str, - Field( - description='Canonical asset_group_id from /schemas/core/asset-group-vocabulary.json. Non-canonical IDs are valid but trigger soft warnings.' - ), - ] - asset_type: Annotated[ - AssetType, - Field( - description='Discriminator selecting the asset schema this slot accepts. SDK codegen uses this to type the slot value. `display_tag` is the atomic third-party display representation (URL, inline markup, or paired redirect). `published_post` is an existing-post reference asset. `pixel_tracker` / `vast_tracker` / `daast_tracker` are renderer-fired tracker primitives. `object` is a last-resort fallback.' - ), - ] - required: Annotated[ - StrictBool | None, Field(description='Whether this slot is required for a valid manifest.') - ] = False - min: Annotated[ - SchemaInt | None, Field(description='Minimum count for repeatable / pool slots.', ge=0) - ] = None - max: Annotated[ - SchemaInt | None, Field(description='Maximum count for repeatable / pool slots.', ge=1) - ] = None - max_chars: Annotated[ - SchemaInt | None, - Field( - description="Per-slot character limit. Valid only when `asset_type` is `text`, `markdown`, or `brief`. Mutually exclusive with `max_size_kb` (which applies to binary asset types). Schema enforces via if/then so a producer can't set both on the same slot.", - ge=1, - ), - ] = None - max_size_kb: Annotated[ - SchemaInt | None, - Field( - description="Per-slot file size limit in exact kilobytes, where 1 KB = 1,000 bytes. Valid only when `asset_type` is `image`, `video`, `audio`, or `zip`. Mutually exclusive with `max_chars` (which applies to text asset types). Schema enforces via if/then so a producer can't set both on the same slot.", - ge=1, - ), - ] = None - pixel_ratios: Annotated[ - list[PixelRatio] | None, - Field( - description="Accepted intrinsic-pixel densities for this image-bearing slot. Valid when `asset_type` is `image`, and on a `card` slot where it constrains each card's image media (video media is unaffected). This makes density available to every canonical carrying image assets (native, carousel, responsive, companion images, and image itself), not only `format_kind: image`. When the image canonical also declares top-level `params.pixel_ratios`, the effective set is the intersection; an empty intersection is invalid. One matching asset satisfies the slot unless `required_pixel_ratios` requires rendition coverage.", - min_length=1, - ), - ] = None - required_pixel_ratios: Annotated[ - list[RequiredPixelRatio] | None, - Field( - description='Required density coverage for an image rendition set. Valid only when `asset_type` is `image` and `pixel_ratios` is also declared. Every value MUST appear in the effective accepted set after intersecting any top-level image `params.pixel_ratios`, and the manifest slot value MUST be an array containing exactly one matching image rendition for each required ratio. Other accepted ratios remain optional. For example, `pixel_ratios: [1, 1.5, 2]` with `required_pixel_ratios: [1, 2]` requires the 1x and 2x renditions while making 1.5x optional. SDKs enforce intersection, subset, coverage, and duplicate-ratio rules because JSON Schema draft-07 cannot express them generically.', - min_length=1, - ), - ] = None - logo_slots: Annotated[ - list[logo_slot.LogoSlot] | None, - Field( - description='When `asset_group_id` is `logo`, renderer-facing brand.json logo slots acceptable for this format slot. Producers selecting from brand.json SHOULD prefer `logos[]` entries whose `slots[]` intersects this list, then apply `visual_guidelines.logo_usage_rules[]`.' - ), - ] = None - required_logo_slots: Annotated[ - list[logo_slot.LogoSlot] | None, - Field( - description='Subset of `logo_slots` for which this format expects explicit logo coverage. A manifest or brand-derived logo pool SHOULD include at least one usable logo for each required slot; if coverage is missing, builders SHOULD surface a validation warning or approval mapping instead of guessing from prose.' - ), - ] = None - description: Annotated[ - str | None, - Field(description='Human-readable description of what the slot expects from the buyer.'), - ] = None - consumed_for_production: Annotated[ - StrictBool | None, - Field( - description="Dispatch hint for `build_creative` and v1↔v2 wire translators: when `true`, the slot's value is consumed as INPUT to a production step (host-read script, brief copy fed to generative synthesis, catalog feed driving per-SKU rendering) and is not rendered verbatim. When `false` (default), the slot's value is rendered verbatim on the placement (image bytes, video file, display tag).\n\nMotivates the v1↔v2 dispatch table: pre-v2 buyers shipped production-consumed inputs separately in a `inputs` map on the build_creative request; v2 collapses inputs and rendered assets into a single `assets` map keyed by `asset_group_id`. SDK translators between v1 and v2 use this flag per canonical to know which assets in the v2 manifest map back to v1 `inputs` vs v1 `assets`. Without the per-slot flag the dispatch table lives in adopter code and every SDK gets it slightly different.\n\nProducers SHOULD set this explicitly on slots whose consumption pattern isn't obvious (host-read scripts on `audio_hosted`, briefs on generative `video_hosted`, catalog feeds on `sponsored_placement`). For canonicals where every slot is render-verbatim (`image`, `display_tag`, `video_vast`, `audio_vast`), the default `false` is sufficient and the flag MAY be omitted." - ), - ] = False - - -class CanonicalFormatBase(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - StrictBool | None, - Field( - description='When true, this canonical or seller narrowing may not work as declared. Adopters SHOULD preflight it with validate_input or in a sandbox and SHOULD NOT route production budget without testing; experimental status never makes the deprecated v1 path preferable. Drivers include unsettled spec shape, an adopter runtime gap, and custom shapes awaiting promotion. This replaces the earlier status plus runtime_status axes. Sellers SHOULD set experimental whenever a canonical or declaration is not production-ready.' - ), - ] = False - deprecated: Annotated[ - StrictBool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - StrictBool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The six inherently-v2 canonicals in 3.2 are `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`, `seller_rendered_stateful_display`, and `coordinated_placements`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - StrictBool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[platform_extension_ref.PlatformExtensionReference] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - StrictBool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[downstream_connection_requirement.DownstreamConnectionRequirement] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - SchemaInt | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None diff --git a/src/adcp/types/generated_poc/formats/canonical/agent_placement.py b/src/adcp/types/generated_poc/formats/canonical/agent_placement.py deleted file mode 100644 index 3caf1718c..000000000 --- a/src/adcp/types/generated_poc/formats/canonical/agent_placement.py +++ /dev/null @@ -1,90 +0,0 @@ -# generated by datamodel-codegen: -# filename: formats/canonical/agent_placement.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import SchemaInt -from pydantic import ConfigDict, Field, StrictBool - -from ._base import CanonicalFormatBase - - -class OutputModality(StrEnum): - text = 'text' - audio = 'audio' - card = 'card' - - -class CanonicalFormatAgentPlacementAiSurfaceSponsoredPlacement(CanonicalFormatBase): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - Any | None, - Field( - description="Marked experimental at 3.1 GA: the canonical's tracking model (mention-level impression + attribution, postback shape, cross-surface dedup) is intentionally underspecified for 3.1. Adopters claiming `agent_placement` ship private tracking integrations; buyer agents MUST treat attribution as adapter-defined until the 3.2 tracking-macro spec lands. Promotion to non-experimental gated on the 3.2 tracking-contract spec." - ), - ] = True - v1_translatable: Annotated[ - Any | None, - Field( - description="Inherently new in v2 — AI-surface sponsored mentions weren't expressible as v1 named formats. SDKs MUST NOT emit `FORMAT_PROJECTION_FAILED` for products using this canonical; the v1-unreachability is structural." - ), - ] = False - slots: Annotated[ - Any | None, - Field( - description="agent_placement has minimal buyer-shipped slots — the surface composes the rendered output from brand context (resolved via the manifest's top-level `brand` BrandRef) plus optional offering_ref and landing_page_url assets. None of these assets are rendered verbatim by the buyer; the agent chooses how to use them." - ), - ] = [ - {'asset_group_id': 'offering_ref', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, - ] - output_modality: Annotated[ - OutputModality | None, - Field( - description='How the surface presents the mention. `text` = inline text (chat, search snippet). `audio` = TTS-synthesized voice. `card` = structured card with optional image + text.' - ), - ] = None - max_mention_length_chars: Annotated[ - SchemaInt | None, - Field( - description='For text output: maximum length of the surface-composed mention text.', - ge=1, - ), - ] = None - max_mention_duration_ms: Annotated[ - SchemaInt | None, - Field( - description='For audio output: maximum duration of the spoken mention in milliseconds.', - ge=1, - ), - ] = None - supports_offering_reference: Annotated[ - StrictBool | None, - Field( - description='Whether the product accepts an offering reference (specific product/service to promote within the mention) in addition to brand context.' - ), - ] = None - supports_landing_page_url: Annotated[ - StrictBool | None, - Field( - description='Whether the surface attaches a landing page URL to the mention (citation, learn-more link).' - ), - ] = None - tone_constraints: Annotated[ - list[str] | None, - Field( - description="**Advisory only.** Buyer-declared brand-voice preferences the surface SHOULD honor (e.g., ['formal', 'no_superlatives']). LLM/agentic surfaces have no protocol-level mechanism to verify enforcement — adopters that need hard guarantees should rely on brand.json voice declarations and post-mention review rather than this field. Future revisions may tie this to a structured tone vocabulary; for now treat as free-text guidance." - ), - ] = None - disclosure_required: Annotated[ - StrictBool | None, - Field( - description='Whether the surface must include an explicit sponsorship disclosure label.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/formats/canonical/audio_daast.py b/src/adcp/types/generated_poc/formats/canonical/audio_daast.py deleted file mode 100644 index 992d7d5e3..000000000 --- a/src/adcp/types/generated_poc/formats/canonical/audio_daast.py +++ /dev/null @@ -1,68 +0,0 @@ -# generated by datamodel-codegen: -# filename: formats/canonical/audio_daast.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._scalar import ScalarInt - -from typing import Annotated, Any - -from adcp.types.base import SchemaInt -from pydantic import ConfigDict, Field, StrictBool - -from ...core import daast_tracker_constraints -from ...enums import daast_version as daast_version_1 -from ._base import CanonicalFormatBase - - -class DurationMsRangeItem(ScalarInt): - __slots__ = () - _constraints = {'ge': 0} - - -class CanonicalFormatDaastAudio(CanonicalFormatBase): - model_config = ConfigDict( - extra='allow', - ) - slots: Annotated[ - Any | None, - Field( - description="Default slots for audio_daast canonical. Buyer ships a DAAST tag (URL or inline XML, 1.0 or 1.1) plus an optional clickthrough URL. Tracking events are inherent to DAAST and don't require explicit slots." - ), - ] = [ - {'asset_group_id': 'daast_tag', 'asset_type': 'daast', 'required': True}, - {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, - ] - daast_version: Annotated[ - daast_version_1.DaastVersion | None, - Field( - deprecated=True, - description='Deprecated one-element alias for `daast_versions`. Producers use either the singular legacy alias or the plural 3.2 field, never both.', - ), - ] = None - daast_versions: Annotated[ - daast_tracker_constraints.DaastVersions | None, - Field( - description="Accepted DAAST versions for this format option. A tracker execution selector's daast_versions must be a nonempty subset of this set." - ), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - SchemaInt | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - linear_required: StrictBool | None = None - max_wrapper_depth: Annotated[SchemaInt | None, Field(ge=0)] = None - ssl_required: StrictBool | None = None - companion_image_required: StrictBool | None = None diff --git a/src/adcp/types/generated_poc/formats/canonical/audio_hosted.py b/src/adcp/types/generated_poc/formats/canonical/audio_hosted.py deleted file mode 100644 index bc9bc1480..000000000 --- a/src/adcp/types/generated_poc/formats/canonical/audio_hosted.py +++ /dev/null @@ -1,125 +0,0 @@ -# generated by datamodel-codegen: -# filename: formats/canonical/audio_hosted.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._scalar import ScalarInt - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import SchemaInt -from pydantic import ConfigDict, Field, StrictBool, StrictFloat - -from ._base import CanonicalFormatBase - - -class DurationMsRange(ScalarInt): - __slots__ = () - _constraints = {'ge': 0} - - -class AudioCodec(StrEnum): - mp3 = 'mp3' - aac = 'aac' - wav = 'wav' - opus = 'opus' - flac = 'flac' - - -class AudioSampleRate(ScalarInt): - __slots__ = () - _constraints = {'ge': 1} - - -class AudioChannel(StrEnum): - mono = 'mono' - stereo = 'stereo' - - -class AssetSource(StrEnum): - buyer_uploaded = 'buyer_uploaded' - publisher_host_recorded = 'publisher_host_recorded' - seller_pre_rendered_from_brief = 'seller_pre_rendered_from_brief' - seller_human_designed = 'seller_human_designed' - agent_synthesized = 'agent_synthesized' - publisher_owned_reference = 'publisher_owned_reference' - - -class BuyerAssetAcceptance(StrEnum): - accepted = 'accepted' - rejected = 'rejected' - - -class CanonicalFormatHostedAudio(CanonicalFormatBase): - model_config = ConfigDict( - extra='allow', - ) - slots: Annotated[ - Any | None, - Field( - description="Default slots for buyer-uploaded audio. Host-read products override with a `script` (asset_type: text) or `creative_brief` (asset_type: brief) slot in place of `audio_main`, plus `asset_source: 'publisher_host_recorded'` and `buyer_asset_acceptance: 'rejected'`. TTS-from-script products override similarly with `asset_source: 'seller_pre_rendered_from_brief'`." - ), - ] = [ - {'asset_group_id': 'audio_main', 'asset_type': 'audio', 'required': True}, - {'asset_group_id': 'companion_image', 'asset_type': 'image', 'required': False}, - {'asset_group_id': 'brand_name', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, - ] - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - SchemaInt | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - audio_codecs: list[AudioCodec] | None = None - audio_sample_rates: list[AudioSampleRate] | None = None - audio_channels: list[AudioChannel] | None = None - min_bitrate_kbps: Annotated[SchemaInt | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[SchemaInt | None, Field(ge=1)] = None - max_file_size_mb: Annotated[ - StrictFloat | None, - Field( - description='Maximum hosted audio file size in decimal megabytes. Agents that proxy or cache media SHOULD advertise their effective transport ceiling here.', - gt=0.0, - ), - ] = None - loudness_lufs: Annotated[ - StrictFloat | None, - Field( - description='Required integrated loudness in LUFS (typical: -16 for streaming/podcast, -23 for broadcast). Negative values.' - ), - ] = None - loudness_tolerance_db: Annotated[ - StrictFloat | None, - Field(description='Permitted deviation from loudness_lufs in dB.', ge=0.0), - ] = None - true_peak_dbfs: Annotated[ - StrictFloat | None, Field(description='Maximum true-peak level in dBFS (typical: -2).') - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered audio bytes come from. Single shared enum across canonicals (see `image.json#asset_source` for the full semantics). `publisher_host_recorded`: the publisher's host records the audio (podcast host-read pattern); buyer must use the publisher's build_creative capability. `publisher_owned_reference` is valid only when the product accepts a reference asset whose publisher-owned source resolves to playable audio. `publisher_host_recorded` remains the normal audio-specific host-read value." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded audio. When `rejected`, the buyer cannot ship an audio asset directly — they must use build_creative (or sync_creatives with brief inputs) so the seller produces the audio. Combined with `asset_source`, lets a product declare 'I produce audio from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - companion_image_required: StrictBool | None = None - companion_image_aspect_ratio: str | None = None - companion_image_max_file_size_kb: Annotated[SchemaInt | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None diff --git a/src/adcp/types/generated_poc/formats/canonical/audio_vast.py b/src/adcp/types/generated_poc/formats/canonical/audio_vast.py deleted file mode 100644 index 841bbe664..000000000 --- a/src/adcp/types/generated_poc/formats/canonical/audio_vast.py +++ /dev/null @@ -1,83 +0,0 @@ -# generated by datamodel-codegen: -# filename: formats/canonical/audio_vast.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._scalar import ScalarInt - -from typing import Annotated, Any - -from adcp.types.base import SchemaInt -from pydantic import ConfigDict, Field, StrictBool - -from ...core import vast_media_file_requirements -from ...enums import vast_version as vast_version_1 -from ._base import CanonicalFormatBase - - -class DurationMsRangeItem(ScalarInt): - __slots__ = () - _constraints = {'ge': 0} - - -class CanonicalFormatVastAudio(CanonicalFormatBase): - model_config = ConfigDict( - extra='allow', - ) - slots: Annotated[ - Any | None, - Field( - description='Default slots for the audio_vast canonical. The buyer supplies a VAST tag (URL or inline XML) plus an optional clickthrough URL, which falls back to the VAST ClickThrough when omitted. Tracking events and companion creatives carried by the VAST document do not require separate slots.' - ), - ] = [ - {'asset_group_id': 'vast_tag', 'asset_type': 'vast', 'required': True}, - {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, - ] - vast_versions: Annotated[ - list[vast_version_1.VastVersion] | None, - Field( - description='VAST audio versions accepted by this product format option. Standardized audio support begins at VAST 4.1; listing 2.0, 3.0, or 4.0 explicitly declares seller-supported legacy audio interoperability rather than standards-conformant VAST audio. The asset declares exactly one vast_version; compatibility requires membership in this set and the seller-wide execution set.', - min_length=1, - ), - ] = None - vast_version: Annotated[ - vast_version_1.VastVersion | None, - Field( - deprecated=True, - description='Deprecated one-element alias for vast_versions. Producers use either the singular legacy alias or the plural 3.2 field, never both. VAST 4.1+ is the standards-conformant audio profile; older values declare legacy audio interoperability.', - ), - ] = None - media_file_requirements: Annotated[ - vast_media_file_requirements.VastMediafileRequirements | None, - Field( - description='Technical acceptance constraints for alternative audio MediaFile renditions in each resolved InLine linear creative. Declared MIME types must be audio/*; visual-dimension constraints are invalid because VAST represents audio MediaFiles with width and height 0.' - ), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. duration_ms_exact takes precedence when both fields are present; SDKs SHOULD warn when both are supplied.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - SchemaInt | None, - Field( - description='When set, the resolved audio creative duration must equal this value. Takes precedence over duration_ms_range.', - ge=1, - ), - ] = None - skippable_after_ms: Annotated[ - SchemaInt | None, - Field(description='When skippable, the buyer-side skip threshold in milliseconds.', ge=0), - ] = None - max_wrapper_depth: Annotated[ - SchemaInt | None, Field(description='Maximum VAST wrapper redirect depth permitted.', ge=0) - ] = None - ssl_required: StrictBool | None = None - companion_image_required: Annotated[ - StrictBool | None, - Field(description='Whether the resolved VAST audio ad must include a companion creative.'), - ] = None diff --git a/src/adcp/types/generated_poc/formats/canonical/coordinated_placements.py b/src/adcp/types/generated_poc/formats/canonical/coordinated_placements.py deleted file mode 100644 index 1cf202bf3..000000000 --- a/src/adcp/types/generated_poc/formats/canonical/coordinated_placements.py +++ /dev/null @@ -1,3101 +0,0 @@ -# generated by datamodel-codegen: -# filename: formats/canonical/coordinated_placements.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarFloat, ScalarInt, ScalarStr -from pydantic import ConfigDict, Field, StrictBool, StrictFloat - -from ...core import canvas_constraint, daast_tracker_constraints -from ...core import format_option_ref as format_option_ref_1 -from ...core import placement_ref as placement_ref_1 -from ...core import vast_media_file_requirements -from ...enums import activation_method, catalog_type -from ...enums import ctv_ad_experience as ctv_ad_experience_1 -from ...enums import daast_version as daast_version_1 -from ...enums import motion_level as motion_level_1 -from ...enums import vast_version as vast_version_1 -from ._base import CanonicalFormatBase - - -class ServingPolicy(StrEnum): - seller_served_only = 'seller_served_only' - third_party_allowed = 'third_party_allowed' - - -class MotionLevel(StrEnum): - static = 'static' - limited_motion = 'limited_motion' - - -class Size(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - width: Annotated[SchemaInt, Field(ge=1)] - height: Annotated[SchemaInt, Field(ge=1)] - - -class PixelRatio(ScalarFloat): - __slots__ = () - _constraints = {'gt': 0.0} - - -class ImageFormat(StrEnum): - jpg = 'jpg' - jpeg = 'jpeg' - png = 'png' - gif = 'gif' - webp = 'webp' - svg = 'svg' - - -class AssetSource(StrEnum): - buyer_uploaded = 'buyer_uploaded' - publisher_host_recorded = 'publisher_host_recorded' - seller_pre_rendered_from_brief = 'seller_pre_rendered_from_brief' - seller_human_designed = 'seller_human_designed' - agent_synthesized = 'agent_synthesized' - publisher_owned_reference = 'publisher_owned_reference' - - -class BuyerAssetAcceptance(StrEnum): - accepted = 'accepted' - rejected = 'rejected' - - -class MraidVersion(StrEnum): - field_2_0 = '2.0' - field_3_0 = '3.0' - - -class ClicktagMacro(StrEnum): - clickTag = 'clickTag' - clickTAG = 'clickTAG' - - -class SupportedTagType(StrEnum): - iframe = 'iframe' - javascript = 'javascript' - field_1x1_redirect = '1x1_redirect' - - -class SupportedDeliveryType(StrEnum): - tag_url = 'tag_url' - inline_markup = 'inline_markup' - paired_redirect = 'paired_redirect' - - -class AllowedCardMediaAssetType(StrEnum): - image = 'image' - video = 'video' - - -class Orientation(StrEnum): - vertical = 'vertical' - horizontal = 'horizontal' - square = 'square' - - -class DurationMsRange(ScalarInt): - __slots__ = () - _constraints = {'ge': 0} - - -class VideoCodec(StrEnum): - h264 = 'h264' - h265 = 'h265' - vp8 = 'vp8' - vp9 = 'vp9' - av1 = 'av1' - prores = 'prores' - - -class AudioCodec(StrEnum): - aac = 'aac' - mp3 = 'mp3' - opus = 'opus' - pcm = 'pcm' - - -class Container(StrEnum): - mp4 = 'mp4' - webm = 'webm' - mov = 'mov' - - -class Captions(StrEnum): - required = 'required' - recommended = 'recommended' - not_required = 'not_required' - - -class CompanionBannerWidth(ScalarInt): - __slots__ = () - _constraints = {'ge': 1} - - -class CompanionBannerHeight(CompanionBannerWidth): - pass - - -class VpaidVersion(StrEnum): - field_1_0 = '1.0' - field_2_0 = '2.0' - - -class DurationMsRangeItem(DurationMsRange): - pass - - -class CreativeType(StrEnum): - linear = 'linear' - nonlinear = 'nonlinear' - either = 'either' - - -class AudioCodec2(StrEnum): - mp3 = 'mp3' - aac = 'aac' - wav = 'wav' - opus = 'opus' - flac = 'flac' - - -class AudioSampleRate(CompanionBannerWidth): - pass - - -class AudioChannel(StrEnum): - mono = 'mono' - stereo = 'stereo' - - -class FanoutMode(StrEnum): - per_item = 'per_item' - multi_item_in_creative = 'multi_item_in_creative' - single_item = 'single_item' - - -class SupportedIdType(StrEnum): - asin = 'asin' - sku = 'sku' - gtin = 'gtin' - offering_id = 'offering_id' - store_id = 'store_id' - hotel_id = 'hotel_id' - flight_id = 'flight_id' - vehicle_id = 'vehicle_id' - listing_id = 'listing_id' - program_id = 'program_id' - destination_id = 'destination_id' - app_id = 'app_id' - job_id = 'job_id' - - -class ItemProductionModel(StrEnum): - buyer_uploaded = 'buyer_uploaded' - seller_pre_rendered_from_brief = 'seller_pre_rendered_from_brief' - seller_human_designed = 'seller_human_designed' - agent_synthesized = 'agent_synthesized' - - -class MenuPlacement(StrEnum): - tile = 'tile' - headline_banner = 'headline_banner' - - -class FocusBehavior(StrEnum): - none = 'none' - autoplay_muted = 'autoplay_muted' - autoplay_sound = 'autoplay_sound' - - -class MainImageSize(Size): - pass - - -class IconSize(Size): - pass - - -class ImageFormat1(StrEnum): - jpg = 'jpg' - jpeg = 'jpeg' - png = 'png' - gif = 'gif' - webp = 'webp' - - -class AssetSource5(StrEnum): - buyer_uploaded = 'buyer_uploaded' - seller_pre_rendered_from_brief = 'seller_pre_rendered_from_brief' - seller_human_designed = 'seller_human_designed' - agent_synthesized = 'agent_synthesized' - publisher_owned_reference = 'publisher_owned_reference' - - -class OutputModality(StrEnum): - text = 'text' - audio = 'audio' - card = 'card' - - -class SupplyMode(StrEnum): - components = 'components' - rendered_canvases = 'rendered_canvases' - layered_source = 'layered_source' - - -class Anchoring(StrEnum): - inline = 'inline' - sticky_top = 'sticky_top' - sticky_bottom = 'sticky_bottom' - overlay = 'overlay' - fullscreen_overlay = 'fullscreen_overlay' - underlay = 'underlay' - - -class SlotBinding(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[a-z0-9_]+$'} - - -class Motion(StrEnum): - static = 'static' - animated = 'animated' - - -class WidthRangeItem(CompanionBannerWidth): - pass - - -class WidthMode(StrEnum): - full_bleed = 'full_bleed' - gutter_residual = 'gutter_residual' - - -class HeightRangeItem(CompanionBannerWidth): - pass - - -class Breakpoint(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - breakpoint_id: Annotated[ - str, - Field( - description='Stable identifier used by `state_canvases[].breakpoint_id`.', - pattern='^[a-z][a-z0-9_]*$', - ), - ] - width: Annotated[SchemaInt | None, Field(ge=1)] = None - width_range: Annotated[ - list[WidthRangeItem] | None, - Field( - description='Inclusive [minimum, maximum] accepted canvas width. The first value MUST be no greater than the second; semantic validators enforce ordering.', - max_length=2, - min_length=2, - ), - ] = None - width_mode: Annotated[ - WidthMode | None, - Field( - description='Fluid width classes. `full_bleed`: edge-to-edge viewport width (IAB adhesion, topscroll). `gutter_residual`: the margin remaining beside the page content column (desktop skins). Fluid breakpoints cannot be targeted by `rendered_canvases` supply — semantic validation rejects that pairing.' - ), - ] = None - height: Annotated[SchemaInt | None, Field(ge=1)] = None - height_range: Annotated[ - list[HeightRangeItem] | None, - Field( - description='Inclusive [minimum, maximum] accepted canvas height. The first value MUST be no greater than the second; semantic validators enforce ordering.', - max_length=2, - min_length=2, - ), - ] = None - viewport_height_percent: Annotated[ - StrictFloat | None, - Field( - description='Height as a percentage of the viewport (topscroll 80%, mobile skin panel 15%). Fluid; cannot be targeted by `rendered_canvases` supply.', - gt=0.0, - le=100.0, - ), - ] = None - canvas_aspect_ratio: Annotated[ - str | None, - Field( - description='Aspect ratio the rendered canvas maintains across the size range (IAB flexible units, ratio-bucket responsive). When both fixed `width` and `height` are declared, the ratio MUST be consistent with them.', - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - - -class State(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - state_id: Annotated[ - str, - Field( - description='Stable identifier used by `state_canvases[].state_id` and `state_click_urls[].state_id`.', - pattern='^[a-z][a-z0-9_]*$', - ), - ] - anchoring: Annotated[ - Anchoring, - Field( - description='`underlay` renders the canvas beneath page content, which scrolls over it (IAB New Ad Portfolio underlay class: skins, reveal units). Transitions into `overlay`/`fullscreen_overlay` states SHOULD be user-initiated; non-user-action entries emit LEAN policy warnings.' - ), - ] - slot_bindings: Annotated[ - list[SlotBinding] | None, - Field( - description='Asset group IDs rendered in this state (components mode). Makes the template executable: given components and bindings, assembly is deterministic. Every value MUST resolve to a declared slot. Omitted means all supplied component slots may render.' - ), - ] = None - motion: Annotated[ - Motion | None, - Field( - description='Whether the seller-rendered layout animates within this state (attract loops, load animations). Intra-state animation is seller-rendered; buyer canvases stay static images.' - ), - ] = Motion.static - max_animation_s: Annotated[ - StrictFloat | None, - Field( - description='Upper bound on intra-state animation duration. Required when `motion` is `animated`.', - gt=0.0, - ), - ] = None - breakpoints: Annotated[list[Breakpoint], Field(min_length=1)] - close_affordance: Annotated[ - StrictBool, - Field( - description='Whether this state visibly renders a seller-controlled close affordance. When true, rendering MUST follow IAB New Ad Portfolio close-button guidance (top-right, minimum 50×50 dp, available from state entry).' - ), - ] - - -class Reveal(StrEnum): - none = 'none' - clip_window = 'clip_window' - scroll_parallax = 'scroll_parallax' - - -class Trigger(StrEnum): - timer = 'timer' - in_view_timer = 'in_view_timer' - scroll_threshold = 'scroll_threshold' - scroll_progress = 'scroll_progress' - user_action = 'user_action' - media_event = 'media_event' - - -class Input(StrEnum): - tap = 'tap' - hover = 'hover' - swipe_up = 'swipe_up' - swipe_down = 'swipe_down' - swipe_left = 'swipe_left' - swipe_right = 'swipe_right' - scroll = 'scroll' - expand_control = 'expand_control' - collapse_control = 'collapse_control' - - -class MediaEvent(StrEnum): - video_start = 'video_start' - video_complete = 'video_complete' - - -class Direction(StrEnum): - down = 'down' - up = 'up' - - -class TransitionMode(StrEnum): - instant = 'instant' - animated = 'animated' - scroll_linked = 'scroll_linked' - instant_1 = 'instant' - animated_1 = 'animated' - - -class ScrollReference(StrEnum): - document_progress = 'document_progress' - containing_scroller_progress = 'containing_scroller_progress' - - -class Transitions(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - transition_id: Annotated[ - str, - Field( - description='Stable transition identifier for preview and reporting.', - pattern='^[a-z][a-z0-9_]*$', - ), - ] - from_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] - to_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] - trigger: Annotated[ - Literal['timer'], - Field( - description='Cause of the transition. `timer` counts from state entry; `in_view_timer` counts viewable time in the current state (industry auto-collapse is N seconds in view). `media_event` fires on `video_main` playback milestones. The outcome is expressed separately by `to_state_id`.' - ), - ] = 'timer' - input: Annotated[ - Input | None, - Field( - description='Input driving a user or scroll transition. `hover` expansion is disallowed by IAB NAP guidance and emits a LEAN policy warning visible to buyers.' - ), - ] = None - media_event: Annotated[ - MediaEvent | None, - Field( - description='Playback milestone of `video_main` driving a `media_event` transition (endframes, collapse-on-complete).' - ), - ] = None - direction: Annotated[ - Direction | None, - Field( - description='Scroll direction that arms a `scroll_threshold` transition; enables direction-aware expand/collapse cycles. Re-crossing in the opposite direction does not re-fire this transition.' - ), - ] = Direction.down - transition_mode: Annotated[ - TransitionMode, - Field( - description='`scroll_linked` continuously interpolates the seller-owned layout between the two declared endpoint states; it is valid only with trigger `scroll_progress` and does not permit buyer scripting.' - ), - ] - delay_ms: Annotated[ - SchemaInt, - Field( - description='For `timer`: delay after `from_state_id` activates (re-entry restarts it). For `in_view_timer`: accumulated viewable milliseconds in `from_state_id`. Timer-class transitions in a state cycle MUST declare at least 1000 (anti-strobe floor).', - ge=0, - ), - ] - duration_ms: Annotated[ - SchemaInt | None, - Field(description='Duration of a seller-rendered animated transition.', ge=0), - ] = None - scroll_threshold_percent: Annotated[ - StrictFloat | None, - Field( - description='Viewport/page scroll threshold that starts a `scroll_threshold` transition.', - ge=0.0, - le=100.0, - ), - ] = None - scroll_reference: Annotated[ - ScrollReference | None, - Field( - description='Reference frame for scroll percentages. Progress is `scroll_offset / max(scroll_extent - viewport_extent, 1) * 100`, measured on either the page document or the nearest seller-declared containing scroller.' - ), - ] = None - scroll_start_percent: Annotated[ - StrictFloat | None, - Field( - description='Start of the bounded scroll interval for a `scroll_progress` transition.', - ge=0.0, - le=100.0, - ), - ] = None - scroll_end_percent: Annotated[ - StrictFloat | None, - Field( - description='End of the bounded scroll interval for a `scroll_progress` transition. MUST be greater than `scroll_start_percent`.', - ge=0.0, - le=100.0, - ), - ] = None - preserve_playback: Annotated[ - StrictBool | None, - Field( - description='Whether `video_main` continues without restart while the seller changes state.' - ), - ] = False - - -class Transitions1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - transition_id: Annotated[ - str, - Field( - description='Stable transition identifier for preview and reporting.', - pattern='^[a-z][a-z0-9_]*$', - ), - ] - from_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] - to_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] - trigger: Annotated[ - Literal['in_view_timer'], - Field( - description='Cause of the transition. `timer` counts from state entry; `in_view_timer` counts viewable time in the current state (industry auto-collapse is N seconds in view). `media_event` fires on `video_main` playback milestones. The outcome is expressed separately by `to_state_id`.' - ), - ] = 'in_view_timer' - input: Annotated[ - Input | None, - Field( - description='Input driving a user or scroll transition. `hover` expansion is disallowed by IAB NAP guidance and emits a LEAN policy warning visible to buyers.' - ), - ] = None - media_event: Annotated[ - MediaEvent | None, - Field( - description='Playback milestone of `video_main` driving a `media_event` transition (endframes, collapse-on-complete).' - ), - ] = None - direction: Annotated[ - Direction | None, - Field( - description='Scroll direction that arms a `scroll_threshold` transition; enables direction-aware expand/collapse cycles. Re-crossing in the opposite direction does not re-fire this transition.' - ), - ] = Direction.down - transition_mode: Annotated[ - TransitionMode, - Field( - description='`scroll_linked` continuously interpolates the seller-owned layout between the two declared endpoint states; it is valid only with trigger `scroll_progress` and does not permit buyer scripting.' - ), - ] - delay_ms: Annotated[ - SchemaInt, - Field( - description='For `timer`: delay after `from_state_id` activates (re-entry restarts it). For `in_view_timer`: accumulated viewable milliseconds in `from_state_id`. Timer-class transitions in a state cycle MUST declare at least 1000 (anti-strobe floor).', - ge=0, - ), - ] - duration_ms: Annotated[ - SchemaInt | None, - Field(description='Duration of a seller-rendered animated transition.', ge=0), - ] = None - scroll_threshold_percent: Annotated[ - StrictFloat | None, - Field( - description='Viewport/page scroll threshold that starts a `scroll_threshold` transition.', - ge=0.0, - le=100.0, - ), - ] = None - scroll_reference: Annotated[ - ScrollReference | None, - Field( - description='Reference frame for scroll percentages. Progress is `scroll_offset / max(scroll_extent - viewport_extent, 1) * 100`, measured on either the page document or the nearest seller-declared containing scroller.' - ), - ] = None - scroll_start_percent: Annotated[ - StrictFloat | None, - Field( - description='Start of the bounded scroll interval for a `scroll_progress` transition.', - ge=0.0, - le=100.0, - ), - ] = None - scroll_end_percent: Annotated[ - StrictFloat | None, - Field( - description='End of the bounded scroll interval for a `scroll_progress` transition. MUST be greater than `scroll_start_percent`.', - ge=0.0, - le=100.0, - ), - ] = None - preserve_playback: Annotated[ - StrictBool | None, - Field( - description='Whether `video_main` continues without restart while the seller changes state.' - ), - ] = False - - -class Transitions2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - transition_id: Annotated[ - str, - Field( - description='Stable transition identifier for preview and reporting.', - pattern='^[a-z][a-z0-9_]*$', - ), - ] - from_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] - to_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] - trigger: Annotated[ - Literal['scroll_threshold'], - Field( - description='Cause of the transition. `timer` counts from state entry; `in_view_timer` counts viewable time in the current state (industry auto-collapse is N seconds in view). `media_event` fires on `video_main` playback milestones. The outcome is expressed separately by `to_state_id`.' - ), - ] = 'scroll_threshold' - input: Annotated[ - Literal['scroll'], - Field( - description='Input driving a user or scroll transition. `hover` expansion is disallowed by IAB NAP guidance and emits a LEAN policy warning visible to buyers.' - ), - ] = 'scroll' - media_event: Annotated[ - MediaEvent | None, - Field( - description='Playback milestone of `video_main` driving a `media_event` transition (endframes, collapse-on-complete).' - ), - ] = None - direction: Annotated[ - Direction | None, - Field( - description='Scroll direction that arms a `scroll_threshold` transition; enables direction-aware expand/collapse cycles. Re-crossing in the opposite direction does not re-fire this transition.' - ), - ] = Direction.down - transition_mode: Annotated[ - TransitionMode, - Field( - description='`scroll_linked` continuously interpolates the seller-owned layout between the two declared endpoint states; it is valid only with trigger `scroll_progress` and does not permit buyer scripting.' - ), - ] - delay_ms: Annotated[ - SchemaInt | None, - Field( - description='For `timer`: delay after `from_state_id` activates (re-entry restarts it). For `in_view_timer`: accumulated viewable milliseconds in `from_state_id`. Timer-class transitions in a state cycle MUST declare at least 1000 (anti-strobe floor).', - ge=0, - ), - ] = None - duration_ms: Annotated[ - SchemaInt | None, - Field(description='Duration of a seller-rendered animated transition.', ge=0), - ] = None - scroll_threshold_percent: Annotated[ - StrictFloat, - Field( - description='Viewport/page scroll threshold that starts a `scroll_threshold` transition.', - ge=0.0, - le=100.0, - ), - ] - scroll_reference: Annotated[ - ScrollReference, - Field( - description='Reference frame for scroll percentages. Progress is `scroll_offset / max(scroll_extent - viewport_extent, 1) * 100`, measured on either the page document or the nearest seller-declared containing scroller.' - ), - ] - scroll_start_percent: Annotated[ - StrictFloat | None, - Field( - description='Start of the bounded scroll interval for a `scroll_progress` transition.', - ge=0.0, - le=100.0, - ), - ] = None - scroll_end_percent: Annotated[ - StrictFloat | None, - Field( - description='End of the bounded scroll interval for a `scroll_progress` transition. MUST be greater than `scroll_start_percent`.', - ge=0.0, - le=100.0, - ), - ] = None - preserve_playback: Annotated[ - StrictBool | None, - Field( - description='Whether `video_main` continues without restart while the seller changes state.' - ), - ] = False - - -class TransitionMode3(StrEnum): - instant = 'instant' - animated = 'animated' - scroll_linked = 'scroll_linked' - - -class Transitions3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - transition_id: Annotated[ - str, - Field( - description='Stable transition identifier for preview and reporting.', - pattern='^[a-z][a-z0-9_]*$', - ), - ] - from_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] - to_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] - trigger: Annotated[ - Literal['scroll_progress'], - Field( - description='Cause of the transition. `timer` counts from state entry; `in_view_timer` counts viewable time in the current state (industry auto-collapse is N seconds in view). `media_event` fires on `video_main` playback milestones. The outcome is expressed separately by `to_state_id`.' - ), - ] = 'scroll_progress' - input: Annotated[ - Literal['scroll'], - Field( - description='Input driving a user or scroll transition. `hover` expansion is disallowed by IAB NAP guidance and emits a LEAN policy warning visible to buyers.' - ), - ] = 'scroll' - media_event: Annotated[ - MediaEvent | None, - Field( - description='Playback milestone of `video_main` driving a `media_event` transition (endframes, collapse-on-complete).' - ), - ] = None - direction: Annotated[ - Direction | None, - Field( - description='Scroll direction that arms a `scroll_threshold` transition; enables direction-aware expand/collapse cycles. Re-crossing in the opposite direction does not re-fire this transition.' - ), - ] = Direction.down - transition_mode: Annotated[ - Literal['scroll_linked'], - Field( - description='`scroll_linked` continuously interpolates the seller-owned layout between the two declared endpoint states; it is valid only with trigger `scroll_progress` and does not permit buyer scripting.' - ), - ] = 'scroll_linked' - delay_ms: Annotated[ - SchemaInt | None, - Field( - description='For `timer`: delay after `from_state_id` activates (re-entry restarts it). For `in_view_timer`: accumulated viewable milliseconds in `from_state_id`. Timer-class transitions in a state cycle MUST declare at least 1000 (anti-strobe floor).', - ge=0, - ), - ] = None - duration_ms: Annotated[ - SchemaInt | None, - Field(description='Duration of a seller-rendered animated transition.', ge=0), - ] = None - scroll_threshold_percent: Annotated[ - StrictFloat | None, - Field( - description='Viewport/page scroll threshold that starts a `scroll_threshold` transition.', - ge=0.0, - le=100.0, - ), - ] = None - scroll_reference: Annotated[ - ScrollReference, - Field( - description='Reference frame for scroll percentages. Progress is `scroll_offset / max(scroll_extent - viewport_extent, 1) * 100`, measured on either the page document or the nearest seller-declared containing scroller.' - ), - ] - scroll_start_percent: Annotated[ - StrictFloat, - Field( - description='Start of the bounded scroll interval for a `scroll_progress` transition.', - ge=0.0, - le=100.0, - ), - ] - scroll_end_percent: Annotated[ - StrictFloat, - Field( - description='End of the bounded scroll interval for a `scroll_progress` transition. MUST be greater than `scroll_start_percent`.', - ge=0.0, - le=100.0, - ), - ] - preserve_playback: Annotated[ - StrictBool | None, - Field( - description='Whether `video_main` continues without restart while the seller changes state.' - ), - ] = False - - -class Transitions4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - transition_id: Annotated[ - str, - Field( - description='Stable transition identifier for preview and reporting.', - pattern='^[a-z][a-z0-9_]*$', - ), - ] - from_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] - to_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] - trigger: Annotated[ - Literal['user_action'], - Field( - description='Cause of the transition. `timer` counts from state entry; `in_view_timer` counts viewable time in the current state (industry auto-collapse is N seconds in view). `media_event` fires on `video_main` playback milestones. The outcome is expressed separately by `to_state_id`.' - ), - ] = 'user_action' - input: Annotated[ - Input, - Field( - description='Input driving a user or scroll transition. `hover` expansion is disallowed by IAB NAP guidance and emits a LEAN policy warning visible to buyers.' - ), - ] - media_event: Annotated[ - MediaEvent | None, - Field( - description='Playback milestone of `video_main` driving a `media_event` transition (endframes, collapse-on-complete).' - ), - ] = None - direction: Annotated[ - Direction | None, - Field( - description='Scroll direction that arms a `scroll_threshold` transition; enables direction-aware expand/collapse cycles. Re-crossing in the opposite direction does not re-fire this transition.' - ), - ] = Direction.down - transition_mode: Annotated[ - TransitionMode, - Field( - description='`scroll_linked` continuously interpolates the seller-owned layout between the two declared endpoint states; it is valid only with trigger `scroll_progress` and does not permit buyer scripting.' - ), - ] - delay_ms: Annotated[ - SchemaInt | None, - Field( - description='For `timer`: delay after `from_state_id` activates (re-entry restarts it). For `in_view_timer`: accumulated viewable milliseconds in `from_state_id`. Timer-class transitions in a state cycle MUST declare at least 1000 (anti-strobe floor).', - ge=0, - ), - ] = None - duration_ms: Annotated[ - SchemaInt | None, - Field(description='Duration of a seller-rendered animated transition.', ge=0), - ] = None - scroll_threshold_percent: Annotated[ - StrictFloat | None, - Field( - description='Viewport/page scroll threshold that starts a `scroll_threshold` transition.', - ge=0.0, - le=100.0, - ), - ] = None - scroll_reference: Annotated[ - ScrollReference | None, - Field( - description='Reference frame for scroll percentages. Progress is `scroll_offset / max(scroll_extent - viewport_extent, 1) * 100`, measured on either the page document or the nearest seller-declared containing scroller.' - ), - ] = None - scroll_start_percent: Annotated[ - StrictFloat | None, - Field( - description='Start of the bounded scroll interval for a `scroll_progress` transition.', - ge=0.0, - le=100.0, - ), - ] = None - scroll_end_percent: Annotated[ - StrictFloat | None, - Field( - description='End of the bounded scroll interval for a `scroll_progress` transition. MUST be greater than `scroll_start_percent`.', - ge=0.0, - le=100.0, - ), - ] = None - preserve_playback: Annotated[ - StrictBool | None, - Field( - description='Whether `video_main` continues without restart while the seller changes state.' - ), - ] = False - - -class Transitions5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - transition_id: Annotated[ - str, - Field( - description='Stable transition identifier for preview and reporting.', - pattern='^[a-z][a-z0-9_]*$', - ), - ] - from_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] - to_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] - trigger: Annotated[ - Literal['media_event'], - Field( - description='Cause of the transition. `timer` counts from state entry; `in_view_timer` counts viewable time in the current state (industry auto-collapse is N seconds in view). `media_event` fires on `video_main` playback milestones. The outcome is expressed separately by `to_state_id`.' - ), - ] = 'media_event' - input: Annotated[ - Input | None, - Field( - description='Input driving a user or scroll transition. `hover` expansion is disallowed by IAB NAP guidance and emits a LEAN policy warning visible to buyers.' - ), - ] = None - media_event: Annotated[ - MediaEvent, - Field( - description='Playback milestone of `video_main` driving a `media_event` transition (endframes, collapse-on-complete).' - ), - ] - direction: Annotated[ - Direction | None, - Field( - description='Scroll direction that arms a `scroll_threshold` transition; enables direction-aware expand/collapse cycles. Re-crossing in the opposite direction does not re-fire this transition.' - ), - ] = Direction.down - transition_mode: Annotated[ - TransitionMode, - Field( - description='`scroll_linked` continuously interpolates the seller-owned layout between the two declared endpoint states; it is valid only with trigger `scroll_progress` and does not permit buyer scripting.' - ), - ] - delay_ms: Annotated[ - SchemaInt | None, - Field( - description='For `timer`: delay after `from_state_id` activates (re-entry restarts it). For `in_view_timer`: accumulated viewable milliseconds in `from_state_id`. Timer-class transitions in a state cycle MUST declare at least 1000 (anti-strobe floor).', - ge=0, - ), - ] = None - duration_ms: Annotated[ - SchemaInt | None, - Field(description='Duration of a seller-rendered animated transition.', ge=0), - ] = None - scroll_threshold_percent: Annotated[ - StrictFloat | None, - Field( - description='Viewport/page scroll threshold that starts a `scroll_threshold` transition.', - ge=0.0, - le=100.0, - ), - ] = None - scroll_reference: Annotated[ - ScrollReference | None, - Field( - description='Reference frame for scroll percentages. Progress is `scroll_offset / max(scroll_extent - viewport_extent, 1) * 100`, measured on either the page document or the nearest seller-declared containing scroller.' - ), - ] = None - scroll_start_percent: Annotated[ - StrictFloat | None, - Field( - description='Start of the bounded scroll interval for a `scroll_progress` transition.', - ge=0.0, - le=100.0, - ), - ] = None - scroll_end_percent: Annotated[ - StrictFloat | None, - Field( - description='End of the bounded scroll interval for a `scroll_progress` transition. MUST be greater than `scroll_start_percent`.', - ge=0.0, - le=100.0, - ), - ] = None - preserve_playback: Annotated[ - StrictBool | None, - Field( - description='Whether `video_main` continues without restart while the seller changes state.' - ), - ] = False - - -class Clickthrough(StrEnum): - required = 'required' - optional = 'optional' - none = 'none' - - -class UserControls(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - dismissible: StrictBool - user_collapsible: StrictBool - - -class VideoPlayback(StrEnum): - none = 'none' - auto_muted = 'auto_muted' - user_initiated = 'user_initiated' - - -class AssetType(StrEnum): - image = 'image' - video = 'video' - audio = 'audio' - text = 'text' - markdown = 'markdown' - url = 'url' - html = 'html' - css = 'css' - javascript = 'javascript' - vast = 'vast' - daast = 'daast' - webhook = 'webhook' - brief = 'brief' - catalog = 'catalog' - published_post = 'published_post' - zip = 'zip' - card = 'card' - object = 'object' - pixel_tracker = 'pixel_tracker' - vast_tracker = 'vast_tracker' - daast_tracker = 'daast_tracker' - - -class SharedSlot(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_group_id: Annotated[str, Field(pattern='^[a-z0-9_]+$')] - asset_type: AssetType - required: StrictBool | None = False - min: Annotated[SchemaInt | None, Field(ge=0)] = None - max: Annotated[SchemaInt | None, Field(ge=1)] = None - consumed_by: Annotated[ - list[str], - Field( - description='Component IDs that consume this shared asset. Every value MUST resolve to `components[].component_id`.', - min_length=1, - ), - ] - - -class Components(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - component_id: Annotated[ - str, - Field( - description='Stable coordination-local component identifier. Values MUST be unique within `components[]`.', - pattern='^[a-z][a-z0-9_]*$', - ), - ] - placement_ref: Annotated[ - placement_ref_1.PlacementReference, - Field( - description="Public product placement this component supplies. MUST resolve against the containing product's `placements[]`." - ), - ] - required: StrictBool - sequence: Annotated[ - SchemaInt | None, - Field( - description='Declared presentation order for sequential-messaging coordinated buys. Components sharing a sequence value present simultaneously; absent means unordered/simultaneous (default). Sequence declares seller-rendered ordering, not buyer-controlled timing.', - ge=1, - ), - ] = None - serving_policy: Annotated[ - ServingPolicy | None, - Field( - description='Per-component serving/tracking policy. Some sellers restrict specific components (e.g., page skins) to first-party serving while allowing third-party tags on sibling components. Defaults to the product-level policy when absent.' - ), - ] = None - canvas_constraints: Annotated[ - list[canvas_constraint.CanvasConstraint] | None, - Field( - description='Artwork constraints applied to this component, including safe areas and seller-reserved regions.' - ), - ] = None - format_option_ref: Annotated[ - format_option_ref_1.FormatOptionReference, - Field( - description='Reference to a sibling `format_options[]` entry on the same product. It MUST NOT resolve to `custom` or `coordinated_placements`.' - ), - ] - format_kind: str | None = None - params: dict[str, Any] | None = None - - -class Params(CanonicalFormatBase): - model_config = ConfigDict( - extra='allow', - ) - motion_level: MotionLevel | None = None - slots: Annotated[ - Any | None, - Field( - description="Default slots for image canonical. Buyer ships an image asset (file or hosted URL) plus optional headline, body text, primary text (long-form caption), CTA (typically constrained to an enum via `cta_values`), and clickthrough URL. Products MAY override the default — make `headline` required, narrow `cta` to a value enum, or remove slots the surface doesn't consume." - ), - ] = [ - {'asset_group_id': 'image_main', 'asset_type': 'image', 'required': True}, - {'asset_group_id': 'headline', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'body_text', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'primary_text', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'cta', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, - ] - width: Annotated[ - SchemaInt | None, - Field( - description="Logical render width in pixels — use for fixed-size slots (e.g., a 300×250 IAB MREC). When `pixel_ratios` is absent, the required image asset width is the same value (1x). When `pixel_ratios` is present, an accepted asset's intrinsic width is `width × pixel_ratio`. For multi-size flexible slots, use `sizes[]`; for responsive slots, use the min/max fields. The three size modes are mutually exclusive.", - ge=1, - ), - ] = None - height: Annotated[ - SchemaInt | None, - Field( - description='Logical render height in pixels. Intrinsic asset height is `height × pixel_ratio`, where the ratio defaults to 1 when `pixel_ratios` is absent. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted logical (width, height) render pairs for a multi-size flexible slot. The buyer ships an asset matching one logical size multiplied by one accepted `pixel_ratios` entry (or by 1 when `pixel_ratios` is absent). SDKs MUST treat size and density as separate axes: a 600×500 intrinsic asset at 2x satisfies a logical 300×250 size; it does not create a logical 600×500 placement. Mirrors OpenRTB `banner.format[]` semantics. Mutually exclusive with `(width, height)` and with responsive ranges.', - min_length=1, - ), - ] = None - pixel_ratios: Annotated[ - list[PixelRatio] | None, - Field( - description='Accepted intrinsic-pixel densities for image assets, expressed as intrinsic pixels per logical render pixel (for example `[1, 2]` accepts both standard and Retina renditions). Absence means `[1]` for backward compatibility. This is an acceptance set, not a requirement to submit every rendition: one `image_main` asset satisfying any listed ratio is sufficient unless the effective `image_main` slot declares `required_pixel_ratios`. SDKs determine the effective ratio from `asset.pixel_ratio` when supplied, otherwise infer it from intrinsic asset dimensions divided by the matched logical size. Width and height ratios MUST agree.', - min_length=1, - ), - ] = None - min_width: Annotated[ - SchemaInt | None, - Field( - description="Minimum accepted width in pixels for responsive slots that adapt within a range (e.g., 'any width from 300 to 970'). Use with `max_width` (and optionally `min_height`/`max_height`). Mutually exclusive with `(width, height)` and `sizes[]`.", - ge=1, - ), - ] = None - max_width: Annotated[ - SchemaInt | None, - Field( - description='Maximum accepted width in pixels for responsive slots. Pair with `min_width`. See `min_width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - min_height: Annotated[ - SchemaInt | None, - Field( - description='Minimum accepted height in pixels for responsive slots. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - SchemaInt | None, - Field( - description='Maximum accepted height in pixels for responsive slots. Pair with `min_height`.', - ge=1, - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description="Optional aspect ratio constraint (e.g., '1.91:1', '1:1'). When provided alongside `width`/`height`, must agree. When used with `sizes[]` or responsive ranges, narrows accepted entries to those matching the aspect ratio.", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - max_file_size_kb: Annotated[ - SchemaInt | None, Field(description='Maximum file size in kilobytes.', ge=1) - ] = None - image_formats: Annotated[ - list[ImageFormat] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - StrictBool | None, - Field(description='Whether the image and its trackers must be served over HTTPS.'), - ] = None - headline_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None - body_text_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW'])." - ), - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered asset bytes come from. Single shared enum across all canonicals (`image`, `video_hosted`, `audio_hosted` — replaces the earlier per-canonical `image_source` / `video_source` / `audio_source` fields). `buyer_uploaded` (default): buyer ships a pre-rendered asset. `publisher_host_recorded`: publisher's host records the asset (audio-specific; podcast host-read pattern). `seller_pre_rendered_from_brief`: buyer ships a brief plus structured copy; seller renders ONE asset at sync_creatives or build_creative time (generative-DSP pattern). `seller_human_designed`: seller's design team renders manually from a brief. `agent_synthesized`: AI synthesis pipeline; pair with `synthesis_nondeterministic: true` when the platform cannot guarantee in-spec output (Veo/Sora/Imagen-class). `publisher_owned_reference`: buyer references an existing post or publisher-owned object via a `published_post` slot; the seller resolves and serves the referenced content after authorization/review rather than receiving uploaded bytes.\n\nNot every value is meaningful on every canonical — `publisher_host_recorded` is audio-specific; on `image` or `video_hosted` it has no defined behavior. `publisher_owned_reference` is meaningful only when the product's `slots` declaration accepts a reference asset such as `published_post`. Adopters MUST select a value appropriate to the canonical's asset type. The `slots` declaration is the binding contract for what the buyer ships; `asset_source` is informational and lets buyers understand the production model when picking products." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded assets. When `rejected`, the buyer cannot ship pre-rendered bytes directly — they must use build_creative (or sync_creatives with brief inputs or reference assets) so the seller produces or resolves the asset. Combined with `asset_source`, lets a product declare 'I produce assets from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`) or 'I accept existing post references, not uploaded bytes' (asset_source=`publisher_owned_reference`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - ctv_ad_experience: Annotated[ - ctv_ad_experience_1.CtvAdExperience | None, - Field( - description='CTV experience this option serves. On `image` only `pause` and `screensaver` are valid — the image-plus-copy contract the major pause-ad sellers ingest (seller composites the frame; typical canvas 1920×1080 or a transparent-region overlay). Other experiences route per the matrix in docs/creative/ctv-experiences.mdx.' - ), - ] = None - activation_methods: Annotated[ - list[activation_method.CreativeActivationMethod] | None, - Field( - description='Viewer activation mechanisms this option offers (e.g. `qr_code` on a pause frame). Activations are engagement events, not impressions.' - ), - ] = None - - -class Components1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - component_id: Annotated[ - str, - Field( - description='Stable coordination-local component identifier. Values MUST be unique within `components[]`.', - pattern='^[a-z][a-z0-9_]*$', - ), - ] - placement_ref: Annotated[ - placement_ref_1.PlacementReference, - Field( - description="Public product placement this component supplies. MUST resolve against the containing product's `placements[]`." - ), - ] - required: StrictBool - sequence: Annotated[ - SchemaInt | None, - Field( - description='Declared presentation order for sequential-messaging coordinated buys. Components sharing a sequence value present simultaneously; absent means unordered/simultaneous (default). Sequence declares seller-rendered ordering, not buyer-controlled timing.', - ge=1, - ), - ] = None - serving_policy: Annotated[ - ServingPolicy | None, - Field( - description='Per-component serving/tracking policy. Some sellers restrict specific components (e.g., page skins) to first-party serving while allowing third-party tags on sibling components. Defaults to the product-level policy when absent.' - ), - ] = None - canvas_constraints: Annotated[ - list[canvas_constraint.CanvasConstraint] | None, - Field( - description='Artwork constraints applied to this component, including safe areas and seller-reserved regions.' - ), - ] = None - format_option_ref: Annotated[ - format_option_ref_1.FormatOptionReference | None, - Field( - description='Reference to a sibling `format_options[]` entry on the same product. It MUST NOT resolve to `custom` or `coordinated_placements`.' - ), - ] = None - format_kind: Literal['image'] = 'image' - params: Annotated[ - Params, - Field( - description='Static image creative format. Slots: `image_main` (image asset, file or hosted URL), optional `headline` (text), `body_text` (text), `cta` (text/enum), `landing_page_url` (url). Tracking model: impression pixel + click URL via universal_macros, with optional viewability pixel. Distinct from `html5` (interactive bundles) and `display_tag` (third-party served). AR/dimensions narrow to specific sizes via product parameters — covers IAB display sizes (300x250, 728x90, 970x250, etc.) without a separate iab_size enum.', - title='Canonical Format: Image', - ), - ] - - -class Params2(CanonicalFormatBase): - model_config = ConfigDict( - extra='allow', - ) - slots: Annotated[ - Any | None, - Field( - description="Default slots for html5 canonical. Buyer ships a zip bundle plus optional backup image (required when `backup_image_required: true`) and clickthrough URL. The zip's entry point is typically `index.html`; click handling uses the `clickTag` (or `clickTAG`) macro substituted by the seller at serve time." - ), - ] = [ - {'asset_group_id': 'html5_bundle', 'asset_type': 'zip', 'required': True}, - {'asset_group_id': 'backup_image', 'asset_type': 'image', 'required': False}, - {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, - ] - width: Annotated[ - SchemaInt | None, - Field( - description='Required banner width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - SchemaInt | None, - Field( - description='Required banner height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot (publisher banner that accepts 300×250 OR 728×90 OR 970×250). Mirrors OpenRTB `banner.format[]`. Mutually exclusive with `(width, height)` and with responsive ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - SchemaInt | None, - Field( - description='Minimum accepted width for responsive HTML5 banners that adapt within a range. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - SchemaInt | None, - Field( - description='Maximum accepted width for responsive HTML5 banners. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - SchemaInt | None, - Field( - description='Minimum accepted height for responsive HTML5 banners. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - SchemaInt | None, - Field( - description='Maximum accepted height for responsive HTML5 banners. Pair with `min_height`.', - ge=1, - ), - ] = None - max_initial_load_kb: Annotated[ - SchemaInt | None, - Field( - description='Maximum initial-load file size (zip + above-the-fold assets) in kilobytes. IAB display standards: 200 KB for fixed sizes, 100 KB for mobile.', - ge=1, - ), - ] = None - max_polite_load_kb: Annotated[ - SchemaInt | None, - Field( - description='Maximum polite-load file size after host-initiated subload, in kilobytes. IAB display standards: 500 KB for fixed sizes.', - ge=1, - ), - ] = None - host_initiated_subload: Annotated[ - StrictBool | None, - Field( - description='Whether the host page must initiate the polite-load phase. IAB-compliant banners require true.' - ), - ] = None - max_animation_duration_ms: Annotated[ - SchemaInt | None, - Field( - description='Maximum total animation duration in milliseconds. IAB standard: 30000 (30 seconds).', - ge=0, - ), - ] = None - max_cpu_load_percent: Annotated[ - SchemaInt | None, - Field(description='Maximum CPU load percentage during render.', ge=1, le=100), - ] = None - mraid_required: Annotated[ - StrictBool | None, - Field(description='Whether MRAID compatibility is required (mobile in-app).'), - ] = None - mraid_version: Annotated[ - MraidVersion | None, - Field(description='Required MRAID version when mraid_required is true.'), - ] = None - om_sdk_required: Annotated[ - StrictBool | None, - Field(description='Whether IAB Open Measurement SDK integration is required.'), - ] = None - clicktag_macro: Annotated[ - ClicktagMacro | None, Field(description='Name of the click-tag macro the bundle must use.') - ] = None - backup_image_required: Annotated[ - StrictBool | None, - Field( - description='Whether a backup image must accompany the zip for non-HTML5 environments.' - ), - ] = None - backup_image_max_size_kb: Annotated[ - SchemaInt | None, Field(description='Maximum backup image file size in kilobytes.', ge=1) - ] = None - ssl_required: StrictBool | None = None - - -class Components2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - component_id: Annotated[ - str, - Field( - description='Stable coordination-local component identifier. Values MUST be unique within `components[]`.', - pattern='^[a-z][a-z0-9_]*$', - ), - ] - placement_ref: Annotated[ - placement_ref_1.PlacementReference, - Field( - description="Public product placement this component supplies. MUST resolve against the containing product's `placements[]`." - ), - ] - required: StrictBool - sequence: Annotated[ - SchemaInt | None, - Field( - description='Declared presentation order for sequential-messaging coordinated buys. Components sharing a sequence value present simultaneously; absent means unordered/simultaneous (default). Sequence declares seller-rendered ordering, not buyer-controlled timing.', - ge=1, - ), - ] = None - serving_policy: Annotated[ - ServingPolicy | None, - Field( - description='Per-component serving/tracking policy. Some sellers restrict specific components (e.g., page skins) to first-party serving while allowing third-party tags on sibling components. Defaults to the product-level policy when absent.' - ), - ] = None - canvas_constraints: Annotated[ - list[canvas_constraint.CanvasConstraint] | None, - Field( - description='Artwork constraints applied to this component, including safe areas and seller-reserved regions.' - ), - ] = None - format_option_ref: Annotated[ - format_option_ref_1.FormatOptionReference | None, - Field( - description='Reference to a sibling `format_options[]` entry on the same product. It MUST NOT resolve to `custom` or `coordinated_placements`.' - ), - ] = None - format_kind: Literal['html5'] = 'html5' - params: Annotated[ - Params2, - Field( - description="Interactive HTML5 banner delivered as a zip archive. Slot: `html5_bundle` (zip asset). Tracking model: MRAID + IAB Open Measurement (OM-SDK) + click-tag macro substitution + backup image fallback. Receivers unpack the zip, validate internal structure, and serve from CDN. Distinct from `image` (static, non-interactive) and `display_tag` (third-party served). The zip's entry point is typically `index.html`; click handling uses `clickTag` (or `clickTAG`) macro substitution.", - title='Canonical Format: HTML5 Banner', - ), - ] - - -class Params3(CanonicalFormatBase): - model_config = ConfigDict( - extra='allow', - ) - slots: Annotated[ - Any | None, - Field( - description='Backward-compatible URL-delivery slots. `tag_url` MUST use `url_type: ad_request`. A format option accepting `inline_markup` or `paired_redirect` overrides this list with a required slot whose `asset_type` is `display_tag`; the display-tag asset keeps paired redirect URLs atomic.' - ), - ] = [ - {'asset_group_id': 'tag_url', 'asset_type': 'url', 'required': True}, - {'asset_group_id': 'backup_image', 'asset_type': 'image', 'required': False}, - ] - width: Annotated[ - SchemaInt | None, - Field( - description='Required tag rendering width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - SchemaInt | None, - Field( - description='Required tag rendering height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description="List of accepted (width, height) pairs for a multi-size flexible slot. The buyer's third-party tag must render at one of the listed sizes; the seller picks which size to request at impression time. Mutually exclusive with `(width, height)` and with responsive ranges.", - min_length=1, - ), - ] = None - min_width: Annotated[ - SchemaInt | None, - Field( - description='Minimum accepted width for responsive third-party tags. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - SchemaInt | None, - Field( - description='Maximum accepted width for responsive third-party tags. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - SchemaInt | None, - Field( - description='Minimum accepted height for responsive third-party tags. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - SchemaInt | None, - Field( - description='Maximum accepted height for responsive third-party tags. Pair with `min_height`.', - ge=1, - ), - ] = None - supported_tag_types: Annotated[ - list[SupportedTagType] | None, - Field( - deprecated=True, - description='Deprecated ambiguous mechanism list. Use `supported_delivery_types`; markup subtype lives on the `display_tag` asset.', - ), - ] = None - supported_delivery_types: Annotated[ - list[SupportedDeliveryType] | None, - Field( - description='Closed set of delivery types this format option can traffic. `paired_redirect` means one atomic ad-request/click-through pair (Internal Redirect semantics), never independently matchable URL slots.', - min_length=1, - ), - ] = None - ssl_required: Annotated[ - StrictBool | None, Field(description='Whether the tag URL must be HTTPS.') - ] = None - max_redirect_depth: Annotated[ - SchemaInt | None, Field(description='Maximum redirect chain depth permitted.', ge=0) - ] = None - max_response_time_ms: Annotated[ - SchemaInt | None, - Field(description='Maximum tag-server response time in milliseconds.', ge=1), - ] = None - backup_image_required: Annotated[ - StrictBool | None, - Field( - description='Whether a backup image must accompany the tag for environments that cannot render the third-party tag.' - ), - ] = None - backup_image_max_size_kb: Annotated[SchemaInt | None, Field(ge=1)] = None - om_sdk_required: Annotated[ - StrictBool | None, - Field( - description="Whether the buyer's tag must integrate IAB Open Measurement SDK for viewability." - ), - ] = None - - -class Components3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - component_id: Annotated[ - str, - Field( - description='Stable coordination-local component identifier. Values MUST be unique within `components[]`.', - pattern='^[a-z][a-z0-9_]*$', - ), - ] - placement_ref: Annotated[ - placement_ref_1.PlacementReference, - Field( - description="Public product placement this component supplies. MUST resolve against the containing product's `placements[]`." - ), - ] - required: StrictBool - sequence: Annotated[ - SchemaInt | None, - Field( - description='Declared presentation order for sequential-messaging coordinated buys. Components sharing a sequence value present simultaneously; absent means unordered/simultaneous (default). Sequence declares seller-rendered ordering, not buyer-controlled timing.', - ge=1, - ), - ] = None - serving_policy: Annotated[ - ServingPolicy | None, - Field( - description='Per-component serving/tracking policy. Some sellers restrict specific components (e.g., page skins) to first-party serving while allowing third-party tags on sibling components. Defaults to the product-level policy when absent.' - ), - ] = None - canvas_constraints: Annotated[ - list[canvas_constraint.CanvasConstraint] | None, - Field( - description='Artwork constraints applied to this component, including safe areas and seller-reserved regions.' - ), - ] = None - format_option_ref: Annotated[ - format_option_ref_1.FormatOptionReference | None, - Field( - description='Reference to a sibling `format_options[]` entry on the same product. It MUST NOT resolve to `custom` or `coordinated_placements`.' - ), - ] = None - format_kind: Literal['display_tag'] = 'display_tag' - params: Annotated[ - Params3, - Field( - description='Third-party-served display creative delivered as one of three explicit representations: a single ad-request URL, byte-preserved inline markup, or an atomic paired redirect (`ad_request_url` plus `clickthrough_url`). The backward-compatible default slot remains `tag_url` (`url_type: ad_request`). Format options accepting inline or paired delivery override `slots` with a `display_tag` asset and declare `supported_delivery_types`. Tracking is opaque to the seller except for declared macro resolution. Distinct from `image` and seller-hosted `html5` zip bundles.', - title='Canonical Format: Display Tag', - ), - ] - - -class Params4(CanonicalFormatBase): - model_config = ConfigDict( - extra='allow', - ) - v1_translatable: Annotated[ - Any | None, - Field( - description="Inherently new in v2 — multi-card carousels (Meta carousel, Pinterest pin collections, Snap collection ads) weren't expressible as v1 named formats. SDKs MUST NOT emit `FORMAT_PROJECTION_FAILED` for products using this canonical; the v1-unreachability is structural." - ), - ] = False - slots: Annotated[ - Any | None, - Field( - description="Default slots for image_carousel. The `cards` slot's value in the manifest is an array of [card-asset](/schemas/core/assets/card-asset.json) objects; `min` / `max` constrain card count." - ), - ] = [ - {'asset_group_id': 'cards', 'asset_type': 'card', 'required': True, 'min': 2, 'max': 10}, - {'asset_group_id': 'primary_text', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, - ] - card_aspect_ratio: Annotated[ - str | None, - Field( - description="Aspect ratio shared across all cards (e.g., '1:1', '1.91:1', '4:5').", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_cards: Annotated[ - SchemaInt | None, Field(description='Minimum card count (typical: 2 or 3).', ge=2) - ] = None - max_cards: Annotated[ - SchemaInt | None, - Field(description='Maximum card count (typical: 6, 10, or 35 depending on platform).'), - ] = None - allowed_card_media_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='Asset types each card\'s `media` field may carry. Default: [\'image\']. Polymorphic carousels (Meta) allow [\'image\', \'video\']. Renamed from `allowed_card_asset_types` to disambiguate that this constrains the card\'s media payload, not the card-asset itself (which is always asset_type: "card").' - ), - ] = None - allowed_card_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - deprecated=True, - description='DEPRECATED — alias for `allowed_card_media_asset_types`. Kept for back-compat; prefer the new field name. Removed in 5.0.', - ), - ] = None - card_image_max_file_size_kb: Annotated[SchemaInt | None, Field(ge=1)] = None - card_video_max_file_size_kb: Annotated[SchemaInt | None, Field(ge=1)] = None - card_video_max_duration_ms: Annotated[SchemaInt | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[ - SchemaInt | None, - Field(description='Maximum length of the carousel-level primary text.', ge=1), - ] = None - card_headline_max_chars: Annotated[ - SchemaInt | None, - Field( - description='Per-card headline character limit. Governs the `headline` field on each card-asset in the `cards` slot.', - ge=1, - ), - ] = None - card_description_max_chars: Annotated[ - SchemaInt | None, - Field( - description='Per-card description character limit. Governs the `description` field on each card-asset in the `cards` slot. Distinct from `card_headline_max_chars`: description is longer body copy (typically 100-500 chars); headline is the short label (typically 25-40 chars).', - ge=1, - ), - ] = None - ssl_required: StrictBool | None = None - - -class Components4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - component_id: Annotated[ - str, - Field( - description='Stable coordination-local component identifier. Values MUST be unique within `components[]`.', - pattern='^[a-z][a-z0-9_]*$', - ), - ] - placement_ref: Annotated[ - placement_ref_1.PlacementReference, - Field( - description="Public product placement this component supplies. MUST resolve against the containing product's `placements[]`." - ), - ] - required: StrictBool - sequence: Annotated[ - SchemaInt | None, - Field( - description='Declared presentation order for sequential-messaging coordinated buys. Components sharing a sequence value present simultaneously; absent means unordered/simultaneous (default). Sequence declares seller-rendered ordering, not buyer-controlled timing.', - ge=1, - ), - ] = None - serving_policy: Annotated[ - ServingPolicy | None, - Field( - description='Per-component serving/tracking policy. Some sellers restrict specific components (e.g., page skins) to first-party serving while allowing third-party tags on sibling components. Defaults to the product-level policy when absent.' - ), - ] = None - canvas_constraints: Annotated[ - list[canvas_constraint.CanvasConstraint] | None, - Field( - description='Artwork constraints applied to this component, including safe areas and seller-reserved regions.' - ), - ] = None - format_option_ref: Annotated[ - format_option_ref_1.FormatOptionReference | None, - Field( - description='Reference to a sibling `format_options[]` entry on the same product. It MUST NOT resolve to `custom` or `coordinated_placements`.' - ), - ] = None - format_kind: Literal['image_carousel'] = 'image_carousel' - params: Annotated[ - Params4, - Field( - description='Multi-card swipeable carousel. The buyer ships a `cards` slot whose value is an **array** of [card-asset](/schemas/core/assets/card-asset.json) objects (a single key with an array value — NOT one key per card, NOT dotted/bracketed paths). Each card-asset carries: `asset_type: "card"`, `media` (an image or video asset), optional `headline` (text), optional `landing_page_url` (url asset). Per-card structure is the same across all cards; mixed orientations not allowed within a single carousel. Tracking model: per-card impression and engagement pixels + carousel-level engagement (swipe, view-time). Allowed asset types for a card\'s `media` field: `image` and `video` (Meta-style mixed-media); platforms can narrow to image-only or video-only via `allowed_card_media_asset_types`.\n\nThe manifest\'s `assets.cards` value is an array of card-asset objects. Example: `"cards": [{"asset_type": "card", "media": {"asset_type": "image", "url": "..."}, "headline": "Buy now", "landing_page_url": {"asset_type": "url", "url_type": "clickthrough", "url": "..."}}, ...]`. Each card-asset validates against the card schema; per-card platform extensions attach via the card\'s `platform_extensions` field, never via inline non-canonical keys.', - title='Canonical Format: Image Carousel', - ), - ] - - -class Params5(CanonicalFormatBase): - model_config = ConfigDict( - extra='allow', - ) - slots: Annotated[ - Any | None, - Field( - description='Default slots for video_hosted canonical. Buyer ships a video asset (file or hosted URL); optional headline, primary text (long-form caption), CTA (typically constrained via `cta_values`), brand_name (typical for vertical short-form), companion_banner (typical for horizontal instream), and clickthrough URL. Products MAY override or extend the default — e.g., remove `companion_banner` for short-form vertical, narrow `cta` to a value enum, mark `landing_page_url` as required.' - ), - ] = [ - {'asset_group_id': 'video_main', 'asset_type': 'video', 'required': True}, - {'asset_group_id': 'headline', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'primary_text', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'cta', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'brand_name', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'companion_banner', 'asset_type': 'image', 'required': False}, - {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, - ] - orientation: Annotated[ - Orientation | None, - Field( - description='Video orientation. Vertical = 9:16 (Reels, Stories, Shorts). Horizontal = 16:9 (instream, CTV). Square = 1:1 (in-feed).' - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description='Aspect ratio. Inferred from orientation if omitted.', - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_width: Annotated[SchemaInt | None, Field(ge=1)] = None - min_height: Annotated[SchemaInt | None, Field(ge=1)] = None - max_width: Annotated[SchemaInt | None, Field(ge=1)] = None - max_height: Annotated[SchemaInt | None, Field(ge=1)] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - SchemaInt | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship (see `duration_ms_range` description).', - ge=1, - ), - ] = None - video_codecs: list[VideoCodec] | None = None - audio_codecs: list[AudioCodec] | None = None - containers: list[Container] | None = None - min_bitrate_kbps: Annotated[SchemaInt | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[SchemaInt | None, Field(ge=1)] = None - max_file_size_mb: Annotated[ - SchemaInt | None, - Field(description='Maximum file size, where 1 MB is exactly 1,000,000 bytes.', ge=1), - ] = None - frame_rates: list[StrictFloat] | None = None - captions: Captions | None = None - om_sdk_required: StrictBool | None = None - headline_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None - cta_values: list[str] | None = None - companion_banner_widths: Annotated[ - list[CompanionBannerWidth] | None, - Field(description='Permitted companion banner widths (instream video).'), - ] = None - companion_banner_heights: list[CompanionBannerHeight] | None = None - asset_source: Annotated[ - AssetSource | None, - Field( - description='Where the rendered asset bytes come from. Single shared enum across canonicals. See `image.json#asset_source` for the full semantics. `publisher_host_recorded` is audio-specific and has no defined behavior on video. `publisher_owned_reference` is valid when the product accepts an existing post reference via a `published_post` slot instead of uploaded video bytes. Adopters MUST select a value appropriate to the canonical.' - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded video. When `rejected`, the buyer cannot ship a video asset directly — they must use build_creative, sync_creatives with brief inputs, or sync_creatives with an accepted reference asset so the seller produces or resolves the video.' - ), - ] = BuyerAssetAcceptance.accepted - ctv_ad_experience: Annotated[ - ctv_ad_experience_1.CtvAdExperience | None, - Field( - description='CTV experience this option serves. On `video_hosted` only `screensaver` is valid (ambient looping video the platform plays on idle). Other experiences route per the matrix in docs/creative/ctv-experiences.mdx; linear CTV video declares no experience.' - ), - ] = None - - -class Components5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - component_id: Annotated[ - str, - Field( - description='Stable coordination-local component identifier. Values MUST be unique within `components[]`.', - pattern='^[a-z][a-z0-9_]*$', - ), - ] - placement_ref: Annotated[ - placement_ref_1.PlacementReference, - Field( - description="Public product placement this component supplies. MUST resolve against the containing product's `placements[]`." - ), - ] - required: StrictBool - sequence: Annotated[ - SchemaInt | None, - Field( - description='Declared presentation order for sequential-messaging coordinated buys. Components sharing a sequence value present simultaneously; absent means unordered/simultaneous (default). Sequence declares seller-rendered ordering, not buyer-controlled timing.', - ge=1, - ), - ] = None - serving_policy: Annotated[ - ServingPolicy | None, - Field( - description='Per-component serving/tracking policy. Some sellers restrict specific components (e.g., page skins) to first-party serving while allowing third-party tags on sibling components. Defaults to the product-level policy when absent.' - ), - ] = None - canvas_constraints: Annotated[ - list[canvas_constraint.CanvasConstraint] | None, - Field( - description='Artwork constraints applied to this component, including safe areas and seller-reserved regions.' - ), - ] = None - format_option_ref: Annotated[ - format_option_ref_1.FormatOptionReference | None, - Field( - description='Reference to a sibling `format_options[]` entry on the same product. It MUST NOT resolve to `custom` or `coordinated_placements`.' - ), - ] = None - format_kind: Literal['video_hosted'] = 'video_hosted' - params: Annotated[ - Params5, - Field( - description='Direct video file (mp4/webm/mov) hosted by the buyer. Slot: `video_main` (video asset, file or hosted URL), optional `headline`, `brand_name`, `cta`, `companion_banner`, `landing_page_url`. Tracking model: IAB Open Measurement SDK + external impression/click/quartile pixels via universal_macros. Orientation is a parameter (vertical 9:16 / horizontal 16:9 / square 1:1); slot shape includes optional `brand_name` (typical for vertical short-form) and optional `companion_banner` (typical for horizontal instream). Distinct from `video_vast` (VAST tag, inherent VAST event tracking) — receivers fire impression and click pixels at delivery time.', - title='Canonical Format: Hosted Video', - ), - ] - - -class Params6(CanonicalFormatBase): - model_config = ConfigDict( - extra='allow', - ) - slots: Annotated[ - Any | None, - Field( - description="Default slots for video_vast canonical. Buyer ships a VAST tag (URL or inline XML, VAST 2.x-4.x) plus an optional clickthrough URL (which falls back to the VAST `ClickThrough` element when omitted). Tracking events are inherent to VAST and don't require explicit slots." - ), - ] = [ - {'asset_group_id': 'vast_tag', 'asset_type': 'vast', 'required': True}, - {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, - ] - orientation: Orientation | None = None - aspect_ratio: Annotated[ - str | None, Field(pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$') - ] = None - vast_versions: Annotated[ - list[vast_version_1.VastVersion] | None, - Field( - description='VAST versions accepted by this product format option. The asset still declares exactly one `vast_version`; compatibility requires membership in this set and the seller-wide execution set.', - min_length=1, - ), - ] = None - vast_version: Annotated[ - vast_version_1.VastVersion | None, - Field( - deprecated=True, - description='Deprecated one-element alias for `vast_versions`. Producers use either the singular legacy alias or the plural 3.2 field, never both.', - ), - ] = None - media_file_requirements: Annotated[ - vast_media_file_requirements.VastMediafileRequirements | None, - Field( - description='Technical acceptance constraints for alternative VAST MediaFile renditions. Each applicable resolved InLine linear creative needs at least one MediaFile satisfying all declared constraints.' - ), - ] = None - vpaid_enabled: Annotated[ - StrictBool | None, - Field( - description='Whether VPAID interactivity is supported. When true, the VAST tag may carry VPAID JS/Flash payloads.' - ), - ] = None - vpaid_version: VpaidVersion | None = None - simid_supported: Annotated[ - StrictBool | None, - Field( - description='Whether the seller accepts IAB SIMID through `` on a Linear VAST creative. SIMID is not a generic VAST extension and cannot be serialized under NonLinearAds; every `ctv_ad_experience` profile therefore forbids `true`.' - ), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - SchemaInt | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - min_width: Annotated[ - SchemaInt | None, - Field( - description='Minimum placement/player width in pixels. MediaFile rendition dimensions are declared in `media_file_requirements`.', - ge=1, - ), - ] = None - max_width: Annotated[ - SchemaInt | None, - Field( - description='Maximum placement/player width in pixels. MediaFile rendition dimensions are declared in `media_file_requirements`.', - ge=1, - ), - ] = None - min_height: Annotated[ - SchemaInt | None, - Field( - description='Minimum placement/player height in pixels. MediaFile rendition dimensions are declared in `media_file_requirements`.', - ge=1, - ), - ] = None - max_height: Annotated[ - SchemaInt | None, - Field( - description='Maximum placement/player height in pixels. MediaFile rendition dimensions are declared in `media_file_requirements`.', - ge=1, - ), - ] = None - creative_type: Annotated[ - CreativeType | None, - Field( - description='Required VAST creative class: `linear` (in-stream Linear), `nonlinear` (NonLinearAds overlay-class), or `either`. Supersedes `linear_required`; when both are present `creative_type` wins, and validators treat `linear_required: true` with no `creative_type` as `linear`.' - ), - ] = None - ctv_ad_experience: Annotated[ - ctv_ad_experience_1.CtvAdExperience | None, - Field( - description='CTV experience this option is eligible to serve. On video_vast only `pause`, `screensaver`, `overlay`, `squeezeback`, and `in_scene` are valid (`menu` routes to native_in_feed or sponsored_placement), and `creative_type` MUST be `nonlinear`. Because VAST places `` only under Linear ``, `simid_supported` MUST NOT be true on any of these NonLinear profiles. Per-experience floors: `overlay` and `squeezeback` require a 10s minimum duration; `in_scene` requires a 3s minimum brand-exposure duration and forbids interactivity (`vpaid_enabled` MUST NOT be true); `pause` has no duration floor and ends on viewer or device action.' - ), - ] = None - motion_level: Annotated[ - motion_level_1.CreativeMotionLevel | None, - Field(description='Accepted motion class for the rendered creative (AdCOM attrs 21-23).'), - ] = None - activation_methods: Annotated[ - list[activation_method.CreativeActivationMethod] | None, - Field( - description='Viewer activation mechanisms this option offers. Activations are engagement events, not impressions.' - ), - ] = None - linear_required: Annotated[ - StrictBool | None, - Field( - description='Whether the VAST creative must be linear (non-skippable in-stream). Superseded by `creative_type`; retained for pre-3.2 declarations.' - ), - ] = None - skippable_after_ms: Annotated[ - SchemaInt | None, - Field( - description='When skippable, the buyer-side skip threshold in milliseconds (e.g., 5000 for 5-second skippable pre-roll).', - ge=0, - ), - ] = None - max_wrapper_depth: Annotated[ - SchemaInt | None, Field(description='Maximum VAST wrapper redirect depth permitted.', ge=0) - ] = None - ssl_required: StrictBool | None = None - - -class Components6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - component_id: Annotated[ - str, - Field( - description='Stable coordination-local component identifier. Values MUST be unique within `components[]`.', - pattern='^[a-z][a-z0-9_]*$', - ), - ] - placement_ref: Annotated[ - placement_ref_1.PlacementReference, - Field( - description="Public product placement this component supplies. MUST resolve against the containing product's `placements[]`." - ), - ] - required: StrictBool - sequence: Annotated[ - SchemaInt | None, - Field( - description='Declared presentation order for sequential-messaging coordinated buys. Components sharing a sequence value present simultaneously; absent means unordered/simultaneous (default). Sequence declares seller-rendered ordering, not buyer-controlled timing.', - ge=1, - ), - ] = None - serving_policy: Annotated[ - ServingPolicy | None, - Field( - description='Per-component serving/tracking policy. Some sellers restrict specific components (e.g., page skins) to first-party serving while allowing third-party tags on sibling components. Defaults to the product-level policy when absent.' - ), - ] = None - canvas_constraints: Annotated[ - list[canvas_constraint.CanvasConstraint] | None, - Field( - description='Artwork constraints applied to this component, including safe areas and seller-reserved regions.' - ), - ] = None - format_option_ref: Annotated[ - format_option_ref_1.FormatOptionReference | None, - Field( - description='Reference to a sibling `format_options[]` entry on the same product. It MUST NOT resolve to `custom` or `coordinated_placements`.' - ), - ] = None - format_kind: Literal['video_vast'] = 'video_vast' - params: Annotated[ - Params6, - Field( - description='VAST-tag-delivered video creative. Slot: `vast_tag` (vast asset, URL or inline XML, VAST 2.x-4.x). Tracking model: VAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `expand`, `collapse`, `fullscreen`, `creativeView`, `clickTracking`, `error`. VPAID interactivity via `vpaid_enabled: true` flag. SIMID is carried by the first-class VAST 4.1+ `` element on Linear creatives. Orientation is a parameter (vertical / horizontal / square). Distinct from `video_hosted` (direct file with external tracking).', - title='Canonical Format: VAST Video', - ), - ] - - -class Params7(CanonicalFormatBase): - model_config = ConfigDict( - extra='allow', - ) - slots: Annotated[ - Any | None, - Field( - description="Default slots for buyer-uploaded audio. Host-read products override with a `script` (asset_type: text) or `creative_brief` (asset_type: brief) slot in place of `audio_main`, plus `asset_source: 'publisher_host_recorded'` and `buyer_asset_acceptance: 'rejected'`. TTS-from-script products override similarly with `asset_source: 'seller_pre_rendered_from_brief'`." - ), - ] = [ - {'asset_group_id': 'audio_main', 'asset_type': 'audio', 'required': True}, - {'asset_group_id': 'companion_image', 'asset_type': 'image', 'required': False}, - {'asset_group_id': 'brand_name', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, - ] - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - SchemaInt | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - audio_codecs: list[AudioCodec2] | None = None - audio_sample_rates: list[AudioSampleRate] | None = None - audio_channels: list[AudioChannel] | None = None - min_bitrate_kbps: Annotated[SchemaInt | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[SchemaInt | None, Field(ge=1)] = None - max_file_size_mb: Annotated[ - StrictFloat | None, - Field( - description='Maximum hosted audio file size in decimal megabytes. Agents that proxy or cache media SHOULD advertise their effective transport ceiling here.', - gt=0.0, - ), - ] = None - loudness_lufs: Annotated[ - StrictFloat | None, - Field( - description='Required integrated loudness in LUFS (typical: -16 for streaming/podcast, -23 for broadcast). Negative values.' - ), - ] = None - loudness_tolerance_db: Annotated[ - StrictFloat | None, - Field(description='Permitted deviation from loudness_lufs in dB.', ge=0.0), - ] = None - true_peak_dbfs: Annotated[ - StrictFloat | None, Field(description='Maximum true-peak level in dBFS (typical: -2).') - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered audio bytes come from. Single shared enum across canonicals (see `image.json#asset_source` for the full semantics). `publisher_host_recorded`: the publisher's host records the audio (podcast host-read pattern); buyer must use the publisher's build_creative capability. `publisher_owned_reference` is valid only when the product accepts a reference asset whose publisher-owned source resolves to playable audio. `publisher_host_recorded` remains the normal audio-specific host-read value." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded audio. When `rejected`, the buyer cannot ship an audio asset directly — they must use build_creative (or sync_creatives with brief inputs) so the seller produces the audio. Combined with `asset_source`, lets a product declare 'I produce audio from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - companion_image_required: StrictBool | None = None - companion_image_aspect_ratio: str | None = None - companion_image_max_file_size_kb: Annotated[SchemaInt | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None - - -class Components7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - component_id: Annotated[ - str, - Field( - description='Stable coordination-local component identifier. Values MUST be unique within `components[]`.', - pattern='^[a-z][a-z0-9_]*$', - ), - ] - placement_ref: Annotated[ - placement_ref_1.PlacementReference, - Field( - description="Public product placement this component supplies. MUST resolve against the containing product's `placements[]`." - ), - ] - required: StrictBool - sequence: Annotated[ - SchemaInt | None, - Field( - description='Declared presentation order for sequential-messaging coordinated buys. Components sharing a sequence value present simultaneously; absent means unordered/simultaneous (default). Sequence declares seller-rendered ordering, not buyer-controlled timing.', - ge=1, - ), - ] = None - serving_policy: Annotated[ - ServingPolicy | None, - Field( - description='Per-component serving/tracking policy. Some sellers restrict specific components (e.g., page skins) to first-party serving while allowing third-party tags on sibling components. Defaults to the product-level policy when absent.' - ), - ] = None - canvas_constraints: Annotated[ - list[canvas_constraint.CanvasConstraint] | None, - Field( - description='Artwork constraints applied to this component, including safe areas and seller-reserved regions.' - ), - ] = None - format_option_ref: Annotated[ - format_option_ref_1.FormatOptionReference | None, - Field( - description='Reference to a sibling `format_options[]` entry on the same product. It MUST NOT resolve to `custom` or `coordinated_placements`.' - ), - ] = None - format_kind: Literal['audio_hosted'] = 'audio_hosted' - params: Annotated[ - Params7, - Field( - description="Direct audio creative — buyer ships an `audio` asset (mp3/aac/wav) for asset-driven products, or ships a `script` / `creative_brief` text asset for products where the seller produces audio internally (podcast host-reads, TTS synthesis). Optional companion slots: `companion_image`, `brand_name`, `landing_page_url`. Tracking model: standard impression + completion + companion-image-click pixels via universal_macros. Distinct from `audio_daast` (DAAST tag, inherent DAAST event tracking). For host-reads and synthesized audio, the format declares `asset_source: 'publisher_host_recorded'` or `'agent_synthesized'` plus `buyer_asset_acceptance: 'rejected'`; the format's `slots` declaration enumerates which assets the buyer ships (e.g., `script` text asset for host-reads). The seller decides how to consume each asset (render verbatim vs produce audio from text) — there is no separate manifest 'inputs' map; everything the buyer ships goes in `assets`.", - title='Canonical Format: Hosted Audio', - ), - ] - - -class Params9(CanonicalFormatBase): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - Any | None, - Field( - description='Marked experimental at 3.1 GA: the canonical covers 4 meaningfully different retail-media adapter contracts (Amazon SP, Criteo SP / CitrusAd SP, Pinterest Collection, generative-per-SKU). Adopter contracts vary; buyers MUST validate per-adapter behavior before routing budget. Promotion to non-experimental gated on the #4592 adapter-contract docs work.' - ), - ] = True - v1_translatable: Annotated[ - Any | None, - Field( - description="Inherently new in v2 — retail-media catalog placements weren't expressible as v1 named formats. SDKs MUST NOT emit `FORMAT_PROJECTION_FAILED` for products using this canonical; the v1-unreachability is structural, not a registry-coverage gap." - ), - ] = False - slots: Any | None = [ - {'asset_group_id': 'source_catalog', 'required': True, 'asset_type': 'catalog'}, - {'asset_group_id': 'hero_asset', 'required': False, 'asset_type': 'image'}, - {'asset_group_id': 'landing_page_url', 'required': False, 'asset_type': 'url'}, - ] - supported_catalog_types: Annotated[ - list[catalog_type.CatalogType] | None, - Field(description='Catalog types this product accepts.'), - ] = None - min_items: Annotated[ - SchemaInt | None, Field(description='Minimum catalog item count buyer must supply.', ge=1) - ] = None - max_items: Annotated[ - SchemaInt | None, Field(description='Maximum items considered for placement.') - ] = None - fanout_mode: Annotated[ - FanoutMode | None, - Field( - description='How items map to delivery: per_item = one ad per catalog item; multi_item_in_creative = composed multi-item ad (Pinterest Collection, Snap Collection); single_item = one ad showing one item.' - ), - ] = None - required_catalog_fields: Annotated[ - list[str] | None, - Field( - description="Catalog item fields the seller requires (e.g., ['title', 'image_url', 'price'])." - ), - ] = None - supported_id_types: Annotated[ - list[SupportedIdType] | None, - Field(description='Catalog identifier types the placement renders against.'), - ] = None - hero_asset_supported: Annotated[ - StrictBool | None, - Field( - description='Whether the buyer can supply a hero/banner asset alongside the catalog (Pinterest Collection pattern).' - ), - ] = None - item_production_model: Annotated[ - ItemProductionModel | None, - Field( - description='How each per-item creative is produced. Covers the same production-source axis as `asset_source` on `image` / `video_hosted` / `audio_hosted` but with a 4-value subset — drops `publisher_host_recorded` because it\'s audio-specific and doesn\'t apply to retail-media catalog placements. SDK codegen MAY share a base enum and narrow per-canonical, or emit two distinct enums; either way the wire values overlap exactly for the 4 retained values. `buyer_uploaded` (default, current Amazon/Criteo/CitrusAd pattern): the buyer\'s catalog already contains rendered assets per item; the seller composes the placement using those assets. ("Uploaded" reads slightly off for catalog-keyed items where the buyer didn\'t actively upload bytes — the catalog ingestion already supplied them — but the semantic is the same: rendered bytes are buyer-supplied, not seller-produced.) `seller_pre_rendered_from_brief`: the buyer ships a brief plus the catalog reference; the seller renders one creative per catalog item from the brief at sync_creatives time. `seller_human_designed`: seller\'s design team produces per-item renders manually. `agent_synthesized`: AI synthesis pipeline produces per-item renders; pair with `synthesis_nondeterministic: true` for Veo/Sora-class generative video applied per item. Captures the multi-output generative pattern (1 brief × N catalog items → N rendered creatives) under the existing canonical without requiring a separate canonical. Distinct from `fanout_mode`, which describes how items map to delivery slots after rendering.' - ), - ] = ItemProductionModel.buyer_uploaded - ctv_ad_experience: Annotated[ - ctv_ad_experience_1.CtvAdExperience | None, - Field( - description='CTV experience this option serves. On `sponsored_placement`: `menu` (sponsored app/content tiles and rows whose assets derive from a catalog listing — Fire-TV-tile pattern), `squeezeback`, and `in_scene` (seller-composited brand integrations produced from the catalog/brief rather than a buyer wire creative). Asset-bundle menu heroes route to `native_in_feed`.' - ), - ] = None - - -class Components9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - component_id: Annotated[ - str, - Field( - description='Stable coordination-local component identifier. Values MUST be unique within `components[]`.', - pattern='^[a-z][a-z0-9_]*$', - ), - ] - placement_ref: Annotated[ - placement_ref_1.PlacementReference, - Field( - description="Public product placement this component supplies. MUST resolve against the containing product's `placements[]`." - ), - ] - required: StrictBool - sequence: Annotated[ - SchemaInt | None, - Field( - description='Declared presentation order for sequential-messaging coordinated buys. Components sharing a sequence value present simultaneously; absent means unordered/simultaneous (default). Sequence declares seller-rendered ordering, not buyer-controlled timing.', - ge=1, - ), - ] = None - serving_policy: Annotated[ - ServingPolicy | None, - Field( - description='Per-component serving/tracking policy. Some sellers restrict specific components (e.g., page skins) to first-party serving while allowing third-party tags on sibling components. Defaults to the product-level policy when absent.' - ), - ] = None - canvas_constraints: Annotated[ - list[canvas_constraint.CanvasConstraint] | None, - Field( - description='Artwork constraints applied to this component, including safe areas and seller-reserved regions.' - ), - ] = None - format_option_ref: Annotated[ - format_option_ref_1.FormatOptionReference | None, - Field( - description='Reference to a sibling `format_options[]` entry on the same product. It MUST NOT resolve to `custom` or `coordinated_placements`.' - ), - ] = None - format_kind: Literal['sponsored_placement'] = 'sponsored_placement' - params: Annotated[ - Params9, - Field( - description="Catalog-driven retail-media format. Slot: `source_catalog` (catalog asset — product/SKU/ASIN/GTIN catalog reference, REQUIRED), optional `hero_asset`, optional `landing_page_url`. Buyer supplies the catalog reference; surface composes per-item or multi-item rendering using its native placement template. **Composition is deterministic** — buyer can predict per-slot rendering from the catalog item structure. Tracking model: per-item impression + click + conversion (catalog-keyed via offering_id/sku/gtin macros). Covers Amazon Sponsored Products, Criteo Sponsored Products, CitrusAd Sponsored Products, Walmart Connect Sponsored Products, Pinterest Collection (catalog-driven mode).\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for catalog-driven retail-media placements ONLY. The defining feature is the `source_catalog` slot — products under this canonical compose their creative *per catalog item* using the buyer-supplied catalog feed. Without a catalog feed there is nothing to render against. Buyer agents reading `format_kind: sponsored_placement` MUST attach a catalog reference; sellers MUST require `source_catalog` in the manifest.\n\n**Not this canonical (route elsewhere):**\n- IAB in-feed native ads, content-recommendation widgets (Taboola, Outbrain, Yahoo Native, AdMob Native, in-feed sponsored cards) — use `native_in_feed` (asset-bundle composition; no catalog).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Single-image or single-video creative — use `image` or `video_hosted`.\n\nThe earlier broader framing ('any sponsored placement') was too loose for buyer-agent routing — a buyer reading `sponsored_placement` couldn't disambiguate a catalog-driven Amazon SP from an in-feed Taboola widget. As of 3.1, the canonical is narrowed to catalog-keyed retail-media; native moves to `native_in_feed`. Distinct from `responsive_creative` (algorithmic combinator from buyer pool) and `agent_placement` (text/audio AI-surface composition).", - title='Canonical Format: Sponsored Placement (retail-media catalog-driven)', - ), - ] - - -class Params10(CanonicalFormatBase): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - Any | None, - Field( - description='Stable at 3.1 GA. Shape mirrors IAB OpenRTB Native 1.2 — the renderer contract is well-established across in-feed native and content-recommendation adopters.' - ), - ] = False - v1_translatable: Annotated[ - Any | None, - Field( - description='Translates to v1 named native formats (e.g., `native_standard`, `native_content`) via the projection registry. Sellers with existing v1 named native formats SHOULD point `v1_format_ref[]` at them.' - ), - ] = True - slots: Annotated[ - Any | None, - Field( - description="Default slot shape for native_in_feed. Mirrors IAB OpenRTB Native 1.2 asset types, including the Native video asset: `video` carries a VAST document (the Native 1.2 `vasttag` field) for video-bearing native units such as CTV menu heroes with focus-triggered playback. Products MAY override (`slots_override` on the projection ref) to narrow per-slot limits (`max_chars` on title/body) or remove unused slots (a content-recommendation slot that doesn't display an icon)." - ), - ] = [ - {'asset_group_id': 'title', 'asset_type': 'text', 'required': True}, - {'asset_group_id': 'body_text', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'main_image', 'asset_type': 'image', 'required': False}, - {'asset_group_id': 'icon', 'asset_type': 'image', 'required': False}, - {'asset_group_id': 'cta', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'advertiser_name', 'asset_type': 'text', 'required': True}, - {'asset_group_id': 'sponsored_label', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': True}, - {'asset_group_id': 'display_url', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'rating', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'price', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'video', 'asset_type': 'vast', 'required': False}, - {'asset_group_id': 'impression_tracker', 'asset_type': 'pixel_tracker', 'required': False}, - {'asset_group_id': 'viewability_tracker', 'asset_type': 'pixel_tracker', 'required': False}, - {'asset_group_id': 'click_tracker', 'asset_type': 'pixel_tracker', 'required': False}, - ] - ctv_ad_experience: Annotated[ - ctv_ad_experience_1.CtvAdExperience | None, - Field( - description='CTV experience this option serves. On native_in_feed `menu` and `overlay` are valid. `menu`: smart-TV home/menu surfaces where the platform assembles buyer assets (background/main image, logo/icon, copy, optional focus-triggered video); `menu_placement` selects the tile vs headline-banner variant, and catalog-derived sponsored tiles route to `sponsored_placement` instead. `overlay`: seller-composited in-stream overlays supplied as an asset bundle (video, logo, imagery, copy, activation copy) — the contract overlay sellers that do not ingest VAST tags use; VAST-ingesting sellers publish a `video_vast` sibling option instead. The wire name stays `native_in_feed` for 3.x even though neither surface is literally in-feed.' - ), - ] = None - menu_placement: Annotated[ - MenuPlacement | None, - Field( - description='Menu surface variant, mapping to OpenRTB Native `plcmttype` 1 (tile/feed) and 3 (headline banner). Valid only with `ctv_ad_experience: "menu"`.' - ), - ] = None - focus_behavior: Annotated[ - FocusBehavior | None, - Field( - description='What happens when the viewer\'s remote focus lands on the unit. `autoplay_*` requires a `video` asset; playback method maps to AdCOM playbackmethod on OpenRTB bridges. Valid only with `ctv_ad_experience: "menu"`.' - ), - ] = None - motion_level: Annotated[ - motion_level_1.CreativeMotionLevel | None, - Field(description='Accepted motion class for the rendered unit (AdCOM attrs 21-23).'), - ] = None - activation_methods: Annotated[ - list[activation_method.CreativeActivationMethod] | None, - Field( - description='Viewer activation mechanisms this option offers (QR, deep link, send-to-device). Activations are engagement events, not impressions.' - ), - ] = None - title_max_chars: Annotated[ - SchemaInt | None, - Field( - description='Maximum character length for the title slot. IAB native typical: 25 (short) to 90 (long). Buyer agents SHOULD validate ship-time title length against this.', - ge=1, - ), - ] = None - body_text_max_chars: Annotated[ - SchemaInt | None, - Field( - description='Maximum character length for the body_text slot. IAB native typical: 90 (mainline) to 140 (extended).', - ge=1, - ), - ] = None - cta_max_chars: Annotated[ - SchemaInt | None, - Field(description='Maximum character length for the cta slot. Typical: 15–25.', ge=1), - ] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW', 'SIGN_UP', 'DOWNLOAD']). When set, narrows the cta slot to a closed enum." - ), - ] = None - main_image_sizes: Annotated[ - list[MainImageSize] | None, - Field( - description='Accepted logical (width, height) pairs for the main_image slot. Common IAB native sizes: 1200×627 (1.91:1), 1080×1080 (1:1), 1080×1350 (4:5). When the effective main_image slot declares `pixel_ratios`, intrinsic asset dimensions are the matched logical pair multiplied by the selected ratio; absence remains 1x.', - min_length=1, - ), - ] = None - icon_size: Annotated[ - IconSize | None, - Field( - description='Required logical (width, height) for the icon slot when present (typical: 80×80 or 100×100). When the effective icon slot declares `pixel_ratios`, intrinsic dimensions are multiplied by the selected ratio; absence remains 1x.' - ), - ] = None - max_image_file_size_kb: Annotated[ - SchemaInt | None, - Field(description='Maximum file size in kilobytes for main_image and icon.', ge=1), - ] = None - image_formats: Annotated[ - list[ImageFormat1] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - StrictBool | None, - Field( - description='Whether trackers, landing pages, and image URLs must be served over HTTPS.' - ), - ] = None - asset_source: Annotated[ - AssetSource5 | None, - Field( - description="Where the rendered native assets come from. `publisher_host_recorded` is omitted (audio-specific and not meaningful for native). Other values mirror the shared production-source axis used on `image` / `video_hosted`. `buyer_uploaded` (default): buyer ships pre-rendered title/image/body. `seller_pre_rendered_from_brief`: buyer ships a brief, seller renders the native bundle. `agent_synthesized`: AI synthesis pipeline produces title + image + body from a brief; pair with `synthesis_nondeterministic: true` for generative pipelines that can't guarantee in-spec output. `publisher_owned_reference`: buyer ships an existing published post reference; the seller resolves the post into the native presentation after authorization/review." - ), - ] = AssetSource5.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded native assets. When `rejected`, the buyer cannot ship pre-rendered title/image/body — they must use `build_creative`, `sync_creatives` with brief inputs, or an accepted `published_post` reference so the seller produces or resolves the native bundle.' - ), - ] = BuyerAssetAcceptance.accepted - - -class Components10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - component_id: Annotated[ - str, - Field( - description='Stable coordination-local component identifier. Values MUST be unique within `components[]`.', - pattern='^[a-z][a-z0-9_]*$', - ), - ] - placement_ref: Annotated[ - placement_ref_1.PlacementReference, - Field( - description="Public product placement this component supplies. MUST resolve against the containing product's `placements[]`." - ), - ] - required: StrictBool - sequence: Annotated[ - SchemaInt | None, - Field( - description='Declared presentation order for sequential-messaging coordinated buys. Components sharing a sequence value present simultaneously; absent means unordered/simultaneous (default). Sequence declares seller-rendered ordering, not buyer-controlled timing.', - ge=1, - ), - ] = None - serving_policy: Annotated[ - ServingPolicy | None, - Field( - description='Per-component serving/tracking policy. Some sellers restrict specific components (e.g., page skins) to first-party serving while allowing third-party tags on sibling components. Defaults to the product-level policy when absent.' - ), - ] = None - canvas_constraints: Annotated[ - list[canvas_constraint.CanvasConstraint] | None, - Field( - description='Artwork constraints applied to this component, including safe areas and seller-reserved regions.' - ), - ] = None - format_option_ref: Annotated[ - format_option_ref_1.FormatOptionReference | None, - Field( - description='Reference to a sibling `format_options[]` entry on the same product. It MUST NOT resolve to `custom` or `coordinated_placements`.' - ), - ] = None - format_kind: Literal['native_in_feed'] = 'native_in_feed' - params: Annotated[ - Params10, - Field( - description='IAB-shaped native creative for in-feed and content-recommendation surfaces. Default slots cover the primary IAB OpenRTB Native 1.2 asset types — `title` (Title Asset), `body_text` (Data Asset type 2), `main_image` (Image Asset main), `icon` (Image Asset icon), `cta` (Data Asset type 12), `advertiser_name` (Data Asset type 1), `sponsored_label` (Title-adjacent), `landing_page_url` (Link Asset), `display_url` (Data Asset type 11 — visible URL/domain, distinct from clickthrough), `rating` (Data Asset type 3 — app/product rating), `price` (Data Asset type 6 — product price), plus renderer-fired `impression_tracker` / `viewability_tracker` / `click_tracker` (`pixel_tracker`). Products MAY use `slots_override` to add other IAB Native data asset types (likes — type 4, downloads — type 5, saleprice — type 7, phone_number — type 8, address — type 9, desc2 — type 10, etc.) or to remove slots the surface doesn\'t render. The publisher\'s renderer assembles these into its own look-and-feel — feed card, content-recommendation slot, in-stream native unit. Buyer ships a single asset bundle; the surface chooses presentation.\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for:\n- IAB OpenRTB Native 1.2 in-feed native ads (publisher feeds, app feeds)\n- Content-recommendation widgets (Taboola, Outbrain, Yahoo Recommendations)\n- AdMob Native / Yahoo Native publisher slots\n- In-feed sponsored placements without catalog dependency\n\n**Not this canonical:**\n- Catalog-driven retail-media (Amazon SP, Criteo SP, CitrusAd SP) — use `sponsored_placement` (requires `source_catalog`).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Multi-card carousel — use `image_carousel`.\n- Video-first native units where the asset is a hosted video file — use `video_hosted` with `applies_to_channels: ["native"]`. (Distinct from the CTV menu profile: a menu hero remains this canonical because the platform assembles the full asset bundle and the video rides the Native 1.2 `vasttag` video asset, playing on focus rather than being the unit itself.)\n\nDistinct from `sponsored_placement` along the catalog axis: native_in_feed is asset-bundle composition; sponsored_placement is catalog-row composition. A buyer agent reading `format_kind: native_in_feed` knows to assemble title + image + body + CTA; reading `format_kind: sponsored_placement` knows to attach a catalog feed.', - title='Canonical Format: Native In-Feed', - ), - ] - - -class Params11(CanonicalFormatBase): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - Any | None, - Field( - description="Marked experimental at 3.1 GA: composition is algorithmic (the surface picks combinations and reports per-asset breakdowns), and there's no clean v1-translatable equivalent. Buyers ship asset pools rather than rendered creatives; the surface's per-impression composition cannot be predicted by `validate_input`. Adopters SHOULD validate behavior per surface (Google PMax vs Meta Advantage+ creative differ meaningfully)." - ), - ] = True - v1_translatable: Annotated[ - Any | None, - Field( - description="Inherently new in v2 — algorithmic asset-pool composition (Google PMax / Meta Advantage+ creative) wasn't expressible as v1 named formats. SDKs MUST NOT emit `FORMAT_PROJECTION_FAILED` for products using this canonical; the v1-unreachability is structural." - ), - ] = False - slots: Any | None = [ - { - 'asset_group_id': 'headlines', - 'asset_type': 'text', - 'required': True, - 'min': 3, - 'max': 15, - }, - { - 'asset_group_id': 'long_headlines', - 'asset_type': 'text', - 'required': False, - 'min': 1, - 'max': 5, - }, - { - 'asset_group_id': 'descriptions', - 'asset_type': 'text', - 'required': True, - 'min': 2, - 'max': 5, - }, - { - 'asset_group_id': 'images_landscape', - 'asset_type': 'image', - 'required': False, - 'min': 1, - 'max': 20, - }, - { - 'asset_group_id': 'images_square', - 'asset_type': 'image', - 'required': False, - 'min': 1, - 'max': 20, - }, - { - 'asset_group_id': 'images_vertical', - 'asset_type': 'image', - 'required': False, - 'min': 1, - 'max': 20, - }, - {'asset_group_id': 'video', 'asset_type': 'video', 'required': False, 'min': 0, 'max': 5}, - { - 'asset_group_id': 'logo', - 'asset_type': 'image', - 'required': True, - 'min': 1, - 'max': 5, - 'logo_slots': [ - 'logo_card_light', - 'logo_card_dark', - 'marketplace_listing', - 'ad_end_card', - ], - 'required_logo_slots': ['logo_card_light', 'logo_card_dark'], - }, - { - 'asset_group_id': 'landing_page_url', - 'asset_type': 'url', - 'required': True, - 'min': 1, - 'max': 1, - }, - ] - headlines_min: Annotated[SchemaInt | None, Field(ge=0)] = None - headlines_max: Annotated[SchemaInt | None, Field(ge=0)] = None - headline_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None - long_headlines_min: Annotated[SchemaInt | None, Field(ge=0)] = None - long_headlines_max: Annotated[SchemaInt | None, Field(ge=0)] = None - long_headline_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None - descriptions_min: Annotated[SchemaInt | None, Field(ge=0)] = None - descriptions_max: Annotated[SchemaInt | None, Field(ge=0)] = None - description_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None - images_landscape_min: Annotated[SchemaInt | None, Field(ge=0)] = None - images_landscape_max: Annotated[SchemaInt | None, Field(ge=0)] = None - images_landscape_aspect_ratio: str | None = None - images_square_min: Annotated[SchemaInt | None, Field(ge=0)] = None - images_square_max: Annotated[SchemaInt | None, Field(ge=0)] = None - images_vertical_min: Annotated[SchemaInt | None, Field(ge=0)] = None - images_vertical_max: Annotated[SchemaInt | None, Field(ge=0)] = None - videos_min: Annotated[SchemaInt | None, Field(ge=0)] = None - videos_max: Annotated[SchemaInt | None, Field(ge=0)] = None - video_min_duration_ms: Annotated[SchemaInt | None, Field(ge=1)] = None - video_max_duration_ms: Annotated[SchemaInt | None, Field(ge=1)] = None - logo_min: Annotated[SchemaInt | None, Field(ge=0)] = None - logo_max: Annotated[SchemaInt | None, Field(ge=0)] = None - logo_aspect_ratios: list[str] | None = None - business_name_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None - asset_image_max_file_size_kb: Annotated[SchemaInt | None, Field(ge=1)] = None - supports_catalog_input: Annotated[ - StrictBool | None, - Field( - description='Whether the product can additionally consume a catalog reference (e.g., PMax with product feed).' - ), - ] = None - - -class Components11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - component_id: Annotated[ - str, - Field( - description='Stable coordination-local component identifier. Values MUST be unique within `components[]`.', - pattern='^[a-z][a-z0-9_]*$', - ), - ] - placement_ref: Annotated[ - placement_ref_1.PlacementReference, - Field( - description="Public product placement this component supplies. MUST resolve against the containing product's `placements[]`." - ), - ] - required: StrictBool - sequence: Annotated[ - SchemaInt | None, - Field( - description='Declared presentation order for sequential-messaging coordinated buys. Components sharing a sequence value present simultaneously; absent means unordered/simultaneous (default). Sequence declares seller-rendered ordering, not buyer-controlled timing.', - ge=1, - ), - ] = None - serving_policy: Annotated[ - ServingPolicy | None, - Field( - description='Per-component serving/tracking policy. Some sellers restrict specific components (e.g., page skins) to first-party serving while allowing third-party tags on sibling components. Defaults to the product-level policy when absent.' - ), - ] = None - canvas_constraints: Annotated[ - list[canvas_constraint.CanvasConstraint] | None, - Field( - description='Artwork constraints applied to this component, including safe areas and seller-reserved regions.' - ), - ] = None - format_option_ref: Annotated[ - format_option_ref_1.FormatOptionReference | None, - Field( - description='Reference to a sibling `format_options[]` entry on the same product. It MUST NOT resolve to `custom` or `coordinated_placements`.' - ), - ] = None - format_kind: Literal['responsive_creative'] = 'responsive_creative' - params: Annotated[ - Params11, - Field( - description='Buyer supplies a pool of typed assets (multiple headlines, descriptions, images, videos, logos); the surface algorithmically composes combinations per placement. **Composition is algorithmic** — surface picks combinations and reports per-asset performance breakdowns. Covers Google Responsive Display Ads (RDA), Responsive Search Ads (RSA), Performance Max (PMax), Demand Gen, and Meta Advantage+ creative. Industry term: "Responsive" (Google) / "Advantage+ creative" (Meta) / "Dynamic Creative" (older Meta term). Distinct from `sponsored_placement` (catalog-driven, deterministic) and `agent_placement` (AI-surface composition). The structured `slots` field below enumerates expected canonical asset_group_id slots; per-slot count/length narrowing lives in flat parameters (`headlines_min`, `headline_max_chars`, etc.).', - title='Canonical Format: Responsive Creative', - ), - ] - - -class Params12(CanonicalFormatBase): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - Any | None, - Field( - description="Marked experimental at 3.1 GA: the canonical's tracking model (mention-level impression + attribution, postback shape, cross-surface dedup) is intentionally underspecified for 3.1. Adopters claiming `agent_placement` ship private tracking integrations; buyer agents MUST treat attribution as adapter-defined until the 3.2 tracking-macro spec lands. Promotion to non-experimental gated on the 3.2 tracking-contract spec." - ), - ] = True - v1_translatable: Annotated[ - Any | None, - Field( - description="Inherently new in v2 — AI-surface sponsored mentions weren't expressible as v1 named formats. SDKs MUST NOT emit `FORMAT_PROJECTION_FAILED` for products using this canonical; the v1-unreachability is structural." - ), - ] = False - slots: Annotated[ - Any | None, - Field( - description="agent_placement has minimal buyer-shipped slots — the surface composes the rendered output from brand context (resolved via the manifest's top-level `brand` BrandRef) plus optional offering_ref and landing_page_url assets. None of these assets are rendered verbatim by the buyer; the agent chooses how to use them." - ), - ] = [ - {'asset_group_id': 'offering_ref', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, - ] - output_modality: Annotated[ - OutputModality | None, - Field( - description='How the surface presents the mention. `text` = inline text (chat, search snippet). `audio` = TTS-synthesized voice. `card` = structured card with optional image + text.' - ), - ] = None - max_mention_length_chars: Annotated[ - SchemaInt | None, - Field( - description='For text output: maximum length of the surface-composed mention text.', - ge=1, - ), - ] = None - max_mention_duration_ms: Annotated[ - SchemaInt | None, - Field( - description='For audio output: maximum duration of the spoken mention in milliseconds.', - ge=1, - ), - ] = None - supports_offering_reference: Annotated[ - StrictBool | None, - Field( - description='Whether the product accepts an offering reference (specific product/service to promote within the mention) in addition to brand context.' - ), - ] = None - supports_landing_page_url: Annotated[ - StrictBool | None, - Field( - description='Whether the surface attaches a landing page URL to the mention (citation, learn-more link).' - ), - ] = None - tone_constraints: Annotated[ - list[str] | None, - Field( - description="**Advisory only.** Buyer-declared brand-voice preferences the surface SHOULD honor (e.g., ['formal', 'no_superlatives']). LLM/agentic surfaces have no protocol-level mechanism to verify enforcement — adopters that need hard guarantees should rely on brand.json voice declarations and post-mention review rather than this field. Future revisions may tie this to a structured tone vocabulary; for now treat as free-text guidance." - ), - ] = None - disclosure_required: Annotated[ - StrictBool | None, - Field( - description='Whether the surface must include an explicit sponsorship disclosure label.' - ), - ] = None - - -class Components12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - component_id: Annotated[ - str, - Field( - description='Stable coordination-local component identifier. Values MUST be unique within `components[]`.', - pattern='^[a-z][a-z0-9_]*$', - ), - ] - placement_ref: Annotated[ - placement_ref_1.PlacementReference, - Field( - description="Public product placement this component supplies. MUST resolve against the containing product's `placements[]`." - ), - ] - required: StrictBool - sequence: Annotated[ - SchemaInt | None, - Field( - description='Declared presentation order for sequential-messaging coordinated buys. Components sharing a sequence value present simultaneously; absent means unordered/simultaneous (default). Sequence declares seller-rendered ordering, not buyer-controlled timing.', - ge=1, - ), - ] = None - serving_policy: Annotated[ - ServingPolicy | None, - Field( - description='Per-component serving/tracking policy. Some sellers restrict specific components (e.g., page skins) to first-party serving while allowing third-party tags on sibling components. Defaults to the product-level policy when absent.' - ), - ] = None - canvas_constraints: Annotated[ - list[canvas_constraint.CanvasConstraint] | None, - Field( - description='Artwork constraints applied to this component, including safe areas and seller-reserved regions.' - ), - ] = None - format_option_ref: Annotated[ - format_option_ref_1.FormatOptionReference | None, - Field( - description='Reference to a sibling `format_options[]` entry on the same product. It MUST NOT resolve to `custom` or `coordinated_placements`.' - ), - ] = None - format_kind: Literal['agent_placement'] = 'agent_placement' - params: Annotated[ - Params12, - Field( - description="**3.2-track canonical.** The structural shape (algorithmic composition + brand-context input + optional offering/landing_page) is captured here so adopters can declare against it in 3.1 catalogs, but the **mention-level tracking contract is intentionally underspecified for 3.1**: no normative macro vocabulary, no postback shape, no cross-surface dedup model. Adopters claiming `agent_placement` in 3.1 ship private tracking integrations and SHOULD leave `experimental: true` on the product declaration that references this canonical; buyer agents MUST treat agent_placement attribution as adapter-defined until the 3.2 tracking-macro spec lands. The canonical promotes to a normatively-buyer-callable surface in 3.2 (or later) once the tracking contract is specified.\n\nSponsored placement integrated into an AI-surface's response to a user. Buyer supplies a `BrandRef` (resolving brand.json for context), an optional `offering_ref` to focus the mention on a specific offering, and an optional `landing_page_url` the surface MAY attach as a citation. The surface (LLM, voice assistant, sponsored-search ranker) composes a natural-language mention, sponsored card, or audio snippet within its response to a user query. **Composition is algorithmic** — the agent chooses phrasing and presentation. Output asset_type varies by surface: `text` for chat UIs and sponsored search snippets; `audio` (synthesized) for voice assistants; `card` for structured AI-surface result cards. Tracking model: mention-level impression + attribution events; per-mention id keys back to brand and offering — but see the 3.2-track note above; the wire shape of these events is not yet specified. Distinct from `si_chat` (which is the user-converses-with-brand's-agent pattern — brand owns the conversational surface) and from `sponsored_placement` (retail-media catalog-driven). Parallels `sponsored_placement` structurally: both are surface-composed placements; agent_placement is for AI/agentic surfaces, sponsored_placement is for retail media.", - title='Canonical Format: Agent Placement (AI-surface sponsored placement)', - ), - ] - - -class Params13(CanonicalFormatBase): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - Any | None, - Field( - description='Experimental in AdCP 3.2 while the creative working group gathers implementation evidence across premium web and mobile/app sellers.' - ), - ] = True - v1_translatable: Annotated[ - Any | None, - Field( - description='No v1 named-format equivalent can express multiple seller-rendered states and their breakpoint bindings.' - ), - ] = False - since_version: Any | None = '3.2' - composition_model: Any | None = 'deterministic' - supply_mode: Annotated[ - SupplyMode | None, - Field( - description='Which end of the template contract the buyer feeds. `components`: buyer supplies component slots; seller renders states (no `state_canvases`/`layered_source` assets allowed). `rendered_canvases`: buyer supplies exactly one `state_canvases` image per declared state × breakpoint pair. `layered_source`: buyer ships design source (+ optional `font_files`); seller production derives states (`production_window_business_days` applies) — transitional for sellers without executable templates.' - ), - ] = SupplyMode.components - slots: Annotated[ - Any | None, - Field( - description='Default manifest slots; which are consumed depends on `supply_mode`. `state_canvases` images MUST carry `state_id` and `breakpoint_id`, and `state_click_urls` entries MUST carry `state_id` (semantic validators resolve the bindings). Component images SHOULD carry `focal_point` for deterministic seller cropping. `landing_page_url` is the default destination (see `clickthrough`). `font_files` MUST contain only buyer-licensed fonts; publisher-proprietary fonts never travel in manifests. Only image, video, text, url, zip, and pixel_tracker slot asset types are accepted — executable types (javascript, html, css, webhook) are rejected even via `slots` overrides.' - ), - ] = [ - {'asset_group_id': 'state_canvases', 'asset_type': 'image', 'required': False, 'min': 1}, - {'asset_group_id': 'logo', 'asset_type': 'image', 'required': False}, - {'asset_group_id': 'imagery', 'asset_type': 'image', 'required': False, 'min': 0}, - {'asset_group_id': 'headline', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'messaging', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'cta', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'legal_text', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'video_main', 'asset_type': 'video', 'required': False}, - {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': True}, - {'asset_group_id': 'state_click_urls', 'asset_type': 'url', 'required': False, 'min': 0}, - {'asset_group_id': 'layered_source', 'asset_type': 'zip', 'required': False}, - {'asset_group_id': 'font_files', 'asset_type': 'zip', 'required': False}, - ] - states: Annotated[ - list[State], - Field( - description='Finite visual states known at buy time; state and breakpoint IDs form the canvas-key matrix. Runtime causes live in `transitions[]`. A single-state unit (topscroll, interscroller, skin) declares one state, no transitions, and typically a `reveal` mechanic.', - min_length=1, - ), - ] - initial_state_id: Annotated[ - str, - Field( - description='State rendered when the unit first becomes visible. MUST resolve to `states[].state_id`; for a single-state unit it MUST equal the sole state.', - pattern='^[a-z][a-z0-9_]*$', - ), - ] - reveal: Annotated[ - Reveal | None, - Field( - description='How the unit enters view, distinct from state changes. `clip_window`: canvas fixed and progressively exposed through a scrolling window (interscroller, topscroll). `scroll_parallax`: canvas moves at a different rate than content. Reveal is presentation of one canvas, not a transition; do not fabricate a second state to express it.' - ), - ] = Reveal.none - transitions: Annotated[ - list[Transitions | Transitions1 | Transitions2 | Transitions3 | Transitions4 | Transitions5] - | None, - Field( - description='Bounded seller-rendered transitions between declared visual states. Required when `states` has more than one entry; MUST be omitted for single-state units. Every non-initial state MUST be reachable from `initial_state_id`. Dismissal is terminal unit behavior declared by `user_controls.dismissible`, not a hidden visual state.', - min_length=1, - ), - ] = None - clickthrough: Annotated[ - Clickthrough | None, - Field( - description='Destination policy. `required` (default): manifest MUST supply `landing_page_url`. `optional`: click-optional units (in-feed brand units) may omit it. `none`: unit is non-clickable; manifests MUST NOT supply `landing_page_url` or `state_click_urls`. Per-state overrides via `state_click_urls` entries carrying `state_id`; `landing_page_url` is the fallback for unlisted states.' - ), - ] = Clickthrough.required - user_controls: Annotated[ - UserControls, - Field( - description="When any state anchors as `overlay` or `fullscreen_overlay`, either `dismissible` MUST be true or that state's `close_affordance` MUST be true (dismissibility floor; semantic validators enforce)." - ), - ] - canvas_constraints: Annotated[ - list[canvas_constraint.CanvasConstraint] | None, - Field( - description='Rectangular areas constraining buyer artwork. Omitted state/breakpoint selectors apply the constraint to every canvas. For fluid or range-sized breakpoints, use percent-unit regions.' - ), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='Accepted embedded-video duration [min, max]. `duration_ms_exact` takes precedence when both are present.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[SchemaInt | None, Field(ge=1)] = None - aspect_ratio: Annotated[ - str | None, - Field( - description='Embedded-video aspect ratio.', - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - containers: list[Container] | None = None - video_playback: VideoPlayback | None = None - max_initial_load_kb: Annotated[SchemaInt | None, Field(ge=1)] = None - max_subload_kb: Annotated[ - SchemaInt | None, - Field( - description='Ceiling on assets loaded after the window load event (IAB LEAN subload). Pairs with `max_initial_load_kb` to mirror the New Ad Portfolio initial/subload weight pair.', - ge=1, - ), - ] = None - polite_load: Annotated[ - StrictBool | None, - Field( - description="When true, non-initial assets load only after the host page's window load event (IAB LEAN subload boundary)." - ), - ] = None - - -class Components13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - component_id: Annotated[ - str, - Field( - description='Stable coordination-local component identifier. Values MUST be unique within `components[]`.', - pattern='^[a-z][a-z0-9_]*$', - ), - ] - placement_ref: Annotated[ - placement_ref_1.PlacementReference, - Field( - description="Public product placement this component supplies. MUST resolve against the containing product's `placements[]`." - ), - ] - required: StrictBool - sequence: Annotated[ - SchemaInt | None, - Field( - description='Declared presentation order for sequential-messaging coordinated buys. Components sharing a sequence value present simultaneously; absent means unordered/simultaneous (default). Sequence declares seller-rendered ordering, not buyer-controlled timing.', - ge=1, - ), - ] = None - serving_policy: Annotated[ - ServingPolicy | None, - Field( - description='Per-component serving/tracking policy. Some sellers restrict specific components (e.g., page skins) to first-party serving while allowing third-party tags on sibling components. Defaults to the product-level policy when absent.' - ), - ] = None - canvas_constraints: Annotated[ - list[canvas_constraint.CanvasConstraint] | None, - Field( - description='Artwork constraints applied to this component, including safe areas and seller-reserved regions.' - ), - ] = None - format_option_ref: Annotated[ - format_option_ref_1.FormatOptionReference | None, - Field( - description='Reference to a sibling `format_options[]` entry on the same product. It MUST NOT resolve to `custom` or `coordinated_placements`.' - ), - ] = None - format_kind: Literal['seller_rendered_stateful_display'] = 'seller_rendered_stateful_display' - params: Annotated[ - Params13, - Field( - description='Seller-rendered display unit whose declaration is an executable template contract: buyer-known visual states, explicit transitions, breakpoint canvases, and per-state slot bindings. The seller owns the runtime; `supply_mode` declares which end the buyer feeds. For machine-rendered `components` and `rendered_canvases` supply, sellers MUST support `preview_creative` returning every state × breakpoint from a candidate manifest. `layered_source` instead follows the asynchronous seller-production preview path after the declared production window. `composition_model: deterministic` describes serving the finished states, not instant derivation from layered source. Buyer-executable HTML/MRAID is `html5`, a buyer-delivered tag is `display_tag`, arbitrary games/hotspots/scripts remain `custom`, and per-impression algorithmic assembly is `responsive_creative`.', - title='Canonical Format: Seller-Rendered Stateful Display', - ), - ] - - -class Params8(CanonicalFormatBase): - model_config = ConfigDict( - extra='allow', - ) - slots: Annotated[ - Any | None, - Field( - description="Default slots for audio_daast canonical. Buyer ships a DAAST tag (URL or inline XML, 1.0 or 1.1) plus an optional clickthrough URL. Tracking events are inherent to DAAST and don't require explicit slots." - ), - ] = [ - {'asset_group_id': 'daast_tag', 'asset_type': 'daast', 'required': True}, - {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, - ] - daast_version: Annotated[ - daast_version_1.DaastVersion | None, - Field( - deprecated=True, - description='Deprecated one-element alias for `daast_versions`. Producers use either the singular legacy alias or the plural 3.2 field, never both.', - ), - ] = None - daast_versions: Annotated[ - daast_tracker_constraints.DaastVersions | None, - Field( - description="Accepted DAAST versions for this format option. A tracker execution selector's daast_versions must be a nonempty subset of this set." - ), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - SchemaInt | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - linear_required: StrictBool | None = None - max_wrapper_depth: Annotated[SchemaInt | None, Field(ge=0)] = None - ssl_required: StrictBool | None = None - companion_image_required: StrictBool | None = None - - -class Components8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - component_id: Annotated[ - str, - Field( - description='Stable coordination-local component identifier. Values MUST be unique within `components[]`.', - pattern='^[a-z][a-z0-9_]*$', - ), - ] - placement_ref: Annotated[ - placement_ref_1.PlacementReference, - Field( - description="Public product placement this component supplies. MUST resolve against the containing product's `placements[]`." - ), - ] - required: StrictBool - sequence: Annotated[ - SchemaInt | None, - Field( - description='Declared presentation order for sequential-messaging coordinated buys. Components sharing a sequence value present simultaneously; absent means unordered/simultaneous (default). Sequence declares seller-rendered ordering, not buyer-controlled timing.', - ge=1, - ), - ] = None - serving_policy: Annotated[ - ServingPolicy | None, - Field( - description='Per-component serving/tracking policy. Some sellers restrict specific components (e.g., page skins) to first-party serving while allowing third-party tags on sibling components. Defaults to the product-level policy when absent.' - ), - ] = None - canvas_constraints: Annotated[ - list[canvas_constraint.CanvasConstraint] | None, - Field( - description='Artwork constraints applied to this component, including safe areas and seller-reserved regions.' - ), - ] = None - format_option_ref: Annotated[ - format_option_ref_1.FormatOptionReference | None, - Field( - description='Reference to a sibling `format_options[]` entry on the same product. It MUST NOT resolve to `custom` or `coordinated_placements`.' - ), - ] = None - format_kind: Literal['audio_daast'] = 'audio_daast' - params: Annotated[ - Params8, - Field( - description='DAAST-tag-delivered audio creative (audio analog of VAST). Slot: `daast_tag` (daast asset, URL or inline XML). Tracking model: DAAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `clickTracking`, `error`. Distinct from `audio_hosted` (direct file with external tracking).', - title='Canonical Format: DAAST Audio', - ), - ] - - -class CanonicalFormatCoordinatedPlacements(CanonicalFormatBase): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - Any | None, - Field( - description='Experimental in AdCP 3.2 while the creative working group gathers implementation evidence for atomic cross-placement composition.' - ), - ] = True - v1_translatable: Annotated[ - Any | None, - Field( - description='No v1 named-format equivalent can express a coordinated multi-placement buy.' - ), - ] = False - since_version: Any | None = '3.2' - composition_model: Any | None = 'deterministic' - components: Annotated[ - list[ - Components - | Components1 - | Components2 - | Components3 - | Components4 - | Components5 - | Components6 - | Components7 - | Components8 - | Components9 - | Components10 - | Components11 - | Components12 - | Components13 - ], - Field(min_length=2), - ] - shared_slots: Annotated[ - list[SharedSlot] | None, - Field( - description='Manifest slots supplied once and consumed by one or more coordinated components.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/formats/canonical/display_tag.py b/src/adcp/types/generated_poc/formats/canonical/display_tag.py deleted file mode 100644 index cd4079799..000000000 --- a/src/adcp/types/generated_poc/formats/canonical/display_tag.py +++ /dev/null @@ -1,134 +0,0 @@ -# generated by datamodel-codegen: -# filename: formats/canonical/display_tag.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictBool - -from ._base import CanonicalFormatBase - - -class Size(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - width: Annotated[SchemaInt, Field(ge=1)] - height: Annotated[SchemaInt, Field(ge=1)] - - -class SupportedTagType(StrEnum): - iframe = 'iframe' - javascript = 'javascript' - field_1x1_redirect = '1x1_redirect' - - -class SupportedDeliveryType(StrEnum): - tag_url = 'tag_url' - inline_markup = 'inline_markup' - paired_redirect = 'paired_redirect' - - -class CanonicalFormatDisplayTag(CanonicalFormatBase): - model_config = ConfigDict( - extra='allow', - ) - slots: Annotated[ - Any | None, - Field( - description='Backward-compatible URL-delivery slots. `tag_url` MUST use `url_type: ad_request`. A format option accepting `inline_markup` or `paired_redirect` overrides this list with a required slot whose `asset_type` is `display_tag`; the display-tag asset keeps paired redirect URLs atomic.' - ), - ] = [ - {'asset_group_id': 'tag_url', 'asset_type': 'url', 'required': True}, - {'asset_group_id': 'backup_image', 'asset_type': 'image', 'required': False}, - ] - width: Annotated[ - SchemaInt | None, - Field( - description='Required tag rendering width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - SchemaInt | None, - Field( - description='Required tag rendering height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description="List of accepted (width, height) pairs for a multi-size flexible slot. The buyer's third-party tag must render at one of the listed sizes; the seller picks which size to request at impression time. Mutually exclusive with `(width, height)` and with responsive ranges.", - min_length=1, - ), - ] = None - min_width: Annotated[ - SchemaInt | None, - Field( - description='Minimum accepted width for responsive third-party tags. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - SchemaInt | None, - Field( - description='Maximum accepted width for responsive third-party tags. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - SchemaInt | None, - Field( - description='Minimum accepted height for responsive third-party tags. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - SchemaInt | None, - Field( - description='Maximum accepted height for responsive third-party tags. Pair with `min_height`.', - ge=1, - ), - ] = None - supported_tag_types: Annotated[ - list[SupportedTagType] | None, - Field( - deprecated=True, - description='Deprecated ambiguous mechanism list. Use `supported_delivery_types`; markup subtype lives on the `display_tag` asset.', - ), - ] = None - supported_delivery_types: Annotated[ - list[SupportedDeliveryType] | None, - Field( - description='Closed set of delivery types this format option can traffic. `paired_redirect` means one atomic ad-request/click-through pair (Internal Redirect semantics), never independently matchable URL slots.', - min_length=1, - ), - ] = None - ssl_required: Annotated[ - StrictBool | None, Field(description='Whether the tag URL must be HTTPS.') - ] = None - max_redirect_depth: Annotated[ - SchemaInt | None, Field(description='Maximum redirect chain depth permitted.', ge=0) - ] = None - max_response_time_ms: Annotated[ - SchemaInt | None, - Field(description='Maximum tag-server response time in milliseconds.', ge=1), - ] = None - backup_image_required: Annotated[ - StrictBool | None, - Field( - description='Whether a backup image must accompany the tag for environments that cannot render the third-party tag.' - ), - ] = None - backup_image_max_size_kb: Annotated[SchemaInt | None, Field(ge=1)] = None - om_sdk_required: Annotated[ - StrictBool | None, - Field( - description="Whether the buyer's tag must integrate IAB Open Measurement SDK for viewability." - ), - ] = None diff --git a/src/adcp/types/generated_poc/formats/canonical/html5.py b/src/adcp/types/generated_poc/formats/canonical/html5.py deleted file mode 100644 index 055fb170c..000000000 --- a/src/adcp/types/generated_poc/formats/canonical/html5.py +++ /dev/null @@ -1,152 +0,0 @@ -# generated by datamodel-codegen: -# filename: formats/canonical/html5.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictBool - -from ._base import CanonicalFormatBase - - -class Size(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - width: Annotated[SchemaInt, Field(ge=1)] - height: Annotated[SchemaInt, Field(ge=1)] - - -class MraidVersion(StrEnum): - field_2_0 = '2.0' - field_3_0 = '3.0' - - -class ClicktagMacro(StrEnum): - clickTag = 'clickTag' - clickTAG = 'clickTAG' - - -class CanonicalFormatHtml5Banner(CanonicalFormatBase): - model_config = ConfigDict( - extra='allow', - ) - slots: Annotated[ - Any | None, - Field( - description="Default slots for html5 canonical. Buyer ships a zip bundle plus optional backup image (required when `backup_image_required: true`) and clickthrough URL. The zip's entry point is typically `index.html`; click handling uses the `clickTag` (or `clickTAG`) macro substituted by the seller at serve time." - ), - ] = [ - {'asset_group_id': 'html5_bundle', 'asset_type': 'zip', 'required': True}, - {'asset_group_id': 'backup_image', 'asset_type': 'image', 'required': False}, - {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, - ] - width: Annotated[ - SchemaInt | None, - Field( - description='Required banner width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - SchemaInt | None, - Field( - description='Required banner height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot (publisher banner that accepts 300×250 OR 728×90 OR 970×250). Mirrors OpenRTB `banner.format[]`. Mutually exclusive with `(width, height)` and with responsive ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - SchemaInt | None, - Field( - description='Minimum accepted width for responsive HTML5 banners that adapt within a range. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - SchemaInt | None, - Field( - description='Maximum accepted width for responsive HTML5 banners. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - SchemaInt | None, - Field( - description='Minimum accepted height for responsive HTML5 banners. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - SchemaInt | None, - Field( - description='Maximum accepted height for responsive HTML5 banners. Pair with `min_height`.', - ge=1, - ), - ] = None - max_initial_load_kb: Annotated[ - SchemaInt | None, - Field( - description='Maximum initial-load file size (zip + above-the-fold assets) in kilobytes. IAB display standards: 200 KB for fixed sizes, 100 KB for mobile.', - ge=1, - ), - ] = None - max_polite_load_kb: Annotated[ - SchemaInt | None, - Field( - description='Maximum polite-load file size after host-initiated subload, in kilobytes. IAB display standards: 500 KB for fixed sizes.', - ge=1, - ), - ] = None - host_initiated_subload: Annotated[ - StrictBool | None, - Field( - description='Whether the host page must initiate the polite-load phase. IAB-compliant banners require true.' - ), - ] = None - max_animation_duration_ms: Annotated[ - SchemaInt | None, - Field( - description='Maximum total animation duration in milliseconds. IAB standard: 30000 (30 seconds).', - ge=0, - ), - ] = None - max_cpu_load_percent: Annotated[ - SchemaInt | None, - Field(description='Maximum CPU load percentage during render.', ge=1, le=100), - ] = None - mraid_required: Annotated[ - StrictBool | None, - Field(description='Whether MRAID compatibility is required (mobile in-app).'), - ] = None - mraid_version: Annotated[ - MraidVersion | None, - Field(description='Required MRAID version when mraid_required is true.'), - ] = None - om_sdk_required: Annotated[ - StrictBool | None, - Field(description='Whether IAB Open Measurement SDK integration is required.'), - ] = None - clicktag_macro: Annotated[ - ClicktagMacro | None, Field(description='Name of the click-tag macro the bundle must use.') - ] = None - backup_image_required: Annotated[ - StrictBool | None, - Field( - description='Whether a backup image must accompany the zip for non-HTML5 environments.' - ), - ] = None - backup_image_max_size_kb: Annotated[ - SchemaInt | None, Field(description='Maximum backup image file size in kilobytes.', ge=1) - ] = None - ssl_required: StrictBool | None = None diff --git a/src/adcp/types/generated_poc/formats/canonical/image.py b/src/adcp/types/generated_poc/formats/canonical/image.py deleted file mode 100644 index dc9cf22f0..000000000 --- a/src/adcp/types/generated_poc/formats/canonical/image.py +++ /dev/null @@ -1,182 +0,0 @@ -# generated by datamodel-codegen: -# filename: formats/canonical/image.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarFloat -from pydantic import ConfigDict, Field, StrictBool - -from ...enums import activation_method -from ...enums import ctv_ad_experience as ctv_ad_experience_1 -from ._base import CanonicalFormatBase - - -class MotionLevel(StrEnum): - static = 'static' - limited_motion = 'limited_motion' - - -class Size(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - width: Annotated[SchemaInt, Field(ge=1)] - height: Annotated[SchemaInt, Field(ge=1)] - - -class PixelRatio(ScalarFloat): - __slots__ = () - _constraints = {'gt': 0.0} - - -class ImageFormat(StrEnum): - jpg = 'jpg' - jpeg = 'jpeg' - png = 'png' - gif = 'gif' - webp = 'webp' - svg = 'svg' - - -class AssetSource(StrEnum): - buyer_uploaded = 'buyer_uploaded' - publisher_host_recorded = 'publisher_host_recorded' - seller_pre_rendered_from_brief = 'seller_pre_rendered_from_brief' - seller_human_designed = 'seller_human_designed' - agent_synthesized = 'agent_synthesized' - publisher_owned_reference = 'publisher_owned_reference' - - -class BuyerAssetAcceptance(StrEnum): - accepted = 'accepted' - rejected = 'rejected' - - -class CanonicalFormatImage(CanonicalFormatBase): - model_config = ConfigDict( - extra='allow', - ) - motion_level: MotionLevel | None = None - slots: Annotated[ - Any | None, - Field( - description="Default slots for image canonical. Buyer ships an image asset (file or hosted URL) plus optional headline, body text, primary text (long-form caption), CTA (typically constrained to an enum via `cta_values`), and clickthrough URL. Products MAY override the default — make `headline` required, narrow `cta` to a value enum, or remove slots the surface doesn't consume." - ), - ] = [ - {'asset_group_id': 'image_main', 'asset_type': 'image', 'required': True}, - {'asset_group_id': 'headline', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'body_text', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'primary_text', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'cta', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, - ] - width: Annotated[ - SchemaInt | None, - Field( - description="Logical render width in pixels — use for fixed-size slots (e.g., a 300×250 IAB MREC). When `pixel_ratios` is absent, the required image asset width is the same value (1x). When `pixel_ratios` is present, an accepted asset's intrinsic width is `width × pixel_ratio`. For multi-size flexible slots, use `sizes[]`; for responsive slots, use the min/max fields. The three size modes are mutually exclusive.", - ge=1, - ), - ] = None - height: Annotated[ - SchemaInt | None, - Field( - description='Logical render height in pixels. Intrinsic asset height is `height × pixel_ratio`, where the ratio defaults to 1 when `pixel_ratios` is absent. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted logical (width, height) render pairs for a multi-size flexible slot. The buyer ships an asset matching one logical size multiplied by one accepted `pixel_ratios` entry (or by 1 when `pixel_ratios` is absent). SDKs MUST treat size and density as separate axes: a 600×500 intrinsic asset at 2x satisfies a logical 300×250 size; it does not create a logical 600×500 placement. Mirrors OpenRTB `banner.format[]` semantics. Mutually exclusive with `(width, height)` and with responsive ranges.', - min_length=1, - ), - ] = None - pixel_ratios: Annotated[ - list[PixelRatio] | None, - Field( - description='Accepted intrinsic-pixel densities for image assets, expressed as intrinsic pixels per logical render pixel (for example `[1, 2]` accepts both standard and Retina renditions). Absence means `[1]` for backward compatibility. This is an acceptance set, not a requirement to submit every rendition: one `image_main` asset satisfying any listed ratio is sufficient unless the effective `image_main` slot declares `required_pixel_ratios`. SDKs determine the effective ratio from `asset.pixel_ratio` when supplied, otherwise infer it from intrinsic asset dimensions divided by the matched logical size. Width and height ratios MUST agree.', - min_length=1, - ), - ] = None - min_width: Annotated[ - SchemaInt | None, - Field( - description="Minimum accepted width in pixels for responsive slots that adapt within a range (e.g., 'any width from 300 to 970'). Use with `max_width` (and optionally `min_height`/`max_height`). Mutually exclusive with `(width, height)` and `sizes[]`.", - ge=1, - ), - ] = None - max_width: Annotated[ - SchemaInt | None, - Field( - description='Maximum accepted width in pixels for responsive slots. Pair with `min_width`. See `min_width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - min_height: Annotated[ - SchemaInt | None, - Field( - description='Minimum accepted height in pixels for responsive slots. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - SchemaInt | None, - Field( - description='Maximum accepted height in pixels for responsive slots. Pair with `min_height`.', - ge=1, - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description="Optional aspect ratio constraint (e.g., '1.91:1', '1:1'). When provided alongside `width`/`height`, must agree. When used with `sizes[]` or responsive ranges, narrows accepted entries to those matching the aspect ratio.", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - max_file_size_kb: Annotated[ - SchemaInt | None, Field(description='Maximum file size in kilobytes.', ge=1) - ] = None - image_formats: Annotated[ - list[ImageFormat] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - StrictBool | None, - Field(description='Whether the image and its trackers must be served over HTTPS.'), - ] = None - headline_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None - body_text_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW'])." - ), - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered asset bytes come from. Single shared enum across all canonicals (`image`, `video_hosted`, `audio_hosted` — replaces the earlier per-canonical `image_source` / `video_source` / `audio_source` fields). `buyer_uploaded` (default): buyer ships a pre-rendered asset. `publisher_host_recorded`: publisher's host records the asset (audio-specific; podcast host-read pattern). `seller_pre_rendered_from_brief`: buyer ships a brief plus structured copy; seller renders ONE asset at sync_creatives or build_creative time (generative-DSP pattern). `seller_human_designed`: seller's design team renders manually from a brief. `agent_synthesized`: AI synthesis pipeline; pair with `synthesis_nondeterministic: true` when the platform cannot guarantee in-spec output (Veo/Sora/Imagen-class). `publisher_owned_reference`: buyer references an existing post or publisher-owned object via a `published_post` slot; the seller resolves and serves the referenced content after authorization/review rather than receiving uploaded bytes.\n\nNot every value is meaningful on every canonical — `publisher_host_recorded` is audio-specific; on `image` or `video_hosted` it has no defined behavior. `publisher_owned_reference` is meaningful only when the product's `slots` declaration accepts a reference asset such as `published_post`. Adopters MUST select a value appropriate to the canonical's asset type. The `slots` declaration is the binding contract for what the buyer ships; `asset_source` is informational and lets buyers understand the production model when picking products." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded assets. When `rejected`, the buyer cannot ship pre-rendered bytes directly — they must use build_creative (or sync_creatives with brief inputs or reference assets) so the seller produces or resolves the asset. Combined with `asset_source`, lets a product declare 'I produce assets from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`) or 'I accept existing post references, not uploaded bytes' (asset_source=`publisher_owned_reference`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - ctv_ad_experience: Annotated[ - ctv_ad_experience_1.CtvAdExperience | None, - Field( - description='CTV experience this option serves. On `image` only `pause` and `screensaver` are valid — the image-plus-copy contract the major pause-ad sellers ingest (seller composites the frame; typical canvas 1920×1080 or a transparent-region overlay). Other experiences route per the matrix in docs/creative/ctv-experiences.mdx.' - ), - ] = None - activation_methods: Annotated[ - list[activation_method.CreativeActivationMethod] | None, - Field( - description='Viewer activation mechanisms this option offers (e.g. `qr_code` on a pause frame). Activations are engagement events, not impressions.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/formats/canonical/image_carousel.py b/src/adcp/types/generated_poc/formats/canonical/image_carousel.py deleted file mode 100644 index ed5b1bade..000000000 --- a/src/adcp/types/generated_poc/formats/canonical/image_carousel.py +++ /dev/null @@ -1,89 +0,0 @@ -# generated by datamodel-codegen: -# filename: formats/canonical/image_carousel.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import SchemaInt -from pydantic import ConfigDict, Field, StrictBool - -from ._base import CanonicalFormatBase - - -class AllowedCardMediaAssetType(StrEnum): - image = 'image' - video = 'video' - - -class CanonicalFormatImageCarousel(CanonicalFormatBase): - model_config = ConfigDict( - extra='allow', - ) - v1_translatable: Annotated[ - Any | None, - Field( - description="Inherently new in v2 — multi-card carousels (Meta carousel, Pinterest pin collections, Snap collection ads) weren't expressible as v1 named formats. SDKs MUST NOT emit `FORMAT_PROJECTION_FAILED` for products using this canonical; the v1-unreachability is structural." - ), - ] = False - slots: Annotated[ - Any | None, - Field( - description="Default slots for image_carousel. The `cards` slot's value in the manifest is an array of [card-asset](/schemas/core/assets/card-asset.json) objects; `min` / `max` constrain card count." - ), - ] = [ - {'asset_group_id': 'cards', 'asset_type': 'card', 'required': True, 'min': 2, 'max': 10}, - {'asset_group_id': 'primary_text', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, - ] - card_aspect_ratio: Annotated[ - str | None, - Field( - description="Aspect ratio shared across all cards (e.g., '1:1', '1.91:1', '4:5').", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_cards: Annotated[ - SchemaInt | None, Field(description='Minimum card count (typical: 2 or 3).', ge=2) - ] = None - max_cards: Annotated[ - SchemaInt | None, - Field(description='Maximum card count (typical: 6, 10, or 35 depending on platform).'), - ] = None - allowed_card_media_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='Asset types each card\'s `media` field may carry. Default: [\'image\']. Polymorphic carousels (Meta) allow [\'image\', \'video\']. Renamed from `allowed_card_asset_types` to disambiguate that this constrains the card\'s media payload, not the card-asset itself (which is always asset_type: "card").' - ), - ] = None - allowed_card_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - deprecated=True, - description='DEPRECATED — alias for `allowed_card_media_asset_types`. Kept for back-compat; prefer the new field name. Removed in 5.0.', - ), - ] = None - card_image_max_file_size_kb: Annotated[SchemaInt | None, Field(ge=1)] = None - card_video_max_file_size_kb: Annotated[SchemaInt | None, Field(ge=1)] = None - card_video_max_duration_ms: Annotated[SchemaInt | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[ - SchemaInt | None, - Field(description='Maximum length of the carousel-level primary text.', ge=1), - ] = None - card_headline_max_chars: Annotated[ - SchemaInt | None, - Field( - description='Per-card headline character limit. Governs the `headline` field on each card-asset in the `cards` slot.', - ge=1, - ), - ] = None - card_description_max_chars: Annotated[ - SchemaInt | None, - Field( - description='Per-card description character limit. Governs the `description` field on each card-asset in the `cards` slot. Distinct from `card_headline_max_chars`: description is longer body copy (typically 100-500 chars); headline is the short label (typically 25-40 chars).', - ge=1, - ), - ] = None - ssl_required: StrictBool | None = None diff --git a/src/adcp/types/generated_poc/formats/canonical/native_in_feed.py b/src/adcp/types/generated_poc/formats/canonical/native_in_feed.py deleted file mode 100644 index c23c5ad89..000000000 --- a/src/adcp/types/generated_poc/formats/canonical/native_in_feed.py +++ /dev/null @@ -1,190 +0,0 @@ -# generated by datamodel-codegen: -# filename: formats/canonical/native_in_feed.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictBool - -from ...enums import activation_method -from ...enums import ctv_ad_experience as ctv_ad_experience_1 -from ...enums import motion_level as motion_level_1 -from ._base import CanonicalFormatBase - - -class MenuPlacement(StrEnum): - tile = 'tile' - headline_banner = 'headline_banner' - - -class FocusBehavior(StrEnum): - none = 'none' - autoplay_muted = 'autoplay_muted' - autoplay_sound = 'autoplay_sound' - - -class MainImageSize(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - width: Annotated[SchemaInt, Field(ge=1)] - height: Annotated[SchemaInt, Field(ge=1)] - - -class IconSize(MainImageSize): - pass - - -class ImageFormat(StrEnum): - jpg = 'jpg' - jpeg = 'jpeg' - png = 'png' - gif = 'gif' - webp = 'webp' - - -class AssetSource(StrEnum): - buyer_uploaded = 'buyer_uploaded' - seller_pre_rendered_from_brief = 'seller_pre_rendered_from_brief' - seller_human_designed = 'seller_human_designed' - agent_synthesized = 'agent_synthesized' - publisher_owned_reference = 'publisher_owned_reference' - - -class BuyerAssetAcceptance(StrEnum): - accepted = 'accepted' - rejected = 'rejected' - - -class CanonicalFormatNativeInFeed(CanonicalFormatBase): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - Any | None, - Field( - description='Stable at 3.1 GA. Shape mirrors IAB OpenRTB Native 1.2 — the renderer contract is well-established across in-feed native and content-recommendation adopters.' - ), - ] = False - v1_translatable: Annotated[ - Any | None, - Field( - description='Translates to v1 named native formats (e.g., `native_standard`, `native_content`) via the projection registry. Sellers with existing v1 named native formats SHOULD point `v1_format_ref[]` at them.' - ), - ] = True - slots: Annotated[ - Any | None, - Field( - description="Default slot shape for native_in_feed. Mirrors IAB OpenRTB Native 1.2 asset types, including the Native video asset: `video` carries a VAST document (the Native 1.2 `vasttag` field) for video-bearing native units such as CTV menu heroes with focus-triggered playback. Products MAY override (`slots_override` on the projection ref) to narrow per-slot limits (`max_chars` on title/body) or remove unused slots (a content-recommendation slot that doesn't display an icon)." - ), - ] = [ - {'asset_group_id': 'title', 'asset_type': 'text', 'required': True}, - {'asset_group_id': 'body_text', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'main_image', 'asset_type': 'image', 'required': False}, - {'asset_group_id': 'icon', 'asset_type': 'image', 'required': False}, - {'asset_group_id': 'cta', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'advertiser_name', 'asset_type': 'text', 'required': True}, - {'asset_group_id': 'sponsored_label', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': True}, - {'asset_group_id': 'display_url', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'rating', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'price', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'video', 'asset_type': 'vast', 'required': False}, - {'asset_group_id': 'impression_tracker', 'asset_type': 'pixel_tracker', 'required': False}, - {'asset_group_id': 'viewability_tracker', 'asset_type': 'pixel_tracker', 'required': False}, - {'asset_group_id': 'click_tracker', 'asset_type': 'pixel_tracker', 'required': False}, - ] - ctv_ad_experience: Annotated[ - ctv_ad_experience_1.CtvAdExperience | None, - Field( - description='CTV experience this option serves. On native_in_feed `menu` and `overlay` are valid. `menu`: smart-TV home/menu surfaces where the platform assembles buyer assets (background/main image, logo/icon, copy, optional focus-triggered video); `menu_placement` selects the tile vs headline-banner variant, and catalog-derived sponsored tiles route to `sponsored_placement` instead. `overlay`: seller-composited in-stream overlays supplied as an asset bundle (video, logo, imagery, copy, activation copy) — the contract overlay sellers that do not ingest VAST tags use; VAST-ingesting sellers publish a `video_vast` sibling option instead. The wire name stays `native_in_feed` for 3.x even though neither surface is literally in-feed.' - ), - ] = None - menu_placement: Annotated[ - MenuPlacement | None, - Field( - description='Menu surface variant, mapping to OpenRTB Native `plcmttype` 1 (tile/feed) and 3 (headline banner). Valid only with `ctv_ad_experience: "menu"`.' - ), - ] = None - focus_behavior: Annotated[ - FocusBehavior | None, - Field( - description='What happens when the viewer\'s remote focus lands on the unit. `autoplay_*` requires a `video` asset; playback method maps to AdCOM playbackmethod on OpenRTB bridges. Valid only with `ctv_ad_experience: "menu"`.' - ), - ] = None - motion_level: Annotated[ - motion_level_1.CreativeMotionLevel | None, - Field(description='Accepted motion class for the rendered unit (AdCOM attrs 21-23).'), - ] = None - activation_methods: Annotated[ - list[activation_method.CreativeActivationMethod] | None, - Field( - description='Viewer activation mechanisms this option offers (QR, deep link, send-to-device). Activations are engagement events, not impressions.' - ), - ] = None - title_max_chars: Annotated[ - SchemaInt | None, - Field( - description='Maximum character length for the title slot. IAB native typical: 25 (short) to 90 (long). Buyer agents SHOULD validate ship-time title length against this.', - ge=1, - ), - ] = None - body_text_max_chars: Annotated[ - SchemaInt | None, - Field( - description='Maximum character length for the body_text slot. IAB native typical: 90 (mainline) to 140 (extended).', - ge=1, - ), - ] = None - cta_max_chars: Annotated[ - SchemaInt | None, - Field(description='Maximum character length for the cta slot. Typical: 15–25.', ge=1), - ] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW', 'SIGN_UP', 'DOWNLOAD']). When set, narrows the cta slot to a closed enum." - ), - ] = None - main_image_sizes: Annotated[ - list[MainImageSize] | None, - Field( - description='Accepted logical (width, height) pairs for the main_image slot. Common IAB native sizes: 1200×627 (1.91:1), 1080×1080 (1:1), 1080×1350 (4:5). When the effective main_image slot declares `pixel_ratios`, intrinsic asset dimensions are the matched logical pair multiplied by the selected ratio; absence remains 1x.', - min_length=1, - ), - ] = None - icon_size: Annotated[ - IconSize | None, - Field( - description='Required logical (width, height) for the icon slot when present (typical: 80×80 or 100×100). When the effective icon slot declares `pixel_ratios`, intrinsic dimensions are multiplied by the selected ratio; absence remains 1x.' - ), - ] = None - max_image_file_size_kb: Annotated[ - SchemaInt | None, - Field(description='Maximum file size in kilobytes for main_image and icon.', ge=1), - ] = None - image_formats: Annotated[ - list[ImageFormat] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - StrictBool | None, - Field( - description='Whether trackers, landing pages, and image URLs must be served over HTTPS.' - ), - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered native assets come from. `publisher_host_recorded` is omitted (audio-specific and not meaningful for native). Other values mirror the shared production-source axis used on `image` / `video_hosted`. `buyer_uploaded` (default): buyer ships pre-rendered title/image/body. `seller_pre_rendered_from_brief`: buyer ships a brief, seller renders the native bundle. `agent_synthesized`: AI synthesis pipeline produces title + image + body from a brief; pair with `synthesis_nondeterministic: true` for generative pipelines that can't guarantee in-spec output. `publisher_owned_reference`: buyer ships an existing published post reference; the seller resolves the post into the native presentation after authorization/review." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded native assets. When `rejected`, the buyer cannot ship pre-rendered title/image/body — they must use `build_creative`, `sync_creatives` with brief inputs, or an accepted `published_post` reference so the seller produces or resolves the native bundle.' - ), - ] = BuyerAssetAcceptance.accepted diff --git a/src/adcp/types/generated_poc/formats/canonical/responsive_creative.py b/src/adcp/types/generated_poc/formats/canonical/responsive_creative.py deleted file mode 100644 index bc698e42e..000000000 --- a/src/adcp/types/generated_poc/formats/canonical/responsive_creative.py +++ /dev/null @@ -1,127 +0,0 @@ -# generated by datamodel-codegen: -# filename: formats/canonical/responsive_creative.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from adcp.types.base import SchemaInt -from pydantic import ConfigDict, Field, StrictBool - -from ._base import CanonicalFormatBase - - -class CanonicalFormatResponsiveCreative(CanonicalFormatBase): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - Any | None, - Field( - description="Marked experimental at 3.1 GA: composition is algorithmic (the surface picks combinations and reports per-asset breakdowns), and there's no clean v1-translatable equivalent. Buyers ship asset pools rather than rendered creatives; the surface's per-impression composition cannot be predicted by `validate_input`. Adopters SHOULD validate behavior per surface (Google PMax vs Meta Advantage+ creative differ meaningfully)." - ), - ] = True - v1_translatable: Annotated[ - Any | None, - Field( - description="Inherently new in v2 — algorithmic asset-pool composition (Google PMax / Meta Advantage+ creative) wasn't expressible as v1 named formats. SDKs MUST NOT emit `FORMAT_PROJECTION_FAILED` for products using this canonical; the v1-unreachability is structural." - ), - ] = False - slots: Any | None = [ - { - 'asset_group_id': 'headlines', - 'asset_type': 'text', - 'required': True, - 'min': 3, - 'max': 15, - }, - { - 'asset_group_id': 'long_headlines', - 'asset_type': 'text', - 'required': False, - 'min': 1, - 'max': 5, - }, - { - 'asset_group_id': 'descriptions', - 'asset_type': 'text', - 'required': True, - 'min': 2, - 'max': 5, - }, - { - 'asset_group_id': 'images_landscape', - 'asset_type': 'image', - 'required': False, - 'min': 1, - 'max': 20, - }, - { - 'asset_group_id': 'images_square', - 'asset_type': 'image', - 'required': False, - 'min': 1, - 'max': 20, - }, - { - 'asset_group_id': 'images_vertical', - 'asset_type': 'image', - 'required': False, - 'min': 1, - 'max': 20, - }, - {'asset_group_id': 'video', 'asset_type': 'video', 'required': False, 'min': 0, 'max': 5}, - { - 'asset_group_id': 'logo', - 'asset_type': 'image', - 'required': True, - 'min': 1, - 'max': 5, - 'logo_slots': [ - 'logo_card_light', - 'logo_card_dark', - 'marketplace_listing', - 'ad_end_card', - ], - 'required_logo_slots': ['logo_card_light', 'logo_card_dark'], - }, - { - 'asset_group_id': 'landing_page_url', - 'asset_type': 'url', - 'required': True, - 'min': 1, - 'max': 1, - }, - ] - headlines_min: Annotated[SchemaInt | None, Field(ge=0)] = None - headlines_max: Annotated[SchemaInt | None, Field(ge=0)] = None - headline_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None - long_headlines_min: Annotated[SchemaInt | None, Field(ge=0)] = None - long_headlines_max: Annotated[SchemaInt | None, Field(ge=0)] = None - long_headline_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None - descriptions_min: Annotated[SchemaInt | None, Field(ge=0)] = None - descriptions_max: Annotated[SchemaInt | None, Field(ge=0)] = None - description_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None - images_landscape_min: Annotated[SchemaInt | None, Field(ge=0)] = None - images_landscape_max: Annotated[SchemaInt | None, Field(ge=0)] = None - images_landscape_aspect_ratio: str | None = None - images_square_min: Annotated[SchemaInt | None, Field(ge=0)] = None - images_square_max: Annotated[SchemaInt | None, Field(ge=0)] = None - images_vertical_min: Annotated[SchemaInt | None, Field(ge=0)] = None - images_vertical_max: Annotated[SchemaInt | None, Field(ge=0)] = None - videos_min: Annotated[SchemaInt | None, Field(ge=0)] = None - videos_max: Annotated[SchemaInt | None, Field(ge=0)] = None - video_min_duration_ms: Annotated[SchemaInt | None, Field(ge=1)] = None - video_max_duration_ms: Annotated[SchemaInt | None, Field(ge=1)] = None - logo_min: Annotated[SchemaInt | None, Field(ge=0)] = None - logo_max: Annotated[SchemaInt | None, Field(ge=0)] = None - logo_aspect_ratios: list[str] | None = None - business_name_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None - asset_image_max_file_size_kb: Annotated[SchemaInt | None, Field(ge=1)] = None - supports_catalog_input: Annotated[ - StrictBool | None, - Field( - description='Whether the product can additionally consume a catalog reference (e.g., PMax with product feed).' - ), - ] = None diff --git a/src/adcp/types/generated_poc/formats/canonical/seller_rendered_stateful_display.py b/src/adcp/types/generated_poc/formats/canonical/seller_rendered_stateful_display.py deleted file mode 100644 index cb7c76dfa..000000000 --- a/src/adcp/types/generated_poc/formats/canonical/seller_rendered_stateful_display.py +++ /dev/null @@ -1,915 +0,0 @@ -# generated by datamodel-codegen: -# filename: formats/canonical/seller_rendered_stateful_display.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarInt, ScalarStr -from pydantic import ConfigDict, Field, StrictBool, StrictFloat - -from ...core import canvas_constraint -from ._base import CanonicalFormatBase - - -class SupplyMode(StrEnum): - components = 'components' - rendered_canvases = 'rendered_canvases' - layered_source = 'layered_source' - - -class Anchoring(StrEnum): - inline = 'inline' - sticky_top = 'sticky_top' - sticky_bottom = 'sticky_bottom' - overlay = 'overlay' - fullscreen_overlay = 'fullscreen_overlay' - underlay = 'underlay' - - -class SlotBinding(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[a-z0-9_]+$'} - - -class Motion(StrEnum): - static = 'static' - animated = 'animated' - - -class WidthRangeItem(ScalarInt): - __slots__ = () - _constraints = {'ge': 1} - - -class WidthMode(StrEnum): - full_bleed = 'full_bleed' - gutter_residual = 'gutter_residual' - - -class HeightRangeItem(WidthRangeItem): - pass - - -class Breakpoint(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - breakpoint_id: Annotated[ - str, - Field( - description='Stable identifier used by `state_canvases[].breakpoint_id`.', - pattern='^[a-z][a-z0-9_]*$', - ), - ] - width: Annotated[SchemaInt | None, Field(ge=1)] = None - width_range: Annotated[ - list[WidthRangeItem] | None, - Field( - description='Inclusive [minimum, maximum] accepted canvas width. The first value MUST be no greater than the second; semantic validators enforce ordering.', - max_length=2, - min_length=2, - ), - ] = None - width_mode: Annotated[ - WidthMode | None, - Field( - description='Fluid width classes. `full_bleed`: edge-to-edge viewport width (IAB adhesion, topscroll). `gutter_residual`: the margin remaining beside the page content column (desktop skins). Fluid breakpoints cannot be targeted by `rendered_canvases` supply — semantic validation rejects that pairing.' - ), - ] = None - height: Annotated[SchemaInt | None, Field(ge=1)] = None - height_range: Annotated[ - list[HeightRangeItem] | None, - Field( - description='Inclusive [minimum, maximum] accepted canvas height. The first value MUST be no greater than the second; semantic validators enforce ordering.', - max_length=2, - min_length=2, - ), - ] = None - viewport_height_percent: Annotated[ - StrictFloat | None, - Field( - description='Height as a percentage of the viewport (topscroll 80%, mobile skin panel 15%). Fluid; cannot be targeted by `rendered_canvases` supply.', - gt=0.0, - le=100.0, - ), - ] = None - canvas_aspect_ratio: Annotated[ - str | None, - Field( - description='Aspect ratio the rendered canvas maintains across the size range (IAB flexible units, ratio-bucket responsive). When both fixed `width` and `height` are declared, the ratio MUST be consistent with them.', - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - - -class State(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - state_id: Annotated[ - str, - Field( - description='Stable identifier used by `state_canvases[].state_id` and `state_click_urls[].state_id`.', - pattern='^[a-z][a-z0-9_]*$', - ), - ] - anchoring: Annotated[ - Anchoring, - Field( - description='`underlay` renders the canvas beneath page content, which scrolls over it (IAB New Ad Portfolio underlay class: skins, reveal units). Transitions into `overlay`/`fullscreen_overlay` states SHOULD be user-initiated; non-user-action entries emit LEAN policy warnings.' - ), - ] - slot_bindings: Annotated[ - list[SlotBinding] | None, - Field( - description='Asset group IDs rendered in this state (components mode). Makes the template executable: given components and bindings, assembly is deterministic. Every value MUST resolve to a declared slot. Omitted means all supplied component slots may render.' - ), - ] = None - motion: Annotated[ - Motion | None, - Field( - description='Whether the seller-rendered layout animates within this state (attract loops, load animations). Intra-state animation is seller-rendered; buyer canvases stay static images.' - ), - ] = Motion.static - max_animation_s: Annotated[ - StrictFloat | None, - Field( - description='Upper bound on intra-state animation duration. Required when `motion` is `animated`.', - gt=0.0, - ), - ] = None - breakpoints: Annotated[list[Breakpoint], Field(min_length=1)] - close_affordance: Annotated[ - StrictBool, - Field( - description='Whether this state visibly renders a seller-controlled close affordance. When true, rendering MUST follow IAB New Ad Portfolio close-button guidance (top-right, minimum 50×50 dp, available from state entry).' - ), - ] - - -class Reveal(StrEnum): - none = 'none' - clip_window = 'clip_window' - scroll_parallax = 'scroll_parallax' - - -class Trigger(StrEnum): - timer = 'timer' - in_view_timer = 'in_view_timer' - scroll_threshold = 'scroll_threshold' - scroll_progress = 'scroll_progress' - user_action = 'user_action' - media_event = 'media_event' - - -class Input(StrEnum): - tap = 'tap' - hover = 'hover' - swipe_up = 'swipe_up' - swipe_down = 'swipe_down' - swipe_left = 'swipe_left' - swipe_right = 'swipe_right' - scroll = 'scroll' - expand_control = 'expand_control' - collapse_control = 'collapse_control' - - -class MediaEvent(StrEnum): - video_start = 'video_start' - video_complete = 'video_complete' - - -class Direction(StrEnum): - down = 'down' - up = 'up' - - -class TransitionMode(StrEnum): - instant = 'instant' - animated = 'animated' - scroll_linked = 'scroll_linked' - instant_1 = 'instant' - animated_1 = 'animated' - - -class ScrollReference(StrEnum): - document_progress = 'document_progress' - containing_scroller_progress = 'containing_scroller_progress' - - -class Transitions(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - transition_id: Annotated[ - str, - Field( - description='Stable transition identifier for preview and reporting.', - pattern='^[a-z][a-z0-9_]*$', - ), - ] - from_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] - to_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] - trigger: Annotated[ - Literal['timer'], - Field( - description='Cause of the transition. `timer` counts from state entry; `in_view_timer` counts viewable time in the current state (industry auto-collapse is N seconds in view). `media_event` fires on `video_main` playback milestones. The outcome is expressed separately by `to_state_id`.' - ), - ] = 'timer' - input: Annotated[ - Input | None, - Field( - description='Input driving a user or scroll transition. `hover` expansion is disallowed by IAB NAP guidance and emits a LEAN policy warning visible to buyers.' - ), - ] = None - media_event: Annotated[ - MediaEvent | None, - Field( - description='Playback milestone of `video_main` driving a `media_event` transition (endframes, collapse-on-complete).' - ), - ] = None - direction: Annotated[ - Direction | None, - Field( - description='Scroll direction that arms a `scroll_threshold` transition; enables direction-aware expand/collapse cycles. Re-crossing in the opposite direction does not re-fire this transition.' - ), - ] = Direction.down - transition_mode: Annotated[ - TransitionMode, - Field( - description='`scroll_linked` continuously interpolates the seller-owned layout between the two declared endpoint states; it is valid only with trigger `scroll_progress` and does not permit buyer scripting.' - ), - ] - delay_ms: Annotated[ - SchemaInt, - Field( - description='For `timer`: delay after `from_state_id` activates (re-entry restarts it). For `in_view_timer`: accumulated viewable milliseconds in `from_state_id`. Timer-class transitions in a state cycle MUST declare at least 1000 (anti-strobe floor).', - ge=0, - ), - ] - duration_ms: Annotated[ - SchemaInt | None, - Field(description='Duration of a seller-rendered animated transition.', ge=0), - ] = None - scroll_threshold_percent: Annotated[ - StrictFloat | None, - Field( - description='Viewport/page scroll threshold that starts a `scroll_threshold` transition.', - ge=0.0, - le=100.0, - ), - ] = None - scroll_reference: Annotated[ - ScrollReference | None, - Field( - description='Reference frame for scroll percentages. Progress is `scroll_offset / max(scroll_extent - viewport_extent, 1) * 100`, measured on either the page document or the nearest seller-declared containing scroller.' - ), - ] = None - scroll_start_percent: Annotated[ - StrictFloat | None, - Field( - description='Start of the bounded scroll interval for a `scroll_progress` transition.', - ge=0.0, - le=100.0, - ), - ] = None - scroll_end_percent: Annotated[ - StrictFloat | None, - Field( - description='End of the bounded scroll interval for a `scroll_progress` transition. MUST be greater than `scroll_start_percent`.', - ge=0.0, - le=100.0, - ), - ] = None - preserve_playback: Annotated[ - StrictBool | None, - Field( - description='Whether `video_main` continues without restart while the seller changes state.' - ), - ] = False - - -class Transitions7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - transition_id: Annotated[ - str, - Field( - description='Stable transition identifier for preview and reporting.', - pattern='^[a-z][a-z0-9_]*$', - ), - ] - from_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] - to_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] - trigger: Annotated[ - Literal['in_view_timer'], - Field( - description='Cause of the transition. `timer` counts from state entry; `in_view_timer` counts viewable time in the current state (industry auto-collapse is N seconds in view). `media_event` fires on `video_main` playback milestones. The outcome is expressed separately by `to_state_id`.' - ), - ] = 'in_view_timer' - input: Annotated[ - Input | None, - Field( - description='Input driving a user or scroll transition. `hover` expansion is disallowed by IAB NAP guidance and emits a LEAN policy warning visible to buyers.' - ), - ] = None - media_event: Annotated[ - MediaEvent | None, - Field( - description='Playback milestone of `video_main` driving a `media_event` transition (endframes, collapse-on-complete).' - ), - ] = None - direction: Annotated[ - Direction | None, - Field( - description='Scroll direction that arms a `scroll_threshold` transition; enables direction-aware expand/collapse cycles. Re-crossing in the opposite direction does not re-fire this transition.' - ), - ] = Direction.down - transition_mode: Annotated[ - TransitionMode, - Field( - description='`scroll_linked` continuously interpolates the seller-owned layout between the two declared endpoint states; it is valid only with trigger `scroll_progress` and does not permit buyer scripting.' - ), - ] - delay_ms: Annotated[ - SchemaInt, - Field( - description='For `timer`: delay after `from_state_id` activates (re-entry restarts it). For `in_view_timer`: accumulated viewable milliseconds in `from_state_id`. Timer-class transitions in a state cycle MUST declare at least 1000 (anti-strobe floor).', - ge=0, - ), - ] - duration_ms: Annotated[ - SchemaInt | None, - Field(description='Duration of a seller-rendered animated transition.', ge=0), - ] = None - scroll_threshold_percent: Annotated[ - StrictFloat | None, - Field( - description='Viewport/page scroll threshold that starts a `scroll_threshold` transition.', - ge=0.0, - le=100.0, - ), - ] = None - scroll_reference: Annotated[ - ScrollReference | None, - Field( - description='Reference frame for scroll percentages. Progress is `scroll_offset / max(scroll_extent - viewport_extent, 1) * 100`, measured on either the page document or the nearest seller-declared containing scroller.' - ), - ] = None - scroll_start_percent: Annotated[ - StrictFloat | None, - Field( - description='Start of the bounded scroll interval for a `scroll_progress` transition.', - ge=0.0, - le=100.0, - ), - ] = None - scroll_end_percent: Annotated[ - StrictFloat | None, - Field( - description='End of the bounded scroll interval for a `scroll_progress` transition. MUST be greater than `scroll_start_percent`.', - ge=0.0, - le=100.0, - ), - ] = None - preserve_playback: Annotated[ - StrictBool | None, - Field( - description='Whether `video_main` continues without restart while the seller changes state.' - ), - ] = False - - -class Transitions8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - transition_id: Annotated[ - str, - Field( - description='Stable transition identifier for preview and reporting.', - pattern='^[a-z][a-z0-9_]*$', - ), - ] - from_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] - to_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] - trigger: Annotated[ - Literal['scroll_threshold'], - Field( - description='Cause of the transition. `timer` counts from state entry; `in_view_timer` counts viewable time in the current state (industry auto-collapse is N seconds in view). `media_event` fires on `video_main` playback milestones. The outcome is expressed separately by `to_state_id`.' - ), - ] = 'scroll_threshold' - input: Annotated[ - Literal['scroll'], - Field( - description='Input driving a user or scroll transition. `hover` expansion is disallowed by IAB NAP guidance and emits a LEAN policy warning visible to buyers.' - ), - ] = 'scroll' - media_event: Annotated[ - MediaEvent | None, - Field( - description='Playback milestone of `video_main` driving a `media_event` transition (endframes, collapse-on-complete).' - ), - ] = None - direction: Annotated[ - Direction | None, - Field( - description='Scroll direction that arms a `scroll_threshold` transition; enables direction-aware expand/collapse cycles. Re-crossing in the opposite direction does not re-fire this transition.' - ), - ] = Direction.down - transition_mode: Annotated[ - TransitionMode, - Field( - description='`scroll_linked` continuously interpolates the seller-owned layout between the two declared endpoint states; it is valid only with trigger `scroll_progress` and does not permit buyer scripting.' - ), - ] - delay_ms: Annotated[ - SchemaInt | None, - Field( - description='For `timer`: delay after `from_state_id` activates (re-entry restarts it). For `in_view_timer`: accumulated viewable milliseconds in `from_state_id`. Timer-class transitions in a state cycle MUST declare at least 1000 (anti-strobe floor).', - ge=0, - ), - ] = None - duration_ms: Annotated[ - SchemaInt | None, - Field(description='Duration of a seller-rendered animated transition.', ge=0), - ] = None - scroll_threshold_percent: Annotated[ - StrictFloat, - Field( - description='Viewport/page scroll threshold that starts a `scroll_threshold` transition.', - ge=0.0, - le=100.0, - ), - ] - scroll_reference: Annotated[ - ScrollReference, - Field( - description='Reference frame for scroll percentages. Progress is `scroll_offset / max(scroll_extent - viewport_extent, 1) * 100`, measured on either the page document or the nearest seller-declared containing scroller.' - ), - ] - scroll_start_percent: Annotated[ - StrictFloat | None, - Field( - description='Start of the bounded scroll interval for a `scroll_progress` transition.', - ge=0.0, - le=100.0, - ), - ] = None - scroll_end_percent: Annotated[ - StrictFloat | None, - Field( - description='End of the bounded scroll interval for a `scroll_progress` transition. MUST be greater than `scroll_start_percent`.', - ge=0.0, - le=100.0, - ), - ] = None - preserve_playback: Annotated[ - StrictBool | None, - Field( - description='Whether `video_main` continues without restart while the seller changes state.' - ), - ] = False - - -class TransitionMode9(StrEnum): - instant = 'instant' - animated = 'animated' - scroll_linked = 'scroll_linked' - - -class Transitions9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - transition_id: Annotated[ - str, - Field( - description='Stable transition identifier for preview and reporting.', - pattern='^[a-z][a-z0-9_]*$', - ), - ] - from_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] - to_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] - trigger: Annotated[ - Literal['scroll_progress'], - Field( - description='Cause of the transition. `timer` counts from state entry; `in_view_timer` counts viewable time in the current state (industry auto-collapse is N seconds in view). `media_event` fires on `video_main` playback milestones. The outcome is expressed separately by `to_state_id`.' - ), - ] = 'scroll_progress' - input: Annotated[ - Literal['scroll'], - Field( - description='Input driving a user or scroll transition. `hover` expansion is disallowed by IAB NAP guidance and emits a LEAN policy warning visible to buyers.' - ), - ] = 'scroll' - media_event: Annotated[ - MediaEvent | None, - Field( - description='Playback milestone of `video_main` driving a `media_event` transition (endframes, collapse-on-complete).' - ), - ] = None - direction: Annotated[ - Direction | None, - Field( - description='Scroll direction that arms a `scroll_threshold` transition; enables direction-aware expand/collapse cycles. Re-crossing in the opposite direction does not re-fire this transition.' - ), - ] = Direction.down - transition_mode: Annotated[ - Literal['scroll_linked'], - Field( - description='`scroll_linked` continuously interpolates the seller-owned layout between the two declared endpoint states; it is valid only with trigger `scroll_progress` and does not permit buyer scripting.' - ), - ] = 'scroll_linked' - delay_ms: Annotated[ - SchemaInt | None, - Field( - description='For `timer`: delay after `from_state_id` activates (re-entry restarts it). For `in_view_timer`: accumulated viewable milliseconds in `from_state_id`. Timer-class transitions in a state cycle MUST declare at least 1000 (anti-strobe floor).', - ge=0, - ), - ] = None - duration_ms: Annotated[ - SchemaInt | None, - Field(description='Duration of a seller-rendered animated transition.', ge=0), - ] = None - scroll_threshold_percent: Annotated[ - StrictFloat | None, - Field( - description='Viewport/page scroll threshold that starts a `scroll_threshold` transition.', - ge=0.0, - le=100.0, - ), - ] = None - scroll_reference: Annotated[ - ScrollReference, - Field( - description='Reference frame for scroll percentages. Progress is `scroll_offset / max(scroll_extent - viewport_extent, 1) * 100`, measured on either the page document or the nearest seller-declared containing scroller.' - ), - ] - scroll_start_percent: Annotated[ - StrictFloat, - Field( - description='Start of the bounded scroll interval for a `scroll_progress` transition.', - ge=0.0, - le=100.0, - ), - ] - scroll_end_percent: Annotated[ - StrictFloat, - Field( - description='End of the bounded scroll interval for a `scroll_progress` transition. MUST be greater than `scroll_start_percent`.', - ge=0.0, - le=100.0, - ), - ] - preserve_playback: Annotated[ - StrictBool | None, - Field( - description='Whether `video_main` continues without restart while the seller changes state.' - ), - ] = False - - -class Transitions10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - transition_id: Annotated[ - str, - Field( - description='Stable transition identifier for preview and reporting.', - pattern='^[a-z][a-z0-9_]*$', - ), - ] - from_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] - to_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] - trigger: Annotated[ - Literal['user_action'], - Field( - description='Cause of the transition. `timer` counts from state entry; `in_view_timer` counts viewable time in the current state (industry auto-collapse is N seconds in view). `media_event` fires on `video_main` playback milestones. The outcome is expressed separately by `to_state_id`.' - ), - ] = 'user_action' - input: Annotated[ - Input, - Field( - description='Input driving a user or scroll transition. `hover` expansion is disallowed by IAB NAP guidance and emits a LEAN policy warning visible to buyers.' - ), - ] - media_event: Annotated[ - MediaEvent | None, - Field( - description='Playback milestone of `video_main` driving a `media_event` transition (endframes, collapse-on-complete).' - ), - ] = None - direction: Annotated[ - Direction | None, - Field( - description='Scroll direction that arms a `scroll_threshold` transition; enables direction-aware expand/collapse cycles. Re-crossing in the opposite direction does not re-fire this transition.' - ), - ] = Direction.down - transition_mode: Annotated[ - TransitionMode, - Field( - description='`scroll_linked` continuously interpolates the seller-owned layout between the two declared endpoint states; it is valid only with trigger `scroll_progress` and does not permit buyer scripting.' - ), - ] - delay_ms: Annotated[ - SchemaInt | None, - Field( - description='For `timer`: delay after `from_state_id` activates (re-entry restarts it). For `in_view_timer`: accumulated viewable milliseconds in `from_state_id`. Timer-class transitions in a state cycle MUST declare at least 1000 (anti-strobe floor).', - ge=0, - ), - ] = None - duration_ms: Annotated[ - SchemaInt | None, - Field(description='Duration of a seller-rendered animated transition.', ge=0), - ] = None - scroll_threshold_percent: Annotated[ - StrictFloat | None, - Field( - description='Viewport/page scroll threshold that starts a `scroll_threshold` transition.', - ge=0.0, - le=100.0, - ), - ] = None - scroll_reference: Annotated[ - ScrollReference | None, - Field( - description='Reference frame for scroll percentages. Progress is `scroll_offset / max(scroll_extent - viewport_extent, 1) * 100`, measured on either the page document or the nearest seller-declared containing scroller.' - ), - ] = None - scroll_start_percent: Annotated[ - StrictFloat | None, - Field( - description='Start of the bounded scroll interval for a `scroll_progress` transition.', - ge=0.0, - le=100.0, - ), - ] = None - scroll_end_percent: Annotated[ - StrictFloat | None, - Field( - description='End of the bounded scroll interval for a `scroll_progress` transition. MUST be greater than `scroll_start_percent`.', - ge=0.0, - le=100.0, - ), - ] = None - preserve_playback: Annotated[ - StrictBool | None, - Field( - description='Whether `video_main` continues without restart while the seller changes state.' - ), - ] = False - - -class Transitions11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - transition_id: Annotated[ - str, - Field( - description='Stable transition identifier for preview and reporting.', - pattern='^[a-z][a-z0-9_]*$', - ), - ] - from_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] - to_state_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] - trigger: Annotated[ - Literal['media_event'], - Field( - description='Cause of the transition. `timer` counts from state entry; `in_view_timer` counts viewable time in the current state (industry auto-collapse is N seconds in view). `media_event` fires on `video_main` playback milestones. The outcome is expressed separately by `to_state_id`.' - ), - ] = 'media_event' - input: Annotated[ - Input | None, - Field( - description='Input driving a user or scroll transition. `hover` expansion is disallowed by IAB NAP guidance and emits a LEAN policy warning visible to buyers.' - ), - ] = None - media_event: Annotated[ - MediaEvent, - Field( - description='Playback milestone of `video_main` driving a `media_event` transition (endframes, collapse-on-complete).' - ), - ] - direction: Annotated[ - Direction | None, - Field( - description='Scroll direction that arms a `scroll_threshold` transition; enables direction-aware expand/collapse cycles. Re-crossing in the opposite direction does not re-fire this transition.' - ), - ] = Direction.down - transition_mode: Annotated[ - TransitionMode, - Field( - description='`scroll_linked` continuously interpolates the seller-owned layout between the two declared endpoint states; it is valid only with trigger `scroll_progress` and does not permit buyer scripting.' - ), - ] - delay_ms: Annotated[ - SchemaInt | None, - Field( - description='For `timer`: delay after `from_state_id` activates (re-entry restarts it). For `in_view_timer`: accumulated viewable milliseconds in `from_state_id`. Timer-class transitions in a state cycle MUST declare at least 1000 (anti-strobe floor).', - ge=0, - ), - ] = None - duration_ms: Annotated[ - SchemaInt | None, - Field(description='Duration of a seller-rendered animated transition.', ge=0), - ] = None - scroll_threshold_percent: Annotated[ - StrictFloat | None, - Field( - description='Viewport/page scroll threshold that starts a `scroll_threshold` transition.', - ge=0.0, - le=100.0, - ), - ] = None - scroll_reference: Annotated[ - ScrollReference | None, - Field( - description='Reference frame for scroll percentages. Progress is `scroll_offset / max(scroll_extent - viewport_extent, 1) * 100`, measured on either the page document or the nearest seller-declared containing scroller.' - ), - ] = None - scroll_start_percent: Annotated[ - StrictFloat | None, - Field( - description='Start of the bounded scroll interval for a `scroll_progress` transition.', - ge=0.0, - le=100.0, - ), - ] = None - scroll_end_percent: Annotated[ - StrictFloat | None, - Field( - description='End of the bounded scroll interval for a `scroll_progress` transition. MUST be greater than `scroll_start_percent`.', - ge=0.0, - le=100.0, - ), - ] = None - preserve_playback: Annotated[ - StrictBool | None, - Field( - description='Whether `video_main` continues without restart while the seller changes state.' - ), - ] = False - - -class Clickthrough(StrEnum): - required = 'required' - optional = 'optional' - none = 'none' - - -class UserControls(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - dismissible: StrictBool - user_collapsible: StrictBool - - -class DurationMsRange(ScalarInt): - __slots__ = () - _constraints = {'ge': 0} - - -class Container(StrEnum): - mp4 = 'mp4' - webm = 'webm' - mov = 'mov' - - -class VideoPlayback(StrEnum): - none = 'none' - auto_muted = 'auto_muted' - user_initiated = 'user_initiated' - - -class CanonicalFormatSellerRenderedStatefulDisplay(CanonicalFormatBase): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - Any | None, - Field( - description='Experimental in AdCP 3.2 while the creative working group gathers implementation evidence across premium web and mobile/app sellers.' - ), - ] = True - v1_translatable: Annotated[ - Any | None, - Field( - description='No v1 named-format equivalent can express multiple seller-rendered states and their breakpoint bindings.' - ), - ] = False - since_version: Any | None = '3.2' - composition_model: Any | None = 'deterministic' - supply_mode: Annotated[ - SupplyMode | None, - Field( - description='Which end of the template contract the buyer feeds. `components`: buyer supplies component slots; seller renders states (no `state_canvases`/`layered_source` assets allowed). `rendered_canvases`: buyer supplies exactly one `state_canvases` image per declared state × breakpoint pair. `layered_source`: buyer ships design source (+ optional `font_files`); seller production derives states (`production_window_business_days` applies) — transitional for sellers without executable templates.' - ), - ] = SupplyMode.components - slots: Annotated[ - Any | None, - Field( - description='Default manifest slots; which are consumed depends on `supply_mode`. `state_canvases` images MUST carry `state_id` and `breakpoint_id`, and `state_click_urls` entries MUST carry `state_id` (semantic validators resolve the bindings). Component images SHOULD carry `focal_point` for deterministic seller cropping. `landing_page_url` is the default destination (see `clickthrough`). `font_files` MUST contain only buyer-licensed fonts; publisher-proprietary fonts never travel in manifests. Only image, video, text, url, zip, and pixel_tracker slot asset types are accepted — executable types (javascript, html, css, webhook) are rejected even via `slots` overrides.' - ), - ] = [ - {'asset_group_id': 'state_canvases', 'asset_type': 'image', 'required': False, 'min': 1}, - {'asset_group_id': 'logo', 'asset_type': 'image', 'required': False}, - {'asset_group_id': 'imagery', 'asset_type': 'image', 'required': False, 'min': 0}, - {'asset_group_id': 'headline', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'messaging', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'cta', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'legal_text', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'video_main', 'asset_type': 'video', 'required': False}, - {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': True}, - {'asset_group_id': 'state_click_urls', 'asset_type': 'url', 'required': False, 'min': 0}, - {'asset_group_id': 'layered_source', 'asset_type': 'zip', 'required': False}, - {'asset_group_id': 'font_files', 'asset_type': 'zip', 'required': False}, - ] - states: Annotated[ - list[State], - Field( - description='Finite visual states known at buy time; state and breakpoint IDs form the canvas-key matrix. Runtime causes live in `transitions[]`. A single-state unit (topscroll, interscroller, skin) declares one state, no transitions, and typically a `reveal` mechanic.', - min_length=1, - ), - ] - initial_state_id: Annotated[ - str, - Field( - description='State rendered when the unit first becomes visible. MUST resolve to `states[].state_id`; for a single-state unit it MUST equal the sole state.', - pattern='^[a-z][a-z0-9_]*$', - ), - ] - reveal: Annotated[ - Reveal | None, - Field( - description='How the unit enters view, distinct from state changes. `clip_window`: canvas fixed and progressively exposed through a scrolling window (interscroller, topscroll). `scroll_parallax`: canvas moves at a different rate than content. Reveal is presentation of one canvas, not a transition; do not fabricate a second state to express it.' - ), - ] = Reveal.none - transitions: Annotated[ - list[ - Transitions | Transitions7 | Transitions8 | Transitions9 | Transitions10 | Transitions11 - ] - | None, - Field( - description='Bounded seller-rendered transitions between declared visual states. Required when `states` has more than one entry; MUST be omitted for single-state units. Every non-initial state MUST be reachable from `initial_state_id`. Dismissal is terminal unit behavior declared by `user_controls.dismissible`, not a hidden visual state.', - min_length=1, - ), - ] = None - clickthrough: Annotated[ - Clickthrough | None, - Field( - description='Destination policy. `required` (default): manifest MUST supply `landing_page_url`. `optional`: click-optional units (in-feed brand units) may omit it. `none`: unit is non-clickable; manifests MUST NOT supply `landing_page_url` or `state_click_urls`. Per-state overrides via `state_click_urls` entries carrying `state_id`; `landing_page_url` is the fallback for unlisted states.' - ), - ] = Clickthrough.required - user_controls: Annotated[ - UserControls, - Field( - description="When any state anchors as `overlay` or `fullscreen_overlay`, either `dismissible` MUST be true or that state's `close_affordance` MUST be true (dismissibility floor; semantic validators enforce)." - ), - ] - canvas_constraints: Annotated[ - list[canvas_constraint.CanvasConstraint] | None, - Field( - description='Rectangular areas constraining buyer artwork. Omitted state/breakpoint selectors apply the constraint to every canvas. For fluid or range-sized breakpoints, use percent-unit regions.' - ), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='Accepted embedded-video duration [min, max]. `duration_ms_exact` takes precedence when both are present.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[SchemaInt | None, Field(ge=1)] = None - aspect_ratio: Annotated[ - str | None, - Field( - description='Embedded-video aspect ratio.', - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - containers: list[Container] | None = None - video_playback: VideoPlayback | None = None - max_initial_load_kb: Annotated[SchemaInt | None, Field(ge=1)] = None - max_subload_kb: Annotated[ - SchemaInt | None, - Field( - description='Ceiling on assets loaded after the window load event (IAB LEAN subload). Pairs with `max_initial_load_kb` to mirror the New Ad Portfolio initial/subload weight pair.', - ge=1, - ), - ] = None - polite_load: Annotated[ - StrictBool | None, - Field( - description="When true, non-initial assets load only after the host page's window load event (IAB LEAN subload boundary)." - ), - ] = None diff --git a/src/adcp/types/generated_poc/formats/canonical/sponsored_placement.py b/src/adcp/types/generated_poc/formats/canonical/sponsored_placement.py deleted file mode 100644 index b3ab0970d..000000000 --- a/src/adcp/types/generated_poc/formats/canonical/sponsored_placement.py +++ /dev/null @@ -1,111 +0,0 @@ -# generated by datamodel-codegen: -# filename: formats/canonical/sponsored_placement.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import SchemaInt -from pydantic import ConfigDict, Field, StrictBool - -from ...enums import catalog_type -from ...enums import ctv_ad_experience as ctv_ad_experience_1 -from ._base import CanonicalFormatBase - - -class FanoutMode(StrEnum): - per_item = 'per_item' - multi_item_in_creative = 'multi_item_in_creative' - single_item = 'single_item' - - -class SupportedIdType(StrEnum): - asin = 'asin' - sku = 'sku' - gtin = 'gtin' - offering_id = 'offering_id' - store_id = 'store_id' - hotel_id = 'hotel_id' - flight_id = 'flight_id' - vehicle_id = 'vehicle_id' - listing_id = 'listing_id' - program_id = 'program_id' - destination_id = 'destination_id' - app_id = 'app_id' - job_id = 'job_id' - - -class ItemProductionModel(StrEnum): - buyer_uploaded = 'buyer_uploaded' - seller_pre_rendered_from_brief = 'seller_pre_rendered_from_brief' - seller_human_designed = 'seller_human_designed' - agent_synthesized = 'agent_synthesized' - - -class CanonicalFormatSponsoredPlacementRetailMediaCatalogDriven(CanonicalFormatBase): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - Any | None, - Field( - description='Marked experimental at 3.1 GA: the canonical covers 4 meaningfully different retail-media adapter contracts (Amazon SP, Criteo SP / CitrusAd SP, Pinterest Collection, generative-per-SKU). Adopter contracts vary; buyers MUST validate per-adapter behavior before routing budget. Promotion to non-experimental gated on the #4592 adapter-contract docs work.' - ), - ] = True - v1_translatable: Annotated[ - Any | None, - Field( - description="Inherently new in v2 — retail-media catalog placements weren't expressible as v1 named formats. SDKs MUST NOT emit `FORMAT_PROJECTION_FAILED` for products using this canonical; the v1-unreachability is structural, not a registry-coverage gap." - ), - ] = False - slots: Any | None = [ - {'asset_group_id': 'source_catalog', 'required': True, 'asset_type': 'catalog'}, - {'asset_group_id': 'hero_asset', 'required': False, 'asset_type': 'image'}, - {'asset_group_id': 'landing_page_url', 'required': False, 'asset_type': 'url'}, - ] - supported_catalog_types: Annotated[ - list[catalog_type.CatalogType] | None, - Field(description='Catalog types this product accepts.'), - ] = None - min_items: Annotated[ - SchemaInt | None, Field(description='Minimum catalog item count buyer must supply.', ge=1) - ] = None - max_items: Annotated[ - SchemaInt | None, Field(description='Maximum items considered for placement.') - ] = None - fanout_mode: Annotated[ - FanoutMode | None, - Field( - description='How items map to delivery: per_item = one ad per catalog item; multi_item_in_creative = composed multi-item ad (Pinterest Collection, Snap Collection); single_item = one ad showing one item.' - ), - ] = None - required_catalog_fields: Annotated[ - list[str] | None, - Field( - description="Catalog item fields the seller requires (e.g., ['title', 'image_url', 'price'])." - ), - ] = None - supported_id_types: Annotated[ - list[SupportedIdType] | None, - Field(description='Catalog identifier types the placement renders against.'), - ] = None - hero_asset_supported: Annotated[ - StrictBool | None, - Field( - description='Whether the buyer can supply a hero/banner asset alongside the catalog (Pinterest Collection pattern).' - ), - ] = None - item_production_model: Annotated[ - ItemProductionModel | None, - Field( - description='How each per-item creative is produced. Covers the same production-source axis as `asset_source` on `image` / `video_hosted` / `audio_hosted` but with a 4-value subset — drops `publisher_host_recorded` because it\'s audio-specific and doesn\'t apply to retail-media catalog placements. SDK codegen MAY share a base enum and narrow per-canonical, or emit two distinct enums; either way the wire values overlap exactly for the 4 retained values. `buyer_uploaded` (default, current Amazon/Criteo/CitrusAd pattern): the buyer\'s catalog already contains rendered assets per item; the seller composes the placement using those assets. ("Uploaded" reads slightly off for catalog-keyed items where the buyer didn\'t actively upload bytes — the catalog ingestion already supplied them — but the semantic is the same: rendered bytes are buyer-supplied, not seller-produced.) `seller_pre_rendered_from_brief`: the buyer ships a brief plus the catalog reference; the seller renders one creative per catalog item from the brief at sync_creatives time. `seller_human_designed`: seller\'s design team produces per-item renders manually. `agent_synthesized`: AI synthesis pipeline produces per-item renders; pair with `synthesis_nondeterministic: true` for Veo/Sora-class generative video applied per item. Captures the multi-output generative pattern (1 brief × N catalog items → N rendered creatives) under the existing canonical without requiring a separate canonical. Distinct from `fanout_mode`, which describes how items map to delivery slots after rendering.' - ), - ] = ItemProductionModel.buyer_uploaded - ctv_ad_experience: Annotated[ - ctv_ad_experience_1.CtvAdExperience | None, - Field( - description='CTV experience this option serves. On `sponsored_placement`: `menu` (sponsored app/content tiles and rows whose assets derive from a catalog listing — Fire-TV-tile pattern), `squeezeback`, and `in_scene` (seller-composited brand integrations produced from the catalog/brief rather than a buyer wire creative). Asset-bundle menu heroes route to `native_in_feed`.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/formats/canonical/video_hosted.py b/src/adcp/types/generated_poc/formats/canonical/video_hosted.py deleted file mode 100644 index b5bcd0e21..000000000 --- a/src/adcp/types/generated_poc/formats/canonical/video_hosted.py +++ /dev/null @@ -1,169 +0,0 @@ -# generated by datamodel-codegen: -# filename: formats/canonical/video_hosted.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._scalar import ScalarInt - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import SchemaInt -from pydantic import ConfigDict, Field, StrictBool, StrictFloat - -from ...enums import ctv_ad_experience as ctv_ad_experience_1 -from ._base import CanonicalFormatBase - - -class Orientation(StrEnum): - vertical = 'vertical' - horizontal = 'horizontal' - square = 'square' - - -class DurationMsRange(ScalarInt): - __slots__ = () - _constraints = {'ge': 0} - - -class VideoCodec(StrEnum): - h264 = 'h264' - h265 = 'h265' - vp8 = 'vp8' - vp9 = 'vp9' - av1 = 'av1' - prores = 'prores' - - -class AudioCodec(StrEnum): - aac = 'aac' - mp3 = 'mp3' - opus = 'opus' - pcm = 'pcm' - - -class Container(StrEnum): - mp4 = 'mp4' - webm = 'webm' - mov = 'mov' - - -class Captions(StrEnum): - required = 'required' - recommended = 'recommended' - not_required = 'not_required' - - -class CompanionBannerWidth(ScalarInt): - __slots__ = () - _constraints = {'ge': 1} - - -class CompanionBannerHeight(CompanionBannerWidth): - pass - - -class AssetSource(StrEnum): - buyer_uploaded = 'buyer_uploaded' - publisher_host_recorded = 'publisher_host_recorded' - seller_pre_rendered_from_brief = 'seller_pre_rendered_from_brief' - seller_human_designed = 'seller_human_designed' - agent_synthesized = 'agent_synthesized' - publisher_owned_reference = 'publisher_owned_reference' - - -class BuyerAssetAcceptance(StrEnum): - accepted = 'accepted' - rejected = 'rejected' - - -class CanonicalFormatHostedVideo(CanonicalFormatBase): - model_config = ConfigDict( - extra='allow', - ) - slots: Annotated[ - Any | None, - Field( - description='Default slots for video_hosted canonical. Buyer ships a video asset (file or hosted URL); optional headline, primary text (long-form caption), CTA (typically constrained via `cta_values`), brand_name (typical for vertical short-form), companion_banner (typical for horizontal instream), and clickthrough URL. Products MAY override or extend the default — e.g., remove `companion_banner` for short-form vertical, narrow `cta` to a value enum, mark `landing_page_url` as required.' - ), - ] = [ - {'asset_group_id': 'video_main', 'asset_type': 'video', 'required': True}, - {'asset_group_id': 'headline', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'primary_text', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'cta', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'brand_name', 'asset_type': 'text', 'required': False}, - {'asset_group_id': 'companion_banner', 'asset_type': 'image', 'required': False}, - {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, - ] - orientation: Annotated[ - Orientation | None, - Field( - description='Video orientation. Vertical = 9:16 (Reels, Stories, Shorts). Horizontal = 16:9 (instream, CTV). Square = 1:1 (in-feed).' - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description='Aspect ratio. Inferred from orientation if omitted.', - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_width: Annotated[SchemaInt | None, Field(ge=1)] = None - min_height: Annotated[SchemaInt | None, Field(ge=1)] = None - max_width: Annotated[SchemaInt | None, Field(ge=1)] = None - max_height: Annotated[SchemaInt | None, Field(ge=1)] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - SchemaInt | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship (see `duration_ms_range` description).', - ge=1, - ), - ] = None - video_codecs: list[VideoCodec] | None = None - audio_codecs: list[AudioCodec] | None = None - containers: list[Container] | None = None - min_bitrate_kbps: Annotated[SchemaInt | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[SchemaInt | None, Field(ge=1)] = None - max_file_size_mb: Annotated[ - SchemaInt | None, - Field(description='Maximum file size, where 1 MB is exactly 1,000,000 bytes.', ge=1), - ] = None - frame_rates: list[StrictFloat] | None = None - captions: Captions | None = None - om_sdk_required: StrictBool | None = None - headline_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[SchemaInt | None, Field(ge=1)] = None - cta_values: list[str] | None = None - companion_banner_widths: Annotated[ - list[CompanionBannerWidth] | None, - Field(description='Permitted companion banner widths (instream video).'), - ] = None - companion_banner_heights: list[CompanionBannerHeight] | None = None - asset_source: Annotated[ - AssetSource | None, - Field( - description='Where the rendered asset bytes come from. Single shared enum across canonicals. See `image.json#asset_source` for the full semantics. `publisher_host_recorded` is audio-specific and has no defined behavior on video. `publisher_owned_reference` is valid when the product accepts an existing post reference via a `published_post` slot instead of uploaded video bytes. Adopters MUST select a value appropriate to the canonical.' - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded video. When `rejected`, the buyer cannot ship a video asset directly — they must use build_creative, sync_creatives with brief inputs, or sync_creatives with an accepted reference asset so the seller produces or resolves the video.' - ), - ] = BuyerAssetAcceptance.accepted - ctv_ad_experience: Annotated[ - ctv_ad_experience_1.CtvAdExperience | None, - Field( - description='CTV experience this option serves. On `video_hosted` only `screensaver` is valid (ambient looping video the platform plays on idle). Other experiences route per the matrix in docs/creative/ctv-experiences.mdx; linear CTV video declares no experience.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/formats/canonical/video_vast.py b/src/adcp/types/generated_poc/formats/canonical/video_vast.py deleted file mode 100644 index 4d746ee51..000000000 --- a/src/adcp/types/generated_poc/formats/canonical/video_vast.py +++ /dev/null @@ -1,176 +0,0 @@ -# generated by datamodel-codegen: -# filename: formats/canonical/video_vast.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._scalar import ScalarInt - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import SchemaInt -from pydantic import ConfigDict, Field, StrictBool - -from ...core import vast_media_file_requirements -from ...enums import activation_method -from ...enums import ctv_ad_experience as ctv_ad_experience_1 -from ...enums import motion_level as motion_level_1 -from ...enums import vast_version as vast_version_1 -from ._base import CanonicalFormatBase - - -class Orientation(StrEnum): - vertical = 'vertical' - horizontal = 'horizontal' - square = 'square' - - -class VpaidVersion(StrEnum): - field_1_0 = '1.0' - field_2_0 = '2.0' - - -class DurationMsRangeItem(ScalarInt): - __slots__ = () - _constraints = {'ge': 0} - - -class CreativeType(StrEnum): - linear = 'linear' - nonlinear = 'nonlinear' - either = 'either' - - -class CanonicalFormatVastVideo(CanonicalFormatBase): - model_config = ConfigDict( - extra='allow', - ) - slots: Annotated[ - Any | None, - Field( - description="Default slots for video_vast canonical. Buyer ships a VAST tag (URL or inline XML, VAST 2.x-4.x) plus an optional clickthrough URL (which falls back to the VAST `ClickThrough` element when omitted). Tracking events are inherent to VAST and don't require explicit slots." - ), - ] = [ - {'asset_group_id': 'vast_tag', 'asset_type': 'vast', 'required': True}, - {'asset_group_id': 'landing_page_url', 'asset_type': 'url', 'required': False}, - ] - orientation: Orientation | None = None - aspect_ratio: Annotated[ - str | None, Field(pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$') - ] = None - vast_versions: Annotated[ - list[vast_version_1.VastVersion] | None, - Field( - description='VAST versions accepted by this product format option. The asset still declares exactly one `vast_version`; compatibility requires membership in this set and the seller-wide execution set.', - min_length=1, - ), - ] = None - vast_version: Annotated[ - vast_version_1.VastVersion | None, - Field( - deprecated=True, - description='Deprecated one-element alias for `vast_versions`. Producers use either the singular legacy alias or the plural 3.2 field, never both.', - ), - ] = None - media_file_requirements: Annotated[ - vast_media_file_requirements.VastMediafileRequirements | None, - Field( - description='Technical acceptance constraints for alternative VAST MediaFile renditions. Each applicable resolved InLine linear creative needs at least one MediaFile satisfying all declared constraints.' - ), - ] = None - vpaid_enabled: Annotated[ - StrictBool | None, - Field( - description='Whether VPAID interactivity is supported. When true, the VAST tag may carry VPAID JS/Flash payloads.' - ), - ] = None - vpaid_version: VpaidVersion | None = None - simid_supported: Annotated[ - StrictBool | None, - Field( - description='Whether the seller accepts IAB SIMID through `` on a Linear VAST creative. SIMID is not a generic VAST extension and cannot be serialized under NonLinearAds; every `ctv_ad_experience` profile therefore forbids `true`.' - ), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - SchemaInt | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - min_width: Annotated[ - SchemaInt | None, - Field( - description='Minimum placement/player width in pixels. MediaFile rendition dimensions are declared in `media_file_requirements`.', - ge=1, - ), - ] = None - max_width: Annotated[ - SchemaInt | None, - Field( - description='Maximum placement/player width in pixels. MediaFile rendition dimensions are declared in `media_file_requirements`.', - ge=1, - ), - ] = None - min_height: Annotated[ - SchemaInt | None, - Field( - description='Minimum placement/player height in pixels. MediaFile rendition dimensions are declared in `media_file_requirements`.', - ge=1, - ), - ] = None - max_height: Annotated[ - SchemaInt | None, - Field( - description='Maximum placement/player height in pixels. MediaFile rendition dimensions are declared in `media_file_requirements`.', - ge=1, - ), - ] = None - creative_type: Annotated[ - CreativeType | None, - Field( - description='Required VAST creative class: `linear` (in-stream Linear), `nonlinear` (NonLinearAds overlay-class), or `either`. Supersedes `linear_required`; when both are present `creative_type` wins, and validators treat `linear_required: true` with no `creative_type` as `linear`.' - ), - ] = None - ctv_ad_experience: Annotated[ - ctv_ad_experience_1.CtvAdExperience | None, - Field( - description='CTV experience this option is eligible to serve. On video_vast only `pause`, `screensaver`, `overlay`, `squeezeback`, and `in_scene` are valid (`menu` routes to native_in_feed or sponsored_placement), and `creative_type` MUST be `nonlinear`. Because VAST places `` only under Linear ``, `simid_supported` MUST NOT be true on any of these NonLinear profiles. Per-experience floors: `overlay` and `squeezeback` require a 10s minimum duration; `in_scene` requires a 3s minimum brand-exposure duration and forbids interactivity (`vpaid_enabled` MUST NOT be true); `pause` has no duration floor and ends on viewer or device action.' - ), - ] = None - motion_level: Annotated[ - motion_level_1.CreativeMotionLevel | None, - Field(description='Accepted motion class for the rendered creative (AdCOM attrs 21-23).'), - ] = None - activation_methods: Annotated[ - list[activation_method.CreativeActivationMethod] | None, - Field( - description='Viewer activation mechanisms this option offers. Activations are engagement events, not impressions.' - ), - ] = None - linear_required: Annotated[ - StrictBool | None, - Field( - description='Whether the VAST creative must be linear (non-skippable in-stream). Superseded by `creative_type`; retained for pre-3.2 declarations.' - ), - ] = None - skippable_after_ms: Annotated[ - SchemaInt | None, - Field( - description='When skippable, the buyer-side skip threshold in milliseconds (e.g., 5000 for 5-second skippable pre-roll).', - ge=0, - ), - ] = None - max_wrapper_depth: Annotated[ - SchemaInt | None, Field(description='Maximum VAST wrapper redirect depth permitted.', ge=0) - ] = None - ssl_required: StrictBool | None = None diff --git a/src/adcp/types/generated_poc/governance/__init__.py b/src/adcp/types/generated_poc/governance/__init__.py deleted file mode 100644 index a01e99719..000000000 --- a/src/adcp/types/generated_poc/governance/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# generated by datamodel-codegen: -# filename: .schema_temp -# timestamp: 2026-06-01T00:32:59+00:00 diff --git a/src/adcp/types/generated_poc/governance/accepted_governance_agents.py b/src/adcp/types/generated_poc/governance/accepted_governance_agents.py deleted file mode 100644 index 07468ee00..000000000 --- a/src/adcp/types/generated_poc/governance/accepted_governance_agents.py +++ /dev/null @@ -1,81 +0,0 @@ -# generated by datamodel-codegen: -# filename: governance/accepted_governance_agents.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Any, Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, ConfigDict, Field, RootModel - - -class AnyOf1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['agent_url'] = 'agent_url' - agent_url: Annotated[ - AnyUrl, - Field( - description='Exact canonical agent endpoint without userinfo, query, or fragment. Redirect targets, DNS aliases, and URLs asserted by the candidate do not satisfy this matcher.' - ), - ] - - -class VerificationMode(StrEnum): - spec = 'spec' - live = 'live' - - -class AnyOf2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['verification'] = 'verification' - registry: Annotated[ - AnyUrl, - Field( - description='Seller-configured trusted verification registry without userinfo, query, or fragment. The candidate cannot supply or override this URL; fetches use the registry-resolution security contract.' - ), - ] - role: Annotated[ - str, - Field( - description="Role asserted by the trusted registry's verified record, never by candidate self-description.", - pattern='^[a-z][a-z0-9_-]*$', - ), - ] - adcp_version: Annotated[ - str, - Field( - description='Registry protocol version in canonical MAJOR.MINOR form.', - pattern='^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)$', - ), - ] - verification_modes: Annotated[list[VerificationMode], Field(min_length=1)] - max_age_seconds: Annotated[ - SchemaInt, - Field( - description='Maximum age of the registry evidence at binding time. Zero requires a fresh result. Evidence is pinned to the accepted binding; later registry drift does not silently revoke an existing binding.', - ge=0, - ), - ] - - -class AnyOf(RootModel[AnyOf1 | AnyOf2]): - root: Annotated[AnyOf1 | AnyOf2, Field(discriminator='kind')] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - - - -class AcceptedGovernanceAgents(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - any_of: Annotated[list[AnyOf], Field(min_length=1)] diff --git a/src/adcp/types/generated_poc/governance/attribute_definition.py b/src/adcp/types/generated_poc/governance/attribute_definition.py deleted file mode 100644 index 5946ab296..000000000 --- a/src/adcp/types/generated_poc/governance/attribute_definition.py +++ /dev/null @@ -1,72 +0,0 @@ -# generated by datamodel-codegen: -# filename: governance/attribute_definition.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - - -class RegulatoryBasi(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - name: Annotated[str, Field(description="Name of the regulation (e.g., 'GDPR Article 9(1)').")] - jurisdictions: Annotated[ - list[str] | None, - Field(description='ISO 3166-1 alpha-2 codes where this regulation applies.'), - ] = None - summary: Annotated[ - str, Field(description='How this regulation defines or restricts the attribute.') - ] - - -class AttributeDefinition(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - attribute_id: Annotated[ - str, - Field( - description='Unique identifier for this attribute. Used in plan.restricted_attributes, signal-definition.restricted_attributes, and data marketplace catalog entries.', - pattern='^[a-z][a-z0-9_]*$', - ), - ] - name: Annotated[str, Field(description="Human-readable name (e.g., 'Health Data').")] - description: Annotated[ - str, - Field( - description='What this attribute category covers. Defines the boundary — what is and is not included.' - ), - ] - regulatory_basis: Annotated[ - list[RegulatoryBasi] | None, - Field(description='Regulations that define or restrict this attribute category.'), - ] = None - includes: Annotated[ - list[str] | None, - Field( - description="Specific data types that fall within this category (e.g., for health_data: 'medical conditions', 'disability status', 'prescription history', 'inferred health from behavioral signals')." - ), - ] = None - excludes: Annotated[ - list[str] | None, - Field( - description='Data types that might seem related but are explicitly outside this category. Helps with boundary cases.' - ), - ] = None - signal_patterns: Annotated[ - list[str] | None, - Field( - description="Common signal naming or tagging patterns that indicate this attribute (e.g., 'health:', 'condition_', 'diagnosis_'). Data providers and governance agents use these as hints when signals lack explicit restricted_attributes declarations." - ), - ] = None - guidance: Annotated[ - str | None, - Field( - description='Implementation notes. Covers edge cases, inferred vs. declared data, and common pitfalls.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/governance/audience_constraints.py b/src/adcp/types/generated_poc/governance/audience_constraints.py deleted file mode 100644 index 7364e4f4e..000000000 --- a/src/adcp/types/generated_poc/governance/audience_constraints.py +++ /dev/null @@ -1,32 +0,0 @@ -# generated by datamodel-codegen: -# filename: governance/audience_constraints.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..core import audience_selector - - -class AudienceConstraints(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - include: Annotated[ - list[audience_selector.AudienceSelector] | None, - Field( - description="Desired audience criteria. The seller's targeting should align with these. Each criterion is evaluated independently — the combined targeting should satisfy at least one inclusion criterion.", - min_length=1, - ), - ] = None - exclude: Annotated[ - list[audience_selector.AudienceSelector] | None, - Field( - description="Excluded audience criteria. The seller's targeting must not overlap with these. Exclusions take precedence over inclusions. Used for protected groups, vulnerable communities, regulatory restrictions, or brand safety.", - min_length=1, - ), - ] = None diff --git a/src/adcp/types/generated_poc/governance/check_governance_request.py b/src/adcp/types/generated_poc/governance/check_governance_request.py deleted file mode 100644 index 4640f2b27..000000000 --- a/src/adcp/types/generated_poc/governance/check_governance_request.py +++ /dev/null @@ -1,599 +0,0 @@ -# generated by datamodel-codegen: -# filename: governance/check_governance_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictFloat, StringConstraints - -from ..core import brand_ref, business_entity, canonical_proposal -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core import planned_delivery as planned_delivery_1 -from ..core.attestation_reference import AttestationReference -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import governance_phase -from ..enums import purchase_type as purchase_type_1 - - -class CheckGovernanceRequest1(AdCPBaseModel): - pass - - -class ProposedCommitment(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - amount: Annotated[StrictFloat, Field(ge=0.0)] - currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] - - -class ExecutionCommitment(ProposedCommitment): - pass - - -class ReportingPeriod(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - start: AwareDatetime - end: AwareDatetime - - -class Pacing(StrEnum): - ahead = 'ahead' - on_track = 'on_track' - behind = 'behind' - - -class Baseline(StrEnum): - census = 'census' - platform = 'platform' - custom = 'custom' - - -class AudienceDistribution(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - baseline: Annotated[ - Baseline, - Field( - description="Population baseline used for index calculation. 'census': national census or equivalent population data. 'platform': the seller's active user base. 'custom': a custom baseline defined by the seller (describe in baseline_description)." - ), - ] - baseline_description: Annotated[ - str | None, - Field( - description="Description of the baseline when baseline is 'custom' (e.g., 'US adults 18+ with broadband access')." - ), - ] = None - indices: Annotated[ - dict[Annotated[str, StringConstraints(pattern=r'^[a-z_]+:.+$')], StrictFloat], - Field( - description="Audience index values for the current reporting period. Keys are seller-defined dimension:value strings (e.g., 'age:25-34', 'gender:female', 'income:high'). The protocol does not mandate a taxonomy — dimensions and value labels vary by seller. Values are index relative to the declared baseline (1.0 = at parity, >1.0 = over-indexed, <1.0 = under-indexed)." - ), - ] - cumulative_indices: Annotated[ - dict[Annotated[str, StringConstraints(pattern=r'^[a-z_]+:.+$')], StrictFloat] | None, - Field( - description='Cumulative audience index values since the governed action started. Same key format as indices (dimension:value). Use for detecting sustained bias drift that may not appear in a single reporting period.' - ), - ] = None - - -class DeliveryMetrics(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - statement_id: Annotated[ - str | None, - Field( - description='Stable seller-issued identifier for this immutable delivery statement.', - max_length=255, - min_length=1, - ), - ] = None - statement_digest: Annotated[ - str | None, - Field( - description='SHA-256 digest of RFC 8785 JCS over {seller_reference, delivery_metrics}, excluding statement_digest itself. The authenticated submission binds the seller to this digest.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - sequence: Annotated[ - SchemaInt | None, - Field( - description='Monotonically increasing sequence for this governed action. A statement ID or sequence cannot be reused with different content.', - ge=1, - ), - ] = None - issued_at: Annotated[ - AwareDatetime | None, Field(description='When the seller issued the canonical statement.') - ] = None - reporting_period: Annotated[ - ReportingPeriod, Field(description='Start and end timestamps for the reporting window.') - ] - spend: Annotated[ - StrictFloat | None, Field(description='Total spend during the reporting period.', ge=0.0) - ] = None - cumulative_spend: Annotated[ - StrictFloat | None, - Field(description='Total spend since the governed action started.', ge=0.0), - ] = None - currency: Annotated[ - str | None, - Field( - description='Currency of spend fields; must match the plan and planned delivery.', - pattern='^[A-Z]{3}$', - ), - ] = None - impressions: Annotated[ - SchemaInt | None, - Field(description='Impressions delivered during the reporting period.', ge=0), - ] = None - cumulative_impressions: Annotated[ - SchemaInt | None, - Field(description='Total impressions since the governed action started.', ge=0), - ] = None - geo_distribution: Annotated[ - dict[str, StrictFloat] | None, - Field( - description='Actual geographic distribution. Keys are ISO 3166-1 alpha-2 codes, values are percentages.' - ), - ] = None - channel_distribution: Annotated[ - dict[str, StrictFloat] | None, - Field( - description='Actual channel distribution. Keys are channel enum values, values are percentages.' - ), - ] = None - pacing: Annotated[ - Pacing | None, - Field( - description='Whether delivery is ahead of, on track with, or behind the planned pace.' - ), - ] = None - audience_distribution: Annotated[ - AudienceDistribution | None, - Field( - description='Actual audience composition during the reporting period. Enables mid-flight drift detection when actual delivery skews from planned audience targeting.' - ), - ] = None - - -class AudienceDistribution1(AudienceDistribution): - pass - - -class DeliveryMetrics2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - statement_id: Annotated[ - str | None, - Field( - description='Stable seller-issued identifier for this immutable delivery statement.', - max_length=255, - min_length=1, - ), - ] = None - statement_digest: Annotated[ - str | None, - Field( - description='SHA-256 digest of RFC 8785 JCS over {seller_reference, delivery_metrics}, excluding statement_digest itself. The authenticated submission binds the seller to this digest.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - sequence: Annotated[ - SchemaInt | None, - Field( - description='Monotonically increasing sequence for this governed action. A statement ID or sequence cannot be reused with different content.', - ge=1, - ), - ] = None - issued_at: Annotated[ - AwareDatetime | None, Field(description='When the seller issued the canonical statement.') - ] = None - reporting_period: Annotated[ - ReportingPeriod, Field(description='Start and end timestamps for the reporting window.') - ] - spend: Annotated[ - StrictFloat | None, Field(description='Total spend during the reporting period.', ge=0.0) - ] = None - cumulative_spend: Annotated[ - StrictFloat | None, - Field(description='Total spend since the governed action started.', ge=0.0), - ] = None - currency: Annotated[ - str | None, - Field( - description='Currency of spend fields; must match the plan and planned delivery.', - pattern='^[A-Z]{3}$', - ), - ] = None - impressions: Annotated[ - SchemaInt | None, - Field(description='Impressions delivered during the reporting period.', ge=0), - ] = None - cumulative_impressions: Annotated[ - SchemaInt | None, - Field(description='Total impressions since the governed action started.', ge=0), - ] = None - geo_distribution: Annotated[ - dict[str, StrictFloat] | None, - Field( - description='Actual geographic distribution. Keys are ISO 3166-1 alpha-2 codes, values are percentages.' - ), - ] = None - channel_distribution: Annotated[ - dict[str, StrictFloat] | None, - Field( - description='Actual channel distribution. Keys are channel enum values, values are percentages.' - ), - ] = None - pacing: Annotated[ - Pacing | None, - Field( - description='Whether delivery is ahead of, on track with, or behind the planned pace.' - ), - ] = None - audience_distribution: Annotated[ - AudienceDistribution1 | None, - Field( - description='Actual audience composition during the reporting period. Enables mid-flight drift detection when actual delivery skews from planned audience targeting.' - ), - ] = None - - -class Subject(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Literal['agent'] = 'agent' - resource_type: Literal['https://adcontextprotocol.org/claims/subjects/signal'] = 'https://adcontextprotocol.org/claims/subjects/signal' - agent_url: Annotated[ - AnyUrl, Field(description='Canonical HTTPS endpoint of the agent the claim concerns.') - ] - ext: ext_1.ExtensionObject | None = None - - -class Subject6(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Literal['resource'] = 'resource' - resource_type: Annotated[ - Literal['https://adcontextprotocol.org/claims/subjects/signal'], - Field( - description='Open, absolute URI naming the subject vocabulary, such as https://adcontextprotocol.org/claims/subjects/signal. AdCP does not maintain an exhaustive enum.' - ), - ] = 'https://adcontextprotocol.org/claims/subjects/signal' - namespace: Annotated[ - AnyUrl, - Field( - description='Absolute URI identifying the namespace in which id is unique. This may be an AdCP agent endpoint, a catalog origin, or a domain-specific namespace URI.' - ), - ] - id: Annotated[ - str, - Field( - description='Stable identifier for the subject within namespace. It MUST NOT be compared without resource_type and namespace.', - max_length=1024, - min_length=1, - ), - ] - content_digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 pin for the exact content or immutable snapshot identified by this resource subject. This is part of the complete typed subject identity and is distinct from AttestationReference.content_digest, which pins credential bytes.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - ext: ext_1.ExtensionObject | None = None - - -class Subject8(Subject): - type: Literal['agent'] = 'agent' - - -class Subject9(Subject6): - type: Literal['resource'] = 'resource' - - -class Subject4(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Literal['brand'] = 'brand' - resource_type: Literal['https://adcontextprotocol.org/claims/subjects/signal'] = 'https://adcontextprotocol.org/claims/subjects/signal' - brand: brand_ref.BrandReference - ext: ext_1.ExtensionObject | None = None - - -class RuntimeAttestation(AttestationReference): - subject: Annotated[ - Subject4 | Subject | Subject6 | None, - Field( - description='Typed identity of the entity or object an attestation credential is about. Brand and agent subjects reuse canonical AdCP identities. Other resources use an open, URI-namespaced resource_type plus an identifier whose namespace is explicit. Evaluators MUST compare the resolved credential subject to this complete typed identity, not to id alone.', - discriminator='type', - examples=[ - { - 'type': 'brand', - 'brand': {'domain': 'nova-brands.example', 'brand_id': 'nova_motors'}, - }, - { - 'type': 'resource', - 'resource_type': 'https://adcontextprotocol.org/claims/subjects/signal', - 'namespace': 'https://signals.meridian.example/adcp', - 'id': 'signal_urban_commuters', - }, - ], - title='Attestation Subject', - ), - ] = None - - -class CheckGovernanceRequest2(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - plan_id: Annotated[ - str | None, - Field( - description="Campaign governance plan identifier. Required on the initial intent or availability check, before a governance_context exists. Optional on subsequent checks: the governance agent derives the plan from its own signed governance_context. If both are present, the governance agent MUST reject the request when plan_id does not match the token's plan binding. Services MUST treat governance_context as authoritative and MUST NOT require a buyer to disclose plan_id. A plan is owned by the authenticated buyer principal that synchronized it; plan_id is an identifier, not an account credential." - ), - ] = None - caller: Annotated[ - AnyUrl, - Field( - description='Claimed URL of the agent making the request. The transport credential MUST resolve to an agent URL; the governance agent requires an exact match and uses only that resolved URL for authorization, audit, and signed context issuance. On intent checks the authenticated buyer must be the plan owner or hold an active delegation, while approved_sellers is evaluated against the target service that becomes the token audience. On execution checks the authenticated caller MUST equal that preserved audience. An unresolved body assertion never grants plan access or authorization.' - ), - ] - purchase_type: Annotated[ - purchase_type_1.PurchaseType | None, - Field( - description="The type of financial commitment being checked. Determines which budget allocation (if any) to validate against. Defaults to 'media_buy' when omitted." - ), - ] = purchase_type_1.PurchaseType.media_buy - target_agent: Annotated[ - AnyUrl | None, - Field( - description='Exact agent URL of the downstream service that will receive the governed task. Required on intent checks and copied byte-for-byte into the signed governance_context aud claim. This routing and authorization field is not part of payload: payload remains exactly the downstream task arguments. A consultation re-check MUST use the same target_agent.' - ), - ] = None - proposed_commitment: Annotated[ - ProposedCommitment | None, - Field( - description='Task-neutral monetary amount the intent would authorize. For update_media_buy and control_media_buy this is the buyer-computed positive incremental commitment, not the post-update total. For accept_proposal it is derived from the supplied proposal commercial_terms; for buy_products it is derived from the purchase payload. Amount 0 explicitly represents a verified no-cost action. The governance agent persists this value as authoritative check state.' - ), - ] = None - execution_commitment: Annotated[ - ExecutionCommitment | None, - Field( - description='Seller-computed positive incremental commitment for a MediaBuy execution check. The seller MUST derive this atomically from its authoritative proposal or current revision and the requested operation, and the governance agent MUST reject it when it exceeds the prior intent ceiling or uses another currency.' - ), - ] = None - tool: Annotated[ - str | None, - Field( - description="The AdCP tool being checked (e.g., 'create_media_buy', 'acquire_rights', 'activate_signal'). Present on intent checks (orchestrator). The governance agent uses the presence of tool+payload to identify an intent check." - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='The full downstream tool arguments exactly as they will be sent to target_agent. Present on intent checks. Governance routing metadata is carried by target_agent, never injected into this object. The governance agent can inspect any field to validate against the plan.' - ), - ] = None - proposal: Annotated[ - canonical_proposal.CanonicalProposal | None, - Field( - description='Exact committed proposal being authorized for accept_proposal. The governance agent verifies proposal.terms_digest against commercial_terms and binds that digest into its decision state; the downstream payload carries the same digest without repeating the terms.' - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description='Opaque authorization context from a prior approved check_governance response. Services pass it verbatim on execution and lifecycle checks; the issuing governance agent derives the plan and prior decision from the token. Intermediaries MUST NOT parse it for business logic. Governance agents MUST emit a compact JWS per the AdCP JWS profile.', - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - consultation_context: Annotated[ - str | None, - Field( - description='Opaque, non-authorizing handle returned with an intent conditions verdict. Pass it only when re-checking the adjusted intent so the governance agent can correlate negotiation attempts. The governance agent MUST resolve it under the authenticated principal and reject the re-check unless principal, caller, plan_id, tool, purchase_type, and target audience match the original conditions check. Services MUST NOT receive or accept this value as authorization.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]+$', - ), - ] = None - phase: Annotated[ - governance_phase.GovernancePhase | None, - Field( - description="The phase of an execution-shaped governed action. Ignored for intent-shaped tool+payload requests. 'purchase': initial commitment; 'modification': update to an existing commitment; 'delivery': periodic delivery reporting. Defaults to purchase if omitted. planned_delivery_1.media_buy_id is optional for purchase and required for modification/delivery." - ), - ] = governance_phase.GovernancePhase.purchase - planned_delivery: Annotated[ - planned_delivery_1.PlannedDelivery | None, - Field(description='What the seller will actually deliver. Present on execution checks.'), - ] = None - delivery_metrics: Annotated[ - DeliveryMetrics | None, - Field( - description="Seller-attributed canonical delivery statement. MUST be present for 'delivery' phase. The authenticated seller binds one immutable statement_id and digest to a monotonically increasing sequence; the buyer can later submit the copy it received or an independent observation through report_plan_outcome." - ), - ] = None - modification_summary: Annotated[ - str | None, - Field( - description="Human-readable summary of what changed. SHOULD be present for 'modification' phase.", - max_length=1000, - ), - ] = None - runtime_attestations: Annotated[ - list[RuntimeAttestation] | None, - Field( - description='Optional independently issued runtime evidence for an activate_signal intent check whose payload action is activate (or omitted, which defaults to activate). It MUST NOT be supplied for deactivate. Each item is the shared portable AttestationReference from the core #4529 contract; it carries no authoritative buyer-supplied decision or confidence. The governance agent MUST evaluate every item under adcp.attestations plus governance.runtime_attestations capability policy, preserve input order in response runtime_attestation_evaluations[], and reject off-policy issuers, resolvers, credential origins, and verifier nominations without network access. This field is per-check evidence outside the synced plan and therefore outside the plan_hash preimage. Other tools and purchase types cannot carry this field.', - max_length=10, - min_length=1, - ), - ] = None - invoice_recipient: Annotated[ - business_entity.BusinessEntity | None, - Field( - description='Invoice recipient from the purchase request. MUST be present when the tool payload includes invoice_recipient, so the governance agent can validate billing changes.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class RuntimeAttestation1(AttestationReference): - subject: Annotated[ - Subject4 | Subject8 | Subject9 | None, - Field( - description='Typed identity of the entity or object an attestation credential is about. Brand and agent subjects reuse canonical AdCP identities. Other resources use an open, URI-namespaced resource_type plus an identifier whose namespace is explicit. Evaluators MUST compare the resolved credential subject to this complete typed identity, not to id alone.', - discriminator='type', - examples=[ - { - 'type': 'brand', - 'brand': {'domain': 'nova-brands.example', 'brand_id': 'nova_motors'}, - }, - { - 'type': 'resource', - 'resource_type': 'https://adcontextprotocol.org/claims/subjects/signal', - 'namespace': 'https://signals.meridian.example/adcp', - 'id': 'signal_urban_commuters', - }, - ], - title='Attestation Subject', - ), - ] = None - - -class CheckGovernanceRequest3(CheckGovernanceRequest1, CheckGovernanceRequest2): - model_config = ConfigDict( - extra='allow', - ) - plan_id: Annotated[ - str | None, - Field( - description="Campaign governance plan identifier. Required on the initial intent or availability check, before a governance_context exists. Optional on subsequent checks: the governance agent derives the plan from its own signed governance_context. If both are present, the governance agent MUST reject the request when plan_id does not match the token's plan binding. Services MUST treat governance_context as authoritative and MUST NOT require a buyer to disclose plan_id. A plan is owned by the authenticated buyer principal that synchronized it; plan_id is an identifier, not an account credential." - ), - ] = None - caller: Annotated[ - AnyUrl, - Field( - description='Claimed URL of the agent making the request. The transport credential MUST resolve to an agent URL; the governance agent requires an exact match and uses only that resolved URL for authorization, audit, and signed context issuance. On intent checks the authenticated buyer must be the plan owner or hold an active delegation, while approved_sellers is evaluated against the target service that becomes the token audience. On execution checks the authenticated caller MUST equal that preserved audience. An unresolved body assertion never grants plan access or authorization.' - ), - ] - purchase_type: Annotated[ - purchase_type_1.PurchaseType | None, - Field( - description="The type of financial commitment being checked. Determines which budget allocation (if any) to validate against. Defaults to 'media_buy' when omitted." - ), - ] = purchase_type_1.PurchaseType.media_buy - target_agent: Annotated[ - AnyUrl | None, - Field( - description='Exact agent URL of the downstream service that will receive the governed task. Required on intent checks and copied byte-for-byte into the signed governance_context aud claim. This routing and authorization field is not part of payload: payload remains exactly the downstream task arguments. A consultation re-check MUST use the same target_agent.' - ), - ] = None - proposed_commitment: Annotated[ - ProposedCommitment | None, - Field( - description='Task-neutral monetary amount the intent would authorize. For update_media_buy and control_media_buy this is the buyer-computed positive incremental commitment, not the post-update total. For accept_proposal it is derived from the supplied proposal commercial_terms; for buy_products it is derived from the purchase payload. Amount 0 explicitly represents a verified no-cost action. The governance agent persists this value as authoritative check state.' - ), - ] = None - execution_commitment: Annotated[ - ExecutionCommitment | None, - Field( - description='Seller-computed positive incremental commitment for a MediaBuy execution check. The seller MUST derive this atomically from its authoritative proposal or current revision and the requested operation, and the governance agent MUST reject it when it exceeds the prior intent ceiling or uses another currency.' - ), - ] = None - tool: Annotated[ - str | None, - Field( - description="The AdCP tool being checked (e.g., 'create_media_buy', 'acquire_rights', 'activate_signal'). Present on intent checks (orchestrator). The governance agent uses the presence of tool+payload to identify an intent check." - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='The full downstream tool arguments exactly as they will be sent to target_agent. Present on intent checks. Governance routing metadata is carried by target_agent, never injected into this object. The governance agent can inspect any field to validate against the plan.' - ), - ] = None - proposal: Annotated[ - canonical_proposal.CanonicalProposal | None, - Field( - description='Exact committed proposal being authorized for accept_proposal. The governance agent verifies proposal.terms_digest against commercial_terms and binds that digest into its decision state; the downstream payload carries the same digest without repeating the terms.' - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description='Opaque authorization context from a prior approved check_governance response. Services pass it verbatim on execution and lifecycle checks; the issuing governance agent derives the plan and prior decision from the token. Intermediaries MUST NOT parse it for business logic. Governance agents MUST emit a compact JWS per the AdCP JWS profile.', - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - consultation_context: Annotated[ - str | None, - Field( - description='Opaque, non-authorizing handle returned with an intent conditions verdict. Pass it only when re-checking the adjusted intent so the governance agent can correlate negotiation attempts. The governance agent MUST resolve it under the authenticated principal and reject the re-check unless principal, caller, plan_id, tool, purchase_type, and target audience match the original conditions check. Services MUST NOT receive or accept this value as authorization.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]+$', - ), - ] = None - phase: Annotated[ - governance_phase.GovernancePhase | None, - Field( - description="The phase of an execution-shaped governed action. Ignored for intent-shaped tool+payload requests. 'purchase': initial commitment; 'modification': update to an existing commitment; 'delivery': periodic delivery reporting. Defaults to purchase if omitted. planned_delivery_1.media_buy_id is optional for purchase and required for modification/delivery." - ), - ] = governance_phase.GovernancePhase.purchase - planned_delivery: Annotated[ - planned_delivery_1.PlannedDelivery | None, - Field(description='What the seller will actually deliver. Present on execution checks.'), - ] = None - delivery_metrics: Annotated[ - DeliveryMetrics2 | None, - Field( - description="Seller-attributed canonical delivery statement. MUST be present for 'delivery' phase. The authenticated seller binds one immutable statement_id and digest to a monotonically increasing sequence; the buyer can later submit the copy it received or an independent observation through report_plan_outcome." - ), - ] = None - modification_summary: Annotated[ - str | None, - Field( - description="Human-readable summary of what changed. SHOULD be present for 'modification' phase.", - max_length=1000, - ), - ] = None - runtime_attestations: Annotated[ - list[RuntimeAttestation1] | None, - Field( - description='Optional independently issued runtime evidence for an activate_signal intent check whose payload action is activate (or omitted, which defaults to activate). It MUST NOT be supplied for deactivate. Each item is the shared portable AttestationReference from the core #4529 contract; it carries no authoritative buyer-supplied decision or confidence. The governance agent MUST evaluate every item under adcp.attestations plus governance.runtime_attestations capability policy, preserve input order in response runtime_attestation_evaluations[], and reject off-policy issuers, resolvers, credential origins, and verifier nominations without network access. This field is per-check evidence outside the synced plan and therefore outside the plan_hash preimage. Other tools and purchase types cannot carry this field.', - max_length=10, - min_length=1, - ), - ] = None - invoice_recipient: Annotated[ - business_entity.BusinessEntity | None, - Field( - description='Invoice recipient from the purchase request. MUST be present when the tool payload includes invoice_recipient, so the governance agent can validate billing changes.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class CheckGovernanceRequest(CheckGovernanceRequest3): - 'Universal governance check for campaign actions. The governance agent infers the check type from the fields present: tool+payload = intent check (proposed, orchestrator-side); planned_delivery or delivery_metrics with governance_context = execution or lifecycle check (committed, service-side). Proposal acceptance supplies the immutable proposal separately so governance can inspect its typed commercial terms while payload remains the exact downstream arguments. MediaBuy controls use buyer-proposed and seller-computed positive-delta ceilings. The first check is addressed by plan_id. Subsequent service-side checks use the opaque governance_context as the authoritative plan binding.' diff --git a/src/adcp/types/generated_poc/governance/check_governance_response.py b/src/adcp/types/generated_poc/governance/check_governance_response.py deleted file mode 100644 index e1bd753eb..000000000 --- a/src/adcp/types/generated_poc/governance/check_governance_response.py +++ /dev/null @@ -1,287 +0,0 @@ -# generated by datamodel-codegen: -# filename: governance/check_governance_response.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat, model_validator - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.attestation_evaluation import AttestationEvaluation -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import escalation_severity, governance_decision, governance_mode - - -class CheckType(StrEnum): - intent = 'intent' - execution = 'execution' - - -class Condition(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - field: Annotated[ - str, - Field( - description='Dot-path rooted at the complete check_governance request arguments (for example payload.total_budget.amount or proposed_commitment.amount). Conditions are not valid for committed execution checks.' - ), - ] - required_value: Annotated[ - Any | None, - Field( - description='The value the field must have for approval. When present, the condition is machine-actionable. When absent, the condition is advisory.' - ), - ] = None - reason: Annotated[str, Field(description='Why this condition is required.')] - - -class ReportingPeriod(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - start: AwareDatetime - end: AwareDatetime - - -class CanonicalPayload(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - seller_reference: str - delivery_metrics: dict[str, Any] - - -class DeliveryStatement(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - statement_id: str - statement_digest: Annotated[str, Field(pattern='^sha256:[a-f0-9]{64}$')] - sequence: Annotated[SchemaInt, Field(ge=1)] - issued_at: AwareDatetime - seller_reference: str - reporting_period: ReportingPeriod - cumulative_spend: Annotated[StrictFloat, Field(ge=0.0)] - currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] - canonical_payload: Annotated[ - CanonicalPayload, - Field( - description='Exact retained digest-bearing payload. To verify statement_digest, apply RFC 8785 JCS after removing delivery_metrics.statement_digest.' - ), - ] - - -class ActionBinding(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - action_type: Annotated[ - Literal['https://adcontextprotocol.org/actions/governance-check'], - Field( - description='Absolute URI naming the action vocabulary, such as an AdCP governance-check or audience-evidence snapshot type.' - ), - ] = 'https://adcontextprotocol.org/actions/governance-check' - action_id: Annotated[ - str, - Field( - description="Stable identifier of the consuming action within the domain consumer's namespace.", - max_length=1024, - min_length=1, - ), - ] - action_digest: Annotated[ - str | None, - Field( - description='SHA-256 digest of the domain-defined canonical action preimage.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - - -class Finding(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - category_id: Annotated[ - str, - Field( - description="Validation category that flagged the issue (e.g., 'budget_compliance', 'regulatory_compliance', 'brand_safety'). This is an **agent-internal** taxonomy: the string is defined by the governance agent's own policy model and is not constrained to any protocol-level enum. Sellers and buyers MUST NOT pattern-match `category_id` values against a fixed list — treat them as opaque labels with human-readable significance for audit but no machine-level contract. See the Campaign Governance specification for how an agent composes internal specialist review behind one endpoint." - ), - ] - policy_id: Annotated[ - str | None, - Field( - description="ID of the policy that triggered this finding. May reference a registry policy (with source: registry) or a bespoke inline policy (with source: inline). Bespoke policy_ids are unique within their authoring container; use source_plan_id when findings aggregate across multiple plans (e.g., portfolio evaluations). When the violation traces to a producer-tagged surface (feature-requirement, creative-feature-result, or validation-result feature) carrying policy_id, governance agents SHOULD echo that policy_id here for end-to-end traceability, and MUST NOT invent a policy_id that wasn't present on the originating surface. See /docs/governance/policy-attribution." - ), - ] = None - source_plan_id: Annotated[ - str | None, - Field( - description="For portfolio or aggregated evaluations where findings draw on bespoke policies from multiple member plans: identifies the plan whose policy triggered this finding. Omit when the finding's policy_id is unambiguous within the response context (e.g., single-plan check_governance)." - ), - ] = None - severity: escalation_severity.EscalationSeverity - explanation: Annotated[str, Field(description='Human-readable description of the issue.')] - details: Annotated[ - dict[str, Any] | None, Field(description='Structured details for programmatic consumption.') - ] = None - confidence: Annotated[ - StrictFloat | None, - Field( - description="Confidence score (0-1) in this finding. Distinguishes 'this definitely violates the policy' (0.95) from 'this might violate depending on how audience segments resolve' (0.6). When absent, the finding is presented without a confidence qualifier.", - ge=0.0, - le=1.0, - ), - ] = None - uncertainty_reason: Annotated[ - str | None, - Field( - description="Explanation of why confidence is below 1.0 (e.g., 'Targeting includes regions that partially overlap jurisdiction boundaries'). Present when confidence is below a governance-agent-defined threshold." - ), - ] = None - attestation_reference_digest: Annotated[ - str | None, - Field( - description='When this finding relies on or reports a runtime attestation evaluation, the reference_digest of the exact AttestationReference in runtime_attestation_evaluations[]. Findings MUST NOT copy credential bodies or treat a presenter assertion as verified evidence.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - - -class RuntimeAttestationEvaluation(AttestationEvaluation): - action_binding: Annotated[ - ActionBinding, - Field( - description='Optional binding to the consuming action or readback. Domain consumers that rely on an evaluation MUST carry either an action id or an action digest so the result cannot be transplanted to an unrelated decision.' - ), - ] - - -class CheckGovernanceResponse(AdcpVersionEnvelope): - @model_validator(mode='before') - @classmethod - def _status_to_verdict(cls, data: Any) -> Any: - if isinstance(data, dict) and 'verdict' not in data and 'status' in data: - data = dict(data) - data['verdict'] = data['status'] - return data - - model_config = ConfigDict( - extra='allow', - ) - check_id: Annotated[ - str, - Field( - description='Unique identifier for this governance check record. Use in report_plan_outcome to link outcomes to the check that authorized them.' - ), - ] - verdict: Annotated[ - governance_decision.GovernanceDecision, - Field( - description='Governance verdict: approved | denied | conditions. Renamed from `status` in 3.1 to free the top-level `status` key for the envelope task-status (TaskStatus) under MCP flat-on-the-wire serialization. The enum values are unchanged; only the property name moved.' - ), - ] - check_type: Annotated[ - CheckType | None, - Field( - description='Check shape that produced the verdict. Required for the cross-role governance_enforcement contract. Its presence selects the modern verdict-specific response rules; its absence selects the deprecated legacy 3.x compatibility shape. Intent checks may return conditions; execution checks are binary approved or denied.' - ), - ] = None - plan_id: Annotated[ - str | None, - Field( - description='Plan identifier echoed on an initial plan-addressed check. Optional on continuation checks addressed by governance_context; services do not need this value and MUST treat the token binding as authoritative.' - ), - ] = None - explanation: Annotated[ - str, Field(description='Human-readable explanation of the governance decision.') - ] - findings: Annotated[ - list[Finding] | None, - Field( - description="Specific issues found during the governance check. Present when verdict is 'denied' or 'conditions'. MAY also be present on 'approved' for informational findings (e.g., budget approaching limit)." - ), - ] = None - conditions: Annotated[ - list[Condition] | None, - Field( - description="Intent-phase counterproposal. Present only when verdict is 'conditions'. It does not authorize execution and MUST NOT be returned for execution or lifecycle checks. Each field path is rooted at the complete check_governance request arguments, so both payload.* and proposed_commitment.* can be addressed. After applying conditions, the caller MUST re-call check_governance with the adjusted parameters and receive approved before proceeding." - ), - ] = None - consultation_context: Annotated[ - str | None, - Field( - description='Opaque negotiation handle present only with modern conditions responses. It carries no authorization and MUST NOT be sent to a downstream service. The governance agent MUST bind it server-side to the authenticated principal, caller, plan, tool, purchase type, and target audience, and reject a re-check if any binding changes. The buyer returns it only on the adjusted intent re-check.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]+$', - ), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field( - description="When this approval expires. In the cross-role shape, present only when verdict is 'approved'. Deprecated legacy conditions responses may also carry it for 3.x compatibility. The caller must act before this time or re-call check_governance. A lapsed approval is no approval." - ), - ] = None - next_check: Annotated[ - AwareDatetime | None, - Field( - description='When the seller should next call check_governance with delivery metrics. Present when the governance agent expects ongoing delivery reporting.' - ), - ] = None - delivery_statement: Annotated[ - DeliveryStatement | None, - Field( - description='Canonical seller-attributed delivery statement retained by governance. Present on delivery execution checks. The buyer binds any later observation to this exact statement through report_plan_outcome.' - ), - ] = None - categories_evaluated: Annotated[ - list[str] | None, - Field( - description="Governance categories evaluated during this check. Each value is an **agent-internal** label (e.g., `budget_authority`, `regulatory_compliance`, or any internal-reviewer key the agent's policy model defines) — not a protocol-level enum. Since one governance agent per account composes all specialist review behind its single endpoint, `categories_evaluated` is how that internal decomposition surfaces to auditors. Consumers MUST treat values as opaque labels for display and audit, not as a machine-level contract." - ), - ] = None - policies_evaluated: Annotated[ - list[str] | None, - Field( - description="Policy IDs evaluated during this check. Includes registry policy IDs (resolved via the policy registry) and any inline `policy_id`s declared in the plan's `custom_policies`." - ), - ] = None - mode: Annotated[ - governance_mode.GovernanceMode | None, - Field( - description='Governance enforcement mode active when this check was evaluated. Allows counterparties, regulators, and auditors to distinguish whether a finding blocked execution (enforce) or was logged silently (audit).' - ), - ] = None - runtime_attestation_evaluations: Annotated[ - list[RuntimeAttestationEvaluation] | None, - Field( - description="Evaluator-of-record results for request runtime_attestations[], in the same order and with exactly one result per presentation. Each result is the shared AttestationEvaluation and MUST bind to this response's check_id through action_binding.action_type = https://adcontextprotocol.org/actions/governance-check and action_binding.action_id = check_id. The signed governance_context MUST bind the same reference_digest/outcome pairs; large evidence stays in the audit log rather than the token.", - max_length=10, - min_length=1, - ), - ] = None - runtime_attestation_binding_digest: Annotated[ - str | None, - Field( - description='SHA-256 of RFC 8785 JCS({ evaluations: runtime_attestation_evaluations, findings: attestation_bound_findings }), where attestation_bound_findings is the response findings[] subset carrying attestation_reference_digest, preserved in response order. Required whenever runtime_attestation_evaluations is present. The governance_context JWS carries this exact value as runtime_attestation_binding_digest; get_plan_audit_logs retains ordered {reference, evaluation} pairs so auditors can first recompute every reference_digest and then prove which evaluations and findings the signed decision relied on.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description='Opaque authorization context for this governed action. Present only when verdict is approved; denied and conditions responses MUST NOT carry it. The buyer attaches it to the protocol envelope when sending the governed request. The service persists and forwards it on subsequent execution and lifecycle checks without requiring plan_id.\n\nGovernance agents MUST emit a compact JWS per the AdCP JWS profile. Verifiers validate the standard authorization claims but MUST NOT interpret embedded governance state for business logic. The issuing governance agent uses the token to recover its internal plan and decision state.', - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/governance/get_plan_audit_logs_request.py b/src/adcp/types/generated_poc/governance/get_plan_audit_logs_request.py deleted file mode 100644 index caee40acd..000000000 --- a/src/adcp/types/generated_poc/governance/get_plan_audit_logs_request.py +++ /dev/null @@ -1,66 +0,0 @@ -# generated by datamodel-codegen: -# filename: governance/get_plan_audit_logs_request.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import ConfigDict, Field, StrictBool, model_validator - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import purchase_type - - -class GetPlanAuditLogsRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - plan_ids: Annotated[ - list[str] | None, - Field( - description='Plan IDs to retrieve. For a single plan, pass a one-element array. Plans uniquely scope account and operator; do not include a separate `account` field — the governance agent resolves account from each plan. Including `account` is rejected by `additionalProperties: false`.', - min_length=1, - ), - ] = None - portfolio_plan_ids: Annotated[ - list[str] | None, - Field( - description='Portfolio plan IDs. The governance agent expands each to its member_plan_ids and returns combined audit data.', - min_length=1, - ), - ] = None - governance_contexts: Annotated[ - list[str] | None, - Field( - description='Filter audit entries by governance context. Returns only checks and outcomes that share these governance contexts, enabling lifecycle tracing across purchase types.', - min_length=1, - ), - ] = None - purchase_types: Annotated[ - list[purchase_type.PurchaseType] | None, - Field( - description="Filter audit entries by purchase type. Returns only checks and outcomes matching these purchase types (e.g., ['rights_license'] to see all rights activity).", - min_length=1, - ), - ] = None - include_entries: Annotated[ - StrictBool | None, Field(description='Include the full audit trail. Default: false.') - ] = False - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - @model_validator(mode='after') - def _require_schema_required_group(self) -> GetPlanAuditLogsRequest: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('plan_ids',), ('portfolio_plan_ids',), ('governance_contexts',),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'GetPlanAuditLogsRequest requires at least one of these field groups: plan_ids | portfolio_plan_ids | governance_contexts' - ) diff --git a/src/adcp/types/generated_poc/governance/get_plan_audit_logs_response.py b/src/adcp/types/generated_poc/governance/get_plan_audit_logs_response.py deleted file mode 100644 index 190e79c51..000000000 --- a/src/adcp/types/generated_poc/governance/get_plan_audit_logs_response.py +++ /dev/null @@ -1,727 +0,0 @@ -# generated by datamodel-codegen: -# filename: governance/get_plan_audit_logs_response.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat - -from ..core import attestation_evaluation, attestation_reference -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import escalation_severity, governance_decision, governance_mode, outcome_type -from ..enums import purchase_type as purchase_type_1 -from . import reported_outcome_error - - -class Status(StrEnum): - active = 'active' - suspended = 'suspended' - completed = 'completed' - - -class AccountingMode(StrEnum): - gross_commitment = 'gross_commitment' - verified_net_cost = 'verified_net_cost' - - -class Budget(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - authorized: Annotated[ - StrictFloat | None, Field(description='Total authorized budget from the plan.') - ] = None - accounting_mode: AccountingMode | None = None - committed: Annotated[ - StrictFloat | None, - Field( - description='Legacy 3.x alias for net_committed after verified headroom-restoring adjustments.' - ), - ] = None - gross_committed: Annotated[ - StrictFloat | None, - Field( - description='Total authoritative commitment recorded from completed outcomes before adjustments.' - ), - ] = None - adjustments_reported: Annotated[ - StrictFloat | None, - Field( - description='Sum of all append-only adjustment amounts, including audit-only refunds, credits, and makegoods.' - ), - ] = None - adjustments_verified: Annotated[ - StrictFloat | None, - Field( - description='Sum of buyer-accepted economic reductions in plan currency; accepted makegoods contribute zero.' - ), - ] = None - net_cost: Annotated[ - StrictFloat | None, - Field( - description='Gross commitment minus verified economic reductions, independent of whether accounting_mode restores headroom.' - ), - ] = None - headroom_restored: Annotated[ - StrictFloat | None, - Field( - description='Sum of verified adjustments eligible to restore reusable headroom under accounting_mode.' - ), - ] = None - net_committed: Annotated[ - StrictFloat | None, - Field(description='Current plan obligation after verified headroom-restoring adjustments.'), - ] = None - ledger_committed: Annotated[ - StrictFloat | None, - Field(description='Current amount charged against plan headroom under accounting_mode.'), - ] = None - remaining: Annotated[StrictFloat | None, Field(description='Authorized minus committed.')] = ( - None - ) - utilization_pct: Annotated[ - StrictFloat | None, Field(description='Committed as a percentage of authorized.') - ] = None - - -class ChannelAllocation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - committed: Annotated[ - StrictFloat | None, Field(description='Budget committed to this channel.') - ] = None - pct: Annotated[ - StrictFloat | None, Field(description="Channel's share of the authorized total budget.") - ] = None - - -class Statuses(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - approved: SchemaInt | None = None - denied: SchemaInt | None = None - conditions: SchemaInt | None = None - human_reviewed: Annotated[ - SchemaInt | None, - Field( - description='Supplementary count of checks that went through internal human review. These checks are also counted in approved or denied.' - ), - ] = None - - -class Escalation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - check_id: Annotated[str, Field(description='The escalated governance check.')] - reason: Annotated[str, Field(description='Why it was escalated.')] - resolution: Annotated[ - str | None, - Field(description="How it was resolved (e.g., 'approved_by_human', 'rejected_by_human')."), - ] = None - resolved_at: Annotated[ - AwareDatetime | None, Field(description='ISO 8601 resolution timestamp.') - ] = None - - -class EscalationRateTrend(StrEnum): - increasing = 'increasing' - stable = 'stable' - declining = 'declining' - - -class Thresholds(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - escalation_rate_max: Annotated[ - StrictFloat | None, - Field( - description='Maximum acceptable escalation rate. A rate above this suggests policy miscalibration.', - ge=0.0, - le=1.0, - ), - ] = None - escalation_rate_min: Annotated[ - StrictFloat | None, - Field( - description='Minimum acceptable escalation rate. A rate below this may indicate eroding oversight.', - ge=0.0, - le=1.0, - ), - ] = None - auto_approval_rate_max: Annotated[ - StrictFloat | None, - Field(description='Maximum acceptable auto-approval rate.', ge=0.0, le=1.0), - ] = None - human_override_rate_max: Annotated[ - StrictFloat | None, - Field( - description="Maximum acceptable human override rate. A high rate suggests the governance agent's recommendations are poorly calibrated.", - ge=0.0, - le=1.0, - ), - ] = None - - -class DriftMetrics(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - escalation_rate: Annotated[ - StrictFloat | None, - Field(description='Fraction of checks that resulted in escalation.', ge=0.0, le=1.0), - ] = None - escalation_rate_trend: Annotated[ - EscalationRateTrend | None, - Field(description="Direction of escalation rate over the plan's lifetime."), - ] = None - auto_approval_rate: Annotated[ - StrictFloat | None, - Field( - description='Fraction of checks approved without human intervention.', ge=0.0, le=1.0 - ), - ] = None - human_override_rate: Annotated[ - StrictFloat | None, - Field( - description="Fraction of escalations where the human overrode the governance agent's recommendation.", - ge=0.0, - le=1.0, - ), - ] = None - mean_confidence: Annotated[ - StrictFloat | None, - Field( - description='Average confidence score across all findings. Present when findings include confidence scores.', - ge=0.0, - le=1.0, - ), - ] = None - thresholds: Annotated[ - Thresholds | None, - Field( - description='Organization-defined thresholds for drift metrics. When a metric crosses its threshold, the governance agent SHOULD include a finding on the next check. Set by the organization in governance agent configuration, echoed here for visibility.' - ), - ] = None - - -class Summary(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - checks_performed: Annotated[ - SchemaInt | None, Field(description='Total governance checks performed.') - ] = None - outcomes_reported: Annotated[ - SchemaInt | None, Field(description='Total outcomes reported.') - ] = None - adjustments_reported: Annotated[ - SchemaInt | None, Field(description='Total append-only adjustment records reported.') - ] = None - adjustments_verified: Annotated[ - SchemaInt | None, - Field( - description='Count of adjustment records accepted by the plan owner, including makegoods.' - ), - ] = None - statuses: Annotated[ - Statuses | None, Field(description='Count of each governance check status.') - ] = None - findings_count: Annotated[ - SchemaInt | None, Field(description='Total findings across all checks and outcomes.') - ] = None - escalations: Annotated[ - list[Escalation] | None, Field(description='All escalations and their resolutions.') - ] = None - drift_metrics: Annotated[ - DriftMetrics | None, - Field( - description='Aggregate governance metrics for detecting oversight drift. A declining escalation rate may indicate well-calibrated governance or eroding human oversight -- surfacing the trend lets the organization make that judgment.' - ), - ] = None - - -class Type(StrEnum): - check = 'check' - outcome = 'outcome' - adjustment = 'adjustment' - - -class CheckType(StrEnum): - intent = 'intent' - execution = 'execution' - - -class Finding(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - category_id: str - policy_id: str | None = None - source_plan_id: Annotated[ - str | None, - Field( - description='Plan whose policy triggered this finding when the audit entry aggregates findings across plans.' - ), - ] = None - severity: escalation_severity.EscalationSeverity - explanation: str - details: Annotated[ - dict[str, Any] | None, - Field( - description='Structured finding details retained exactly as emitted by check_governance.' - ), - ] = None - confidence: Annotated[StrictFloat | None, Field(ge=0.0, le=1.0)] = None - uncertainty_reason: Annotated[ - str | None, - Field( - description='Reason the finding carries less than full confidence, retained exactly as emitted by check_governance.' - ), - ] = None - attestation_reference_digest: Annotated[ - str | None, - Field( - description='reference_digest of the runtime attestation evaluation this finding relied on.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - - -class ReportingPeriod(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - start: AwareDatetime - end: AwareDatetime - - -class CanonicalPayload(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - seller_reference: str - delivery_metrics: dict[str, Any] - - -class DeliveryStatement(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - statement_id: str - statement_digest: Annotated[str, Field(pattern='^sha256:[a-f0-9]{64}$')] - sequence: Annotated[SchemaInt, Field(ge=1)] - issued_at: AwareDatetime - seller_reference: str - cumulative_spend: Annotated[StrictFloat, Field(ge=0.0)] - currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] - reporting_period: ReportingPeriod - canonical_payload: Annotated[ - CanonicalPayload, - Field( - description='Exact retained payload used to verify statement_digest after removing delivery_metrics.statement_digest.' - ), - ] - - -class AdjustmentType(StrEnum): - decommitment = 'decommitment' - refund = 'refund' - credit = 'credit' - makegood = 'makegood' - - -class Amount(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - amount: Annotated[StrictFloat, Field(gt=0.0)] - currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] - - -class Source(StrEnum): - seller_statement_copy = 'seller_statement_copy' - buyer_measurement = 'buyer_measurement' - - -class Delivery(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - observation_id: Annotated[ - str | None, - Field( - description='Stable buyer-issued identifier for this observation.', - max_length=255, - min_length=1, - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Whether the buyer is forwarding the seller statement it received or reporting an independently measured observation. A forwarded seller statement is not independent evidence.' - ), - ] = None - observed_at: AwareDatetime | None = None - seller_statement_id: Annotated[ - str | None, - Field( - description='Seller statement identifier as received by the buyer. Required for seller_statement_copy.' - ), - ] = None - seller_statement_digest: Annotated[ - str | None, - Field( - description='Digest received with the seller statement. Governance compares it to the seller-authenticated canonical digest.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - reporting_period: Annotated[ - ReportingPeriod | None, - Field(description='Start and end timestamps for the reporting window.'), - ] = None - cumulative_spend: Annotated[ - StrictFloat | None, - Field(description='Buyer-attributed cumulative spend for the governed action.', ge=0.0), - ] = None - currency: Annotated[str | None, Field(pattern='^[A-Z]{3}$')] = None - period_closed: Annotated[ - StrictBool | None, - Field( - description='Buyer-declared operational closure of this governance reporting period. When true, governance freezes the period after recording this observation. An unresolved mismatch becomes closed_unresolved and no longer blocks later governance. Closure is not final billing or commercial settlement.' - ), - ] = False - impressions: Annotated[SchemaInt | None, Field(ge=0)] = None - - -class DeliveryReconciliationStatus(StrEnum): - consistent = 'consistent' - measurement_variance = 'measurement_variance' - disputed = 'disputed' - unmatched = 'unmatched' - closed_unresolved = 'closed_unresolved' - - -class DeliveryPeriodState(StrEnum): - open = 'open' - closed = 'closed' - - -class AdjustmentState(StrEnum): - reported = 'reported' - verified = 'verified' - disputed = 'disputed' - - -class EvidenceType(StrEnum): - decommitment_agreement = 'decommitment_agreement' - refund_settlement = 'refund_settlement' - credit_note = 'credit_note' - makegood_agreement = 'makegood_agreement' - - -class Evidence(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - evidence_id: str - evidence_type: EvidenceType - digest: Annotated[str, Field(pattern='^sha256:[a-f0-9]{64}$')] - issued_at: AwareDatetime - - -class DeliveryReportingPeriod(ReportingPeriod): - pass - - -class GovernedAction(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - governance_context: Annotated[ - str, Field(description="Governance context correlating this action's lifecycle.") - ] - purchase_type: Annotated[ - purchase_type_1.PurchaseType, Field(description='Type of financial commitment.') - ] - status: Annotated[Status, Field(description='Action status.')] - committed: Annotated[ - StrictFloat, Field(description='Gross authoritative commitment recorded for this action.') - ] - adjustments_reported: Annotated[ - StrictFloat | None, - Field(description='Sum of all append-only adjustment amounts for this action.'), - ] = None - adjustments_verified: Annotated[ - StrictFloat | None, Field(description='Buyer-accepted economic reductions for this action.') - ] = None - net_cost: Annotated[ - StrictFloat | None, - Field(description='Gross action commitment minus verified economic reductions.'), - ] = None - headroom_restored: Annotated[ - StrictFloat | None, - Field( - description='Verified adjustments eligible to restore headroom under the plan accounting mode.' - ), - ] = None - net_committed: Annotated[ - StrictFloat | None, - Field(description='Current ledger obligation for this action after restored headroom.'), - ] = None - seller_reported_spend: Annotated[StrictFloat | None, Field(ge=0.0)] = None - buyer_observed_spend: Annotated[StrictFloat | None, Field(ge=0.0)] = None - delivery_reporting_period: Annotated[ - DeliveryReportingPeriod | None, - Field(description='Current seller statement period summarized for this governed action.'), - ] = None - conservative_exposure: Annotated[ - StrictFloat | None, - Field( - description='Greater of seller-reported and buyer-observed spend while evidence is unresolved.', - ge=0.0, - ), - ] = None - delivery_reconciliation_status: DeliveryReconciliationStatus | None = None - delivery_period_state: Annotated[ - DeliveryPeriodState | None, - Field( - description='Whether the current governance reporting period remains actionable. Closure is not final billing.' - ), - ] = None - check_count: Annotated[ - SchemaInt, Field(description='Number of governance checks performed for this action.') - ] - seller_reference: Annotated[ - str | None, - Field( - description="The seller's identifier for the resource (e.g., media_buy_id, rights_grant_id). Present when reported via report_plan_outcome." - ), - ] = None - - -class RuntimeAttestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - reference: attestation_reference.AttestationReference - evaluation: attestation_evaluation.AttestationEvaluation - - -class Entry(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - id: Annotated[str, Field(description='Entry identifier.')] - type: Annotated[Type, Field(description='Entry type.')] - timestamp: Annotated[AwareDatetime, Field(description='ISO 8601 timestamp.')] - plan_id: Annotated[ - str | None, - Field( - description='Plan this entry belongs to. Present when querying multiple plans or a portfolio.' - ), - ] = None - caller: Annotated[ - AnyUrl | None, - Field( - description='URL of the agent that made the request. Resolved from the credentials used on the governance callback.' - ), - ] = None - tool: Annotated[str | None, Field(description='The AdCP tool (present for check entries).')] = ( - None - ) - verdict: Annotated[ - governance_decision.GovernanceDecision | None, - Field( - description='Governance verdict (present for check entries). Renamed from `status` in 3.1 alongside check-governance-response for vocabulary consistency.' - ), - ] = None - check_type: Annotated[ - CheckType | None, - Field( - description='Whether the check was an intent check (orchestrator) or execution check (seller). Inferred from the fields present on the original check request. Present for check entries.' - ), - ] = None - mode: Annotated[ - governance_mode.GovernanceMode | None, - Field( - description="Governance mode active at the moment this specific check was evaluated. Governance agents SHOULD populate this field on check entries, recording the mode from their runtime configuration at the moment check_governance was processed — not derived from a plan field. This is a per-check value: if the operator changes mode between checks for the same governed action, each entry records the mode active for that entry. A future `governed_actions[].mode` field would describe the action's current mode, which may differ from the most recent entry's `mode` if the plan has since been re-synced. Absent for outcome entries and for pre-3.1 governance agents that do not surface mode on audit responses." - ), - ] = None - explanation: Annotated[ - str | None, - Field( - description='Human-readable explanation of the governance decision (present for check entries).' - ), - ] = None - policies_evaluated: Annotated[ - list[str] | None, - Field( - description="Policy IDs evaluated during this check. Includes registry policy IDs (resolved via the policy registry) and any inline `policy_id`s declared in the plan's `custom_policies`. Present for check entries." - ), - ] = None - categories_evaluated: Annotated[ - list[str] | None, - Field( - description="Governance categories evaluated (e.g., 'budget_authority', 'regulatory_compliance'). Present for check entries." - ), - ] = None - findings: Annotated[ - list[Finding] | None, - Field( - description='Findings from this check or outcome. Same structure as check_governance response findings.' - ), - ] = None - delivery_statement: Annotated[ - DeliveryStatement | None, - Field(description='Canonical seller statement retained on a delivery check entry.'), - ] = None - outcome: Annotated[ - outcome_type.OutcomeType | None, - Field(description='Outcome type (present for outcome entries).'), - ] = None - error: Annotated[ - reported_outcome_error.ReportedOutcomeError | None, - Field( - description='Buyer-attributed error retained from a failed report_plan_outcome call. This is a copy or classification supplied by the reporter, not authenticated seller evidence.' - ), - ] = None - outcome_id: Annotated[ - str | None, Field(description='Source completed outcome (present for adjustment entries).') - ] = None - seller_adjustment_id: Annotated[ - str | None, - Field( - description='Seller-issued source adjustment identifier (present for adjustment entries).' - ), - ] = None - adjustment_type: Annotated[ - AdjustmentType | None, - Field(description='Commercial adjustment type (present for adjustment entries).'), - ] = None - amount: Annotated[ - Amount | None, - Field(description='Adjustment amount and currency (present for adjustment entries).'), - ] = None - headroom_restored: Annotated[ - StrictFloat | None, - Field( - description='Current-obligation headroom restored by this adjustment. Zero for audit-only types.', - ge=0.0, - ), - ] = None - reason: Annotated[str | None, Field(description='Seller-supplied adjustment reason.')] = None - effective_at: Annotated[ - AwareDatetime | None, Field(description="When the seller's adjustment became effective.") - ] = None - committed_budget: Annotated[ - StrictFloat | None, - Field( - description='Governance-authorized budget reserved (present for completed outcome entries).' - ), - ] = None - reported_committed_budget: Annotated[ - StrictFloat | None, - Field( - description='Caller-reported seller amount retained for reconciliation. Never ledger authority.', - ge=0.0, - ), - ] = None - seller_reference: Annotated[ - str | None, Field(description='Seller resource identifier retained on an outcome entry.') - ] = None - delivery: Annotated[ - Delivery | None, - Field( - description='Buyer-attributed observation retained as audit evidence on a delivery outcome. It never overwrites the seller statement.' - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description='Governance context for this entry (present for check and outcome entries).' - ), - ] = None - plan_hash: Annotated[ - str | None, - Field( - description='Audit-layer binding to the plan revision this attestation was evaluated over — base64url_no_pad(SHA-256(JCS(plan_payload))) per Plan binding and audit in the campaign-governance specification. Present on check entries. Auditors and buyer-side compliance verify by recomputing over the retained plan revision and byte-comparing the decoded 32-byte digests.', - pattern='^[A-Za-z0-9_-]{43}$', - ), - ] = None - runtime_attestations: Annotated[ - list[RuntimeAttestation] | None, - Field( - description='Portable attestation presentations and their evaluator-of-record results retained as ordered pairs for this check entry. Auditors recompute each evaluation.reference_digest from JCS(reference), then recompute runtime_attestation_binding_digest from the ordered evaluations and attestation-bound findings.', - max_length=10, - min_length=1, - ), - ] = None - runtime_attestation_binding_digest: Annotated[ - str | None, - Field( - description="Binding digest copied from the check response and signed governance_context. Auditors recompute it from this entry's ordered runtime_attestations[].evaluation values plus findings carrying attestation_reference_digest after verifying each paired reference digest.", - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - purchase_type: Annotated[ - purchase_type_1.PurchaseType | None, Field(description='Purchase type for this entry.') - ] = None - outcome_status: Annotated[ - str | None, Field(description='Outcome status (present for outcome entries).') - ] = None - delivery_reconciliation_status: DeliveryReconciliationStatus | None = None - delivery_period_state: Annotated[ - DeliveryPeriodState | None, - Field(description='Operational governance-window state for this delivery observation.'), - ] = None - adjustment_state: AdjustmentState | None = None - verified_amount: Annotated[StrictFloat | None, Field(ge=0.0)] = None - evidence: Evidence | None = None - reviewed_by: AnyUrl | None = None - reviewed_at: AwareDatetime | None = None - review_reason: str | None = None - - -class Plan(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - plan_id: Annotated[str, Field(description='Plan identifier.')] - plan_version: Annotated[SchemaInt, Field(description='Current plan version.')] - status: Annotated[Status, Field(description='Plan lifecycle status.')] - budget: Annotated[Budget, Field(description='Budget state.')] - channel_allocation: Annotated[ - dict[str, ChannelAllocation] | None, - Field(description='Current channel mix. Keyed by channel ID.'), - ] = None - summary: Annotated[Summary, Field(description='Aggregate validation and outcome statistics.')] - entries: Annotated[ - list[Entry] | None, - Field(description='Ordered audit trail. Only present when include_entries is true.'), - ] = None - governed_actions: Annotated[ - list[GovernedAction], - Field( - description="Per-action breakdown joined by the governance agent's stable opaque action binding. governance_context is the original intent context exposed for correlation; lifecycle tokens may differ because each approval is freshly signed." - ), - ] - - -class GetPlanAuditLogsResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - plans: Annotated[list[Plan], Field(description='Audit data for each requested plan.')] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/governance/policy_category_definition.py b/src/adcp/types/generated_poc/governance/policy_category_definition.py deleted file mode 100644 index 248d8c0ac..000000000 --- a/src/adcp/types/generated_poc/governance/policy_category_definition.py +++ /dev/null @@ -1,105 +0,0 @@ -# generated by datamodel-codegen: -# filename: governance/policy_category_definition.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictBool - -from ..enums import restricted_attribute - - -class Facet(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - facet_id: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] - name: Annotated[str, Field(min_length=1)] - description: Annotated[str, Field(min_length=1)] - - -class RegulatoryFramework(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - name: Annotated[ - str, Field(description="Name of the regulation or standard (e.g., 'US COPPA').") - ] - jurisdictions: Annotated[ - list[str] | None, - Field(description='ISO 3166-1 alpha-2 codes where this framework applies.'), - ] = None - summary: Annotated[ - str, Field(description='Brief summary of what the framework requires or prohibits.') - ] - policy_ids: Annotated[ - list[str] | None, Field(description='Registry policy IDs that implement this framework.') - ] = None - - -class PolicyCategoryDefinition(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - category_id: Annotated[ - str, - Field( - description='Unique identifier for this category. Used in plan.policy_categories, signal-definition.policy_categories, and policy-entry.policy_categories.', - pattern='^[a-z][a-z0-9_]*$', - ), - ] - name: Annotated[ - str, Field(description="Human-readable name (e.g., 'Children-Directed Content').") - ] - description: Annotated[ - str, - Field( - description='What this category covers. Defines the boundary — what campaigns or data fall under this category.' - ), - ] - facets: Annotated[ - list[Facet] | None, - Field( - description='Registry-defined distinctions within the category that materially affect policy applicability or seller acceptance. Facets refine a category without creating a new top-level policy category.', - min_length=1, - ), - ] = None - regulatory_frameworks: Annotated[ - list[RegulatoryFramework] | None, - Field( - description='Key regulations and standards grouped under this category. Governance agents use this to resolve specific policies from the registry.' - ), - ] = None - restricted_attributes: Annotated[ - list[restricted_attribute.RestrictedAttribute] | None, - Field( - description="Restricted attribute categories that regulations in this category prohibit for targeting. Governance agents enforce these when the category is active on a plan — if a plan declares policy_categories: ['fair_housing'], the governance agent restricts targeting on these attributes." - ), - ] = None - requires_human_review: Annotated[ - StrictBool | None, - Field( - description='When true, any plan declaring this category MUST set plan.human_review_required = true. Use for regulatory regimes that mandate human oversight under GDPR Art 22 or EU AI Act Annex III — fair_housing, fair_lending, fair_employment, pharmaceutical_advertising, and similar high-risk categories. Category-level setting applies to all policies and plans referencing it; policies can override on policy-entry.requires_human_review. Effective immediately regardless of individual policy `effective_date` fields.' - ), - ] = False - industries: Annotated[ - list[str] | None, - Field( - description="Industries where this category commonly applies (e.g., 'pharmaceutical' for age_restricted). Governance agents MAY suggest relevant categories when a plan's brand industry matches but no policy_categories are declared." - ), - ] = None - guidance: Annotated[ - str | None, - Field( - description='Implementation notes for governance agents. Edge cases, disambiguation, and common pitfalls.' - ), - ] = None - related_categories: Annotated[ - list[str] | None, - Field( - description="Categories that frequently co-occur (e.g., 'children_directed' often appears with 'age_restricted')." - ), - ] = None diff --git a/src/adcp/types/generated_poc/governance/policy_entry.py b/src/adcp/types/generated_poc/governance/policy_entry.py deleted file mode 100644 index ebf6dff1f..000000000 --- a/src/adcp/types/generated_poc/governance/policy_entry.py +++ /dev/null @@ -1,194 +0,0 @@ -# generated by datamodel-codegen: -# filename: governance/policy_entry.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from datetime import date -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, StrictBool - -from ..core import ext as ext_1 -from ..enums import channels as channels_1 -from ..enums import governance_domain, policy_category, policy_enforcement -from ..media_buy import acceptance_policy_profile - - -class Source(StrEnum): - registry = 'registry' - inline = 'inline' - - -class Issuer(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description='Lowercase registrable or organizational domain used as the stable issuer identifier.', - pattern='^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z]{2,63}$', - ), - ] - name: Annotated[str | None, Field(min_length=1)] = None - - -class Exemplar(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scenario: Annotated[ - str, - Field(description='A concrete scenario describing an advertising action or configuration.'), - ] - explanation: Annotated[str, Field(description='Why this scenario passes or fails the policy.')] - - -class Exemplars(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - pass_: Annotated[ - list[Exemplar] | None, - Field(alias='pass', description='Scenarios that comply with this policy.'), - ] = None - fail: Annotated[ - list[Exemplar] | None, Field(description='Scenarios that violate this policy.') - ] = None - - -class PolicyEntry(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - policy_id: Annotated[ - str, - Field( - description='Unique identifier for this policy. Registry-published ids are canonical (e.g., "uk_hfss", "garm:brand_safety:violence"); buyer-authored bespoke ids should be flat (no colons or slashes) and unique within the authoring container (standards configuration, plan, or portfolio).' - ), - ] - source: Annotated[ - Source | None, - Field( - description="Origin of this policy. 'registry' = published to the shared AdCP policy registry with full regulatory metadata. 'inline' = authored bespoke for a specific standards configuration, plan, or portfolio. Defaults to 'inline'. Governance agents MUST set 'registry' when publishing to the registry. Within AdCP *task* payloads (every `$ref` to this schema in a request or response), the field is always 'inline' — registry entries are served by the policy registry API, not embedded in task traffic. The x-entity annotation on `policy_id` assumes the task-payload invariant; if a future task schema adopts registry-publishing, split the annotation accordingly (see issue #2685)." - ), - ] = Source.inline - version: Annotated[ - str | None, - Field( - description='Semver version string (e.g., "1.0.0"). Incremented when policy content changes. Optional for inline bespoke policies — defaults to "1.0.0". SHOULD be provided for registry-published policies.' - ), - ] = None - name: Annotated[ - str | None, - Field( - description='Human-readable name (e.g., "UK HFSS Restrictions"). Optional for inline bespoke policies — servers MAY default to policy_id.' - ), - ] = None - description: Annotated[ - str | None, Field(description='Brief summary of what this policy covers.', max_length=500) - ] = None - category: Annotated[ - policy_category.PolicyCategory | None, - Field( - description='The nature of the obligation: regulation (legal requirement) or standard (best practice). Optional for inline bespoke policies — defaults to "standard".' - ), - ] = None - enforcement: Annotated[ - policy_enforcement.PolicyEnforcementLevel, - Field( - description='How governance agents treat violations. Regulations are typically "must"; standards are typically "should".' - ), - ] - requires_human_review: Annotated[ - StrictBool | None, - Field( - description='When true, plans subject to this policy MUST set plan.human_review_required = true. Use for policies that mandate human oversight of decisions affecting data subjects — e.g., GDPR Article 22 (solely automated decisions with legal or similarly significant effects) and EU AI Act Annex III high-risk categories (credit, insurance pricing, recruitment, housing allocation). Governance agents MUST escalate any plan action whose resolved policies include requires_human_review: true. Unlike `enforcement`, this flag applies as soon as the policy is resolved — it is NOT gated by `effective_date`. Art 22 GDPR and similar foundational obligations may predate an AI-Act-specific effective date; the human-review requirement fires regardless.' - ), - ] = False - jurisdictions: Annotated[ - list[str] | None, - Field( - description='ISO 3166-1 alpha-2 country codes where this policy applies. Empty array means the policy is not jurisdiction-specific.' - ), - ] = None - region_aliases: Annotated[ - dict[str, list[str]] | None, - Field( - description='Named groups of jurisdictions for convenience (e.g., {"EU": ["AT","BE","BG",...]}). Governance agents expand aliases when matching against a plan\'s target jurisdictions.' - ), - ] = None - policy_categories: Annotated[ - list[str] | None, - Field( - description='Regulatory categories this policy belongs to (e.g., ["children_directed", "age_restricted"]). Used for automatic matching against a campaign plan\'s declared policy_categories. A single policy can belong to multiple categories.' - ), - ] = None - channels: Annotated[ - list[channels_1.MediaChannel] | None, - Field( - description='Advertising channels this policy applies to. If omitted or null, the policy applies to all channels.' - ), - ] = None - governance_domains: Annotated[ - list[governance_domain.GovernanceDomain] | None, - Field( - description='Governance sub-domains this policy applies to. Determines which types of governance agents can declare registry:{policy_id} features. For example, a policy with domains ["creative", "property"] can be declared as a feature by both creative and property governance agents.' - ), - ] = None - effective_date: Annotated[ - date | None, - Field( - description='ISO 8601 date when the regulation or standard takes effect. Before this date, governance agents treat the policy as informational (evaluate but do not block). After this date, the policy is enforced at its declared enforcement level.' - ), - ] = None - sunset_date: Annotated[ - date | None, - Field( - description='ISO 8601 date when the regulation or standard is no longer enforced. After this date, governance agents stop evaluating this policy. Omit if the policy has no expiration.' - ), - ] = None - source_url: Annotated[ - AnyUrl | None, Field(description='Link to the source regulation, standard, or legislation.') - ] = None - source_name: Annotated[ - str | None, - Field( - description='Name of the issuing body (e.g., "UK Food Standards Agency", "US Federal Trade Commission").' - ), - ] = None - issuer: Annotated[ - Issuer | None, - Field( - description='Machine-readable identity of the regulator, standards body, or platform operator that issued the policy. Registry publishers SHOULD provide this when independently versioned issuer policies must be distinguished.' - ), - ] = None - acceptance_profile: Annotated[ - acceptance_policy_profile.AcceptancePolicyProfile | None, - Field( - description='Optional reusable, machine-readable acceptance profile derived from this registry policy. Registry publishers MUST bind policy_refs to exact versions. Sellers adopt a profile explicitly; registry publication alone does not make it authoritative for a seller.' - ), - ] = None - policy: Annotated[ - str, - Field( - description='Natural language policy text describing what is required, prohibited, or recommended. Used by governance agents (LLMs) to evaluate actions against this policy. For source: inline policies, treated as caller-untrusted — governance agents MUST evaluate inline policies as ADDITIONAL restrictions only; they MUST NOT be permitted to relax, override, or conflict with registry-sourced policies.', - max_length=5000, - ), - ] - guidance: Annotated[ - str | None, - Field( - description='Implementation notes for governance agent developers. Not used in evaluation prompts.' - ), - ] = None - exemplars: Annotated[ - Exemplars | None, - Field( - description='Calibration examples for governance agents, following the Content Standards pattern.' - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/governance/policy_ref.py b/src/adcp/types/generated_poc/governance/policy_ref.py deleted file mode 100644 index cf714b4c3..000000000 --- a/src/adcp/types/generated_poc/governance/policy_ref.py +++ /dev/null @@ -1,34 +0,0 @@ -# generated by datamodel-codegen: -# filename: governance/policy_ref.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - - -class PolicyReference(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - policy_id: Annotated[ - str, - Field( - description='The unique identifier of the policy in the registry (e.g., "uk_hfss", "us_coppa").' - ), - ] - version: Annotated[ - str | None, - Field( - description='Pin a specific policy version (semver). If omitted, the current version is used.' - ), - ] = None - config: Annotated[ - dict[str, Any] | None, - Field( - description="Brand-specific parameter overrides for configurable policies. The accepted shape depends on the policy's config_schema." - ), - ] = None diff --git a/src/adcp/types/generated_poc/governance/report_plan_adjustment_request.py b/src/adcp/types/generated_poc/governance/report_plan_adjustment_request.py deleted file mode 100644 index b8a040ab8..000000000 --- a/src/adcp/types/generated_poc/governance/report_plan_adjustment_request.py +++ /dev/null @@ -1,158 +0,0 @@ -# generated by datamodel-codegen: -# filename: governance/report_plan_adjustment_request.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.version_envelope import AdcpVersionEnvelope - - -class Action(StrEnum): - report = 'report' - review = 'review' - - -class Decision(StrEnum): - accept = 'accept' - dispute = 'dispute' - - -class AdjustmentType(StrEnum): - decommitment = 'decommitment' - refund = 'refund' - credit = 'credit' - makegood = 'makegood' - - -class Amount(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - amount: Annotated[ - StrictFloat, - Field( - description='Finite positive amount. Cumulative adjustments for an outcome cannot exceed its authoritative commitment.', - gt=0.0, - ), - ] - currency: Annotated[ - str, - Field( - description='Three-letter uppercase currency code matching the plan currency.', - pattern='^[A-Z]{3}$', - ), - ] - - -class EvidenceType(StrEnum): - decommitment_agreement = 'decommitment_agreement' - refund_settlement = 'refund_settlement' - credit_note = 'credit_note' - makegood_agreement = 'makegood_agreement' - - -class Evidence(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - evidence_id: Annotated[ - str, - Field( - description="Immutable identifier in the authenticated seller's namespace. Reuse with different adjustment evidence is a conflict.", - max_length=255, - min_length=1, - ), - ] - evidence_type: EvidenceType - digest: Annotated[str, Field(pattern='^sha256:[a-f0-9]{64}$')] - issued_at: AwareDatetime - - -class ReportPlanAdjustmentRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - action: Annotated[ - Action, - Field( - description='report is seller-authenticated and creates a non-authoritative record; review is plan-owner-authenticated and accepts or disputes it.' - ), - ] - plan_id: Annotated[str, Field(description='Plan containing the source outcome.', min_length=1)] - outcome_id: Annotated[ - str | None, - Field( - description='Completed outcome whose authoritative commitment is being adjusted.', - min_length=1, - ), - ] = None - adjustment_id: Annotated[ - str | None, Field(description='Adjustment to accept or dispute. Required for review.') - ] = None - decision: Annotated[ - Decision | None, - Field( - description='Buyer review decision. Acceptance is blocked while delivery evidence for the governed action is disputed in an open governance period; a historical closed_unresolved period is audit evidence, not a billing determination.' - ), - ] = None - seller_reference: Annotated[ - str | None, - Field( - description='Seller resource identifier. Must exactly match the reference retained on the source outcome.', - max_length=255, - min_length=1, - ), - ] = None - seller_adjustment_id: Annotated[ - str | None, - Field( - description="Stable identifier of the adjustment in the authenticated seller's system.", - max_length=255, - min_length=1, - ), - ] = None - adjustment_type: Annotated[ - AdjustmentType | None, - Field( - description='Commercial meaning. Verified decommitments restore headroom in every mode; verified refunds and credits restore it only in verified_net_cost mode; makegoods never restore cash headroom.' - ), - ] = None - amount: Annotated[ - Amount | None, Field(description='Positive adjustment amount in the plan currency.') - ] = None - reason: Annotated[ - str | None, - Field( - description='Human-readable reason retained in the audit trail.', - max_length=1000, - min_length=1, - ), - ] = None - effective_at: Annotated[ - AwareDatetime | None, Field(description="When the seller's adjustment became effective.") - ] = None - evidence: Annotated[ - Evidence | None, - Field( - description='Integrity-bound commercial source record supplied by the seller. Its evidence_type must correspond to adjustment_type.' - ), - ] = None - idempotency_key: Annotated[ - str, - Field( - description='Caller-generated retry key. Exact replays return the original report or review; reuse with another payload is rejected.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/governance/report_plan_adjustment_response.py b/src/adcp/types/generated_poc/governance/report_plan_adjustment_response.py deleted file mode 100644 index e6d3fe8cc..000000000 --- a/src/adcp/types/generated_poc/governance/report_plan_adjustment_response.py +++ /dev/null @@ -1,89 +0,0 @@ -# generated by datamodel-codegen: -# filename: governance/report_plan_adjustment_response.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictBool, StrictFloat - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.version_envelope import AdcpVersionEnvelope - - -class AdjustmentState(StrEnum): - reported = 'reported' - verified = 'verified' - disputed = 'disputed' - - -class AdjustmentType(StrEnum): - decommitment = 'decommitment' - refund = 'refund' - credit = 'credit' - makegood = 'makegood' - - -class Amount(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - amount: Annotated[StrictFloat, Field(gt=0.0)] - currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] - - -class AccountingMode(StrEnum): - gross_commitment = 'gross_commitment' - verified_net_cost = 'verified_net_cost' - - -class PlanSummary(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - accounting_mode: AccountingMode - gross_committed: Annotated[StrictFloat, Field(ge=0.0)] - adjustments_reported: Annotated[StrictFloat, Field(ge=0.0)] - adjustments_verified: Annotated[StrictFloat, Field(ge=0.0)] - net_cost: Annotated[StrictFloat, Field(ge=0.0)] - headroom_restored: Annotated[StrictFloat, Field(ge=0.0)] - ledger_committed: Annotated[StrictFloat, Field(ge=0.0)] - net_committed: Annotated[StrictFloat, Field(ge=0.0)] - budget_remaining: StrictFloat - - -class ReportPlanAdjustmentResponse(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - adjustment_id: Annotated[ - str, Field(description='Governance-agent identifier for the adjustment record.') - ] - adjustment_state: Annotated[ - AdjustmentState, - Field( - description='reported has seller evidence but no buyer decision; verified was accepted by the plan owner; disputed was rejected by the plan owner. Only verified records can affect net cost or headroom.' - ), - ] - adjustment_type: AdjustmentType - amount: Amount - headroom_restored: Annotated[ - StrictFloat, - Field( - description='Amount by which current ledger commitment was reduced under the plan accounting mode.', - ge=0.0, - ), - ] - plan_summary: PlanSummary - replayed: Annotated[ - StrictBool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. `replayed` appears only when the request actually resolved through the idempotency cache. Pure reads may ignore an optional `idempotency_key`; when a seller voluntarily caches keyed reads, those responses use the same replay indicator and full cache contract." - ), - ] = False - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/governance/report_plan_outcome_request.py b/src/adcp/types/generated_poc/governance/report_plan_outcome_request.py deleted file mode 100644 index 0ada0773b..000000000 --- a/src/adcp/types/generated_poc/governance/report_plan_outcome_request.py +++ /dev/null @@ -1,182 +0,0 @@ -# generated by datamodel-codegen: -# filename: governance/report_plan_outcome_request.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core import planned_delivery as planned_delivery_1 -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import outcome_type -from ..enums import purchase_type as purchase_type_1 -from . import reported_outcome_error - - -class Package(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - budget: Annotated[StrictFloat | None, Field(ge=0.0)] = None - - -class Source(StrEnum): - seller_statement_copy = 'seller_statement_copy' - buyer_measurement = 'buyer_measurement' - - -class ReportingPeriod(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - start: AwareDatetime - end: AwareDatetime - - -class Delivery(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - observation_id: Annotated[ - str, - Field( - description='Stable buyer-issued identifier for this observation.', - max_length=255, - min_length=1, - ), - ] - source: Annotated[ - Source, - Field( - description='Whether the buyer is forwarding the seller statement it received or reporting an independently measured observation. A forwarded seller statement is not independent evidence.' - ), - ] - observed_at: AwareDatetime - seller_statement_id: Annotated[ - str | None, - Field( - description='Seller statement identifier as received by the buyer. Required for seller_statement_copy.' - ), - ] = None - seller_statement_digest: Annotated[ - str | None, - Field( - description='Digest received with the seller statement. Governance compares it to the seller-authenticated canonical digest.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - reporting_period: Annotated[ - ReportingPeriod, Field(description='Start and end timestamps for the reporting window.') - ] - cumulative_spend: Annotated[ - StrictFloat, - Field(description='Buyer-attributed cumulative spend for the governed action.', ge=0.0), - ] - currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] - period_closed: Annotated[ - StrictBool | None, - Field( - description='Buyer-declared operational closure of this governance reporting period. When true, governance freezes the period after recording this observation. An unresolved mismatch becomes closed_unresolved and no longer blocks later governance. Closure is not final billing or commercial settlement. Only the authenticated plan owner may set this to true; the governance agent MUST reject it from any other authenticated reporter.' - ), - ] = False - impressions: Annotated[SchemaInt | None, Field(ge=0)] = None - - -class SellerResponse(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - seller_reference: Annotated[ - str | None, - Field( - description="The seller's identifier for the created resource (e.g., media_buy_id, rights_grant_id, deployment_id). Not interpreted by the governance agent — included in audit logs for human-readable traceability alongside the opaque governance_context.", - max_length=255, - ), - ] = None - committed_budget: Annotated[ - StrictFloat | None, - Field( - description='Buyer-reported seller amount retained for reconciliation and audit. It is never ledger authority: the governance agent derives the reserved commitment from its own approved intent check, or from the matching purchase execution check when one exists. A report above that authorized amount is rejected; a lower report does not restore headroom.', - ge=0.0, - ), - ] = None - packages: Annotated[ - list[Package] | None, - Field(description='Confirmed packages with actual budget and targeting.'), - ] = None - planned_delivery: Annotated[ - planned_delivery_1.PlannedDelivery | None, - Field( - description="What the seller said it will deliver. When seller-side governance is not configured, this is the governance agent's only view of the seller's delivery parameters." - ), - ] = None - creative_deadline: Annotated[ - AwareDatetime | None, Field(description='ISO 8601 deadline for creative submission.') - ] = None - - -class ReportPlanOutcomeRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - plan_id: Annotated[ - str, - Field( - description='The plan this outcome is for. The plan is owned by the authenticated buyer that synchronized it; plan_id is an identifier, not an account credential. Completed and failed settlements inherit their commercial binding from the exact approved check tuple.' - ), - ] - check_id: Annotated[ - str | None, - Field( - description='The check_id from check_governance. Required for completed and failed outcomes and for buyer delivery observations. A delivery observation names the exact seller delivery check whose canonical statement is being compared.' - ), - ] = None - idempotency_key: Annotated[ - str, - Field( - description='Buyer-generated unique key for this outcome report. An identical retry returns the cached response without another settlement; reuse with a different canonical payload returns IDEMPOTENCY_CONFLICT. Use a fresh UUID v4 for each distinct report.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - purchase_type: Annotated[ - purchase_type_1.PurchaseType | None, - Field( - description="The type of financial commitment this outcome is for. Must equal the original approved intent's purchase_type. Determines which budget allocation (if any) to charge against. Defaults to 'media_buy' when omitted." - ), - ] = purchase_type_1.PurchaseType.media_buy - outcome: Annotated[outcome_type.OutcomeType, Field(description='Outcome type.')] - seller_response: Annotated[ - SellerResponse | None, - Field(description="The seller's full response. Required when outcome is 'completed'."), - ] = None - delivery: Annotated[ - Delivery | None, - Field( - description='Buyer-attributed observation compared with the canonical seller delivery statement identified by check_id. This evidence never overwrites seller evidence or creates a second commitment. A conflict produces an explicit disputed reconciliation state while the operational period is open; the plan owner may close it without asserting final billing truth.' - ), - ] = None - error: Annotated[ - reported_outcome_error.ReportedOutcomeError | None, - Field( - description='Buyer-attributed error associated with a failed seller interaction. Required when outcome is failed; classification_source=seller_response_copy preserves what the buyer received without claiming seller-attested provenance.' - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description='Opaque governance context from the check_governance response. Required with check_id for completed and failed outcomes and buyer delivery observations.', - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/governance/report_plan_outcome_response.py b/src/adcp/types/generated_poc/governance/report_plan_outcome_response.py deleted file mode 100644 index be309cc36..000000000 --- a/src/adcp/types/generated_poc/governance/report_plan_outcome_response.py +++ /dev/null @@ -1,118 +0,0 @@ -# generated by datamodel-codegen: -# filename: governance/report_plan_outcome_response.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, model_validator, StrictBool, StrictFloat - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import escalation_severity - - -class OutcomeState(StrEnum): - accepted = 'accepted' - findings = 'findings' - - -class DeliveryReconciliationStatus(StrEnum): - consistent = 'consistent' - measurement_variance = 'measurement_variance' - disputed = 'disputed' - unmatched = 'unmatched' - closed_unresolved = 'closed_unresolved' - - -class DeliveryPeriodState(StrEnum): - open = 'open' - closed = 'closed' - - -class Finding(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - category_id: Annotated[str, Field(description='Which validation category flagged the issue.')] - severity: Annotated[ - escalation_severity.EscalationSeverity, Field(description='Finding severity.') - ] - explanation: Annotated[str, Field(description='Human-readable description of the issue.')] - details: Annotated[ - dict[str, Any] | None, Field(description='Structured details for programmatic consumption.') - ] = None - - -class PlanSummary(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - total_committed: Annotated[ - StrictFloat | None, - Field(description='Total budget committed across all campaigns in the plan.'), - ] = None - budget_remaining: Annotated[ - StrictFloat | None, Field(description='Authorized budget minus total committed.') - ] = None - - -class ReportPlanOutcomeResponse(AdcpVersionEnvelope): - @model_validator(mode='before') - @classmethod - def _status_to_outcome_state(cls, data: Any) -> Any: - if isinstance(data, dict) and 'outcome_state' not in data and 'status' in data: - data = dict(data) - data['outcome_state'] = data['status'] - return data - - model_config = ConfigDict( - extra='allow', - ) - outcome_id: Annotated[str, Field(description='Unique identifier for this outcome record.')] - outcome_state: Annotated[ - OutcomeState, - Field( - description="Outcome state. 'accepted' means state updated with no issues. 'findings' means issues were detected. Renamed from `status` in 3.1 to free the top-level `status` key for the envelope task-status (TaskStatus) under MCP flat-on-the-wire serialization." - ), - ] - committed_budget: Annotated[ - StrictFloat | None, - Field( - description="Budget committed from this outcome. Present for 'completed' and 'failed' outcomes." - ), - ] = None - delivery_reconciliation_status: Annotated[ - DeliveryReconciliationStatus | None, - Field( - description='Comparison state between the buyer-attributed observation and canonical seller statement. Present for delivery outcomes. A disputed state blocks adjustment verification while the period is open. measurement_variance records a buyer-measured cumulative_spend that differs from the seller statement; it never blocks verification — the higher amount bounds conservative exposure and decommitments instead. closed_unresolved preserves the discrepancy after operational closure without claiming a final billing result.' - ), - ] = None - delivery_period_state: Annotated[ - DeliveryPeriodState | None, - Field( - description='Operational state of the governance reporting period. Closure freezes governance evidence for that period but is not billing settlement.' - ), - ] = None - findings: Annotated[ - list[Finding] | None, - Field(description="Issues detected. Present only when outcome_state is 'findings'."), - ] = None - plan_summary: Annotated[ - PlanSummary | None, - Field( - description="Updated plan budget state. Present for 'completed' and 'failed' outcomes." - ), - ] = None - replayed: Annotated[ - StrictBool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. `replayed` appears only when the request actually resolved through the idempotency cache. Pure reads may ignore an optional `idempotency_key`; when a seller voluntarily caches keyed reads, those responses use the same replay indicator and full cache contract." - ), - ] = False - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/governance/reported_outcome_error.py b/src/adcp/types/generated_poc/governance/reported_outcome_error.py deleted file mode 100644 index 38574a17f..000000000 --- a/src/adcp/types/generated_poc/governance/reported_outcome_error.py +++ /dev/null @@ -1,97 +0,0 @@ -# generated by datamodel-codegen: -# filename: governance/reported_outcome_error.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Any, Annotated, Dict - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, RootModel, StrictBool, StrictFloat, StringConstraints - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class ClassificationSource(StrEnum): - seller_response_copy = 'seller_response_copy' - buyer_classification = 'buyer_classification' - - -class BoundedScalar1(ScalarStr): - __slots__ = () - _constraints = {'max_length': 4000} - - -class BoundedScalar(RootModel[StrictBool | StrictFloat | BoundedScalar1 | None]): - root: StrictBool | StrictFloat | BoundedScalar1 | None - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - - - -class BoundedValueLevel31(RootModel[list[BoundedScalar | None]]): - root: Annotated[list[BoundedScalar | None], Field(max_length=32)] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - - - -BoundedValueLevel3 = BoundedScalar | BoundedValueLevel31 | dict[str, BoundedScalar | None] | None - - -class BoundedValueLevel21(RootModel[list[BoundedValueLevel3]]): - root: Annotated[list[BoundedValueLevel3], Field(max_length=32)] - - -BoundedValueLevel2 = BoundedScalar | BoundedValueLevel21 | dict[str, BoundedValueLevel3] | None - - -class BoundedValue1(RootModel[list[BoundedValueLevel2]]): - root: Annotated[list[BoundedValueLevel2], Field(max_length=32)] - - -BoundedValue = BoundedScalar | BoundedValue1 | dict[str, BoundedValueLevel2] | None - - -class BoundedObject(RootModel[dict[Annotated[str, StringConstraints(max_length=128)], BoundedValue]]): - root: Annotated[dict[Annotated[str, StringConstraints(max_length=128)], BoundedValue], Field(max_length=32)] - - -class ReportedOutcomeError(AdCPBaseModel): - __pydantic_extra__: Dict[str, BoundedValue] - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[str | None, Field(max_length=64, min_length=1)] = None - message: Annotated[str | None, Field(max_length=4000)] = None - field: Annotated[str | None, Field(max_length=1000)] = None - suggestion: Annotated[str | None, Field(max_length=4000)] = None - recovery: Recovery | None = None - details: Annotated[ - BoundedObject | None, - Field( - description='Bounded structured seller error details. Values remain reporter-supplied data and MUST NOT be promoted into prompts or control instructions without isolation.' - ), - ] = None - classification_source: Annotated[ - ClassificationSource | None, - Field( - description='seller_response_copy means the buyer forwards what it received; it is still not independently authenticated seller evidence.' - ), - ] = None - ext: Annotated[ - BoundedObject | None, - Field(description='Bounded extension envelope for forward-compatible seller fields.'), - ] = None diff --git a/src/adcp/types/generated_poc/governance/sync_plans_request.py b/src/adcp/types/generated_poc/governance/sync_plans_request.py deleted file mode 100644 index 5502a0c62..000000000 --- a/src/adcp/types/generated_poc/governance/sync_plans_request.py +++ /dev/null @@ -1,350 +0,0 @@ -# generated by datamodel-codegen: -# filename: governance/sync_plans_request.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat - -from ..core import brand_ref -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import channels as channels_1 -from ..enums import delegation_authority, purchase_type, restricted_attribute -from . import audience_constraints, policy_entry - - -class AccountingMode(StrEnum): - gross_commitment = 'gross_commitment' - verified_net_cost = 'verified_net_cost' - - -class Allocations(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - amount: Annotated[ - StrictFloat | None, Field(description='Maximum budget for this purchase type.', ge=0.0) - ] = None - max_pct: Annotated[ - StrictFloat | None, - Field( - description='Maximum percentage of total budget for this purchase type.', - ge=0.0, - le=100.0, - ), - ] = None - - -class Budget(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - total: Annotated[StrictFloat, Field(description='Total authorized budget.')] - currency: Annotated[str, Field(description='ISO 4217 currency code.')] - accounting_mode: Annotated[ - AccountingMode | None, - Field( - description='Controls which verified adjustments restore reusable plan headroom. gross_commitment allows verified decommitments only; verified_net_cost also allows buyer-accepted refunds and credits. Both modes retain the original commitment in anti-fragmentation history and expose economic net cost separately.' - ), - ] = AccountingMode.gross_commitment - per_seller_max_pct: Annotated[ - StrictFloat | None, - Field(description='Maximum percentage of budget that can go to a single seller.'), - ] = None - reallocation_threshold: Annotated[ - StrictFloat, - Field( - description='Amount above which budget reallocations require human escalation. The orchestrator can reallocate spend across sellers, channels, or purchase types up to this threshold per change without asking a human. Set equal to `total` for effectively unlimited reallocation; set to 0 to require approval for every reallocation. Separate from `plan.human_review_required`, which governs decisions affecting data subjects (targeting, creative, delivery) under GDPR Art 22 / EU AI Act Annex III. Denominated in `budget.currency`.', - ge=0.0, - ), - ] - reallocation_unlimited: Annotated[ - StrictBool | None, - Field( - description='Set to true to allow the orchestrator to reallocate without any limit up to `total`. Mutually exclusive with `reallocation_threshold`. Use this for deliberate full-autonomy declarations rather than setting `reallocation_threshold: total` (which silently tightens when `total` changes).' - ), - ] = None - allocations: Annotated[ - dict[purchase_type.PurchaseType, Allocations] | None, - Field( - description='Optional budget partition across purchase types. Keys are purchase-type enum values (media_buy, rights_license, signal_activation, creative_services). When present, the governance agent validates spend against both the total and the per-type allocation. When absent, all spend counts against the single total regardless of purchase type.' - ), - ] = None - - -class Budget2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - total: Annotated[StrictFloat, Field(description='Total authorized budget.')] - currency: Annotated[str, Field(description='ISO 4217 currency code.')] - accounting_mode: Annotated[ - AccountingMode | None, - Field( - description='Controls which verified adjustments restore reusable plan headroom. gross_commitment allows verified decommitments only; verified_net_cost also allows buyer-accepted refunds and credits. Both modes retain the original commitment in anti-fragmentation history and expose economic net cost separately.' - ), - ] = AccountingMode.gross_commitment - per_seller_max_pct: Annotated[ - StrictFloat | None, - Field(description='Maximum percentage of budget that can go to a single seller.'), - ] = None - reallocation_threshold: Annotated[ - StrictFloat | None, - Field( - description='Amount above which budget reallocations require human escalation. The orchestrator can reallocate spend across sellers, channels, or purchase types up to this threshold per change without asking a human. Set equal to `total` for effectively unlimited reallocation; set to 0 to require approval for every reallocation. Separate from `plan.human_review_required`, which governs decisions affecting data subjects (targeting, creative, delivery) under GDPR Art 22 / EU AI Act Annex III. Denominated in `budget.currency`.', - ge=0.0, - ), - ] = None - reallocation_unlimited: Annotated[ - Literal[True], - Field( - description='Set to true to allow the orchestrator to reallocate without any limit up to `total`. Mutually exclusive with `reallocation_threshold`. Use this for deliberate full-autonomy declarations rather than setting `reallocation_threshold: total` (which silently tightens when `total` changes).' - ), - ] - allocations: Annotated[ - dict[purchase_type.PurchaseType, Allocations] | None, - Field( - description='Optional budget partition across purchase types. Keys are purchase-type enum values (media_buy, rights_license, signal_activation, creative_services). When present, the governance agent validates spend against both the total and the per-type allocation. When absent, all spend counts against the single total regardless of purchase type.' - ), - ] = None - - -class MixTargets(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - min_pct: StrictFloat | None = None - max_pct: StrictFloat | None = None - - -class Channels(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - required: Annotated[ - list[channels_1.MediaChannel] | None, - Field(description='Channels that must be included in the media mix.'), - ] = None - allowed: Annotated[ - list[channels_1.MediaChannel] | None, - Field(description='Channels the orchestrator may use.'), - ] = None - mix_targets: Annotated[ - dict[str, MixTargets] | None, - Field(description='Target allocation ranges per channel, keyed by channel ID.'), - ] = None - - -class Flight(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - start: Annotated[AwareDatetime, Field(description='Flight start (ISO 8601).')] - end: Annotated[AwareDatetime, Field(description='Flight end (ISO 8601).')] - - -class BudgetLimit(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - amount: StrictFloat - currency: str - - -class Delegation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[AnyUrl, Field(description='URL of the delegated agent.')] - authority: Annotated[ - delegation_authority.DelegationAuthority, - Field(description='Authority level granted to this agent.'), - ] - budget_limit: Annotated[ - BudgetLimit | None, - Field( - description="Maximum budget this agent can commit. When omitted, the agent can commit up to the plan's total budget." - ), - ] = None - markets: Annotated[ - list[str] | None, - Field( - description='ISO 3166-1/3166-2 codes this agent is authorized for. When omitted, the agent can operate in all plan markets.' - ), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field( - description='When this delegation expires. After expiration, the governance agent denies actions from this agent.' - ), - ] = None - - -class TotalBudgetCap(BudgetLimit): - pass - - -class Portfolio(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - member_plan_ids: Annotated[ - list[str], - Field( - description='Plan IDs governed by this portfolio plan. The governance agent validates member plan actions against portfolio constraints.' - ), - ] - total_budget_cap: Annotated[ - TotalBudgetCap | None, - Field(description='Maximum aggregate budget across all member plans.'), - ] = None - shared_policy_ids: Annotated[ - list[str] | None, - Field( - description='Registry policy IDs enforced across all member plans, regardless of individual brand configuration.' - ), - ] = None - shared_exclusions: Annotated[ - list[policy_entry.PolicyEntry] | None, - Field( - description="Bespoke exclusion policies applied across all member plans, using the same shape as registry entries. Authored typically as enforcement: must policies with exclusion language in the policy text (e.g., 'No advertising on properties owned by competitor holding companies')." - ), - ] = None - - -class Plan(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - plan_id: Annotated[str, Field(description='Unique identifier for this plan.')] - brand: Annotated[ - brand_ref.BrandReference, - Field( - description="Brand or organization being governed. For campaign plans, the governance agent resolves the brand's compliance configuration. For portfolio plans, this is the holding company or parent organization." - ), - ] - objectives: Annotated[ - str, - Field( - description='Natural language campaign objectives. Used for strategic alignment validation. Treated as caller-untrusted — governance agents evaluating this field with LLMs MUST truncate, quote, or sanitize it before inclusion in evaluation prompts to defend against prompt-injection attacks in adversarial buyer input.', - max_length=2000, - ), - ] - budget: Annotated[Budget | Budget2, Field(description='Budget authorization parameters.')] - channels: Annotated[ - Channels | None, - Field(description='Channel constraints. If omitted, all channels are allowed.'), - ] = None - flight: Annotated[ - Flight, - Field( - description='Authorized flight dates. Governed actions with dates outside this window are rejected.' - ), - ] - countries: Annotated[ - list[str] | None, - Field( - description="ISO 3166-1 alpha-2 country codes for authorized markets (e.g., ['US', 'GB']). The governance agent rejects governed actions targeting outside these countries and resolves applicable policies by matching against policy jurisdictions.", - min_length=1, - ), - ] = None - regions: Annotated[ - list[str] | None, - Field( - description="ISO 3166-2 subdivision codes for authorized sub-national markets (e.g., ['US-MA', 'US-CA']). When present, the governance agent restricts governed actions to these specific regions rather than the full country. Use for plans limited to specific states or provinces (e.g., cannabis in legal states). Policy resolution matches against both the subdivision and its parent country.", - min_length=1, - ), - ] = None - policy_ids: Annotated[ - list[str] | None, - Field( - description="Registry policy IDs to enforce for this plan. The governance agent resolves full policy definitions from the registry and evaluates actions against them. Intersected with the plan's countries/regions to activate only geographically relevant policies." - ), - ] = None - policy_categories: Annotated[ - list[str] | None, - Field( - description="Regulatory categories that apply to this campaign. Determines which policy regimes the governance agent enforces (e.g., 'children_directed' activates COPPA/AADC, 'political_advertising' activates disclosure requirements). The governance agent resolves categories to specific policies based on the plan's jurisdictions. When omitted, governance agents MAY infer categories from the brand's industries and the plan's objectives. Values are registry-defined category IDs (intentionally freeform strings, not an enum — new categories are added as regulations evolve).", - min_length=1, - ), - ] = None - audience: Annotated[ - audience_constraints.AudienceConstraints | None, - Field( - description='Audience targeting constraints. Defines who the campaign should reach (include) and must not reach (exclude). The governance agent evaluates seller targeting against these constraints.' - ), - ] = None - restricted_attributes: Annotated[ - list[restricted_attribute.RestrictedAttribute] | None, - Field( - description='Personal data categories that must not be used for targeting in this campaign. Applies horizontally across all audience criteria. Used for EU DSA Article 26 compliance (prohibits targeting on GDPR Article 9 special categories) and similar regulations. The governance agent flags any audience targeting that references these attributes.', - min_length=1, - ), - ] = None - restricted_attributes_custom: Annotated[ - list[str] | None, - Field( - description="Additional restricted attributes not covered by the restricted-attribute enum. Freeform strings for jurisdiction-specific or brand-specific restrictions beyond GDPR Article 9 categories (e.g., 'financial_status', 'immigration_status'). Governance agents use semantic matching for these.", - min_length=1, - ), - ] = None - min_audience_size: Annotated[ - SchemaInt | None, - Field( - description='Minimum audience segment size. Prevents micro-targeting by ensuring segments meet a k-anonymity threshold. Applies to the estimated combined (intersection) audience when multiple criteria are used, not just individual criterion sizes. The governance agent validates this by querying published signal metadata or seller-reported segment sizes. When segment size data is unavailable, the governance agent SHOULD produce a finding with reduced confidence rather than silently passing.', - ge=1, - ), - ] = None - human_review_required: Annotated[ - StrictBool | None, - Field( - description='When true, the governance agent MUST escalate any action on this plan for human review before execution — regardless of budget. Required by regulations that prohibit solely automated decisions affecting data subjects (GDPR Art 22, EU AI Act Annex III): credit, insurance pricing, recruitment, housing allocation. Governance agents MUST set this to true automatically when any resolved policy or policy_category has requires_human_review: true. Distinct from budget.reallocation_threshold, which only governs budget reallocation autonomy — human_review_required covers decisions affecting individuals (targeting, creative, delivery).' - ), - ] = False - custom_policies: Annotated[ - list[policy_entry.PolicyEntry] | None, - Field( - description='Bespoke policies specific to this campaign, using the same shape as registry entries. Each policy has a policy_id, enforcement (must|should), and natural-language policy text. Governance findings reference policy_id to identify which policy triggered. For quick authoring, omit version/name/category — servers default them.' - ), - ] = None - approved_sellers: Annotated[ - list[AnyUrl] | None, - Field(description='List of approved seller agent URLs. null means any seller.'), - ] = None - delegations: Annotated[ - list[Delegation] | None, - Field( - description="Agents authorized to execute against this plan. Each delegation scopes an agent's authority by budget, markets, and expiration. The governance agent validates that the requesting agent matches a delegation before approving actions." - ), - ] = None - portfolio: Annotated[ - Portfolio | None, - Field( - description='Portfolio-level governance constraints. When present, this plan acts as a portfolio plan that governs member plans. Portfolio plans define cross-brand constraints that no individual brand plan can override.' - ), - ] = None - ext: ext_1.ExtensionObject | None = None - - -class SyncPlansRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for at-most-once execution. `plan_id` gives resource-level dedup per plan, but the sync envelope emits audit events and can trigger governance reapproval — this key prevents those side effects from firing twice on retry. MUST be unique per (seller, request) pair. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - plans: Annotated[list[Plan], Field(description='One or more campaign plans to sync.')] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/governance/sync_plans_response.py b/src/adcp/types/generated_poc/governance/sync_plans_response.py deleted file mode 100644 index d3c6b2f33..000000000 --- a/src/adcp/types/generated_poc/governance/sync_plans_response.py +++ /dev/null @@ -1,101 +0,0 @@ -# generated by datamodel-codegen: -# filename: governance/sync_plans_response.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictBool - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import policy_enforcement - - -class Status(StrEnum): - active = 'active' - error = 'error' - - -class Status51(StrEnum): - active = 'active' - inactive = 'inactive' - - -class Category(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - category_id: Annotated[str, Field(description='Validation category identifier.')] - status: Annotated[Status51, Field(description='Whether this category is active for this plan.')] - - -class Source(StrEnum): - explicit = 'explicit' - auto_applied = 'auto_applied' - - -class ResolvedPolicy(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - policy_id: Annotated[str, Field(description='Registry policy ID.')] - source: Annotated[ - Source, - Field( - description="How this policy was included. 'explicit': referenced in the brand compliance configuration. 'auto_applied': matched automatically by jurisdiction or policy category." - ), - ] - enforcement: Annotated[ - policy_enforcement.PolicyEnforcementLevel, - Field(description='Enforcement level for this policy.'), - ] - reason: Annotated[ - str | None, - Field( - description="Why this policy was included (e.g., 'Matched jurisdiction US and policy category pharmaceutical_advertising')." - ), - ] = None - - -class Plan(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - plan_id: Annotated[str, Field(description='Plan identifier.')] - status: Annotated[ - Status, - Field( - description="Sync result status. 'active' means sync succeeded; 'error' means sync failed." - ), - ] - version: Annotated[SchemaInt, Field(description='Plan version (increments on each sync).')] - categories: Annotated[ - list[Category] | None, Field(description='Validation categories active for this plan.') - ] = None - resolved_policies: Annotated[ - list[ResolvedPolicy] | None, - Field( - description='Policies the governance agent will enforce for this plan. Includes explicitly referenced policies from the brand compliance configuration and auto-applied policies matched by jurisdiction or policy category. Present when the governance agent supports policy resolution.' - ), - ] = None - - -class SyncPlansResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - plans: Annotated[list[Plan], Field(description='Status for each synced plan.')] - replayed: Annotated[ - StrictBool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. `replayed` appears only when the request actually resolved through the idempotency cache. Pure reads may ignore an optional `idempotency_key`; when a seller voluntarily caches keyed reads, those responses use the same replay indicator and full cache contract." - ), - ] = False - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/__init__.py b/src/adcp/types/generated_poc/media_buy/__init__.py deleted file mode 100644 index 5a3cca36b..000000000 --- a/src/adcp/types/generated_poc/media_buy/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# generated by datamodel-codegen: -# filename: .schema_temp -# timestamp: 2025-11-22T15:23:24+00:00 diff --git a/src/adcp/types/generated_poc/media_buy/accept_proposal_async_response_input_required.py b/src/adcp/types/generated_poc/media_buy/accept_proposal_async_response_input_required.py deleted file mode 100644 index 4f4331caf..000000000 --- a/src/adcp/types/generated_poc/media_buy/accept_proposal_async_response_input_required.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/accept_proposal_async_response_input_required.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from ..core.compact_task_input_required import CompactTaskInputRequired - - -class AcceptProposalInputRequired(CompactTaskInputRequired): - pass diff --git a/src/adcp/types/generated_poc/media_buy/accept_proposal_async_response_submitted.py b/src/adcp/types/generated_poc/media_buy/accept_proposal_async_response_submitted.py deleted file mode 100644 index 2a8b8d39b..000000000 --- a/src/adcp/types/generated_poc/media_buy/accept_proposal_async_response_submitted.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/accept_proposal_async_response_submitted.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from ..core.compact_task_submitted import CompactTaskSubmitted - - -class AcceptProposalSubmitted(CompactTaskSubmitted): - pass diff --git a/src/adcp/types/generated_poc/media_buy/accept_proposal_async_response_working.py b/src/adcp/types/generated_poc/media_buy/accept_proposal_async_response_working.py deleted file mode 100644 index fa34afec8..000000000 --- a/src/adcp/types/generated_poc/media_buy/accept_proposal_async_response_working.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/accept_proposal_async_response_working.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from ..core.compact_task_working import CompactTaskWorking - - -class AcceptProposalWorking(CompactTaskWorking): - pass diff --git a/src/adcp/types/generated_poc/media_buy/accept_proposal_request.py b/src/adcp/types/generated_poc/media_buy/accept_proposal_request.py deleted file mode 100644 index 87c28449e..000000000 --- a/src/adcp/types/generated_poc/media_buy/accept_proposal_request.py +++ /dev/null @@ -1,132 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/accept_proposal_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat - -from ..core import canonical_account_ref -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core import push_notification_config as push_notification_config_1 -from ..core import reporting_webhook as reporting_webhook_1 -from ..core.opportunity_context import OpportunityContext - - -class TotalBudget(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - amount: Annotated[StrictFloat, Field(ge=0.0)] - currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] - - -class IoAcceptance(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - io_id: Annotated[str, Field(min_length=1)] - accepted_at: AwareDatetime - signatory: Annotated[str, Field(max_length=250, min_length=1)] - signature_id: Annotated[str | None, Field(min_length=1)] = None - - -class Status(StrEnum): - open = 'open' - closed = 'closed' - - -class Opportunity(OpportunityContext): - status: Annotated[ - Literal['closed'], - Field( - description='Whether the planning cycle remains open. On calls after initial creation, omission means no status update and MUST NOT reopen or close the opportunity implicitly, except that successful create_media_buy proposal execution explicitly infers accepted closure.' - ), - ] = 'closed' - - -class AcceptProposalRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?)?$', - ), - ] = None - adcp_major_version: Annotated[ - SchemaInt | None, - Field( - deprecated=True, - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - idempotency_key: Annotated[ - str, Field(max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$') - ] - name: Annotated[ - str | None, - Field( - description='Human-readable MediaBuy name supplied by the buyer for trafficking UI display and operational communication. When supplied, this value wins over proposal.name; the seller MUST persist it and echo it unchanged on the commitment success response and subsequent get_media_buys reads. It is operational metadata outside accepted_proposal and is not covered by proposal_terms_digest or terms_digest. When an acceptance creates a MediaBuy and name is absent, the seller MAY seed the MediaBuy name from proposal.name only when proposal.name already satisfies the MediaBuy name constraints (non-whitespace and no longer than 255 characters); the seller MUST NOT silently truncate or otherwise rewrite it. A seeded value counts as a name created through AdCP and MUST be reported on commitment and read surfaces. This display label is not an identifier or financial reference.', - max_length=255, - min_length=1, - pattern='\\S', - ), - ] = None - account: canonical_account_ref.CanonicalAccountReference - proposal_id: Annotated[str, Field(min_length=1)] - proposal_terms_digest: Annotated[ - str, - Field( - description='terms_digest from the committed proposal. The seller MUST atomically verify both ID and digest before acceptance.', - pattern='^sha256:[A-Za-z0-9_-]{43}$', - ), - ] - total_budget: Annotated[ - TotalBudget | None, - Field( - description='Execution amount when the committed proposal defines scalable percentages or constraints rather than a fixed total.' - ), - ] = None - daily_budget_cap: Annotated[ - StrictFloat | None, - Field( - description="Optional hard aggregate daily spend ceiling applied when the committed proposal is accepted. It constrains execution without changing the proposal's negotiated pricing.", - ge=0.0, - ), - ] = None - budget_cap_timezone: Annotated[ - str | None, - Field( - description='Optional shared IANA cap-day timezone override. Requires buyer_timezone_override support. When omitted, budget_capping.timezone_basis selects Account.timezone or fixed_timezone.', - min_length=1, - ), - ] = None - io_acceptance: IoAcceptance | None = None - purchase_order_ref: Annotated[str | None, Field(max_length=255, min_length=1)] = None - governance_context: Annotated[str | None, Field(max_length=4096, min_length=1)] = None - push_notification_config: push_notification_config_1.PushNotificationConfig | None = None - reporting_webhook: Annotated[ - reporting_webhook_1.ReportingWebhook | None, - Field( - description='Optional reporting delivery configuration established atomically when the proposal is accepted. This is execution metadata and does not alter the accepted commercial terms digest.' - ), - ] = None - opportunity: Annotated[ - Opportunity | None, - Field( - description='Optional planning-cycle closure. Success infers closed with accepted_with_seller when status is omitted. If the proposal carries opportunity_id, a supplied ID MUST match; the accepted proposal preserves that association.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/accept_proposal_response.py b/src/adcp/types/generated_poc/media_buy/accept_proposal_response.py deleted file mode 100644 index 3ce66798a..000000000 --- a/src/adcp/types/generated_poc/media_buy/accept_proposal_response.py +++ /dev/null @@ -1,154 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/accept_proposal_response.json -# timestamp: 2026-10-03T14:21:15+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field - -from ..core import canonical_media_buy_action -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 -from ..core.canonical_proposal import CanonicalProposal -from ..core.warning import Warning as Warning_1 -from ..enums import media_buy_status as media_buy_status_1 - - -class ProposalStatus(StrEnum): - draft = 'draft' - committed = 'committed' - accepted = 'accepted' - - -class PurchaseBinding(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - purchase_index: Annotated[SchemaInt, Field(ge=0)] - product_id: Annotated[str, Field(min_length=1)] - package_id: Annotated[str, Field(min_length=1)] - - -class Code(StrEnum): - inventory_shortfall_forecast = 'inventory_shortfall_forecast' - flight_change_creates_pacing_risk = 'flight_change_creates_pacing_risk' - fields_ignored_due_to_precedence = 'fields_ignored_due_to_precedence' - inventory_shortfall_forecast_1 = 'inventory_shortfall_forecast' - flight_change_creates_pacing_risk_1 = 'flight_change_creates_pacing_risk' - - -class AcceptProposalResponse4(AdCPBaseModel): - pass - - -class AcceptProposalResponse2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - status: Literal['failed'] = 'failed' - errors: Annotated[list[error.Error], Field(min_length=1)] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - replayed: Literal[True] | None = None - - -class AcceptProposalResponse3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - status: Literal['submitted'] = 'submitted' - task_id: Annotated[str, Field(min_length=1)] - message: Annotated[str | None, Field(max_length=2000)] = None - errors: list[error.Error] | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - replayed: Literal[True] | None = None - - -class AcceptProposalResponse6(AcceptProposalResponse2, AcceptProposalResponse4): - pass - - -class AcceptProposalResponse7(AcceptProposalResponse3, AcceptProposalResponse4): - pass - - -class Warning(Warning_1): - code: Annotated[ - Code | None, - Field( - description='Closed warning vocabulary for the negotiated AdCP release. A non-blocking condition observed while an operation still succeeded. New values are added through normal protocol releases; buyers validate against the negotiated release.', - title='Warning Code', - ), - ] = None - - -class AcceptedProposal(CanonicalProposal): - proposal_status: Annotated[ - Literal['accepted'], - Field( - description='draft is indicative and unreserved; committed has firm terms with inventory reserved until expires_at; accepted is the historical snapshot attached to a MediaBuy.', - title='Proposal Status', - ), - ] = 'accepted' - media_buy_id: Annotated[str, Field(min_length=1)] - accepted_at: Any - commercial_terms: Any - terms_digest: Any - - -class AcceptProposalResponse1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - status: Literal['completed'] = 'completed' - media_buy_id: Annotated[str, Field(min_length=1)] - name: Annotated[ - str | None, - Field( - description='Persisted human-readable MediaBuy name for trafficking UI display and operational communication. The seller MUST echo a buyer-supplied request name unchanged; when the seller seeded a new MediaBuy name from an already-valid proposal.name, it MUST return that value unchanged here. Existing named MediaBuys return the stored value on amendment or cancellation commitments. This operational metadata is outside accepted_proposal and is not covered by terms_digest. This display label is not an identifier or financial reference.', - max_length=255, - min_length=1, - pattern='\\S', - ), - ] = None - revision: Annotated[SchemaInt, Field(ge=1)] - media_buy_status: media_buy_status_1.MediaBuyStatus | None = None - confirmed_at: AwareDatetime | None = None - accepted_proposal: AcceptedProposal - purchase_bindings: Annotated[ - list[PurchaseBinding], - Field( - description='Execution identities assigned to the immutable purchases. purchase_index is the zero-based position in accepted_proposal.commercial_terms.purchases and disambiguates repeated product IDs.', - min_length=1, - ), - ] - available_actions: list[canonical_media_buy_action.CanonicalMediaBuyAction] - warnings: Annotated[ - list[Warning] | None, - Field( - description='Non-blocking observations about this completed commitment. The MediaBuy was still created or amended exactly as represented. Continuing conditions also appear as indicators on get_media_buys.', - min_length=1, - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - replayed: Literal[True] | None = None - - -class AcceptProposalResponse5(AcceptProposalResponse1, AcceptProposalResponse4): - pass - - -AcceptProposalResponse = Annotated[ - AcceptProposalResponse5 | AcceptProposalResponse6 | AcceptProposalResponse7, - Field( - description='Compact proposal-acceptance result containing the resulting MediaBuy identity and accepted immutable proposal snapshot.', - title='Accept Proposal Response', - ), -] diff --git a/src/adcp/types/generated_poc/media_buy/acceptance_context.py b/src/adcp/types/generated_poc/media_buy/acceptance_context.py deleted file mode 100644 index 82430b235..000000000 --- a/src/adcp/types/generated_poc/media_buy/acceptance_context.py +++ /dev/null @@ -1,64 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/acceptance_context.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field - -from ..core import ext as ext_1 -from ..enums import advertiser_industry as advertiser_industry_1 - - -class SubjectFacet(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[a-z][a-z0-9_]*$'} - - -class Subject(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - subject_category: Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')] - subject_facets: Annotated[list[SubjectFacet] | None, Field(min_length=1)] = None - - -class AdvertiserRole(SubjectFacet): - pass - - -class AdvertiserJurisdiction(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{2}$'} - - -class DeliveryJurisdiction(AdvertiserJurisdiction): - pass - - -class AcceptanceContext(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - subjects: Annotated[list[Subject] | None, Field(min_length=1)] = None - advertiser_roles: Annotated[list[AdvertiserRole] | None, Field(min_length=1)] = None - advertiser_industry: advertiser_industry_1.AdvertiserIndustry | None = None - advertiser_jurisdictions: Annotated[ - list[AdvertiserJurisdiction] | None, - Field( - description='Jurisdictions in which the advertiser is established or legally organized. This is distinct from where an ad will be delivered.', - min_length=1, - ), - ] = None - delivery_jurisdictions: Annotated[ - list[DeliveryJurisdiction] | None, - Field( - description="Jurisdictions in which the proposed advertising will be delivered. Seller acceptance rules' jurisdictions and jurisdiction_groups match this field.", - min_length=1, - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/acceptance_policy_catalog.py b/src/adcp/types/generated_poc/media_buy/acceptance_policy_catalog.py deleted file mode 100644 index faa7f51ea..000000000 --- a/src/adcp/types/generated_poc/media_buy/acceptance_policy_catalog.py +++ /dev/null @@ -1,45 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/acceptance_policy_catalog.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field, model_validator - -from ..core import ext as ext_1 -from . import acceptance_policy_profile, acceptance_policy_profile_ref - - -class AcceptancePolicyCatalog(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - catalog_version: Annotated[str, Field(min_length=1)] - generated_at: AwareDatetime | None = None - profiles: Annotated[ - list[acceptance_policy_profile.AcceptancePolicyProfile] | None, Field(min_length=1) - ] = None - registry_profiles: Annotated[ - list[acceptance_policy_profile_ref.RegistryAcceptancePolicyProfileReference] | None, - Field( - description='Exact reusable profiles adopted from the shared policy registry. Resolution failure is unknown, never allowed. A seller adds a distinct local profile to narrow a registry profile.', - min_length=1, - ), - ] = None - ext: ext_1.ExtensionObject | None = None - - @model_validator(mode='after') - def _require_schema_required_group(self) -> AcceptancePolicyCatalog: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('profiles',), ('registry_profiles',),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'AcceptancePolicyCatalog requires at least one of these field groups: profiles | registry_profiles' - ) diff --git a/src/adcp/types/generated_poc/media_buy/acceptance_policy_profile.py b/src/adcp/types/generated_poc/media_buy/acceptance_policy_profile.py deleted file mode 100644 index 63d64d68b..000000000 --- a/src/adcp/types/generated_poc/media_buy/acceptance_policy_profile.py +++ /dev/null @@ -1,126 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/acceptance_policy_profile.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, StringConstraints - -from ..core import ext as ext_1 -from . import acceptance_policy_rule - - -class PolicyRef(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - policy_id: Annotated[str, Field(min_length=1)] - version: Annotated[str, Field(min_length=1)] - content_digest: Annotated[ - str, - Field( - description="SHA-256 digest of the referenced policy entry's RFC 8785 JCS serialization with acceptance_profile omitted.", - pattern='^sha256:[a-f0-9]{64}$', - ), - ] - - -class Coverage(StrEnum): - partial = 'partial' - complete = 'complete' - - -class SubjectCategory(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[a-z][a-z0-9_]*$'} - - -class AppliesToEnum(StrEnum): - account = 'account' - media_buy = 'media_buy' - creative = 'creative' - landing_page = 'landing_page' - targeting = 'targeting' - delivery = 'delivery' - format = 'format' # type: ignore[assignment] - - -class Jurisdiction(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{2}$'} - - -class JurisdictionGroup(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z][A-Z0-9_-]*$'} - - -class Scope(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - subject_categories: Annotated[list[SubjectCategory], Field(min_length=1)] - applies_to: Annotated[list[AppliesToEnum], Field(min_length=1)] - jurisdictions: Annotated[list[Jurisdiction] | None, Field(min_length=1)] = None - jurisdiction_groups: Annotated[ - list[JurisdictionGroup] | None, - Field(description="Named groups declared in this profile's region_aliases.", min_length=1), - ] = None - all_jurisdictions: Annotated[ - Literal[True] | None, - Field( - description='Explicitly states that the scope is not limited by delivery jurisdiction.' - ), - ] = None - - -class RegionAliase(Jurisdiction): - pass - - -class AcceptancePolicyProfile(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - profile_id: Annotated[str, Field(pattern='^[A-Za-z0-9_.:-]+$')] - version: Annotated[str, Field(min_length=1)] - content_digest: Annotated[ - str, - Field( - description='SHA-256 digest of the RFC 8785 JCS serialization of this profile with content_digest omitted. A profile_id/version pair is immutable; consumers reject a resolved profile whose digest differs.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] - policy_refs: Annotated[ - list[PolicyRef], - Field( - description='Exact registry policy versions from which this profile was derived. Consumers MUST NOT silently substitute a different version.', - min_length=1, - ), - ] - coverage: Annotated[ - Coverage, - Field( - description='partial means additional unpublished rules may apply and omission is unknown. complete means this profile is exhaustive only for its declared scope and version.' - ), - ] - scope: Annotated[ - Scope | None, - Field( - description='The boundary within which a complete profile claims exhaustiveness. It is informative for partial profiles and mandatory for complete profiles.' - ), - ] = None - region_aliases: Annotated[ - dict[Annotated[str, StringConstraints(pattern=r'^[A-Z][A-Z0-9_-]*$')], list[RegionAliase]] | None, - Field( - description='Profile-local named country groups. Rules may reference only keys declared here; consumers expand them before matching.' - ), - ] = None - description: Annotated[str | None, Field(min_length=1)] = None - rules: Annotated[list[acceptance_policy_rule.AcceptancePolicyRule], Field(min_length=1)] - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/acceptance_policy_profile_ref.py b/src/adcp/types/generated_poc/media_buy/acceptance_policy_profile_ref.py deleted file mode 100644 index 2fee5fa12..000000000 --- a/src/adcp/types/generated_poc/media_buy/acceptance_policy_profile_ref.py +++ /dev/null @@ -1,22 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/acceptance_policy_profile_ref.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - - -class RegistryAcceptancePolicyProfileReference(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - policy_id: Annotated[str, Field(min_length=1)] - policy_version: Annotated[str, Field(min_length=1)] - policy_digest: Annotated[str, Field(pattern='^sha256:[a-f0-9]{64}$')] - profile_id: Annotated[str, Field(pattern='^[A-Za-z0-9_.:-]+$')] - profile_version: Annotated[str, Field(min_length=1)] - profile_digest: Annotated[str, Field(pattern='^sha256:[a-f0-9]{64}$')] diff --git a/src/adcp/types/generated_poc/media_buy/acceptance_policy_requirement.py b/src/adcp/types/generated_poc/media_buy/acceptance_policy_requirement.py deleted file mode 100644 index 978f25770..000000000 --- a/src/adcp/types/generated_poc/media_buy/acceptance_policy_requirement.py +++ /dev/null @@ -1,221 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/acceptance_policy_requirement.json -# timestamp: 2026-10-04T00:53:27+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import AwareDatetime, ConfigDict, Field - -from ..core import ext as ext_1 -from ..enums import restricted_attribute - - -class AcceptancePolicyRequirement1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['category_declaration'] = 'category_declaration' - declaration: Annotated[str | None, Field(min_length=1)] = None - description: Annotated[str | None, Field(min_length=1)] = None - - -class AcceptancePolicyRequirement2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['advertiser_verification'] = 'advertiser_verification' - verification_scheme: Annotated[str | None, Field(min_length=1)] = None - description: Annotated[str | None, Field(min_length=1)] = None - - -class Criterion(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[a-z][a-z0-9_.:-]*$'} - - -class AcceptancePolicyRequirement3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['advertiser_eligibility'] = 'advertiser_eligibility' - criteria: Annotated[ - list[Criterion], - Field( - description='Stable criteria such as domestic_entity, citizen_or_resident, official_election_authority, or eligible_agency.', - min_length=1, - ), - ] - description: Annotated[str | None, Field(min_length=1)] = None - - -class AcceptancePolicyRequirement4(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['funding_restriction'] = 'funding_restriction' - criteria: Annotated[ - list[Criterion], - Field( - description='Stable restrictions such as no_foreign_funding or sponsor_identity_required.', - min_length=1, - ), - ] - description: Annotated[str | None, Field(min_length=1)] = None - - -class AcceptancePolicyRequirement5(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['certification'] = 'certification' - credential: Annotated[str | None, Field(min_length=1)] = None - description: Annotated[str | None, Field(min_length=1)] = None - - -class AcceptancePolicyRequirement6(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['license'] = 'license' - credential: Annotated[str | None, Field(min_length=1)] = None - description: Annotated[str | None, Field(min_length=1)] = None - - -class AcceptancePolicyRequirement7(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['prior_authorization'] = 'prior_authorization' - description: Annotated[str | None, Field(min_length=1)] = None - - -class AcceptancePolicyRequirement8(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['account_setup'] = 'account_setup' - description: Annotated[str | None, Field(min_length=1)] = None - - -class AcceptancePolicyRequirement9(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['sales_assisted'] = 'sales_assisted' - description: Annotated[str | None, Field(min_length=1)] = None - - -class AcceptancePolicyRequirement10(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['disclosure'] = 'disclosure' - format: Annotated[str | None, Field(min_length=1)] = None - placement: Annotated[str | None, Field(min_length=1)] = None - description: Annotated[str | None, Field(min_length=1)] = None - - -class AcceptancePolicyRequirement12(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['creative_restriction'] = 'creative_restriction' - description: Annotated[str, Field(min_length=1)] - - -class AcceptancePolicyRequirement13(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['destination_restriction'] = 'destination_restriction' - description: Annotated[str, Field(min_length=1)] - - -class FormatId(ScalarStr): - __slots__ = () - _constraints = {'min_length': 1} - - -class AcceptancePolicyRequirement14(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['format_restriction'] = 'format_restriction' - format_ids: Annotated[ - list[FormatId] | None, - Field( - deprecated=True, - description='Deprecated in AdCP 3.2 and removed in AdCP 4.0. This named-format restriction is retained for 3.x compatibility; new policies identify canonical format options in description or an extension until a registry-stable format-option identity is standardized.', - min_length=1, - ), - ] = None - description: Annotated[str | None, Field(min_length=1)] = None - - -class AcceptancePolicyRequirement15(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['time_restriction'] = 'time_restriction' - starts_at: AwareDatetime | None = None - ends_at: AwareDatetime | None = None - description: Annotated[str | None, Field(min_length=1)] = None - - -class AcceptancePolicyRequirement16(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['transparency_reporting'] = 'transparency_reporting' - description: Annotated[str | None, Field(min_length=1)] = None - - -class AcceptancePolicyRequirement11(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['targeting_restriction'] = 'targeting_restriction' - restricted_attributes: Annotated[ - list[restricted_attribute.RestrictedAttribute] | None, Field(min_length=1) - ] = None - description: Annotated[str | None, Field(min_length=1)] = None - - -class AcceptancePolicyRequirement17(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['custom'] = 'custom' - id: Annotated[str, Field(pattern='^[a-z][a-z0-9_.:-]*$')] - description: Annotated[str, Field(min_length=1)] - ext: ext_1.ExtensionObject | None = None - - -AcceptancePolicyRequirement = Annotated[ - AcceptancePolicyRequirement1 - | AcceptancePolicyRequirement2 - | AcceptancePolicyRequirement3 - | AcceptancePolicyRequirement4 - | AcceptancePolicyRequirement5 - | AcceptancePolicyRequirement6 - | AcceptancePolicyRequirement7 - | AcceptancePolicyRequirement8 - | AcceptancePolicyRequirement9 - | AcceptancePolicyRequirement10 - | AcceptancePolicyRequirement11 - | AcceptancePolicyRequirement12 - | AcceptancePolicyRequirement13 - | AcceptancePolicyRequirement14 - | AcceptancePolicyRequirement15 - | AcceptancePolicyRequirement16 - | AcceptancePolicyRequirement17, - Field( - description='A typed prerequisite or restriction attached to a conditional seller-acceptance rule. The kind is coarse and machine-actionable; exact obligations remain in the referenced registry policies.', - discriminator='kind', - title='Acceptance Policy Requirement', - ), -] diff --git a/src/adcp/types/generated_poc/media_buy/acceptance_policy_rule.py b/src/adcp/types/generated_poc/media_buy/acceptance_policy_rule.py deleted file mode 100644 index 3186497ba..000000000 --- a/src/adcp/types/generated_poc/media_buy/acceptance_policy_rule.py +++ /dev/null @@ -1,120 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/acceptance_policy_rule.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import AwareDatetime, ConfigDict, Field - -from ..core import ext as ext_1 -from . import acceptance_policy_requirement - - -class SubjectFacet(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[a-z][a-z0-9_]*$'} - - -class AdvertiserRole(SubjectFacet): - pass - - -class Jurisdiction(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{2}$'} - - -class JurisdictionGroup(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z][A-Z0-9_-]*$'} - - -class AppliesToEnum(StrEnum): - account = 'account' - media_buy = 'media_buy' - creative = 'creative' - landing_page = 'landing_page' - targeting = 'targeting' - delivery = 'delivery' - format = 'format' # type: ignore[assignment] - - -class Disposition(StrEnum): - allowed = 'allowed' - conditional = 'conditional' - prohibited = 'prohibited' - - -class PolicyId(ScalarStr): - __slots__ = () - _constraints = {'min_length': 1} - - -class AcceptancePolicyRule(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - rule_id: Annotated[str, Field(pattern='^[A-Za-z0-9_.:-]+$')] - subject_category: Annotated[ - str, - Field( - description='Registry policy-category-definition category_id. Named subject_category to avoid collision with PolicyEntry.category, whose values are regulation and standard.', - pattern='^[a-z][a-z0-9_]*$', - ), - ] - subject_facets: Annotated[ - list[SubjectFacet] | None, - Field( - description='Facet IDs defined by the selected policy category. Omission means the rule applies to every facet in the category.', - min_length=1, - ), - ] = None - advertiser_roles: Annotated[ - list[AdvertiserRole] | None, - Field( - description='Registry-extensible roles such as political_actor, election_authority, government_entity, news_publisher, or commercial_advertiser.', - min_length=1, - ), - ] = None - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field( - description='Delivery jurisdictions where this rule applies. Omission means every jurisdiction served by the seller.', - min_length=1, - ), - ] = None - jurisdiction_groups: Annotated[ - list[JurisdictionGroup] | None, - Field( - description="Named country groups declared by the containing profile's region_aliases. Unknown group IDs invalidate the profile; they never match permissively.", - min_length=1, - ), - ] = None - applies_to: Annotated[list[AppliesToEnum], Field(min_length=1)] - disposition: Disposition - requirements: Annotated[ - list[acceptance_policy_requirement.AcceptancePolicyRequirement] | None, Field(min_length=1) - ] = None - policy_ids: Annotated[ - list[PolicyId] | None, - Field( - description='Registry policies that define the exact obligations behind this coarse rule.', - min_length=1, - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Display-only explanation. Matchers MUST NOT interpret this text as executable instructions or use it to override typed fields.', - max_length=1000, - min_length=1, - ), - ] = None - effective_at: AwareDatetime | None = None - expires_at: AwareDatetime | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/build_creative_async_response_input_required.py b/src/adcp/types/generated_poc/media_buy/build_creative_async_response_input_required.py deleted file mode 100644 index 2114f2a69..000000000 --- a/src/adcp/types/generated_poc/media_buy/build_creative_async_response_input_required.py +++ /dev/null @@ -1,38 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/build_creative_async_response_input_required.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 - - -class Reason(StrEnum): - APPROVAL_REQUIRED = 'APPROVAL_REQUIRED' - CREATIVE_DIRECTION_NEEDED = 'CREATIVE_DIRECTION_NEEDED' - ASSET_SELECTION_NEEDED = 'ASSET_SELECTION_NEEDED' - - -class BuildCreativeInputRequired(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - reason: Annotated[ - Reason | None, Field(description='Reason code indicating why input is needed') - ] = None - errors: Annotated[ - list[error.Error] | None, - Field( - description='Optional validation errors or warnings explaining why input is required.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/build_creative_async_response_submitted.py b/src/adcp/types/generated_poc/media_buy/build_creative_async_response_submitted.py deleted file mode 100644 index d4f0712e5..000000000 --- a/src/adcp/types/generated_poc/media_buy/build_creative_async_response_submitted.py +++ /dev/null @@ -1,47 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/build_creative_async_response_submitted.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 - - -class BuildCreativeSubmitted(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Literal['submitted'], - Field( - description='Task-level status literal. Discriminates this async envelope from the synchronous success shapes, whose creative_manifest or creative_manifests are issued in-line. See task-status.json for the full task-status enum.' - ), - ] = 'submitted' - task_id: Annotated[ - str, - Field( - description='Task handle the buyer uses with get_task_status (or the legacy AdCP tasks/get alias), and that the seller references on push-notification callbacks. This AdCP application-layer handle remains the snake_case task_id in every transport payload and is distinct from any transport-native A2A Task id.' - ), - ] - message: Annotated[ - str | None, - Field( - description="Optional human-readable explanation of why the task is submitted — e.g., 'Generative build queued; typical turnaround 3–5 minutes.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", - max_length=2000, - ), - ] = None - errors: Annotated[ - list[error.Error] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/build_creative_async_response_working.py b/src/adcp/types/generated_poc/media_buy/build_creative_async_response_working.py deleted file mode 100644 index 58a3cfee1..000000000 --- a/src/adcp/types/generated_poc/media_buy/build_creative_async_response_working.py +++ /dev/null @@ -1,34 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/build_creative_async_response_working.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictFloat - -from ..core import context as context_1 -from ..core import ext as ext_1 - - -class BuildCreativeWorking(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - percentage: Annotated[ - StrictFloat | None, Field(description='Completion percentage (0-100)', ge=0.0, le=100.0) - ] = None - current_step: Annotated[ - str | None, - Field( - description="Current step or phase of the operation (e.g., 'generating_assets', 'resolving_macros', 'rendering_preview')" - ), - ] = None - total_steps: Annotated[ - SchemaInt | None, Field(description='Total number of steps in the operation', ge=1) - ] = None - step_number: Annotated[SchemaInt | None, Field(description='Current step number', ge=1)] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/build_creative_request.py b/src/adcp/types/generated_poc/media_buy/build_creative_request.py deleted file mode 100644 index 851016f9d..000000000 --- a/src/adcp/types/generated_poc/media_buy/build_creative_request.py +++ /dev/null @@ -1,471 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/build_creative_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, StrictBool, StrictFloat - -from ..core import account_ref, brand_ref -from ..core import context as context_1 -from ..core import creative_manifest as creative_manifest_1 -from ..core import creative_representation_set as creative_representation_set_1 -from ..core import evaluator_spec -from ..core import ext as ext_1 -from ..core import format_id -from ..core import push_notification_config as push_notification_config_1 -from ..core import representation_destination as representation_destination_1 -from ..core import signal_id as signal_id_1 -from ..core import signal_ref as signal_ref_1 -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import creative_quality, creative_selection_strategy -from ..enums import preview_output_format as preview_output_format_1 -from ..enums import representation_selection_strategy as representation_selection_strategy_1 - - -class TargetCapabilityId(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[a-zA-Z0-9_-]+$'} - - -class Mode(StrEnum): - execute = 'execute' - estimate = 'estimate' - - -class MaxSpend(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - amount: Annotated[ - StrictFloat, - Field( - description='Maximum aggregate vendor_cost to incur on this call, in `currency`.', - ge=0.0, - ), - ] - currency: Annotated[ - str, Field(description='ISO 4217 currency; MUST match the rate card.', pattern='^[A-Z]{3}$') - ] - - -class SignalCondition4(AdCPBaseModel): - signal_agent_segment_id: Annotated[ - str | None, - Field( - description="Optional opaque resolved-segment handle for this fan-out condition — the RESOLVED condition identity, distinct from signal_ref's DEFINITION identity. When get_signals or a product signal_targeting_options entry exposed a signal_agent_segment_id for the targeted signal, echo it here verbatim so the produced creative's signal_condition carries the resolved-segment identity that the trafficking-compatibility check (SIGNAL_TARGETING_INCOMPATIBLE) matches on exactly. Providers MAY namespace it (e.g. provider_a:weather:rain_realtime vs provider_b:precip:high) so cross-provider conditions stay distinct without a shared taxonomy registry; treat as opaque, do not parse the namespace for business logic. Prefer it over reconstructing condition identity from categorical values — categorical {signal_ref,value} identity is the weaker fallback, appropriate only for inherently-categorical signals with no resolved handle, and never a cross-provider equivalence claim." - ), - ] = None - - -class Dimension(StrEnum): - voice = 'voice' - theme = 'theme' - best_of_n = 'best_of_n' - transformer_config = 'transformer_config' - custom = 'custom' - - -class VariantAxis(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - dimension: Annotated[ - Dimension, - Field( - description='What varies across variants. `transformer_config` varies a named config param (name it in `field`); `best_of_n` lets the agent explore and rank; `custom` is agent-defined (describe in `label`).' - ), - ] - field: Annotated[ - str | None, - Field( - description='The transformer `config` param `field` this axis sweeps. REQUIRED when `dimension` is `transformer_config`; `values[]` are then interpreted as values for `config[field]`. Omit for other dimensions.' - ), - ] = None - values: Annotated[ - list[Any] | None, - Field( - description='Caller-fixed set of values for the axis (e.g. ["isaac", "sara"] for dimension `voice`). When present, len(values) is the variant count and is authoritative over max_variants.', - min_length=1, - ), - ] = None - label: Annotated[ - str | None, - Field(description='Human-readable description of the axis, especially for `custom`.'), - ] = None - - -class KeepMode(StrEnum): - keep_all = 'keep_all' - keep_one = 'keep_one' - keep_some = 'keep_some' - - -class PreviewInput(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Human-readable name for this input set (e.g., 'Sunny morning on mobile', 'Evening podcast ad')" - ), - ] - macros: Annotated[ - dict[str, str] | None, Field(description='Macro values to use for this preview variant') - ] = None - context_description: Annotated[ - str | None, - Field(description='Natural language description of the context for AI-generated content'), - ] = None - - -class SignalCondition1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - signal_ref_1.SignalRef | None, - Field(description='The signal to target. New targeting constraints SHOULD use signal_ref.'), - ] = None - signal_id: Annotated[ - signal_id_1.SignalId | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - ), - ] = None - value_type: Annotated[Literal['binary'], Field(description='Discriminator for binary signals')] = 'binary' - value: Annotated[ - StrictBool, - Field( - description='Whether to include (true) or exclude (false) users matching this signal' - ), - ] - - -class SignalCondition2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - signal_ref_1.SignalRef | None, - Field(description='The signal to target. New targeting constraints SHOULD use signal_ref.'), - ] = None - signal_id: Annotated[ - signal_id_1.SignalId | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - ), - ] = None - value_type: Annotated[ - Literal['categorical'], Field(description='Discriminator for categorical signals') - ] = 'categorical' - values: Annotated[ - list[str], - Field( - description='Values to target. Users with any of these values will be included.', - min_length=1, - ), - ] - - -class SignalCondition3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - signal_ref_1.SignalRef | None, - Field(description='The signal to target. New targeting constraints SHOULD use signal_ref.'), - ] = None - signal_id: Annotated[ - signal_id_1.SignalId | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - ), - ] = None - value_type: Annotated[ - Literal['numeric'], Field(description='Discriminator for numeric signals') - ] = 'numeric' - min_value: Annotated[ - StrictFloat | None, - Field( - description="Minimum value (inclusive). Omit for no minimum. Must be <= max_value when both are provided. Should be >= signal's range.min if defined." - ), - ] = None - max_value: Annotated[ - StrictFloat | None, - Field( - description="Maximum value (inclusive). Omit for no maximum. Must be >= min_value when both are provided. Should be <= signal's range.max if defined." - ), - ] = None - - -class SignalCondition5(SignalCondition1, SignalCondition4): - pass - - -class SignalCondition6(SignalCondition2, SignalCondition4): - pass - - -class SignalCondition7(SignalCondition3, SignalCondition4): - pass - - -SignalCondition = SignalCondition5 | SignalCondition6 | SignalCondition7 - - -class BuildCreativeRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - governance_context: Annotated[ - str | None, - Field( - description='Opaque intent authorization when this creative execution incurs vendor cost.', - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - message: Annotated[ - str | None, - Field( - description='Natural language instructions for the transformation or generation. For pure generation, this is the creative brief. For transformation, this provides guidance on how to adapt the creative. For refinement, this describes the desired changes.' - ), - ] = None - creative_manifest: Annotated[ - creative_manifest_1.CreativeManifest | None, - Field( - description='Creative manifest to transform or generate from. On the canonical 3.2 path it carries `format_kind`, optional `format_option_ref`, and the required input assets. For transformation (for example resizing or reformatting), this is the complete creative to adapt. When creative_id is provided, the agent resolves the creative from its library and this field is ignored.' - ), - ] = None - creative_representation_set: Annotated[ - creative_representation_set_1.CreativeRepresentationSet | None, - Field( - description="Complete creative revision containing equivalent trafficking representations, of which exactly one is selected for this seller-bound output. This mode is accepted only by the destination sales agent and requires representation_destination plus representation_selection_strategy. The target capability selects the seller's build route; representation_destination supplies the binding inventory contract. The resolver verifies revision_content_digest against the complete set, retains every representation unchanged, selects exactly one compatible representation, and returns a manifest carrying representation_selection. If macro_values is present, selection happens first and binding affects only the derived output; the retained representation set and its revision binding never change. When none is compatible, the request fails with CREATIVE_REPRESENTATION_UNRESOLVED and one representation_rejections entry per candidate." - ), - ] = None - representation_destination: Annotated[ - representation_destination_1.RepresentationDestination | None, - Field( - description='Seller-owned product and effective format context for representation resolution. Required only with creative_representation_set and meaningful only when this endpoint is the destination sales agent.' - ), - ] = None - representation_selection_strategy: Annotated[ - representation_selection_strategy_1.RepresentationSelectionStrategy | None, - Field( - description='Deterministic strategy to apply after compatibility filtering. Required with creative_representation_set and MUST be advertised by creative.representation_resolution.strategies.' - ), - ] = None - creative_id: Annotated[ - str | None, - Field( - description="Reference to a creative in the agent's library. The creative agent resolves this to a manifest from its library. Use this instead of creative_manifest when retrieving an existing creative for tag generation or format adaptation." - ), - ] = None - concept_id: Annotated[ - str | None, - Field( - description='Creative concept containing the creative. Creative agents SHOULD assign globally unique creative_id values; when they cannot guarantee uniqueness, concept_id is REQUIRED to disambiguate.' - ), - ] = None - media_buy_id: Annotated[ - str | None, - Field( - description='Media buy identifier for tag generation context. When the creative agent is also the ad server, this provides the trafficking context needed to generate placement-specific tags (e.g., CM360 placement ID). Not needed when tags are generated at the creative level (most creative platforms).' - ), - ] = None - package_id: Annotated[ - str | None, - Field( - description='Package identifier within the media buy. Used with media_buy_id when the creative agent needs line-item-level context for tag generation. Omit to get a tag not scoped to a specific package.' - ), - ] = None - target_format_id: Annotated[ - format_id.FormatReferenceStructuredObject | None, - Field( - deprecated=True, - description='**DEPRECATED in 3.2.** Legacy named-format selector. Use `target_capability_id` with a value advertised in `get_adcp_capabilities.creative.supported_formats[].capability_id`.', - ), - ] = None - target_format_ids: Annotated[ - list[format_id.FormatReferenceStructuredObject] | None, - Field( - deprecated=True, - description='**DEPRECATED in 3.2.** Legacy named-format selectors. Use `target_capability_ids` with values advertised in `get_adcp_capabilities.creative.supported_formats[].capability_id`.', - max_length=50, - min_length=1, - ), - ] = None - target_capability_id: Annotated[ - str | None, - Field( - description='Canonical 3.2 single-output selector. Matches exactly one `get_adcp_capabilities.creative.supported_formats[].capability_id` advertised by this creative agent. The matched entry supplies the canonical `format` declaration used to validate inputs and the returned manifest. Mutually exclusive with `target_capability_ids` and the deprecated target_format_id fields.', - pattern='^[a-zA-Z0-9_-]+$', - ), - ] = None - target_capability_ids: Annotated[ - list[TargetCapabilityId] | None, - Field( - description='Canonical 3.2 multi-output selector. Each value matches a `get_adcp_capabilities.creative.supported_formats[].capability_id`. The creative agent produces one canonical manifest per capability in request order. Mutually exclusive with `target_capability_id` and the deprecated target_format_id fields.', - max_length=50, - min_length=1, - ), - ] = None - transformer_id: Annotated[ - str | None, - Field( - description="Selects an account-scoped transformer (discovered via list_transformers) to perform the build. One transformer per call. When present, the build uses this transformer and target_capability_id/target_capability_ids select which of its outputs to produce — they MUST be a subset of the transformer's output_capability_ids. Deprecated target_format_id fields use the legacy output_format_ids compatibility path. Render configuration goes in `config`." - ), - ] = None - config: Annotated[ - dict[str, Any] | None, - Field( - description='Typed render configuration for the selected transformer, keyed by each param\'s `field` (from the transformer\'s params[] in list_transformers). Example: { "voice": "isaac", "speaking_rate": 1.1, "mastering_preset": "podcast" }. The agent MUST validate `config` against the transformer\'s live params for this account and reject unrecognized keys and out-of-range / non-enumerated values with a field-attributed error (e.g. `config.voice`) rather than silently ignoring them — config drives a paid render. Genuinely vendor-specific or experimental knobs not declared as params belong in `ext`, not here. (The schema leaves this object open because legal keys are dynamic per transformer; strict validation is a normative agent obligation.) When `refine_from_build_variant_id` is set, `config` is applied as a DELTA over the parent leaf\'s config.' - ), - ] = None - refine_from_build_variant_id: Annotated[ - str | None, - Field( - description='Refine a previously produced variant and return new lineage-linked variants. The transformer and target capability are inherited from the parent leaf. A refinement request MUST omit transformer_id, target_capability_id(s), and deprecated target_format_id(s); changing transformer or output format is a new transformation build, not refinement. Requires creative.supports_refinement.' - ), - ] = None - mode: Annotated[ - Mode | None, - Field( - description="`execute` (default) produces and bills the creative(s). `estimate` is a DRY RUN: the agent produces nothing and bills nothing, and returns a BuildCreativeEstimate with a projected cost band (cost_low/cost_high) computed against THIS request's actual inputs (script length, brief, catalog size, max_creatives × max_variants) — the band the buyer cannot derive itself, since per_unit gives the rate but not the unit count. Requires the agent to advertise `creative.supports_spend_controls`; otherwise rejected with `UNSUPPORTED_FEATURE`." - ), - ] = Mode.execute - max_spend: Annotated[ - MaxSpend | None, - Field( - description='Hard per-call spend ceiling. The agent produces leaves until the NEXT leaf would push the run\'s aggregate vendor_cost over `amount`, then STOPS and returns the partial BuildCreativeVariantSuccess produced so far with `budget_status: "capped"` (every returned leaf is real, trafficable, and billed — nothing produced is discarded; the leaf shortfall is `leaves_returned` < `leaves_total`). If even the first leaf would exceed the cap, the call fails with BUDGET_CAP_REACHED. `currency` MUST match the rate card\'s currency (the agent does not FX-convert) or the request is rejected with INVALID_REQUEST (error.field `max_spend.currency`). Requires `creative.supports_spend_controls`. Caps a SINGLE call — to bound a refinement loop, track aggregate vendor_cost across calls and stop issuing them (buyer responsibility in this revision). max_spend bounds only build-time vendor_cost: CPM-priced builds (estimate basis `cpm_deferred`) have build-time vendor_cost 0 and accrue at serve time, so max_spend never engages for them — bound a CPM fan-out with max_creatives instead.' - ), - ] = None - max_creatives: Annotated[ - SchemaInt | None, - Field( - description='Caps how many DISTINCT creatives to produce along the catalog/item fan-out axis — one creative per catalog item. Use it to sample a large catalog (e.g. send 150 job openings, set max_creatives: 5 to preview five). Distinct from item_limit, which caps how many catalog items a SINGLE creative consumes (DCO-style). Omitted with a catalog input means one creative per item up to the catalog/format bound; omitted without a catalog collapses to a single creative. Large fan-outs may return asynchronously. Mutually exclusive with `refine_from_build_variant_id` (refinement targets one prior creative, not a catalog fan-out). Supported only when the agent advertises `creative.multiplicity.supports_catalog_fanout`; values above `max_creatives_limit` are clamped. Pair with `max_spend` to bound the bill of a large fan-out.', - ge=1, - ), - ] = None - signal_conditions: Annotated[ - list[SignalCondition] | None, - Field( - description="Advisory keep-all PRODUCTION axis: produce one distinct creative group per signal condition, each kept and trafficked with its own signal targeting (e.g. a rain creative AND a sun creative). Sibling to max_creatives (catalog axis), NOT a variant_axis value (which is choose-among). Each item reuses SignalTargeting (value_type-discriminated binary/categorical/numeric over signal_ref) so the produced group's signal_condition resolves condition identity through the SAME schema the sales-side package targeting uses, plus an optional signal_agent_segment_id carrying the RESOLVED-segment identity (vs signal_ref's definition identity) — echo a provider-exposed handle verbatim; it is the primary trafficking-compatibility key, with categorical signal_ref+value as the weaker fallback. Per #5280 this is an ADVISORY context pointer — it informs production and MUST NOT hard-block at the build_creative layer; trafficking-compatibility (a sun creative MUST NOT serve into rain-targeted packages) is enforced reject-at-trafficking on the sales side (SIGNAL_TARGETING_INCOMPATIBLE), not here. Triggers the BuildCreativeVariantSuccess shape. Supported only when the agent advertises creative.multiplicity.supports_signal_fanout; condition counts above max_signal_conditions_limit are CLAMPED (not rejected), consistent with max_creatives. Composes with max_creatives (catalog × conditions cross-product) and max_variants (variants per group).", - min_length=1, - ), - ] = None - max_variants: Annotated[ - SchemaInt | None, - Field( - description='Caps how many ALTERNATIVES to produce per creative (different voices, themes, best-of-N, etc.). Default 1 preserves single-output behavior. Each variant is a real, independently-billed build (you pay for all produced); the buyer keeps one or many. When variant_axis.values[] is provided, its length is authoritative over max_variants. Resolutions/quality tiers are NOT variants — request them as additional target formats.', - ge=1, - ), - ] = 1 - variant_axis: Annotated[ - VariantAxis | None, - Field( - description='Declares the dimension along which variants differ. When `values` is provided, the agent produces exactly one variant per value (e.g. an A/B of two voices). When only `dimension` is provided, the agent chooses up to max_variants variants along that dimension (e.g. best-of-N, themes).' - ), - ] = None - keep_mode: Annotated[ - KeepMode | None, - Field( - description='Advisory hint for how the buyer intends to use the variants. `keep_one` (best-of-N) and `keep_some` signal the agent to set `recommended`/`rank` on returned variants. Advisory only — it does not change what is returned or billed; every produced variant is returned and charged. Keeping is a client act of trafficking the chosen build_variant_id(s).' - ), - ] = KeepMode.keep_all - selection_strategy: Annotated[ - creative_selection_strategy.CreativeSelectionStrategy | None, - Field( - description='Governs HOW the agent samples when max_creatives < items_total (folds #5262). audience_relevance draws its ranking input from the SAME signal_ref pointers in signal_conditions / package targeting — NOT a parallel signals[] array. proximity takes a location input (geo shape TBD — WG open). inventory_priority is seller-side catalog metadata (margin/overstock/promo; no buyer input). random is the status-quo default. Per-creative selection ordering surfaces on the existing rank / recommended fields of creatives[].variants[], not a new selection_rank. Advisory; absent => agent default (random).' - ), - ] = None - account: Annotated[ - account_ref.AccountReference | None, - Field( - description='Account reference for pricing and billing. When present, the creative agent applies account-specific pricing from the rate card, records the build against the account for billing, and can enforce account-level quotas or entitlements. Required by creative agents that charge for their services.' - ), - ] = None - brand: Annotated[ - brand_ref.BrandReference | None, - Field( - description='Brand reference for creative generation. Resolved to full brand identity (colors, logos, tone) at execution time.' - ), - ] = None - quality: Annotated[ - creative_quality.CreativeQuality | None, - Field( - description="Quality tier for generation. 'draft' produces fast, lower-fidelity output for iteration and review. 'production' produces full-quality output for final delivery. If omitted, the creative agent uses its own default. For non-generative transforms (e.g., format resizing), creative agents MAY ignore this field." - ), - ] = None - evaluator: Annotated[ - evaluator_spec.EvaluatorSpec | None, - Field( - description="Optional advisory evaluator (buyer-attached pointer, #5280) declaring how produced variants should be evaluated and ranked — the rank-side of the get_creative_features feature oracle. Experimental (x-status: experimental): the whole evaluator surface is new and unfrozen, and requires creative.supports_evaluator, which sellers MUST pair with `creative.evaluator` in experimental_features. Drives the producing agent's gate-then-rank pipeline over its best_of_n exploration: per leaf, evaluate (the chosen form) → optionally GATE (`evaluator.feature_requirement[]`, drop fails — internal pruning of which leaves the agent recommends, never an AdCP-layer block of an already-produced billable leaf) → RANK survivors (`evaluator.rank_by`, an explicit {feature_id, direction} ordering). Feature discovery uses get_adcp_capabilities governance.creative_features for rank_by, feature_requirement, and eval.features[]; evaluator_id is a pre-provisioned/account-arranged preset, not an ID discovered from that catalog. Populates a per-leaf `eval` block of creative-feature values (creative-feature-result[]) when supports_evaluator. When the evaluator names an external agent (`evaluator.feature_agent.agent_url` or the agent-form `agent_url`), that agent MUST appear in the seller's `creative_policy.accepted_verifiers[]` (the same allowlist #5280 established for provenance verify_agent); an off-list agent is rejected with `EVALUATOR_AGENT_NOT_ACCEPTED`. The outbound evaluator call authenticates on the transport (request signing/JWKS, mTLS, or a pre-provisioned static credential); credentials and caller-supplied trust material MUST NOT appear in evaluator, context, ext, or creative payload fields, and credential- or trust-material keys should be rejected with `CREDENTIAL_IN_ARGS`. With no `feature_requirement`, evaluation is advisory only and does not change what is produced or billed; an unreachable/unknown on-list agent degrades to seller-default ranking (advisory errors[] note), not a failure. Requires creative.supports_evaluator; otherwise ignored." - ), - ] = None - item_limit: Annotated[ - SchemaInt | None, - Field( - description="Maximum number of catalog items a SINGLE creative consumes when generating (DCO-style — e.g. how many items fill one carousel/feed creative). When a catalog asset contains more items than this limit, the creative agent selects the top items based on relevance or catalog ordering. When item_limit exceeds the format's max_items, the creative agent SHOULD use the lesser of the two. Ignored when the manifest contains no catalog assets. Distinct from `max_creatives`, which fans OUT across catalog items to produce one distinct creative per item.", - ge=1, - ), - ] = None - include_preview: Annotated[ - StrictBool | None, - Field( - description="When true, requests the creative agent to include preview renders in the response alongside the manifest. Agents that support this return a 'preview' object in the response using the same structure as preview_creative. Agents that do not support inline preview simply omit the field. This avoids a separate preview_creative round trip for platforms that generate previews as a byproduct of building." - ), - ] = None - preview_inputs: Annotated[ - list[PreviewInput] | None, - Field( - description='Input sets for preview generation when include_preview is true. Supported with a single target_capability_id; multi-capability requests generate one default preview per output. Deprecated target-format selectors retain equivalent compatibility behavior.', - min_length=1, - ), - ] = None - preview_quality: Annotated[ - creative_quality.CreativeQuality | None, - Field( - description="Render quality for inline preview when include_preview is true. 'draft' produces fast, lower-fidelity renderings. 'production' produces full-quality renderings. Independent of the build quality parameter — you can build at draft quality and preview at production quality, or vice versa. If omitted, the creative agent uses its own default. Ignored when include_preview is false or omitted." - ), - ] = None - preview_output_format: Annotated[ - preview_output_format_1.PreviewOutputFormat | None, - Field( - description="Output format for preview renders when include_preview is true. 'url' returns preview_url (iframe-embeddable URL), 'html' returns preview_html (raw HTML). Ignored when include_preview is false or omitted." - ), - ] = preview_output_format_1.PreviewOutputFormat.url - macro_values: Annotated[ - dict[str, str] | None, - Field( - description="Raw concrete values offered for build-time binding, keyed by AdCP universal semantic (for example CLICK_URL or CACHEBUSTER). With declarations, a value binds only a verified-universal `resolve_value` occurrence performed_by `creative_agent`, using that declaration's exact context and encoding; callers MUST NOT pre-encode it. The selected `creative.supported_formats[]` route's macro_resolution_capabilities is the binding build/preview capability set; seller-wide and product sets apply only on the sales execution path. Values never short-circuit `translate_to_native`: translation emits its target declaration and the complete target capability chain remains required. For creative_representation_set, selection happens before binding and the complete representation set remains byte-identical. Without declarations, the 3.x legacy path remains: creative agents may translate recognized AdCP tokens using the existing `translateUniversalMacros` contract, preserve omitted placeholders for the sales agent, and ignore unknown keys. Existing unmapped/frozen-consent diagnostics remain required." - ), - ] = None - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for this request. Prevents duplicate creative generation on retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - push_notification_config: Annotated[ - push_notification_config_1.PushNotificationConfig | None, - Field( - description='Optional webhook configuration for async terminal completion/failure notifications on build_creative. Meaningful only when the request enters the async lifecycle and returns a Submitted envelope. Submitted envelopes with `task_id` remain pollable through `get_task_status` (legacy `tasks/get`) whether or not this field is present. If a request includes this field and the agent returns a Submitted envelope, the agent MUST deliver at least the terminal completion/failure notification to the configured URL; intermediate progress notifications are MAY. If the agent cannot honor the webhook channel, it MUST reject the request with a structured error instead of silently accepting. This field does not change response timing semantics: agents MUST NOT route a request through the async/Submitted arm or emit async delivery solely because `push_notification_config` is present; requests that can be completed inline still return the synchronous success shape.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/build_creative_response.py b/src/adcp/types/generated_poc/media_buy/build_creative_response.py deleted file mode 100644 index 05c595c51..000000000 --- a/src/adcp/types/generated_poc/media_buy/build_creative_response.py +++ /dev/null @@ -1,235 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/build_creative_response.json -# timestamp: preserved-by-post-generate-fixes - -from __future__ import annotations - -from typing import Annotated, Any, Literal, TypeAlias - -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StringConstraints - -from ..core.version_envelope import AdcpVersionEnvelope -from ..core import context as context_1 -from ..core import creative_consumption as creative_consumption_1 -from ..core import creative_manifest as creative_manifest_1 -from ..core import error as error_1 -from ..core import ext as ext_1 -from ..core import format_id as format_id_1 -from ..core import signal_targeting as signal_targeting_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..creative import creative_feature_result as creative_feature_result_1 -from ..creative import preview_render as preview_render_1 -from ..enums import creative_selection_strategy as creative_selection_strategy_1 -from ..enums import task_status as task_status_1 - - -class Input(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - name: str - macros: dict[str, str] | None = None - context_description: str | None = None - - -class Preview2(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - preview_id: str - renders: Annotated[list[preview_render_1.PreviewRender], Field(min_length=1)] - input: Input - - -class Preview(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - previews: Annotated[list[Preview2], Field(min_length=1)] - interactive_url: AnyUrl | None = None - expires_at: AwareDatetime - - -class Input2(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - name: str - macros: dict[str, str] | None = None - context_description: str | None = None - - -class Preview4(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - preview_id: str - format_id: format_id_1.FormatReferenceStructuredObject | None = None - capability_id: Annotated[str, StringConstraints(pattern='^[a-zA-Z0-9_-]+$')] | None = None - renders: Annotated[list[preview_render_1.PreviewRender], Field(min_length=1)] - input: Input2 - - -class Preview3(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - previews: Annotated[list[Preview4], Field(min_length=1)] - interactive_url: AnyUrl | None = None - expires_at: AwareDatetime - - -class CatalogItemRef(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - catalog_type: str | None = None - item_id: str - - -class Eval(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - features: list[creative_feature_result_1.CreativeFeatureResult] | None = None - ranked_against: Annotated[int, Field(ge=1)] | None = None - calls_used: Annotated[int, Field(ge=0)] | None = None - seconds_used: Annotated[float, Field(ge=0)] | None = None - ext: ext_1.ExtensionObject | None = None - - -class Variant(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - build_variant_id: str - recipe_hash: str | None = None - parent_build_variant_id: str | None = None - creative_manifest: creative_manifest_1.CreativeManifest - variant_axis_value: Any | None = None - recommended: bool | None = None - rank: Annotated[int, Field(ge=1)] | None = None - eval: Eval | None = None - pricing_option_id: str | None = None - vendor_cost: Annotated[float, Field(ge=0)] | None = None - currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] | None = None - consumption: creative_consumption_1.CreativeConsumption | None = None - - -class Creative(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - build_creative_id: str | None = None - catalog_item_ref: CatalogItemRef | None = None - signal_condition: signal_targeting_1.SignalTargeting | None = None - variants: Annotated[list[Variant], Field(min_length=1)] | None = None - errors: Annotated[list[error_1.Error], Field(min_length=1)] | None = None - - -class PerLeaf(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - catalog_item_ref: dict[str, Any] | None = None - variant_axis_value: Any | None = None - pricing_option_id: str | None = None - cost_low: Annotated[float, Field(ge=0)] | None = None - cost_high: Annotated[float, Field(ge=0)] | None = None - consumption_estimate: creative_consumption_1.CreativeConsumption | None = None - - -class Estimate(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - items_total: Annotated[int, Field(ge=0)] | None = None - items_to_produce: Annotated[int, Field(ge=0)] | None = None - conditions_total: Annotated[int, Field(ge=1)] | None = None - variants_per_item: Annotated[int, Field(ge=1)] | None = None - leaves_total: Annotated[int, Field(ge=0)] | None = None - currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] - cost_low: Annotated[float, Field(ge=0)] - cost_high: Annotated[float, Field(ge=0)] - cost_expected: Annotated[float, Field(ge=0)] | None = None - basis: Literal['fixed', 'estimated_units', 'cpm_deferred'] - per_leaf: list[PerLeaf] | None = None - - -class BuildCreativeResponse1(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - creative_manifest: creative_manifest_1.CreativeManifest - build_variant_id: str | None = None - recipe_hash: str | None = None - sandbox: bool | None = None - expires_at: AwareDatetime | None = None - preview: Preview | None = None - preview_error: error_1.Error | None = None - pricing_option_id: str | None = None - vendor_cost: Annotated[float, Field(ge=0)] | None = None - currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] | None = None - consumption: creative_consumption_1.CreativeConsumption | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class BuildCreativeResponse2(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - errors: Annotated[list[error_1.Error], Field(min_length=1)] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class BuildCreativeResponse3(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - creative_manifests: Annotated[list[creative_manifest_1.CreativeManifest], Field(min_length=1)] - sandbox: bool | None = None - expires_at: AwareDatetime | None = None - preview: Preview3 | None = None - preview_error: error_1.Error | None = None - pricing_option_id: str | None = None - vendor_cost: Annotated[float, Field(ge=0)] | None = None - currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] | None = None - consumption: creative_consumption_1.CreativeConsumption | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class BuildCreativeResponse4(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - creatives: Annotated[list[Creative], Field(min_length=1)] - items_total: Annotated[int, Field(ge=0)] | None = None - items_returned: Annotated[int, Field(ge=0)] | None = None - leaves_total: Annotated[int, Field(ge=0)] | None = None - leaves_returned: Annotated[int, Field(ge=0)] | None = None - vendor_cost: Annotated[float, Field(ge=0)] | None = None - currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] | None = None - keep_mode_applied: Literal['keep_all', 'keep_one', 'keep_some'] | None = None - selection_strategy_applied: creative_selection_strategy_1.CreativeSelectionStrategy | None = None - budget_status: Literal['complete', 'capped'] | None = None - errors: list[error_1.Error] | None = None - sandbox: bool | None = None - expires_at: AwareDatetime | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class BuildCreativeResponse5(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - mode: Literal['estimate'] = 'estimate' - estimate: Estimate - expires_at: AwareDatetime | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class BuildCreativeResponse6(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow', validate_default=True) - status: Literal[task_status_1.TaskStatus.submitted] = task_status_1.TaskStatus.submitted - task_id: str - message: Annotated[str, StringConstraints(max_length=2000)] | None = None - errors: list[error_1.Error] | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -BuildCreativeResponse: TypeAlias = BuildCreativeResponse1 | BuildCreativeResponse2 | BuildCreativeResponse3 | BuildCreativeResponse4 | BuildCreativeResponse5 | BuildCreativeResponse6 - - -__all__ = [ - 'BuildCreativeResponse', - 'BuildCreativeResponse1', - 'BuildCreativeResponse2', - 'BuildCreativeResponse3', - 'BuildCreativeResponse4', - 'BuildCreativeResponse5', - 'BuildCreativeResponse6', - 'CatalogItemRef', - 'Creative', - 'Estimate', - 'Eval', - 'Input', - 'Input2', - 'PerLeaf', - 'Preview', - 'Preview2', - 'Preview3', - 'Preview4', - 'Variant', -] diff --git a/src/adcp/types/generated_poc/media_buy/buy_products_async_response_input_required.py b/src/adcp/types/generated_poc/media_buy/buy_products_async_response_input_required.py deleted file mode 100644 index 5c992fc9a..000000000 --- a/src/adcp/types/generated_poc/media_buy/buy_products_async_response_input_required.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/buy_products_async_response_input_required.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from ..core.compact_task_input_required import CompactTaskInputRequired - - -class BuyProductsInputRequired(CompactTaskInputRequired): - pass diff --git a/src/adcp/types/generated_poc/media_buy/buy_products_async_response_submitted.py b/src/adcp/types/generated_poc/media_buy/buy_products_async_response_submitted.py deleted file mode 100644 index 831747a61..000000000 --- a/src/adcp/types/generated_poc/media_buy/buy_products_async_response_submitted.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/buy_products_async_response_submitted.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from ..core.compact_task_submitted import CompactTaskSubmitted - - -class BuyProductsSubmitted(CompactTaskSubmitted): - pass diff --git a/src/adcp/types/generated_poc/media_buy/buy_products_async_response_working.py b/src/adcp/types/generated_poc/media_buy/buy_products_async_response_working.py deleted file mode 100644 index a0fcc2ad2..000000000 --- a/src/adcp/types/generated_poc/media_buy/buy_products_async_response_working.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/buy_products_async_response_working.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from ..core.compact_task_working import CompactTaskWorking - - -class BuyProductsWorking(CompactTaskWorking): - pass diff --git a/src/adcp/types/generated_poc/media_buy/buy_products_request.py b/src/adcp/types/generated_poc/media_buy/buy_products_request.py deleted file mode 100644 index b2a959c31..000000000 --- a/src/adcp/types/generated_poc/media_buy/buy_products_request.py +++ /dev/null @@ -1,173 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/buy_products_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat - -from ..core import ( - bidding_policy, - brand_key, - business_entity, - canonical_account_ref, - canonical_budget_allocation, -) -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core import media_buy_frequency_cap -from ..core import push_notification_config as push_notification_config_1 -from ..core import reporting_webhook as reporting_webhook_1 -from ..core import start_timing -from ..core.opportunity_context import OpportunityContext -from ..enums import advertiser_industry as advertiser_industry_1 -from ..enums import pacing as pacing_1 -from . import product_purchase_input - - -class TotalBudget(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - amount: Annotated[StrictFloat, Field(ge=0.0)] - currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] - - -class Status(StrEnum): - open = 'open' - closed = 'closed' - - -class Opportunity(OpportunityContext): - status: Annotated[ - Literal['closed'], - Field( - description='Whether the planning cycle remains open. On calls after initial creation, omission means no status update and MUST NOT reopen or close the opportunity implicitly, except that successful create_media_buy proposal execution explicitly infers accepted closure.' - ), - ] = 'closed' - - -class BuyProductsRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?)?$', - ), - ] = None - adcp_major_version: Annotated[ - SchemaInt | None, - Field( - deprecated=True, - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - idempotency_key: Annotated[ - str, Field(max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$') - ] - name: Annotated[ - str | None, - Field( - description='Human-readable name for this media buy, shared by buyer and seller for trafficking UI display and operational communication. When supplied, the seller MUST persist it and echo it unchanged on the commitment success response and subsequent get_media_buys reads. The name is operational metadata outside accepted_proposal and is not covered by terms_digest. This display label is not an identifier or financial reference.', - max_length=255, - min_length=1, - pattern='\\S', - ), - ] = None - account: Annotated[ - canonical_account_ref.CanonicalAccountReference, - Field( - description='Execution account. A natural-key account is the single brand source and MUST NOT be combined with top-level brand.' - ), - ] - brand: Annotated[ - brand_key.BrandKey | None, - Field( - description='Brand source required when account is ID-only. Omit when account already contains brand and operator.' - ), - ] = None - advertiser_industry: Annotated[ - advertiser_industry_1.AdvertiserIndustry | None, - Field( - description='Industry classification for this campaign. Sellers may infer it from the resolved brand manifest when omitted.' - ), - ] = None - feed_version: Annotated[ - str, - Field( - description='list_products feed_version containing the published offers being accepted. Sellers reject stale or mismatched versions rather than silently applying changed terms.', - min_length=1, - ), - ] - pricing_version: Annotated[ - str | None, - Field( - description='list_products pricing_version containing the accepted rate. Buyers MUST include this whenever list_products returned one; omission means the seller does not version pricing separately.', - min_length=1, - ), - ] = None - purchases: Annotated[list[product_purchase_input.ProductPurchaseInput], Field(min_length=1)] - total_budget: TotalBudget | None = None - daily_budget_cap: Annotated[ - StrictFloat | None, - Field( - description="Optional hard aggregate daily spend ceiling in total_budget.currency or the media buy's derived currency. It bounds the shared daily spend pool without allocating or reserving amounts for purchases.", - ge=0.0, - ), - ] = None - frequency_cap: Annotated[ - media_buy_frequency_cap.MediaBuyFrequencyCap | None, - Field( - description='Optional max-impression cap using one counter across purchases. Buyers send it only when aggregate_frequency_capping is advertised. Every selected product must declare compatible media_buy_support; otherwise the purchase is rejected atomically with UNSUPPORTED_FEATURE before any mutation, never clamped.' - ), - ] = None - budget_cap_timezone: Annotated[ - str | None, - Field( - description='Optional IANA timezone override shared by every aggregate and purchase daily cap. Requires buyer_timezone_override support; otherwise rejected with UNSUPPORTED_FEATURE. When omitted, budget_capping.timezone_basis selects Account.timezone or fixed_timezone.', - min_length=1, - ), - ] = None - budget_allocation: canonical_budget_allocation.CanonicalBudgetAllocation | None = None - start_time: start_timing.StartTiming - end_time: AwareDatetime - pacing: Annotated[ - pacing_1.Pacing | None, - Field( - description='Aggregate media-buy pacing. In seller-optimized allocation, a seller declaring media_buy.features.seller_optimized_budget MUST accept omission and `even`; it MAY reject `asap` or `front_loaded` with UNSUPPORTED_FEATURE (error.field `pacing`) before any provider mutation and MUST NOT silently coerce them to `even`. Fixed-allocation semantics are unchanged.' - ), - ] = None - bidding: bidding_policy.BiddingPolicy | None = None - paused: StrictBool | None = False - purchase_order_ref: Annotated[str | None, Field(max_length=255, min_length=1)] = None - agency_estimate_number: Annotated[str | None, Field(max_length=100)] = None - invoice_recipient: Annotated[ - business_entity.BusinessEntity | None, - Field(description='Authorized per-buy billing entity override.'), - ] = None - governance_context: Annotated[str | None, Field(max_length=4096, min_length=1)] = None - push_notification_config: push_notification_config_1.PushNotificationConfig | None = None - reporting_webhook: Annotated[ - reporting_webhook_1.ReportingWebhook | None, - Field( - description='Optional reporting delivery configuration established atomically with the MediaBuy. This is execution metadata and is not part of the immutable product pricing terms.' - ), - ] = None - opportunity: Annotated[ - Opportunity | None, - Field( - description='Optional planning-cycle closure for a direct product purchase. Success infers closed with accepted_with_seller when status is omitted; an explicit status MUST carry that same closure.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/buy_products_response.py b/src/adcp/types/generated_poc/media_buy/buy_products_response.py deleted file mode 100644 index 02fb7ca94..000000000 --- a/src/adcp/types/generated_poc/media_buy/buy_products_response.py +++ /dev/null @@ -1,154 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/buy_products_response.json -# timestamp: 2026-10-03T14:21:15+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field - -from ..core import canonical_media_buy_action -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 -from ..core.canonical_proposal import CanonicalProposal -from ..core.warning import Warning as Warning_1 -from ..enums import media_buy_status as media_buy_status_1 - - -class ProposalStatus(StrEnum): - draft = 'draft' - committed = 'committed' - accepted = 'accepted' - - -class PurchaseBinding(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - purchase_index: Annotated[SchemaInt, Field(ge=0)] - product_id: Annotated[str, Field(min_length=1)] - package_id: Annotated[str, Field(min_length=1)] - - -class Code(StrEnum): - inventory_shortfall_forecast = 'inventory_shortfall_forecast' - flight_change_creates_pacing_risk = 'flight_change_creates_pacing_risk' - fields_ignored_due_to_precedence = 'fields_ignored_due_to_precedence' - inventory_shortfall_forecast_1 = 'inventory_shortfall_forecast' - flight_change_creates_pacing_risk_1 = 'flight_change_creates_pacing_risk' - - -class BuyProductsResponse4(AdCPBaseModel): - pass - - -class BuyProductsResponse2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - status: Literal['failed'] = 'failed' - errors: Annotated[list[error.Error], Field(min_length=1)] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - replayed: Literal[True] | None = None - - -class BuyProductsResponse3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - status: Literal['submitted'] = 'submitted' - task_id: Annotated[str, Field(min_length=1)] - message: Annotated[str | None, Field(max_length=2000)] = None - errors: list[error.Error] | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - replayed: Literal[True] | None = None - - -class BuyProductsResponse6(BuyProductsResponse2, BuyProductsResponse4): - pass - - -class BuyProductsResponse7(BuyProductsResponse3, BuyProductsResponse4): - pass - - -class Warning(Warning_1): - code: Annotated[ - Code | None, - Field( - description='Closed warning vocabulary for the negotiated AdCP release. A non-blocking condition observed while an operation still succeeded. New values are added through normal protocol releases; buyers validate against the negotiated release.', - title='Warning Code', - ), - ] = None - - -class AcceptedProposal(CanonicalProposal): - proposal_status: Annotated[ - Literal['accepted'], - Field( - description='draft is indicative and unreserved; committed has firm terms with inventory reserved until expires_at; accepted is the historical snapshot attached to a MediaBuy.', - title='Proposal Status', - ), - ] = 'accepted' - media_buy_id: Annotated[str, Field(min_length=1)] - accepted_at: Any - commercial_terms: Any - terms_digest: Any - - -class BuyProductsResponse1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - status: Literal['completed'] = 'completed' - media_buy_id: Annotated[str, Field(min_length=1)] - name: Annotated[ - str | None, - Field( - description='Persisted human-readable MediaBuy name for trafficking UI display and operational communication. The seller MUST echo a buyer-supplied request name unchanged; when the seller seeded a new MediaBuy name from an already-valid proposal.name, it MUST return that value unchanged here. Existing named MediaBuys return the stored value on amendment or cancellation commitments. This operational metadata is outside accepted_proposal and is not covered by terms_digest. This display label is not an identifier or financial reference.', - max_length=255, - min_length=1, - pattern='\\S', - ), - ] = None - revision: Annotated[SchemaInt, Field(ge=1)] - media_buy_status: media_buy_status_1.MediaBuyStatus | None = None - confirmed_at: AwareDatetime | None = None - accepted_proposal: AcceptedProposal - purchase_bindings: Annotated[ - list[PurchaseBinding], - Field( - description='Execution identities assigned to the immutable purchases. purchase_index is the zero-based position in accepted_proposal.commercial_terms.purchases and disambiguates repeated product IDs.', - min_length=1, - ), - ] - available_actions: list[canonical_media_buy_action.CanonicalMediaBuyAction] - warnings: Annotated[ - list[Warning] | None, - Field( - description='Non-blocking observations about this completed commitment. The MediaBuy was still created or amended exactly as represented. Continuing conditions also appear as indicators on get_media_buys.', - min_length=1, - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - replayed: Literal[True] | None = None - - -class BuyProductsResponse5(BuyProductsResponse1, BuyProductsResponse4): - pass - - -BuyProductsResponse = Annotated[ - BuyProductsResponse5 | BuyProductsResponse6 | BuyProductsResponse7, - Field( - description='Compact direct-purchase result containing the MediaBuy identity and accepted immutable proposal snapshot.', - title='Buy Products Response', - ), -] diff --git a/src/adcp/types/generated_poc/media_buy/change_term.py b/src/adcp/types/generated_poc/media_buy/change_term.py deleted file mode 100644 index 538ea99ed..000000000 --- a/src/adcp/types/generated_poc/media_buy/change_term.py +++ /dev/null @@ -1,100 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/change_term.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, model_validator - -from ..core import ext as ext_1 -from ..core import sla_window -from ..enums import canonical_media_buy_action, canonical_media_buy_action_mode -from . import change_term_constraints - - -class AllowedStatus(StrEnum): - pending_creatives = 'pending_creatives' - pending_start = 'pending_start' - active = 'active' - paused = 'paused' - - -class Condition(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Za-z0-9][A-Za-z0-9_.:-]{0,199}$'} - - -class MediaBuyChangeTerm(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - term_id: Annotated[str, Field(pattern='^[A-Za-z0-9_.:-]+$')] - action: canonical_media_buy_action.CanonicalMediaBuyActionName - service_mode: canonical_media_buy_action_mode.CanonicalMediaBuyActionMode - allowed_statuses: Annotated[ - list[AllowedStatus] | None, - Field( - description='Non-terminal MediaBuy statuses in which this negotiated right may be exercised. When absent, the right applies in every non-terminal status where the canonical action itself is meaningful. This field describes contractual lifecycle scope; available_actions[] remains authoritative for the current instant.', - min_length=1, - ), - ] = None - processing_sla: Annotated[ - sla_window.SlaWindow | None, - Field( - description='Binding elapsed-time acknowledgement and completion commitment. Sellers account for weekends and non-working periods when declaring the maximum.' - ), - ] = None - conditions: Annotated[ - list[Condition] | None, - Field( - description='Opaque stable condition identifiers defined by terms_ref or bilateral commercial documentation. Implementations compare identifiers; they MUST NOT execute or interpret them as instructions.', - min_length=1, - ), - ] = None - constraints: Annotated[ - change_term_constraints.MediaBuyChangeTermConstraints | None, - Field( - description='Portable bounds that buyer and seller SDKs can preflight. Omission means no machine-readable bound was promised; opaque conditions remain unevaluated.' - ), - ] = None - terms_ref: Annotated[ - str | None, - Field( - description="Stable contract reference. Resolving it cannot expand the typed right and MUST use the caller's normal authenticated contract-document path, never ambient seller credentials.", - max_length=1000, - min_length=1, - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Display-only summary; it cannot grant authority, add an action, or override typed fields.', - max_length=1000, - min_length=1, - ), - ] = None - ext: ext_1.ExtensionObject | None = None - - @model_validator(mode='after') - def _validate_constraint_action(self) -> MediaBuyChangeTerm: - if self.constraints is None: - return self - kind = self.constraints.kind - allowed = { - 'budget': { - 'increase_budget', 'decrease_budget', 'reallocate_budget', - 'update_budget_allocation', 'update_spend_target', - }, - 'flight': {'extend_flight', 'shorten_flight', 'update_flight_dates'}, - 'package_count': {'add_packages', 'remove_packages'}, - 'effective_timing': {'pause', 'resume', 'cancel'}, - } - action = self.action.value - if action not in allowed.get(kind, set()): - raise ValueError('constraint kind is incompatible with action') - return self diff --git a/src/adcp/types/generated_poc/media_buy/change_term_constraints.py b/src/adcp/types/generated_poc/media_buy/change_term_constraints.py deleted file mode 100644 index af8157059..000000000 --- a/src/adcp/types/generated_poc/media_buy/change_term_constraints.py +++ /dev/null @@ -1,149 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/change_term_constraints.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat, model_validator - -from ..core import duration - - -class MediaBuyChangeTermConstraints3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['package_count'] = 'package_count' - max_additions: Annotated[ - SchemaInt | None, - Field(description='Maximum packages that may be added by one exercise of the right.', ge=0), - ] = None - max_removals: Annotated[ - SchemaInt | None, - Field( - description='Maximum packages that may be removed by one exercise of the right.', ge=0 - ), - ] = None - max_result_count: Annotated[ - SchemaInt | None, Field(description='Maximum active package count after the change.', ge=0) - ] = None - - - @model_validator(mode='after') - def _require_portable_bound(self) -> MediaBuyChangeTermConstraints3: - if not any(getattr(self, name) is not None for name in ('max_additions', 'max_removals', 'max_result_count')): - raise ValueError('at least one portable constraint bound is required') - return self - - -class Money(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - amount: Annotated[StrictFloat, Field(ge=0.0)] - currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] - - -class MediaBuyChangeTermConstraints1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['budget'] = 'budget' - max_delta_amount: Annotated[ - Money | None, - Field( - description='Maximum absolute amount by which the affected budget may change in the direction named by the action.' - ), - ] = None - max_delta_percent: Annotated[ - StrictFloat | None, - Field( - description='Maximum percentage change relative to the current committed value. Values above 100 are valid for increases greater than the current value.', - ge=0.0, - ), - ] = None - min_result_amount: Annotated[ - Money | None, Field(description='Minimum resulting committed value after the change.') - ] = None - max_result_amount: Annotated[ - Money | None, Field(description='Maximum resulting committed value after the change.') - ] = None - - - @model_validator(mode='after') - def _require_portable_bound(self) -> MediaBuyChangeTermConstraints1: - if not any(getattr(self, name) is not None for name in ('max_delta_amount', 'max_delta_percent', 'min_result_amount', 'max_result_amount')): - raise ValueError('at least one portable constraint bound is required') - return self - - -class MediaBuyChangeTermConstraints2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['flight'] = 'flight' - max_change: Annotated[ - duration.Duration | None, - Field( - description='Maximum extension, shortening, or shift in the direction named by the action.' - ), - ] = None - earliest_result: Annotated[ - AwareDatetime | None, - Field(description='Earliest resulting start or end timestamp accepted for this action.'), - ] = None - latest_result: Annotated[ - AwareDatetime | None, - Field(description='Latest resulting start or end timestamp accepted for this action.'), - ] = None - minimum_notice: Annotated[ - duration.Duration | None, - Field( - description='Minimum elapsed notice before the requested flight change may take effect.' - ), - ] = None - - - @model_validator(mode='after') - def _require_portable_bound(self) -> MediaBuyChangeTermConstraints2: - if not any(getattr(self, name) is not None for name in ('max_change', 'earliest_result', 'latest_result', 'minimum_notice')): - raise ValueError('at least one portable constraint bound is required') - return self - - -class MediaBuyChangeTermConstraints4(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['effective_timing'] = 'effective_timing' - minimum_notice: Annotated[ - duration.Duration | None, - Field( - description='Minimum elapsed notice before pause, resume, cancellation, or another operational action may take effect.' - ), - ] = None - earliest_effective_at: AwareDatetime | None = None - latest_effective_at: AwareDatetime | None = None - - - @model_validator(mode='after') - def _require_portable_bound(self) -> MediaBuyChangeTermConstraints4: - if not any(getattr(self, name) is not None for name in ('minimum_notice', 'earliest_effective_at', 'latest_effective_at')): - raise ValueError('at least one portable constraint bound is required') - return self - - -MediaBuyChangeTermConstraints = Annotated[ - MediaBuyChangeTermConstraints1 - | MediaBuyChangeTermConstraints2 - | MediaBuyChangeTermConstraints3 - | MediaBuyChangeTermConstraints4, - Field( - description='Optional machine-readable bounds on a proposal-bound change right. These constraints narrow the action named by the containing change term; they never grant an additional action or expand the accepted commercial envelope. Sellers omit this object when no portable bound is available, and buyers then treat any opaque conditions as unevaluated rather than assuming the change will be accepted.', - discriminator='kind', - title='Media Buy Change Term Constraints', - ), -] diff --git a/src/adcp/types/generated_poc/media_buy/commercial_terms.py b/src/adcp/types/generated_poc/media_buy/commercial_terms.py deleted file mode 100644 index e9eff5bf8..000000000 --- a/src/adcp/types/generated_poc/media_buy/commercial_terms.py +++ /dev/null @@ -1,180 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/commercial_terms.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat, model_validator - -from ..core import ( - bidding_policy, - brand_key, - business_entity, - canonical_budget_allocation, - canonical_reporting_commitment, - media_buy_frequency_cap, - start_timing, -) -from ..enums import advertiser_industry as advertiser_industry_1 -from ..enums import pacing as pacing_1 -from . import change_term, product_purchase - - -class TotalBudget(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - amount: Annotated[StrictFloat, Field(ge=0.0)] - currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] - - -class Fee(TotalBudget): - pass - - -class CancellationTerms(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - effective_at: AwareDatetime - fee: Fee | None = None - reason: Annotated[str | None, Field(max_length=500, min_length=1)] = None - - -class ReportingCommitment(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - purchase_index: Annotated[SchemaInt, Field(ge=0)] - metrics: Annotated[ - list[canonical_reporting_commitment.CanonicalReportingCommitment], Field(min_length=1) - ] - - -class CommercialTerms(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - source_feed_version: Annotated[ - str | None, - Field( - description='Wholesale product feed version against which direct published offers were accepted. Omitted when the seller authored terms outside a wholesale snapshot.', - min_length=1, - ), - ] = None - source_pricing_version: Annotated[ - str | None, - Field( - description='Pricing-layer version against which published rates were accepted.', - min_length=1, - ), - ] = None - brand: brand_key.BrandKey - advertiser_industry: advertiser_industry_1.AdvertiserIndustry | None = None - purchases: Annotated[ - list[product_purchase.ProductPurchase], - Field( - description='Exact canonical product, pricing, format, catalog, budget, targeting, bidding, optimization, resolved flight, measurement, and performance terms in the commercial envelope.', - min_length=1, - ), - ] - start_time: start_timing.StartTiming - end_time: AwareDatetime - total_budget: TotalBudget | None = None - daily_budget_cap: Annotated[ - StrictFloat | None, - Field( - description='Hard aggregate daily spend ceiling accepted as part of these terms. It bounds total spend without creating purchase allocations.', - ge=0.0, - ), - ] = None - frequency_cap: Annotated[ - media_buy_frequency_cap.MediaBuyFrequencyCap | None, - Field( - description='Hard MediaBuy-level cap accepted as part of these terms. One counter aggregates exposures across every purchase; purchase targeting caps remain independently binding.' - ), - ] = None - budget_cap_timezone: Annotated[ - str | None, - Field( - description='Shared IANA calendar-day boundary for aggregate and purchase daily caps in these terms.', - min_length=1, - ), - ] = None - budget_allocation: canonical_budget_allocation.CanonicalBudgetAllocation | None = None - pacing: pacing_1.Pacing | None = None - bidding: Annotated[ - bidding_policy.BiddingPolicy | None, - Field( - description="Media-buy bidding policy. A proposal answering criteria.outcome_target.cost_per states here the cost the seller can plan to, which the buyer adopts on acceptance: the requested strength, and an amount greater than or equal to the ask (the ask when the seller can forecast goal volume under it within the buyer's budget, otherwise the lowest such amount), denominated in the purchases' pricing currency, which equals cost_per.currency. It is an execution control, not an expected price; when the planned spend at that amount is below total_budget, forecast points carry metrics.spend. See outcome-target.json for goal binding." - ), - ] = None - invoice_recipient: business_entity.BusinessEntity | None = None - purchase_order_ref: Annotated[str | None, Field(max_length=255, min_length=1)] = None - agency_estimate_number: Annotated[str | None, Field(max_length=100)] = None - reporting_commitments: Annotated[ - list[ReportingCommitment] | None, - Field( - description='Binding reporting contract keyed by position in purchases. Amendments preserve prior entries and add metrics with effective_at; seller-assigned package IDs live in the execution binding, outside this digest.', - min_length=1, - ), - ] = None - cancellation_terms: CancellationTerms | None = None - change_terms: Annotated[ - list[change_term.MediaBuyChangeTerm] | None, - Field( - description='Binding buyer change rights included in the commercial envelope and therefore covered by terms_digest. Entries are uniquely keyed by action. When this field is present, an omitted action is not a negotiated change right. Omission of the entire field means legacy-unspecified rights, not a prohibition.', - min_length=1, - ), - ] = None - - @model_validator(mode='after') - def _validate_change_term_set(self) -> CommercialTerms: - if self.change_terms is None: - return self - actions = [term.action.value for term in self.change_terms] - term_ids = [term.term_id for term in self.change_terms] - if len(set(actions)) != len(actions): - raise ValueError('change_terms must be uniquely keyed by action') - if len(set(term_ids)) != len(term_ids): - raise ValueError('change_terms term_id values must be unique') - currencies = set() - for purchase in self.purchases: - if purchase.pricing is None: - raise ValueError('accepted commercial-term purchases require resolved pricing') - currencies.add(purchase.pricing.currency) - for term in self.change_terms: - if term.constraints is None: - continue - constraint = term.constraints - if constraint.kind == 'budget': - money_fields = ( - constraint.max_delta_amount, - constraint.min_result_amount, - constraint.max_result_amount, - ) - if any(money is not None and money.currency not in currencies for money in money_fields): - raise ValueError('change-term monetary constraint currency must match purchases') - if ( - constraint.min_result_amount is not None - and constraint.max_result_amount is not None - and constraint.min_result_amount.amount > constraint.max_result_amount.amount - ): - raise ValueError('change-term minimum result exceeds maximum result') - elif constraint.kind == 'flight': - if ( - constraint.earliest_result is not None - and constraint.latest_result is not None - and constraint.earliest_result > constraint.latest_result - ): - raise ValueError('change-term earliest result exceeds latest result') - elif constraint.kind == 'effective_timing' and ( - constraint.earliest_effective_at is not None - and constraint.latest_effective_at is not None - and constraint.earliest_effective_at > constraint.latest_effective_at - ): - raise ValueError('change-term earliest effective time exceeds latest time') - return self diff --git a/src/adcp/types/generated_poc/media_buy/control_media_buy_async_response_input_required.py b/src/adcp/types/generated_poc/media_buy/control_media_buy_async_response_input_required.py deleted file mode 100644 index f8fc35474..000000000 --- a/src/adcp/types/generated_poc/media_buy/control_media_buy_async_response_input_required.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/control_media_buy_async_response_input_required.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from ..core.compact_task_input_required import CompactTaskInputRequired - - -class ControlMediaBuyInputRequired(CompactTaskInputRequired): - pass diff --git a/src/adcp/types/generated_poc/media_buy/control_media_buy_async_response_submitted.py b/src/adcp/types/generated_poc/media_buy/control_media_buy_async_response_submitted.py deleted file mode 100644 index 51056eb18..000000000 --- a/src/adcp/types/generated_poc/media_buy/control_media_buy_async_response_submitted.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/control_media_buy_async_response_submitted.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from ..core.compact_task_submitted import CompactTaskSubmitted - - -class ControlMediaBuySubmitted(CompactTaskSubmitted): - pass diff --git a/src/adcp/types/generated_poc/media_buy/control_media_buy_async_response_working.py b/src/adcp/types/generated_poc/media_buy/control_media_buy_async_response_working.py deleted file mode 100644 index 97c710f41..000000000 --- a/src/adcp/types/generated_poc/media_buy/control_media_buy_async_response_working.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/control_media_buy_async_response_working.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from ..core.compact_task_working import CompactTaskWorking - - -class ControlMediaBuyWorking(CompactTaskWorking): - pass diff --git a/src/adcp/types/generated_poc/media_buy/control_media_buy_request.py b/src/adcp/types/generated_poc/media_buy/control_media_buy_request.py deleted file mode 100644 index 614d7325e..000000000 --- a/src/adcp/types/generated_poc/media_buy/control_media_buy_request.py +++ /dev/null @@ -1,133 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/control_media_buy_request.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictBool, StrictFloat, model_validator - -from ..core import bidding_policy, canonical_account_ref, canonical_budget_allocation -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core import media_buy_frequency_cap -from ..core import push_notification_config as push_notification_config_1 -from ..core import reporting_webhook as reporting_webhook_1 -from ..enums import pacing as pacing_1 -from . import package_control - - -class TotalBudget(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - amount: Annotated[StrictFloat, Field(ge=0.0)] - currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] - - -class ControlMediaBuyRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?)?$', - ), - ] = None - adcp_major_version: Annotated[ - SchemaInt | None, - Field( - deprecated=True, - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - idempotency_key: Annotated[ - str, Field(max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$') - ] - account: canonical_account_ref.CanonicalAccountReference - media_buy_id: Annotated[str, Field(min_length=1)] - revision: Annotated[ - SchemaInt, - Field( - description='Required optimistic-concurrency revision from the latest MediaBuy snapshot.', - ge=1, - ), - ] - name: Annotated[ - str | None, - Field( - description='Replace the human-readable MediaBuy name as revision-checked operational metadata. This display label is not an identifier, financial reference, or change to the accepted commercial terms.', - max_length=255, - min_length=1, - pattern='\\S', - ), - ] = None - paused: StrictBool | None = None - canceled: Annotated[ - Literal[True] | None, - Field( - description='Exercise an already-accepted unilateral cancellation right. A cancellation requiring seller agreement is requested by refining the accepted proposal.' - ), - ] = None - cancellation_reason: Annotated[str | None, Field(max_length=500, min_length=1)] = None - total_budget: TotalBudget | None = None - daily_budget_cap: Annotated[ - StrictFloat | None, - Field( - description='Replace the hard aggregate daily cap; null removes it. Numeric changes apply immediately with current-cap-day spend counted and do not redistribute purchase caps.', - ge=0.0, - ), - ] = None - frequency_cap: Annotated[ - media_buy_frequency_cap.MediaBuyFrequencyCap | None, - Field( - description='Replace the shared MediaBuy frequency cap; null removes it. The change applies immediately without resetting counters: qualifying prior exposures still count in the resulting active window. Sellers MUST reject the complete mutation with UNSUPPORTED_FEATURE, before any change, if the cap is outside declared constraints or any active package cannot participate in the resulting shared counter, and MUST NOT clamp it. Requires update_media_buy_frequency_cap in available_actions.' - ), - ] = None - budget_cap_timezone: Annotated[ - str | None, - Field( - description='Replace the shared IANA cap-day timezone override; null restores the default selected by budget_capping.timezone_basis (Account.timezone or fixed_timezone). A timezone change begins at the next boundary under the previously effective timezone.', - min_length=1, - ), - ] = None - budget_allocation: canonical_budget_allocation.CanonicalBudgetAllocation | None = None - pacing: Annotated[ - pacing_1.Pacing | None, - Field( - description='Replace aggregate media-buy pacing. In seller-optimized allocation, a seller declaring media_buy.features.seller_optimized_budget MUST accept omission and `even`; it MAY reject `asap` or `front_loaded` with UNSUPPORTED_FEATURE (error.field `pacing`) before any provider mutation and MUST NOT silently coerce them to `even`. Fixed-allocation semantics are unchanged.' - ), - ] = None - bidding: bidding_policy.BiddingPolicy | None = None - packages: Annotated[ - list[package_control.PackageControl] | None, - Field( - description='Operational patches keyed by package_id. Each package_id MUST appear at most once; sellers reject duplicate IDs atomically.', - min_length=1, - ), - ] = None - reporting_webhook: reporting_webhook_1.ReportingWebhook | None = None - governance_context: Annotated[str | None, Field(max_length=4096, min_length=1)] = None - push_notification_config: push_notification_config_1.PushNotificationConfig | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - @model_validator(mode='after') - def _require_schema_required_group(self) -> ControlMediaBuyRequest: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('name',), ('paused',), ('canceled',), ('total_budget',), ('daily_budget_cap',), ('frequency_cap',), ('budget_cap_timezone',), ('budget_allocation',), ('pacing',), ('bidding',), ('packages',), ('reporting_webhook',),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'ControlMediaBuyRequest requires at least one of these field groups: name | paused | canceled | total_budget | daily_budget_cap | frequency_cap | budget_cap_timezone | budget_allocation | pacing | bidding | packages | reporting_webhook' - ) diff --git a/src/adcp/types/generated_poc/media_buy/control_media_buy_response.py b/src/adcp/types/generated_poc/media_buy/control_media_buy_response.py deleted file mode 100644 index f0774a596..000000000 --- a/src/adcp/types/generated_poc/media_buy/control_media_buy_response.py +++ /dev/null @@ -1,97 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/control_media_buy_response.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarStr -from pydantic import AwareDatetime, ConfigDict, Field - -from ..core import canonical_media_buy_action -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 -from ..core.warning import Warning as Warning_1 -from ..enums import media_buy_status as media_buy_status_1 - - -class AffectedPackageId(ScalarStr): - __slots__ = () - _constraints = {'min_length': 1} - - -class Code(StrEnum): - inventory_shortfall_forecast = 'inventory_shortfall_forecast' - flight_change_creates_pacing_risk = 'flight_change_creates_pacing_risk' - fields_ignored_due_to_precedence = 'fields_ignored_due_to_precedence' - - -class ControlMediaBuyResponse2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - status: Literal['failed'] = 'failed' - errors: Annotated[list[error.Error], Field(min_length=1)] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - replayed: Literal[True] | None = None - - -class ControlMediaBuyResponse3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - status: Literal['submitted'] = 'submitted' - task_id: Annotated[str, Field(min_length=1)] - message: Annotated[str | None, Field(max_length=2000)] = None - errors: list[error.Error] | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - replayed: Literal[True] | None = None - - -class Warning(Warning_1): - code: Annotated[ - Literal['inventory_shortfall_forecast'], - Field( - description='Closed warning vocabulary for the negotiated AdCP release. A non-blocking condition observed while an operation still succeeded. New values are added through normal protocol releases; buyers validate against the negotiated release.', - title='Warning Code', - ), - ] = 'inventory_shortfall_forecast' - - -class ControlMediaBuyResponse1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - status: Literal['completed'] = 'completed' - media_buy_id: Annotated[str, Field(min_length=1)] - revision: Annotated[SchemaInt, Field(ge=1)] - media_buy_status: media_buy_status_1.MediaBuyStatus | None = None - implementation_date: AwareDatetime | None = None - affected_package_ids: list[AffectedPackageId] | None = None - available_actions: list[canonical_media_buy_action.CanonicalMediaBuyAction] | None = None - warnings: Annotated[ - list[Warning] | None, - Field( - description='Non-blocking observations about this completed control. The control was still applied exactly as represented. Continuing conditions also appear as indicators on get_media_buys.', - min_length=1, - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - replayed: Literal[True] | None = None - - -ControlMediaBuyResponse = Annotated[ - ControlMediaBuyResponse1 | ControlMediaBuyResponse2 | ControlMediaBuyResponse3, - Field( - description='Result of applying operational controls without embedding the package or creative object graphs.', - discriminator='status', - title='Control Media Buy Response', - ), -] diff --git a/src/adcp/types/generated_poc/media_buy/create_media_buy_async_response_input_required.py b/src/adcp/types/generated_poc/media_buy/create_media_buy_async_response_input_required.py deleted file mode 100644 index 414b45bfb..000000000 --- a/src/adcp/types/generated_poc/media_buy/create_media_buy_async_response_input_required.py +++ /dev/null @@ -1,37 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/create_media_buy_async_response_input_required.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 - - -class Reason(StrEnum): - APPROVAL_REQUIRED = 'APPROVAL_REQUIRED' - BUDGET_EXCEEDS_LIMIT = 'BUDGET_EXCEEDS_LIMIT' - - -class CreateMediaBuyInputRequired(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - reason: Annotated[ - Reason | None, Field(description='Reason code indicating why input is needed') - ] = None - errors: Annotated[ - list[error.Error] | None, - Field( - description='Optional validation errors or warnings for debugging purposes. Helps explain why input is required.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/create_media_buy_async_response_submitted.py b/src/adcp/types/generated_poc/media_buy/create_media_buy_async_response_submitted.py deleted file mode 100644 index ff9d898ff..000000000 --- a/src/adcp/types/generated_poc/media_buy/create_media_buy_async_response_submitted.py +++ /dev/null @@ -1,47 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/create_media_buy_async_response_submitted.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 - - -class CreateMediaBuySubmitted(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Literal['submitted'], - Field( - description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, which carries media-buy lifecycle state in `media_buy_status`. See task-status.json for the full task-status enum.' - ), - ] = 'submitted' - task_id: Annotated[ - str, - Field( - description='Task handle the buyer uses with get_task_status (or the legacy AdCP tasks/get alias), and that the seller references on push-notification callbacks. The media_buy_id is issued on the completion artifact, not here. This AdCP application-layer handle remains the snake_case task_id in every transport payload and is distinct from any transport-native A2A Task id.' - ), - ] - message: Annotated[ - str | None, - Field( - description="Optional human-readable explanation of why the task is submitted — e.g., 'Awaiting IO signature from sales team; typical turnaround 2–4 hours.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", - max_length=2000, - ), - ] = None - errors: Annotated[ - list[error.Error] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/create_media_buy_async_response_working.py b/src/adcp/types/generated_poc/media_buy/create_media_buy_async_response_working.py deleted file mode 100644 index d7feb5362..000000000 --- a/src/adcp/types/generated_poc/media_buy/create_media_buy_async_response_working.py +++ /dev/null @@ -1,31 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/create_media_buy_async_response_working.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictFloat - -from ..core import context as context_1 -from ..core import ext as ext_1 - - -class CreateMediaBuyWorking(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - percentage: Annotated[ - StrictFloat | None, Field(description='Completion percentage (0-100)', ge=0.0, le=100.0) - ] = None - current_step: Annotated[ - str | None, Field(description='Current step or phase of the operation') - ] = None - total_steps: Annotated[ - SchemaInt | None, Field(description='Total number of steps in the operation', ge=1) - ] = None - step_number: Annotated[SchemaInt | None, Field(description='Current step number', ge=1)] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/create_media_buy_request.py b/src/adcp/types/generated_poc/media_buy/create_media_buy_request.py deleted file mode 100644 index 236bd73a0..000000000 --- a/src/adcp/types/generated_poc/media_buy/create_media_buy_request.py +++ /dev/null @@ -1,330 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/create_media_buy_request.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from collections.abc import Sequence -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat, model_validator - -from ..core import account_ref, bidding_policy, brand_ref -from ..core import budget_allocation as budget_allocation_1 -from ..core import business_entity -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core import media_buy_frequency_cap -from ..core import push_notification_config as push_notification_config_1 -from ..core import reporting_webhook as reporting_webhook_1 -from ..core import start_timing -from ..core.opportunity_context import OpportunityContext -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import advertiser_industry as advertiser_industry_1 -from ..enums import auth_scheme -from ..enums import pacing as pacing_1 -from . import package_request - - -class Status(StrEnum): - open = 'open' - closed = 'closed' - - -class TotalBudget(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - amount: Annotated[StrictFloat, Field(description='Total budget amount', ge=0.0)] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - - -class IoAcceptance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - io_id: Annotated[ - str, Field(description="The io_id from the proposal's insertion_order being accepted") - ] - accepted_at: Annotated[ - AwareDatetime, Field(description='ISO 8601 timestamp when the IO was accepted') - ] - signatory: Annotated[ - str, - Field( - description='Who accepted the IO — agent identifier or human name', - max_length=250, - min_length=1, - ), - ] - signature_id: Annotated[ - str | None, - Field( - description='Reference to the electronic signature from the signing service, when signing_url was used' - ), - ] = None - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[auth_scheme.AuthenticationScheme], - Field( - description="Array of authentication schemes. ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD use the RFC 9421 webhook signing profile instead.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class DeliveryMode(StrEnum): - realtime = 'realtime' - batched = 'batched' - - -class BatchFrequency(StrEnum): - hourly = 'hourly' - daily = 'daily' - - -class ArtifactWebhook(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - url: Annotated[AnyUrl, Field(description='Webhook endpoint URL for artifact delivery')] - token: Annotated[ - str | None, - Field( - description='Optional client-provided token for webhook validation. Echoed back in webhook payload to validate request authenticity.', - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication, - Field( - deprecated=True, - description="Legacy authentication configuration for webhook delivery (A2A-compatible). Opts the receiver into Bearer or HMAC-SHA256 signing. Both schemes are deprecated; the preferred signing profile for new integrations is RFC 9421, where the seller signs with a key published at its brand.json agents[] entry and the buyer verifies against the seller's JWKS — no shared secret crosses the wire (see docs/building/implementation/security.mdx#webhook-callbacks). This field is required in AdCP 3.x; the requirement is removed in AdCP 4.0 when the default RFC 9421 path becomes the only path.", - ), - ] - delivery_mode: Annotated[ - DeliveryMode, - Field( - description="How artifacts are delivered. 'realtime' pushes artifacts as impressions occur. 'batched' aggregates artifacts and pushes periodically (see batch_frequency)." - ), - ] - batch_frequency: Annotated[ - BatchFrequency | None, - Field( - description="For batched delivery, how often to push artifacts. Required when delivery_mode is 'batched'." - ), - ] = None - sampling_rate: Annotated[ - StrictFloat | None, - Field( - description='Fraction of impressions to include (0-1). 1.0 = all impressions, 0.1 = 10% sample. Default: 1.0', - ge=0.0, - le=1.0, - ), - ] = None - - -class Opportunity(OpportunityContext): - status: Annotated[ - Literal['closed'], - Field( - description='Whether the planning cycle remains open. On calls after initial creation, omission means no status update and MUST NOT reopen or close the opportunity implicitly, except that successful create_media_buy proposal execution explicitly infers accepted closure.' - ), - ] = 'closed' - - -class CreateMediaBuyRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - governance_context: Annotated[ - str | None, - Field( - description='Opaque intent authorization for this media-buy commitment. Required when governance applies to the resolved account.', - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for this request. If a request with the same idempotency_key and account has already been processed, the seller returns the existing media buy rather than creating a duplicate. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - plan_id: Annotated[ - str | None, - Field( - deprecated=True, - description='DEPRECATED on seller-facing requests. New buyers send the approved governance_context on the protocol envelope; the seller forwards that opaque context and does not need the plan identifier. If both are present, the governance agent MUST reject a mismatch. Removed in 4.0.', - ), - ] = None - account: Annotated[ - account_ref.AccountReference, - Field( - description='Account to bill for this media buy. Pass a natural key (brand, operator, optional sandbox) or a seller-assigned account_id from list_accounts.' - ), - ] - proposal_id: Annotated[ - str | None, - Field( - description="ID of the exact committed proposal snapshot to execute. With total_budget, the publisher creates packages using the proposal's fixed percentages or seller-optimized constraints. Mutually exclusive: provide packages or proposal_id, not both. AdCP 3.2 request_proposals and ordinary refine_proposals revisions issue drafts; refine_proposals action finalize creates the executable committed hold. Sellers reject draft, declined, or previously executed snapshots, while exact retries with the original idempotency key replay historical success. Changed commercial terms are issued under a new proposal_id, so no separate proposal version is required." - ), - ] = None - opportunity: Annotated[ - Opportunity | None, - Field( - description='Optional planning-cycle closure. Sellers infer successful proposal execution as closed with close_reason accepted_with_seller when status is omitted; when status is present it MUST be closed with that reason. If the proposal was issued under an opportunity_id, a supplied ID MUST match it.' - ), - ] = None - total_budget: Annotated[ - TotalBudget | None, - Field( - description='Hard aggregate lifetime budget for the media buy. Required when executing a proposal and for seller-optimized explicit packages. Optional in fixed explicit-package mode; when present there, amount MUST equal the sum of package budgets. For a fixed proposal, the publisher applies allocation percentages to this amount. For a seller-optimized proposal or explicit buy, packages draw dynamically from this shared total.' - ), - ] = None - daily_budget_cap: Annotated[ - StrictFloat | None, - Field( - description='Optional hard aggregate daily spend ceiling in the media-buy currency. It limits total spend without allocating package amounts. Package caps are subordinate and need not sum to it. Requires advertised media_buy budget-capping scope; otherwise rejected with UNSUPPORTED_FEATURE.', - ge=0.0, - ), - ] = None - frequency_cap: Annotated[ - media_buy_frequency_cap.MediaBuyFrequencyCap | None, - Field( - description='Optional max-impression cap using one counter across explicit packages. Requires advertised aggregate_frequency_capping and compatible media_buy_support on every product; otherwise sellers MUST reject with UNSUPPORTED_FEATURE before any mutation and MUST NOT silently drop, soften, or clamp the cap. Omit when executing proposal_id because accepted terms are authoritative.' - ), - ] = None - budget_cap_timezone: Annotated[ - str | None, - Field( - description='Optional shared IANA day boundary override for all caps. Requires buyer_timezone_override; otherwise rejected with UNSUPPORTED_FEATURE. When omitted, budget_capping.timezone_basis selects Account.timezone or the advertised fixed_timezone.', - min_length=1, - ), - ] = None - budget_allocation: Annotated[ - budget_allocation_1.BudgetAllocation | None, - Field( - description='How budget is allocated across explicit packages. Omission means fixed allocation (legacy-compatible). Buyer agents SHOULD send budget_allocation explicitly ({mode: "fixed"} or seller_optimized) rather than rely on omission, and when the principal\'s instruction does not determine whether the budget is one shared pool across packages or split per package, SHOULD ask the principal rather than guess. seller_optimized requires advertised media_buy.features.seller_optimized_budget; inside it, package budget caps, min_spend_target, and package pacing each require their own advertised sub-capability (seller_optimized_package_budgets, seller_optimized_min_spend_targets, seller_optimized_package_pacing), otherwise the request is rejected with UNSUPPORTED_FEATURE before any over-subscription validation. In proposal mode the committed proposal supplies this configuration and callers MUST omit it here.' - ), - ] = None - packages: Annotated[ - Sequence[package_request.PackageRequest] | None, - Field( - description='Array of package configurations. Required when not using proposal_id. Mutually exclusive: provide packages or proposal_id, not both. Fixed allocation requires budget on every package. Seller-optimized allocation permits package budget to be omitted or to act as a hard cap. When executing a proposal, omit packages; the seller derives them from the committed proposal.', - min_length=1, - ), - ] = None - brand: Annotated[ - brand_ref.BrandReference, - Field( - description='Brand reference for this media buy. Resolved to full brand identity at execution time from brand.json or the registry.' - ), - ] - advertiser_industry: Annotated[ - advertiser_industry_1.AdvertiserIndustry | None, - Field( - description="Industry classification for this specific campaign. A brand may operate across multiple industries (brand.json industries field), but each media buy targets one. For example, a consumer health company running a wellness campaign sends 'healthcare.wellness', not 'cpg'. Sellers map this to platform-native codes (e.g., Spotify ADV categories, LinkedIn industry IDs). When omitted, sellers may infer from the brand manifest's industries field." - ), - ] = None - invoice_recipient: Annotated[ - business_entity.BusinessEntity | None, - Field( - description="Override the account's default billing entity for this specific buy. When provided, the seller invoices this entity instead. The seller MUST validate the invoice recipient is authorized for this account. When governance_agents are configured, the seller MUST include invoice_recipient in the check_governance request." - ), - ] = None - io_acceptance: Annotated[ - IoAcceptance | None, - Field( - description="Acceptance of an insertion order from a committed proposal. Required when the proposal's insertion_order has requires_signature: true. References the io_id from the proposal's insertion_order." - ), - ] = None - po_number: Annotated[str | None, Field(description='Purchase order number for tracking')] = None - name: Annotated[ - str | None, - Field( - description='Human-readable name for this media buy, shared by buyer and seller for trafficking UI display and operational communication. When supplied, the seller MUST persist it and echo it unchanged on the create success response and subsequent get_media_buys reads. This display label is not an identifier or financial reference.', - max_length=255, - min_length=1, - pattern='\\S', - ), - ] = None - agency_estimate_number: Annotated[ - str | None, - Field( - description="Agency estimate or authorization number. Primary financial reference for broadcast buys — links the order to the agency's media plan and billing system. Travels with the order and creative traffic identifiers through the transaction lifecycle.", - max_length=100, - ), - ] = None - start_time: start_timing.StartTiming - end_time: Annotated[ - AwareDatetime, Field(description='Campaign end date/time in ISO 8601 format') - ] - pacing: Annotated[ - pacing_1.Pacing | None, - Field( - description="Aggregate pacing strategy for the media-buy budget across the media-buy flight. This controls how much the buy spends over time. Package pacing is subordinate and influences which package receives the aggregate spend; package pacing MUST NOT cause aggregate delivery to exceed this strategy. Defaults to even when total_budget is present. When executing a proposal that carries pacing, omit this field or send the identical value; the seller MUST reject a conflicting override with TERMS_REJECTED. In a seller-optimized buy, a seller declaring media_buy.features.seller_optimized_budget MUST accept omission and `even`; it MAY reject `asap` or `front_loaded` with UNSUPPORTED_FEATURE (error.field `pacing`) before any provider mutation and MUST NOT silently coerce them to `even`. Pacing carried by the seller's own committed proposal is not subject to that rejection. Fixed-allocation semantics are unchanged." - ), - ] = None - bidding: Annotated[ - bidding_policy.BiddingPolicy | None, - Field( - description="Complete media-buy bidding default inherited by packages that omit package.bidding. `{automatic:true}` records an explicit automatic policy. In seller-optimized mode, cost_per/roas bind to the primary budget_allocation_1.optimization_goals goal. In fixed mode, inherited cost_per is valid only when inheriting package primary-goal result units are compatible; inherited roas requires value-bearing primary goals. Every monetary field uses total_budget.currency or the single currency derived for the media buy, and every affected pricing option MUST declare that currency. Sellers MUST reject incompatible units, combinations, currency, or overrides before mutation with BIDDING_PLACEMENT_CONFLICT. Media-buy bidding combined with any inheriting package's legacy bid_price or legacy monetary optimization-goal target is ambiguous and MUST be rejected with AMBIGUOUS_BIDDING_POLICY." - ), - ] = None - paused: Annotated[ - StrictBool | None, - Field( - description="Create the media buy in a paused delivery state. When true, and the buy would otherwise be active because creatives are assigned and the flight has started, the seller returns media_buy_status 'paused'. Setup blockers still take precedence: a buy with no creatives remains 'pending_creatives', and a future-dated buy remains 'pending_start' until its flight can start. Defaults to false." - ), - ] = False - push_notification_config: Annotated[ - push_notification_config_1.PushNotificationConfig | None, - Field( - description='Optional webhook configuration for async task status notifications. Publisher will send webhooks when status changes (working, input-required, completed, failed, canceled). Buyers SHOULD supply `push_notification_config_1.operation_id` as the canonical correlation value; publishers echo that field back verbatim in webhook payloads and MUST NOT parse the URL to derive it.' - ), - ] = None - reporting_webhook: Annotated[ - reporting_webhook_1.ReportingWebhook | None, - Field(description='Optional webhook configuration for automated reporting delivery'), - ] = None - artifact_webhook: Annotated[ - ArtifactWebhook | None, - Field( - description='Optional webhook configuration for content artifact delivery. Used by governance agents to validate content adjacency. Seller pushes artifacts to this endpoint; orchestrator forwards to governance agent for validation.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - @model_validator(mode='after') - def _require_schema_required_group(self) -> CreateMediaBuyRequest: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('packages',), ('packages', 'total_budget', 'budget_allocation'), ('proposal_id', 'total_budget'),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'CreateMediaBuyRequest requires at least one of these field groups: packages | packages+total_budget+budget_allocation | proposal_id+total_budget' - ) diff --git a/src/adcp/types/generated_poc/media_buy/create_media_buy_response.py b/src/adcp/types/generated_poc/media_buy/create_media_buy_response.py deleted file mode 100644 index bc099d3bc..000000000 --- a/src/adcp/types/generated_poc/media_buy/create_media_buy_response.py +++ /dev/null @@ -1,107 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/create_media_buy_response.json -# timestamp: preserved-by-post-generate-fixes - -from __future__ import annotations - -from typing import Annotated, Any, Literal, TypeAlias - -from pydantic import AwareDatetime, ConfigDict, Field, StringConstraints, model_validator - -from ..core.version_envelope import AdcpVersionEnvelope -from ..core import account as account_1 -from ..core import business_entity as business_entity_1 -from ..core import context as context_1 -from ..core import error as error_1 -from ..core import ext as ext_1 -from ..core import media_buy_available_action as media_buy_available_action_1 -from ..core import media_buy_frequency_cap as media_buy_frequency_cap_1 -from ..core import package as package_1 -from ..core import planned_delivery as planned_delivery_1 -from ..core import warning as warning_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..enums import media_buy_status as media_buy_status_1 -from ..enums import media_buy_valid_action as media_buy_valid_action_1 -from ..enums import pacing as pacing_1 -from ..enums import task_status as task_status_1 -from adcp.types.media_buy_status_helpers import MEDIA_BUY_LEGACY_STATUS_VALUES, unwrap_enum_value - - -class CreateMediaBuyResponse1(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - status: Literal['completed'] = 'completed' - proposal_id: Annotated[str, StringConstraints(min_length=1)] | None = None - media_buy_id: str - name: Annotated[str, StringConstraints(pattern='\\S', min_length=1, max_length=255)] | None = None - account: account_1.Account | None = None - invoice_recipient: business_entity_1.BusinessEntity | None = None - media_buy_status: media_buy_status_1.MediaBuyStatus | None = None - confirmed_at: AwareDatetime | None - creative_deadline: AwareDatetime | None = None - revision: Annotated[int, Field(ge=1)] - currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] | None = None - total_budget: Annotated[float, Field(ge=0)] | None = None - daily_budget_cap: Annotated[float, Field(ge=0)] | None = None - frequency_cap: media_buy_frequency_cap_1.MediaBuyFrequencyCap | None = None - budget_cap_timezone: str | None = None - budget_allocation: Any | None = None - pacing: pacing_1.Pacing | None = None - bidding: Any | None = None - valid_actions: list[media_buy_valid_action_1.MediaBuyValidAction] | None = None - available_actions: list[media_buy_available_action_1.MediaBuyAvailableAction] | None = None - packages: list[package_1.Package] - planned_delivery: planned_delivery_1.PlannedDelivery | None = None - warnings: list[warning_1.Warning] | None = None - sandbox: bool | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - @model_validator(mode='before') - @classmethod - def _normalize_legacy_status(cls, data: Any) -> Any: - if not isinstance(data, dict): - return data - raw_status = unwrap_enum_value(data.get('status')) - media_buy_status = unwrap_enum_value(data.get('media_buy_status')) - if raw_status is None: - data = dict(data) - data['status'] = 'completed' - elif raw_status == 'completed': - data = dict(data) - data['status'] = 'completed' - elif media_buy_status is None and raw_status in MEDIA_BUY_LEGACY_STATUS_VALUES: - data = dict(data) - data['media_buy_status'] = raw_status - data['status'] = 'completed' - elif media_buy_status is not None and raw_status == media_buy_status: - data = dict(data) - data['status'] = 'completed' - return data - - -class CreateMediaBuyResponse2(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - errors: Annotated[list[error_1.Error], Field(min_length=1)] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class CreateMediaBuyResponse3(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow', validate_default=True) - status: Literal[task_status_1.TaskStatus.submitted] = task_status_1.TaskStatus.submitted - task_id: str - message: Annotated[str, StringConstraints(max_length=2000)] | None = None - errors: list[error_1.Error] | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -CreateMediaBuyResponse: TypeAlias = CreateMediaBuyResponse1 | CreateMediaBuyResponse2 | CreateMediaBuyResponse3 - - -__all__ = [ - 'CreateMediaBuyResponse', - 'CreateMediaBuyResponse1', - 'CreateMediaBuyResponse2', - 'CreateMediaBuyResponse3', -] diff --git a/src/adcp/types/generated_poc/media_buy/decline_proposals_async_response_input_required.py b/src/adcp/types/generated_poc/media_buy/decline_proposals_async_response_input_required.py deleted file mode 100644 index 0b5157524..000000000 --- a/src/adcp/types/generated_poc/media_buy/decline_proposals_async_response_input_required.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/decline_proposals_async_response_input_required.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from ..core.compact_task_input_required import CompactTaskInputRequired - - -class DeclineProposalsInputRequired(CompactTaskInputRequired): - pass diff --git a/src/adcp/types/generated_poc/media_buy/decline_proposals_async_response_submitted.py b/src/adcp/types/generated_poc/media_buy/decline_proposals_async_response_submitted.py deleted file mode 100644 index 841dcf86a..000000000 --- a/src/adcp/types/generated_poc/media_buy/decline_proposals_async_response_submitted.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/decline_proposals_async_response_submitted.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from ..core.compact_task_submitted import CompactTaskSubmitted - - -class DeclineProposalsSubmitted(CompactTaskSubmitted): - pass diff --git a/src/adcp/types/generated_poc/media_buy/decline_proposals_async_response_working.py b/src/adcp/types/generated_poc/media_buy/decline_proposals_async_response_working.py deleted file mode 100644 index 39dec4352..000000000 --- a/src/adcp/types/generated_poc/media_buy/decline_proposals_async_response_working.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/decline_proposals_async_response_working.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from ..core.compact_task_working import CompactTaskWorking - - -class DeclineProposalsWorking(CompactTaskWorking): - pass diff --git a/src/adcp/types/generated_poc/media_buy/decline_proposals_request.py b/src/adcp/types/generated_poc/media_buy/decline_proposals_request.py deleted file mode 100644 index c5adf354f..000000000 --- a/src/adcp/types/generated_poc/media_buy/decline_proposals_request.py +++ /dev/null @@ -1,71 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/decline_proposals_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field - -from ..core import context as context_1 -from ..core import opportunity_context -from ..core import push_notification_config as push_notification_config_1 -from . import proposal_decline - - -class DeclineProposalsRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?)?$', - ), - ] = None - adcp_major_version: Annotated[ - SchemaInt | None, - Field( - deprecated=True, - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='MCP compatibility field: servers ignore this value; A2A uses transport-native Message/Task contextId.', - min_length=1, - ), - ] = None - context: context_1.ContextObject | None = None - governance_context: Annotated[str | None, Field(max_length=4096, min_length=1)] = None - push_notification_config: push_notification_config_1.PushNotificationConfig | None = None - idempotency_key: Annotated[ - str, - Field( - description='Client-generated key required for retry-safe proposal decline.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - declines: Annotated[ - list[proposal_decline.ProposalDecline], - Field( - description='Proposal declines to apply. proposal_id is the semantic uniqueness key and values MUST be unique even when two entries otherwise differ; implementations enforce this rule because JSON Schema uniqueItems only compares whole objects. Results preserve request order.', - max_length=25, - min_length=1, - ), - ] - opportunity: Annotated[ - opportunity_context.OpportunityContext | None, - Field( - description='Optional planning-cycle update. Every named proposal MUST belong to this opportunity_id. Sellers apply the update only when every result is declined; if any result is unable, the opportunity remains unchanged. Use status closed when these declines end the broader opportunity.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/media_buy/decline_proposals_response.py b/src/adcp/types/generated_poc/media_buy/decline_proposals_response.py deleted file mode 100644 index 002168408..000000000 --- a/src/adcp/types/generated_poc/media_buy/decline_proposals_response.py +++ /dev/null @@ -1,108 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/decline_proposals_response.json -# timestamp: 2026-10-03T14:21:15+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 -from ..core.compact_task_submitted import CompactTaskSubmitted - - -class Outcome(StrEnum): - declined = 'declined' - unable = 'unable' - - -class Results1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - proposal_id: Annotated[str, Field(min_length=1)] - outcome: Literal['declined'] = 'declined' - reason: Annotated[ - str | None, - Field( - description='Why the seller could not apply a decline. Present only for outcome unable.', - min_length=1, - ), - ] = None - - -class Results2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - proposal_id: Annotated[str, Field(min_length=1)] - outcome: Literal['unable'] = 'unable' - reason: Annotated[ - str, - Field( - description='Why the seller could not apply a decline. Present only for outcome unable.', - min_length=1, - ), - ] - - -Results = Annotated[ - Results1 | Results2, - Field(discriminator='outcome'), -] - - -class Results4(Results1): - outcome: Literal['declined'] = 'declined' - - -class Results5(Results2): - outcome: Literal['unable'] = 'unable' - - -Results3 = Annotated[ - Results4 | Results5, - Field(discriminator='outcome'), -] - - -class DeclineProposalsResponse1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - results: Annotated[list[Results], Field(min_length=1)] - status: Literal['submitted'] = 'submitted' - task_id: Annotated[str | None, Field(min_length=1)] = None - message: Annotated[str | None, Field(max_length=2000)] = None - errors: list[error.Error] | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - replayed: Literal[True] | None = None - - -class DeclineProposalsResponse2(CompactTaskSubmitted): - model_config = ConfigDict( - extra='forbid', - ) - results: Annotated[list[Results3] | None, Field(min_length=1)] = None - status: Literal['submitted'] = 'submitted' - task_id: Annotated[str | None, Field(min_length=1)] = None - message: Annotated[str | None, Field(max_length=2000)] = None - errors: list[error.Error] | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - replayed: Literal[True] | None = None - - -DeclineProposalsResponse = Annotated[ - DeclineProposalsResponse1 | DeclineProposalsResponse2, - Field( - description='One ordered terminal result for each requested proposal decline.', - title='Decline Proposals Response', - ), -] diff --git a/src/adcp/types/generated_poc/media_buy/get_media_buy_delivery_request.py b/src/adcp/types/generated_poc/media_buy/get_media_buy_delivery_request.py deleted file mode 100644 index 20127403a..000000000 --- a/src/adcp/types/generated_poc/media_buy/get_media_buy_delivery_request.py +++ /dev/null @@ -1,564 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/get_media_buy_delivery_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Any -from pydantic import SerializerFunctionWrapHandler, model_serializer, model_validator - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, RootModel, StrictBool - -from ..core import account_ref -from ..core import context as context_1 -from ..core import delivery_breakdown_controls, demographic_age_range, duration -from ..core import ext as ext_1 -from ..core import pagination_request -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import attribution_model, available_metric -from ..enums import geo_level as geo_level_1 -from ..enums import ( - legacy_postal_system, - media_buy_status, - metro_system, - postal_system, - reporting_frequency, -) -from ..enums import sort_direction as sort_direction_1 -from ..enums import sort_metric - - -class AttributionWindow(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - post_click: Annotated[ - duration.Duration | None, Field(description='Post-click attribution window to apply.') - ] = None - post_view: Annotated[ - duration.Duration | None, Field(description='Post-view attribution window to apply.') - ] = None - model: Annotated[ - attribution_model.AttributionModel | None, - Field( - description='Attribution model to use. When omitted, the seller applies their default model.' - ), - ] = None - - -class Spot(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - limit: Annotated[ - SchemaInt | None, - Field( - description='Optional maximum number of spot rows to return. When the response is incomplete because of this limit or a seller-imposed maximum, by_spot_truncated is true.', - ge=1, - ), - ] = None - - -class StatusFilter(RootModel[list[media_buy_status.MediaBuyStatus]]): - root: Annotated[ - list[media_buy_status.MediaBuyStatus], - Field( - description='Filter by status. Can be a single status or array of statuses', - min_length=1, - ), - ] - - -class CatalogItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - limit: Annotated[ - SchemaInt | None, - Field( - description='Maximum number of catalog_item entries to return. When omitted, the seller returns its automatic default set.', - ge=1, - ), - ] = None - sort_by: Annotated[ - sort_metric.SortMetric | None, - Field( - description="Metric to sort breakdown rows by, in `sort_direction` order (descending by default). Falls back to 'spend' when the seller does not report the requested metric at this breakdown's row grain; on fallback the sort direction resets to 'desc'. Rows lacking a value for the applied sort metric order last regardless of direction. The applied sort is echoed in the response." - ), - ] = sort_metric.SortMetric.spend - sort_direction: Annotated[ - sort_direction_1.SortDirection | None, - Field( - description="Direction for sort_by ordering. Defaults to 'desc' (largest first). 'asc' enables bottom-N queries (e.g., the 25 worst placements by viewable_rate) that cannot be recovered from a truncated descending pull. Sellers MUST apply the requested direction to the applied sort metric — direction has no availability fallback." - ), - ] = sort_direction_1.SortDirection.desc - - -class Creative(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - limit: Annotated[ - SchemaInt | None, - Field( - description='Maximum number of creative entries to return. When omitted, the seller returns its automatic default set.', - ge=1, - ), - ] = None - sort_by: Annotated[ - sort_metric.SortMetric | None, - Field( - description="Metric to sort breakdown rows by, in `sort_direction` order (descending by default). Falls back to 'spend' when the seller does not report the requested metric at this breakdown's row grain; on fallback the sort direction resets to 'desc'. Rows lacking a value for the applied sort metric order last regardless of direction. The applied sort is echoed in the response." - ), - ] = sort_metric.SortMetric.spend - sort_direction: Annotated[ - sort_direction_1.SortDirection | None, - Field( - description="Direction for sort_by ordering. Defaults to 'desc' (largest first). 'asc' enables bottom-N queries (e.g., the 25 worst placements by viewable_rate) that cannot be recovered from a truncated descending pull. Sellers MUST apply the requested direction to the applied sort metric — direction has no availability fallback." - ), - ] = sort_direction_1.SortDirection.desc - - -class Keyword(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - limit: Annotated[ - SchemaInt | None, - Field( - description='Maximum number of keyword entries to return. When omitted, the seller returns its automatic default set.', - ge=1, - ), - ] = None - sort_by: Annotated[ - sort_metric.SortMetric | None, - Field( - description="Metric to sort breakdown rows by, in `sort_direction` order (descending by default). Falls back to 'spend' when the seller does not report the requested metric at this breakdown's row grain; on fallback the sort direction resets to 'desc'. Rows lacking a value for the applied sort metric order last regardless of direction. The applied sort is echoed in the response." - ), - ] = sort_metric.SortMetric.spend - sort_direction: Annotated[ - sort_direction_1.SortDirection | None, - Field( - description="Direction for sort_by ordering. Defaults to 'desc' (largest first). 'asc' enables bottom-N queries (e.g., the 25 worst placements by viewable_rate) that cannot be recovered from a truncated descending pull. Sellers MUST apply the requested direction to the applied sort metric — direction has no availability fallback." - ), - ] = sort_direction_1.SortDirection.desc - - -class Geo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - geo_level: Annotated[ - geo_level_1.GeographicTargetingLevel, - Field(description='Geographic granularity level for the breakdown'), - ] - system: Annotated[ - metro_system.MetroAreaSystem - | postal_system.PostalCodeSystem - | legacy_postal_system.CountryFusedPostalCodeSystem - | None, - Field( - description="Optional classification system for metro or postal_area levels. Metro uses metro-system values (e.g., 'nielsen_dma'); native postal_area uses country-local postal-system values with country (e.g., country 'US', system 'zip'); deprecated legacy postal_area requests may use legacy-postal-system values such as 'us_zip'. Omit to request the level without selecting a specific system." - ), - ] = None - country: Annotated[ - str | None, - Field( - description='ISO 3166-1 alpha-2 country code. Required for native postal_area requests; omitted for legacy postal_area and non-postal geo requests.', - pattern='^[A-Z]{2}$', - ), - ] = None - limit: Annotated[ - SchemaInt | None, - Field( - description='Maximum number of geo entries to return. Defaults to 25. When truncated, by_geo_truncated is true in the response.', - ge=1, - ), - ] = 25 - sort_by: Annotated[ - sort_metric.SortMetric | None, - Field( - description="Metric to sort breakdown rows by, in `sort_direction` order (descending by default). Falls back to 'spend' when the seller does not report the requested metric at this breakdown's row grain; on fallback the sort direction resets to 'desc'. Rows lacking a value for the applied sort metric order last regardless of direction. The applied sort is echoed in the response." - ), - ] = sort_metric.SortMetric.spend - sort_direction: Annotated[ - sort_direction_1.SortDirection | None, - Field( - description="Direction for sort_by ordering. Defaults to 'desc' (largest first). 'asc' enables bottom-N queries (e.g., the 25 worst placements by viewable_rate) that cannot be recovered from a truncated descending pull. Sellers MUST apply the requested direction to the applied sort metric — direction has no availability fallback." - ), - ] = sort_direction_1.SortDirection.desc - - -class DeviceType(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - limit: Annotated[ - SchemaInt | None, - Field( - description='Maximum number of entries to return. When omitted, all entries are returned (the enum is small and bounded).', - ge=1, - ), - ] = None - sort_by: Annotated[ - sort_metric.SortMetric | None, - Field( - description="Metric to sort breakdown rows by, in `sort_direction` order (descending by default). Falls back to 'spend' when the seller does not report the requested metric at this breakdown's row grain; on fallback the sort direction resets to 'desc'. Rows lacking a value for the applied sort metric order last regardless of direction. The applied sort is echoed in the response." - ), - ] = sort_metric.SortMetric.spend - sort_direction: Annotated[ - sort_direction_1.SortDirection | None, - Field( - description="Direction for sort_by ordering. Defaults to 'desc' (largest first). 'asc' enables bottom-N queries (e.g., the 25 worst placements by viewable_rate) that cannot be recovered from a truncated descending pull. Sellers MUST apply the requested direction to the applied sort metric — direction has no availability fallback." - ), - ] = sort_direction_1.SortDirection.desc - cursor: Annotated[ - str | None, - Field( - description="Opaque cursor from a previous response's by_device_type_pagination to fetch the next page of a truncated by_device_type breakdown. Omit for the first page. limit, sort_by, and sort_direction MUST be repeated unchanged across paged requests for the same logical query." - ), - ] = None - - -class DevicePlatform(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - limit: Annotated[ - SchemaInt | None, - Field( - description='Maximum number of entries to return. When omitted, all entries are returned (the enum is small and bounded).', - ge=1, - ), - ] = None - sort_by: Annotated[ - sort_metric.SortMetric | None, - Field( - description="Metric to sort breakdown rows by, in `sort_direction` order (descending by default). Falls back to 'spend' when the seller does not report the requested metric at this breakdown's row grain; on fallback the sort direction resets to 'desc'. Rows lacking a value for the applied sort metric order last regardless of direction. The applied sort is echoed in the response." - ), - ] = sort_metric.SortMetric.spend - sort_direction: Annotated[ - sort_direction_1.SortDirection | None, - Field( - description="Direction for sort_by ordering. Defaults to 'desc' (largest first). 'asc' enables bottom-N queries (e.g., the 25 worst placements by viewable_rate) that cannot be recovered from a truncated descending pull. Sellers MUST apply the requested direction to the applied sort metric — direction has no availability fallback." - ), - ] = sort_direction_1.SortDirection.desc - cursor: Annotated[ - str | None, - Field( - description="Opaque cursor from a previous response's by_device_platform_pagination to fetch the next page of a truncated by_device_platform breakdown. Omit for the first page. limit, sort_by, and sort_direction MUST be repeated unchanged across paged requests for the same logical query." - ), - ] = None - - -class Format(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - limit: Annotated[ - SchemaInt | None, - Field( - description='Maximum number of format rows to return. When omitted, all rows are returned because the canonical format-kind vocabulary is small and bounded.', - ge=1, - ), - ] = None - sort_by: Annotated[ - sort_metric.SortMetric | None, - Field( - description="Metric to sort breakdown rows by, in `sort_direction` order (descending by default). Falls back to 'spend' when the seller does not report the requested metric at this breakdown's row grain; on fallback the sort direction resets to 'desc'. Rows lacking a value for the applied sort metric order last regardless of direction. The applied sort is echoed in the response." - ), - ] = sort_metric.SortMetric.spend - sort_direction: Annotated[ - sort_direction_1.SortDirection | None, - Field( - description="Direction for sort_by ordering. Defaults to 'desc' (largest first). 'asc' enables bottom-N queries (e.g., the 25 worst placements by viewable_rate) that cannot be recovered from a truncated descending pull. Sellers MUST apply the requested direction to the applied sort metric — direction has no availability fallback." - ), - ] = sort_direction_1.SortDirection.desc - - -class Audience(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - limit: Annotated[ - SchemaInt | None, - Field(description='Maximum number of entries to return. Defaults to 25.', ge=1), - ] = 25 - sort_by: Annotated[ - sort_metric.SortMetric | None, - Field( - description="Metric to sort breakdown rows by, in `sort_direction` order (descending by default). Falls back to 'spend' when the seller does not report the requested metric at this breakdown's row grain; on fallback the sort direction resets to 'desc'. Rows lacking a value for the applied sort metric order last regardless of direction. The applied sort is echoed in the response." - ), - ] = sort_metric.SortMetric.spend - sort_direction: Annotated[ - sort_direction_1.SortDirection | None, - Field( - description="Direction for sort_by ordering. Defaults to 'desc' (largest first). 'asc' enables bottom-N queries (e.g., the 25 worst placements by viewable_rate) that cannot be recovered from a truncated descending pull. Sellers MUST apply the requested direction to the applied sort metric — direction has no availability fallback." - ), - ] = sort_direction_1.SortDirection.desc - cursor: Annotated[ - str | None, - Field( - description="Opaque cursor from a previous response's by_audience_pagination to fetch the next page of a truncated by_audience breakdown. Omit for the first page. limit, sort_by, and sort_direction MUST be repeated unchanged across paged requests for the same logical query." - ), - ] = None - - -class Demographic(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - age_ranges: Annotated[ - list[demographic_age_range.DemographicAgeRange] | None, - Field( - description="Optional canonical age ranges to return as distinct rows. Copying an applied targeting predicate here requests aligned reporting only when the reporting capability supports that exact predicate. Omit to request the product's declared native demographic breakdown.", - min_length=1, - ), - ] = None - limit: Annotated[ - SchemaInt | None, - Field(description='Maximum number of demographic entries to return. Defaults to 25.', ge=1), - ] = 25 - sort_by: Annotated[ - sort_metric.SortMetric | None, - Field( - description="Metric to sort breakdown rows by, in `sort_direction` order (descending by default). Falls back to 'spend' when the seller does not report the requested metric at this breakdown's row grain; on fallback the sort direction resets to 'desc'. Rows lacking a value for the applied sort metric order last regardless of direction. The applied sort is echoed in the response." - ), - ] = sort_metric.SortMetric.spend - sort_direction: Annotated[ - sort_direction_1.SortDirection | None, - Field( - description="Direction for sort_by ordering. Defaults to 'desc' (largest first). 'asc' enables bottom-N queries (e.g., the 25 worst placements by viewable_rate) that cannot be recovered from a truncated descending pull. Sellers MUST apply the requested direction to the applied sort metric — direction has no availability fallback." - ), - ] = sort_direction_1.SortDirection.desc - - -class Placement(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - limit: Annotated[ - SchemaInt | None, - Field(description='Maximum number of entries to return. Defaults to 25.', ge=1), - ] = 25 - sort_by: Annotated[ - sort_metric.SortMetric | None, - Field( - description="Metric to sort breakdown rows by, in `sort_direction` order (descending by default). Falls back to 'spend' when the seller does not report the requested metric at this breakdown's row grain; on fallback the sort direction resets to 'desc'. Rows lacking a value for the applied sort metric order last regardless of direction. The applied sort is echoed in the response." - ), - ] = sort_metric.SortMetric.spend - sort_direction: Annotated[ - sort_direction_1.SortDirection | None, - Field( - description="Direction for sort_by ordering. Defaults to 'desc' (largest first). 'asc' enables bottom-N queries (e.g., the 25 worst placements by viewable_rate) that cannot be recovered from a truncated descending pull. Sellers MUST apply the requested direction to the applied sort metric — direction has no availability fallback." - ), - ] = sort_direction_1.SortDirection.desc - cursor: Annotated[ - str | None, - Field( - description="Opaque cursor from a previous response's by_placement_pagination to fetch the next page of a truncated by_placement breakdown. Omit for the first page. limit, sort_by, and sort_direction MUST be repeated unchanged across paged requests for the same logical query." - ), - ] = None - - -class ReportingDimensions(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - catalog_item: Annotated[ - CatalogItem | None, - Field( - description='Request a negotiated catalog_item breakdown. Omitting this key preserves the automatic behavior — sellers return catalog_item rows at their discretion with no truncation contract. Including it (even as {}) makes the truncation disclosure and applied-sort echo binding.' - ), - ] = None - creative: Annotated[ - Creative | None, - Field( - description='Request a negotiated creative breakdown. Omitting this key preserves the automatic behavior — sellers return creative rows at their discretion with no truncation contract. Including it (even as {}) makes the truncation disclosure and applied-sort echo binding.' - ), - ] = None - keyword: Annotated[ - Keyword | None, - Field( - description='Request a negotiated keyword breakdown. Omitting this key preserves the automatic behavior — sellers return keyword rows at their discretion with no truncation contract. Including it (even as {}) makes the truncation disclosure and applied-sort echo binding.' - ), - ] = None - geo: Annotated[ - Geo | None, - Field( - description='Request geographic breakdown. Check reporting_capabilities.supports_geo_breakdown for available levels and systems.' - ), - ] = None - device_type: Annotated[ - DeviceType | None, Field(description='Request device type breakdown.') - ] = None - device_platform: Annotated[ - DevicePlatform | None, Field(description='Request device platform breakdown.') - ] = None - format: Annotated[ - Format | None, - Field( - description='Request delivery broken down by canonical creative format kind. This dimension is negotiated on the GET path. Reporting webhook configuration does not negotiate or guarantee dimensional breakdowns, although a webhook payload may carry the same fields as an extension.' - ), - ] = None - audience: Annotated[ - Audience | None, Field(description='Request audience segment breakdown.') - ] = None - demographic: Annotated[ - Demographic | None, - Field( - description="Request delivery broken down by demographic. Check the product's reporting_capabilities.supports_demographic_breakdown independently from demographic_targeting. When age_ranges is present, every requested range MUST be exactly supported by exact_predicates or equal one of the declared enumerated_intervals; sellers MUST reject unsupported ranges with UNSUPPORTED_FEATURE rather than silently widen or narrow them." - ), - ] = None - spot: Annotated[ - Spot | None, - Field( - description='Request a spot-level as-run airing log for broadcast TV, radio, or other scheduled inventory. Rows are ordered by aired_at ascending. When limit is omitted, sellers SHOULD return the complete log for the requested reporting period.' - ), - ] = None - placement: Annotated[Placement | None, Field(description='Request placement breakdown.')] = None - property: Annotated[ - delivery_breakdown_controls.DeliveryBreakdownControls | None, - Field( - description='Request delivery by canonical publisher property. Check reporting_capabilities.supports_property_breakdown.' - ), - ] = None - collection: Annotated[ - delivery_breakdown_controls.DeliveryBreakdownControls | None, - Field( - description='Request delivery by canonical content collection. Check reporting_capabilities.supports_collection_breakdown.' - ), - ] = None - installment: Annotated[ - delivery_breakdown_controls.DeliveryBreakdownControls | None, - Field( - description='Request delivery by canonical installment within a collection. Check reporting_capabilities.supports_installment_breakdown.' - ), - ] = None - collection_property: Annotated[ - delivery_breakdown_controls.DeliveryBreakdownControls | None, - Field( - description='Request delivery at the collection × property intersection. This is the reporting grain that can prove a collection delivered on a particular host service; independent collection and property marginals cannot. Check reporting_capabilities.supports_collection_property_breakdown.' - ), - ] = None - installment_property: Annotated[ - delivery_breakdown_controls.DeliveryBreakdownControls | None, - Field( - description='Request delivery at the installment × property intersection. This is the reporting grain that can prove a specific airing, episode, issue, or programming block delivered on a particular host service; independent installment and property marginals cannot. Check reporting_capabilities.supports_installment_property_breakdown.' - ), - ] = None - placement_property: Annotated[ - delivery_breakdown_controls.DeliveryBreakdownControls | None, - Field( - description='Request delivery at the placement × property intersection. Use when a placement may span multiple web, app, social, or assistant properties. Check reporting_capabilities.supports_placement_property_breakdown.' - ), - ] = None - - -class GetMediaBuyDeliveryRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - account: Annotated[ - account_ref.AccountReference | None, - Field( - description='Filter delivery data to a specific account. When omitted, returns data across all accessible accounts.' - ), - ] = None - media_buy_ids: Annotated[ - list[str] | None, - Field(description='Array of media buy IDs to get delivery data for', min_length=1), - ] = None - reporting_revision_id: Annotated[ - str | None, - Field( - description='Exact immutable reporting revision to retrieve. This additive Reliable Reporting selector returns content bound to that revision, including its immutable row count, control totals, and content SHA-256. It is mutually exclusive with media-buy, date, metric, and breakdown selectors.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - pagination: Annotated[ - pagination_request.PaginationRequest | None, - Field( - description='Cursor pagination. With reporting_revision_id, pages immutable authoritative rows while all revision metadata repeats on every page.' - ), - ] = None - status_filter: Annotated[ - media_buy_status.MediaBuyStatus | StatusFilter | None, - Field(description='Filter by status. Can be a single status or array of statuses'), - ] = None - start_date: Annotated[ - str | None, - Field( - description="Inclusive start date for the reporting period (YYYY-MM-DD), as a calendar date in the reporting timezone: the reporting_capabilities.timezone of the products behind the in-scope packages. It is a UTC day only when that timezone is UTC. When omitted along with end_date, returns campaign lifetime data. Only accepted when the product's reporting_capabilities.date_range_support is 'date_range'. A date-bounded request whose in-scope packages span more than one reporting timezone MUST be rejected with VALIDATION_ERROR. The buyer narrows media_buy_ids to buys that share one reporting timezone, or omits both dates when a single buy's packages span reporting timezones.", - pattern='^\\d{4}-\\d{2}-\\d{2}$', - ), - ] = None - end_date: Annotated[ - str | None, - Field( - description="Exclusive end date for the reporting period (YYYY-MM-DD), as a calendar date in the same reporting timezone as start_date. Must be later than start_date. When omitted along with start_date, returns campaign lifetime data. Only accepted when the product's reporting_capabilities.date_range_support is 'date_range'.", - pattern='^\\d{4}-\\d{2}-\\d{2}$', - ), - ] = None - include_package_daily_breakdown: Annotated[ - StrictBool | None, - Field( - description='When true, include daily_breakdown arrays within each package in by_package. Useful for per-package pacing analysis and line-item monitoring. Omit or set false to reduce response size — package daily data can be large for multi-package buys over long flights.' - ), - ] = False - requested_metrics: Annotated[ - list[available_metric.AvailableMetric] | None, - Field( - description="Optional list of metrics to include in the response. When omitted, all available metrics are included (unchanged behavior). Applies to every metrics-bearing object in the response: totals, by_package, daily and window slices, and breakdown rows. impressions and spend are always included regardless of this list, except that a legacy or externally created mixed-currency buy MUST omit monetary and money-derived values from media-buy and window totals, MUST omit daily_breakdown, and MUST report monetary values only on currency-qualified package rows (including window package rows). Requesting a leaf metric identity returns its canonical nested carrier — e.g. requesting viewable_rate returns the viewability object, requesting quartile_75 returns quartile_data — never a flat duplicate. Metrics requested but not available for this buy are omitted from the response without error; contract accountability is unchanged — missing_metrics still reconciles against committed_metrics, but sellers MUST NOT list a metric in missing_metrics when its absence is solely due to this narrowing. Must be a subset of the product's reporting_capabilities.available_metrics; values outside the declared set are ignored. Subset evaluation follows the container-subsumption rule in enums/available-metric.json. Sort is evaluated before narrowing: excluding a metric from this list never triggers the sort_by fallback, and breakdown rows may be ordered by a metric absent from the narrowed payload — the applied-sort echo still names it. Same narrowing semantics as reporting_webhook.requested_metrics, with one shape difference: this field requires at least one entry when present (omit it entirely for full payloads), while the webhook field permits an empty array with the same meaning as omission.", - min_length=1, - ), - ] = None - time_granularity: Annotated[ - reporting_frequency.ReportingFrequency | None, - Field( - description="Per-window slice granularity for the pull, using the same vocabulary as reporting_webhook.reporting_frequency. When set, the seller returns per-window delivery slices over the date range — useful for reconstructing data a buyer's webhook receiver missed, since the slice payload is shape-aligned with what reporting_webhook would have delivered for the same window. Capability-scoped: the value MUST be one of the seller's declared reporting_capabilities.windowed_pull_granularities; otherwise the seller MUST return UNSUPPORTED_GRANULARITY. When set to daily, weekly, monthly, or quarterly and the in-scope packages span more than one reporting timezone, the seller MUST return VALIDATION_ERROR. When omitted, behavior is unchanged (cumulative aggregates plus optional daily breakdowns per existing fields)." - ), - ] = None - include_window_breakdown: Annotated[ - StrictBool | None, - Field( - description="When true, the response includes media_buy_deliveries[].windows[] — an array of per-window delivery slices over the date range at the requested time_granularity. Ignored when time_granularity is omitted. Each window's payload mirrors what reporting_webhook would have delivered for the same window, enabling lossless GET-path recovery for buyers who missed webhook fires. Omit or set false to reduce response size when only cumulative aggregates are needed." - ), - ] = False - attribution_window: Annotated[ - AttributionWindow | None, - Field( - description='Attribution window to apply for conversion metrics. When provided, the seller returns conversion data using the requested lookback windows instead of their platform default. The seller echoes the applied window in the response. Sellers that do not support configurable windows ignore this field and return their default. Check get_adcp_capabilities conversion_tracking.attribution_windows for available options.' - ), - ] = None - reporting_dimensions: Annotated[ - ReportingDimensions | None, - Field( - description='Request dimensional breakdowns in delivery reporting. Each key enables a specific breakdown dimension within by_package — include as an empty object (e.g., "device_type": {}) to activate with defaults. Omit entirely for no breakdowns (backward compatible). Unsupported dimensions are silently omitted from the response. For every requested dimension that the product declares supported, the seller MUST return the corresponding array (possibly empty) and its truncated flag. Metric-sorted dimensions also return their applied-sort echoes; demographic and property-grain arrays also return their suppressed flag. Spot uses aired_at ordering and has no sort echoes. Note: keyword, catalog_item, and creative breakdowns are returned automatically when the seller supports them; including their keys here is optional and upgrades them to this negotiated contract without changing the automatic default.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - @model_validator(mode='after') - def _validate_delivery_selector_mode(self) -> GetMediaBuyDeliveryRequest: - if self.reporting_revision_id is not None: - if self.model_fields_set.intersection(('media_buy_ids', 'start_date', 'end_date', 'status_filter', 'requested_metrics', 'reporting_dimensions', 'attribution_window', 'include_package_daily_breakdown', 'time_granularity', 'include_window_breakdown')): - raise ValueError('exact revision requests forbid aggregate selectors, even false or null') - elif self.pagination is not None: - raise ValueError('pagination requires reporting_revision_id') - return self - - @model_serializer(mode='wrap') - def _serialize_delivery_selector_mode(self, handler: SerializerFunctionWrapHandler) -> dict[str, Any]: - value: dict[str, Any] = handler(self) - if self.reporting_revision_id is not None: - for name in ('media_buy_ids', 'start_date', 'end_date', 'status_filter', 'requested_metrics', 'reporting_dimensions', 'attribution_window', 'include_package_daily_breakdown', 'time_granularity', 'include_window_breakdown'): - if name not in self.model_fields_set: - value.pop(name, None) - return value diff --git a/src/adcp/types/generated_poc/media_buy/get_media_buy_delivery_response.py b/src/adcp/types/generated_poc/media_buy/get_media_buy_delivery_response.py deleted file mode 100644 index ed8b66d42..000000000 --- a/src/adcp/types/generated_poc/media_buy/get_media_buy_delivery_response.py +++ /dev/null @@ -1,1235 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/get_media_buy_delivery_response.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from collections.abc import Sequence -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat - -from ..core import attribution_window as attribution_window_1 -from ..core import ( - canonical_format_kind, - catalog_item_delivery_metrics, - collection_delivery_metrics, - collection_property_delivery_metrics, -) -from ..core import context as context_1 -from ..core import ( - creative_delivery_metrics, - delivery_metric_aggregate, - delivery_metrics, - demographic_age_range, - error, -) -from ..core import ext as ext_1 -from ..core import ( - geo_delivery_metrics, - installment_delivery_metrics, - installment_property_delivery_metrics, - keyword_delivery_metrics, - missing_metric, - package_delivery_metric_value, - pagination_response, - placement_delivery_metrics, - placement_property_delivery_metrics, - property_delivery_metrics, - reporting_control_total, -) -from ..core import reporting_revision as reporting_revision_1 -from ..core.delivery_metrics import DeliveryMetrics -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import audience_source as audience_source_1 -from ..enums import delivery_status as delivery_status_1 -from ..enums import demographic_system as demographic_system_1 -from ..enums import device_platform as device_platform_1 -from ..enums import device_type as device_type_1 -from ..enums import pricing_model as pricing_model_1 -from ..enums import reach_aggregation as reach_aggregation_1 -from ..enums import reach_unit as reach_unit_1 -from ..enums import sort_direction, sort_metric - - -class NotificationType(StrEnum): - scheduled = 'scheduled' - final = 'final' - delayed = 'delayed' - adjusted = 'adjusted' - window_update = 'window_update' - - -class ReportingPeriod(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - start: Annotated[ - AwareDatetime, - Field( - description="Inclusive RFC 3339 start instant. For a date-bounded request, the start of start_date in the reporting timezone, written with that zone's UTC offset or as the equivalent UTC instant (e.g., 2026-04-15T00:00:00-04:00 or 2026-04-15T04:00:00Z for America/New_York; 2026-04-15T00:00:00Z for UTC)." - ), - ] - end: Annotated[ - AwareDatetime, - Field( - description='Exclusive RFC 3339 end instant. For a date-bounded request, the start of end_date in the reporting timezone (e.g., 2026-04-16T00:00:00-04:00 for a report covering 2026-04-15 in America/New_York).' - ), - ] - timezone: Annotated[ - str | None, - Field( - description="Reporting timezone applied to this report, as 'UTC' or an IANA timezone identifier. It equals the in-scope products' reporting_capabilities.timezone. start_date, end_date, the reporting_period boundaries, daily_breakdown[].date, and daily, weekly, or monthly windows[] all use it, so a stored report stays interpretable if the product's capability later changes. Sellers SHOULD return it on reads without reporting_revision_id whenever every in-scope media buy shares one reporting timezone. Exact reporting_revision_id reads return the revision period, which carries source_timezone instead.", - examples=['UTC', 'America/New_York'], - min_length=1, - ), - ] = None - - -class Status(StrEnum): - pending_creatives = 'pending_creatives' - pending_start = 'pending_start' - pending = 'pending' - active = 'active' - paused = 'paused' - completed = 'completed' - rejected = 'rejected' - canceled = 'canceled' - failed = 'failed' - reporting_delayed = 'reporting_delayed' - - -class DailyBreakdownItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - date: Annotated[ - str, - Field( - description="Calendar date (YYYY-MM-DD) in the reporting timezone: reporting_period.timezone when present, otherwise this package's product reporting_capabilities.timezone. The row covers that local day, which can be 23 or 25 hours long across a DST change.", - pattern='^\\d{4}-\\d{2}-\\d{2}$', - ), - ] - impressions: Annotated[ - StrictFloat, Field(description='Daily impressions for this package', ge=0.0) - ] - spend: Annotated[StrictFloat, Field(description='Daily spend for this package', ge=0.0)] - conversions: Annotated[ - StrictFloat | None, Field(description='Daily conversions for this package', ge=0.0) - ] = None - conversion_value: Annotated[ - StrictFloat | None, Field(description='Daily conversion value for this package', ge=0.0) - ] = None - commissionable_value: Annotated[ - StrictFloat | None, - Field( - description='Daily settled conversion value eligible for revenue-share commission for this package', - ge=0.0, - ), - ] = None - roas: Annotated[ - StrictFloat | None, - Field(description='Daily return on ad spend (conversion_value / spend)', ge=0.0), - ] = None - new_to_brand_rate: Annotated[ - StrictFloat | None, - Field( - description='Daily fraction of conversions from first-time brand buyers (0 = none, 1 = all)', - ge=0.0, - le=1.0, - ), - ] = None - - -class DailyBreakdownItem1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - date: Annotated[ - str, - Field( - description="Calendar date (YYYY-MM-DD) in the reporting timezone: reporting_period.timezone when present, otherwise this media buy's product reporting_capabilities.timezone. The row covers that local day, which can be 23 or 25 hours long across a DST change.", - pattern='^\\d{4}-\\d{2}-\\d{2}$', - ), - ] - impressions: Annotated[StrictFloat, Field(description='Daily impressions', ge=0.0)] - spend: Annotated[StrictFloat, Field(description='Daily spend', ge=0.0)] - conversions: Annotated[StrictFloat | None, Field(description='Daily conversions', ge=0.0)] = ( - None - ) - conversion_value: Annotated[ - StrictFloat | None, Field(description='Daily conversion value', ge=0.0) - ] = None - commissionable_value: Annotated[ - StrictFloat | None, - Field( - description='Daily settled conversion value eligible for revenue-share commission', - ge=0.0, - ), - ] = None - roas: Annotated[ - StrictFloat | None, - Field(description='Daily return on ad spend (conversion_value / spend)', ge=0.0), - ] = None - new_to_brand_rate: Annotated[ - StrictFloat | None, - Field( - description='Daily fraction of conversions from first-time brand buyers (0 = none, 1 = all)', - ge=0.0, - le=1.0, - ), - ] = None - - -class ReportingRevisionBinding(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - reporting_revision_id: Annotated[str, Field(max_length=255, min_length=1)] - row_count: Annotated[SchemaInt, Field(ge=0)] - control_totals: list[reporting_control_total.ReportingControlTotal] - content_sha256: Annotated[ - str, - Field( - description='SHA-256 of RFC 8785 JCS of {reporting_revision_id,row_count,control_totals,reporting_rows}, where reporting_rows is the complete ordered sequence concatenated across every cursor page; identical to reporting_revision_1.revision_content_sha256.', - pattern='^[A-Fa-f0-9]{64}$', - ), - ] - - -class AggregatedTotals(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - impressions: Annotated[ - StrictFloat, Field(description='Total impressions delivered across all media buys', ge=0.0) - ] - spend: Annotated[ - StrictFloat, Field(description='Total amount spent across all media buys', ge=0.0) - ] - clicks: Annotated[ - StrictFloat | None, - Field(description='Total clicks across all media buys (if applicable)', ge=0.0), - ] = None - completed_views: Annotated[ - StrictFloat | None, - Field( - description='Total audio/video completions across all media buys (if applicable)', - ge=0.0, - ), - ] = None - views: Annotated[ - StrictFloat | None, - Field(description='Total views across all media buys (if applicable)', ge=0.0), - ] = None - conversions: Annotated[ - StrictFloat | None, - Field(description='Total conversions across all media buys (if applicable)', ge=0.0), - ] = None - conversion_value: Annotated[ - StrictFloat | None, - Field(description='Total conversion value across all media buys (if applicable)', ge=0.0), - ] = None - commissionable_value: Annotated[ - StrictFloat | None, - Field( - description='Total settled conversion value eligible for revenue-share commission across all media buys (if applicable)', - ge=0.0, - ), - ] = None - roas: Annotated[ - StrictFloat | None, - Field( - description='Aggregate return on ad spend across all media buys (total conversion_value / total spend)', - ge=0.0, - ), - ] = None - new_to_brand_rate: Annotated[ - StrictFloat | None, - Field( - description='Fraction of total conversions across all media buys from first-time brand buyers (weighted by conversion volume, not a simple average of per-buy rates)', - ge=0.0, - le=1.0, - ), - ] = None - cost_per_acquisition: Annotated[ - StrictFloat | None, - Field( - description='Aggregate cost per conversion across all media buys (total spend / total conversions)', - ge=0.0, - ), - ] = None - completion_rate: Annotated[ - StrictFloat | None, - Field( - description='Aggregate completion rate across all media buys (weighted by impressions, not a simple average of per-buy rates). Null indicates the metric is not applicable to the aggregated buys (e.g. all non-video inventory).', - ge=0.0, - le=1.0, - ), - ] = None - reach: Annotated[ - StrictFloat | None, - Field( - description='Reach across all media buys. Only present when all media buys share the same reach_unit. Omitted when reach units are heterogeneous — use per-buy reach values instead. The optional reach_aggregation field declares whether this value is deduplicated across buys or is a sum of constituent reach values.', - ge=0.0, - ), - ] = None - reach_aggregation: Annotated[ - reach_aggregation_1.ReachAggregation | None, - Field( - description='How reach was combined across the media buys in this aggregate. When omitted, legacy reach semantics are unknown and consumers MUST NOT use reach as the denominator for frequency.' - ), - ] = None - reach_unit: Annotated[ - reach_unit_1.ReachUnit | None, - Field( - description='Unit of measurement for reach. Only present when all aggregated media buys use the same reach_unit.' - ), - ] = None - frequency: Annotated[ - StrictFloat | None, - Field( - description='Average frequency per reach unit across all media buys (impressions / reach). In new payloads, only present when reach is present and reach_aggregation is deduplicated. MUST be omitted when reach_aggregation is sum_of_constituent_reach. Legacy payloads that omit reach_aggregation remain schema-valid, but consumers MUST NOT treat their reach as a safe frequency denominator.', - ge=0.0, - ), - ] = None - media_buy_count: Annotated[ - SchemaInt, Field(description='Number of media buys included in the response', ge=0) - ] - metric_aggregates: Annotated[ - list[delivery_metric_aggregate.DeliveryMetricAggregate] | None, - Field( - description="Cross-buy delivery aggregates partitioned by qualifier. Row-symmetric with `package.committed_metrics` and `by_package[].missing_metrics` — same atomic unit `(scope, metric_id, qualifier)` — so reconciliation collapses to a row-level join on the tuple. Granularity rule: one row per `(metric_id, full-qualifier-set)`, reported at the finest available granularity; buyers re-aggregate up if they want a coarser view. Used only for metrics with non-empty qualifier sets — unqualified metrics (`impressions`, `spend`, `media_buy_count`, etc.) remain at the top of `aggregated_totals`. **Mutual exclusion MUST**: for any `metric_id` appearing in `metric_aggregates`, the corresponding top-level scalar in `aggregated_totals` MUST be omitted (not zeroed) — avoids duplicate sources of truth. The qualifier vocabulary on this delivery surface is closed today (`additionalProperties: false`, same content as `committed_metrics.qualifier`) but is expected to **diverge from contract qualifier in future minors** as transparency disclosures buyers don't commit to ship delivery-only (e.g., `tracker_firing` pending #3832 resolution). Each row carries a `value` plus inlined per-metric component fields (e.g., `measurable_impressions` and `viewable_impressions` for `viewable_rate`; `spend` and `conversions` for `cost_per_acquisition`). Per-buy `totals` keeps its flat shape — each buy is single-qualifier by definition; only the aggregate spans qualifiers. **Qualifier-set drift across reports**: when a campaign gains a new qualifier mid-flight (e.g., adds `tracker_firing` partitioning in week 2), prior periods' rows remain valid at their original granularity; buyers SHOULD NOT retroactively repartition.", - examples=[ - [ - { - 'scope': 'standard', - 'metric_id': 'viewable_rate', - 'qualifier': {'viewability_standard': 'mrc'}, - 'value': 0.7286, - 'measurable_impressions': 700000, - 'viewable_impressions': 510000, - }, - { - 'scope': 'standard', - 'metric_id': 'viewable_rate', - 'qualifier': {'viewability_standard': 'groupm'}, - 'value': 0.55, - 'measurable_impressions': 180000, - 'viewable_impressions': 99000, - }, - { - 'scope': 'vendor', - 'vendor': {'domain': 'attentionvendor.example'}, - 'metric_id': 'attention_units', - 'qualifier': {}, - 'value': 4.2, - 'measurable_impressions': 800000, - }, - ] - ], - ), - ] = None - - -class Totals(DeliveryMetrics): - effective_rate: Annotated[ - StrictFloat | None, - Field( - description="Effective rate paid per unit based on pricing_model (e.g., actual CPM for 'cpm', actual cost per completed view for 'cpcv', actual cost per point for 'cpp')", - ge=0.0, - ), - ] = None - - -class ByFormatItem(DeliveryMetrics): - format_kind: Annotated[ - canonical_format_kind.CanonicalFormatKind, - Field( - description='Canonical creative format kind aggregated by this row. This identifies creative shape, not duration or other format-option parameters.' - ), - ] - impressions: Any - spend: Any - - -class ByDeviceTypeItem(DeliveryMetrics): - device_type: Annotated[ - device_type_1.DeviceType, - Field(description='Device form factor (desktop, mobile, tablet, ctv, dooh, unknown)'), - ] - impressions: Any - spend: Any - - -class ByDevicePlatformItem(DeliveryMetrics): - device_platform: Annotated[ - device_platform_1.DevicePlatform, Field(description='Operating system platform') - ] - impressions: Any - spend: Any - - -class ByAudienceItem(DeliveryMetrics): - audience_id: Annotated[ - str, - Field( - description="Audience segment identifier. For 'synced' source, matches audience_id from sync_audiences. For other sources, seller-defined." - ), - ] - audience_source: Annotated[ - audience_source_1.AudienceSource, - Field( - description='Origin of the audience segment (synced, platform, third_party, lookalike, retargeting, unknown)' - ), - ] - audience_name: Annotated[ - str | None, Field(description='Human-readable audience segment name') - ] = None - impressions: Any - spend: Any - - -class ByDemographicItem(DeliveryMetrics): - demographic: Annotated[ - str, - Field( - description="Demographic code in the declared system's notation (for example, P25-54 for Nielsen).", - min_length=1, - pattern='\\S', - ), - ] - demographic_system: Annotated[ - demographic_system_1.DemographicSystem, - Field(description='Measurement system that defines the demographic code.'), - ] - age: Annotated[ - demographic_age_range.DemographicAgeRange | None, - Field( - description="Canonical age interval represented by this row. Required by protocol semantics for age ranges requested through reporting_dimensions.demographic.age_ranges and for intervals declared in the product's demographic reporting capability." - ), - ] = None - impressions: Any - spend: Any - - -class BySpotItem(DeliveryMetrics): - spot_id: Annotated[ - str, - Field( - description='Stable seller-scoped identifier for this scheduled spot occurrence. Reused across measurement-window updates and available to later preemption or makegood workflows. Distinct from creative_id.', - min_length=1, - ), - ] - creative_id: Annotated[ - str | None, - Field( - description="Optional identifier for the creative that aired in this spot occurrence, matching the package's creative asset or assignment. For library-backed sellers this is the sync_creatives identifier; inline-only sellers use the packages[].creatives creative_id.", - min_length=1, - ), - ] = None - aired_at: Annotated[ - AwareDatetime, - Field( - description='RFC 3339 timestamp when the spot actually aired. Its presence proves an airing; it is not inferred from impressions.' - ), - ] - network: Annotated[ - str | None, - Field( - description="Optional network or syndicated service name, for example 'USA Network' or 'Westwood One'.", - min_length=1, - ), - ] = None - station: Annotated[ - str | None, - Field( - description="Optional station identifier or call sign, for example 'WABC-TV' or 'WNYC-FM'.", - min_length=1, - ), - ] = None - daypart: Annotated[ - str | None, - Field( - description="Optional seller-defined daypart label, for example 'prime_time' or 'morning_drive'.", - min_length=1, - ), - ] = None - - -class ByPackageItem1(DeliveryMetrics): - package_id: Annotated[str, Field(description="Seller's package identifier")] - currency: Annotated[ - str | None, - Field( - description="ISO 4217 denomination for this package window's monetary values. Required by the runtime currency-coherence rule when neither the parent media-buy row nor deprecated response-wide field supplies a currency and this package window contains monetary values; otherwise it MUST match the row or response currency when present.", - pattern='^[A-Z]{3}$', - ), - ] = None - - -class Window(AdCPBaseModel): - window_start: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 start of the window slice (inclusive). Daily, weekly, monthly, and quarterly slices start on reporting-timezone calendar boundaries (see reporting_period.timezone), which are UTC midnights only when that timezone is UTC. Weekly slices start Monday 00:00 in the reporting timezone (ISO 8601 weeks). Quarterly slices start on 1 January, 1 April, 1 July and 1 October.' - ), - ] - window_end: Annotated[ - AwareDatetime, - Field( - description="ISO 8601 end of the window slice (exclusive). The next row's window_start equals this value when slices are contiguous." - ), - ] - totals: Annotated[ - delivery_metrics.DeliveryMetrics, - Field( - description='Aggregate metrics for this window slice across all packages. Shape-aligned with reporting_webhook fire payloads at the same granularity.' - ), - ] - by_package: Annotated[ - list[ByPackageItem1] | None, - Field( - description='Per-package metrics for this window slice, using the same metric envelope and package identity as the parent media_buy_deliveries[].by_package row but scoped to the window. Requested reporting_dimensions do not apply to these webhook-aligned recovery rows; sellers may include dimensional fields only as webhook payload extensions, not as a guaranteed result of the GET request. Sellers MAY omit by_package when per-package window-level data is unavailable; when present, package_id values MUST match the parent by_package entries.' - ), - ] = None - is_final: Annotated[ - StrictBool | None, - Field( - description='Whether the slice data is final for this window. Same semantics as the per-package is_final flag — when false, a later read may return revised numbers for the same window as measurement matures (broadcast C3 → C7, post-IVT filtering, etc.).' - ), - ] = None - measurement_window: Annotated[ - str | None, - Field( - description="Which measurement-window stage this slice represents (e.g., 'live', 'c3', 'c7'), referencing a window_id from the product's reporting_capabilities.measurement_windows. Absent when the data is not phased.", - max_length=50, - ), - ] = None - - -class ByPackageItem(DeliveryMetrics): - package_id: Annotated[str, Field(description="Seller's package identifier")] - pacing_index: Annotated[ - StrictFloat | None, - Field( - description='Package delivery pace relative to its package-level pacing plan (1.0 = on track, <1.0 = behind, >1.0 = ahead). In seller-optimized mode this is a subordinate diagnostic and may be absent when no package pacing preference exists.', - ge=0.0, - ), - ] = None - pricing_model: Annotated[ - pricing_model_1.PricingModel, - Field( - description='The pricing model used for this package (e.g., cpm, cpcv, cpp). Indicates how the package is billed and which metrics are most relevant for optimization.' - ), - ] - rate: Annotated[ - StrictFloat, - Field( - description='The pricing rate for this package. For fixed-rate pricing, this is the agreed currency-denominated unit rate (e.g., CPM rate of 12.50 means $12.50 per 1,000 impressions). For auction-based pricing, this is the effective rate based on actual delivery. For revenue_share, this is the decimal commission rate (e.g., 0.04 means 4%) and is not itself currency-denominated.', - ge=0.0, - ), - ] - currency: Annotated[ - str, - Field( - description="ISO 4217 currency code for this package's spend and currency-denominated pricing rate. The rate for revenue_share is a dimensionless commission fraction, but attributed monetary values still use this currency. When the enclosing media_buy_deliveries[].currency is present, this value MUST equal it. For AdCP-authored buys both values MUST equal the media-buy currency. A different package currency is permitted only for a legacy or externally created mixed-currency buy whose row currency, daily_breakdown, and row/window monetary totals are omitted. AdCP does not perform currency conversion.", - pattern='^[A-Z]{3}$', - ), - ] - delivery_status: Annotated[ - delivery_status_1.DeliveryStatus | None, - Field( - description="System-reported operational state of this package. Reflects actual delivery state independent of buyer pause control. 'not_delivering' means zero impressions were recorded for the entire reporting_period while the package was in-flight. Sellers SHOULD only report 'not_delivering' once the package's data is_final for the period — a provisional (is_final: false) zero may still be measurement catching up, not genuine non-delivery." - ), - ] = None - paused: Annotated[ - StrictBool | None, - Field(description='Whether this package is currently paused by the buyer'), - ] = None - is_final: Annotated[ - StrictBool | None, - Field( - description="Whether this delivery data is final for the reporting period. When false, the data may be updated as measurement matures (e.g., broadcast C7 window accumulating DVR playback) or as processing completes (e.g., IVT filtering, deduplication). When true, the seller considers this data closed — no further updates for this period — and is willing to invoice on it subject to the buy's `measurement_terms.billing_measurement`. Absent means the seller does not distinguish provisional from final data." - ), - ] = None - finalized_at: Annotated[ - AwareDatetime | None, - Field( - description="ISO 8601 timestamp at which this package's data became final. Present only when `is_final: true`. Anchors reconciliation and (when later defined) dispute-window clocks against the buy's `measurement_terms.billing_measurement.measurement_window`." - ), - ] = None - measurement_window: Annotated[ - str | None, - Field( - description="Which measurement window this data represents, referencing a window_id from the product's reporting_capabilities.measurement_windows. For broadcast: 'live', 'c3', 'c7'. When absent, the data is not windowed (standard digital reporting). When present with is_final: false, a later report for the same period will provide a wider window or more complete data.", - examples=['live', 'c3', 'c7'], - max_length=50, - ), - ] = None - supersedes_window: Annotated[ - str | None, - Field( - description="Which measurement window this data replaces. Present on window_update notifications to indicate progression (e.g., 'live' when reporting C3 data that supersedes live-only numbers). Absent on the first report for a period. Buyers should replace stored data for the superseded window with this report's data.", - examples=['live', 'c3'], - max_length=50, - ), - ] = None - missing_metrics: Annotated[ - list[missing_metric.MissingMetric] | None, - Field( - description="Metrics that the binding reporting contract declared but that are NOT populated in this report. Reconciliation source: when `package.committed_metrics` is present, `missing_metrics` is computed against entries where `committed_at < reporting_period.end` — independent of subsequent product mutations and respecting the commitment timestamp on each entry (a metric committed mid-flight is only flagged missing in reports for periods after its commitment). When `package.committed_metrics` is absent, fall back to the product's current `reporting_capabilities.available_metrics` (no timestamp filter). Empty array (or absent) indicates clean delivery against the contract. Non-empty signals an accountability breach — the seller committed to the metric but did not produce the value here. Sellers MUST exclude metrics that are not yet measurable for the current `measurement_window` (e.g., post-IVT counts during the live window) — those will appear (or not) when a wider window supersedes this report via `supersedes_window`. Each entry uses an explicit `scope` discriminator: `standard` for entries from the closed `available-metric.json` enum, `vendor` for vendor-defined metrics anchored on a BrandRef. Symmetric with `committed_metrics`. When the request narrowed the payload via requested_metrics, sellers MUST NOT list a committed metric here solely because the buyer excluded it — missing_metrics reports delivery gaps, not request narrowing.", - examples=[ - [], - [{'scope': 'standard', 'metric_id': 'completed_views'}], - [ - {'scope': 'standard', 'metric_id': 'completed_views'}, - { - 'scope': 'vendor', - 'vendor': {'domain': 'attentionvendor.example'}, - 'metric_id': 'attention_units', - }, - ], - ], - ), - ] = None - metric_values: Annotated[ - list[package_delivery_metric_value.PackageDeliveryMetricValue] | None, - Field( - description='Qualified standard delivery values for this package. Each entry is the delivered counterpart to a standard-scope package.committed_metrics or by_package[].missing_metrics row, using the same atomic key (scope, metric_id, qualifier). Sellers report one row per full qualifier set at package grain; when a metric appears here, its flat scalar counterpart on the package MUST be omitted to avoid two sources of truth. Vendor-scope values continue to use vendor_metric_values. Buyers reconcile rows directly and perform any compatible cross-package aggregation themselves.' - ), - ] = None - by_catalog_item: Annotated[ - list[catalog_item_delivery_metrics.CatalogItemDeliveryMetrics] | None, - Field( - description='Delivery by catalog item within this package. Available for catalog-driven packages when the seller supports item-level reporting.' - ), - ] = None - by_catalog_item_truncated: Annotated[ - StrictBool | None, - Field( - description='Whether by_catalog_item was truncated due to the requested limit or a seller-imposed maximum. Sellers MUST return this flag whenever by_catalog_item is present and the request included reporting_dimensions.catalog_item (false means the list is complete). When the breakdown was returned automatically without a request key, the flag is RECOMMENDED but not required — automatic rows carry no completeness contract.' - ), - ] = None - by_catalog_item_sorted_by: Annotated[ - sort_metric.SortMetric | None, - Field( - description="The metric actually used to order by_catalog_item rows. Sellers MUST return this field whenever by_catalog_item is present and the request included reporting_dimensions.catalog_item. When the seller cannot sort by the requested sort_by metric it falls back to 'spend'; this echo makes the fallback visible instead of silently returning rows the buyer will misread as ordered by the requested metric. When the breakdown was returned automatically without a request key, the field is RECOMMENDED but not required — automatic rows carry no completeness contract." - ), - ] = None - by_catalog_item_sort_direction: Annotated[ - sort_direction.SortDirection | None, - Field( - description='The direction actually applied to by_catalog_item ordering. Sellers MUST return this field whenever by_catalog_item is present and the request included reporting_dimensions.catalog_item, alongside by_catalog_item_sorted_by. When the breakdown was returned automatically without a request key, the field is RECOMMENDED but not required — automatic rows carry no completeness contract.' - ), - ] = None - by_creative: Annotated[ - list[creative_delivery_metrics.CreativeDeliveryMetrics] | None, - Field( - description='Metrics broken down by creative within this package. Available when the seller supports creative-level reporting.' - ), - ] = None - by_format: Annotated[ - list[ByFormatItem] | None, - Field( - description="Delivery by canonical creative format kind within this package. Negotiated on the GET path when the buyer requests reporting_dimensions.format and the product declares supports_format_breakdown; reporting webhook configuration does not negotiate or guarantee this breakdown. Each row aggregates every served creative of that format kind. Sellers MUST aggregate all delivery using adopter-defined shapes into one format_kind 'custom' row. When by_format_truncated is false, additive metrics such as impressions and spend across the rows SHOULD reconcile to the corresponding package totals, subject to the measurement and attribution semantics of each metric. Buyers MUST NOT expect row-level correspondence between by_format and by_creative because the two breakdowns are independently produced at different grains." - ), - ] = None - by_format_truncated: Annotated[ - StrictBool | None, - Field( - description='Whether by_format was truncated due to the requested limit or a seller-imposed maximum. Sellers MUST return this flag whenever by_format is present (false means the list is complete).' - ), - ] = None - by_format_sorted_by: Annotated[ - sort_metric.SortMetric | None, - Field( - description="The metric actually used to order by_format rows. Sellers MUST return this field whenever by_format is present. When the seller cannot sort by the requested sort_by metric it falls back to 'spend'; this echo makes the fallback visible instead of silently returning rows the buyer will misread as ordered by the requested metric." - ), - ] = None - by_format_sort_direction: Annotated[ - sort_direction.SortDirection | None, - Field( - description='The direction actually applied to by_format ordering. Sellers MUST return this field whenever by_format is present.' - ), - ] = None - by_creative_truncated: Annotated[ - StrictBool | None, - Field( - description='Whether by_creative was truncated due to the requested limit or a seller-imposed maximum. Sellers MUST return this flag whenever by_creative is present and the request included reporting_dimensions.creative (false means the list is complete). When the breakdown was returned automatically without a request key, the flag is RECOMMENDED but not required — automatic rows carry no completeness contract.' - ), - ] = None - by_creative_sorted_by: Annotated[ - sort_metric.SortMetric | None, - Field( - description="The metric actually used to order by_creative rows. Sellers MUST return this field whenever by_creative is present and the request included reporting_dimensions.creative. When the seller cannot sort by the requested sort_by metric it falls back to 'spend'; this echo makes the fallback visible instead of silently returning rows the buyer will misread as ordered by the requested metric. When the breakdown was returned automatically without a request key, the field is RECOMMENDED but not required — automatic rows carry no completeness contract." - ), - ] = None - by_creative_sort_direction: Annotated[ - sort_direction.SortDirection | None, - Field( - description='The direction actually applied to by_creative ordering. Sellers MUST return this field whenever by_creative is present and the request included reporting_dimensions.creative, alongside by_creative_sorted_by. When the breakdown was returned automatically without a request key, the field is RECOMMENDED but not required — automatic rows carry no completeness contract.' - ), - ] = None - by_keyword: Annotated[ - list[keyword_delivery_metrics.KeywordDeliveryMetrics] | None, - Field( - description='Metrics broken down by keyword within this package. One row per (keyword, match_type) pair — the same keyword with different match types appears as separate rows. Keyword-grain only: rows reflect aggregate performance of each targeted keyword, not individual search queries. Rows may not sum to package totals when a single impression is attributed to the triggering keyword only. Available for search and retail media packages when the seller supports keyword-level reporting.' - ), - ] = None - by_keyword_truncated: Annotated[ - StrictBool | None, - Field( - description='Whether by_keyword was truncated due to the requested limit or a seller-imposed maximum. Sellers MUST return this flag whenever by_keyword is present and the request included reporting_dimensions.keyword (false means the list is complete). When the breakdown was returned automatically without a request key, the flag is RECOMMENDED but not required — automatic rows carry no completeness contract.' - ), - ] = None - by_keyword_sorted_by: Annotated[ - sort_metric.SortMetric | None, - Field( - description="The metric actually used to order by_keyword rows. Sellers MUST return this field whenever by_keyword is present and the request included reporting_dimensions.keyword. When the seller cannot sort by the requested sort_by metric it falls back to 'spend'; this echo makes the fallback visible instead of silently returning rows the buyer will misread as ordered by the requested metric. When the breakdown was returned automatically without a request key, the field is RECOMMENDED but not required — automatic rows carry no completeness contract." - ), - ] = None - by_keyword_sort_direction: Annotated[ - sort_direction.SortDirection | None, - Field( - description='The direction actually applied to by_keyword ordering. Sellers MUST return this field whenever by_keyword is present and the request included reporting_dimensions.keyword, alongside by_keyword_sorted_by. When the breakdown was returned automatically without a request key, the field is RECOMMENDED but not required — automatic rows carry no completeness contract.' - ), - ] = None - by_geo: Annotated[ - list[geo_delivery_metrics.GeoDeliveryMetrics] | None, - Field( - description="Delivery by geographic area within this package. Available when the buyer requests geo breakdown via reporting_dimensions and the seller supports it. Each dimension's rows are independent slices that should sum to the package total." - ), - ] = None - by_geo_truncated: Annotated[ - StrictBool | None, - Field( - description='Whether by_geo was truncated due to the requested limit or a seller-imposed maximum. Sellers MUST return this flag whenever by_geo is present (false means the list is complete).' - ), - ] = None - by_geo_sorted_by: Annotated[ - sort_metric.SortMetric | None, - Field( - description="The metric actually used to order by_geo rows. Sellers MUST return this field whenever by_geo is present. When the seller cannot sort by the requested sort_by metric it falls back to 'spend'; this echo makes the fallback visible instead of silently returning rows the buyer will misread as ordered by the requested metric." - ), - ] = None - by_geo_sort_direction: Annotated[ - sort_direction.SortDirection | None, - Field( - description='The direction actually applied to by_geo ordering. Sellers MUST return this field whenever by_geo is present.' - ), - ] = None - by_device_type: Annotated[ - list[ByDeviceTypeItem] | None, - Field( - description='Delivery by device form factor within this package. Available when the buyer requests device_type breakdown via reporting_dimensions and the seller supports it.' - ), - ] = None - by_device_type_truncated: Annotated[ - StrictBool | None, - Field( - description='Whether by_device_type was truncated. Sellers MUST return this flag whenever by_device_type is present (false means the list is complete).' - ), - ] = None - by_device_type_sorted_by: Annotated[ - sort_metric.SortMetric | None, - Field( - description="The metric actually used to order by_device_type rows. Sellers MUST return this field whenever by_device_type is present. When the seller cannot sort by the requested sort_by metric it falls back to 'spend'; this echo makes the fallback visible instead of silently returning rows the buyer will misread as ordered by the requested metric." - ), - ] = None - by_device_type_sort_direction: Annotated[ - sort_direction.SortDirection | None, - Field( - description='The direction actually applied to by_device_type ordering. Sellers MUST return this field whenever by_device_type is present.' - ), - ] = None - by_device_type_pagination: Annotated[ - pagination_response.PaginationResponse | None, - Field( - description="Cursor to retrieve the remaining by_device_type rows when by_device_type_truncated is true. Sellers MUST return this field whenever by_device_type_truncated is true; omit when false or when by_device_type is absent. Pass the cursor back in the corresponding request's reporting_dimensions.device_type_1.cursor to fetch the next page; other reporting_dimensions.device_type request fields (limit, sort_by, sort_direction) MUST be repeated unchanged across paged requests." - ), - ] = None - by_device_platform: Annotated[ - list[ByDevicePlatformItem] | None, - Field( - description='Delivery by operating system within this package. Available when the buyer requests device_platform breakdown via reporting_dimensions and the seller supports it. Useful for CTV campaigns where tvOS vs Roku OS vs Fire OS matters.' - ), - ] = None - by_device_platform_truncated: Annotated[ - StrictBool | None, - Field( - description='Whether by_device_platform was truncated. Sellers MUST return this flag whenever by_device_platform is present (false means the list is complete).' - ), - ] = None - by_device_platform_sorted_by: Annotated[ - sort_metric.SortMetric | None, - Field( - description="The metric actually used to order by_device_platform rows. Sellers MUST return this field whenever by_device_platform is present. When the seller cannot sort by the requested sort_by metric it falls back to 'spend'; this echo makes the fallback visible instead of silently returning rows the buyer will misread as ordered by the requested metric." - ), - ] = None - by_device_platform_sort_direction: Annotated[ - sort_direction.SortDirection | None, - Field( - description='The direction actually applied to by_device_platform ordering. Sellers MUST return this field whenever by_device_platform is present.' - ), - ] = None - by_device_platform_pagination: Annotated[ - pagination_response.PaginationResponse | None, - Field( - description="Cursor to retrieve the remaining by_device_platform rows when by_device_platform_truncated is true. Sellers MUST return this field whenever by_device_platform_truncated is true; omit when false or when by_device_platform is absent. Pass the cursor back in the corresponding request's reporting_dimensions.device_platform_1.cursor to fetch the next page; other reporting_dimensions.device_platform request fields (limit, sort_by, sort_direction) MUST be repeated unchanged across paged requests." - ), - ] = None - by_audience: Annotated[ - list[ByAudienceItem] | None, - Field( - description="Delivery by audience segment within this package. Available when the buyer requests audience breakdown via reporting_dimensions and the seller supports it. Only 'synced' audiences are directly targetable via the targeting overlay; other sources are informational." - ), - ] = None - by_audience_truncated: Annotated[ - StrictBool | None, - Field( - description='Whether by_audience was truncated. Sellers MUST return this flag whenever by_audience is present (false means the list is complete).' - ), - ] = None - by_audience_sorted_by: Annotated[ - sort_metric.SortMetric | None, - Field( - description="The metric actually used to order by_audience rows. Sellers MUST return this field whenever by_audience is present. When the seller cannot sort by the requested sort_by metric it falls back to 'spend'; this echo makes the fallback visible instead of silently returning rows the buyer will misread as ordered by the requested metric." - ), - ] = None - by_audience_sort_direction: Annotated[ - sort_direction.SortDirection | None, - Field( - description='The direction actually applied to by_audience ordering. Sellers MUST return this field whenever by_audience is present.' - ), - ] = None - by_audience_pagination: Annotated[ - pagination_response.PaginationResponse | None, - Field( - description="Cursor to retrieve the remaining by_audience rows when by_audience_truncated is true. Sellers MUST return this field whenever by_audience_truncated is true; omit when false or when by_audience is absent. Pass the cursor back in the corresponding request's reporting_dimensions.audience.cursor to fetch the next page; other reporting_dimensions.audience request fields (limit, sort_by, sort_direction) MUST be repeated unchanged across paged requests." - ), - ] = None - by_demographic: Annotated[ - list[ByDemographicItem] | None, - Field( - description='Delivery by demographic within this package. Available when the buyer requests demographic breakdown and the product declares supports_demographic_breakdown. A free-form measurement code does not prove alignment with buyer targeting. When age is present it is the authoritative machine-comparable interval; for requested age_ranges, sellers MUST echo the exact requested interval and MUST NOT substitute a wider or narrower native bucket.' - ), - ] = None - by_demographic_truncated: Annotated[ - StrictBool | None, - Field( - description='Whether non-suppressed by_demographic rows were truncated due to the requested limit or a seller-imposed maximum. Sellers MUST return this flag whenever by_demographic is present. False means every non-suppressed row is present; inspect by_demographic_suppressed separately before reconciling rows to package totals.' - ), - ] = None - by_demographic_sorted_by: Annotated[ - sort_metric.SortMetric | None, - Field( - description="The metric actually used to order by_demographic rows. Sellers MUST return this field whenever by_demographic is present. When the seller cannot sort by the requested sort_by metric it falls back to 'spend'; this echo makes the fallback visible instead of silently returning rows the buyer will misread as ordered by the requested metric." - ), - ] = None - by_demographic_sort_direction: Annotated[ - sort_direction.SortDirection | None, - Field( - description='The direction actually applied to by_demographic ordering. Sellers MUST return this field whenever by_demographic is present.' - ), - ] = None - by_demographic_suppressed: Annotated[ - StrictBool | None, - Field( - description='Whether one or more otherwise reportable demographic rows were omitted due to privacy, policy, or measurement thresholds. Sellers MUST return this flag whenever by_demographic is present. False means no rows were threshold-suppressed.' - ), - ] = None - by_placement: Annotated[ - list[placement_delivery_metrics.PlacementDeliveryMetrics] | None, - Field( - description='Delivery by placement within this package. placement_id remains required for 3.1 compatibility. New 3.2 sellers also emit placement_identity, whose discriminator separates publisher-catalog identity from sales-agent-defined inline identity.' - ), - ] = None - by_placement_truncated: Annotated[ - StrictBool | None, - Field( - description='Whether by_placement was truncated. Sellers MUST return this flag whenever by_placement is present (false means the list is complete).' - ), - ] = None - by_placement_sorted_by: Annotated[ - sort_metric.SortMetric | None, - Field( - description="The metric actually used to order by_placement rows. Sellers MUST return this field whenever by_placement is present. When the seller cannot sort by the requested sort_by metric it falls back to 'spend'; this echo makes the fallback visible instead of silently returning rows the buyer will misread as ordered by the requested metric." - ), - ] = None - by_placement_sort_direction: Annotated[ - sort_direction.SortDirection | None, - Field( - description='The direction actually applied to by_placement ordering. Sellers MUST return this field whenever by_placement is present.' - ), - ] = None - by_placement_pagination: Annotated[ - pagination_response.PaginationResponse | None, - Field( - description="Cursor to retrieve the remaining by_placement rows when by_placement_truncated is true. Sellers MUST return this field whenever by_placement_truncated is true; omit when false or when by_placement is absent. Pass the cursor back in the corresponding request's reporting_dimensions.placement.cursor to fetch the next page; other reporting_dimensions.placement request fields (limit, sort_by, sort_direction) MUST be repeated unchanged across paged requests." - ), - ] = None - by_property: Annotated[ - list[property_delivery_metrics.PropertyDeliveryMetrics] | None, - Field( - description='Delivery by publisher property within this package. Each row identifies the actual surface with an operational identifier and adds property_ref when it resolves to a canonical publisher catalog entry. Rows are independent of by_collection.' - ), - ] = None - by_property_truncated: Annotated[ - StrictBool | None, - Field( - description='Whether non-suppressed by_property rows were truncated. Sellers MUST return this flag whenever by_property is present. False means every non-suppressed row is present; inspect by_property_suppressed before reconciling rows to package totals.' - ), - ] = None - by_property_suppressed: Annotated[ - StrictBool | None, - Field( - description='Whether one or more otherwise reportable by_property rows were omitted from this response due to privacy, policy, or measurement thresholds. Sellers MUST return this flag whenever by_property is present. False means no rows were threshold-suppressed. Both suppression and truncation may be true.' - ), - ] = None - by_property_sorted_by: Annotated[ - sort_metric.SortMetric | None, - Field( - description='The metric actually used to order by_property rows. Sellers MUST return this field whenever by_property is present.' - ), - ] = None - by_property_sort_direction: Annotated[ - sort_direction.SortDirection | None, - Field( - description='The direction actually applied to by_property rows. Sellers MUST return this field whenever by_property is present.' - ), - ] = None - by_collection: Annotated[ - list[collection_delivery_metrics.CollectionDeliveryMetrics] | None, - Field( - description='Delivery by publisher-scoped collection within this package. This is a marginal breakdown and does not by itself prove which property carried a collection.' - ), - ] = None - by_collection_truncated: Annotated[ - StrictBool | None, - Field( - description='Whether by_collection was truncated. Sellers MUST return this flag whenever by_collection is present.' - ), - ] = None - by_collection_sorted_by: Annotated[ - sort_metric.SortMetric | None, - Field( - description='The metric actually used to order by_collection rows. Sellers MUST return this field whenever by_collection is present.' - ), - ] = None - by_collection_sort_direction: Annotated[ - sort_direction.SortDirection | None, - Field( - description='The direction actually applied to by_collection rows. Sellers MUST return this field whenever by_collection is present.' - ), - ] = None - by_installment: Annotated[ - list[installment_delivery_metrics.InstallmentDeliveryMetrics] | None, - Field(description='Delivery by canonically identified installment within this package.'), - ] = None - by_installment_truncated: Annotated[ - StrictBool | None, - Field( - description='Whether by_installment was truncated. Sellers MUST return this flag whenever by_installment is present.' - ), - ] = None - by_installment_sorted_by: Annotated[ - sort_metric.SortMetric | None, - Field( - description='The metric actually used to order by_installment rows. Sellers MUST return this field whenever by_installment is present.' - ), - ] = None - by_installment_sort_direction: Annotated[ - sort_direction.SortDirection | None, - Field( - description='The direction actually applied to by_installment rows. Sellers MUST return this field whenever by_installment is present.' - ), - ] = None - by_collection_property: Annotated[ - list[collection_property_delivery_metrics.CollectionPropertyDeliveryMetrics] | None, - Field( - description='Delivery at the collection × property intersection. A row is affirmative delivery evidence that the referenced collection ran on the referenced property; it is not merely a carriage or catalog assertion.' - ), - ] = None - by_collection_property_truncated: Annotated[ - StrictBool | None, - Field( - description='Whether non-suppressed by_collection_property rows were truncated. Sellers MUST return this flag whenever by_collection_property is present.' - ), - ] = None - by_collection_property_suppressed: Annotated[ - StrictBool | None, - Field( - description='Whether one or more otherwise reportable by_collection_property rows were omitted from this response due to privacy, policy, or measurement thresholds. Sellers MUST return this flag whenever by_collection_property is present. False means no rows were threshold-suppressed. Both suppression and truncation may be true.' - ), - ] = None - by_collection_property_sorted_by: Annotated[ - sort_metric.SortMetric | None, - Field( - description='The metric actually used to order by_collection_property rows. Sellers MUST return this field whenever by_collection_property is present.' - ), - ] = None - by_collection_property_sort_direction: Annotated[ - sort_direction.SortDirection | None, - Field( - description='The direction actually applied to by_collection_property rows. Sellers MUST return this field whenever by_collection_property is present.' - ), - ] = None - by_installment_property: Annotated[ - list[installment_property_delivery_metrics.InstallmentPropertyDeliveryMetrics] | None, - Field( - description='Delivery at the installment × property intersection. A row is affirmative delivery evidence that the referenced airing, episode, issue, or programming block ran on the referenced property; it is not inferred from collection carriage or independent marginals.' - ), - ] = None - by_installment_property_truncated: Annotated[ - StrictBool | None, - Field( - description='Whether non-suppressed by_installment_property rows were truncated. Sellers MUST return this flag whenever by_installment_property is present.' - ), - ] = None - by_installment_property_suppressed: Annotated[ - StrictBool | None, - Field( - description='Whether one or more otherwise reportable by_installment_property rows were omitted from this response due to privacy, policy, or measurement thresholds. Sellers MUST return this flag whenever by_installment_property is present. False means no rows were threshold-suppressed. Both suppression and truncation may be true.' - ), - ] = None - by_installment_property_sorted_by: Annotated[ - sort_metric.SortMetric | None, - Field( - description='The metric actually used to order by_installment_property rows. Sellers MUST return this field whenever by_installment_property is present.' - ), - ] = None - by_installment_property_sort_direction: Annotated[ - sort_direction.SortDirection | None, - Field( - description='The direction actually applied to by_installment_property rows. Sellers MUST return this field whenever by_installment_property is present.' - ), - ] = None - by_placement_property: Annotated[ - list[placement_property_delivery_metrics.PlacementPropertyDeliveryMetrics] | None, - Field( - description='Delivery at the placement × property intersection. This proves which actual property carried a placement that may span more than one property.' - ), - ] = None - by_placement_property_truncated: Annotated[ - StrictBool | None, - Field( - description='Whether non-suppressed by_placement_property rows were truncated. Sellers MUST return this flag whenever by_placement_property is present.' - ), - ] = None - by_placement_property_suppressed: Annotated[ - StrictBool | None, - Field( - description='Whether one or more otherwise reportable by_placement_property rows were omitted from this response due to privacy, policy, or measurement thresholds. Sellers MUST return this flag whenever by_placement_property is present. False means no rows were threshold-suppressed. Both suppression and truncation may be true.' - ), - ] = None - by_placement_property_sorted_by: Annotated[ - sort_metric.SortMetric | None, - Field( - description='The metric actually used to order by_placement_property rows. Sellers MUST return this field whenever by_placement_property is present.' - ), - ] = None - by_placement_property_sort_direction: Annotated[ - sort_direction.SortDirection | None, - Field( - description='The direction actually applied to by_placement_property rows. Sellers MUST return this field whenever by_placement_property is present.' - ), - ] = None - by_spot: Annotated[ - list[BySpotItem] | None, - Field( - description='Spot-level as-run airing records for broadcast TV, radio, or other scheduled inventory. Available when the buyer requests spot breakdown and the product declares supports_spot_breakdown. Sellers MUST order rows by aired_at ascending. The same spot_id is reused when a later package measurement_window adds or revises metrics. Network and station are optional so station-direct radio and network-level TV records use the same channel-neutral shape. Sellers SHOULD populate creative_id whenever they can associate a specific airing with a creative, particularly when the package could serve more than one creative during any part of the reporting period, including a mid-period replacement. Sellers that cannot make that association MUST omit creative_id rather than emit a default or placeholder. Buyers MUST NOT aggregate by_spot rows by creative_id and expect the result to equal by_creative impressions for the same creative: the two dimensions are independently produced at different granularities and are subject to different metric-maturation and attribution semantics within the package measurement_window. by_creative is the authoritative creative-performance aggregate; creative_id on a by_spot row identifies which creative aired, not an independent metric roll-up source.' - ), - ] = None - by_spot_truncated: Annotated[ - StrictBool | None, - Field( - description='Whether by_spot is incomplete because of the requested limit or a seller-imposed maximum. Sellers MUST return this flag whenever by_spot is present (false means the as-run log is complete for the requested reporting period).' - ), - ] = None - daily_breakdown: Annotated[ - list[DailyBreakdownItem] | None, - Field( - description='Day-by-day delivery for this package. Only present when include_package_daily_breakdown is true in the request. Enables per-package pacing analysis and line-item monitoring.' - ), - ] = None - spend: Any - - -class MediaBuyDelivery(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_buy_id: Annotated[str, Field(description="Seller's media buy identifier")] - currency: Annotated[ - str | None, - Field( - description="ISO 4217 denomination for monetary values in this media-buy delivery row, including totals, package spend, and currency-denominated package rates. Sellers SHOULD populate this field whenever all monetary values in the row share one currency. For AdCP-authored buys it MUST equal the media-buy currency, and every by_package[].currency MUST equal it. For a legacy or externally created mixed-currency buy, omit this field, daily_breakdown, and all monetary or money-derived values from row and window totals; report those values only at package grain with each package's own currency. AdCP does not perform currency conversion.", - pattern='^[A-Z]{3}$', - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current media buy status. Lifecycle states use the same taxonomy as media-buy-status (`pending_creatives`, `pending_start`, `active`, `paused`, `completed`, `rejected`, `canceled`). In webhook context, reporting_delayed indicates data temporarily unavailable. `pending` is accepted as a legacy alias for pending_start.' - ), - ] - expected_availability: Annotated[ - AwareDatetime | None, - Field( - description='When delayed data is expected to be available (only present when status is reporting_delayed)' - ), - ] = None - is_adjusted: Annotated[ - StrictBool | None, - Field( - description='Indicates this delivery contains updated data for a previously reported period. Buyer should replace previous period data with these totals.' - ), - ] = None - is_final: Annotated[ - StrictBool | None, - Field( - description="Whether this row's delivery data is final for the reporting period. The row does not carry its own `measurement_window` — that lives on each `by_package[*]` entry. Reconciliation joins on per-package `measurement_window`; this row-level flag is a convenience roll-up. Sellers MUST NOT emit `is_final: true` at the row level unless every entry in `by_package` has `is_final: true` for the same `measurement_window` as the buy's `measurement_terms.billing_measurement.measurement_window` (or for the row's natural window when no `billing_measurement.measurement_window` is set). On any disagreement between row-level and package-level finality, package-level is authoritative. When true, the seller considers these numbers closed and is willing to invoice on them subject to `measurement_terms.billing_measurement`. When false, numbers may still move as measurement matures (broadcast C3 → C7) or processing completes (IVT scrubbing, dedup). When absent, the seller does not distinguish provisional from final at the row level — consult per-package `is_final`." - ), - ] = None - finalized_at: Annotated[ - AwareDatetime | None, - Field( - description="ISO 8601 timestamp at which this row became final. Present only when `is_final: true`. Anchors the buyer's reconciliation and (when later defined) dispute-window clocks against the buy's `measurement_terms.billing_measurement`. Computed as the latest `finalized_at` across the row's packages for the reconciliation window." - ), - ] = None - pricing_model: Annotated[ - pricing_model_1.PricingModel | None, - Field(description='Pricing model used for this media buy'), - ] = None - pacing_index: Annotated[ - StrictFloat | None, - Field( - description='Aggregate media-buy delivery pace relative to the media-buy pacing plan (1.0 = on track, <1.0 = behind, >1.0 = ahead). This is the authoritative pacing signal for seller-optimized buys; package pacing indexes are subordinate diagnostics.', - ge=0.0, - ), - ] = None - totals: Totals - by_package: Annotated[list[ByPackageItem], Field(description='Metrics broken down by package')] - windows: Annotated[ - list[Window] | None, - Field( - description="Per-window delivery slices over the reporting period at the requested time_granularity. Only present when the request set time_granularity and include_window_breakdown: true. Each slice mirrors what reporting_webhook would have delivered for the same window — buyers who missed webhook fires can reconstruct identical data by reading this array. Slice rows are ordered by window_start ascending; consecutive rows are contiguous (each row's window_end equals the next row's window_start) and partition the requested date range at the chosen granularity. For a legacy or external mixed-currency media buy, monetary and money-derived values MUST be omitted from each window totals object and reported only in currency-qualified windows[].by_package rows. Sellers MUST exclude this field when time_granularity is omitted; when set, sellers MUST honor pulls at any granularity in reporting_capabilities.windowed_pull_granularities (otherwise return UNSUPPORTED_GRANULARITY). See snapshot-and-log Rule 4 for the two-paths-parity contract this surface anchors." - ), - ] = None - daily_breakdown: Annotated[ - list[DailyBreakdownItem1] | None, - Field( - description="Day-by-day delivery for a media-buy row with one currency. Sellers MUST omit this aggregate breakdown for a legacy or externally created mixed-currency buy because these rows have no package currency field. Sellers MUST also omit this aggregate breakdown when the media buy's packages span more than one reporting timezone; package-level daily_breakdown remains, each in its own product's reporting timezone." - ), - ] = None - - -class GetMediaBuyDeliveryResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - notification_type: Annotated[ - NotificationType | None, - Field( - description='Type of webhook notification (only present in webhook deliveries): scheduled = regular periodic update, final = campaign completed, delayed = data not yet available, adjusted = resending period with corrected data (same window), window_update = resending period with a wider measurement window (e.g., C3 superseding live, C7 superseding C3)' - ), - ] = None - partial_data: Annotated[ - StrictBool | None, - Field( - description='Indicates if any media buys in this webhook have missing/delayed data (only present in webhook deliveries)' - ), - ] = None - unavailable_count: Annotated[ - SchemaInt | None, - Field( - description='Number of media buys with reporting_delayed or failed status (only present in webhook deliveries when partial_data is true)', - ge=0, - ), - ] = None - sequence_number: Annotated[ - SchemaInt | None, - Field( - description='Sequential notification number (only present in webhook deliveries, starts at 1)', - ge=1, - ), - ] = None - next_expected_at: Annotated[ - AwareDatetime | None, - Field( - description="ISO 8601 timestamp for next expected notification (only present in webhook deliveries when notification_type is not 'final')" - ), - ] = None - reporting_period: Annotated[ - ReportingPeriod, - Field( - description="Half-open period for the report: start is inclusive and end is exclusive. Both are instants. For a date-bounded request they are the instants at which start_date and end_date begin in the reporting timezone (the in-scope products' reporting_capabilities.timezone, echoed in timezone). They fall on UTC midnight only when that timezone is UTC. An exact reporting_revision_id read returns the revision's period instead, whose source_timezone names its calendar." - ), - ] - reporting_revision_binding: Annotated[ - ReportingRevisionBinding | None, - Field( - description='Present only for a reporting_revision_id selector. Binds the complete ordered reporting_rows sequence obtained by concatenating every cursor page to one immutable Reliable Reporting revision; consumers verify content_sha256 over RFC 8785 JCS of {reporting_revision_id,row_count,control_totals,reporting_rows} before using it as revision evidence.' - ), - ] = None - reporting_revision: Annotated[ - reporting_revision_1.ReportingRevision | None, - Field( - description='Full immutable revision metadata, identical to the get_reporting_status revision record.' - ), - ] = None - reporting_rows: Annotated[ - list[dict[str, Any]] | None, - Field( - description="One page of authoritative logical rows for an exact reporting_revision_id read. Validate every row against the revision's digest-pinned schema; concatenate pages in cursor order before verifying the binding digest." - ), - ] = None - pagination: Annotated[ - pagination_response.PaginationResponse | None, - Field( - description='Exact revision pages are frozen for the cursor walk. total_count is mandatory for reporting_revision_id and equals reporting_revision_1.row_count and reporting_revision_binding.row_count; it counts the complete ordered row sequence, not this page.' - ), - ] = None - currency: Annotated[ - str | None, - Field( - deprecated=True, - description='Deprecated in AdCP 3.2 and removed in AdCP 4.0. Optional legacy response-wide ISO 4217 currency code. It may be used only when every monetary value in the response has that denomination. A delivery response can contain media buys with different currencies, so buyers MUST NOT interpret this field as an aggregation currency or evidence of currency conversion. Prefer media_buy_deliveries[].currency when present and package-level currency otherwise.', - pattern='^[A-Z]{3}$', - ), - ] = None - attribution_window: Annotated[ - attribution_window_1.AttributionWindow | None, - Field( - description='Attribution methodology and lookback windows used for conversion metrics in this response. All media buys from a single seller share the same attribution methodology. Enables cross-platform comparison (e.g., Amazon 14-day click vs. Criteo 30-day click).' - ), - ] = None - aggregated_totals: Annotated[ - AggregatedTotals | None, - Field( - deprecated=True, - description='Deprecated in AdCP 3.2 and removed in AdCP 4.0. Legacy combined metrics across all returned media buys. When this field is present, the deprecated response-wide currency is required and denominates its spend. Cross-buy totals are unsafe when currencies, metric qualifiers, measurement windows, finality, or deduplication semantics differ. Sellers SHOULD omit this field; buyers SHOULD aggregate media_buy_deliveries[] only when the relevant row semantics are compatible.', - ), - ] = None - media_buy_deliveries: Annotated[ - Sequence[MediaBuyDelivery], - Field( - description='Array of delivery data for media buys. When used in webhook notifications, may contain multiple media buys aggregated by publisher. When used in get_media_buy_delivery API responses, typically contains requested media buys.' - ), - ] - errors: Annotated[ - list[error.Error] | None, - Field( - description='Task-specific errors and warnings (e.g., missing delivery data, reporting platform issues)' - ), - ] = None - sandbox: Annotated[ - StrictBool | None, - Field(description='When true, this response contains simulated data from sandbox mode.'), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/get_media_buys_request.py b/src/adcp/types/generated_poc/media_buy/get_media_buys_request.py deleted file mode 100644 index f98980d8f..000000000 --- a/src/adcp/types/generated_poc/media_buy/get_media_buys_request.py +++ /dev/null @@ -1,95 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/get_media_buys_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import SchemaInt -from pydantic import ConfigDict, Field, RootModel, StrictBool - -from ..core import account_ref -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core import pagination_request -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import indicator_type, media_buy_status - - -class StatusFilter(RootModel[list[media_buy_status.MediaBuyStatus]]): - root: Annotated[ - list[media_buy_status.MediaBuyStatus], - Field( - description='Filter by status. Can be a single status or array of statuses. Defaults to ["active"] when media_buy_ids is omitted. When media_buy_ids is provided, no implicit status filter is applied.', - min_length=1, - ), - ] - - -class GetMediaBuysRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - account: Annotated[ - account_ref.AccountReference | None, - Field( - description='Account to retrieve media buys for. When omitted, returns data across all accessible accounts.' - ), - ] = None - media_buy_ids: Annotated[ - list[str] | None, - Field( - description='Array of media buy IDs to retrieve. When omitted, returns a paginated set of accessible media buys matching status_filter.', - min_length=1, - ), - ] = None - status_filter: Annotated[ - media_buy_status.MediaBuyStatus | StatusFilter | None, - Field( - description='Filter by status. Can be a single status or array of statuses. Defaults to ["active"] when media_buy_ids is omitted. When media_buy_ids is provided, no implicit status filter is applied.' - ), - ] = None - indicator_types: Annotated[ - list[indicator_type.IndicatorType] | None, - Field( - description='Return media buys with at least one matching current indicator on the media buy, a package, or a package–creative assignment. Values within this field use OR logic; this field composes with status_filter using AND logic. Buyers MUST NOT send this filter unless the seller advertises every requested type in get_adcp_capabilities.media_buy.supported_indicator_types. A seller MAY reject a request that violates this precondition with UNSUPPORTED_FEATURE rather than silently returning an unfiltered superset.', - min_length=1, - ), - ] = None - include_snapshot: Annotated[ - StrictBool | None, - Field( - description='When true, include a near-real-time delivery snapshot for each package. Snapshots reflect the latest available entity-level stats from the platform (e.g., updated every ~15 minutes on GAM, ~1 hour on batch-only platforms). The staleness_seconds field on each snapshot indicates data freshness. If a snapshot cannot be returned, package.snapshot_unavailable_reason explains why. Defaults to false.' - ), - ] = False - include_history: Annotated[ - SchemaInt | None, - Field( - description='When present, include the last N revision history entries for each media buy (returns min(N, available entries)). Each entry contains revision number, timestamp, actor, and a summary of what changed. Omit or set to 0 to exclude history (default). Recommended: 5-10 for monitoring, 50+ for audit.', - ge=0, - le=1000, - ), - ] = 0 - include_webhook_activity: Annotated[ - StrictBool | None, - Field( - description="When true, each returned media buy includes a `webhook_activity` array describing recent delivery-report webhook fires for the calling principal. Used by buyer agents to verify whether a publisher actually fired against the buyer's registered endpoint and what the endpoint returned — closes the operator-ticket loop for webhook debugging. Scoped to the calling principal: a buyer sees only fires targeting its own endpoint, even when multiple principals share visibility into the same media buy. Defaults to false. See `webhook_activity_limit` for the per-buy cap." - ), - ] = False - webhook_activity_limit: Annotated[ - SchemaInt | None, - Field( - description="Maximum number of webhook delivery records to return per media buy, ordered most-recent first. Ignored when `include_webhook_activity` is false. Sellers that surface webhook activity MUST retain records for at least 30 days from each record's `completed_at` (see `webhook_activity` description in the response schema for the `pending`-status carve-out); sellers unable to honor that floor MUST omit the field entirely rather than truncate. When a buy has more historical fires than the limit, only the most recent are returned — there is no cursor for older fires; this surface is a debug aid, not a full audit log.", - ge=1, - le=200, - ), - ] = 50 - pagination: Annotated[ - pagination_request.PaginationRequest | None, - Field( - description='Cursor-based pagination controls. Strongly recommended when querying broad scopes (for example, all active media buys in an account).' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/get_media_buys_response.py b/src/adcp/types/generated_poc/media_buy/get_media_buys_response.py deleted file mode 100644 index e649ceaca..000000000 --- a/src/adcp/types/generated_poc/media_buy/get_media_buys_response.py +++ /dev/null @@ -1,807 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/get_media_buys_response.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from collections.abc import Sequence -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat - -from ..core import account as account_1 -from ..core import bidding_policy -from ..core import budget_allocation as budget_allocation_1 -from ..core import business_entity, canonical_format_kind, canonical_media_buy_action -from ..core import context as context_1 -from ..core import creative_approval_scope, error -from ..core import ext as ext_1 -from ..core import ( - format_id, - format_option_ref, - impairment, - indicator_scope, - media_buy_available_action, - media_buy_frequency_cap, - optimization_goal, - package_format_snapshot, - package_targeting_resolution, - pagination_response, - targeting, - webhook_activity_record, -) -from ..core.canonical_proposal import CanonicalProposal -from ..core.indicator_bearing import IndicatorBearingResourceState -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import canceled_by as canceled_by_1 -from ..enums import creative_approval_status -from ..enums import delivery_status as delivery_status_1 -from ..enums import media_buy_health, media_buy_status, media_buy_valid_action -from ..enums import pacing as pacing_1 -from ..enums import snapshot_unavailable_reason as snapshot_unavailable_reason_1 - - -class IndicatorTypesEvaluatedEnum(StrEnum): - creative_fatigue = 'creative_fatigue' - creative_quality_opportunity = 'creative_quality_opportunity' - creative_diversity_low = 'creative_diversity_low' - audience_saturation = 'audience_saturation' - inventory_shortfall_forecast = 'inventory_shortfall_forecast' - pacing_risk = 'pacing_risk' - budget_constrained = 'budget_constrained' - budget_constrained_1 = 'budget_constrained' - - -class ProposalStatus(StrEnum): - draft = 'draft' - committed = 'committed' - accepted = 'accepted' - - -class Cancellation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - canceled_at: Annotated[ - AwareDatetime, Field(description='ISO 8601 timestamp when this media buy was canceled.') - ] - canceled_by: Annotated[ - canceled_by_1.CanceledBy, Field(description='Which party initiated the cancellation.') - ] - reason: Annotated[ - str | None, Field(description='Reason the media buy was canceled.', max_length=500) - ] = None - - -class HistoryItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - revision: Annotated[ - SchemaInt, Field(description='Revision number after this change was applied.', ge=1) - ] - timestamp: Annotated[AwareDatetime, Field(description='When this change occurred.')] - actor: Annotated[ - str | None, - Field( - description='Identity of who made the change — derived from authentication context, not caller-provided. Format is seller-defined (e.g., agent URL, user email, API key label).' - ), - ] = None - action: Annotated[ - str, - Field( - description='What happened. Standard actions: created, activated, paused, resumed, canceled, rejected, completed, updated_budget, updated_dates, updated_packages, package_canceled, package_paused, package_resumed. Sellers MAY use additional platform-specific actions (e.g., creative_approved, targeting_updated) — use ext on the history entry for structured metadata about custom actions.' - ), - ] - summary: Annotated[ - str | None, - Field( - description="Human-readable summary of the change (e.g., 'Budget increased from $5,000 to $7,500 on pkg_abc').", - max_length=500, - ), - ] = None - package_id: Annotated[ - str | None, - Field(description='Package affected, when the change targeted a specific package.'), - ] = None - ext: ext_1.ExtensionObject | None = None - - -class IndicatorTypesEvaluatedEnum1(StrEnum): - creative_fatigue = 'creative_fatigue' - creative_quality_opportunity = 'creative_quality_opportunity' - creative_diversity_low = 'creative_diversity_low' - audience_saturation = 'audience_saturation' - inventory_shortfall_forecast = 'inventory_shortfall_forecast' - pacing_risk = 'pacing_risk' - budget_constrained = 'budget_constrained' - creative_diversity_low_1 = 'creative_diversity_low' - audience_saturation_1 = 'audience_saturation' - inventory_shortfall_forecast_1 = 'inventory_shortfall_forecast' - pacing_risk_1 = 'pacing_risk' - budget_constrained_1 = 'budget_constrained' - - -class Cancellation1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - canceled_at: Annotated[ - AwareDatetime, Field(description='ISO 8601 timestamp when this package was canceled.') - ] - canceled_by: Annotated[ - canceled_by_1.CanceledBy, - Field(description='Which party initiated the package cancellation.'), - ] - reason: Annotated[ - str | None, Field(description='Reason the package was canceled.', max_length=500) - ] = None - - -class IndicatorTypesEvaluatedEnum2(StrEnum): - creative_fatigue = 'creative_fatigue' - creative_quality_opportunity = 'creative_quality_opportunity' - creative_diversity_low = 'creative_diversity_low' - audience_saturation = 'audience_saturation' - inventory_shortfall_forecast = 'inventory_shortfall_forecast' - pacing_risk = 'pacing_risk' - budget_constrained = 'budget_constrained' - creative_fatigue_1 = 'creative_fatigue' - creative_quality_opportunity_1 = 'creative_quality_opportunity' - - -class Snapshot(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - as_of: Annotated[ - AwareDatetime, - Field(description='ISO 8601 timestamp when this snapshot was captured by the platform'), - ] - staleness_seconds: Annotated[ - SchemaInt, - Field( - description='Maximum age of this data in seconds. For example, 900 means the data may be up to 15 minutes old. Use this to interpret zero delivery: a value of 900 means zero impressions is likely real; a value of 14400 means reporting may still be catching up.', - ge=0, - ), - ] - impressions: Annotated[ - StrictFloat, Field(description='Total impressions delivered since package start', ge=0.0) - ] - spend: Annotated[ - StrictFloat, - Field( - description='Total spend since package start, denominated in snapshot.currency when present, otherwise package.currency or media_buy.currency', - ge=0.0, - ), - ] - currency: Annotated[ - str | None, - Field( - description='ISO 4217 currency code for spend in this snapshot. Optional when unchanged from package.currency or media_buy.currency.', - pattern='^[A-Z]{3}$', - ), - ] = None - clicks: Annotated[ - StrictFloat | None, - Field(description='Total clicks since package start (when available)', ge=0.0), - ] = None - pacing_index: Annotated[ - StrictFloat | None, - Field( - description='Current delivery pace relative to expected (1.0 = on track, <1.0 = behind, >1.0 = ahead). Absent when pacing cannot be determined.', - ge=0.0, - ), - ] = None - delivery_status: Annotated[ - delivery_status_1.DeliveryStatus | None, - Field( - description="Operational delivery state of this package. 'not_delivering' means the package is within its scheduled flight but has delivered zero impressions for at least one full staleness cycle — the signal for automated price adjustments or buyer alerts. Implementers must not return 'not_delivering' until at least staleness_seconds have elapsed since package activation." - ), - ] = None - ext: Annotated[ - ext_1.ExtensionObject | None, - Field(description='Optional extension object for seller-specific snapshot fields.'), - ] = None - - -class Indicator(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - IndicatorTypesEvaluatedEnum, - Field( - description="A seller-asserted material risk or optimization opportunity that currently warrants buyer attention on an AdCP resource relationship. AdCP standardizes the broad meaning of each value for the protocol release; it does not standardize or version the seller's detection methodology or prescribe a universal remedy.", - title='Indicator Type', - ), - ] - detected_at: Annotated[ - AwareDatetime | None, - Field( - description='When the seller first detected the current uninterrupted occurrence of this indicator. Keep this value stable while the condition remains present. If the condition clears and is later detected again, use the new detection time. Optional because some upstream platforms expose current assessments without an original detection timestamp.' - ), - ] = None - scope: Annotated[ - list[indicator_scope.IndicatorScope] | None, - Field( - description='Optional narrower publisher or placement scope within the enclosing media buy, package, or package–creative assignment. Omit only when the seller evaluated and asserts the indicator across the whole enclosing resource/relationship. When partial indicators_evaluated_scope is declared, every returned indicator MUST include scope and every entry MUST fall within that coverage. This is scope, not source: the responding seller remains the source.', - min_length=1, - ), - ] = None - ext: Annotated[ - ext_1.ExtensionObject | None, - Field( - description='Seller- or provider-specific detail such as scores, thresholds, evaluation windows, methodology identifiers, or upstream attribution. Core consumers must not need ext to understand the broad meaning of type.' - ), - ] = None - - -class Indicator1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - IndicatorTypesEvaluatedEnum1, - Field( - description="A seller-asserted material risk or optimization opportunity that currently warrants buyer attention on an AdCP resource relationship. AdCP standardizes the broad meaning of each value for the protocol release; it does not standardize or version the seller's detection methodology or prescribe a universal remedy.", - title='Indicator Type', - ), - ] - detected_at: Annotated[ - AwareDatetime | None, - Field( - description='When the seller first detected the current uninterrupted occurrence of this indicator. Keep this value stable while the condition remains present. If the condition clears and is later detected again, use the new detection time. Optional because some upstream platforms expose current assessments without an original detection timestamp.' - ), - ] = None - scope: Annotated[ - list[indicator_scope.IndicatorScope] | None, - Field( - description='Optional narrower publisher or placement scope within the enclosing media buy, package, or package–creative assignment. Omit only when the seller evaluated and asserts the indicator across the whole enclosing resource/relationship. When partial indicators_evaluated_scope is declared, every returned indicator MUST include scope and every entry MUST fall within that coverage. This is scope, not source: the responding seller remains the source.', - min_length=1, - ), - ] = None - ext: Annotated[ - ext_1.ExtensionObject | None, - Field( - description='Seller- or provider-specific detail such as scores, thresholds, evaluation windows, methodology identifiers, or upstream attribution. Core consumers must not need ext to understand the broad meaning of type.' - ), - ] = None - - -class Indicator2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - IndicatorTypesEvaluatedEnum2, - Field( - description="A seller-asserted material risk or optimization opportunity that currently warrants buyer attention on an AdCP resource relationship. AdCP standardizes the broad meaning of each value for the protocol release; it does not standardize or version the seller's detection methodology or prescribe a universal remedy.", - title='Indicator Type', - ), - ] - detected_at: Annotated[ - AwareDatetime | None, - Field( - description='When the seller first detected the current uninterrupted occurrence of this indicator. Keep this value stable while the condition remains present. If the condition clears and is later detected again, use the new detection time. Optional because some upstream platforms expose current assessments without an original detection timestamp.' - ), - ] = None - scope: Annotated[ - list[indicator_scope.IndicatorScope] | None, - Field( - description='Optional narrower publisher or placement scope within the enclosing media buy, package, or package–creative assignment. Omit only when the seller evaluated and asserts the indicator across the whole enclosing resource/relationship. When partial indicators_evaluated_scope is declared, every returned indicator MUST include scope and every entry MUST fall within that coverage. This is scope, not source: the responding seller remains the source.', - min_length=1, - ), - ] = None - ext: Annotated[ - ext_1.ExtensionObject | None, - Field( - description='Seller- or provider-specific detail such as scores, thresholds, evaluation windows, methodology identifiers, or upstream attribution. Core consumers must not need ext to understand the broad meaning of type.' - ), - ] = None - - -class CreativeApproval(IndicatorBearingResourceState): - model_config = ConfigDict( - extra='allow', - ) - indicator_types_evaluated: Annotated[ - list[IndicatorTypesEvaluatedEnum2] | None, - Field( - description='Indicator types covered by this snapshot. Required whenever indicators is present. Types omitted from this list remain unknown even when indicators is empty. Every returned indicator.type MUST appear in this list.', - min_length=1, - ), - ] = None - indicators: Annotated[ - list[Indicator2] | None, - Field( - description='Current seller assertions for the indicator types and publisher/placement coverage named by the sibling evaluation fields. Omitted means unknown or not evaluated. A present empty array means evaluated with no current assertion for indicator_types_evaluated in the evaluated scope.' - ), - ] = None - creative_id: Annotated[str, Field(description='Creative identifier')] - approval_status: creative_approval_status.CreativeApprovalStatus - rejection_reason: Annotated[ - str | None, - Field( - description="Human-readable explanation of why the creative was rejected. Present only when approval_status is 'rejected'." - ), - ] = None - approval_scopes: Annotated[ - list[creative_approval_scope.ScopedCreativeApproval] | None, - Field( - description='Complete, disjoint publisher/placement approval partition when approval_status is partially_approved. A normalized scope appears once. For one publisher, use either one publisher-wide row or placement-specific rows, never both. Omit when one approval_status applies uniformly to the whole assignment. The same scoped outcomes are mirrored on list_creatives.', - min_length=2, - ), - ] = None - indicators_as_of: Annotated[ - AwareDatetime | None, - Field( - description='When the seller last completed the evaluation represented by indicators for this relationship. Required whenever indicators is present, including an empty array.' - ), - ] = None - indicators_evaluated_scope: Annotated[ - list[indicator_scope.IndicatorScope] | None, - Field( - description='Optional publisher or placement scopes covered by this evaluation. Omit when indicators covers the whole package–creative assignment. When present, scopes not listed remain unknown; every returned indicator.scope entry MUST be contained by this set.', - min_length=1, - ), - ] = None - - -class AcceptedProposal(CanonicalProposal): - proposal_status: Annotated[ - Literal['accepted'], - Field( - description='draft is indicative and unreserved; committed has firm terms with inventory reserved until expires_at; accepted is the historical snapshot attached to a MediaBuy.', - title='Proposal Status', - ), - ] = 'accepted' - media_buy_id: Annotated[str, Field(min_length=1)] - accepted_at: Any - commercial_terms: Any - terms_digest: Any - - -class Package(IndicatorBearingResourceState): - model_config = ConfigDict( - extra='allow', - ) - indicator_types_evaluated: Annotated[ - list[IndicatorTypesEvaluatedEnum1] | None, - Field( - description='Indicator types covered by this snapshot. Required whenever indicators is present. Types omitted from this list remain unknown even when indicators is empty. Every returned indicator.type MUST appear in this list.', - min_length=1, - ), - ] = None - indicators: Annotated[ - list[Indicator1] | None, - Field( - description='Current seller assertions for the indicator types and publisher/placement coverage named by the sibling evaluation fields. Omitted means unknown or not evaluated. A present empty array means evaluated with no current assertion for indicator_types_evaluated in the evaluated scope.' - ), - ] = None - package_id: Annotated[str, Field(description="Seller's package identifier")] - product_id: Annotated[ - str | None, - Field( - description="Product identifier this package is purchased from. For packages created from an explicit create_media_buy package request, sellers MUST echo the request package's product_id on every response package object that represents that requested package." - ), - ] = None - budget: Annotated[ - StrictFloat | None, - Field( - description='Hard lifetime package spend cap denominated in media_buy.currency. In seller-optimized mode this is not a current allocation.', - ge=0.0, - ), - ] = None - min_spend_target: Annotated[ - StrictFloat | None, - Field( - description='Accepted soft lifetime spend target for this package under seller-optimized allocation.', - ge=0.0, - ), - ] = None - daily_budget_cap: Annotated[ - StrictFloat | None, - Field( - description='Current hard package spend ceiling per shared media-buy cap day, denominated in media_buy.currency. It is a subordinate ceiling, not a reserved allocation; media_buy.budget_cap_timezone defines the day boundary.', - ge=0.0, - ), - ] = None - currency: Annotated[ - str | None, - Field( - description='Legacy/readback package denomination for buys created outside the canonical 3.2 path. For AdCP-authored media buys this MUST equal media_buy.currency; canonical package budget and BiddingPolicy values always use media_buy.currency. Snapshot currency may still identify externally reported spend denomination.', - pattern='^[A-Z]{3}$', - ), - ] = None - bid_price: Annotated[ - StrictFloat | None, - Field( - deprecated=True, - description='DEPRECATED legacy bid representation. 3.2 sellers SHOULD normalize and echo package.bidding instead.', - ge=0.0, - ), - ] = None - bidding: Annotated[ - bidding_policy.BiddingPolicy | None, - Field( - description='Package-authored bidding override. `{automatic:true}` explicitly overrides media_buy.bidding with provider automatic delivery. Omitted when this package inherits; sellers MUST NOT copy an inherited block here. Monetary fields use media_buy.currency.' - ), - ] = None - optimization_goals: Annotated[ - list[optimization_goal.OptimizationGoal] | None, - Field( - description='Current package objective functions. Currency-bearing execution controls are returned separately in package.bidding or inherited from media_buy.bidding.', - min_length=1, - ), - ] = None - format_ids: Annotated[ - list[format_id.FormatReferenceStructuredObject] | None, - Field( - deprecated=True, - description='Deprecated in AdCP 3.2; removed in AdCP 4.0. Legacy named-format IDs supplied for this package on create_media_buy. Sellers SHOULD echo this field whenever the request included it, including dual-emission cases where another selector won precedence.', - min_length=1, - ), - ] = None - format_option_refs: Annotated[ - list[format_option_ref.FormatOptionReference] | None, - Field( - description='Structured 3.1+ format option references supplied for this package on create_media_buy. Sellers SHOULD echo this field whenever the request included it.', - min_length=1, - ), - ] = None - format_kind: Annotated[ - canonical_format_kind.CanonicalFormatKind | None, - Field( - description='Direct canonical selector supplied for this package on create_media_buy. Sellers SHOULD echo this field whenever the request included it, including informational-echo cases where another selector won precedence.' - ), - ] = None - params: Annotated[ - dict[str, Any] | None, - Field( - description='Parameters for the direct canonical selector in `format_kind`, echoed from the create_media_buy request whenever the request included it. Requires `format_kind`.' - ), - ] = None - impressions: Annotated[ - StrictFloat | None, - Field(description='Goal impression count for impression-based packages', ge=0.0), - ] = None - pacing: Annotated[ - pacing_1.Pacing | None, - Field( - description='Package-level pacing preference. Under seller-optimized allocation this is subordinate to the media-buy aggregate pacing.' - ), - ] = None - targeting_overlay: Annotated[ - targeting.TargetingOverlay | None, - Field( - description='Complete effective targeting applied to this package, including configured-product targeting and the most recent package-specific overlay. Sellers SHOULD echo persisted targeting so buyers can verify stored state without replaying requests. Sellers MUST echo geo_places and geo_places_exclude whenever either was persisted, including the exact applied system_version and normalized values, so buyers can audit catalog-backed targeting. Sellers using placement, property-list, or collection-list targeting MUST include the committed inventory selection here. placement_selection mode default SHOULD resolve to mode selected with committed refs when enumerable; collection_selection follows the same rule, materializing the committed selectors even when the selection was produced through collection_list references.' - ), - ] = None - targeting_resolution: Annotated[ - package_targeting_resolution.PackageTargetingResolution | None, - Field( - description='Execution details for accepted package targeting. Sellers MUST include targeting_resolution.demographics whenever demographic targeting was requested or applied; its applied predicate and execution fields report the effective booked state.' - ), - ] = None - start_time: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 flight start time for this package. Use to determine whether the package is within its scheduled flight before interpreting delivery status.' - ), - ] = None - end_time: Annotated[ - AwareDatetime | None, Field(description='ISO 8601 flight end time for this package') - ] = None - paused: Annotated[ - StrictBool | None, - Field(description='Whether this package is currently paused by the buyer'), - ] = None - canceled: Annotated[ - StrictBool | None, - Field( - description='Whether this package has been canceled. Canceled packages stop delivery and cannot be reactivated.' - ), - ] = None - cancellation: Annotated[ - Cancellation1 | None, - Field(description='Cancellation metadata. Present only when canceled is true.'), - ] = None - creative_deadline: Annotated[ - AwareDatetime | None, - Field( - description="ISO 8601 timestamp for creative upload or change deadline for this package. After this deadline, creative changes are rejected. When absent, the media buy's creative_deadline applies." - ), - ] = None - context: Annotated[ - context_1.ContextObject | None, - Field( - description='Opaque package-level correlation data echoed unchanged from the create_media_buy package request. Sellers MUST include persisted package context on read surfaces when the package was created through AdCP with context, so buyers can reconcile seller-assigned package_id values with their own line items; this is the legacy-safe fallback when an older seller did not echo product_id on the create response. Sellers MAY omit context for packages created outside AdCP or created without context. Sellers MUST NOT parse this object for business logic.' - ), - ] = None - creative_approvals: Annotated[ - list[CreativeApproval] | None, - Field( - description='Approval status for each creative assigned to this package. Absent when no creatives have been assigned.' - ), - ] = None - formats_to_provide: Annotated[ - list[package_format_snapshot.PackageFormatSnapshot] | None, - Field( - description='The immutable PackageFormatSnapshot checklist established for this package at booking time. Contract-bearing snapshots remain present after creative coverage is complete so readback, assignment, and serving never fall back to a mutable live Product declaration.', - min_length=1, - ), - ] = None - formats_pending: Annotated[ - list[package_format_snapshot.PackageFormatSnapshot] | None, - Field( - description='PackageFormatSnapshot entries from formats_to_provide that do not yet have creative coverage. Each entry MUST be canonically equal to the corresponding checklist snapshot and, when product_snapshot_digest is present, carry the identical digest. An empty emitted array means all requirements are covered; absence means readiness was not reported.' - ), - ] = None - format_ids_to_provide: Annotated[ - list[format_id.FormatReferenceStructuredObject] | None, - Field( - deprecated=True, - description='**DEPRECATED in 3.2.** Legacy named-format projection of formats_to_provide retained for older 3.x peers. New sellers emit canonical formats_to_provide declarations.', - ), - ] = None - format_ids_pending: Annotated[ - list[format_id.FormatReferenceStructuredObject] | None, - Field( - deprecated=True, - description='**DEPRECATED in 3.2.** Legacy named-format projection of formats_pending retained for older 3.x peers. New sellers emit canonical formats_pending declarations. An empty emitted array means every projected requirement is covered. Absence means legacy readiness was not reported and MUST NOT be interpreted as full coverage.', - ), - ] = None - snapshot_unavailable_reason: Annotated[ - snapshot_unavailable_reason_1.SnapshotUnavailableReason | None, - Field( - description='Machine-readable reason the snapshot is omitted. Present only when include_snapshot was true and snapshot is unavailable for this package.' - ), - ] = None - snapshot: Annotated[ - Snapshot | None, - Field( - description='Near-real-time delivery snapshot for this package. Only present when include_snapshot was true in the request. Represents the latest available entity-level stats from the platform — not billing-grade data.' - ), - ] = None - ext: ext_1.ExtensionObject | None = None - - -class MediaBuy(IndicatorBearingResourceState): - model_config = ConfigDict( - extra='allow', - ) - indicator_types_evaluated: Annotated[ - list[IndicatorTypesEvaluatedEnum] | None, - Field( - description='Indicator types covered by this snapshot. Required whenever indicators is present. Types omitted from this list remain unknown even when indicators is empty. Every returned indicator.type MUST appear in this list.', - min_length=1, - ), - ] = None - indicators: Annotated[ - list[Indicator] | None, - Field( - description='Current seller assertions for the indicator types and publisher/placement coverage named by the sibling evaluation fields. Omitted means unknown or not evaluated. A present empty array means evaluated with no current assertion for indicator_types_evaluated in the evaluated scope.' - ), - ] = None - media_buy_id: Annotated[str, Field(description="Seller's unique identifier for the media buy")] - name: Annotated[ - str | None, - Field( - description='Persisted human-readable name for this media buy, shared by buyer and seller for trafficking UI display and operational communication. Sellers MUST include name when the media buy was created through AdCP with name. Sellers MAY omit it for media buys created outside AdCP or created without name. This display label is not an identifier or financial reference.', - max_length=255, - min_length=1, - pattern='\\S', - ), - ] = None - accepted_proposal_id: Annotated[ - str | None, - Field( - description='Current accepted commercial snapshot for compact-lifecycle refinement. Updated atomically when an amendment or negotiated cancellation is accepted.', - max_length=255, - min_length=1, - ), - ] = None - accepted_proposal_terms_digest: Annotated[ - str | None, - Field( - description='Digest of the current accepted proposal commercial_terms.', - pattern='^sha256:[A-Za-z0-9_-]{43}$', - ), - ] = None - accepted_proposal: Annotated[ - AcceptedProposal | None, - Field( - description='Current accepted compact proposal, including the complete digested commercial envelope. Required whenever accepted_proposal_id is present so restarted SDKs can recover control-versus-refinement routing without reconstructing historical offers.' - ), - ] = None - account: Annotated[ - account_1.Account | None, Field(description='Account billed for this media buy') - ] = None - invoice_recipient: Annotated[ - business_entity.BusinessEntity | None, - Field( - description='Per-buy invoice recipient when provided at creation. Confirms the seller accepted the billing override. Bank details are omitted (write-only).' - ), - ] = None - status: media_buy_status.MediaBuyStatus - status_as_of: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp indicating when the seller last refreshed the returned media-buy-level `status` from its source of truth. Use this to interpret cached or rolled-up list statuses, especially for curator/storefront aggregators where one buyer-facing buy maps to multiple upstream legs. For rolled-up statuses, this timestamp MUST NOT be later than the oldest upstream status observation that could affect the returned roll-up, so it never overstates freshness. Omit or return null to make no freshness assertion; buyers MUST NOT infer that an omitted or null value means the status is live. This is distinct from `updated_at`, which records when the media buy was last modified.' - ), - ] = None - health: Annotated[ - media_buy_health.MediaBuyHealth | None, - Field( - description='Dependency health of the media buy, orthogonal to `status`. `ok` (default) when no upstream resource that this buy depends on is in an offline state. `impaired` when at least one such resource (audience, creative, catalog_item, event_source, property) is offline and affects delivery for one or more packages — `impairments[]` MUST be non-empty in that case. On terminal-status buys, the seller MAY leave this field in whatever state held at the terminal transition. See lifecycle.mdx § Compliance and the impairment.coherence assertion.' - ), - ] = media_buy_health.MediaBuyHealth.ok - impairments: Annotated[ - list[impairment.Impairment] | None, - Field( - description='Open impairments — upstream dependency state changes that affect delivery for at least one package on this buy. Empty when `health` is `ok`; non-empty iff `health` is `impaired` (health-iff rule on non-terminal buys). Sellers MUST add an entry on the next read after a referenced resource transitions to an offline state, and MUST remove the entry when the resource returns to a serviceable state or stops being a dependency (e.g., via assignment swap via update_media_buy). Staleness budget: the snapshot MUST reflect the impairment within 5 minutes of `impairment.observed_at` regardless of buyer poll cadence — sellers cannot rely on rare buyer polls to defer write propagation. See impairment.coherence assertion for the cross-resource invariant.' - ), - ] = None - rejection_reason: Annotated[ - str | None, - Field( - description="Reason provided by the seller when status is 'rejected'. Present only when status is 'rejected'." - ), - ] = None - currency: Annotated[ - str, - Field( - description='Single ISO 4217 denomination for total_budget, package budget constraints, and canonical BiddingPolicy monetary fields. Every selected pricing option on an AdCP-authored media buy MUST declare this currency. Legacy or externally-created mixed-currency buys must not expose canonical bidding until normalized or split.', - pattern='^[A-Z]{3}$', - ), - ] - total_budget: Annotated[ - StrictFloat, - Field( - description='Hard aggregate lifetime budget, denominated in media_buy.currency', ge=0.0 - ), - ] - daily_budget_cap: Annotated[ - StrictFloat | None, - Field( - description='Current hard aggregate spend ceiling per calendar day, denominated in media_buy.currency. It bounds total spend without allocating or reserving package spend.', - ge=0.0, - ), - ] = None - frequency_cap: Annotated[ - media_buy_frequency_cap.MediaBuyFrequencyCap | None, - Field( - description='Current hard MediaBuy-level cap. Sellers MUST echo it whenever set, separately from package targeting-overlay caps.' - ), - ] = None - budget_cap_timezone: Annotated[ - str | None, - Field( - description='IANA timezone defining the shared calendar-day boundary for every aggregate and package daily cap. Present whenever any daily cap is set on the media buy.' - ), - ] = None - budget_allocation: Annotated[ - budget_allocation_1.BudgetAllocation | None, - Field( - description='Current cross-package allocation configuration. Omitted means fixed allocation for legacy buys.' - ), - ] = None - pacing: Annotated[ - pacing_1.Pacing | None, - Field(description='Aggregate pacing strategy for the media-buy budget.'), - ] = None - bidding: Annotated[ - bidding_policy.BiddingPolicy | None, - Field( - description='Current media-buy-authored bidding policy with scope-specific goal binding and media-buy-currency denomination. Package entries omit bidding when they inherit this block; explicit package automatic overrides remain visible as `{automatic:true}`.' - ), - ] = None - start_time: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 flight start time for this media buy (earliest package start_time). Avoids requiring buyers to compute min(packages[].start_time).' - ), - ] = None - end_time: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 flight end time for this media buy (latest package end_time). Avoids requiring buyers to compute max(packages[].end_time).' - ), - ] = None - creative_deadline: Annotated[ - AwareDatetime | None, Field(description='ISO 8601 timestamp for creative upload deadline') - ] = None - confirmed_at: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the seller committed to this media buy. May be null until seller commitment occurs in deferred/manual approval flows. Once populated, remains stable through later pause, resume, activation, completion, cancellation, and reporting transitions.' - ), - ] - cancellation: Annotated[ - Cancellation | None, - Field(description="Cancellation metadata. Present only when status is 'canceled'."), - ] = None - revision: Annotated[ - SchemaInt, - Field( - description='Current optimistic concurrency token. Pass this in update_media_buy requests intended to change state. Sellers increment it on mutating state changes/updates and reject stale tokens with CONFLICT when a revision token is provided.', - ge=1, - ), - ] - created_at: Annotated[AwareDatetime | None, Field(description='Creation timestamp')] = None - updated_at: Annotated[AwareDatetime | None, Field(description='Last update timestamp')] = None - context: Annotated[ - context_1.ContextObject | None, - Field( - description='Opaque media-buy-level correlation data echoed unchanged from the create_media_buy request. Sellers MUST include persisted context on read surfaces when the media buy was created through AdCP with context, so buyers can reconcile seller-assigned media_buy_id values with their own tracking state. Sellers MAY omit context for media buys created outside AdCP or created without context. Sellers MUST NOT parse this object for business logic.' - ), - ] = None - valid_actions: Annotated[ - list[media_buy_valid_action.MediaBuyValidAction] | None, - Field( - deprecated=True, - description='Flat-vocabulary actions the buyer can perform on this media buy in its current state. Eliminates the need for agents to internalize the state machine — the seller declares what is permitted right now. Deprecated in favor of `available_actions[]`, which carries mode, optional SLA, and a 3.2 change_term_id link. Sellers SHOULD populate both during the 3.x deprecation window; consumers MUST prefer `available_actions[]` when both are present. Removed in 4.0.', - ), - ] = None - available_actions: Annotated[ - list[ - canonical_media_buy_action.CanonicalMediaBuyAction - | media_buy_available_action.MediaBuyAvailableAction - ] - | None, - Field( - description="Structured per-buy resolution of the actions buyer can perform right now. Authoritative — divergence from product `allowed_actions[]` is expected because accepted proposal terms, current state, authorization, and governance delegation are buy-specific. Each entry carries the resolved mode, optional SLA commitment, and in 3.2 an optional change_term_id linking the accepted proposal right. Deprecated 3.1 terms_ref remains readable as an opaque compatibility pointer. Predicate queries via #4425's `requires` grammar address fields by dotted path, e.g. `available_actions.extend_flight.sla.response_max`. Absent SLA means no commitment, not zero commitment — callers composing duration predicates MUST also compose with `present: true` to avoid silently matching sellers who never declared one." - ), - ] = None - webhook_activity: Annotated[ - list[webhook_activity_record.WebhookActivityRecord] | None, - Field( - description='Recent webhook fires relevant to this buy for the calling principal, most-recent first. Includes per-buy delivery/health fires and account-anchored indicators.changed or creative.assignment_changed invalidations whose payload names this media_buy_id. Present only when include_webhook_activity was true and the seller surfaces this debug capability. Account-anchored records MUST include subscriber_id. Three-state presence and the 30-day retention floor follow snapshot-and-log.mdx § Webhook activity log pattern.', - max_length=200, - ), - ] = None - history: Annotated[ - list[HistoryItem] | None, - Field( - description='Revision history entries, most recent first. Only present when include_history > 0 in the request. Each entry represents a state change or update to the media buy. Entries are append-only: sellers MUST NOT modify or delete previously emitted history entries. Callers MAY cache entries by revision number. Returns min(N, available entries) when include_history exceeds the total.' - ), - ] = None - packages: Annotated[ - Sequence[Package], - Field( - description='Packages within this media buy, augmented with creative approval status and optional delivery snapshots' - ), - ] - ext: ext_1.ExtensionObject | None = None - - -class GetMediaBuysResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - media_buys: Annotated[ - Sequence[MediaBuy], - Field( - description='Array of media buys with status, creative approval state, and optional delivery snapshots' - ), - ] - errors: Annotated[ - list[error.Error] | None, - Field( - description='Task-specific errors. A read may return media_buys plus nonfatal resource-state errors. If a pinned place target becomes unexecutable, sellers MUST include PLACE_TARGET_UNAVAILABLE with recovery=correctable, field pointing to the exact media_buys[N].packages[M].targeting_overlay.geo_places[_exclude][A].values[V] response path, and details containing media_buy_id, package_id, system, system_version, country, place_type, and value. The persisted target remains echoed until an intentional update replaces it.' - ), - ] = None - pagination: Annotated[ - pagination_response.PaginationResponse | None, - Field(description='Pagination metadata for the media_buys array.'), - ] = None - sandbox: Annotated[ - StrictBool | None, - Field(description='When true, this response contains simulated data from sandbox mode.'), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/get_products_async_response_input_required.py b/src/adcp/types/generated_poc/media_buy/get_products_async_response_input_required.py deleted file mode 100644 index 30c6eba03..000000000 --- a/src/adcp/types/generated_poc/media_buy/get_products_async_response_input_required.py +++ /dev/null @@ -1,38 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/get_products_async_response_input_required.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core import product - - -class Reason(StrEnum): - CLARIFICATION_NEEDED = 'CLARIFICATION_NEEDED' - BUDGET_REQUIRED = 'BUDGET_REQUIRED' - - -class GetProductsInputRequired(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - reason: Annotated[ - Reason | None, Field(description='Reason code indicating why input is needed') - ] = None - partial_results: Annotated[ - list[product.Product] | None, - Field(description='Partial product results that may help inform the clarification'), - ] = None - suggestions: Annotated[ - list[str] | None, Field(description='Suggested values or options for the required input') - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/get_products_async_response_submitted.py b/src/adcp/types/generated_poc/media_buy/get_products_async_response_submitted.py deleted file mode 100644 index 32193d55f..000000000 --- a/src/adcp/types/generated_poc/media_buy/get_products_async_response_submitted.py +++ /dev/null @@ -1,50 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/get_products_async_response_submitted.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 - - -class GetProductsSubmitted(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Literal['submitted'], - Field( - description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, whose products array is issued in-line. See task-status.json for the full task-status enum.' - ), - ] = 'submitted' - task_id: Annotated[ - str, - Field( - description='Task handle the buyer uses with get_task_status (or the legacy AdCP tasks/get alias), and that the seller references on push-notification callbacks. The products array is issued on the completion artifact, not here. This AdCP application-layer handle remains the snake_case task_id in every transport payload and is distinct from any transport-native A2A Task id.' - ), - ] - message: Annotated[ - str | None, - Field( - description="Optional human-readable explanation of why the task is submitted — e.g., 'Custom curation queued; typical turnaround 10–30 minutes.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", - max_length=2000, - ), - ] = None - estimated_completion: Annotated[ - AwareDatetime | None, Field(description='Estimated completion time for the search') - ] = None - errors: Annotated[ - list[error.Error] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/get_products_async_response_working.py b/src/adcp/types/generated_poc/media_buy/get_products_async_response_working.py deleted file mode 100644 index b34f235a2..000000000 --- a/src/adcp/types/generated_poc/media_buy/get_products_async_response_working.py +++ /dev/null @@ -1,37 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/get_products_async_response_working.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictFloat - -from ..core import context as context_1 -from ..core import ext as ext_1 - - -class GetProductsWorking(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - percentage: Annotated[ - StrictFloat | None, - Field(description='Progress percentage of the search operation', ge=0.0, le=100.0), - ] = None - current_step: Annotated[ - str | None, - Field( - description="Current step in the search process (e.g., 'searching_inventory', 'validating_availability')" - ), - ] = None - total_steps: Annotated[ - SchemaInt | None, Field(description='Total number of steps in the search process') - ] = None - step_number: Annotated[ - SchemaInt | None, Field(description='Current step number (1-indexed)') - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/get_products_rejected.py b/src/adcp/types/generated_poc/media_buy/get_products_rejected.py deleted file mode 100644 index 4b1326afe..000000000 --- a/src/adcp/types/generated_poc/media_buy/get_products_rejected.py +++ /dev/null @@ -1,51 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/get_products_rejected.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from adcp.types._scalar import ScalarStr - -from typing import Annotated, Literal - -from pydantic import ConfigDict, Field - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope - - -class Suggestion(ScalarStr): - __slots__ = () - _constraints = {'max_length': 1000, 'min_length': 1} - - -class GetProductsRejected(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Literal['rejected'], - Field( - description='Task-level business-outcome discriminator. The request was understood and processed, but the seller declined to offer products.' - ), - ] = 'rejected' - reason: Annotated[ - str, - Field( - description="Buyer-facing market explanation for the decline. MAY be sanitized to protect confidential seller rules; for example, 'The requested budget is below the minimum for this inventory' rather than naming the internal rule, candidate products, inventory identifiers, or upstream partners that caused the decision. Plain text only.", - max_length=2000, - min_length=1, - ), - ] - suggestions: Annotated[ - list[Suggestion] | None, - Field( - description='Actionable alternatives the buyer can try, such as changing budget, dates, or channel. If present, the buyer MAY submit a revised brief, but the suggestions do not guarantee acceptance. If absent, the seller is not offering a protocol-level alternative for this brief.', - max_length=20, - min_length=1, - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/get_products_request.py b/src/adcp/types/generated_poc/media_buy/get_products_request.py deleted file mode 100644 index 4eda7c8cc..000000000 --- a/src/adcp/types/generated_poc/media_buy/get_products_request.py +++ /dev/null @@ -1,346 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/get_products_request.json -# timestamp: 2026-10-03T00:36:50+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..core import account_ref, brand_ref -from ..core import catalog as catalog_1 -from ..core import context as context_1 -from ..core import duration -from ..core import ext as ext_1 -from ..core import media_buy_frequency_cap as media_buy_frequency_cap_1 -from ..core import ( - media_buy_support_requirements, - pagination_request, - product_filters, - property_list_ref, -) -from ..core import push_notification_config as push_notification_config_1 -from ..core import targeting, targeting_overlay_requirements -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import delivery_type -from . import acceptance_context as acceptance_context_1 -from . import product_fields - - -class BuyingMode(StrEnum): - brief = 'brief' - wholesale = 'wholesale' - refine = 'refine' - - -class Refine1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['request'], - Field( - description='Change scoped to the overall request — direction for the selection as a whole.' - ), - ] = 'request' - ask: Annotated[ - str, - Field( - description="What the buyer is asking for at the request level (e.g., 'more video options and less display', 'suggest how to combine these products').", - min_length=1, - ), - ] - - -class Action(StrEnum): - include = 'include' - omit = 'omit' - more_like_this = 'more_like_this' - - -class Refine2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[Literal['product'], Field(description='Change scoped to a specific product.')] = 'product' - product_id: Annotated[ - str, Field(description='Product ID from a previous get_products response.', min_length=1) - ] - action: Annotated[ - Action | None, - Field( - description="'include' (default): return this product with updated pricing and data. 'omit': exclude this product from the response. 'more_like_this': find additional products similar to this one (the original is also returned). Optional — when omitted, the seller treats the entry as action: 'include'." - ), - ] = Action.include - ask: Annotated[ - str | None, - Field( - description="What the buyer is asking for on this product. For 'include': specific changes to request (e.g., 'add 16:9 format'). For 'more_like_this': what 'similar' means (e.g., 'same audience but video format'). Ignored when action is 'omit'.", - min_length=1, - ), - ] = None - - -class Action9(StrEnum): - include = 'include' - omit = 'omit' - finalize = 'finalize' - - -class Refine3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['proposal'], Field(description='Change scoped to a specific proposal.') - ] = 'proposal' - proposal_id: Annotated[ - str, Field(description='Proposal ID from a previous get_products response.', min_length=1) - ] - action: Annotated[ - Action9 | None, - Field( - description="'include' (default): return this proposal with updated allocations and pricing. 'omit': exclude this proposal from the response. 'finalize': request firm pricing and inventory hold. New callers use refine_proposals with action revise for a draft successor or action finalize for a committed held successor; terminal feedback is available through decline_proposals.\n\nLegacy finalize is exclusive within the parent `refine[]` array: see the array-level description for the finalize-exclusivity rule (mixing finalize with non-finalize entries is rejected) and multi-finalize atomicity contract." - ), - ] = Action9.include - ask: Annotated[ - str | None, - Field( - description="What the buyer is asking for on this proposal (e.g., 'shift more budget toward video', 'reduce total by 10%'). Ignored when action is omit.", - min_length=1, - ), - ] = None - - -Refine = Annotated[ - Refine1 | Refine2 | Refine3, - Field(discriminator='scope'), -] - - -class Fields(StrEnum): - format_ids = 'format_ids' - outcome_measurement = 'outcome_measurement' - delivery_measurement = 'delivery_measurement' - creative_policy = 'creative_policy' - metric_optimization = 'metric_optimization' - conversion_tracking = 'conversion_tracking' - data_provider_signals = 'data_provider_signals' - included_signals = 'included_signals' - signal_targeting_options = 'signal_targeting_options' - overlay_support = 'overlay_support' - media_buy_support = 'media_buy_support' - installments = 'installments' - is_custom = 'is_custom' - product_card = 'product_card' - product_card_detailed = 'product_card_detailed' - enforced_policies = 'enforced_policies' - trusted_match = 'trusted_match' - - -class Field1(StrEnum): - """Compatibility union of canonical and get-products-only fields.""" - - product_id = 'product_id' - name = 'name' - description = 'description' - publisher_properties = 'publisher_properties' - channels = 'channels' - video_placement_types = 'video_placement_types' - audio_distribution_types = 'audio_distribution_types' - sponsored_placement_types = 'sponsored_placement_types' - social_placement_surfaces = 'social_placement_surfaces' - format_options = 'format_options' - placements = 'placements' - delivery_type = 'delivery_type' - exclusivity = 'exclusivity' - pricing_options = 'pricing_options' - forecast = 'forecast' - reporting_capabilities = 'reporting_capabilities' - measurement_terms = 'measurement_terms' - performance_standards = 'performance_standards' - catalog_types = 'catalog_types' - signal_targeting_allowed = 'signal_targeting_allowed' - signal_targeting_rules = 'signal_targeting_rules' - demographic_targeting = 'demographic_targeting' - overlay_support = 'overlay_support' - collections = 'collections' - collection_targeting_allowed = 'collection_targeting_allowed' - media_buy_support = 'media_buy_support' - audience_evidence = 'audience_evidence' - audience_evidence_selections = 'audience_evidence_selections' - max_optimization_goals = 'max_optimization_goals' - catalog_match = 'catalog_match' - list_applications = 'list_applications' - brief_relevance = 'brief_relevance' - targeting_resolution = 'targeting_resolution' - acceptance_policy_profile_ids = 'acceptance_policy_profile_ids' - identity = 'identity' - execution_requirements = 'execution_requirements' - expires_at = 'expires_at' - allowed_actions = 'allowed_actions' - format_ids = 'format_ids' - outcome_measurement = 'outcome_measurement' - delivery_measurement = 'delivery_measurement' - creative_policy = 'creative_policy' - metric_optimization = 'metric_optimization' - conversion_tracking = 'conversion_tracking' - data_provider_signals = 'data_provider_signals' - included_signals = 'included_signals' - signal_targeting_options = 'signal_targeting_options' - installments = 'installments' - is_custom = 'is_custom' - product_card = 'product_card' - product_card_detailed = 'product_card_detailed' - enforced_policies = 'enforced_policies' - trusted_match = 'trusted_match' - - -class GetProductsRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - idempotency_key: Annotated[ - str | None, - Field( - description='Optional client-generated key on the AdCP 3.x compatibility facade. The field remains optional on every arm for wire compatibility. A seller MAY ignore a supplied key on a guaranteed side-effect-free synchronous read. Buyers SHOULD supply a key whenever the request may allocate a task, finalize a proposal, or otherwise change observable state. When a key is supplied on such a request and the seller declares adcp.idempotency.supported: true, the seller MUST apply the AdCP replay contract before that effect. If the key is omitted or the seller declares adcp.idempotency.supported: false, the buyer has no portable at-most-once retry guarantee after an ambiguous result. New callers SHOULD use the compact 3.2 tasks; each stateful split task has its own idempotency identity, so callers MUST retry with the same tool name. Keys MUST be unique per seller and logical request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] = None - buying_mode: Annotated[ - BuyingMode, - Field( - description="Declares buyer intent for this request. 'brief': publisher curates product recommendations from the provided brief. 'wholesale': buyer requests raw product inventory to apply their own audiences — brief must not be provided, and proposals are omitted. 'refine': iterate on products and proposals from a previous get_products response using the refine array of change requests. v3 clients MUST include buying_mode. Sellers receiving requests from pre-v3 clients without buying_mode SHOULD default to 'brief'. Timing semantics: 'wholesale' is a wholesale product feed read — sellers SHOULD return a synchronous response and MUST NOT route a 'wholesale' request through the async/Submitted arm; partial completion is signalled via the response's incomplete[] field (with optional estimated_wait), not via a task-handoff envelope. 'brief' and 'refine' MAY complete synchronously, or MAY return a Submitted envelope (see get-products-async-response-submitted.json) when curation requires upstream-system queries or HITL review the seller cannot complete inside time_budget. Buyers needing predictable fast wholesale product feed access MUST use 'wholesale'; buyers open to slower curation use 'brief' or 'refine'." - ), - ] - brief: Annotated[ - str | None, - Field( - description="Natural language description of campaign requirements. Required when buying_mode is 'brief'. Must not be provided when buying_mode is 'wholesale' or 'refine'. Buyers SHOULD use structured fields for every requirement that can be expressed structurally, and reserve brief prose for goals, context, preferences, and requirements without a structured representation. Sellers MUST apply explicit hard requirements stated in the brief even when the buyer did not duplicate them in a structured field. When a seller translates hard prose into structured targeting that materially affects product eligibility, pricing, or forecasting, it MUST confirm that interpretation once in GetProductsResponse.targeting_resolution.brief_targeting; otherwise confirmation remains a best practice. If hard prose contradicts a structured field, sellers MUST reject the request with INVALID_REQUEST rather than choose one interpretation or return an unexplained empty result." - ), - ] = None - refine: Annotated[ - list[Refine] | None, - Field( - description="Array of change requests for iterating on products and proposals from a previous get_products response. Each entry declares a scope (request, product, or proposal) and what the buyer is asking for. Only valid when buying_mode is 'refine'. The seller responds to each entry via refinement_applied in the response, matched by position.\n\nFinalize-exclusivity rule: if any entry has `action: 'finalize'`, ALL entries in the array MUST be proposal-scoped with `action: 'finalize'` — mixing finalize entries with `include`/`omit` entries or with request- / product-scoped entries MUST be rejected by the seller with `INVALID_REQUEST`. Finalize is a commit, not a refinement; the buyer expressing intent to commit means refinements have already converged. Buyers needing to refine AND commit in close succession sequence the calls: first a refine call (no finalize), then a finalize call against the resulting `proposal_id`(s).\n\nMulti-finalize semantics: multiple finalize entries against different `proposal_id` values in a single call are allowed and MUST be **atomic at the observation point** — sellers MUST NOT return a success response unless every named proposal has both completed and been persisted as committed. Pre-commit validation runs before any side-effects (inventory pull, terms lock, governance attestation); if any proposal fails validation, the seller MUST reject the entire call without committing any of the named proposals. There is no rollback operation in the spec — an `unfinalize` would itself be a new mutation surface; the atomicity guarantee runs entirely on the seller's pre-commit validation gate, not on post-commit reversal. Sellers that cannot guarantee atomic pre-commit validation MUST reject multi-finalize arrays with `MULTI_FINALIZE_UNSUPPORTED` (preferred — distinguishes seller-side capability gap from a malformed request) or `INVALID_REQUEST` (acceptable fallback for sellers on a pre-3.1 error catalog). If a mid-commit failure occurs *after* validation passed but before all proposals persist (e.g., a downstream ad server fails between commits one and two), the seller MUST return `INTERNAL_ERROR` with `refinement_applied[]` carrying per-position outcomes — the spec does NOT define a recovery path for this case, and buyers SHOULD treat the resulting state as undefined and re-read via `get_media_buys` / equivalent before retrying. Buyers MUST NOT assume multi-finalize support without a successful first attempt — there is no capability flag for this; the failure response is the discovery surface. Buyers whose intent specifically requires atomic commit (e.g., budget-shared proposals where one finalizing without the other is incoherent) MUST be prepared to abandon the intent if the seller returns `MULTI_FINALIZE_UNSUPPORTED` — there is no recovery for that loss of buyer intent beyond sequencing single-finalize calls and accepting the looser commit guarantee.", - min_length=1, - ), - ] = None - brand: Annotated[ - brand_ref.BrandReference | None, - Field( - description='Brand reference for product discovery context. Resolved to full brand identity at execution time.' - ), - ] = None - acceptance_context: Annotated[ - acceptance_context_1.AcceptanceContext | None, - Field( - description='Structured campaign and advertiser facts for seller acceptance-policy preflight. Sellers may infer omitted facts from brand and brief, but uncertainty never implies acceptance.' - ), - ] = None - catalog: Annotated[ - catalog_1.Catalog | None, - Field( - description='Catalog of items the buyer wants to promote. The seller matches catalog items against its inventory and returns products where matches exist. Supports all catalog types: a job catalog finds job ad products, a product catalog finds sponsored product slots. Reference a synced catalog by catalog_id, or provide inline items.' - ), - ] = None - account: Annotated[ - account_ref.AccountReference | None, - Field( - description="Account for product lookup. Returns products with pricing specific to this account's rate card." - ), - ] = None - preferred_delivery_types: Annotated[ - list[delivery_type.DeliveryType] | None, - Field( - description='Delivery types the buyer prefers, in priority order. Unlike filters.delivery_type which excludes non-matching products, this signals preference for curation — the publisher may still include other delivery types when they match the brief well.', - min_length=1, - ), - ] = None - filters: Annotated[ - product_filters.ProductFilters | None, - Field( - description="Offer filters. Valid in brief, wholesale, and refine modes. In every mode, sellers MUST exclude products that do not satisfy them: brief controls curation, wholesale controls feed behavior, and refine controls iteration, but none changes filter semantics. On refine, presence is the complete replacement filter state; when omitted, each referenced product's bound discovery constraints remain in force. Targeting-like legacy fields remain accepted during migration but are deprecated in favor of targeting_overlay and required_overlay_support." - ), - ] = None - targeting_overlay: Annotated[ - targeting.TargetingOverlay | None, - Field( - description="Concrete delivery constraints the buyer expects to carry into create_media_buy. Buyers SHOULD use this field instead of putting equivalent exact targeting only in brief prose. Sellers evaluate these constraints during discovery and scope returned pricing and forecasts to the effective targeting. If a product cannot honor the request exactly, the seller may omit it or return a request-scoped configured product with Product targeting_resolution modifications. Absence of Product targeting_resolution means exact acceptance of this structured overlay; it does not confirm how targeting in the brief was interpreted. On refine, presence is complete replacement state for returned configurations; when omitted, each referenced product's bound targeting remains in force." - ), - ] = None - media_buy_frequency_cap: Annotated[ - media_buy_frequency_cap_1.MediaBuyFrequencyCap | None, - Field( - description='Concrete aggregate max-impression cap intended for the MediaBuy root. Every returned product must participate in one shared counter for this exact value. Legacy proposals echo it as proposals[].frequency_cap; direct buyers repeat it at purchase.' - ), - ] = None - required_overlay_support: Annotated[ - targeting_overlay_requirements.TargetingOverlayRequirements | None, - Field( - description="Minimum product-scoped targeting dimensions the buyer must be able to set independently on packages later. This requests selectable capability, not current targeting values, value-specific availability or forecasts, and not one product per possible value. A requirement true matches support true or any valid support object; an object requirement matches support true or an object containing every requested boolean and requested array subset. The backward-compatible daypart_targets support true is the exception: it satisfies inventory_local requirements only, not iana requirements. Unrequested object fields and numeric limits do not participate. Missing or unknown requirement fields do not match. Seller limit fields are response-only and cannot be requested here. On refine, presence is complete replacement state; when omitted, each referenced product's bound future-support requirements remain in force." - ), - ] = None - required_media_buy_support: Annotated[ - media_buy_support_requirements.ProductMediaBuySupportRequirements | None, - Field( - description='Minimum product participation in shared MediaBuy-level controls. For a frequency cap, every returned product must be composable in one seller-maintained aggregate counter for every requested per unit. On refine, presence is complete replacement state.' - ), - ] = None - property_list: Annotated[ - property_list_ref.PropertyListReference | None, - Field( - deprecated=True, - description='DEPRECATED discovery-only property filter. On compact discovery tasks use criteria.offer_filters.property_list to preserve product eligibility. Use targeting_overlay.property_list only when the buyer intends a delivery constraint, or required_overlay_support.property_list for future selectability. A facade MUST NOT convert this discovery filter into delivery targeting.', - ), - ] = None - fields: Annotated[ - list[product_fields.ProductResponseField | Fields] | None, - Field( - description='Specific product fields to include in the response. When omitted, all fields are returned. Use for lightweight discovery calls where only a subset of product data is needed. product_id and name are always included. `format_ids` is a deprecated 3.x compatibility projection; new integrations request canonical `format_options`. Safety-critical request-specific fields override projection: Product.targeting_resolution and expires_at MUST be included whenever the seller returns modifications, overlay_support MUST be included when required_overlay_support was requested, media_buy_support MUST be included when required_media_buy_support or media_buy_frequency_cap was requested, list_applications MUST be included when a property or collection list is in the effective targeting, and audience_evidence_selections MUST be included when filters.audience_evidence_requirements affects eligibility or ranking. fields controls the optional audience_evidence payload, not either decision receipt. Response-level brief targeting confirmation is not a projected product field.', - min_length=1, - ), - ] = None - time_budget: Annotated[ - duration.Duration | None, - Field( - description='Maximum time the buyer will commit to this request. The seller returns the best results achievable within this budget and does not start processes (human approvals, expensive external queries) that cannot complete in time. When omitted, the seller decides timing.' - ), - ] = None - push_notification_config: Annotated[ - push_notification_config_1.PushNotificationConfig | None, - Field( - description='Optional webhook configuration for async terminal completion/failure notifications on curated discovery. Meaningful only for `buying_mode: "brief"` and `buying_mode: "refine"` requests that enter the async lifecycle. Submitted envelopes with `task_id` remain pollable through `get_task_status` (legacy `tasks/get`) whether or not this field is present. If a brief/refine request includes this field and the seller returns a Submitted envelope, the seller MUST deliver at least the terminal completion/failure notification to the configured URL; intermediate progress notifications are MAY. If the seller cannot honor the webhook channel, it MUST reject the request with a structured error instead of silently accepting. This field does not change wholesale timing semantics: sellers MUST NOT route `buying_mode: "wholesale"` requests through the async/Submitted arm or emit async delivery solely because `push_notification_config` is present; partial wholesale completion is reported via `incomplete[]`.' - ), - ] = None - pagination: Annotated[ - pagination_request.PaginationRequest | None, - Field( - description="Cursor-based pagination controls for get_products. Valid in all buying modes. In brief mode, pagination bounds the seller's returned products[] for the curated answer to the brief and is not an exhaustive catalog-enumeration contract. In refine mode, pagination bounds the refined products[] result implied by refine[] and filters; proposals may accompany a page as plan metadata but are not independently counted by this pagination envelope. In wholesale mode, pagination walks the wholesale product feed and may be combined with wholesale feed versioning." - ), - ] = None - if_wholesale_feed_version: Annotated[ - str | None, - Field( - description="Opaque wholesale_feed_version token returned by a prior wholesale-mode get_products response from this agent. Only valid when buying_mode is wholesale. When provided, the seller compares against its current wholesale product feed version for the buyer's cache_scope and MAY return an unchanged: true response (with products omitted) if nothing has changed. The token is scope-keyed: buyers cache `(cache_scope, wholesale_feed_version)` pairs. Scoping dimensions: (agent, buying_mode, filters, targeting_overlay, media_buy_frequency_cap, required_overlay_support, required_media_buy_support, deprecated property_list, catalog) for cache_scope: 'public'; that tuple plus account identity for cache_scope: 'account'. pagination.cursor is NOT part of the scoping tuple. Backward-compatible: pre-v3.1 agents that ignore this field simply return the full payload, same as the unchanged-server path. See specs/wholesale-feed-webhooks.md for the full sync pattern." - ), - ] = None - if_pricing_version: Annotated[ - str | None, - Field( - description="Opaque pricing_version token from a prior get_products response. MUST only be sent together with if_wholesale_feed_version — pricing version has no structural baseline to compare against on its own. Evaluation order: (1) if_wholesale_feed_version mismatch → seller returns the full payload (pricing is implicitly stale); (2) if_wholesale_feed_version matches but if_pricing_version mismatches → seller returns the full payload so the buyer sees updated pricing_options; (3) both match → seller MAY return unchanged: true. Agents that don't track pricing separately ignore if_pricing_version and fall back to if_wholesale_feed_version semantics. Useful for storefronts that re-price compositions far more often than they re-render product mirrors." - ), - ] = None - context: context_1.ContextObject | None = None - required_policies: Annotated[ - list[str] | None, - Field( - description='Registry policy IDs that the buyer requires to be enforced for products in this response. Sellers filter products to only those that comply with or already enforce the requested policies.' - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/get_products_response.py b/src/adcp/types/generated_poc/media_buy/get_products_response.py deleted file mode 100644 index 10ad1911f..000000000 --- a/src/adcp/types/generated_poc/media_buy/get_products_response.py +++ /dev/null @@ -1,349 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/get_products_response.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, StrictBool, StringConstraints - -from ..core import context as context_1 -from ..core import duration, error -from ..core import ext as ext_1 -from ..core import pagination_response, product, proposal -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from . import get_products_targeting_resolution - - -class Extensions(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - extends: Annotated[ - str, - Field( - description='Canonical concept this extension extends (e.g., `tracking`, `cta_vocabulary`, `destinations`, `placement`).' - ), - ] - fields: Annotated[ - dict[str, Any], - Field( - description='JSON Schema fragment declaring the additional fields this extension contributes.' - ), - ] - version: Annotated[ - str | None, - Field( - description='Semantic version of the extension definition. Distinct from the digest — version is human-readable; digest is the integrity check.' - ), - ] = None - description: str | None = None - - -class Suggestion(ScalarStr): - __slots__ = () - _constraints = {'max_length': 1000, 'min_length': 1} - - -class Status(StrEnum): - applied = 'applied' - partial = 'partial' - unable = 'unable' - - -class RefinementApplied1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['request'], - Field(description="Echoes scope 'request' from the corresponding refine entry."), - ] = 'request' - status: Annotated[ - Status, - Field( - description="'applied': the ask was fulfilled. 'partial': the ask was partially fulfilled — see notes for details. 'unable': the seller could not fulfill the ask — see notes for why." - ), - ] - notes: Annotated[ - str | None, - Field( - description="Seller explanation of what was done, what couldn't be done, or why. Recommended when status is 'partial' or 'unable'." - ), - ] = None - - -class RefinementApplied2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['product'], - Field(description="Echoes scope 'product' from the corresponding refine entry."), - ] = 'product' - product_id: Annotated[ - str, Field(description='Echoes product_id from the corresponding refine entry.') - ] - status: Annotated[ - Status, - Field( - description="'applied': the ask was fulfilled. 'partial': the ask was partially fulfilled — see notes for details. 'unable': the seller could not fulfill the ask — see notes for why." - ), - ] - notes: Annotated[ - str | None, - Field( - description="Seller explanation of what was done, what couldn't be done, or why. Recommended when status is 'partial' or 'unable'." - ), - ] = None - - -class RefinementApplied3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['proposal'], - Field(description="Echoes scope 'proposal' from the corresponding refine entry."), - ] = 'proposal' - proposal_id: Annotated[ - str, Field(description='Echoes proposal_id from the corresponding refine entry.') - ] - status: Annotated[ - Status, - Field( - description="'applied': the ask was fulfilled. 'partial': the ask was partially fulfilled — see notes for details. 'unable': the seller could not fulfill the ask — see notes for why." - ), - ] - notes: Annotated[ - str | None, - Field( - description="Seller explanation of what was done, what couldn't be done, or why. Recommended when status is 'partial' or 'unable'." - ), - ] = None - - -RefinementApplied = Annotated[ - RefinementApplied1 | RefinementApplied2 | RefinementApplied3, - Field(discriminator='scope'), -] - - -class Scope(StrEnum): - products = 'products' - pricing = 'pricing' - forecast = 'forecast' - proposals = 'proposals' - wholesale_feed = 'wholesale_feed' - - -class IncompleteItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Scope, - Field( - description="'products': not all inventory sources were searched. 'pricing': products returned but pricing is absent or unconfirmed. 'forecast': products returned but forecast data is absent. 'proposals': proposals were not generated or are incomplete. 'wholesale_feed': in wholesale mode, full feed enumeration could not complete in the time budget — symmetric with get_signals' 'wholesale_feed' scope so sellers have a precise way to declare wholesale-incomplete on the products surface." - ), - ] - description: Annotated[ - str, Field(description='Human-readable explanation of what is missing and why.') - ] - estimated_wait: Annotated[ - duration.Duration | None, - Field( - description='How much additional time is expected to resolve this scope. Allows the buyer to decide whether to make a new attempt with a larger time_budget; its presence is not an automatic-retry instruction.' - ), - ] = None - - -class Semantics(StrEnum): - only = 'only' - any = 'any' - approximate = 'approximate' - - -class ExcludedBy(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - count: Annotated[ - SchemaInt, - Field( - description='Number of products excluded by this filter, interpreted per the parent `semantics` field.', - ge=0, - ), - ] - values: Annotated[ - list[str | dict[str, Any]] | None, - Field( - description='Optional list of the specific filter values that contributed to exclusions, when meaningful. For `required_metrics`: the metric names that excluded products (strings). For `required_vendor_metrics`: the vendor/metric pin entries (objects). Item shape is filter-specific; the schema admits string OR object items. Buyers without filter-specific knowledge SHOULD treat as opaque.' - ), - ] = None - notes: Annotated[ - str | None, - Field( - description="Optional human-readable note about why this filter narrowed the set (e.g., 'no products in this brief support DV viewability at the requested threshold')." - ), - ] = None - - -class FilterDiagnostics(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - semantics: Annotated[ - Semantics | None, - Field( - description="How `excluded_by[*].count` values are computed across multiple filters. `only`: counts products that would have been included if not for THIS filter alone (deterministic; the right value for 'which filter killed my result set' triage — recommended when feasible). `any`: counts products excluded by ANY filter (so multiple filters' counts may overlap and sum to more than `total_candidates`). `approximate`: sellers SHOULD use this when their pipeline can't cleanly attribute exclusions to a single filter. Buyers SHOULD inspect `semantics` before doing arithmetic on counts." - ), - ] = None - total_candidates: Annotated[ - SchemaInt | None, - Field( - description='Number of products the seller considered before applying `filters`. Baseline for interpreting per-filter exclusion counts. Approximate — sellers MAY return a sampled or capped count when their candidate pool is large. Optional; sellers whose baseline candidate set size is sensitive (revealing market posture or competitive density) MAY omit this while still emitting `excluded_by`.', - ge=0, - ), - ] = None - excluded_by: Annotated[ - dict[str, ExcludedBy] | None, - Field( - description="Per-filter exclusion counts, keyed by the filter property name as it appears in the request's `filters` object (e.g., `pricing_currencies`, `required_metrics`, `required_vendor_metrics`, `required_geo_targeting`, `budget_range`). Values are objects carrying `count` and optional filter-specific detail. Only filters that actually narrowed the set need appear here; absence of a key means that filter did not exclude anything (or was not in the request)." - ), - ] = None - - -class CacheScope(StrEnum): - public = 'public' - account = 'account' - - -class GetProductsResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - products: Annotated[ - list[product.Product] | None, Field(description='Array of matching products') - ] = None - targeting_resolution: Annotated[ - get_products_targeting_resolution.ProductDiscoveryTargetingResolution | None, - Field( - description='Request-level confirmation of structured hard targeting inferred from the brief. Sellers MUST include this when their structured interpretation of hard prose materially affects product eligibility, pricing, or forecasting; otherwise inclusion is a best practice. Omitted when no hard targeting was inferred from the brief.' - ), - ] = None - extensions: Annotated[ - dict[Annotated[str, StringConstraints(pattern=r'^https?://[^@]+@sha256:[a-f0-9]{64}$')], Extensions] | None, - Field( - description='Bundled platform-extension definitions referenced by any product in `products`. Keyed by `@` (e.g., `https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel@sha256:abc...`). When present, lets buyers resolve `platform_extensions` references on product format declarations without a separate fetch. Buyer SDKs cache by URI@digest; subsequent get_products responses MAY omit definitions the buyer already has cached and rely on the digest match. Each value is an extension definition with `extends` (the canonical concept it extends, e.g., `tracking`), `fields` (the schema for additional fields the extension contributes), `version`, and optional `description`.' - ), - ] = None - proposals: Annotated[ - list[proposal.Proposal] | None, - Field( - description='Optional legacy proposed media plans. When the request carries media_buy_frequency_cap, every returned proposal echoes the bound value as proposal.frequency_cap. Buyers may refine or execute a committed proposal by ID.' - ), - ] = None - errors: Annotated[ - list[error.Error] | None, - Field(description='Task-specific errors and warnings (e.g., product filtering issues)'), - ] = None - reason: Annotated[ - str | None, - Field( - description='Buyer-facing market explanation required only on the GetProductsRejected arm. MAY be sanitized to protect confidential seller rules. Plain text only.', - max_length=2000, - min_length=1, - ), - ] = None - suggestions: Annotated[ - list[Suggestion] | None, - Field( - description='Actionable alternatives available only on the GetProductsRejected arm.', - max_length=20, - ), - ] = None - property_list_applied: Annotated[ - StrictBool | None, - Field( - description='[AdCP 3.0] Indicates whether deprecated top-level property_list filtering was applied. True if the agent filtered products based on the provided property_list; every returned product also carries the corresponding property/include list_applications receipt. Absent or false if property_list was not provided or not supported by this agent.' - ), - ] = None - catalog_applied: Annotated[ - StrictBool | None, - Field( - description='Whether the seller filtered results based on the provided catalog. True if the seller matched catalog items against its inventory. Absent or false if no catalog was provided or the seller does not support catalog matching.' - ), - ] = None - refinement_applied: Annotated[ - list[RefinementApplied] | None, - Field( - description="Seller's response to each change request in the refine array, matched by position. Each entry acknowledges whether the corresponding ask was applied, partially applied, or unable to be fulfilled. MUST contain the same number of entries in the same order as the request's refine array. Only present when the request used buying_mode: 'refine'. Each entry MUST echo the request entry's scope and — for product and proposal scopes — the matching id field (product_id or proposal_id), so orchestrators can cross-validate alignment." - ), - ] = None - incomplete: Annotated[ - list[IncompleteItem] | None, - Field( - description="Declares what the seller could not finish within the buyer's time_budget or due to internal limits while still returning a usable response. Each entry identifies a scope that is missing or partial. Absent when the response is fully complete. This field does not classify the condition as retryable; retryability is carried by error.recovery on the error channel.", - min_length=1, - ), - ] = None - filter_diagnostics: Annotated[ - FilterDiagnostics | None, - Field( - description="Optional non-fatal diagnostic block describing how the request's `filters` narrowed the candidate set. Use this to disambiguate empty/small result lists between 'no inventory matches the brief' and 'a specific filter excluded everything', without breaking the filter-not-fail convention (sellers still silently exclude unmatched products; this block is observability, not error reporting). Sellers MAY populate this when meaningful narrowing occurred; buyers MAY use it for triage UX without depending on its presence. Counts only — products are not enumerated by name to avoid leaking competitive intelligence about adjacent campaigns or seller inventory. `total_candidates` and `excluded_by` are independently optional — sellers whose baseline candidate set size is sensitive MAY emit `excluded_by` without `total_candidates`, or vice versa.", - examples=[ - { - 'semantics': 'only', - 'total_candidates': 47, - 'excluded_by': { - 'required_metrics': {'count': 31, 'values': ['completed_views']}, - 'required_geo_targeting': {'count': 9}, - 'pricing_currencies': {'count': 3, 'values': ['USD']}, - 'budget_range': {'count': 7}, - }, - } - ], - ), - ] = None - pagination: Annotated[ - pagination_response.PaginationResponse | None, - Field( - description="Cursor metadata for paginated get_products responses. In brief/refine mode, continuation pages bound returned products[] for the seller's curated or refined answer; proposals may accompany a page as plan metadata but are not independently counted by this pagination envelope, and pagination does not convert the response into an exhaustive feed contract. In wholesale mode, continuation pages walk the wholesale product feed." - ), - ] = None - wholesale_feed_version: Annotated[ - str | None, - Field( - description="Opaque token representing the version of the wholesale product feed state used to compose this response. Sellers that implement conditional-fetch (if_wholesale_feed_version) MUST return this on every wholesale-mode response so buyers can cache and probe later. Buyers MUST treat the value as opaque — no format, no ordering, no inspection. The token is scope-keyed: it describes a version for the cache_scope declared on this response, NOT a global agent version. A buyer caches `(cache_scope, wholesale_feed_version)` pairs and presents the matching token on the next request. Scoping dimensions: (agent, buying_mode, filters, targeting_overlay, media_buy_frequency_cap, required_overlay_support, required_media_buy_support, deprecated property_list, catalog) for cache_scope: 'public'; that tuple plus account_id for cache_scope: 'account'. pagination.cursor is NOT part of the scoping tuple. See specs/wholesale-feed-webhooks.md for the full cache layering model." - ), - ] = None - pricing_version: Annotated[ - str | None, - Field( - description='Opaque token representing the version of the pricing layer, including product pricing_options and nested signal_targeting_options pricing_options. When the seller supports independent pricing versioning, pricing_version changes when prices move but wholesale_feed_version changes only when structure/metadata moves. Same cache_scope keying as wholesale_feed_version. Sellers not separating these MAY omit pricing_version and use wholesale_feed_version for both.' - ), - ] = None - cache_scope: Annotated[ - CacheScope | None, - Field( - description="Declares whether the wholesale_feed_version and pricing_version on this response describe a universal layer or an account-specific overlay. REQUIRED on every 3.1+ response (the 3.1 schema enforces this — the safety property of the two-layer cache model depends on it). 'public': this response describes the seller's published rate card; the buyer MAY dedupe under (agent, buying_mode, filters, targeting_overlay, media_buy_frequency_cap, required_overlay_support, required_media_buy_support, deprecated property_list, catalog) without scoping by account. 'account': this response includes account-specific overrides; the buyer MUST cache the version under that tuple plus account_id. When the request did NOT include `account`, the seller MUST return `cache_scope: 'public'`. When the request included `account`, the seller MUST return either: 'public' (this account prices off the public rate card — buyer dedupes) or 'account' (account-specific overrides exist — buyer caches under the account key). Sellers MAY return 'public' on an account-scoped request that previously had overrides — buyers SHOULD interpret this as a downgrade and drop their account-overlay. Without schema-required cache_scope, a seller silently omitting the field on an account-scoped response would cause buyers to mis-key the cache and serve account-overlay payloads to other accounts. **Backward-compatibility note for 3.1 validators:** SDKs that validate strictly against the 3.1 schema MUST select the validator based on the server-declared `adcp_version` (release-precision version negotiation, 3.1). For responses with `adcp_version` starting `3.0`, the 3.1 cache_scope-required constraint MUST be relaxed." - ), - ] = CacheScope.public - unchanged: Annotated[ - Literal[True] | None, - Field( - description="Present and `true` ONLY on wholesale-mode responses when the request carried if_wholesale_feed_version (and/or if_pricing_version) matching the seller's current version for the buyer's cache_scope, in which case products[] MUST be omitted; wholesale_feed_version (echoed), cache_scope (echoed), and pricing_version (echoed when used) MUST still be present. Buyers receiving unchanged: true MUST NOT mutate their local wholesale product mirror. **One shape per state:** sellers MUST NOT emit `unchanged: false` — the absence of the field IS the signal that the response carries products. Two shapes ({ unchanged: false, products: [...] } vs. { products: [...] }) for the same state would let some sellers always emit the field and some never would, creating an inconsistency the wire shouldn't carry. **Cross-scope isolation:** the comparator that decides `unchanged` MUST be keyed on `(cache_scope, wholesale_feed_version)`, not on the token value alone. A seller MUST NOT emit `unchanged: true` when it resolves the request to a different `cache_scope` than the one whose token the buyer echoed in `if_wholesale_feed_version` (and/or `if_pricing_version`): because the token is scope-keyed, a value minted for `cache_scope: 'public'` cannot match the seller's current token for `cache_scope: 'account'` (or vice-versa), so such a request MUST return the full feed for the resolved scope with that scope's own token." - ), - ] = None - sandbox: Annotated[ - StrictBool | None, - Field(description='When true, this response contains simulated data from sandbox mode.'), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/get_products_targeting_resolution.py b/src/adcp/types/generated_poc/media_buy/get_products_targeting_resolution.py deleted file mode 100644 index 91dc300cb..000000000 --- a/src/adcp/types/generated_poc/media_buy/get_products_targeting_resolution.py +++ /dev/null @@ -1,26 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/get_products_targeting_resolution.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..core import ext as ext_1 -from ..core import targeting - - -class ProductDiscoveryTargetingResolution(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brief_targeting: Annotated[ - targeting.TargetingOverlay, - Field( - description="Structured delivery constraints the seller resolved from explicit hard targeting in the brief. This is the seller's literal interpretation, not a counteroffer: products that cannot honor it are omitted. Sellers MUST emit this confirmation when the interpretation materially affects product eligibility, pricing, or forecasting; otherwise it is a best practice. Unchanged structured targeting_overlay values are not repeated." - ), - ] - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/get_reporting_status_request.py b/src/adcp/types/generated_poc/media_buy/get_reporting_status_request.py deleted file mode 100644 index 809da7fc7..000000000 --- a/src/adcp/types/generated_poc/media_buy/get_reporting_status_request.py +++ /dev/null @@ -1,110 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/get_reporting_status_request.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import AwareDatetime, ConfigDict, Field - -from ..core import canonical_account_ref -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core import pagination_request, reporting_coverage, reporting_delivery_offering -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import reporting_finality, reporting_health - - -class DeliveryConfigId(ScalarStr): - __slots__ = () - _constraints = {'max_length': 64, 'min_length': 1, 'pattern': '^[A-Za-z0-9_.:-]{1,64}$'} - - -class Period(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - start: AwareDatetime - end: AwareDatetime - - -class ReportingStatusView(StrEnum): - summary = 'summary' - periods = 'periods' - revision = 'revision' - - -class GetReportingStatusRequest(AdcpVersionEnvelope): - account: Annotated[ - canonical_account_ref.CanonicalAccountReference, - Field(description='Account whose caller-owned reporting status is queried.'), - ] - view: ReportingStatusView - media_buy_ids: Annotated[ - list[reporting_coverage.ReportingMediaBuyId] | None, - Field( - description='Optional summary/periods scope. Omit for every accessible media buy in the account.', - max_length=100, - min_length=1, - ), - ] = None - delivery_config_ids: Annotated[ - list[DeliveryConfigId] | None, - Field( - description='Optional summary/periods scope. Use to reconcile billing, analytics, and pacing independently. Omit for every active caller-owned configuration.', - max_length=16, - min_length=1, - ), - ] = None - feed_purposes: Annotated[ - list[reporting_delivery_offering.ReportingFeedPurpose] | None, - Field( - description='Optional summary/periods feed filter. The response echoes exact resolved configuration generations so this never creates an opaque aggregate.', - min_length=1, - ), - ] = None - period: Annotated[ - Period | None, - Field( - description="Half-open summary/periods horizon. Omit for the seller's documented operational default horizon; the response always echoes the evaluated scope." - ), - ] = None - health: Annotated[ - list[reporting_health.ReportingHealth] | None, - Field( - description='Periods-view result filter only; it never changes summary health.', - min_length=1, - ), - ] = None - finality: Annotated[list[reporting_finality.ReportingFinality] | None, Field(min_length=1)] = ( - None - ) - reporting_revision_id: Annotated[ - str | None, - Field( - description='Exact retained revision to resolve in revision view.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - changes_after: Annotated[ - str | None, - Field( - description='Periods-view incremental-repair checkpoint previously returned as changes_checkpoint after fully consuming a response. Returns newly committed obligations, revisions, adjustments, materializations, consumer status statements, revision receipts, and adjustment receipts plus the current projection of each affected obligation. Omit for a full ledger read.', - max_length=2048, - min_length=1, - ), - ] = None - pagination: Annotated[ - pagination_request.PaginationRequest | None, - Field( - description='Periods or revision-view pagination. Cursors are bound to the authenticated caller, account, filters, and ledger snapshot.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/get_reporting_status_response.py b/src/adcp/types/generated_poc/media_buy/get_reporting_status_response.py deleted file mode 100644 index 7b44321de..000000000 --- a/src/adcp/types/generated_poc/media_buy/get_reporting_status_response.py +++ /dev/null @@ -1,215 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/get_reporting_status_response.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field, StrictBool, model_validator - -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 -from ..core import ( - pagination_response, - reporting_adjustment, - reporting_adjustment_receipt, - reporting_consumer_status, - reporting_coverage, - reporting_delivery_offering, - reporting_materialization, - reporting_obligation, - reporting_receipt, - reporting_revision, - reporting_status_issue, -) -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import reporting_finality, reporting_health -from . import get_reporting_status_request - - -class ObligationCounts(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - total: Annotated[SchemaInt, Field(ge=0)] - waiting: Annotated[SchemaInt, Field(ge=0)] - healthy: Annotated[SchemaInt, Field(ge=0)] - delayed: Annotated[SchemaInt, Field(ge=0)] - action_required: Annotated[SchemaInt, Field(ge=0)] - complete: Annotated[SchemaInt, Field(ge=0)] - consumer_status_pending: Annotated[ - SchemaInt | None, - Field( - description="Obligations in this scope whose elapsed expected period has passed its consumer-status deadline — expected_at plus automated_recovery_window_seconds — without a current consumer status from the authenticated caller. A chain with any unsuperseded leaf counts as current whatever that leaf says; only an empty chain is pending. Because it counts obligations, a period the seller omitted entirely has no obligation and is not counted here — the buyer's independently derived denominator, not this field, remains the authority on omitted periods. It is a visibility count over the caller's own silence, never a health input: it MUST NOT change health, any other count, or seller-advertised reliability_statistics, and it overlaps the health counts rather than partitioning them. Required when the seller advertises consumer_status_task.", - ge=0, - ), - ] = None - - -class Status(StrEnum): - completed = 'completed' - failed = 'failed' - - -class DeliveryConfigGeneration(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - delivery_config_id: Annotated[str, Field(max_length=64, min_length=1)] - delivery_config_version: Annotated[SchemaInt, Field(ge=1)] - feed_purpose: reporting_delivery_offering.ReportingFeedPurpose - - -class Scope(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - period_start: AwareDatetime - period_end: AwareDatetime - scope_closed: Annotated[ - StrictBool, - Field(description='True only when no new obligation can enter this evaluated scope.'), - ] - media_buy_ids: list[reporting_coverage.ReportingMediaBuyId] | None = None - all_accessible_media_buys: Annotated[ - StrictBool, - Field( - description='True when media_buy_ids was omitted and the scope covers all caller-accessible account buys.' - ), - ] - delivery_config_generations: Annotated[ - list[DeliveryConfigGeneration], - Field( - description='Exact independently reconciled configuration generations in the denominator.' - ), - ] - feed_purposes: list[reporting_delivery_offering.ReportingFeedPurpose] - finality: list[reporting_finality.ReportingFinality] - ledger_retained_from: Annotated[ - AwareDatetime, - Field( - description='Earliest period boundary for which anti-entropy metadata is retained for every selected configuration generation.' - ), - ] - coverage_complete: Annotated[ - StrictBool, - Field( - description='Whether the requested horizon is fully inside retained ledger coverage. False means health cannot prove completeness for the whole requested horizon.' - ), - ] - - -class GetReportingStatusResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - view: get_reporting_status_request.ReportingStatusView | None = None - ledger_snapshot_id: Annotated[ - str | None, - Field( - description="Opaque identity of the seller's consistent reporting-ledger snapshot. Every page reached from one periods cursor MUST return the same value.", - max_length=255, - min_length=1, - ), - ] = None - ledger_as_of: Annotated[ - AwareDatetime | None, - Field( - description='Exclusive observation boundary for ledger_snapshot_id. Revisions committed later appear only in a later reconciliation.' - ), - ] = None - changes_checkpoint: Annotated[ - str | None, - Field( - description='Opaque durable incremental-repair checkpoint for this periods snapshot. Consumers persist it only after consuming every page, then send it as changes_after. It is bound to authenticated caller, account, and filters and MUST order every committed obligation, revision, adjustment, materialization, consumer status statement, revision receipt, and adjustment receipt without gaps.', - max_length=2048, - min_length=1, - ), - ] = None - account_id: Annotated[ - str | None, - Field(description='Resolved seller/storefront account identifier.', min_length=1), - ] = None - scope: Annotated[ - Scope | None, - Field( - description='Exact denominator evaluated for summary or periods health. complete is valid only when scope_closed is true.' - ), - ] = None - health: reporting_health.ReportingHealth | None = None - coverage: Annotated[ - reporting_coverage.ReportingCoverage | None, - Field( - description='Aggregated effective coverage for the exact selected scope. This remains independent of reporting health and finality so a fresh covered subset cannot look like complete campaign reporting.' - ), - ] = None - data_through: Annotated[ - AwareDatetime | None, - Field( - description='Conservative latest included event time across satisfied obligations in scope, or null when unavailable/unknown.' - ), - ] = None - next_expected_at: Annotated[ - AwareDatetime | None, - Field( - description='Next obligation due time for an open scope. In a complete summary (view: summary, health: complete), the nearest future period start, strictly after ledger_as_of, across all active committed configuration generations in scope.delivery_config_generations. Sellers MUST populate it when such a scheduled period exists outside the closed evaluated scope, and omit it when none exists. This complete-scope projection applies only to summary responses. It is derived from the configuration schedule and does not represent an open obligation in the evaluated scope; its presence does not indicate that the scope is still open. Projecting it MUST NOT create, expose, lease, count, or alter an obligation whose period has not closed, or change obligation_counts, scope, or coverage. Obligation expected_at remains period.end plus schedule.delivery_sla; get_media_buy_delivery.next_expected_at remains the next webhook notification time.' - ), - ] = None - obligation_counts: ObligationCounts | None = None - issues: list[reporting_status_issue.ReportingStatusIssue] | None = None - periods: list[reporting_obligation.ReportingObligation] | None = None - revisions: Annotated[ - list[reporting_revision.ReportingRevision] | None, - Field( - description='Revision ledger records on this page. Pagination is over the flat union of obligations, revisions, adjustments, materializations, consumer status statements, revision receipts, and adjustment receipts, avoiding unbounded nested history.' - ), - ] = None - adjustments: Annotated[ - list[reporting_adjustment.ReportingAdjustment] | None, - Field( - description='Immutable post-official accounting corrections on this page. They preserve the original invoice-to-revision binding and are included in flat ledger pagination.' - ), - ] = None - consumer_statuses: Annotated[ - list[reporting_consumer_status.ReportingConsumerStatus] | None, - Field( - description="Authenticated caller's append-only reporting status history on this page. Current leaves are identified by obligation current_consumer_status_id or, for a missing seller obligation, by the supersession chain over configuration generation, report definition, and period. No other consumer's status is disclosed." - ), - ] = None - adjustment_receipts: Annotated[ - list[reporting_adjustment_receipt.ReportingAdjustmentReceipt] | None, - Field( - description='Authenticated Reconciled Billing outcomes for adjustments on this page.' - ), - ] = None - pagination: pagination_response.PaginationResponse | None = None - revision: reporting_revision.ReportingRevision | None = None - materializations: list[reporting_materialization.ReportingMaterialization] | None = None - receipts: Annotated[ - list[reporting_receipt.ReportingReceipt] | None, - Field( - description="Authenticated caller's durable reconciliation receipts. Receipts from another consumer principal are never disclosed." - ), - ] = None - errors: list[error.Error] | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - status: Status - - @model_validator(mode='after') - def _require_schema_required_group(self) -> GetReportingStatusResponse: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('status',),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'GetReportingStatusResponse requires at least one of these field groups: status' - ) diff --git a/src/adcp/types/generated_poc/media_buy/legacy_purchase_continuation_input.py b/src/adcp/types/generated_poc/media_buy/legacy_purchase_continuation_input.py deleted file mode 100644 index 922139c42..000000000 --- a/src/adcp/types/generated_poc/media_buy/legacy_purchase_continuation_input.py +++ /dev/null @@ -1,106 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/legacy_purchase_continuation_input.json -# timestamp: 2026-10-04T00:53:27+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any -from uuid import UUID - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, field_validator - -from ..core import account_ref - - -class SelectedProductId(ScalarStr): - __slots__ = () - _constraints = {'min_length': 1} - - -class AcceptedLoss(StrEnum): - feed_version_not_atomic = 'feed_version_not_atomic' - pricing_version_not_atomic = 'pricing_version_not_atomic' - mutation_idempotency_not_guaranteed = 'mutation_idempotency_not_guaranteed' - - -class CompatibilityPurchaseCoordinatorInput(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - idempotency_key: Annotated[ - UUID, - Field( - description='Replay identity for this logical coordinator operation. Exact retries resume the durable operation record instead of redeeming the continuation again.' - ), - ] - continuation_token: Annotated[ - str, - Field( - description='Opaque token returned by products_available.purchase_continuation.', - min_length=16, - ), - ] - account: Annotated[ - account_ref.AccountReference, - Field( - description='Account identity that must match the account bound into the continuation token.' - ), - ] - selected_product_ids: Annotated[ - list[SelectedProductId], - Field( - description='Non-empty subset of the product IDs bound into the continuation.', - min_length=1, - json_schema_extra={'uniqueItems': True}, - ), - ] - accepted_losses: Annotated[ - list[AcceptedLoss], - Field( - description='Exact loss set returned with the continuation. Missing, extra, or stale consent fails before mutation.', - min_length=2, - json_schema_extra={ - 'uniqueItems': True, - 'allOf': [ - {'contains': {'const': 'feed_version_not_atomic'}}, - {'contains': {'const': 'pricing_version_not_atomic'}}, - ], - }, - ), - ] - legacy_create_request: Annotated[ - dict[str, Any], - Field( - description='Proposed create_media_buy payload. Before mutation the coordinator validates this object against create-media-buy-request.json from source_adcp_version, requires explicit-package mode, and requires its package product IDs to equal selected_product_ids.', - min_length=1, - ), - ] - - - @field_validator('selected_product_ids') - @classmethod - def _selected_product_ids_are_unique( - cls, values: list[SelectedProductId] - ) -> list[SelectedProductId]: - if len(values) != len(set(values)): - raise ValueError('selected_product_ids must contain unique items') - return values - - @field_validator('accepted_losses') - @classmethod - def _accepted_losses_match_schema( - cls, values: list[AcceptedLoss] - ) -> list[AcceptedLoss]: - value_set = set(values) - if len(values) != len(value_set): - raise ValueError('accepted_losses must contain unique items') - required = { - AcceptedLoss.feed_version_not_atomic, - AcceptedLoss.pricing_version_not_atomic, - } - if not required.issubset(value_set): - raise ValueError('accepted_losses must include the required compatibility losses') - return values diff --git a/src/adcp/types/generated_poc/media_buy/list_creative_formats_request.py b/src/adcp/types/generated_poc/media_buy/list_creative_formats_request.py deleted file mode 100644 index f99b270d4..000000000 --- a/src/adcp/types/generated_poc/media_buy/list_creative_formats_request.py +++ /dev/null @@ -1,133 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/list_creative_formats_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import SchemaInt -from pydantic import ConfigDict, Field, StrictBool - -from ..core import account_ref -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core import format_id, pagination_request -from ..core import property_id as property_id_1 -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import asset_content_type -from ..enums import disclosure_persistence as disclosure_persistence_1 -from ..enums import disclosure_position -from ..enums import wcag_level as wcag_level_1 - - -class ListCreativeFormatsRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - format_ids: Annotated[ - list[format_id.FormatReferenceStructuredObject] | None, - Field( - deprecated=True, - description='Deprecated in AdCP 3.2; removed in AdCP 4.0. Return only these specific named-format IDs (for example, from a 3.x get_products response). Use canonical-format discovery in 4.0.', - min_length=1, - ), - ] = None - asset_types: Annotated[ - list[asset_content_type.AssetContentType] | None, - Field( - description="Filter to formats that include these asset types. For third-party tags, search for 'html' or 'javascript'. For published-post reference formats, search for 'published_post'. E.g., ['image', 'text'] returns formats with images and text, ['javascript'] returns formats accepting JavaScript tags.", - min_length=1, - ), - ] = None - max_width: Annotated[ - SchemaInt | None, - Field( - description='Maximum width in pixels (inclusive). Returns formats where ANY render has width <= this value. For multi-render formats, matches if at least one render fits.' - ), - ] = None - max_height: Annotated[ - SchemaInt | None, - Field( - description='Maximum height in pixels (inclusive). Returns formats where ANY render has height <= this value. For multi-render formats, matches if at least one render fits.' - ), - ] = None - min_width: Annotated[ - SchemaInt | None, - Field( - description='Minimum width in pixels (inclusive). Returns formats where ANY render has width >= this value.' - ), - ] = None - min_height: Annotated[ - SchemaInt | None, - Field( - description='Minimum height in pixels (inclusive). Returns formats where ANY render has height >= this value.' - ), - ] = None - is_responsive: Annotated[ - StrictBool | None, - Field( - description='Filter for responsive formats that adapt to container size. When true, returns formats without fixed dimensions.' - ), - ] = None - name_search: Annotated[ - str | None, Field(description='Search for formats by name (case-insensitive partial match)') - ] = None - publisher_domain: Annotated[ - str | None, - Field( - deprecated=True, - description="Deprecated compatibility filter for older 3.x callers. A compatibility implementation MAY project publisher-origin or community-catalog declarations obtained through the registry publisher lookup, but MUST NOT synthesize a publisher catalog from seller products. New callers use `GET /api/registry/publisher?domain=...` for publisher acceptance and `get_products` for this seller's deliverability. The pattern below is a syntactic floor, not an SSRF guard.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - property_id: Annotated[ - property_id_1.PropertyId | None, - Field( - description="Filter to formats supported on the named property within the publisher's catalog. Resolves to a property in the publisher's `adagents.json` `properties[]`; the agent returns only `formats[]` entries whose `applies_to_property_ids` includes this property (or entries with no scope, which apply to all properties). Typically used in combination with `publisher_domain`." - ), - ] = None - wcag_level: Annotated[ - wcag_level_1.WcagLevel | None, - Field( - description='Filter to formats that meet at least this WCAG conformance level (A < AA < AAA)' - ), - ] = None - disclosure_positions: Annotated[ - list[disclosure_position.DisclosurePosition] | None, - Field( - description="Filter to formats that support all of these disclosure positions. When a format has disclosure_capabilities, match against those positions. Otherwise fall back to supported_disclosure_positions. Use to find formats compatible with a brief's compliance requirements.", - min_length=1, - ), - ] = None - disclosure_persistence: Annotated[ - list[disclosure_persistence_1.DisclosurePersistence] | None, - Field( - description='Filter to formats where each requested persistence mode is supported by at least one position in disclosure_capabilities. Different positions may satisfy different modes. Use to find formats compatible with jurisdiction-specific persistence requirements (e.g., continuous for EU AI Act).', - min_length=1, - ), - ] = None - output_format_ids: Annotated[ - list[format_id.FormatReferenceStructuredObject] | None, - Field( - description="Filter to formats whose output_format_ids includes any of these format IDs. Returns formats that can produce these outputs — inspect each result's input_format_ids to see what inputs they accept.", - min_length=1, - ), - ] = None - input_format_ids: Annotated[ - list[format_id.FormatReferenceStructuredObject] | None, - Field( - description="Filter to formats whose input_format_ids includes any of these format IDs. Returns formats that accept these creatives as input — inspect each result's output_format_ids to see what they can produce.", - min_length=1, - ), - ] = None - account: Annotated[ - account_ref.AccountReference | None, - Field( - deprecated=True, - description="**DEPRECATED with `list_creative_formats` in 3.2. Removed at 4.0.** Use `get_products` with `account`; each `Product.format_options[]` lists the formats this seller can deliver for that account. *Legacy 3.x behavior:* scopes the returned formats to this account. Sellers that keep account-specific format catalogs (including sandbox accounts) return that account's formats; sellers with a single catalog MAY ignore this field.", - ), - ] = None - pagination: pagination_request.PaginationRequest | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/list_creative_formats_response.py b/src/adcp/types/generated_poc/media_buy/list_creative_formats_response.py deleted file mode 100644 index 9275a9984..000000000 --- a/src/adcp/types/generated_poc/media_buy/list_creative_formats_response.py +++ /dev/null @@ -1,79 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/list_creative_formats_response.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, StrictBool - -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 -from ..core import format, pagination_response -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import creative_agent_capability - - -class Source(StrEnum): - publisher = 'publisher' - aao_mirror = 'aao_mirror' - agent_derived = 'agent_derived' - - -class CreativeAgent(AdCPBaseModel): - agent_url: Annotated[ - AnyUrl, - Field( - description="Base URL for the creative agent (e.g., 'https://reference.example.com', 'https://dco.example.com')." - ), - ] - agent_name: Annotated[ - str | None, Field(description='Human-readable name for the creative agent') - ] = None - capabilities: Annotated[ - list[creative_agent_capability.CreativeAgentCapability] | None, - Field(description='Capabilities this creative agent provides'), - ] = None - - -class ListCreativeFormatsResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - formats: Annotated[ - list[format.Format], - Field( - deprecated=True, - description="Deprecated named-format definitions projected for older 3.x callers. This list is neither the publisher acceptance catalog nor the seller's canonical product deliverability contract.", - ), - ] - source: Annotated[ - Source | None, - Field( - deprecated=True, - description='Deprecated compatibility provenance. `publisher` means publisher-origin catalog; `aao_mirror` means community catalog; `agent_derived` is retained only to parse historical 3.x responses and MUST NOT be produced by a new 3.2 implementation because seller products are not publisher authority.', - ), - ] = None - creative_agents: Annotated[ - list[CreativeAgent] | None, - Field( - deprecated=True, - description="Deprecated recursive discovery projection retained for historical 3.x responses. New buyers query the registry's canonical creative capability index and confirm candidates with get_adcp_capabilities; they do not recursively walk agent-provided lists.", - ), - ] = None - errors: Annotated[ - list[error.Error] | None, - Field(description='Task-specific errors and warnings (e.g., format availability issues)'), - ] = None - pagination: pagination_response.PaginationResponse | None = None - sandbox: Annotated[ - StrictBool | None, - Field(description='When true, this response contains simulated data from sandbox mode.'), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/list_products_request.py b/src/adcp/types/generated_poc/media_buy/list_products_request.py deleted file mode 100644 index 7a2c69884..000000000 --- a/src/adcp/types/generated_poc/media_buy/list_products_request.py +++ /dev/null @@ -1,85 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/list_products_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field - -from ..core import brand_key, canonical_account_ref -from ..core import context as context_1 -from ..core import push_notification_config as push_notification_config_1 -from . import product_discovery_criteria, product_fields - - -class ListProductsRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?)?$', - ), - ] = None - adcp_major_version: Annotated[ - SchemaInt | None, - Field( - deprecated=True, - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - idempotency_key: Annotated[ - str | None, - Field( - description='Optional replay key accepted uniformly on read calls.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='MCP compatibility field: servers ignore this value; A2A uses transport-native Message/Task contextId.', - min_length=1, - ), - ] = None - context: context_1.ContextObject | None = None - governance_context: Annotated[str | None, Field(max_length=4096, min_length=1)] = None - push_notification_config: Annotated[ - push_notification_config_1.PushNotificationConfig | None, - Field( - description='Uniform per-call envelope field accepted for SDK compatibility. This does not register a wholesale feed subscription; durable product.* and wholesale_feed.bulk_change subscribers are registered through sync_accounts notification_configs.' - ), - ] = None - account: Annotated[ - canonical_account_ref.CanonicalAccountReference | None, - Field( - description='Account scope for pricing and availability. A natural-key account is the single brand source and MUST NOT be combined with top-level brand.' - ), - ] = None - brand: brand_key.BrandKey | None = None - criteria: product_discovery_criteria.ProductDiscoveryCriteria | None = None - fields: product_fields.ProductResponseFields | None = None - cursor: Annotated[str | None, Field(min_length=1)] = None - max_results: Annotated[SchemaInt | None, Field(ge=1, le=100)] = 25 - if_feed_version: Annotated[ - str | None, - Field( - description='Opaque feed version returned by a prior list_products response or wholesale product-feed webhook for the same cache scope and canonicalized selection. Used for repair and conditional reconciliation, not routine polling when webhooks are active.' - ), - ] = None - if_pricing_version: Annotated[ - str | None, - Field( - description='Opaque pricing version returned by a prior list_products response. Valid only with if_feed_version.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/media_buy/list_products_response.py b/src/adcp/types/generated_poc/media_buy/list_products_response.py deleted file mode 100644 index 9c94c3fe6..000000000 --- a/src/adcp/types/generated_poc/media_buy/list_products_response.py +++ /dev/null @@ -1,136 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/list_products_response.json -# timestamp: 2026-10-03T14:21:15+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..core import canonical_product -from ..core import context as context_1 -from ..core import duration -from ..core import ext as ext_1 - - -class Outcome(StrEnum): - listed = 'listed' - unchanged = 'unchanged' - - -class CacheScope(StrEnum): - public = 'public' - account = 'account' - - -class Scope(StrEnum): - products = 'products' - pricing = 'pricing' - forecast = 'forecast' - wholesale_feed = 'wholesale_feed' - - -class IncompleteItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Scope - description: Annotated[str, Field(min_length=1)] - estimated_wait: duration.Duration | None = None - - -class IncompleteItem3(IncompleteItem): - pass - - -class ListProductsResponse1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - outcome: Annotated[ - Literal['listed'], - Field( - description='Whether this response carries a product page or confirms that the selected feed has not changed.' - ), - ] = 'listed' - products: list[canonical_product.CanonicalProduct] - next_cursor: Annotated[ - str | None, - Field( - description='Cursor for the next page. Omitted when this is the last page.', - min_length=1, - ), - ] = None - feed_version: Annotated[ - str, - Field( - description='Opaque version of the selected offer feed. For wholesale webhook consumers this is the same scope-keyed token carried as wholesale_feed_version on product.* and wholesale_feed.bulk_change notifications.' - ), - ] - pricing_version: Annotated[ - str | None, Field(description='Opaque version of the selected pricing layer.') - ] = None - cache_scope: CacheScope - incomplete: Annotated[ - list[IncompleteItem] | None, - Field( - description='Usable partial response with explicitly missing product-list scopes.', - min_length=1, - ), - ] = None - replayed: Literal[True] | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class ListProductsResponse2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - outcome: Annotated[ - Literal['unchanged'], - Field( - description='Whether this response carries a product page or confirms that the selected feed has not changed.' - ), - ] = 'unchanged' - products: list[canonical_product.CanonicalProduct] | None = None - next_cursor: Annotated[ - str | None, - Field( - description='Cursor for the next page. Omitted when this is the last page.', - min_length=1, - ), - ] = None - feed_version: Annotated[ - str, - Field( - description='Opaque version of the selected offer feed. For wholesale webhook consumers this is the same scope-keyed token carried as wholesale_feed_version on product.* and wholesale_feed.bulk_change notifications.' - ), - ] - pricing_version: Annotated[ - str | None, Field(description='Opaque version of the selected pricing layer.') - ] = None - cache_scope: CacheScope - incomplete: Annotated[ - list[IncompleteItem3] | None, - Field( - description='Usable partial response with explicitly missing product-list scopes.', - min_length=1, - ), - ] = None - replayed: Literal[True] | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -ListProductsResponse = Annotated[ - ListProductsResponse1 | ListProductsResponse2, - Field( - description="Canonical product offers and continuation state. Every product carries product_id and name; other compact detail fields follow the request's fields selection. Legacy named-format identifiers are never returned. This response never contains proposals or proposal-lifecycle fields.", - discriminator='outcome', - title='List Products Response', - ), -] diff --git a/src/adcp/types/generated_poc/media_buy/log_event_request.py b/src/adcp/types/generated_poc/media_buy/log_event_request.py deleted file mode 100644 index ae3547f4f..000000000 --- a/src/adcp/types/generated_poc/media_buy/log_event_request.py +++ /dev/null @@ -1,43 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/log_event_request.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import ConfigDict, Field - -from ..core import context as context_1 -from ..core import event -from ..core import ext as ext_1 -from ..core.version_envelope import AdcpVersionEnvelope - - -class LogEventRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - event_source_id: Annotated[ - str, Field(description='Event source configured on the account via sync_event_sources') - ] - test_event_code: Annotated[ - str | None, - Field( - description="Test event code for validation without affecting production data. Events with this code appear in the platform's test events UI." - ), - ] = None - events: Annotated[ - list[event.Event], Field(description='Events to log', max_length=10000, min_length=1) - ] - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for this request. Prevents duplicate event logging on retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/log_event_response.py b/src/adcp/types/generated_poc/media_buy/log_event_response.py deleted file mode 100644 index 6733e580f..000000000 --- a/src/adcp/types/generated_poc/media_buy/log_event_response.py +++ /dev/null @@ -1,52 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/log_event_response.json -# timestamp: preserved-by-post-generate-fixes - -from __future__ import annotations - -from typing import Annotated, TypeAlias - -from pydantic import ConfigDict, Field - -from ..core.version_envelope import AdcpVersionEnvelope -from ..core import context as context_1 -from ..core import error as error_1 -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope - - -class PartialFailure(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - event_id: str - code: str - message: str - - -class LogEventResponse1(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - events_received: Annotated[int, Field(ge=0)] - events_processed: Annotated[int, Field(ge=0)] - partial_failures: list[PartialFailure] | None = None - warnings: list[str] | None = None - match_quality: Annotated[float, Field(ge=0, le=1)] | None = None - sandbox: bool | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class LogEventResponse2(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - errors: Annotated[list[error_1.Error], Field(min_length=1)] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -LogEventResponse: TypeAlias = LogEventResponse1 | LogEventResponse2 - - -__all__ = [ - 'LogEventResponse', - 'LogEventResponse1', - 'LogEventResponse2', - 'PartialFailure', -] diff --git a/src/adcp/types/generated_poc/media_buy/media_buy_commitment_response.py b/src/adcp/types/generated_poc/media_buy/media_buy_commitment_response.py deleted file mode 100644 index fe7103178..000000000 --- a/src/adcp/types/generated_poc/media_buy/media_buy_commitment_response.py +++ /dev/null @@ -1,139 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/media_buy_commitment_response.json -# timestamp: 2026-10-03T14:21:15+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field - -from ..core import canonical_media_buy_action -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 -from ..core.canonical_proposal import CanonicalProposal -from ..core.warning import Warning as Warning_1 -from ..enums import media_buy_status as media_buy_status_1 - - -class ProposalStatus(StrEnum): - draft = 'draft' - committed = 'committed' - accepted = 'accepted' - - -class PurchaseBinding(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - purchase_index: Annotated[SchemaInt, Field(ge=0)] - product_id: Annotated[str, Field(min_length=1)] - package_id: Annotated[str, Field(min_length=1)] - - -class Code(StrEnum): - inventory_shortfall_forecast = 'inventory_shortfall_forecast' - flight_change_creates_pacing_risk = 'flight_change_creates_pacing_risk' - fields_ignored_due_to_precedence = 'fields_ignored_due_to_precedence' - inventory_shortfall_forecast_1 = 'inventory_shortfall_forecast' - flight_change_creates_pacing_risk_1 = 'flight_change_creates_pacing_risk' - - -class MediaBuyCommitmentResponse2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - status: Literal['failed'] = 'failed' - errors: Annotated[list[error.Error], Field(min_length=1)] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - replayed: Literal[True] | None = None - - -class MediaBuyCommitmentResponse3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - status: Literal['submitted'] = 'submitted' - task_id: Annotated[str, Field(min_length=1)] - message: Annotated[str | None, Field(max_length=2000)] = None - errors: list[error.Error] | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - replayed: Literal[True] | None = None - - -class Warning(Warning_1): - code: Annotated[ - Code | None, - Field( - description='Closed warning vocabulary for the negotiated AdCP release. A non-blocking condition observed while an operation still succeeded. New values are added through normal protocol releases; buyers validate against the negotiated release.', - title='Warning Code', - ), - ] = None - - -class AcceptedProposal(CanonicalProposal): - proposal_status: Annotated[ - Literal['accepted'], - Field( - description='draft is indicative and unreserved; committed has firm terms with inventory reserved until expires_at; accepted is the historical snapshot attached to a MediaBuy.', - title='Proposal Status', - ), - ] = 'accepted' - media_buy_id: Annotated[str, Field(min_length=1)] - accepted_at: Any - commercial_terms: Any - terms_digest: Any - - -class MediaBuyCommitmentResponse1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - status: Literal['completed'] = 'completed' - media_buy_id: Annotated[str, Field(min_length=1)] - name: Annotated[ - str | None, - Field( - description='Persisted human-readable MediaBuy name for trafficking UI display and operational communication. The seller MUST echo a buyer-supplied request name unchanged; when the seller seeded a new MediaBuy name from an already-valid proposal.name, it MUST return that value unchanged here. Existing named MediaBuys return the stored value on amendment or cancellation commitments. This operational metadata is outside accepted_proposal and is not covered by terms_digest. This display label is not an identifier or financial reference.', - max_length=255, - min_length=1, - pattern='\\S', - ), - ] = None - revision: Annotated[SchemaInt, Field(ge=1)] - media_buy_status: media_buy_status_1.MediaBuyStatus | None = None - confirmed_at: AwareDatetime | None = None - accepted_proposal: AcceptedProposal - purchase_bindings: Annotated[ - list[PurchaseBinding], - Field( - description='Execution identities assigned to the immutable purchases. purchase_index is the zero-based position in accepted_proposal.commercial_terms.purchases and disambiguates repeated product IDs.', - min_length=1, - ), - ] - available_actions: list[canonical_media_buy_action.CanonicalMediaBuyAction] - warnings: Annotated[ - list[Warning] | None, - Field( - description='Non-blocking observations about this completed commitment. The MediaBuy was still created or amended exactly as represented. Continuing conditions also appear as indicators on get_media_buys.', - min_length=1, - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - replayed: Literal[True] | None = None - - -MediaBuyCommitmentResponse = Annotated[ - MediaBuyCommitmentResponse1 | MediaBuyCommitmentResponse2 | MediaBuyCommitmentResponse3, - Field( - description='Shared result for clean product purchase and proposal acceptance. A successful result returns the MediaBuy identity and the immutable accepted commercial snapshot without embedding creative or package graphs. Optional warnings report non-blocking observations at the commitment boundary; continuing conditions remain readable as indicators through get_media_buys.', - discriminator='status', - title='Media Buy Commitment Response', - ), -] diff --git a/src/adcp/types/generated_poc/media_buy/media_buy_delivery_webhook_result.py b/src/adcp/types/generated_poc/media_buy/media_buy_delivery_webhook_result.py deleted file mode 100644 index 5ac17133c..000000000 --- a/src/adcp/types/generated_poc/media_buy/media_buy_delivery_webhook_result.py +++ /dev/null @@ -1,220 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/media_buy_delivery_webhook_result.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat - -from ..core import attribution_window as attribution_window_1 -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 -from ..core import package_delivery_metric_value -from ..core.delivery_metrics import DeliveryMetrics -from ..enums import delivery_status as delivery_status_1 -from ..enums import pricing_model as pricing_model_1 - - -class NotificationType(StrEnum): - scheduled = 'scheduled' - final = 'final' - delayed = 'delayed' - adjusted = 'adjusted' - window_update = 'window_update' - - -class ReportingPeriod(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - start: AwareDatetime - end: AwareDatetime - timezone: Annotated[ - str | None, - Field( - description="Reporting timezone applied to this report, as 'UTC' or an IANA timezone identifier. Same meaning as get_media_buy_delivery reporting_period.timezone. Sellers SHOULD return it whenever every reported media buy shares one reporting timezone.", - examples=['UTC', 'America/New_York'], - min_length=1, - ), - ] = None - - -class Status(StrEnum): - pending_creatives = 'pending_creatives' - pending_start = 'pending_start' - pending = 'pending' - active = 'active' - paused = 'paused' - completed = 'completed' - rejected = 'rejected' - canceled = 'canceled' - failed = 'failed' - reporting_delayed = 'reporting_delayed' - - -class Totals(DeliveryMetrics): - effective_rate: Annotated[StrictFloat | None, Field(ge=0.0)] = None - - -class ByPackageItem(DeliveryMetrics): - package_id: str | None = None - pacing_index: Annotated[ - StrictFloat | None, - Field( - description='Subordinate package pacing diagnostic. May be absent for seller-optimized packages without package-level pacing.', - ge=0.0, - ), - ] = None - pricing_model: pricing_model_1.PricingModel | None = None - rate: Annotated[StrictFloat | None, Field(ge=0.0)] = None - currency: Annotated[ - str | None, - Field( - description="ISO 4217 currency code for this package's spend and currency-denominated pricing rate. The rate for revenue_share is a dimensionless commission fraction, but attributed monetary values still use this currency. When the enclosing media_buy_deliveries[].currency is present, this value MUST equal it. For AdCP-authored buys both values MUST equal the media-buy currency. A different package currency is permitted only for a legacy or externally created mixed-currency buy whose row currency and row-level monetary totals are omitted. When both row and deprecated response-wide currency are absent, a package with monetary values MUST include this field. AdCP does not perform currency conversion.", - pattern='^[A-Z]{3}$', - ), - ] = None - delivery_status: Annotated[ - delivery_status_1.DeliveryStatus | None, - Field( - description="System-reported operational state of this package. Reflects actual delivery state independent of buyer pause control. 'not_delivering' means zero impressions were recorded for the entire reporting_period while the package was in-flight. Sellers SHOULD only report 'not_delivering' once the package's data is_final for the period — a provisional (is_final: false) zero may still be measurement catching up, not genuine non-delivery." - ), - ] = None - paused: StrictBool | None = None - is_final: StrictBool | None = None - finalized_at: AwareDatetime | None = None - measurement_window: Annotated[str | None, Field(max_length=50)] = None - supersedes_window: Annotated[str | None, Field(max_length=50)] = None - metric_values: Annotated[ - list[package_delivery_metric_value.PackageDeliveryMetricValue] | None, - Field( - description='Qualified standard delivery values for this package. Each entry is the delivered counterpart to a standard-scope package.committed_metrics or by_package[].missing_metrics row, using the same atomic key (scope, metric_id, qualifier). Sellers report one row per full qualifier set at package grain; when a metric appears here, its flat scalar counterpart on the package MUST be omitted to avoid two sources of truth. Vendor-scope values continue to use vendor_metric_values. Buyers reconcile rows directly and perform any compatible cross-package aggregation themselves.' - ), - ] = None - spend: Any - - -class MediaBuyDelivery(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_buy_id: Annotated[str, Field(description="Seller's media buy identifier.")] - currency: Annotated[ - str | None, - Field( - description="ISO 4217 denomination for monetary values in this media-buy delivery row, including totals, package spend, and currency-denominated package rates. Sellers SHOULD populate this field whenever all monetary values in the row share one currency. For AdCP-authored buys it MUST equal the media-buy currency, and every by_package[].currency MUST equal it. For a legacy or externally created mixed-currency buy, omit this field and all monetary or money-derived values from row totals; report those values only at package level with each package's own currency. AdCP does not perform currency conversion.", - pattern='^[A-Z]{3}$', - ), - ] = None - status: Annotated[ - Status, - Field( - description="Current media buy lifecycle or reporting status. This is distinct from the webhook envelope's top-level task status." - ), - ] - expected_availability: Annotated[ - AwareDatetime | None, - Field( - description='When delayed data is expected to be available. Present when status is reporting_delayed.' - ), - ] = None - is_adjusted: Annotated[ - StrictBool | None, - Field( - description='Indicates this row contains updated data for a previously reported period.' - ), - ] = None - is_final: Annotated[ - StrictBool | None, - Field(description="Whether this row's delivery data is final for the reporting period."), - ] = None - finalized_at: Annotated[ - AwareDatetime | None, - Field( - description='Timestamp when this row became final. Present only when is_final is true.' - ), - ] = None - pricing_model: pricing_model_1.PricingModel | None = None - pacing_index: Annotated[ - StrictFloat | None, - Field( - description='Aggregate media-buy delivery pace relative to the media-buy pacing plan. Authoritative for seller-optimized buys.', - ge=0.0, - ), - ] = None - totals: Totals - by_package: Annotated[list[ByPackageItem], Field(description='Metrics broken down by package.')] - - -class MediaBuyDeliveryWebhookResult(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - notification_type: Annotated[ - NotificationType, - Field( - description='Type of delivery-report notification: scheduled = regular periodic update, final = campaign completed, delayed = data not yet available, adjusted = corrected data for the same window, window_update = a wider measurement window supersedes a prior window.' - ), - ] - partial_data: Annotated[ - StrictBool | None, - Field( - description='Indicates if any media buys in this webhook have missing or delayed data.' - ), - ] = None - unavailable_count: Annotated[ - SchemaInt | None, - Field( - description='Number of media buys with reporting_delayed or failed status when partial_data is true.', - ge=0, - ), - ] = None - sequence_number: Annotated[ - SchemaInt | None, - Field( - description='Sequential notification number for this reporting webhook stream.', ge=1 - ), - ] = None - next_expected_at: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp for the next expected notification. Omitted on final notifications.' - ), - ] = None - reporting_period: Annotated[ - ReportingPeriod, - Field( - description="Period covered by the delivery report. start and end are instants on the reporting timezone's period boundaries (the products' reporting_capabilities.timezone, echoed in timezone). They fall on UTC midnight only when that timezone is UTC." - ), - ] - currency: Annotated[ - str | None, - Field( - deprecated=True, - description='Deprecated in AdCP 3.2 and removed in AdCP 4.0. Optional legacy report-wide ISO 4217 currency code. It may be used only when every monetary value in the report has that denomination. A report can contain media buys with different currencies, so buyers MUST NOT interpret this field as an aggregation currency or evidence of currency conversion. Prefer media_buy_deliveries[].currency when present and package-level currency otherwise.', - pattern='^[A-Z]{3}$', - ), - ] = None - attribution_window: Annotated[ - attribution_window_1.AttributionWindow | None, - Field( - description='Attribution methodology and lookback windows used for conversion metrics in this report.' - ), - ] = None - media_buy_deliveries: Annotated[ - list[MediaBuyDelivery], - Field( - description='Delivery rows for one or more media buys included in this notification.' - ), - ] - errors: Annotated[ - list[error.Error] | None, Field(description='Task-specific delivery errors or warnings.') - ] = None - sandbox: StrictBool | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/outcome_target.py b/src/adcp/types/generated_poc/media_buy/outcome_target.py deleted file mode 100644 index fdb0953f2..000000000 --- a/src/adcp/types/generated_poc/media_buy/outcome_target.py +++ /dev/null @@ -1,81 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/outcome_target.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictFloat, model_validator - -from ..core import outcome_target_cost_per -from ..enums import event_type as event_type_1 -from ..enums import forecastable_metric - - -class Goal(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['metric'] = 'metric' - metric: Annotated[ - forecastable_metric.ForecastableMetric, - Field( - description='Delivery metric to plan for, in the same vocabulary forecast points report.' - ), - ] - - -class Goal1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['event'] = 'event' - event_type: Annotated[ - event_type_1.EventType, - Field( - description='Conversion event to plan for, in the same vocabulary forecast points report.' - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description="Required when event_type is 'custom'. Platform-specific name for the custom event.", - min_length=1, - ), - ] = None - - -class OutcomeTarget(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - goal: Annotated[ - Goal | Goal1, - Field( - description='The outcome to plan against: a seller-tracked delivery metric or an advertiser conversion event.', - discriminator='kind', - ), - ] - volume: Annotated[ - StrictFloat | None, - Field( - description="Desired total volume of the goal's metric or event across the planned flight. Alone, the seller solves for budget and answers with total_budget_guidance and a forecast. With cost_per, the seller plans toward the volume at the cost: under a cap it SHOULD keep the buyer's amount and forecast the lower volume it can deliver, unless no volume can be planned at that amount; under a target the ask is plannable when the seller can forecast the volume around it.", - gt=0.0, - ), - ] = None - cost_per: outcome_target_cost_per.OutcomeTargetCostPer | None = None - - @model_validator(mode='after') - def _require_schema_required_group(self) -> OutcomeTarget: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('volume',), ('cost_per',),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'OutcomeTarget requires at least one of these field groups: volume | cost_per' - ) diff --git a/src/adcp/types/generated_poc/media_buy/package_control.py b/src/adcp/types/generated_poc/media_buy/package_control.py deleted file mode 100644 index f33cbb6df..000000000 --- a/src/adcp/types/generated_poc/media_buy/package_control.py +++ /dev/null @@ -1,104 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/package_control.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, StrictBool, StrictFloat, model_validator - -from ..core import bidding_policy, canonical_optimization_goal, keyword_target, targeting_input -from ..enums import pacing as pacing_1 - - -class CatalogId(ScalarStr): - __slots__ = () - _constraints = {'min_length': 1} - - -class PackageControl(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - package_id: Annotated[str, Field(min_length=1)] - budget: Annotated[ - StrictFloat | None, - Field( - description="Replace this package's hard lifetime spend cap. A number in a resulting seller-optimized buy requires advertised media_buy.features.seller_optimized_package_budgets; otherwise rejected with UNSUPPORTED_FEATURE before any over-subscription validation.", - ge=0.0, - ), - ] = None - daily_budget_cap: Annotated[ - StrictFloat | None, - Field( - description="Replace this package's subordinate hard daily cap; null removes it. Numeric changes apply immediately with package spend already incurred in the shared current cap day counted.", - ge=0.0, - ), - ] = None - min_spend_target: Annotated[ - StrictFloat | None, - Field( - description="Replace this package's soft minimum-spend target. A number is valid only for seller-optimized allocation and requires advertised media_buy.features.seller_optimized_min_spend_targets; otherwise rejected with UNSUPPORTED_FEATURE before any over-subscription validation.", - ge=0.0, - ), - ] = None - impressions: Annotated[StrictFloat | None, Field(ge=0.0)] = None - pacing: Annotated[ - pacing_1.Pacing | None, - Field( - description='Replace package pacing, subordinate to media-buy pacing. In a resulting seller-optimized buy, pacing that differs from the media-buy pacing requires advertised media_buy.features.seller_optimized_package_pacing; otherwise rejected with UNSUPPORTED_FEATURE.' - ), - ] = None - bidding: bidding_policy.BiddingPolicy | None = None - paused: StrictBool | None = None - canceled: Annotated[ - Literal[True] | None, - Field( - description='Exercise an already-accepted unilateral package cancellation right. If seller agreement is required, refine the accepted proposal with change_kind amendment; cancellation proposals terminate the whole MediaBuy.' - ), - ] = None - cancellation_reason: Annotated[str | None, Field(max_length=500, min_length=1)] = None - targeting_overlay: Annotated[ - targeting_input.TargetingOverlayInput | None, - Field( - description='Per-dimension targeting patch. Omission preserves the current dimension, a non-null value replaces it, and null clears it. The resulting effective targeting must remain within the accepted commercial envelope and is read back without null dimensions.' - ), - ] = None - catalog_ids: Annotated[ - list[CatalogId] | None, - Field( - description="Replace the package's promoted catalogs with references previously managed through sync_catalogs.", - min_length=1, - ), - ] = None - keyword_targets_add: Annotated[ - list[keyword_target.KeywordTarget] | None, Field(min_length=1) - ] = None - keyword_targets_remove: Annotated[ - list[keyword_target.KeywordTarget] | None, Field(min_length=1) - ] = None - negative_keywords_add: Annotated[ - list[keyword_target.KeywordTarget] | None, Field(min_length=1) - ] = None - negative_keywords_remove: Annotated[ - list[keyword_target.KeywordTarget] | None, Field(min_length=1) - ] = None - optimization_goals: Annotated[ - list[canonical_optimization_goal.CanonicalOptimizationGoal] | None, Field(min_length=1) - ] = None - - @model_validator(mode='after') - def _require_schema_required_group(self) -> PackageControl: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('budget',), ('daily_budget_cap',), ('min_spend_target',), ('impressions',), ('pacing',), ('bidding',), ('paused',), ('canceled',), ('targeting_overlay',), ('catalog_ids',), ('keyword_targets_add',), ('keyword_targets_remove',), ('negative_keywords_add',), ('negative_keywords_remove',), ('optimization_goals',),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'PackageControl requires at least one of these field groups: budget | daily_budget_cap | min_spend_target | impressions | pacing | bidding | paused | canceled | targeting_overlay | catalog_ids | keyword_targets_add | keyword_targets_remove | negative_keywords_add | negative_keywords_remove | optimization_goals' - ) diff --git a/src/adcp/types/generated_poc/media_buy/package_request.py b/src/adcp/types/generated_poc/media_buy/package_request.py deleted file mode 100644 index ba1fe58aa..000000000 --- a/src/adcp/types/generated_poc/media_buy/package_request.py +++ /dev/null @@ -1,298 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/package_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from collections.abc import Sequence -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat, model_validator - -from ..core import audience_evidence_pin -from ..core import audience_evidence_requirements as audience_evidence_requirements_1 -from ..core import bidding_policy, brand_ref, canonical_format_kind, catalog -from ..core import context as context_1 -from ..core import creative_asset, creative_assignment, duration -from ..core import ext as ext_1 -from ..core import format_id, format_option_ref -from ..core import measurement_terms as measurement_terms_1 -from ..core import optimization_goal, performance_standard, targeting_input, vendor_metric_id -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import attribution_methodology as attribution_methodology_1 -from ..enums import available_metric -from ..enums import completion_source as completion_source_1 -from ..enums import lift_dimension as lift_dimension_1 -from ..enums import pacing as pacing_1 -from ..enums import viewability_standard as viewability_standard_1 - - -class Qualifier(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - viewability_standard: viewability_standard_1.ViewabilityStandard | None = None - completion_source: completion_source_1.CompletionSource | None = None - attribution_methodology: attribution_methodology_1.AttributionMethodology | None = None - attribution_window: duration.Duration | None = None - lift_dimension: lift_dimension_1.LiftDimension | None = None - - -class CommittedMetrics1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['standard'], - Field(description='Standard metric from the closed `available-metric.json` enum.'), - ] = 'standard' - metric_id: Annotated[ - available_metric.AvailableMetric, - Field( - description="Identifier for the standard metric. MUST be present in the product's `reporting_capabilities.available_metrics`." - ), - ] - qualifier: Annotated[ - Qualifier | None, - Field( - description='Disambiguator — same shape as on the response-side `committed_metrics`. Required when the buyer wants to pin a specific measurement path: `viewability_standard` for MRC vs GroupM viewability; `completion_source` for seller- vs vendor-attested `completion_rate`; `attribution_methodology` for how attribution was computed (deterministic_purchase, probabilistic, panel_based, modeled); `attribution_window` for the time window over which outcomes are attributed. See response-side description for full semantics.' - ), - ] = None - - -class CommittedMetrics2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['vendor'], - Field(description='Vendor-defined metric, identified by the tuple `(vendor, metric_id)`.'), - ] = 'vendor' - vendor: Annotated[ - brand_ref.BrandReference, - Field( - description="Vendor that defines and computes this metric. The vendor's `brand.json` `agents[type='measurement']` is the canonical anchor." - ), - ] - metric_id: Annotated[ - vendor_metric_id.VendorMetricId, - Field( - description="Identifier for the metric within the vendor's vocabulary. MUST be present in the product's `reporting_capabilities.vendor_metrics` for the same vendor." - ), - ] - methodology_version: Annotated[ - str | None, - Field( - description="Optional buyer-proposed pin of the vendor's `measurement.metrics[].methodology_version`. The seller accepts by echoing it on the confirmed package's `committed_metrics` entry, normalizes to the version it can actually deliver, or rejects with `TERMS_REJECTED`. Opaque string — equality comparison only." - ), - ] = None - qualifier: Annotated[ - Qualifier | None, - Field( - description='Optional disambiguator for vendor metrics committed under more than one methodology or window — same closed key set as standard-scope entries.' - ), - ] = None - - -CommittedMetrics = Annotated[ - CommittedMetrics1 | CommittedMetrics2, - Field(discriminator='scope'), -] - - -class PackageRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - product_id: Annotated[ - str, - Field( - description="Opaque configured product ID returned by get_products. Selecting it accepts the product's disclosed targeting_resolution, pricing, forecast assumptions, and terms. Sellers MUST echo this value on every response package object that represents this requested package." - ), - ] - format_ids: Annotated[ - list[format_id.FormatReferenceStructuredObject] | None, - Field( - deprecated=True, - description='Deprecated in AdCP 3.2; removed in AdCP 4.0. Legacy named-format selector retained for older 3.x peers. New buyers MUST NOT emit this field. Sellers MUST normalize every entry through the canonical mapping path before product satisfaction checks; an entry that cannot be normalized is rejected with `UNSUPPORTED_FEATURE` before any equivalence check. When this field coexists with `format_option_refs` or `format_kind` plus `params`, sellers MUST compare the product option sets selected by each resolved route. Legacy parameter compatibility follows the asymmetric v2-narrows-v1 relation defined by canonical formats, not raw object equality. Different format shapes, selected option sets, or incompatible dimensions are rejected with `CONFLICTING_SELECTORS`; sellers MUST NOT silently ignore the legacy projection. Equivalent dual emission remains valid during the 3.x compatibility window. If omitted and no canonical selector is present, all formats supported by the product are active.', - min_length=1, - ), - ] = None - format_option_refs: Annotated[ - list[format_option_ref.FormatOptionReference] | None, - Field( - description='Canonical 3.2 format-option selector. Each reference matches one target product `format_options[]` entry. Publisher-backed options match `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match `{ scope: "product", format_option_id }`. Sellers reject unresolved options with `UNSUPPORTED_FEATURE` and a field path to the failing entry before comparing co-present routes. New buyers MUST use this route by itself and MUST NOT dual-emit either a direct canonical selector or deprecated `format_ids`. Receivers handling older 3.x multi-route requests MUST resolve every present route, require each route to select the same product option set, and reject disagreement with `CONFLICTING_SELECTORS` before treating `format_option_refs` as authoritative.', - min_length=1, - ), - ] = None - format_kind: Annotated[ - canonical_format_kind.CanonicalFormatKind | None, - Field( - description='Canonical 3.2 direct selector. Names the canonical format shape this package targets when the buyer is not selecting a published `format_option_ref`. Pair with `params` for dimensions, duration, codecs, or other constraints. New buyers MUST NOT combine this route with `format_option_refs` or deprecated `format_ids`. Receivers handling older 3.x multi-route requests MUST equivalence-check every present route before applying precedence and reject disagreement with `CONFLICTING_SELECTORS`. Product satisfaction is directional: broad `{ format_kind: "image" }` does not satisfy a fixed-size product declaration.' - ), - ] = None - params: Annotated[ - dict[str, Any] | None, - Field( - description="Parameters for the direct canonical selector in `format_kind`. Shape follows the selected canonical's parameter vocabulary: dimensions (`width`, `height`, `sizes`), duration (`duration_ms_exact`, `duration_ms_range`), codecs, asset-source and slot narrowing, or other canonical-specific constraints. Requires `format_kind`. For fixed-size image selectors, `width` and `height` MUST co-occur; a selector containing only one dimension is schema-invalid. New buyers omit `params` when selecting by `format_option_refs` or `format_ids`; older multi-route requests are accepted only when every route selects the same product option set." - ), - ] = None - budget: Annotated[ - StrictFloat | None, - Field( - description="Hard lifetime spend cap for this package in the media buy's currency. Required in fixed allocation mode. Optional in seller-optimized mode; when omitted, the package is bounded by the shared total_budget and any other package constraints. In seller-optimized mode this is a ceiling, not a reserved or current allocation, and requires advertised media_buy.features.seller_optimized_package_budgets; otherwise rejected with UNSUPPORTED_FEATURE before any over-subscription validation.", - ge=0.0, - ), - ] = None - min_spend_target: Annotated[ - StrictFloat | None, - Field( - description="Soft lifetime spend target for this package in the media buy's currency. Only valid with seller-optimized budget allocation. Requires advertised media_buy.features.seller_optimized_min_spend_targets; otherwise rejected with UNSUPPORTED_FEATURE before any over-subscription validation. The seller SHOULD attempt to deliver at least this amount before allocating incremental spend elsewhere, but inventory, policy, optimization targets, or other delivery constraints may prevent it. This is not a billing guarantee. Must not exceed the package budget when both are present; a seller advertising both seller_optimized_min_spend_targets and seller_optimized_package_budgets MUST reject a violation with `INVALID_REQUEST`, while a seller missing either capability rejects the undeclared control with UNSUPPORTED_FEATURE before any over-subscription validation.", - ge=0.0, - ), - ] = None - pacing: Annotated[ - pacing_1.Pacing | None, - Field( - description='Package pacing, subordinate to media-buy pacing. In seller-optimized mode, pacing that differs from the media-buy pacing requires advertised media_buy.features.seller_optimized_package_pacing; otherwise rejected with UNSUPPORTED_FEATURE.' - ), - ] = None - pricing_option_id: Annotated[ - str, - Field( - description="ID of the selected pricing option from the product's pricing_options array" - ), - ] - bid_price: Annotated[ - StrictFloat | None, - Field( - deprecated=True, - description='DEPRECATED in 3.2 and removed in the next major. Use bidding.bid_amount or bidding.max_bid. Legacy normalization: selected pricing_option.max_bid=true maps to bidding.max_bid; otherwise maps to bidding.bid_amount. A package MUST NOT supply both representations.', - ge=0.0, - ), - ] = None - bidding: Annotated[ - bidding_policy.BiddingPolicy | None, - Field( - description='Package-authored bidding policy. This complete block replaces, rather than field-merges with, any media-buy bidding policy for this package. `{automatic:true}` explicitly overrides a media-buy policy with provider automatic bidding; omission inherits the complete media-buy block. Monetary fields use the media-buy currency, while the selected pricing option supplies only the auction unit and MUST declare that same currency. Sellers MUST reject a new bidding block combined with legacy bid_price or legacy monetary optimization-goal targets on the same effective package with AMBIGUOUS_BIDDING_POLICY.' - ), - ] = None - impressions: Annotated[ - StrictFloat | None, Field(description='Impression goal for this package', ge=0.0) - ] = None - daily_budget_cap: Annotated[ - StrictFloat | None, - Field( - description="Optional hard package daily spend ceiling in the media-buy currency. It is subordinate, not a reserved allocation; package caps need not sum to the aggregate cap. Uses the media buy's cap timezone. Requires advertised package budget-capping scope; otherwise rejected with UNSUPPORTED_FEATURE.", - ge=0.0, - ), - ] = None - start_time: Annotated[ - AwareDatetime | None, - Field( - description="Flight start date/time for this package in ISO 8601 format. When omitted, the package inherits the media buy's start_time. Must fall within the media buy's date range." - ), - ] = None - end_time: Annotated[ - AwareDatetime | None, - Field( - description="Flight end date/time for this package in ISO 8601 format. When omitted, the package inherits the media buy's end_time. Must fall within the media buy's date range." - ), - ] = None - paused: Annotated[ - StrictBool | None, - Field( - description='Whether this package should be created in a paused state. Paused packages do not deliver impressions. Defaults to false.' - ), - ] = False - catalogs: Annotated[ - list[catalog.Catalog] | None, - Field( - description='Catalogs this package promotes. Each catalog MUST have a distinct type (e.g., one product catalog, one store catalog). This constraint is enforced at the application level — sellers MUST reject requests containing multiple catalogs of the same type with a validation_error. Makes the package catalog-driven: one budget envelope, platform optimizes across items.' - ), - ] = None - optimization_goals: Annotated[ - list[optimization_goal.OptimizationGoal] | None, - Field( - description='Optimization targets for this package. The seller optimizes delivery toward these goals in priority order. Common pattern: event goals (purchase, install) as primary targets at priority 1; metric goals (clicks, views) as secondary proxy signals at priority 2+.', - min_length=1, - ), - ] = None - targeting_overlay: Annotated[ - targeting_input.TargetingOverlayInput | None, - Field( - description="Optional package-specific targeting input with three states per dimension. Omission inherits targeting already bound to the configured product, a non-null value replaces that dimension, and null explicitly suppresses the configured/product default for that dimension. Null cannot remove inherent product scope: sellers reject a clear the product cannot execute rather than silently retaining the default. Non-null fields MUST be declared in the product's overlay_support unless they were already accepted during discovery. The one fixed-inventory restatement exception is placement_selection equal to the product's complete, explicitly enumerated mode: included placement set: that set is an inherent exact match across discovery, create, and update and does not require overlay_support.placement_selection; partial selection still requires a selectable product. Opaque property_list and collection_list references have no equivalent exception because their membership can change independently and cannot be proven equal from the product wire representation. Package readback echoes the complete effective targeting without null dimensions. A supported value with no current inventory returns PRODUCT_UNAVAILABLE rather than a silent substitute or reprice." - ), - ] = None - audience_evidence_requirements: Annotated[ - audience_evidence_requirements_1.AudienceEvidenceRequirements | None, - Field( - description='Buyer policy that the selected product and constructed package MUST satisfy using product audience evidence. This remains planning and suitability evidence, not a targeting instruction. Sellers MUST reject an unsatisfied required policy rather than silently drop it, and a confirmed package MUST include every evidence snapshot used to satisfy this policy in audience_evidence_selections with decision_use package_construction.' - ), - ] = None - audience_evidence_pins: Annotated[ - list[audience_evidence_pin.AudienceEvidencePin] | None, - Field( - description="Exact immutable evidence snapshots selected by the buyer during discovery. The seller MUST match evidence_id, snapshot_id, version, and content_digest against one published snapshot and MUST reject catalog mutation, snapshot reuse, missing snapshots, or substitutions. Every accepted pin MUST be echoed in the confirmed package's audience_evidence_selections with decision_use package_construction.", - min_length=1, - ), - ] = None - measurement_terms: Annotated[ - measurement_terms_1.MeasurementTerms | None, - Field( - description="Buyer's proposed billing measurement and makegood terms. Overrides product defaults. Seller accepts (echoed on confirmed package), rejects with TERMS_REJECTED, or adjusts. When absent, product's measurement_terms apply." - ), - ] = None - performance_standards: Annotated[ - list[performance_standard.PerformanceStandard] | None, - Field( - description="Buyer's proposed performance standards for this package. Overrides product defaults. Seller accepts, rejects with TERMS_REJECTED, or adjusts. When absent, product's performance_standards apply.", - min_length=1, - ), - ] = None - committed_metrics: Annotated[ - list[CommittedMetrics] | None, - Field( - description="Buyer's proposed reporting contract for this package — the metrics the buyer wants the seller to commit to populating in delivery reports. Same negotiation pattern as `measurement_terms` and `performance_standards`: seller accepts (echoes on confirmed package with `committed_at` stamped), rejects with `TERMS_REJECTED` (with explanation of which entries were unworkable), or normalizes (echoes a different but compatible list — buyer can accept by retrying with the normalized terms). When absent, the seller decides what to commit based on the product's `available_metrics` and the buyer's `required_metrics` filter on `get_products`. Each entry uses an explicit `scope` discriminator (`standard` or `vendor`) and identifies the metric — request-side entries do NOT carry `committed_at`; that timestamp is stamped by the seller on accept. Constraints on what the buyer MAY propose: each `scope: standard` entry's `metric_id` MUST be in the product's `available_metrics`, and each `scope: vendor` entry's `(vendor, metric_id)` MUST appear in the product's `vendor_metrics` — sellers SHOULD reject with `TERMS_REJECTED` and reference the offending entry when the proposal exceeds product capability.", - min_length=1, - ), - ] = None - creative_assignments: Annotated[ - list[creative_assignment.CreativeAssignment] | None, - Field( - description='Assign existing library creatives to this package with optional rotation, grouping, weights, and placement targeting. rotation_mode is package-scoped: omission resolves to weighted, and every assignment MUST resolve to the same effective mode. In sequential mode, sequence_position MUST be unique within each package-local group. Sellers reject conflicts with VALIDATION_ERROR before creating the package.', - min_length=1, - ), - ] = None - creatives: Annotated[ - Sequence[creative_asset.CreativeAsset] | None, - Field( - description="Upload creative assets inline and assign to this package. Native localization is not accepted on this path; use sync_creatives before assigning the library creative. When the seller also advertises creative.has_creative_library: true, these creatives enter the seller's creative library and can be reused by creative_id while retained; inline-only sellers may store them as package-scoped assets. Use creative_assignments instead for existing library creatives.", - max_length=100, - min_length=1, - ), - ] = None - agency_estimate_number: Annotated[ - str | None, - Field( - description='Agency estimate or authorization number for this package. Overrides the media buy-level estimate number when different packages correspond to different agency estimates (e.g., different stations or flights within the same buy).', - max_length=100, - ), - ] = None - context: Annotated[ - context_1.ContextObject | None, - Field( - description='Opaque package-level correlation data echoed unchanged in the package response, webhooks, and read surfaces. Buyers targeting mixed seller populations SHOULD include a per-package correlation value here, commonly context_1.buyer_ref, so responses from legacy sellers that do not echo product_id can still be mapped back to the requested product or line item. Do not use deprecated top-level buyer_ref for v3 correlation.' - ), - ] = None - ext: ext_1.ExtensionObject | None = None - - @model_validator(mode='after') - def _validate_format_params(self) -> PackageRequest: - if self.params is not None and self.format_kind is None: - raise ValueError('params requires format_kind') - if self.params is not None and self.format_kind == 'image': - if ('width' in self.params) != ('height' in self.params): - raise ValueError('image params width and height must co-occur') - return self diff --git a/src/adcp/types/generated_poc/media_buy/package_update.py b/src/adcp/types/generated_poc/media_buy/package_update.py deleted file mode 100644 index 4146ac47c..000000000 --- a/src/adcp/types/generated_poc/media_buy/package_update.py +++ /dev/null @@ -1,197 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/package_update.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat - -from ..core import bidding_policy, catalog -from ..core import context as context_1 -from ..core import creative_asset, creative_assignment -from ..core import ext as ext_1 -from ..core import optimization_goal, targeting_input -from ..enums import match_type as match_type_1 -from ..enums import pacing as pacing_1 - - -class KeywordTargetsAddItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - keyword: Annotated[str, Field(description='The keyword to target', min_length=1)] - match_type: match_type_1.MatchType - bid_price: Annotated[ - StrictFloat | None, - Field( - description="Per-keyword bid price. Inherits currency and max_bid interpretation from the package's pricing option.", - ge=0.0, - ), - ] = None - - -class KeywordTargetsRemoveItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - keyword: Annotated[str, Field(description='The keyword to stop targeting', min_length=1)] - match_type: match_type_1.MatchType - - -class NegativeKeywordsAddItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - keyword: Annotated[str, Field(description='The keyword to exclude', min_length=1)] - match_type: match_type_1.MatchType - - -class NegativeKeywordsRemoveItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - keyword: Annotated[str, Field(description='The keyword to stop excluding', min_length=1)] - match_type: match_type_1.MatchType - - -class PackageUpdate(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - package_id: Annotated[str, Field(description="Seller's ID of package to update")] - budget: Annotated[ - StrictFloat | None, - Field( - description='Updated hard spend cap for this package in the media-buy currency. Every selected pricing option in an AdCP-authored media buy MUST declare that same currency. In seller-optimized mode a number changes the package ceiling and null removes it so only the shared total and other constraints bound the package. null is invalid when the resulting allocation mode is fixed. A number in a resulting seller-optimized buy requires advertised media_buy.features.seller_optimized_package_budgets; otherwise rejected with UNSUPPORTED_FEATURE before any over-subscription validation.', - ge=0.0, - ), - ] = None - min_spend_target: Annotated[ - StrictFloat | None, - Field( - description='Updated soft lifetime spend target for this package. A number is valid only for seller-optimized allocation, requires advertised media_buy.features.seller_optimized_min_spend_targets (otherwise UNSUPPORTED_FEATURE, before any over-subscription validation), and must not exceed the resulting package budget when one exists. null removes the target. Sellers MUST validate the complete post-update state atomically.', - ge=0.0, - ), - ] = None - pacing: Annotated[ - pacing_1.Pacing | None, - Field( - description='Updated package pacing, subordinate to media-buy pacing. In a resulting seller-optimized buy, pacing that differs from the media-buy pacing requires advertised media_buy.features.seller_optimized_package_pacing; otherwise rejected with UNSUPPORTED_FEATURE.' - ), - ] = None - bid_price: Annotated[ - StrictFloat | None, - Field( - deprecated=True, - description='DEPRECATED in 3.2 and removed in the next major. Use bidding. A package update MUST NOT supply both a non-null bidding object and bid_price. During migration, bidding:null MAY accompany bid_price to clear the canonical block and set the legacy representation atomically.', - ge=0.0, - ), - ] = None - bidding: Annotated[ - bidding_policy.BiddingPolicy | None, - Field( - description='Replace the complete package-authored bidding policy. An object replaces any prior package block and remains a complete override of the media-buy default. `{automatic:true}` explicitly selects provider automatic bidding at package scope. null clears the package-authored block so the package inherits media-buy bidding; if the media-buy block is also absent, provider automatic delivery applies. Monetary fields use the media-buy currency and require the package pricing option to declare that currency. During legacy migration, null MAY accompany bid_price or monetary optimization-goal targets; only a non-null canonical bidding object conflicts with those representations.' - ), - ] = None - impressions: Annotated[ - StrictFloat | None, Field(description='Updated impression goal for this package', ge=0.0) - ] = None - daily_budget_cap: Annotated[ - StrictFloat | None, - Field( - description="Replace this package's hard daily cap; null removes it. Numeric changes apply immediately with current-day package spend counted. A cap below that spend pauses the package for the day; the aggregate cap remains independently binding.", - ge=0.0, - ), - ] = None - start_time: Annotated[ - AwareDatetime | None, - Field( - description="Updated flight start date/time for this package in ISO 8601 format. Must fall within the media buy's date range." - ), - ] = None - end_time: Annotated[ - AwareDatetime | None, - Field( - description="Updated flight end date/time for this package in ISO 8601 format. Must fall within the media buy's date range." - ), - ] = None - paused: Annotated[ - StrictBool | None, - Field(description='Pause/resume specific package (true = paused, false = active)'), - ] = None - canceled: Annotated[ - Literal[True] | None, - Field( - description='Cancel this specific package. Cancellation is irreversible — canceled packages stop delivery and cannot be reactivated. When true, package cancellation takes precedence over sibling fields on this package: the seller applies only canceled and cancellation_reason for this package and SHOULD return a structured warning naming ignored sibling fields. Root fields and other package updates still participate in the same atomic update when root canceled is absent. Sellers MAY reject with NOT_CANCELLABLE.' - ), - ] = None - cancellation_reason: Annotated[ - str | None, Field(description='Reason for canceling this package.', max_length=500) - ] = None - catalogs: Annotated[ - list[catalog.Catalog] | None, - Field( - description='Replace the catalogs this package promotes. Uses replacement semantics — the provided array replaces the current list. Omit to leave catalogs unchanged.', - min_length=1, - ), - ] = None - optimization_goals: Annotated[ - list[optimization_goal.OptimizationGoal] | None, - Field( - description='Replace all optimization goals for this package. Uses replacement semantics — omit to leave goals unchanged.', - min_length=1, - ), - ] = None - targeting_overlay: Annotated[ - targeting_input.TargetingOverlayInput | None, - Field( - description='Per-dimension targeting patch for this package. Omit targeting_overlay, or omit an individual dimension inside it, to leave the corresponding effective targeting unchanged. A non-null dimension replaces its current value; null clears it, including a value inherited from configured-product selection or a product default. Every resulting effective overlay must remain executable by the product. Sellers reject unsupported or partially applicable changes and use REQUOTE_REQUIRED when a change, including a broader inventory set, falls outside the priced envelope. placement_selection is purchased-inventory targeting; mode default restores the product default, null clears the dimension when the product permits it, and successful readback echoes the committed selected set when enumerable. If a patch removes a placement referenced by an existing creative assignment, the seller MUST reject the update unless the same atomic package mutation supplies a compatible complete creative_assignments replacement. Sellers MUST NOT silently delete assignments or retain orphan placement refs.' - ), - ] = None - keyword_targets_add: Annotated[ - list[KeywordTargetsAddItem] | None, - Field( - description='Keyword targets to add or update on this package. Upserts by (keyword, match_type) identity: if the pair already exists, its bid_price is updated; if not, a new keyword target is added. Use targeting_overlay.keyword_targets in create_media_buy to set the initial list.', - min_length=1, - ), - ] = None - keyword_targets_remove: Annotated[ - list[KeywordTargetsRemoveItem] | None, - Field( - description='Keyword targets to remove from this package. Removes matching (keyword, match_type) pairs. If a specified pair is not present, sellers SHOULD treat it as a no-op for that entry.', - min_length=1, - ), - ] = None - negative_keywords_add: Annotated[ - list[NegativeKeywordsAddItem] | None, - Field( - description='Negative keywords to add to this package. Appends to the existing negative keyword list — does not replace it. If a keyword+match_type pair already exists, sellers SHOULD treat it as a no-op for that entry. Use targeting_overlay.negative_keywords in create_media_buy to set the initial list.', - min_length=1, - ), - ] = None - negative_keywords_remove: Annotated[ - list[NegativeKeywordsRemoveItem] | None, - Field( - description='Negative keywords to remove from this package. Removes matching keyword+match_type pairs from the existing list. If a specified pair is not present, sellers SHOULD treat it as a no-op for that entry.', - min_length=1, - ), - ] = None - creative_assignments: Annotated[ - list[creative_assignment.CreativeAssignment] | None, - Field( - description='Replace creative assignments for this package with optional rotation, grouping, weights, and placement routing. Uses replacement semantics - omit to leave assignments unchanged. rotation_mode is package-scoped: omission resolves to weighted, and every assignment MUST resolve to the same effective mode. In sequential mode, sequence_position MUST be unique within each package-local group. Sellers reject conflicts with VALIDATION_ERROR before mutation. When the same mutation narrows or clears targeting_overlay.placement_selection, this complete replacement MUST remove or reroute every assignment reference that would otherwise be orphaned; the seller validates both changes atomically.' - ), - ] = None - creatives: Annotated[ - list[creative_asset.CreativeAsset] | None, - Field( - description="Replace this package's inline creative assets. Native localization is not accepted on this path; use sync_creatives before assigning the library creative. When the seller also advertises creative.has_creative_library: true, new inline creatives enter the seller's creative library and can be reused by creative_id while retained; inline-only sellers may store them as package-scoped assets. Use creative_assignments instead for existing library creatives.", - max_length=100, - min_length=1, - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/product_discovery_criteria.py b/src/adcp/types/generated_poc/media_buy/product_discovery_criteria.py deleted file mode 100644 index 565df258f..000000000 --- a/src/adcp/types/generated_poc/media_buy/product_discovery_criteria.py +++ /dev/null @@ -1,89 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/product_discovery_criteria.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field - -from ..core import catalog_selection -from ..core import media_buy_frequency_cap as media_buy_frequency_cap_1 -from ..core import ( - media_buy_support_requirements, - product_offer_filters, - targeting, - targeting_overlay_requirements, -) -from . import acceptance_context as acceptance_context_1 -from . import outcome_target as outcome_target_1 - - -class ProductId(ScalarStr): - __slots__ = () - _constraints = {'min_length': 1} - - -class PolicyId(ProductId): - pass - - -class ProductDiscoveryCriteria(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - product_ids: Annotated[ - list[ProductId] | None, - Field( - description='Exact seller-issued products to retrieve or use as proposal candidates.', - min_length=1, - ), - ] = None - offer_filters: Annotated[ - product_offer_filters.ProductOfferFilters | None, - Field( - description='Hard commercial and product-characteristic filters. Every returned product MUST satisfy them. These fields do not become package delivery targeting.' - ), - ] = None - targeting_overlay: Annotated[ - targeting.TargetingOverlay | None, - Field( - description='Concrete delivery constraints the buyer expects to carry into create_media_buy. Returned product pricing and forecasts MUST reflect the effective targeting. A product that cannot apply the request exactly is omitted or returned as a distinct configured product with sparse Product.targeting_resolution modifications; absence of that resolution means exact acceptance.' - ), - ] = None - media_buy_frequency_cap: Annotated[ - media_buy_frequency_cap_1.MediaBuyFrequencyCap | None, - Field( - description='Concrete aggregate max-impression cap expected at the MediaBuy root. Every returned product must participate in one shared counter for this exact value. Proposals carry it in commercial_terms.frequency_cap.' - ), - ] = None - required_overlay_support: Annotated[ - targeting_overlay_requirements.TargetingOverlayRequirements | None, - Field( - description='Minimum product-scoped targeting dimensions the buyer must be able to select independently on packages later. This requests capability, not current values, value-specific availability, a forecast for every later value, or one product per value.' - ), - ] = None - required_media_buy_support: Annotated[ - media_buy_support_requirements.ProductMediaBuySupportRequirements | None, - Field( - description='Minimum product participation in shared MediaBuy-level controls. For frequency caps, every returned product is promising composability in one aggregate seller counter for each requested per unit.' - ), - ] = None - outcome_target: Annotated[ - outcome_target_1.OutcomeTarget | None, - Field( - description='Reverse-forecast planning input for sellers declaring media_buy.outcome_target: a goal plus a volume, a cost target, or both, answered on proposals. Inert on list_products for every seller. See outcome-target.json.' - ), - ] = None - acceptance_context: Annotated[ - acceptance_context_1.AcceptanceContext | None, - Field( - description='Structured campaign and advertiser facts for coarse acceptance-policy matching. A matching product is not a guarantee of final acceptance.' - ), - ] = None - catalog: catalog_selection.CatalogSelection | None = None - policy_ids: Annotated[list[PolicyId] | None, Field(min_length=1)] = None - ext: dict[str, Any] | None = None diff --git a/src/adcp/types/generated_poc/media_buy/product_fields.py b/src/adcp/types/generated_poc/media_buy/product_fields.py deleted file mode 100644 index 6d69a65ef..000000000 --- a/src/adcp/types/generated_poc/media_buy/product_fields.py +++ /dev/null @@ -1,62 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/product_fields.json -# timestamp: 2026-10-01T07:57:26+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from pydantic import Field, RootModel - - -class ProductResponseField(StrEnum): - product_id = 'product_id' - name = 'name' - description = 'description' - publisher_properties = 'publisher_properties' - channels = 'channels' - video_placement_types = 'video_placement_types' - audio_distribution_types = 'audio_distribution_types' - sponsored_placement_types = 'sponsored_placement_types' - social_placement_surfaces = 'social_placement_surfaces' - format_options = 'format_options' - placements = 'placements' - delivery_type = 'delivery_type' - exclusivity = 'exclusivity' - pricing_options = 'pricing_options' - forecast = 'forecast' - reporting_capabilities = 'reporting_capabilities' - measurement_terms = 'measurement_terms' - performance_standards = 'performance_standards' - catalog_types = 'catalog_types' - signal_targeting_allowed = 'signal_targeting_allowed' - signal_targeting_rules = 'signal_targeting_rules' - demographic_targeting = 'demographic_targeting' - overlay_support = 'overlay_support' - collections = 'collections' - collection_targeting_allowed = 'collection_targeting_allowed' - media_buy_support = 'media_buy_support' - audience_evidence = 'audience_evidence' - audience_evidence_selections = 'audience_evidence_selections' - max_optimization_goals = 'max_optimization_goals' - catalog_match = 'catalog_match' - list_applications = 'list_applications' - brief_relevance = 'brief_relevance' - targeting_resolution = 'targeting_resolution' - acceptance_policy_profile_ids = 'acceptance_policy_profile_ids' - identity = 'identity' - execution_requirements = 'execution_requirements' - expires_at = 'expires_at' - allowed_actions = 'allowed_actions' - - -class ProductResponseFields(RootModel[list[ProductResponseField]]): - root: Annotated[ - list[ProductResponseField], - Field( - description='Canonical product fields a buyer requests from list_products. Required product_id and name fields are always returned. list_applications also overrides projection whenever a property or collection list is in the effective targeting because it is the decision receipt for seller-specific list matching. targeting_resolution and expires_at override projection whenever the seller returns modifications, as does collection_targeting_allowed whenever returned overlay_support declares collection_list. Legacy named-format fields remain available only through get_products.', - min_length=1, - title='Product Response Fields', - ), - ] diff --git a/src/adcp/types/generated_poc/media_buy/product_purchase.py b/src/adcp/types/generated_poc/media_buy/product_purchase.py deleted file mode 100644 index 571908505..000000000 --- a/src/adcp/types/generated_poc/media_buy/product_purchase.py +++ /dev/null @@ -1,150 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/product_purchase.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat - -from ..core import ( - audience_evidence_pin, - bidding_policy, - canonical_measurement_terms, - canonical_optimization_goal, - canonical_performance_standard, - canonical_pricing_option, -) -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core import format_option_ref, product_audience_evidence_requirements, targeting -from ..enums import pacing as pacing_1 - - -class CatalogId(ScalarStr): - __slots__ = () - _constraints = {'min_length': 1} - - -class ProductPurchase(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - product_id: Annotated[str, Field(min_length=1)] - pricing_option_id: Annotated[str, Field(min_length=1)] - pricing: Annotated[ - canonical_pricing_option.CanonicalPricingOption | None, - Field( - description='Resolved selected pricing terms. Optional on buy_products input, where pricing_option_id plus the versioned feed identifies the offer; required inside accepted commercial_terms. Its pricing_option_id MUST match the sibling field.' - ), - ] = None - format_option_refs: Annotated[ - list[format_option_ref.FormatOptionReference] | None, - Field( - description='Canonical format options selected from the published product offer. Legacy named-format identifiers are not accepted.', - min_length=1, - ), - ] = None - catalog_ids: Annotated[ - list[CatalogId] | None, - Field( - description='Previously synchronized account catalog IDs promoted by this selection. Callers manage catalog bodies through sync_catalogs rather than inlining them here.', - min_length=1, - ), - ] = None - budget: Annotated[ - StrictFloat | None, - Field( - description='Hard spend cap for this selection in the media-buy currency. In seller-optimized allocation it is an optional ceiling, not a reserved allocation, and requires advertised media_buy.features.seller_optimized_package_budgets; otherwise rejected with UNSUPPORTED_FEATURE before any over-subscription validation.', - ge=0.0, - ), - ] = None - daily_budget_cap: Annotated[ - StrictFloat | None, - Field( - description="Optional hard daily spend ceiling for this purchase. It is subordinate to the media-buy aggregate daily cap and is not a reserved daily allocation. Its day boundary is the media buy's budget_cap_timezone.", - ge=0.0, - ), - ] = None - min_spend_target: Annotated[ - StrictFloat | None, - Field( - description='Soft lifetime spend target for seller-optimized allocation. Requires advertised media_buy.features.seller_optimized_min_spend_targets; otherwise rejected with UNSUPPORTED_FEATURE before any over-subscription validation.', - ge=0.0, - ), - ] = None - impressions: Annotated[ - StrictFloat | None, Field(ge=0.0, title='Product Purchase Impressions') - ] = None - start_time: Annotated[ - AwareDatetime | None, - Field( - description='Resolved package flight start. On direct-purchase input, omission inherits the MediaBuy start; accepted proposal snapshots carry the resolved timestamp.' - ), - ] = None - end_time: Annotated[ - AwareDatetime | None, - Field( - description='Resolved package flight end. On direct-purchase input, omission inherits the MediaBuy end; accepted proposal snapshots carry the resolved timestamp.' - ), - ] = None - pacing: Annotated[ - pacing_1.Pacing | None, - Field( - description='Purchase pacing, subordinate to media-buy pacing. In seller-optimized allocation, pacing that differs from the media-buy pacing requires advertised media_buy.features.seller_optimized_package_pacing; otherwise rejected with UNSUPPORTED_FEATURE.' - ), - ] = None - bidding: bidding_policy.BiddingPolicy | None = None - targeting_overlay: Annotated[ - targeting.TargetingOverlay | None, - Field( - description="Resolved effective buyer-selected targeting, including compatible wholesale signal selections, applied within the product's published targeting contract. Cleared dimensions are omitted; null is invalid in an accepted snapshot." - ), - ] = None - optimization_goals: Annotated[ - list[canonical_optimization_goal.CanonicalOptimizationGoal] | None, Field(min_length=1) - ] = None - audience_evidence_requirements: Annotated[ - product_audience_evidence_requirements.ProductAudienceEvidenceRequirements | None, - Field( - description='Buyer evidence-admissibility policy carried into the accepted purchase snapshot.', - title='Product Purchase Audience Evidence Requirements', - ), - ] = None - audience_evidence_pins: Annotated[ - list[audience_evidence_pin.AudienceEvidencePin] | None, - Field( - description='Exact immutable audience-evidence snapshots selected for package construction.', - min_length=1, - ), - ] = None - agency_estimate_number: Annotated[ - str | None, - Field( - description='Package-level agency estimate or authorization reference.', max_length=100 - ), - ] = None - context: Annotated[ - context_1.ContextObject | None, - Field( - description='Opaque buyer package correlation preserved in the accepted snapshot and readback.' - ), - ] = None - ext: ext_1.ExtensionObject | None = None - measurement_terms: Annotated[ - canonical_measurement_terms.CanonicalMeasurementTerms | None, - Field( - description='Published or negotiated billing-measurement and makegood terms for this purchase. Direct buyers may omit this to inherit the product default; accepted proposal snapshots preserve the resolved terms.', - title='Product Purchase Measurement Terms', - ), - ] = None - performance_standards: Annotated[ - list[canonical_performance_standard.CanonicalPerformanceStandard] | None, - Field( - description='Published or negotiated metric thresholds and measurement vendors. Direct buyers may omit this to inherit the product defaults; accepted proposal snapshots preserve every applicable standard.', - min_length=1, - ), - ] = None diff --git a/src/adcp/types/generated_poc/media_buy/product_purchase_input.py b/src/adcp/types/generated_poc/media_buy/product_purchase_input.py deleted file mode 100644 index 4c213947d..000000000 --- a/src/adcp/types/generated_poc/media_buy/product_purchase_input.py +++ /dev/null @@ -1,150 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/product_purchase_input.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat - -from ..core import ( - audience_evidence_pin, - bidding_policy, - canonical_measurement_terms, - canonical_optimization_goal, - canonical_performance_standard, - canonical_pricing_option, -) -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core import format_option_ref, product_audience_evidence_requirements, targeting_input -from ..enums import pacing as pacing_1 - - -class CatalogId(ScalarStr): - __slots__ = () - _constraints = {'min_length': 1} - - -class ProductPurchaseInput(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - product_id: Annotated[str, Field(min_length=1)] - pricing_option_id: Annotated[str, Field(min_length=1)] - pricing: Annotated[ - canonical_pricing_option.CanonicalPricingOption | None, - Field( - description='Resolved selected pricing terms. Optional on buy_products input, where pricing_option_id plus the versioned feed identifies the offer; required inside accepted commercial_terms. Its pricing_option_id MUST match the sibling field.' - ), - ] = None - format_option_refs: Annotated[ - list[format_option_ref.FormatOptionReference] | None, - Field( - description='Canonical format options selected from the published product offer. Legacy named-format identifiers are not accepted.', - min_length=1, - ), - ] = None - catalog_ids: Annotated[ - list[CatalogId] | None, - Field( - description='Previously synchronized account catalog IDs promoted by this selection. Callers manage catalog bodies through sync_catalogs rather than inlining them here.', - min_length=1, - ), - ] = None - budget: Annotated[ - StrictFloat | None, - Field( - description='Hard spend cap for this selection in the media-buy currency. In seller-optimized allocation it is an optional ceiling, not a reserved allocation, and requires advertised media_buy.features.seller_optimized_package_budgets; otherwise rejected with UNSUPPORTED_FEATURE before any over-subscription validation.', - ge=0.0, - ), - ] = None - daily_budget_cap: Annotated[ - StrictFloat | None, - Field( - description="Optional hard daily spend ceiling for this purchase. It is subordinate to the media-buy aggregate daily cap and is not a reserved daily allocation. Its day boundary is the media buy's budget_cap_timezone.", - ge=0.0, - ), - ] = None - min_spend_target: Annotated[ - StrictFloat | None, - Field( - description='Soft lifetime spend target for seller-optimized allocation. Requires advertised media_buy.features.seller_optimized_min_spend_targets; otherwise rejected with UNSUPPORTED_FEATURE before any over-subscription validation.', - ge=0.0, - ), - ] = None - impressions: Annotated[ - StrictFloat | None, Field(ge=0.0, title='Product Purchase Impressions') - ] = None - start_time: Annotated[ - AwareDatetime | None, - Field( - description='Resolved package flight start. On direct-purchase input, omission inherits the MediaBuy start; accepted proposal snapshots carry the resolved timestamp.' - ), - ] = None - end_time: Annotated[ - AwareDatetime | None, - Field( - description='Resolved package flight end. On direct-purchase input, omission inherits the MediaBuy end; accepted proposal snapshots carry the resolved timestamp.' - ), - ] = None - pacing: Annotated[ - pacing_1.Pacing | None, - Field( - description='Purchase pacing, subordinate to media-buy pacing. In seller-optimized allocation, pacing that differs from the media-buy pacing requires advertised media_buy.features.seller_optimized_package_pacing; otherwise rejected with UNSUPPORTED_FEATURE.' - ), - ] = None - bidding: bidding_policy.BiddingPolicy | None = None - targeting_overlay: Annotated[ - targeting_input.TargetingOverlayInput | None, - Field( - description="Per-dimension targeting input. Omission inherits the selected product's configured/default dimension, a non-null value replaces it, and null explicitly suppresses it. A clear cannot remove inherent product scope." - ), - ] = None - optimization_goals: Annotated[ - list[canonical_optimization_goal.CanonicalOptimizationGoal] | None, Field(min_length=1) - ] = None - audience_evidence_requirements: Annotated[ - product_audience_evidence_requirements.ProductAudienceEvidenceRequirements | None, - Field( - description='Buyer evidence-admissibility policy carried into the accepted purchase snapshot.', - title='Product Purchase Audience Evidence Requirements', - ), - ] = None - audience_evidence_pins: Annotated[ - list[audience_evidence_pin.AudienceEvidencePin] | None, - Field( - description='Exact immutable audience-evidence snapshots selected for package construction.', - min_length=1, - ), - ] = None - agency_estimate_number: Annotated[ - str | None, - Field( - description='Package-level agency estimate or authorization reference.', max_length=100 - ), - ] = None - context: Annotated[ - context_1.ContextObject | None, - Field( - description='Opaque buyer package correlation preserved in the accepted snapshot and readback.' - ), - ] = None - ext: ext_1.ExtensionObject | None = None - measurement_terms: Annotated[ - canonical_measurement_terms.CanonicalMeasurementTerms | None, - Field( - description='Published or negotiated billing-measurement and makegood terms for this purchase. Direct buyers may omit this to inherit the product default; accepted proposal snapshots preserve the resolved terms.', - title='Product Purchase Measurement Terms', - ), - ] = None - performance_standards: Annotated[ - list[canonical_performance_standard.CanonicalPerformanceStandard] | None, - Field( - description='Published or negotiated metric thresholds and measurement vendors. Direct buyers may omit this to inherit the product defaults; accepted proposal snapshots preserve every applicable standard.', - min_length=1, - ), - ] = None diff --git a/src/adcp/types/generated_poc/media_buy/product_refinement.py b/src/adcp/types/generated_poc/media_buy/product_refinement.py deleted file mode 100644 index d04a34d79..000000000 --- a/src/adcp/types/generated_poc/media_buy/product_refinement.py +++ /dev/null @@ -1,87 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/product_refinement.json -# timestamp: 2026-10-03T00:36:50+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel - - -class ProductRefinementRequests2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[Literal['request'], Field(description='Change scoped to the overall request.')] = 'request' - ask: Annotated[ - str, Field(description='What the buyer is asking for at the request level.', min_length=1) - ] - - -class Action(StrEnum): - include = 'include' - omit = 'omit' - more_like_this = 'more_like_this' - - -class ProductRefinementRequests3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[Literal['product'], Field(description='Change scoped to a specific product.')] = 'product' - product_id: Annotated[ - str, - Field(description='Product ID from a previous product-discovery response.', min_length=1), - ] - action: Annotated[Action | None, Field(description='Requested product-level action.')] = ( - Action.include - ) - ask: Annotated[ - str | None, Field(description='What the buyer is asking for on this product.', min_length=1) - ] = None - - -class Action11(StrEnum): - include = 'include' - omit = 'omit' - finalize = 'finalize' - - -class ProductRefinementRequests4(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['proposal'], Field(description='Change scoped to a specific proposal.') - ] = 'proposal' - proposal_id: Annotated[ - str, - Field(description='Proposal ID from a previous product-discovery response.', min_length=1), - ] - action: Annotated[Action11 | None, Field(description='Requested proposal-level action.')] = ( - Action11.include - ) - ask: Annotated[ - str | None, - Field(description='What the buyer is asking for on this proposal.', min_length=1), - ] = None - - -ProductRefinementRequests1 = Annotated[ - ProductRefinementRequests2 | ProductRefinementRequests3 | ProductRefinementRequests4, - Field(discriminator='scope'), -] - - -class ProductRefinementRequests(RootModel[list[ProductRefinementRequests1]]): - root: Annotated[ - list[ProductRefinementRequests1], - Field( - description='Change requests for iterating on product discovery results and proposals.', - min_length=1, - title='Product Refinement Requests', - ), - ] diff --git a/src/adcp/types/generated_poc/media_buy/proposal_budget_constraint.py b/src/adcp/types/generated_poc/media_buy/proposal_budget_constraint.py deleted file mode 100644 index 436995315..000000000 --- a/src/adcp/types/generated_poc/media_buy/proposal_budget_constraint.py +++ /dev/null @@ -1,32 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/proposal_budget_constraint.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictFloat, model_validator - - -class ProposalBudgetConstraint(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - min: Annotated[StrictFloat | None, Field(ge=0.0)] = None - max: Annotated[StrictFloat | None, Field(ge=0.0)] = None - currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] - - @model_validator(mode='after') - def _require_schema_required_group(self) -> ProposalBudgetConstraint: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('min',), ('max',),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'ProposalBudgetConstraint requires at least one of these field groups: min | max' - ) diff --git a/src/adcp/types/generated_poc/media_buy/proposal_decline.py b/src/adcp/types/generated_poc/media_buy/proposal_decline.py deleted file mode 100644 index 971962596..000000000 --- a/src/adcp/types/generated_poc/media_buy/proposal_decline.py +++ /dev/null @@ -1,35 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/proposal_decline.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..enums import proposal_decline_reason - - -class ProposalDecline(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - proposal_id: Annotated[ - str, - Field( - description='Immutable proposal ID returned by request_proposals or refine_proposals.', - max_length=255, - min_length=1, - ), - ] - reason: proposal_decline_reason.ProposalDeclineReason - detail: Annotated[ - str | None, - Field( - description='Optional short, non-identifying explanation. MUST NOT identify a competitor or disclose sensitive campaign information.', - max_length=500, - min_length=1, - ), - ] = None diff --git a/src/adcp/types/generated_poc/media_buy/proposal_refinement.py b/src/adcp/types/generated_poc/media_buy/proposal_refinement.py deleted file mode 100644 index 7370dfeca..000000000 --- a/src/adcp/types/generated_poc/media_buy/proposal_refinement.py +++ /dev/null @@ -1,583 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/proposal_refinement.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat - -from ..core import product_change_map -from . import product_discovery_criteria, proposal_budget_constraint - - -class Action(StrEnum): - revise = 'revise' - finalize = 'finalize' - - -class ChangeKind(StrEnum): - amendment = 'amendment' - cancellation = 'cancellation' - - -class Cpm(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - max: Annotated[StrictFloat, Field(gt=0.0)] - currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] - - -class Impressions(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - min: Annotated[StrictFloat, Field(gt=0.0)] - - -class Flight(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - start_no_later_than: AwareDatetime | None = None - end_no_earlier_than: AwareDatetime | None = None - - -class Constraints(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - total_budget: Annotated[ - proposal_budget_constraint.ProposalBudgetConstraint | None, - Field( - description='Inclusive bounds checked against commercial_terms.total_budget. Satisfied only when commercial_terms.total_budget is present, its currency matches, and its amount falls within every supplied bound.' - ), - ] = None - cpm: Annotated[ - Cpm | None, - Field( - description='Hard ceiling on fixed CPM rates. Satisfied only when every entry of commercial_terms.purchases carries pricing with pricing_model cpm or vcpm, a matching currency, and a fixed_price no greater than max. Auction-priced, missing, or non-CPM pricing on any purchase leaves the constraint unsatisfied.' - ), - ] = None - impressions: Annotated[ - Impressions | None, - Field( - description='Hard minimum contracted impression volume. Satisfied only when every entry of commercial_terms.purchases carries impressions and their sum is at least min.' - ), - ] = None - flight: Annotated[ - Flight | None, - Field( - description='Hard flight-window bounds checked against commercial_terms.start_time and end_time. start_no_later_than is satisfied only by a concrete start_time at or before the bound; an asap start is unverifiable and therefore unsatisfied. end_no_earlier_than is satisfied only by an end_time at or after the bound.' - ), - ] = None - - -class Alternatives(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - count: Annotated[ - SchemaInt, - Field( - description="Number of draft alternatives requested. The protocol maximum is 10. Buyers MUST NOT exceed a seller's lower advertised proposal_refinement.max_alternatives; sellers reject an excessive count at task level with VALIDATION_ERROR rather than clamping it.", - ge=2, - le=10, - ), - ] - - -class Constraints1(Constraints): - pass - - -class Constraints2(Constraints): - pass - - -class Constraints3(Constraints): - pass - - -class Constraints4(Constraints): - pass - - -class Constraints5(Constraints): - pass - - -class Constraints6(Constraints): - pass - - -class Constraints7(Constraints): - pass - - -class ProposalRefinement2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - proposal_id: Annotated[str, Field(min_length=1)] - action: Annotated[ - Literal['finalize'], - Field( - description='revise creates a new draft snapshot with changed commercial terms. finalize MUST target a draft; it creates a new committed snapshot without changing terms and reserves inventory until its expires_at.' - ), - ] = 'finalize' - change_kind: Annotated[ - ChangeKind | None, - Field( - description='Desired successor proposal. cancellation is valid only when the source proposal is accepted and attached to a non-terminal MediaBuy.' - ), - ] = ChangeKind.amendment - constraints: Annotated[ - Constraints | None, - Field( - description='Hard requirements for every revised proposal. Keys are echoed in unsatisfied_constraints on partial or unable results. A draft that fails any present constraint MUST NOT be returned as revised: the result is partial or unable with reason_code constraint_unsatisfiable — which takes precedence over every other reason code — and the failing keys in unsatisfied_constraints.' - ), - ] = None - product_changes: Annotated[ - product_change_map.ProductChangeMap | None, - Field( - description='Product IDs mapped to membership actions checked against commercial_terms.purchases. include requires presence; omit requires absence. The verbs match legacy get_products refinement.' - ), - ] = None - alternatives: Annotated[ - Alternatives | None, - Field( - description='Request drafts with distinct commercial terms by count. Every returned alternative MUST carry distinct commercial_terms and therefore a unique terms_digest; diversity preferences remain in ask.' - ), - ] = None - ask: Annotated[ - str | None, - Field( - description='What the buyer is asking for on this proposal, or the cancellation reason for action revise. The seller returns a new draft proposal snapshot; this text never directly mutates the source proposal or MediaBuy.', - min_length=1, - ), - ] = None - criteria: Annotated[ - product_discovery_criteria.ProductDiscoveryCriteria | None, - Field( - description='Structured discovery changes for action revise. Each present field replaces that criterion from the source proposal; omitted fields inherit. Concrete targeting, future targeting support, and offer filters belong here rather than being repeated in ask.' - ), - ] = None - remove_media_buy_frequency_cap: Annotated[ - Literal[True] | None, - Field( - description='Request a revised draft whose commercial terms omit the existing MediaBuy frequency cap. Omission inherits it. To replace it, provide criteria.media_buy_frequency_cap instead.' - ), - ] = None - - -class ProposalRefinement3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - proposal_id: Annotated[str, Field(min_length=1)] - action: Annotated[ - Literal['revise'], - Field( - description='revise creates a new draft snapshot with changed commercial terms. finalize MUST target a draft; it creates a new committed snapshot without changing terms and reserves inventory until its expires_at.' - ), - ] = 'revise' - change_kind: Annotated[ - ChangeKind | None, - Field( - description='Desired successor proposal. cancellation is valid only when the source proposal is accepted and attached to a non-terminal MediaBuy.' - ), - ] = ChangeKind.amendment - constraints: Annotated[ - Constraints1, - Field( - description='Hard requirements for every revised proposal. Keys are echoed in unsatisfied_constraints on partial or unable results. A draft that fails any present constraint MUST NOT be returned as revised: the result is partial or unable with reason_code constraint_unsatisfiable — which takes precedence over every other reason code — and the failing keys in unsatisfied_constraints.' - ), - ] - product_changes: Annotated[ - product_change_map.ProductChangeMap | None, - Field( - description='Product IDs mapped to membership actions checked against commercial_terms.purchases. include requires presence; omit requires absence. The verbs match legacy get_products refinement.' - ), - ] = None - alternatives: Annotated[ - Alternatives | None, - Field( - description='Request drafts with distinct commercial terms by count. Every returned alternative MUST carry distinct commercial_terms and therefore a unique terms_digest; diversity preferences remain in ask.' - ), - ] = None - ask: Annotated[ - str | None, - Field( - description='What the buyer is asking for on this proposal, or the cancellation reason for action revise. The seller returns a new draft proposal snapshot; this text never directly mutates the source proposal or MediaBuy.', - min_length=1, - ), - ] = None - criteria: Annotated[ - product_discovery_criteria.ProductDiscoveryCriteria | None, - Field( - description='Structured discovery changes for action revise. Each present field replaces that criterion from the source proposal; omitted fields inherit. Concrete targeting, future targeting support, and offer filters belong here rather than being repeated in ask.' - ), - ] = None - remove_media_buy_frequency_cap: Annotated[ - Literal[True] | None, - Field( - description='Request a revised draft whose commercial terms omit the existing MediaBuy frequency cap. Omission inherits it. To replace it, provide criteria.media_buy_frequency_cap instead.' - ), - ] = None - - -class ProposalRefinement4(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - proposal_id: Annotated[str, Field(min_length=1)] - action: Annotated[ - Literal['revise'], - Field( - description='revise creates a new draft snapshot with changed commercial terms. finalize MUST target a draft; it creates a new committed snapshot without changing terms and reserves inventory until its expires_at.' - ), - ] = 'revise' - change_kind: Annotated[ - ChangeKind | None, - Field( - description='Desired successor proposal. cancellation is valid only when the source proposal is accepted and attached to a non-terminal MediaBuy.' - ), - ] = ChangeKind.amendment - constraints: Annotated[ - Constraints2 | None, - Field( - description='Hard requirements for every revised proposal. Keys are echoed in unsatisfied_constraints on partial or unable results. A draft that fails any present constraint MUST NOT be returned as revised: the result is partial or unable with reason_code constraint_unsatisfiable — which takes precedence over every other reason code — and the failing keys in unsatisfied_constraints.' - ), - ] = None - product_changes: Annotated[ - product_change_map.ProductChangeMap, - Field( - description='Product IDs mapped to membership actions checked against commercial_terms.purchases. include requires presence; omit requires absence. The verbs match legacy get_products refinement.' - ), - ] - alternatives: Annotated[ - Alternatives | None, - Field( - description='Request drafts with distinct commercial terms by count. Every returned alternative MUST carry distinct commercial_terms and therefore a unique terms_digest; diversity preferences remain in ask.' - ), - ] = None - ask: Annotated[ - str | None, - Field( - description='What the buyer is asking for on this proposal, or the cancellation reason for action revise. The seller returns a new draft proposal snapshot; this text never directly mutates the source proposal or MediaBuy.', - min_length=1, - ), - ] = None - criteria: Annotated[ - product_discovery_criteria.ProductDiscoveryCriteria | None, - Field( - description='Structured discovery changes for action revise. Each present field replaces that criterion from the source proposal; omitted fields inherit. Concrete targeting, future targeting support, and offer filters belong here rather than being repeated in ask.' - ), - ] = None - remove_media_buy_frequency_cap: Annotated[ - Literal[True] | None, - Field( - description='Request a revised draft whose commercial terms omit the existing MediaBuy frequency cap. Omission inherits it. To replace it, provide criteria.media_buy_frequency_cap instead.' - ), - ] = None - - -class ProposalRefinement5(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - proposal_id: Annotated[str, Field(min_length=1)] - action: Annotated[ - Literal['revise'], - Field( - description='revise creates a new draft snapshot with changed commercial terms. finalize MUST target a draft; it creates a new committed snapshot without changing terms and reserves inventory until its expires_at.' - ), - ] = 'revise' - change_kind: Annotated[ - ChangeKind | None, - Field( - description='Desired successor proposal. cancellation is valid only when the source proposal is accepted and attached to a non-terminal MediaBuy.' - ), - ] = ChangeKind.amendment - constraints: Annotated[ - Constraints3 | None, - Field( - description='Hard requirements for every revised proposal. Keys are echoed in unsatisfied_constraints on partial or unable results. A draft that fails any present constraint MUST NOT be returned as revised: the result is partial or unable with reason_code constraint_unsatisfiable — which takes precedence over every other reason code — and the failing keys in unsatisfied_constraints.' - ), - ] = None - product_changes: Annotated[ - product_change_map.ProductChangeMap | None, - Field( - description='Product IDs mapped to membership actions checked against commercial_terms.purchases. include requires presence; omit requires absence. The verbs match legacy get_products refinement.' - ), - ] = None - alternatives: Annotated[ - Alternatives, - Field( - description='Request drafts with distinct commercial terms by count. Every returned alternative MUST carry distinct commercial_terms and therefore a unique terms_digest; diversity preferences remain in ask.' - ), - ] - ask: Annotated[ - str | None, - Field( - description='What the buyer is asking for on this proposal, or the cancellation reason for action revise. The seller returns a new draft proposal snapshot; this text never directly mutates the source proposal or MediaBuy.', - min_length=1, - ), - ] = None - criteria: Annotated[ - product_discovery_criteria.ProductDiscoveryCriteria | None, - Field( - description='Structured discovery changes for action revise. Each present field replaces that criterion from the source proposal; omitted fields inherit. Concrete targeting, future targeting support, and offer filters belong here rather than being repeated in ask.' - ), - ] = None - remove_media_buy_frequency_cap: Annotated[ - Literal[True] | None, - Field( - description='Request a revised draft whose commercial terms omit the existing MediaBuy frequency cap. Omission inherits it. To replace it, provide criteria.media_buy_frequency_cap instead.' - ), - ] = None - - -class ProposalRefinement6(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - proposal_id: Annotated[str, Field(min_length=1)] - action: Annotated[ - Literal['revise'], - Field( - description='revise creates a new draft snapshot with changed commercial terms. finalize MUST target a draft; it creates a new committed snapshot without changing terms and reserves inventory until its expires_at.' - ), - ] = 'revise' - change_kind: Annotated[ - ChangeKind | None, - Field( - description='Desired successor proposal. cancellation is valid only when the source proposal is accepted and attached to a non-terminal MediaBuy.' - ), - ] = ChangeKind.amendment - constraints: Annotated[ - Constraints4 | None, - Field( - description='Hard requirements for every revised proposal. Keys are echoed in unsatisfied_constraints on partial or unable results. A draft that fails any present constraint MUST NOT be returned as revised: the result is partial or unable with reason_code constraint_unsatisfiable — which takes precedence over every other reason code — and the failing keys in unsatisfied_constraints.' - ), - ] = None - product_changes: Annotated[ - product_change_map.ProductChangeMap | None, - Field( - description='Product IDs mapped to membership actions checked against commercial_terms.purchases. include requires presence; omit requires absence. The verbs match legacy get_products refinement.' - ), - ] = None - alternatives: Annotated[ - Alternatives | None, - Field( - description='Request drafts with distinct commercial terms by count. Every returned alternative MUST carry distinct commercial_terms and therefore a unique terms_digest; diversity preferences remain in ask.' - ), - ] = None - ask: Annotated[ - str, - Field( - description='What the buyer is asking for on this proposal, or the cancellation reason for action revise. The seller returns a new draft proposal snapshot; this text never directly mutates the source proposal or MediaBuy.', - min_length=1, - ), - ] - criteria: Annotated[ - product_discovery_criteria.ProductDiscoveryCriteria | None, - Field( - description='Structured discovery changes for action revise. Each present field replaces that criterion from the source proposal; omitted fields inherit. Concrete targeting, future targeting support, and offer filters belong here rather than being repeated in ask.' - ), - ] = None - remove_media_buy_frequency_cap: Annotated[ - Literal[True] | None, - Field( - description='Request a revised draft whose commercial terms omit the existing MediaBuy frequency cap. Omission inherits it. To replace it, provide criteria.media_buy_frequency_cap instead.' - ), - ] = None - - -class ProposalRefinement7(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - proposal_id: Annotated[str, Field(min_length=1)] - action: Annotated[ - Literal['revise'], - Field( - description='revise creates a new draft snapshot with changed commercial terms. finalize MUST target a draft; it creates a new committed snapshot without changing terms and reserves inventory until its expires_at.' - ), - ] = 'revise' - change_kind: Annotated[ - ChangeKind | None, - Field( - description='Desired successor proposal. cancellation is valid only when the source proposal is accepted and attached to a non-terminal MediaBuy.' - ), - ] = ChangeKind.amendment - constraints: Annotated[ - Constraints5 | None, - Field( - description='Hard requirements for every revised proposal. Keys are echoed in unsatisfied_constraints on partial or unable results. A draft that fails any present constraint MUST NOT be returned as revised: the result is partial or unable with reason_code constraint_unsatisfiable — which takes precedence over every other reason code — and the failing keys in unsatisfied_constraints.' - ), - ] = None - product_changes: Annotated[ - product_change_map.ProductChangeMap | None, - Field( - description='Product IDs mapped to membership actions checked against commercial_terms.purchases. include requires presence; omit requires absence. The verbs match legacy get_products refinement.' - ), - ] = None - alternatives: Annotated[ - Alternatives | None, - Field( - description='Request drafts with distinct commercial terms by count. Every returned alternative MUST carry distinct commercial_terms and therefore a unique terms_digest; diversity preferences remain in ask.' - ), - ] = None - ask: Annotated[ - str | None, - Field( - description='What the buyer is asking for on this proposal, or the cancellation reason for action revise. The seller returns a new draft proposal snapshot; this text never directly mutates the source proposal or MediaBuy.', - min_length=1, - ), - ] = None - criteria: Annotated[ - product_discovery_criteria.ProductDiscoveryCriteria, - Field( - description='Structured discovery changes for action revise. Each present field replaces that criterion from the source proposal; omitted fields inherit. Concrete targeting, future targeting support, and offer filters belong here rather than being repeated in ask.' - ), - ] - remove_media_buy_frequency_cap: Annotated[ - Literal[True] | None, - Field( - description='Request a revised draft whose commercial terms omit the existing MediaBuy frequency cap. Omission inherits it. To replace it, provide criteria.media_buy_frequency_cap instead.' - ), - ] = None - - -class ProposalRefinement8(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - proposal_id: Annotated[str, Field(min_length=1)] - action: Annotated[ - Literal['revise'], - Field( - description='revise creates a new draft snapshot with changed commercial terms. finalize MUST target a draft; it creates a new committed snapshot without changing terms and reserves inventory until its expires_at.' - ), - ] = 'revise' - change_kind: Annotated[ - ChangeKind | None, - Field( - description='Desired successor proposal. cancellation is valid only when the source proposal is accepted and attached to a non-terminal MediaBuy.' - ), - ] = ChangeKind.amendment - constraints: Annotated[ - Constraints6 | None, - Field( - description='Hard requirements for every revised proposal. Keys are echoed in unsatisfied_constraints on partial or unable results. A draft that fails any present constraint MUST NOT be returned as revised: the result is partial or unable with reason_code constraint_unsatisfiable — which takes precedence over every other reason code — and the failing keys in unsatisfied_constraints.' - ), - ] = None - product_changes: Annotated[ - product_change_map.ProductChangeMap | None, - Field( - description='Product IDs mapped to membership actions checked against commercial_terms.purchases. include requires presence; omit requires absence. The verbs match legacy get_products refinement.' - ), - ] = None - alternatives: Annotated[ - Alternatives | None, - Field( - description='Request drafts with distinct commercial terms by count. Every returned alternative MUST carry distinct commercial_terms and therefore a unique terms_digest; diversity preferences remain in ask.' - ), - ] = None - ask: Annotated[ - str | None, - Field( - description='What the buyer is asking for on this proposal, or the cancellation reason for action revise. The seller returns a new draft proposal snapshot; this text never directly mutates the source proposal or MediaBuy.', - min_length=1, - ), - ] = None - criteria: Annotated[ - product_discovery_criteria.ProductDiscoveryCriteria | None, - Field( - description='Structured discovery changes for action revise. Each present field replaces that criterion from the source proposal; omitted fields inherit. Concrete targeting, future targeting support, and offer filters belong here rather than being repeated in ask.' - ), - ] = None - remove_media_buy_frequency_cap: Annotated[ - Literal[True], - Field( - description='Request a revised draft whose commercial terms omit the existing MediaBuy frequency cap. Omission inherits it. To replace it, provide criteria.media_buy_frequency_cap instead.' - ), - ] - - -class ProposalRefinement9(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - proposal_id: Annotated[str, Field(min_length=1)] - action: Annotated[ - Literal['revise'], - Field( - description='revise creates a new draft snapshot with changed commercial terms. finalize MUST target a draft; it creates a new committed snapshot without changing terms and reserves inventory until its expires_at.' - ), - ] = 'revise' - change_kind: Annotated[ - Literal['cancellation'], - Field( - description='Desired successor proposal. cancellation is valid only when the source proposal is accepted and attached to a non-terminal MediaBuy.' - ), - ] = 'cancellation' - constraints: Annotated[ - Constraints7 | None, - Field( - description='Hard requirements for every revised proposal. Keys are echoed in unsatisfied_constraints on partial or unable results. A draft that fails any present constraint MUST NOT be returned as revised: the result is partial or unable with reason_code constraint_unsatisfiable — which takes precedence over every other reason code — and the failing keys in unsatisfied_constraints.' - ), - ] = None - product_changes: Annotated[ - product_change_map.ProductChangeMap | None, - Field( - description='Product IDs mapped to membership actions checked against commercial_terms.purchases. include requires presence; omit requires absence. The verbs match legacy get_products refinement.' - ), - ] = None - alternatives: Annotated[ - Alternatives | None, - Field( - description='Request drafts with distinct commercial terms by count. Every returned alternative MUST carry distinct commercial_terms and therefore a unique terms_digest; diversity preferences remain in ask.' - ), - ] = None - ask: Annotated[ - str | None, - Field( - description='What the buyer is asking for on this proposal, or the cancellation reason for action revise. The seller returns a new draft proposal snapshot; this text never directly mutates the source proposal or MediaBuy.', - min_length=1, - ), - ] = None - criteria: Annotated[ - product_discovery_criteria.ProductDiscoveryCriteria | None, - Field( - description='Structured discovery changes for action revise. Each present field replaces that criterion from the source proposal; omitted fields inherit. Concrete targeting, future targeting support, and offer filters belong here rather than being repeated in ask.' - ), - ] = None - remove_media_buy_frequency_cap: Annotated[ - Literal[True] | None, - Field( - description='Request a revised draft whose commercial terms omit the existing MediaBuy frequency cap. Omission inherits it. To replace it, provide criteria.media_buy_frequency_cap instead.' - ), - ] = None - - -ProposalRefinement = Annotated[ - ProposalRefinement2 - | ProposalRefinement3 - | ProposalRefinement4 - | ProposalRefinement5 - | ProposalRefinement6 - | ProposalRefinement7 - | ProposalRefinement8 - | ProposalRefinement9, - Field( - description='Fork an immutable proposal or finalize a draft into a held committed snapshot. Revising with structured criteria, typed boundaries, product changes, requested alternatives, and/or a semantic ask creates one or more new drafts; finalizing changes no terms. Refining an accepted proposal creates a draft amendment or cancellation proposal against its MediaBuy; the source remains accepted and unchanged. A revised product mix must support the resulting root frequency cap; because omitted criteria inherit, clearing the cap is explicit through remove_media_buy_frequency_cap.', - title='Proposal Refinement', - ), -] diff --git a/src/adcp/types/generated_poc/media_buy/provide_performance_feedback_request.py b/src/adcp/types/generated_poc/media_buy/provide_performance_feedback_request.py deleted file mode 100644 index ef0b8e7d6..000000000 --- a/src/adcp/types/generated_poc/media_buy/provide_performance_feedback_request.py +++ /dev/null @@ -1,31 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/provide_performance_feedback_request.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import ConfigDict, Field - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.performance_feedback_assertion import PerformanceFeedbackAssertion -from ..core.version_envelope import AdcpVersionEnvelope - - -class ProvidePerformanceFeedbackRequest(AdcpVersionEnvelope, PerformanceFeedbackAssertion): - model_config = ConfigDict( - extra='allow', - ) - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for this logical assertion. MUST be unique per receiving agent to prevent cross-agent correlation; use a fresh UUID v4 for each new assertion. Retries use the same key and payload.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/provide_performance_feedback_response.py b/src/adcp/types/generated_poc/media_buy/provide_performance_feedback_response.py deleted file mode 100644 index 865af640b..000000000 --- a/src/adcp/types/generated_poc/media_buy/provide_performance_feedback_response.py +++ /dev/null @@ -1,45 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/provide_performance_feedback_response.json -# timestamp: preserved-by-post-generate-fixes - -from __future__ import annotations - -from typing import Annotated, Literal, TypeAlias - -from pydantic import AwareDatetime, ConfigDict, Field, StringConstraints - -from ..core.version_envelope import AdcpVersionEnvelope -from ..core import context as context_1 -from ..core import error as error_1 -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope - - -class ProvidePerformanceFeedbackResponse1(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - success: Literal[True] - feedback_id: Annotated[str, StringConstraints(min_length=1)] | None = None - application_status: Literal['accepted', 'applied', 'not_applied'] | None = None - status_reason: Annotated[str, StringConstraints(max_length=500)] | None = None - received_at: AwareDatetime | None = None - applied_at: AwareDatetime | None = None - sandbox: bool | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class ProvidePerformanceFeedbackResponse2(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - errors: Annotated[list[error_1.Error], Field(min_length=1)] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -ProvidePerformanceFeedbackResponse: TypeAlias = ProvidePerformanceFeedbackResponse1 | ProvidePerformanceFeedbackResponse2 - - -__all__ = [ - 'ProvidePerformanceFeedbackResponse', - 'ProvidePerformanceFeedbackResponse1', - 'ProvidePerformanceFeedbackResponse2', -] diff --git a/src/adcp/types/generated_poc/media_buy/refine_proposals_async_response_input_required.py b/src/adcp/types/generated_poc/media_buy/refine_proposals_async_response_input_required.py deleted file mode 100644 index 41f47bcb5..000000000 --- a/src/adcp/types/generated_poc/media_buy/refine_proposals_async_response_input_required.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/refine_proposals_async_response_input_required.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from ..core.compact_task_input_required import CompactTaskInputRequired - - -class RefineProposalsInputRequired(CompactTaskInputRequired): - pass diff --git a/src/adcp/types/generated_poc/media_buy/refine_proposals_async_response_submitted.py b/src/adcp/types/generated_poc/media_buy/refine_proposals_async_response_submitted.py deleted file mode 100644 index a21e49ec9..000000000 --- a/src/adcp/types/generated_poc/media_buy/refine_proposals_async_response_submitted.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/refine_proposals_async_response_submitted.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from ..core.compact_task_submitted import CompactTaskSubmitted - - -class RefineProposalsSubmitted(CompactTaskSubmitted): - pass diff --git a/src/adcp/types/generated_poc/media_buy/refine_proposals_async_response_working.py b/src/adcp/types/generated_poc/media_buy/refine_proposals_async_response_working.py deleted file mode 100644 index b885821a3..000000000 --- a/src/adcp/types/generated_poc/media_buy/refine_proposals_async_response_working.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/refine_proposals_async_response_working.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from ..core.compact_task_working import CompactTaskWorking - - -class RefineProposalsWorking(CompactTaskWorking): - pass diff --git a/src/adcp/types/generated_poc/media_buy/refine_proposals_request.py b/src/adcp/types/generated_poc/media_buy/refine_proposals_request.py deleted file mode 100644 index d44e7ad12..000000000 --- a/src/adcp/types/generated_poc/media_buy/refine_proposals_request.py +++ /dev/null @@ -1,68 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/refine_proposals_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field - -from ..core import context as context_1 -from ..core import push_notification_config as push_notification_config_1 -from . import proposal_refinement - - -class Refinements(AdCPBaseModel): - pass - - -class RefineProposalsRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?)?$', - ), - ] = None - adcp_major_version: Annotated[ - SchemaInt | None, - Field( - deprecated=True, - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='MCP compatibility field: servers ignore this value; A2A uses transport-native Message/Task contextId.', - min_length=1, - ), - ] = None - context: context_1.ContextObject | None = None - governance_context: Annotated[str | None, Field(max_length=4096, min_length=1)] = None - push_notification_config: push_notification_config_1.PushNotificationConfig | None = None - idempotency_key: Annotated[ - str, - Field( - description='Client-generated key required for retry-safe proposal refinement.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - refinements: Annotated[ - list[proposal_refinement.ProposalRefinement] | Refinements, - Field( - description='Proposal operations to apply, with at most 25 entries per request. revise creates a draft successor from a draft, committed, or accepted source. finalize MUST target a draft, reserves inventory, and creates a committed successor whose expires_at is the hold deadline. A batch containing finalize MUST contain only finalize entries and is atomic. proposal_id values MUST be unique; results preserve request order.', - max_length=25, - min_length=1, - ), - ] diff --git a/src/adcp/types/generated_poc/media_buy/refine_proposals_response.py b/src/adcp/types/generated_poc/media_buy/refine_proposals_response.py deleted file mode 100644 index 0047a379d..000000000 --- a/src/adcp/types/generated_poc/media_buy/refine_proposals_response.py +++ /dev/null @@ -1,622 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/refine_proposals_response.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarStr -from pydantic import AwareDatetime, ConfigDict, Field, StrictFloat - -from ..core import canonical_delivery_forecast, canonical_product, canonical_proposal -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 -from ..core import insertion_order as insertion_order_1 -from ..core import product_change_map -from ..core.compact_task_submitted import CompactTaskSubmitted -from ..enums import proposal_refinement_reason -from . import commercial_terms as commercial_terms_1 -from . import get_products_targeting_resolution - - -class Outcome(StrEnum): - revised = 'revised' - partial = 'partial' - finalized = 'finalized' - unable = 'unable' - - -class ProposalKind(StrEnum): - new_media_buy = 'new_media_buy' - media_buy_update = 'media_buy_update' - media_buy_cancellation = 'media_buy_cancellation' - - -class ProposalStatus(StrEnum): - draft = 'draft' - committed = 'committed' - accepted = 'accepted' - - -class TotalBudgetGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - min: Annotated[StrictFloat | None, Field(ge=0.0)] = None - recommended: Annotated[StrictFloat | None, Field(ge=0.0)] = None - max: Annotated[StrictFloat | None, Field(ge=0.0)] = None - currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] - - -class UnsatisfiedConstraint(ScalarStr): - __slots__ = () - _constraints = {'min_length': 1} - - -class Suggestion(UnsatisfiedConstraint): - pass - - -class Results(AdCPBaseModel): - pass - - -class Status(StrEnum): - completed = 'completed' - submitted = 'submitted' - - -class Proposal(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - proposal_id: Annotated[str, Field(max_length=255, min_length=1)] - proposal_kind: ProposalKind - parent_proposal_id: Annotated[ - str, - Field( - description="Immediate predecessor this snapshot was forked from. Every proposal produced by refine_proposals carries it, equal to the request's source proposal_id, so negotiation lineage is reconstructible from proposals alone.", - max_length=255, - min_length=1, - ), - ] - media_buy_id: Annotated[str | None, Field(min_length=1)] = None - opportunity_id: Annotated[ - str | None, - Field( - description='Buyer planning cycle associated with this proposal. Revisions inherit it; it does not participate in proposal identity.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - base_media_buy_revision: Annotated[SchemaInt | None, Field(ge=1)] = None - proposal_status: Annotated[ - Literal['draft'], - Field( - description='draft is indicative and unreserved; committed has firm terms with inventory reserved until expires_at; accepted is the historical snapshot attached to a MediaBuy.', - title='Proposal Status', - ), - ] = 'draft' - accepted_at: AwareDatetime | None = None - expires_at: Annotated[ - AwareDatetime | None, - Field( - description='For a draft, the indicative-terms freshness deadline. For a committed proposal, the inventory-hold deadline.' - ), - ] = None - name: Annotated[str, Field(max_length=500, min_length=1)] - description: Annotated[str | None, Field(max_length=2000)] = None - brief_alignment: Annotated[str | None, Field(max_length=2000)] = None - commercial_terms: commercial_terms_1.CommercialTerms - terms_digest: Annotated[ - str, - Field( - description='Base64url SHA-256 digest of the RFC 8785 JCS serialization of commercial_terms, prefixed with sha256:.', - pattern='^sha256:[A-Za-z0-9_-]{43}$', - ), - ] - insertion_order: insertion_order_1.InsertionOrder | None = None - total_budget_guidance: Annotated[ - TotalBudgetGuidance | None, - Field( - description="Optional budget guidance for this proposal — the planning answer to criteria.outcome_target and to open-budget briefs. commercial_terms.total_budget remains the concrete figure the plan is priced at; this band expresses the seller's recommended range around it. When criteria.outcome_target carries cost_per, the cost answer is commercial_terms.bidding.cost_per and this band's currency equals cost_per.currency." - ), - ] = None - forecast: Annotated[ - canonical_delivery_forecast.CanonicalDeliveryForecast | None, - Field( - description="Aggregate forecasted delivery for the proposal. For outcome_target requests, points carry the goal's metric or event key in metrics; with cost_per, that is the goal volume planned under the commercial_terms.bidding policy, and currency equals cost_per.currency." - ), - ] = None - - -class Proposal2(Proposal): - pass - - -class Proposal3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - proposal_id: Annotated[str, Field(max_length=255, min_length=1)] - proposal_kind: ProposalKind - parent_proposal_id: Annotated[ - str, - Field( - description="Immediate predecessor this snapshot was forked from. Every proposal produced by refine_proposals carries it, equal to the request's source proposal_id, so negotiation lineage is reconstructible from proposals alone.", - max_length=255, - min_length=1, - ), - ] - media_buy_id: Annotated[str | None, Field(min_length=1)] = None - opportunity_id: Annotated[ - str | None, - Field( - description='Buyer planning cycle associated with this proposal. Revisions inherit it; it does not participate in proposal identity.', - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - base_media_buy_revision: Annotated[SchemaInt | None, Field(ge=1)] = None - proposal_status: Annotated[ - Literal['committed'], - Field( - description='draft is indicative and unreserved; committed has firm terms with inventory reserved until expires_at; accepted is the historical snapshot attached to a MediaBuy.', - title='Proposal Status', - ), - ] = 'committed' - accepted_at: AwareDatetime | None = None - expires_at: Annotated[ - AwareDatetime, - Field( - description='For a draft, the indicative-terms freshness deadline. For a committed proposal, the inventory-hold deadline.' - ), - ] - name: Annotated[str, Field(max_length=500, min_length=1)] - description: Annotated[str | None, Field(max_length=2000)] = None - brief_alignment: Annotated[str | None, Field(max_length=2000)] = None - commercial_terms: commercial_terms_1.CommercialTerms - terms_digest: Annotated[ - str, - Field( - description='Base64url SHA-256 digest of the RFC 8785 JCS serialization of commercial_terms, prefixed with sha256:.', - pattern='^sha256:[A-Za-z0-9_-]{43}$', - ), - ] - insertion_order: insertion_order_1.InsertionOrder | None = None - total_budget_guidance: Annotated[ - TotalBudgetGuidance | None, - Field( - description="Optional budget guidance for this proposal — the planning answer to criteria.outcome_target and to open-budget briefs. commercial_terms.total_budget remains the concrete figure the plan is priced at; this band expresses the seller's recommended range around it. When criteria.outcome_target carries cost_per, the cost answer is commercial_terms.bidding.cost_per and this band's currency equals cost_per.currency." - ), - ] = None - forecast: Annotated[ - canonical_delivery_forecast.CanonicalDeliveryForecast | None, - Field( - description="Aggregate forecasted delivery for the proposal. For outcome_target requests, points carry the goal's metric or event key in metrics; with cost_per, that is the goal volume planned under the commercial_terms.bidding policy, and currency equals cost_per.currency." - ), - ] = None - - -class Proposal4(Proposal): - pass - - -class Proposal5(Proposal): - pass - - -class Proposal6(Proposal3): - pass - - -class Results9(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - source_proposal_id: Annotated[str, Field(min_length=1)] - outcome: Literal['revised'] = 'revised' - proposal: canonical_proposal.CanonicalProposal | None = None - proposals: Annotated[ - list[Proposal], - Field( - description='Draft successors produced for a revision. Without alternatives this contains one proposal. With alternatives.count, revised contains exactly that many proposals with unique terms_digest values; fewer or commercially duplicate proposals require partial.', - min_length=1, - ), - ] - reason_code: Annotated[ - proposal_refinement_reason.ProposalRefinementReason | None, - Field( - description='Single most-significant code for this result. constraint_unsatisfiable takes precedence over every other code; an alternatives shortfall alongside an unsatisfied constraint remains visible through proposals.length.' - ), - ] = None - reason: Annotated[str | None, Field(min_length=1)] = None - unsatisfied_constraints: Annotated[ - list[UnsatisfiedConstraint] | None, - Field( - description='Stable keys from the request constraints object that were not satisfied by every returned draft. A result carrying any key here MUST use outcome partial or unable, never revised.', - min_length=1, - ), - ] = None - unsatisfied_product_changes: Annotated[ - product_change_map.ProductChangeMap | None, - Field( - description='Requested product actions not satisfied by every returned draft. This is a subset of the request product_changes map and is valid only on partial or unable results.' - ), - ] = None - suggestions: Annotated[list[Suggestion] | None, Field(min_length=1)] = None - targeting_resolution: Annotated[ - get_products_targeting_resolution.ProductDiscoveryTargetingResolution | None, - Field( - description="Confirmation of structured targeting interpreted from this refinement's explicit hard prose instructions. Required when that interpretation materially affects the revised proposal's eligibility, pricing, or forecast; otherwise a best practice." - ), - ] = None - - -class Results10(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - source_proposal_id: Annotated[str, Field(min_length=1)] - outcome: Literal['partial'] = 'partial' - proposal: canonical_proposal.CanonicalProposal | None = None - proposals: Annotated[ - list[Proposal2], - Field( - description='Draft successors produced for a revision. Without alternatives this contains one proposal. With alternatives.count, revised contains exactly that many proposals with unique terms_digest values; fewer or commercially duplicate proposals require partial.', - min_length=1, - ), - ] - reason_code: Annotated[ - proposal_refinement_reason.ProposalRefinementReason, - Field( - description='Single most-significant code for this result. constraint_unsatisfiable takes precedence over every other code; an alternatives shortfall alongside an unsatisfied constraint remains visible through proposals.length.' - ), - ] - reason: Annotated[str, Field(min_length=1)] - unsatisfied_constraints: Annotated[ - list[UnsatisfiedConstraint] | None, - Field( - description='Stable keys from the request constraints object that were not satisfied by every returned draft. A result carrying any key here MUST use outcome partial or unable, never revised.', - min_length=1, - ), - ] = None - unsatisfied_product_changes: Annotated[ - product_change_map.ProductChangeMap | None, - Field( - description='Requested product actions not satisfied by every returned draft. This is a subset of the request product_changes map and is valid only on partial or unable results.' - ), - ] = None - suggestions: Annotated[list[Suggestion] | None, Field(min_length=1)] = None - targeting_resolution: Annotated[ - get_products_targeting_resolution.ProductDiscoveryTargetingResolution | None, - Field( - description="Confirmation of structured targeting interpreted from this refinement's explicit hard prose instructions. Required when that interpretation materially affects the revised proposal's eligibility, pricing, or forecast; otherwise a best practice." - ), - ] = None - - -class Results11(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - source_proposal_id: Annotated[str, Field(min_length=1)] - outcome: Literal['finalized'] = 'finalized' - proposal: Annotated[ - Proposal3, - Field( - description='Compact immutable proposal for the AdCP 3.2 lifecycle. commercial_terms is the sole authoritative commercial envelope; narrative fields do not duplicate legacy allocation or creative graphs.', - title='Canonical Proposal', - ), - ] - proposals: Annotated[ - list[canonical_proposal.CanonicalProposal] | None, - Field( - description='Draft successors produced for a revision. Without alternatives this contains one proposal. With alternatives.count, revised contains exactly that many proposals with unique terms_digest values; fewer or commercially duplicate proposals require partial.', - min_length=1, - ), - ] = None - reason_code: Annotated[ - proposal_refinement_reason.ProposalRefinementReason | None, - Field( - description='Single most-significant code for this result. constraint_unsatisfiable takes precedence over every other code; an alternatives shortfall alongside an unsatisfied constraint remains visible through proposals.length.' - ), - ] = None - reason: Annotated[str | None, Field(min_length=1)] = None - unsatisfied_constraints: Annotated[ - list[UnsatisfiedConstraint] | None, - Field( - description='Stable keys from the request constraints object that were not satisfied by every returned draft. A result carrying any key here MUST use outcome partial or unable, never revised.', - min_length=1, - ), - ] = None - unsatisfied_product_changes: Annotated[ - product_change_map.ProductChangeMap | None, - Field( - description='Requested product actions not satisfied by every returned draft. This is a subset of the request product_changes map and is valid only on partial or unable results.' - ), - ] = None - suggestions: Annotated[list[Suggestion] | None, Field(min_length=1)] = None - targeting_resolution: Annotated[ - get_products_targeting_resolution.ProductDiscoveryTargetingResolution | None, - Field( - description="Confirmation of structured targeting interpreted from this refinement's explicit hard prose instructions. Required when that interpretation materially affects the revised proposal's eligibility, pricing, or forecast; otherwise a best practice." - ), - ] = None - - -class Results12(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - source_proposal_id: Annotated[str, Field(min_length=1)] - outcome: Literal['unable'] = 'unable' - proposal: canonical_proposal.CanonicalProposal | None = None - proposals: Annotated[ - list[canonical_proposal.CanonicalProposal] | None, - Field( - description='Draft successors produced for a revision. Without alternatives this contains one proposal. With alternatives.count, revised contains exactly that many proposals with unique terms_digest values; fewer or commercially duplicate proposals require partial.', - min_length=1, - ), - ] = None - reason_code: Annotated[ - proposal_refinement_reason.ProposalRefinementReason, - Field( - description='Single most-significant code for this result. constraint_unsatisfiable takes precedence over every other code; an alternatives shortfall alongside an unsatisfied constraint remains visible through proposals.length.' - ), - ] - reason: Annotated[str, Field(min_length=1)] - unsatisfied_constraints: Annotated[ - list[UnsatisfiedConstraint] | None, - Field( - description='Stable keys from the request constraints object that were not satisfied by every returned draft. A result carrying any key here MUST use outcome partial or unable, never revised.', - min_length=1, - ), - ] = None - unsatisfied_product_changes: Annotated[ - product_change_map.ProductChangeMap | None, - Field( - description='Requested product actions not satisfied by every returned draft. This is a subset of the request product_changes map and is valid only on partial or unable results.' - ), - ] = None - suggestions: Annotated[list[Suggestion] | None, Field(min_length=1)] = None - targeting_resolution: Annotated[ - get_products_targeting_resolution.ProductDiscoveryTargetingResolution | None, - Field( - description="Confirmation of structured targeting interpreted from this refinement's explicit hard prose instructions. Required when that interpretation materially affects the revised proposal's eligibility, pricing, or forecast; otherwise a best practice." - ), - ] = None - - -Results8 = Annotated[ - Results9 | Results10 | Results11 | Results12, - Field(discriminator='outcome'), -] - - -class Results15(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - source_proposal_id: Annotated[str, Field(min_length=1)] - outcome: Literal['revised'] = 'revised' - proposal: canonical_proposal.CanonicalProposal | None = None - proposals: Annotated[ - list[Proposal4], - Field( - description='Draft successors produced for a revision. Without alternatives this contains one proposal. With alternatives.count, revised contains exactly that many proposals with unique terms_digest values; fewer or commercially duplicate proposals require partial.', - min_length=1, - ), - ] - reason_code: Annotated[ - proposal_refinement_reason.ProposalRefinementReason | None, - Field( - description='Single most-significant code for this result. constraint_unsatisfiable takes precedence over every other code; an alternatives shortfall alongside an unsatisfied constraint remains visible through proposals.length.' - ), - ] = None - reason: Annotated[str | None, Field(min_length=1)] = None - unsatisfied_constraints: Annotated[ - list[UnsatisfiedConstraint] | None, - Field( - description='Stable keys from the request constraints object that were not satisfied by every returned draft. A result carrying any key here MUST use outcome partial or unable, never revised.', - min_length=1, - ), - ] = None - unsatisfied_product_changes: Annotated[ - product_change_map.ProductChangeMap | None, - Field( - description='Requested product actions not satisfied by every returned draft. This is a subset of the request product_changes map and is valid only on partial or unable results.' - ), - ] = None - suggestions: Annotated[list[Suggestion] | None, Field(min_length=1)] = None - targeting_resolution: Annotated[ - get_products_targeting_resolution.ProductDiscoveryTargetingResolution | None, - Field( - description="Confirmation of structured targeting interpreted from this refinement's explicit hard prose instructions. Required when that interpretation materially affects the revised proposal's eligibility, pricing, or forecast; otherwise a best practice." - ), - ] = None - - -class Results16(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - source_proposal_id: Annotated[str, Field(min_length=1)] - outcome: Literal['partial'] = 'partial' - proposal: canonical_proposal.CanonicalProposal | None = None - proposals: Annotated[ - list[Proposal5], - Field( - description='Draft successors produced for a revision. Without alternatives this contains one proposal. With alternatives.count, revised contains exactly that many proposals with unique terms_digest values; fewer or commercially duplicate proposals require partial.', - min_length=1, - ), - ] - reason_code: Annotated[ - proposal_refinement_reason.ProposalRefinementReason, - Field( - description='Single most-significant code for this result. constraint_unsatisfiable takes precedence over every other code; an alternatives shortfall alongside an unsatisfied constraint remains visible through proposals.length.' - ), - ] - reason: Annotated[str, Field(min_length=1)] - unsatisfied_constraints: Annotated[ - list[UnsatisfiedConstraint] | None, - Field( - description='Stable keys from the request constraints object that were not satisfied by every returned draft. A result carrying any key here MUST use outcome partial or unable, never revised.', - min_length=1, - ), - ] = None - unsatisfied_product_changes: Annotated[ - product_change_map.ProductChangeMap | None, - Field( - description='Requested product actions not satisfied by every returned draft. This is a subset of the request product_changes map and is valid only on partial or unable results.' - ), - ] = None - suggestions: Annotated[list[Suggestion] | None, Field(min_length=1)] = None - targeting_resolution: Annotated[ - get_products_targeting_resolution.ProductDiscoveryTargetingResolution | None, - Field( - description="Confirmation of structured targeting interpreted from this refinement's explicit hard prose instructions. Required when that interpretation materially affects the revised proposal's eligibility, pricing, or forecast; otherwise a best practice." - ), - ] = None - - -class Results17(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - source_proposal_id: Annotated[str, Field(min_length=1)] - outcome: Literal['finalized'] = 'finalized' - proposal: Annotated[ - Proposal6, - Field( - description='Compact immutable proposal for the AdCP 3.2 lifecycle. commercial_terms is the sole authoritative commercial envelope; narrative fields do not duplicate legacy allocation or creative graphs.', - title='Canonical Proposal', - ), - ] - proposals: Annotated[ - list[canonical_proposal.CanonicalProposal] | None, - Field( - description='Draft successors produced for a revision. Without alternatives this contains one proposal. With alternatives.count, revised contains exactly that many proposals with unique terms_digest values; fewer or commercially duplicate proposals require partial.', - min_length=1, - ), - ] = None - reason_code: Annotated[ - proposal_refinement_reason.ProposalRefinementReason | None, - Field( - description='Single most-significant code for this result. constraint_unsatisfiable takes precedence over every other code; an alternatives shortfall alongside an unsatisfied constraint remains visible through proposals.length.' - ), - ] = None - reason: Annotated[str | None, Field(min_length=1)] = None - unsatisfied_constraints: Annotated[ - list[UnsatisfiedConstraint] | None, - Field( - description='Stable keys from the request constraints object that were not satisfied by every returned draft. A result carrying any key here MUST use outcome partial or unable, never revised.', - min_length=1, - ), - ] = None - unsatisfied_product_changes: Annotated[ - product_change_map.ProductChangeMap | None, - Field( - description='Requested product actions not satisfied by every returned draft. This is a subset of the request product_changes map and is valid only on partial or unable results.' - ), - ] = None - suggestions: Annotated[list[Suggestion] | None, Field(min_length=1)] = None - targeting_resolution: Annotated[ - get_products_targeting_resolution.ProductDiscoveryTargetingResolution | None, - Field( - description="Confirmation of structured targeting interpreted from this refinement's explicit hard prose instructions. Required when that interpretation materially affects the revised proposal's eligibility, pricing, or forecast; otherwise a best practice." - ), - ] = None - - -class Results18(Results12): - outcome: Literal['unable'] = 'unable' - - -Results14 = Annotated[ - Results15 | Results16 | Results17 | Results18, - Field(discriminator='outcome'), -] - - -class RefineProposalsResponse1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?)?$', - ), - ] = None - results: Annotated[ - list[Results8] | Results, - Field( - description="Ordered results. If any result is finalized, every result MUST be finalized; a finalize batch either creates every requested hold or none. Every returned proposal carries parent_proposal_id equal to the result's source_proposal_id, making negotiation lineage reconstructible from the proposals alone.", - min_length=1, - ), - ] - products: Annotated[ - list[canonical_product.CanonicalProduct], - Field( - description="Canonical products needed to evaluate the resulting terms. For revised or partial results whose effective criteria contain property or collection lists, each affected product MUST carry fresh list_applications receipts from the revision's product reevaluation. Finalization changes no terms and MAY repeat the receipts already bound to the source proposal rather than reevaluating them." - ), - ] - status: Literal['completed'] = 'completed' - task_id: Annotated[str | None, Field(min_length=1)] = None - message: Annotated[str | None, Field(max_length=2000)] = None - errors: list[error.Error] | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - replayed: Literal[True] | None = None - - -class RefineProposalsResponse2(CompactTaskSubmitted): - model_config = ConfigDict( - extra='forbid', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?)?$', - ), - ] = None - results: Annotated[ - list[Results14] | Results | None, - Field( - description="Ordered results. If any result is finalized, every result MUST be finalized; a finalize batch either creates every requested hold or none. Every returned proposal carries parent_proposal_id equal to the result's source_proposal_id, making negotiation lineage reconstructible from the proposals alone.", - min_length=1, - ), - ] = None - products: Annotated[ - list[canonical_product.CanonicalProduct] | None, - Field( - description="Canonical products needed to evaluate the resulting terms. For revised or partial results whose effective criteria contain property or collection lists, each affected product MUST carry fresh list_applications receipts from the revision's product reevaluation. Finalization changes no terms and MAY repeat the receipts already bound to the source proposal rather than reevaluating them." - ), - ] = None - status: Status | None = None - task_id: Annotated[str | None, Field(min_length=1)] = None - message: Annotated[str | None, Field(max_length=2000)] = None - errors: list[error.Error] | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - replayed: Literal[True] | None = None - - -RefineProposalsResponse = Annotated[ - RefineProposalsResponse1 | RefineProposalsResponse2, - Field( - description='One ordered result per requested source proposal. Revision results may carry multiple immutable draft proposals when alternatives were requested; finalization remains one committed proposal per source. Products contains the compact canonical products needed to evaluate the resulting terms.', - title='Refine Proposals Response', - ), -] diff --git a/src/adcp/types/generated_poc/media_buy/request_proposals_async_response_input_required.py b/src/adcp/types/generated_poc/media_buy/request_proposals_async_response_input_required.py deleted file mode 100644 index 217b29631..000000000 --- a/src/adcp/types/generated_poc/media_buy/request_proposals_async_response_input_required.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/request_proposals_async_response_input_required.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from ..core.compact_task_input_required import CompactTaskInputRequired - - -class RequestProposalsInputRequired(CompactTaskInputRequired): - pass diff --git a/src/adcp/types/generated_poc/media_buy/request_proposals_async_response_submitted.py b/src/adcp/types/generated_poc/media_buy/request_proposals_async_response_submitted.py deleted file mode 100644 index d24f29179..000000000 --- a/src/adcp/types/generated_poc/media_buy/request_proposals_async_response_submitted.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/request_proposals_async_response_submitted.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from ..core.compact_task_submitted import CompactTaskSubmitted - - -class RequestProposalsSubmitted(CompactTaskSubmitted): - pass diff --git a/src/adcp/types/generated_poc/media_buy/request_proposals_async_response_working.py b/src/adcp/types/generated_poc/media_buy/request_proposals_async_response_working.py deleted file mode 100644 index b9461e8d8..000000000 --- a/src/adcp/types/generated_poc/media_buy/request_proposals_async_response_working.py +++ /dev/null @@ -1,11 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/request_proposals_async_response_working.json -# timestamp: 2026-08-17T23:02:13+00:00 - -from __future__ import annotations - -from ..core.compact_task_working import CompactTaskWorking - - -class RequestProposalsWorking(CompactTaskWorking): - pass diff --git a/src/adcp/types/generated_poc/media_buy/request_proposals_request.py b/src/adcp/types/generated_poc/media_buy/request_proposals_request.py deleted file mode 100644 index 218ba0e09..000000000 --- a/src/adcp/types/generated_poc/media_buy/request_proposals_request.py +++ /dev/null @@ -1,112 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/request_proposals_request.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, model_validator - -from ..core import brand_key, canonical_account_ref -from ..core import context as context_1 -from ..core import push_notification_config as push_notification_config_1 -from ..core.opportunity_context import OpportunityContext -from . import product_discovery_criteria - - -class Status(StrEnum): - open = 'open' - closed = 'closed' - - -class Opportunity(OpportunityContext): - status: Annotated[ - Literal['open'], - Field( - description='Whether the planning cycle remains open. On calls after initial creation, omission means no status update and MUST NOT reopen or close the opportunity implicitly, except that successful create_media_buy proposal execution explicitly infers accepted closure.' - ), - ] = 'open' - - -class RequestProposalsRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?)?$', - ), - ] = None - adcp_major_version: Annotated[ - SchemaInt | None, - Field( - deprecated=True, - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='MCP compatibility field: servers ignore this value; A2A uses transport-native Message/Task contextId.', - min_length=1, - ), - ] = None - context: context_1.ContextObject | None = None - governance_context: Annotated[str | None, Field(max_length=4096, min_length=1)] = None - push_notification_config: push_notification_config_1.PushNotificationConfig | None = None - idempotency_key: Annotated[ - str, - Field( - description='Client-generated key required for retry-safe proposal creation.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - account: Annotated[ - canonical_account_ref.CanonicalAccountReference | None, - Field( - description='Alternative brand source for proposal terms. Provide either this natural-key account containing brand and operator or top-level brand, not both.' - ), - ] = None - brand: Annotated[ - brand_key.BrandKey | None, - Field( - description='Alternative brand source for proposal terms. Provide either top-level brand or a natural-key account containing brand and operator, not both.' - ), - ] = None - brief: Annotated[ - str, - Field( - description='Campaign goal, strategy, and requirements that are not represented in structured criteria.', - min_length=1, - ), - ] - criteria: product_discovery_criteria.ProductDiscoveryCriteria | None = None - opportunity: Annotated[ - Opportunity | None, - Field( - description='Optional planning-cycle context that the seller associates with every proposal created by this request.' - ), - ] = None - - @model_validator(mode='after') - def _require_schema_required_group(self) -> RequestProposalsRequest: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('brand',), ('account',),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'RequestProposalsRequest requires at least one of these field groups: brand | account' - ) diff --git a/src/adcp/types/generated_poc/media_buy/request_proposals_response.py b/src/adcp/types/generated_poc/media_buy/request_proposals_response.py deleted file mode 100644 index e05874c57..000000000 --- a/src/adcp/types/generated_poc/media_buy/request_proposals_response.py +++ /dev/null @@ -1,398 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/request_proposals_response.json -# timestamp: 2026-10-03T14:21:15+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import AwareDatetime, ConfigDict, Field - -from ..core import canonical_product -from ..core import context as context_1 -from ..core import duration, error -from ..core import ext as ext_1 -from ..core.canonical_proposal import CanonicalProposal -from ..core.compact_task_submitted import CompactTaskSubmitted -from . import get_products_targeting_resolution - - -class Outcome(StrEnum): - proposed = 'proposed' - products_available = 'products_available' - rejected = 'rejected' - - -class Suggestion(ScalarStr): - __slots__ = () - _constraints = {'min_length': 1} - - -class ProposalStatus(StrEnum): - draft = 'draft' - committed = 'committed' - accepted = 'accepted' - - -class Scope(StrEnum): - products = 'products' - pricing = 'pricing' - forecast = 'forecast' - proposals = 'proposals' - wholesale_feed = 'wholesale_feed' - - -class IncompleteItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Scope - description: Annotated[str, Field(min_length=1)] - estimated_wait: duration.Duration | None = None - - -class ProductId(Suggestion): - pass - - -class PurchaseContinuation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['listed_purchase'] = 'listed_purchase' - product_ids: Annotated[ - list[ProductId], - Field( - description='Exact products the coordinator promoted and re-read through account-scoped list_products before returning this result. The set MUST match products[].product_id.', - min_length=1, - ), - ] - cache_scope: Literal['account'] = 'account' - feed_version: Annotated[ - str, - Field( - description='Real seller-issued account-scoped feed fence obtained by re-reading the promoted products through list_products.', - min_length=1, - ), - ] - pricing_version: Annotated[ - str | None, - Field( - description='Real seller-issued pricing fence from the same account-scoped list_products response, when the seller versions pricing independently.', - min_length=1, - ), - ] = None - - -class SourceAdcpVersion(StrEnum): - field_2_5 = '2.5' - field_3_0 = '3.0' - field_3_1 = '3.1' - - -class Loss(StrEnum): - feed_version_not_atomic = 'feed_version_not_atomic' - pricing_version_not_atomic = 'pricing_version_not_atomic' - mutation_idempotency_not_guaranteed = 'mutation_idempotency_not_guaranteed' - - -class Losses(AdCPBaseModel): - pass - - -class PurchaseContinuation1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['legacy_create'] = 'legacy_create' - continuation_token: Annotated[ - str, - Field( - description='Opaque, short-lived coordinator token bound to the caller principal, account, and complete observed product/pricing payload. It is not a seller-issued feed fence.', - min_length=16, - ), - ] - continuation_expires_at: Annotated[ - AwareDatetime, - Field(description='Absolute expiry of the single-use compatibility continuation.'), - ] - source_adcp_version: Annotated[ - SourceAdcpVersion, - Field( - description='Established AdCP version actually negotiated with the peer. This provenance is required; the coordinator MUST NOT infer stronger guarantees from the label.' - ), - ] - product_ids: Annotated[ - list[ProductId], - Field( - description='Exact products bound to this continuation. A follow-up MUST select a non-empty subset and MUST NOT substitute an ID from another discovery result.', - min_length=1, - ), - ] - losses: Annotated[ - list[Loss] | Losses, - Field( - description='Guarantees that the established create_media_buy continuation cannot provide. The coordinator MUST fail before mutation unless the caller explicitly accepts every listed loss.', - min_length=2, - ), - ] - requires_explicit_acceptance: Literal[True] - - -class Status(StrEnum): - completed = 'completed' - submitted = 'submitted' - - -class IncompleteItem5(IncompleteItem): - pass - - -class PurchaseContinuation2(PurchaseContinuation): - pass - - -class PurchaseContinuation3(PurchaseContinuation1): - pass - - -class IncompleteItem6(IncompleteItem): - pass - - -class PurchaseContinuation4(PurchaseContinuation): - pass - - -class PurchaseContinuation5(PurchaseContinuation1): - pass - - -class IncompleteItem7(IncompleteItem): - pass - - -class PurchaseContinuation6(PurchaseContinuation): - pass - - -class PurchaseContinuation7(PurchaseContinuation1): - pass - - -class Proposal(CanonicalProposal): - proposal_status: Annotated[ - Literal['draft'], - Field( - description='draft is indicative and unreserved; committed has firm terms with inventory reserved until expires_at; accepted is the historical snapshot attached to a MediaBuy.', - title='Proposal Status', - ), - ] = 'draft' - expires_at: Annotated[ - AwareDatetime, - Field( - description='For a draft, the indicative-terms freshness deadline. For a committed proposal, the inventory-hold deadline.' - ), - ] - proposal_kind: Any - commercial_terms: Any - terms_digest: Any - - -class RequestProposalsResponse1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?)?$', - ), - ] = None - outcome: Literal['proposed'] = 'proposed' - reason: Annotated[str | None, Field(min_length=1)] = None - suggestions: Annotated[list[Suggestion] | None, Field(min_length=1)] = None - proposals: Annotated[list[Proposal], Field(min_length=1)] - products: Annotated[list[canonical_product.CanonicalProduct], Field(min_length=1)] - incomplete: Annotated[ - list[IncompleteItem] | None, - Field( - description='Usable partial discovery result retained from the established get_products response. Absence means the source response did not declare an incomplete scope; it does not authorize an adapter to infer missing proposal terms.', - min_length=1, - ), - ] = None - purchase_continuation: Annotated[ - PurchaseContinuation | PurchaseContinuation1 | None, - Field( - deprecated=True, - description='Deprecated AdCP 3.x projection instruction for purchasing products returned without a proposal. This is coordinator state, not a claim that the established seller implements a compact task. A native 3.2 seller MUST NOT emit it.', - ), - ] = None - targeting_resolution: Annotated[ - get_products_targeting_resolution.ProductDiscoveryTargetingResolution | None, - Field( - description='Response-level confirmation of structured targeting interpreted from explicit hard requirements in the brief. Sellers MUST emit this when that interpretation materially affects eligibility, pricing, or forecasting; otherwise it remains a best practice. Product-specific changes to explicit criteria.targeting_overlay values remain on Product.targeting_resolution.' - ), - ] = None - status: Literal['completed'] = 'completed' - task_id: Annotated[str | None, Field(min_length=1)] = None - message: Annotated[str | None, Field(max_length=2000)] = None - errors: list[error.Error] | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - replayed: Literal[True] | None = None - - -class RequestProposalsResponse2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?)?$', - ), - ] = None - outcome: Literal['products_available'] = 'products_available' - reason: Annotated[str | None, Field(min_length=1)] = None - suggestions: Annotated[list[Suggestion] | None, Field(min_length=1)] = None - proposals: Annotated[list[Proposal] | None, Field(min_length=1)] = None - products: Annotated[list[canonical_product.CanonicalProduct], Field(min_length=1)] - incomplete: Annotated[ - list[IncompleteItem5] | None, - Field( - description='Usable partial discovery result retained from the established get_products response. Absence means the source response did not declare an incomplete scope; it does not authorize an adapter to infer missing proposal terms.', - min_length=1, - ), - ] = None - purchase_continuation: Annotated[ - PurchaseContinuation2 | PurchaseContinuation3, - Field( - deprecated=True, - description='Deprecated AdCP 3.x projection instruction for purchasing products returned without a proposal. This is coordinator state, not a claim that the established seller implements a compact task. A native 3.2 seller MUST NOT emit it.', - ), - ] - targeting_resolution: Annotated[ - get_products_targeting_resolution.ProductDiscoveryTargetingResolution | None, - Field( - description='Response-level confirmation of structured targeting interpreted from explicit hard requirements in the brief. Sellers MUST emit this when that interpretation materially affects eligibility, pricing, or forecasting; otherwise it remains a best practice. Product-specific changes to explicit criteria.targeting_overlay values remain on Product.targeting_resolution.' - ), - ] = None - status: Literal['completed'] = 'completed' - task_id: Annotated[str | None, Field(min_length=1)] = None - message: Annotated[str | None, Field(max_length=2000)] = None - errors: list[error.Error] | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - replayed: Literal[True] | None = None - - -class RequestProposalsResponse3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?)?$', - ), - ] = None - outcome: Literal['rejected'] = 'rejected' - reason: Annotated[str, Field(min_length=1)] - suggestions: Annotated[list[Suggestion] | None, Field(min_length=1)] = None - proposals: Annotated[list[Proposal] | None, Field(min_length=1)] = None - products: Annotated[list[canonical_product.CanonicalProduct] | None, Field(min_length=1)] = None - incomplete: Annotated[ - list[IncompleteItem6] | None, - Field( - description='Usable partial discovery result retained from the established get_products response. Absence means the source response did not declare an incomplete scope; it does not authorize an adapter to infer missing proposal terms.', - min_length=1, - ), - ] = None - purchase_continuation: Annotated[ - PurchaseContinuation4 | PurchaseContinuation5 | None, - Field( - deprecated=True, - description='Deprecated AdCP 3.x projection instruction for purchasing products returned without a proposal. This is coordinator state, not a claim that the established seller implements a compact task. A native 3.2 seller MUST NOT emit it.', - ), - ] = None - targeting_resolution: Annotated[ - get_products_targeting_resolution.ProductDiscoveryTargetingResolution | None, - Field( - description='Response-level confirmation of structured targeting interpreted from explicit hard requirements in the brief. Sellers MUST emit this when that interpretation materially affects eligibility, pricing, or forecasting; otherwise it remains a best practice. Product-specific changes to explicit criteria.targeting_overlay values remain on Product.targeting_resolution.' - ), - ] = None - status: Literal['completed'] = 'completed' - task_id: Annotated[str | None, Field(min_length=1)] = None - message: Annotated[str | None, Field(max_length=2000)] = None - errors: list[error.Error] | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - replayed: Literal[True] | None = None - - -class RequestProposalsResponse4(CompactTaskSubmitted): - model_config = ConfigDict( - extra='forbid', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?)?$', - ), - ] = None - outcome: Outcome | None = None - reason: Annotated[str | None, Field(min_length=1)] = None - suggestions: Annotated[list[Suggestion] | None, Field(min_length=1)] = None - proposals: Annotated[list[Proposal] | None, Field(min_length=1)] = None - products: Annotated[list[canonical_product.CanonicalProduct] | None, Field(min_length=1)] = None - incomplete: Annotated[ - list[IncompleteItem7] | None, - Field( - description='Usable partial discovery result retained from the established get_products response. Absence means the source response did not declare an incomplete scope; it does not authorize an adapter to infer missing proposal terms.', - min_length=1, - ), - ] = None - purchase_continuation: Annotated[ - PurchaseContinuation6 | PurchaseContinuation7 | None, - Field( - deprecated=True, - description='Deprecated AdCP 3.x projection instruction for purchasing products returned without a proposal. This is coordinator state, not a claim that the established seller implements a compact task. A native 3.2 seller MUST NOT emit it.', - ), - ] = None - targeting_resolution: Annotated[ - get_products_targeting_resolution.ProductDiscoveryTargetingResolution | None, - Field( - description='Response-level confirmation of structured targeting interpreted from explicit hard requirements in the brief. Sellers MUST emit this when that interpretation materially affects eligibility, pricing, or forecasting; otherwise it remains a best practice. Product-specific changes to explicit criteria.targeting_overlay values remain on Product.targeting_resolution.' - ), - ] = None - status: Status | None = None - task_id: Annotated[str | None, Field(min_length=1)] = None - message: Annotated[str | None, Field(max_length=2000)] = None - errors: list[error.Error] | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - replayed: Literal[True] | None = None - - -RequestProposalsResponse = Annotated[ - RequestProposalsResponse1 - | RequestProposalsResponse2 - | RequestProposalsResponse3 - | RequestProposalsResponse4, - Field( - description='One or more immutable draft media-plan proposals and compact canonical products referenced by their purchases. Products always carry product_id and name and never carry legacy named-format identifiers. During the AdCP 3.x compatibility window, an SDK projecting a valid products-only get_products brief result may instead return the deprecated products_available outcome with an explicit purchase continuation. Native 3.2 sellers MUST NOT use that compatibility outcome, and adapters MUST NOT fabricate a proposal, terms digest, or feed version.', - title='Request Proposals Response', - ), -] diff --git a/src/adcp/types/generated_poc/media_buy/sync_audiences_request.py b/src/adcp/types/generated_poc/media_buy/sync_audiences_request.py deleted file mode 100644 index 252741c49..000000000 --- a/src/adcp/types/generated_poc/media_buy/sync_audiences_request.py +++ /dev/null @@ -1,125 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/sync_audiences_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, StrictBool - -from ..core import account_ref, audience_member, audience_source -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import consent_basis as consent_basis_1 - - -class AudienceType(StrEnum): - crm = 'crm' - suppression = 'suppression' - lookalike_seed = 'lookalike_seed' - - -class Tag(ScalarStr): - __slots__ = () - _constraints = {'min_length': 1} - - -class Audience(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - audience_id: Annotated[ - str, - Field( - description="Buyer's identifier for this audience. Used to reference the audience in targeting overlays." - ), - ] - name: Annotated[str | None, Field(description='Human-readable name for this audience')] = None - description: Annotated[ - str | None, - Field( - description="Human-readable description of this audience's composition or purpose (e.g., 'High-value customers who purchased in the last 90 days')." - ), - ] = None - audience_type: Annotated[ - AudienceType | None, - Field( - description="Intended use for this audience. 'crm': target these users. 'suppression': exclude these users from delivery. 'lookalike_seed': use as a seed for the seller's lookalike modeling. Sellers may handle audiences differently based on type (e.g., suppression lists bypass minimum size requirements on some platforms)." - ), - ] = None - tags: Annotated[ - list[Tag] | None, - Field( - description="Buyer-defined tags for organizing and filtering audiences (e.g., 'holiday_2026', 'high_ltv'). Tags are stored by the seller and returned in discovery-only calls." - ), - ] = None - add: Annotated[ - list[audience_member.AudienceMember] | None, - Field( - description='Members to add to this audience. Hashed before sending — normalize emails to lowercase+trim, phones to E.164.', - min_length=1, - ), - ] = None - remove: Annotated[ - list[audience_member.AudienceMember] | None, - Field( - description='Members to remove from this audience. If the same identifier appears in both add and remove in a single request, remove takes precedence.', - min_length=1, - ), - ] = None - source: Annotated[ - audience_source.AudienceSource | None, - Field( - description='External source reference: the seller ingests membership from a shared dataset or vendor-distributed segment instead of inline member deltas. Mutually exclusive with add/remove — an audience is either buyer-pushed or externally sourced, and transport is fixed at creation: a cross-transport upsert (member deltas against a sourced audience, or source against a pushed one) is rejected with CONFLICT (error.field: audience_id); convert by delete-and-recreate. Only send source kinds whose activation pattern the seller declared via audience_activation (undeclared kinds are rejected with UNSUPPORTED_FEATURE). Experimental — see the media_buy.audience_activation feature.' - ), - ] = None - delete: Annotated[ - StrictBool | None, - Field( - description='When true, delete this audience from the account entirely. All other fields on this audience object are ignored. Use this to delete a specific audience without affecting others.' - ), - ] = None - consent_basis: Annotated[ - consent_basis_1.ConsentBasis | None, - Field( - description='GDPR lawful basis for processing this audience list. Informational — not validated by the protocol, but required by some sellers operating in regulated markets (e.g. EU). When omitted, the buyer asserts they have a lawful basis appropriate to their jurisdiction.' - ), - ] = None - - -class SyncAudiencesRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for at-most-once execution. `audience_id` gives resource-level dedup per audience, but the sync envelope emits audit events and may trigger downstream refreshes — this key prevents those side effects from firing twice on retry. Also serves as a request ID on discovery-only calls (when `audiences` is omitted). MUST be unique per (seller, request) pair. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - account: Annotated[ - account_ref.AccountReference, Field(description='Account to manage audiences for.') - ] - audiences: Annotated[ - list[Audience] | None, - Field( - description='Audiences to sync (create or update). When omitted, the call is discovery-only and returns all existing audiences on the account without modification.', - min_length=1, - ), - ] = None - delete_missing: Annotated[ - StrictBool | None, - Field( - description='When true, buyer-managed audiences on the account not included in this sync will be removed. Does not affect seller-managed audiences. Do not combine with an omitted audiences array or all buyer-managed audiences will be deleted.' - ), - ] = False - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/sync_audiences_response.py b/src/adcp/types/generated_poc/media_buy/sync_audiences_response.py deleted file mode 100644 index 1e1f47c8c..000000000 --- a/src/adcp/types/generated_poc/media_buy/sync_audiences_response.py +++ /dev/null @@ -1,94 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/sync_audiences_response.json -# timestamp: preserved-by-post-generate-fixes - -from __future__ import annotations - -from typing import Annotated, Literal, TypeAlias - -from pydantic import AwareDatetime, ConfigDict, Field, StringConstraints - -from ..core.version_envelope import AdcpVersionEnvelope -from ..core import brand_ref as brand_ref_1 -from ..core import context as context_1 -from ..core import error as error_1 -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..enums import audience_status as audience_status_1 -from ..enums import match_id_type as match_id_type_1 -from ..enums import task_status as task_status_1 - - -class MatchBreakdown(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - id_type: match_id_type_1.MatchIdType - submitted: Annotated[int, Field(ge=0)] - matched: Annotated[int, Field(ge=0)] - match_rate: Annotated[float, Field(ge=0, le=1)] - - -class Source(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - kind: Literal['dataset', 'platform_segment'] - vendor: brand_ref_1.BrandReference - locator: str | None = None - segment_ref: str | None = None - columns_read: Annotated[list[str], Field(min_length=1)] | None = None - access_status: Literal['active', 'unavailable'] | None = None - - -class Audience(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - audience_id: str - name: str | None = None - seller_id: str | None = None - action: Literal['created', 'updated', 'unchanged', 'deleted', 'failed'] - status: audience_status_1.AudienceStatus | None = None - uploaded_count: Annotated[int, Field(ge=0)] | None = None - total_uploaded_count: Annotated[int, Field(ge=0)] | None = None - matched_count: Annotated[int, Field(ge=0)] | None = None - effective_match_rate: Annotated[float, Field(ge=0, le=1)] | None = None - match_breakdown: Annotated[list[MatchBreakdown], Field(min_length=1)] | None = None - last_synced_at: AwareDatetime | None = None - source: Source | None = None - minimum_size: Annotated[int, Field(ge=1)] | None = None - errors: list[error_1.Error] | None = None - - -class SyncAudiencesResponse1(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - audiences: list[Audience] - sandbox: bool | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class SyncAudiencesResponse2(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - errors: Annotated[list[error_1.Error], Field(min_length=1)] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class SyncAudiencesResponse3(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow', validate_default=True) - status: Literal[task_status_1.TaskStatus.submitted] = task_status_1.TaskStatus.submitted - task_id: str - message: Annotated[str, StringConstraints(max_length=2000)] | None = None - errors: list[error_1.Error] | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -SyncAudiencesResponse: TypeAlias = SyncAudiencesResponse1 | SyncAudiencesResponse2 | SyncAudiencesResponse3 - - -__all__ = [ - 'SyncAudiencesResponse', - 'SyncAudiencesResponse1', - 'SyncAudiencesResponse2', - 'SyncAudiencesResponse3', - 'Audience', - 'MatchBreakdown', - 'Source', -] diff --git a/src/adcp/types/generated_poc/media_buy/sync_catalogs_async_response_input_required.py b/src/adcp/types/generated_poc/media_buy/sync_catalogs_async_response_input_required.py deleted file mode 100644 index c6801acb0..000000000 --- a/src/adcp/types/generated_poc/media_buy/sync_catalogs_async_response_input_required.py +++ /dev/null @@ -1,35 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/sync_catalogs_async_response_input_required.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..core import context as context_1 -from ..core import ext as ext_1 - - -class Reason(StrEnum): - APPROVAL_REQUIRED = 'APPROVAL_REQUIRED' - FEED_VALIDATION = 'FEED_VALIDATION' - ITEM_REVIEW = 'ITEM_REVIEW' - FEED_ACCESS = 'FEED_ACCESS' - - -class SyncCatalogsInputRequired(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - reason: Annotated[ - Reason | None, - Field( - description='Reason code indicating why buyer input is needed. APPROVAL_REQUIRED: platform requires explicit approval before activating the catalog. FEED_VALIDATION: feed URL returned unexpected format or schema errors. ITEM_REVIEW: platform flagged items for manual review. FEED_ACCESS: platform cannot access the feed URL (authentication, CORS, etc.).' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/sync_catalogs_async_response_submitted.py b/src/adcp/types/generated_poc/media_buy/sync_catalogs_async_response_submitted.py deleted file mode 100644 index a2852c7fe..000000000 --- a/src/adcp/types/generated_poc/media_buy/sync_catalogs_async_response_submitted.py +++ /dev/null @@ -1,47 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/sync_catalogs_async_response_submitted.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 - - -class SyncCatalogsSubmitted(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Literal['submitted'], - Field( - description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, whose catalogs array is issued in-line. See task-status.json for the full task-status enum.' - ), - ] = 'submitted' - task_id: Annotated[ - str, - Field( - description='Task handle the buyer uses with get_task_status (or the legacy AdCP tasks/get alias), and that the seller references on push-notification callbacks. This AdCP application-layer handle remains the snake_case task_id in every transport payload and is distinct from any transport-native A2A Task id.' - ), - ] - message: Annotated[ - str | None, - Field( - description="Optional human-readable explanation of why the task is submitted — e.g., 'Catalog ingestion queued; typical turnaround 5–15 minutes.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", - max_length=2000, - ), - ] = None - errors: Annotated[ - list[error.Error] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/sync_catalogs_async_response_working.py b/src/adcp/types/generated_poc/media_buy/sync_catalogs_async_response_working.py deleted file mode 100644 index d9691add8..000000000 --- a/src/adcp/types/generated_poc/media_buy/sync_catalogs_async_response_working.py +++ /dev/null @@ -1,48 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/sync_catalogs_async_response_working.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictFloat - -from ..core import context as context_1 -from ..core import ext as ext_1 - - -class SyncCatalogsWorking(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - percentage: Annotated[ - StrictFloat | None, Field(description='Completion percentage (0-100)', ge=0.0, le=100.0) - ] = None - current_step: Annotated[ - str | None, - Field( - description="Current step or phase of the operation (e.g., 'Fetching product feed', 'Validating items', 'Platform review')" - ), - ] = None - total_steps: Annotated[ - SchemaInt | None, Field(description='Total number of steps in the operation', ge=1) - ] = None - step_number: Annotated[SchemaInt | None, Field(description='Current step number', ge=1)] = None - catalogs_processed: Annotated[ - SchemaInt | None, Field(description='Number of catalogs processed so far', ge=0) - ] = None - catalogs_total: Annotated[ - SchemaInt | None, Field(description='Total number of catalogs to process', ge=0) - ] = None - items_processed: Annotated[ - SchemaInt | None, - Field(description='Total number of catalog items processed across all catalogs', ge=0), - ] = None - items_total: Annotated[ - SchemaInt | None, - Field(description='Total number of catalog items to process across all catalogs', ge=0), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/sync_catalogs_request.py b/src/adcp/types/generated_poc/media_buy/sync_catalogs_request.py deleted file mode 100644 index cd0e30af5..000000000 --- a/src/adcp/types/generated_poc/media_buy/sync_catalogs_request.py +++ /dev/null @@ -1,98 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/sync_catalogs_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import ConfigDict, Field, StrictBool - -from ..core import ( - account_ref, - catalog, - catalog_item_availability_ref, - catalog_item_availability_update, -) -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core import push_notification_config as push_notification_config_1 -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import validation_mode as validation_mode_1 - - -class SyncCatalogsRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for at-most-once execution. Catalog upserts and item availability transitions can emit audit events or trigger platform work — this key prevents those side effects from firing twice on retry. Also serves as a request ID on discovery-only calls. MUST be unique per (seller, request) pair. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - account: Annotated[ - account_ref.AccountReference, - Field(description='Seller account containing these buyer-managed catalogs.'), - ] - catalogs: Annotated[ - list[catalog.Catalog] | None, - Field( - description='Array of catalog feeds to sync (create or update). When omitted together with item_availability_updates and item_availability_queries, the call is discovery-only and returns all existing catalogs on the account without modification.', - max_length=50, - min_length=1, - ), - ] = None - item_availability_updates: Annotated[ - list[catalog_item_availability_update.CatalogItemAvailabilityUpdate] | None, - Field( - description='Immediate suppress or restore operations for items in buyer-managed catalogs. Sellers declaring media_buy.features.catalog_item_availability_updates MUST process these updates synchronously and MUST NOT silently ignore them or return a submitted task. A seller that does not declare the capability MUST reject the request with UNSUPPORTED_FEATURE before lookup or mutation and MUST NOT interpret it as discovery. The combined number of item_availability_updates and item_availability_queries MUST NOT exceed 1,000; excess entries are an operation-level INVALID_REQUEST before lookup or mutation. Each (catalog_id, catalog_generation, item_id) tuple MUST appear at most once in updates; a duplicate is an operation-level INVALID_REQUEST before mutation in every validation mode. For mixed catalog/update requests, the seller MUST validate and stage the entire request against the post-upsert candidate state, then commit catalog and availability changes atomically. It MUST reject before any mutation if synchronous atomic commit is unavailable. A successful suppress acknowledgement means the seller MUST stop selecting or rendering the item and every cached or pre-generated creative it materialized from the item. Seller-internal generation lineage MUST retain resolved_account_id, catalog_id, catalog_generation, and item_id. If the seller cannot enforce that guarantee, it MUST return a failed per-item result. Suppression persists across scheduled feed fetches and catalog upserts until explicit restore, expires_at, or deletion of the containing catalog. Restore removes only an existing buyer-authored overlay or tombstone in the same catalog generation and cannot override seller rejection, withdrawal, policy, rights, or inventory controls. A restore for an absent item without such prior state fails with REFERENCE_NOT_FOUND.', - max_length=1000, - min_length=1, - ), - ] = None - item_availability_queries: Annotated[ - list[catalog_item_availability_ref.CatalogItemAvailabilityReference] | None, - Field( - description='Read current buyer-authored availability state. Queries require media_buy.features.catalog_item_availability_updates; a seller that does not declare it rejects with UNSUPPORTED_FEATURE before lookup. Any request containing queries is synchronous. In a mixed request the seller validates and stages catalog upserts and availability updates first, evaluates queries against that post-upsert/post-update candidate state, and atomically commits the staged mutations before returning those query results. If the mixed work cannot commit synchronously, it rejects before mutation. The seller returns exactly one item_availability_states entry per query in the same order and echoes request_index and the complete identity. Unknown, inaccessible, stale-generation, and unauthorized references use the normalized REFERENCE_NOT_FOUND shape described by validation_mode. Use a fresh idempotency_key for a current read; a replayed response is a historical snapshot.', - max_length=1000, - min_length=1, - ), - ] = None - catalog_ids: Annotated[ - list[str] | None, - Field( - description='Optional filter to limit sync scope to specific catalog IDs. When provided, only these catalogs will be created/updated. Other catalogs on the account are unaffected.', - max_length=50, - min_length=1, - ), - ] = None - delete_missing: Annotated[ - StrictBool | None, - Field( - description='When true, buyer-managed catalogs on the account not included in this sync will be removed. Does not affect seller-managed catalogs. Requires catalogs; item_availability_updates alone cannot define deletion scope.' - ), - ] = False - dry_run: Annotated[ - StrictBool | None, - Field( - description='When true, preview catalog create, update, and delete changes without applying them. MUST NOT be combined with item_availability_updates.' - ), - ] = False - validation_mode: Annotated[ - validation_mode_1.ValidationMode | None, - Field( - description="Validation strictness for semantically valid-looking catalog and item entries. In strict mode (default), an unknown, inaccessible, unauthorized, or stale-generation catalog/item reference, a known seller-managed catalog, a stale expected_overlay_revision, or another per-entry error fails the entire operation before any catalog or availability mutation. In lenient mode, the seller returns a positionally matched failed result for each such item entry and processes the remaining valid entries. Unknown, inaccessible, unauthorized, and stale-generation references MUST be observationally equivalent: code REFERENCE_NOT_FOUND, message exactly 'Catalog item not found', recovery 'correctable', and no field, suggestion, retry_after, issues, details, or resource metadata. Authorization and lookup MUST use the same externally observable failure path and SHOULD avoid materially distinguishable timing. A known seller-managed catalog may use INVALID_REQUEST only after catalog access is authorized. Request-schema failures, duplicate identity tuples, unsupported capability, batch-limit excess, dry_run conflicts, and mixed requests that cannot commit synchronously and atomically are operation-level failures before lookup or mutation in both modes. A stale revision uses CONFLICT without mutation." - ), - ] = validation_mode_1.ValidationMode.strict - push_notification_config: Annotated[ - push_notification_config_1.PushNotificationConfig | None, - Field( - description='Optional webhook configuration for async sync notifications. Publisher will send webhook when sync completes if operation takes longer than immediate response time (common for large feeds requiring platform review).' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/sync_catalogs_response.py b/src/adcp/types/generated_poc/media_buy/sync_catalogs_response.py deleted file mode 100644 index e9295ef8c..000000000 --- a/src/adcp/types/generated_poc/media_buy/sync_catalogs_response.py +++ /dev/null @@ -1,87 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/sync_catalogs_response.json -# timestamp: preserved-by-post-generate-fixes - -from __future__ import annotations - -from typing import Annotated, Any, Literal, TypeAlias - -from pydantic import AwareDatetime, ConfigDict, Field, StringConstraints - -from ..core.version_envelope import AdcpVersionEnvelope -from ..core import catalog_item_availability_state as catalog_item_availability_state_1 -from ..core import catalog_item_availability_update_result as catalog_item_availability_update_result_1 -from ..core import context as context_1 -from ..core import error as error_1 -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..enums import catalog_action as catalog_action_1 -from ..enums import catalog_item_status as catalog_item_status_1 -from ..enums import task_status as task_status_1 - - -class ItemIssue(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - item_id: str - status: catalog_item_status_1.CatalogItemStatus - reasons: list[str] | None = None - - -class Catalog(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - catalog_id: str - catalog_generation: Annotated[str, StringConstraints(min_length=1, max_length=255)] | None = None - action: catalog_action_1.CatalogAction - platform_id: str | None = None - item_count: Annotated[int, Field(ge=0)] | None = None - items_approved: Annotated[int, Field(ge=0)] | None = None - items_pending: Annotated[int, Field(ge=0)] | None = None - items_rejected: Annotated[int, Field(ge=0)] | None = None - item_issues: list[ItemIssue] | None = None - last_synced_at: AwareDatetime | None = None - next_fetch_at: AwareDatetime | None = None - changes: list[str] | None = None - errors: list[error_1.Error] | None = None - warnings: list[str] | None = None - - -class SyncCatalogsResponse1(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - status: Literal['completed'] | None = None - dry_run: bool | None = None - catalogs: list[Catalog] - item_availability_updates: Annotated[list[catalog_item_availability_update_result_1.CatalogItemAvailabilityUpdateResult], Field(min_length=1, max_length=1000)] | None = None - item_availability_states: Annotated[list[catalog_item_availability_state_1.CatalogItemAvailabilityState], Field(min_length=1, max_length=1000)] | None = None - sandbox: bool | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class SyncCatalogsResponse2(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - errors: Annotated[list[Any], Field(min_length=1)] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class SyncCatalogsResponse3(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow', validate_default=True) - status: Literal[task_status_1.TaskStatus.submitted] = task_status_1.TaskStatus.submitted - task_id: str - message: Annotated[str, StringConstraints(max_length=2000)] | None = None - errors: list[error_1.Error] | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -SyncCatalogsResponse: TypeAlias = SyncCatalogsResponse1 | SyncCatalogsResponse2 | SyncCatalogsResponse3 - - -__all__ = [ - 'SyncCatalogsResponse', - 'SyncCatalogsResponse1', - 'SyncCatalogsResponse2', - 'SyncCatalogsResponse3', - 'Catalog', - 'ItemIssue', -] diff --git a/src/adcp/types/generated_poc/media_buy/sync_event_sources_request.py b/src/adcp/types/generated_poc/media_buy/sync_event_sources_request.py deleted file mode 100644 index 28fba5de5..000000000 --- a/src/adcp/types/generated_poc/media_buy/sync_event_sources_request.py +++ /dev/null @@ -1,95 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/sync_event_sources_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field, StrictBool - -from ..core import account_ref -from ..core import context as context_1 -from ..core import event_surface -from ..core import ext as ext_1 -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import action_source as action_source_1 -from ..enums import event_type - - -class ValueCurrency(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{3}$'} - - -class EventSource(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - event_source_id: Annotated[str, Field(description='Unique identifier for this event source')] - name: Annotated[str | None, Field(description='Human-readable name for this event source')] = ( - None - ) - event_types: Annotated[ - list[event_type.EventType] | None, - Field( - description='Event types this source handles (e.g. purchase, lead). If omitted, accepts all event types.', - min_length=1, - ), - ] = None - value_currencies: Annotated[ - list[ValueCurrency] | None, - Field( - description='ISO 4217 currencies this source accepts for monetary custom_data values. Required for a source used by canonical ROAS. Once declared, log_event monetary records for this source MUST include a currency from this list. Sources omitting the field retain legacy event compatibility but are ineligible for canonical ROAS. One source may list multiple currencies; each media buy consumes only records matching its own currency and no conversion occurs.', - min_length=1, - ), - ] = None - action_source: Annotated[ - action_source_1.ActionSource | None, - Field( - description='Flat action-source category for this event source, such as website, app, in_store, system_generated, or other. Producers SHOULD also populate `surface` when the flat value is too coarse, and SHOULD keep this field aligned with `surface.category` when the category is also an ActionSource value.' - ), - ] = None - allowed_domains: Annotated[ - list[str] | None, - Field(description='Domains authorized to send events for this event source', min_length=1), - ] = None - surface: Annotated[ - event_surface.EventSurface | None, - Field( - description='Optional structured surface this source represents, such as an owned channel, profile, feed, podcast, newsletter list, website, app, or store. Complements the flat `action_source` returned by sellers and avoids putting optimization-relevant surface meaning only in `ext`.' - ), - ] = None - - -class SyncEventSourcesRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for at-most-once execution. `event_source_id` gives resource-level dedup per source, but the sync envelope emits audit events and can trigger downstream pixel provisioning — this key prevents those side effects from firing twice on retry. Also serves as a request ID on discovery-only calls (when `event_sources` is omitted). MUST be unique per (seller, request) pair. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - account: Annotated[ - account_ref.AccountReference, Field(description='Account to configure event sources for.') - ] - event_sources: Annotated[ - list[EventSource] | None, - Field( - description='Event sources to sync (create or update). When omitted, the call is discovery-only and returns all existing event sources on the account without modification.', - min_length=1, - ), - ] = None - delete_missing: Annotated[ - StrictBool | None, - Field(description='When true, event sources not included in this sync will be removed'), - ] = False - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/sync_event_sources_response.py b/src/adcp/types/generated_poc/media_buy/sync_event_sources_response.py deleted file mode 100644 index 2a2294556..000000000 --- a/src/adcp/types/generated_poc/media_buy/sync_event_sources_response.py +++ /dev/null @@ -1,70 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/sync_event_sources_response.json -# timestamp: preserved-by-post-generate-fixes - -from __future__ import annotations - -from typing import Annotated, Literal, TypeAlias - -from pydantic import ConfigDict, Field, StringConstraints - -from ..core.version_envelope import AdcpVersionEnvelope -from ..core import context as context_1 -from ..core import error as error_1 -from ..core import event_source_health as event_source_health_1 -from ..core import event_surface as event_surface_1 -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..enums import action_source as action_source_1 -from ..enums import event_type as event_type_1 - - -class Setup(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - snippet: str | None = None - snippet_type: Literal['javascript', 'html', 'pixel_url', 'server_only'] | None = None - instructions: str | None = None - - -class EventSource(AdcpVersionEnvelope): - model_config = ConfigDict(extra='allow') - event_source_id: str - name: str | None = None - seller_id: str | None = None - event_types: list[event_type_1.EventType] | None = None - value_currencies: Annotated[list[Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')]], Field(min_length=1)] | None = None - action_source: action_source_1.ActionSource | None = None - surface: event_surface_1.EventSurface | None = None - managed_by: Literal['buyer', 'seller'] | None = None - setup: Setup | None = None - action: Literal['created', 'updated', 'unchanged', 'deleted', 'failed'] - health: event_source_health_1.EventSourceHealth | None = None - errors: list[error_1.Error] | None = None - ext: ext_1.ExtensionObject | None = None - - -class SyncEventSourcesResponse1(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - event_sources: list[EventSource] - sandbox: bool | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class SyncEventSourcesResponse2(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - errors: Annotated[list[error_1.Error], Field(min_length=1)] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -SyncEventSourcesResponse: TypeAlias = SyncEventSourcesResponse1 | SyncEventSourcesResponse2 - - -__all__ = [ - 'SyncEventSourcesResponse', - 'SyncEventSourcesResponse1', - 'SyncEventSourcesResponse2', - 'EventSource', - 'Setup', -] diff --git a/src/adcp/types/generated_poc/media_buy/sync_reporting_receipts_request.py b/src/adcp/types/generated_poc/media_buy/sync_reporting_receipts_request.py deleted file mode 100644 index ba34b6130..000000000 --- a/src/adcp/types/generated_poc/media_buy/sync_reporting_receipts_request.py +++ /dev/null @@ -1,75 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/sync_reporting_receipts_request.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import SchemaInt -from pydantic import ConfigDict, Field, model_validator - -from ..core import canonical_account_ref -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core import reporting_adjustment_receipt, reporting_receipt -from ..core.version_envelope import AdcpVersionEnvelope - - -class SyncReportingReceiptsRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?)?$', - ), - ] = None - adcp_major_version: Annotated[ - SchemaInt | None, - Field( - deprecated=True, - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - account: canonical_account_ref.CanonicalAccountReference - idempotency_key: Annotated[ - str, - Field( - description='Client-generated batch key. Exact retries reuse the key and body.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - receipts: Annotated[ - list[reporting_receipt.ReportingReceipt] | None, Field(max_length=100, min_length=1) - ] = None - adjustment_receipts: Annotated[ - list[reporting_adjustment_receipt.ReportingAdjustmentReceipt] | None, - Field( - description='Consumer acceptance or rejection of exact post-official adjustments.', - max_length=100, - min_length=1, - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - @model_validator(mode='after') - def _require_schema_required_group(self) -> SyncReportingReceiptsRequest: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('receipts',), ('adjustment_receipts',),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'SyncReportingReceiptsRequest requires at least one of these field groups: receipts | adjustment_receipts' - ) diff --git a/src/adcp/types/generated_poc/media_buy/sync_reporting_receipts_response.py b/src/adcp/types/generated_poc/media_buy/sync_reporting_receipts_response.py deleted file mode 100644 index 93104470f..000000000 --- a/src/adcp/types/generated_poc/media_buy/sync_reporting_receipts_response.py +++ /dev/null @@ -1,78 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/sync_reporting_receipts_response.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 -from ..core import reporting_adjustment_receipt, reporting_receipt -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope - - -class Results(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - result: Literal['failed'] = 'failed' - reporting_receipt_id: Annotated[ - str, Field(max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$') - ] - errors: Annotated[list[error.Error], Field(max_length=16, min_length=1)] - - -class Results20(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - result: Literal['recorded'] = 'recorded' - receipt: reporting_receipt.ReportingReceipt - - -class Results21(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - result: Literal['unchanged'] = 'unchanged' - receipt: reporting_receipt.ReportingReceipt - - -class Results22(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - result: Literal['recorded'] = 'recorded' - adjustment_receipt: reporting_adjustment_receipt.ReportingAdjustmentReceipt - - -class Results23(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - result: Literal['unchanged'] = 'unchanged' - adjustment_receipt: reporting_adjustment_receipt.ReportingAdjustmentReceipt - - -class SyncReportingReceiptsResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Literal['completed'], - Field( - description='Receipt batches complete synchronously with one result per submitted receipt.' - ), - ] = 'completed' - results: Annotated[ - list[Results20 | Results21 | Results22 | Results23 | Results], - Field(max_length=100, min_length=1), - ] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/sync_reporting_status_request.py b/src/adcp/types/generated_poc/media_buy/sync_reporting_status_request.py deleted file mode 100644 index 9cc099427..000000000 --- a/src/adcp/types/generated_poc/media_buy/sync_reporting_status_request.py +++ /dev/null @@ -1,58 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/sync_reporting_status_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field - -from ..core import canonical_account_ref -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core import reporting_consumer_status - - -class SyncReportingStatusRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?)?$', - ), - ] = None - adcp_major_version: Annotated[ - SchemaInt | None, - Field( - deprecated=True, - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - account: canonical_account_ref.CanonicalAccountReference - idempotency_key: Annotated[ - str, - Field( - description='Client-generated batch key. Exact retries reuse the key and body.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - statuses: Annotated[ - list[reporting_consumer_status.ReportingConsumerStatus], - Field( - description='Immutable status updates. New state uses a new reporting_status_id and explicitly supersedes the current status for that expected period. A batch contains at most one update for each logical status chain. content_mismatch entries report a consumed revision that contradicts the accepted configuration generation; they are operational disagreements about contract facts, never measurement disputes.', - max_length=100, - min_length=1, - ), - ] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/sync_reporting_status_response.py b/src/adcp/types/generated_poc/media_buy/sync_reporting_status_response.py deleted file mode 100644 index 23dd0e74a..000000000 --- a/src/adcp/types/generated_poc/media_buy/sync_reporting_status_response.py +++ /dev/null @@ -1,59 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/sync_reporting_status_response.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 -from ..core import reporting_consumer_status -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope - - -class Results(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - result: Literal['recorded'] = 'recorded' - consumer_status: reporting_consumer_status.ReportingConsumerStatus - - -class Results26(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - result: Literal['unchanged'] = 'unchanged' - consumer_status: reporting_consumer_status.ReportingConsumerStatus - - -class Results27(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - result: Literal['failed'] = 'failed' - reporting_status_id: Annotated[ - str, Field(max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$') - ] - errors: Annotated[list[error.Error], Field(max_length=16, min_length=1)] - - -class SyncReportingStatusResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Literal['completed'], - Field( - description='Status batches complete synchronously with one result per submitted statement.' - ), - ] = 'completed' - results: Annotated[list[Results | Results26 | Results27], Field(max_length=100, min_length=1)] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/update_media_buy_async_response_input_required.py b/src/adcp/types/generated_poc/media_buy/update_media_buy_async_response_input_required.py deleted file mode 100644 index c5d5034a9..000000000 --- a/src/adcp/types/generated_poc/media_buy/update_media_buy_async_response_input_required.py +++ /dev/null @@ -1,30 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/update_media_buy_async_response_input_required.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..core import context as context_1 -from ..core import ext as ext_1 - - -class Reason(StrEnum): - APPROVAL_REQUIRED = 'APPROVAL_REQUIRED' - CHANGE_CONFIRMATION = 'CHANGE_CONFIRMATION' - - -class UpdateMediaBuyInputRequired(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - reason: Annotated[ - Reason | None, Field(description='Reason code indicating why input is needed') - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/update_media_buy_async_response_submitted.py b/src/adcp/types/generated_poc/media_buy/update_media_buy_async_response_submitted.py deleted file mode 100644 index 5b7d9c15e..000000000 --- a/src/adcp/types/generated_poc/media_buy/update_media_buy_async_response_submitted.py +++ /dev/null @@ -1,47 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/update_media_buy_async_response_submitted.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 - - -class UpdateMediaBuySubmitted(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Literal['submitted'], - Field( - description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, whose media_buy_id is issued in-line. See task-status.json for the full task-status enum.' - ), - ] = 'submitted' - task_id: Annotated[ - str, - Field( - description='Task handle the buyer uses with get_task_status (or the legacy AdCP tasks/get alias), and that the seller references on push-notification callbacks. This AdCP application-layer handle remains the snake_case task_id in every transport payload and is distinct from any transport-native A2A Task id.' - ), - ] - message: Annotated[ - str | None, - Field( - description="Optional human-readable explanation of why the task is submitted — e.g., 'Awaiting operator re-approval; typical turnaround 2–4 hours.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", - max_length=2000, - ), - ] = None - errors: Annotated[ - list[error.Error] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/update_media_buy_async_response_working.py b/src/adcp/types/generated_poc/media_buy/update_media_buy_async_response_working.py deleted file mode 100644 index 1542e9dd8..000000000 --- a/src/adcp/types/generated_poc/media_buy/update_media_buy_async_response_working.py +++ /dev/null @@ -1,31 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/update_media_buy_async_response_working.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictFloat - -from ..core import context as context_1 -from ..core import ext as ext_1 - - -class UpdateMediaBuyWorking(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - percentage: Annotated[ - StrictFloat | None, Field(description='Completion percentage (0-100)', ge=0.0, le=100.0) - ] = None - current_step: Annotated[ - str | None, Field(description='Current step or phase of the operation') - ] = None - total_steps: Annotated[ - SchemaInt | None, Field(description='Total number of steps in the operation', ge=1) - ] = None - step_number: Annotated[SchemaInt | None, Field(description='Current step number', ge=1)] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/update_media_buy_request.py b/src/adcp/types/generated_poc/media_buy/update_media_buy_request.py deleted file mode 100644 index 59c8bbe1c..000000000 --- a/src/adcp/types/generated_poc/media_buy/update_media_buy_request.py +++ /dev/null @@ -1,175 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/update_media_buy_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from collections.abc import Sequence -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat - -from ..core import account_ref, bidding_policy -from ..core import budget_allocation as budget_allocation_1 -from ..core import business_entity -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core import media_buy_frequency_cap -from ..core import push_notification_config as push_notification_config_1 -from ..core import reporting_webhook as reporting_webhook_1 -from ..core import start_timing -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import pacing as pacing_1 -from . import package_request, package_update - - -class TotalBudget(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - amount: Annotated[StrictFloat, Field(ge=0.0)] - currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] - - -class UpdateMediaBuyRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - governance_context: Annotated[ - str | None, - Field( - description='Opaque intent authorization for a commitment-increasing media-buy update.', - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - account: Annotated[ - account_ref.AccountReference, - Field( - description='Account that owns this media buy. Pass a natural key (brand, operator, optional sandbox) or a seller-assigned account_id from list_accounts. Required for governance checks and account resolution.' - ), - ] - media_buy_id: Annotated[str, Field(description="Seller's ID of the media buy to update")] - name: Annotated[ - str | None, - Field( - description='Replacement human-readable name for this media buy, used for trafficking UI display and operational communication. Sellers that cannot update name mid-flight SHOULD echo the prior unchanged value in the success response rather than silently dropping the field. This display label is not an identifier or financial reference.', - max_length=255, - min_length=1, - pattern='\\S', - ), - ] = None - revision: Annotated[ - SchemaInt | None, - Field( - description="Expected current revision for optimistic concurrency. Optional for backward compatibility. When provided, sellers MUST reject the update with CONFLICT if the media buy's current revision does not match, and MUST enforce that comparison atomically with the write. Obtain from get_media_buys or the most recent create/update response.", - ge=1, - ), - ] = None - paused: Annotated[ - StrictBool | None, - Field(description='Pause/resume the entire media buy (true = paused, false = active)'), - ] = None - canceled: Annotated[ - Literal[True] | None, - Field( - description='Cancel the entire media buy. Cancellation is irreversible — canceled media buys cannot be reactivated. Sellers MAY reject with NOT_CANCELLABLE if the media buy cannot be canceled in its current state.' - ), - ] = None - cancellation_reason: Annotated[ - str | None, - Field( - description='Reason for cancellation. Sellers SHOULD store this and return it in subsequent get_media_buys responses.', - max_length=500, - ), - ] = None - start_time: start_timing.StartTiming | None = None - end_time: Annotated[ - AwareDatetime | None, Field(description='New end date/time in ISO 8601 format') - ] = None - total_budget: Annotated[ - TotalBudget | None, - Field( - description='Updated hard aggregate lifetime budget. currency MUST equal the existing media-buy currency; an update does not redenominate a buy. When supplied alone (without packages or new_packages), in fixed mode the seller MUST atomically scale every active package budget in proportion to its current committed budget, rejecting the entire request if any derived budget cannot be accepted. When supplied with packages or new_packages, the amount MUST equal the resulting fixed-mode package sum; the seller applies the explicit package mutations and rejects with VALIDATION_ERROR if the total is inconsistent. In seller-optimized mode this changes the shared pool without converting package caps into allocations. Already-spent amounts still count against the new total.' - ), - ] = None - daily_budget_cap: Annotated[ - StrictFloat | None, - Field( - description='Replace the hard aggregate daily cap; null removes it. Numeric changes apply immediately with current-day spend counted. A cap below that spend pauses delivery for the day. Package caps are unchanged. Requires advertised media_buy scope; otherwise rejected with UNSUPPORTED_FEATURE.', - ge=0.0, - ), - ] = None - frequency_cap: Annotated[ - media_buy_frequency_cap.MediaBuyFrequencyCap | None, - Field( - description='Replace the hard MediaBuy-level frequency cap; null removes it. Changes apply immediately without resetting the shared counter, so prior qualifying exposures in the resulting window continue to count. Every active package must support the resulting cap or the request is rejected atomically with UNSUPPORTED_FEATURE before any change; sellers MUST NOT clamp it. The check applies to the state the request would produce, so null combined with new_packages adds those packages uncapped.' - ), - ] = None - budget_cap_timezone: Annotated[ - str | None, - Field( - description='Replace the shared IANA cap-day timezone; null restores the default selected by budget_capping.timezone_basis (Account.timezone or fixed_timezone). Requires buyer_timezone_override. Changes start at the next boundary in the previously effective timezone; numeric cap changes remain immediate.', - min_length=1, - ), - ] = None - budget_allocation: Annotated[ - budget_allocation_1.BudgetAllocation | None, - Field( - description='Updated allocation configuration. Switching between fixed and seller-optimized modes is allowed only when update_budget_allocation is advertised in available_actions and the resulting package constraints are valid. A resulting seller_optimized allocation requires advertised media_buy.features.seller_optimized_budget, and any package budget caps, min_spend_target values, or package pacing it retains require their own advertised sub-capability; otherwise the update is rejected with UNSUPPORTED_FEATURE before any over-subscription validation.' - ), - ] = None - pacing: Annotated[ - pacing_1.Pacing | None, - Field( - description='Updated aggregate media-buy pacing. Package pacing remains subordinate to this aggregate strategy. When the resulting buy is seller-optimized, a seller declaring media_buy.features.seller_optimized_budget MUST accept `even`; it MAY reject `asap` or `front_loaded` with UNSUPPORTED_FEATURE (error.field `pacing`) before any provider mutation, including a switch to seller-optimized allocation that would retain such pacing, and MUST NOT silently coerce them to `even`. Fixed-allocation semantics are unchanged.' - ), - ] = None - bidding: Annotated[ - bidding_policy.BiddingPolicy | None, - Field( - description='Replace the complete media-buy-authored bidding default. An object replaces the prior block; `{automatic:true}` records an explicit automatic policy. null clears it; packages with explicit package.bidding remain explicit, while packages without overrides fall back to provider automatic delivery. Goal binding follows the resulting budget allocation: seller-optimized outcome controls bind to allocation goals, while fixed inherited cost_per requires compatible package result units. Monetary fields use the media-buy currency and all affected pricing options MUST match it. The seller MUST validate all resulting policies atomically before mutation.' - ), - ] = None - packages: Annotated[ - Sequence[package_update.PackageUpdate] | None, - Field(description='Package-specific updates for existing packages', min_length=1), - ] = None - invoice_recipient: Annotated[ - business_entity.BusinessEntity | None, - Field( - description="Update who receives the invoice for this buy. When provided, the seller invoices this entity instead of the account's default billing_entity. The seller MUST validate the invoice recipient is authorized for this account. When governance_agents are configured, the seller MUST include invoice_recipient in the check_governance request." - ), - ] = None - new_packages: Annotated[ - list[package_request.PackageRequest] | None, - Field( - description='New packages to add to this media buy. Uses the same schema as create_media_buy packages. When budget_allocation is omitted or fixed, every new package MUST carry budget and MUST NOT carry min_spend_target. To add a package without a hard cap to an existing seller-optimized buy, include its resulting seller_optimized budget_allocation block in the update so the allocation context is schema-visible. Repeating an unchanged allocation block does not itself switch modes. Sellers that support mid-flight package additions advertise `add_packages` in both `valid_actions[]` (deprecated) and as an entry in `available_actions[]` (authoritative). Sellers that do not support this MUST reject with ACTION_NOT_ALLOWED (preferred) or UNSUPPORTED_FEATURE (legacy). If the buy has a root frequency_cap, every added product must support that exact aggregate cap; otherwise the seller rejects atomically with UNSUPPORTED_FEATURE, leaving the whole update unapplied and preserving the buy and its counter history.', - min_length=1, - ), - ] = None - reporting_webhook: Annotated[ - reporting_webhook_1.ReportingWebhook | None, - Field( - description='Optional webhook configuration for automated reporting delivery. Updates the reporting configuration for this media buy.' - ), - ] = None - push_notification_config: Annotated[ - push_notification_config_1.PushNotificationConfig | None, - Field( - description='Optional webhook configuration for async update notifications. Publisher will send webhook when update completes if operation takes longer than immediate response time. This is separate from reporting_webhook which configures ongoing campaign reporting.' - ), - ] = None - idempotency_key: Annotated[ - str, - Field( - description='Client-generated idempotency key for safe retries. If an update fails without a response, resending with the same idempotency_key guarantees the update is applied at most once. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/media_buy/update_media_buy_response.py b/src/adcp/types/generated_poc/media_buy/update_media_buy_response.py deleted file mode 100644 index a26af9223..000000000 --- a/src/adcp/types/generated_poc/media_buy/update_media_buy_response.py +++ /dev/null @@ -1,103 +0,0 @@ -# generated by datamodel-codegen: -# filename: media_buy/update_media_buy_response.json -# timestamp: preserved-by-post-generate-fixes - -from __future__ import annotations - -from collections.abc import Sequence - -from typing import Annotated, Any, Literal, TypeAlias - -from pydantic import AwareDatetime, ConfigDict, Field, StringConstraints, model_validator - -from ..core.version_envelope import AdcpVersionEnvelope -from ..core import business_entity as business_entity_1 -from ..core import context as context_1 -from ..core import error as error_1 -from ..core import ext as ext_1 -from ..core import media_buy_available_action as media_buy_available_action_1 -from ..core import media_buy_frequency_cap as media_buy_frequency_cap_1 -from ..core import package as package_1 -from ..core import warning as warning_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..enums import media_buy_status as media_buy_status_1 -from ..enums import media_buy_valid_action as media_buy_valid_action_1 -from ..enums import pacing as pacing_1 -from ..enums import task_status as task_status_1 -from adcp.types.media_buy_status_helpers import MEDIA_BUY_LEGACY_STATUS_VALUES, unwrap_enum_value - - -class UpdateMediaBuyResponse1(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - status: Literal['completed'] = 'completed' - media_buy_id: str - name: Annotated[str, StringConstraints(pattern='\\S', min_length=1, max_length=255)] | None = None - media_buy_status: media_buy_status_1.MediaBuyStatus | None = None - revision: Annotated[int, Field(ge=1)] - currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] | None = None - total_budget: Annotated[float, Field(ge=0)] | None = None - daily_budget_cap: Annotated[float, Field(ge=0)] | None = None - frequency_cap: media_buy_frequency_cap_1.MediaBuyFrequencyCap | None = None - budget_cap_timezone: str | None = None - budget_allocation: Any | None = None - pacing: pacing_1.Pacing | None = None - bidding: Any | None = None - implementation_date: AwareDatetime | None = None - invoice_recipient: business_entity_1.BusinessEntity | None = None - affected_packages: Sequence[package_1.Package] | None = None - valid_actions: list[media_buy_valid_action_1.MediaBuyValidAction] | None = None - available_actions: list[media_buy_available_action_1.MediaBuyAvailableAction] | None = None - warnings: list[warning_1.Warning] | None = None - sandbox: bool | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - @model_validator(mode='before') - @classmethod - def _normalize_legacy_status(cls, data: Any) -> Any: - if not isinstance(data, dict): - return data - raw_status = unwrap_enum_value(data.get('status')) - media_buy_status = unwrap_enum_value(data.get('media_buy_status')) - if raw_status is None: - data = dict(data) - data['status'] = 'completed' - elif raw_status == 'completed': - data = dict(data) - data['status'] = 'completed' - elif media_buy_status is None and raw_status in MEDIA_BUY_LEGACY_STATUS_VALUES: - data = dict(data) - data['media_buy_status'] = raw_status - data['status'] = 'completed' - elif media_buy_status is not None and raw_status == media_buy_status: - data = dict(data) - data['status'] = 'completed' - return data - - -class UpdateMediaBuyResponse2(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - errors: Annotated[list[error_1.Error], Field(min_length=1)] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class UpdateMediaBuyResponse3(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow', validate_default=True) - status: Literal[task_status_1.TaskStatus.submitted] = task_status_1.TaskStatus.submitted - task_id: str - message: Annotated[str, StringConstraints(max_length=2000)] | None = None - errors: list[error_1.Error] | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -UpdateMediaBuyResponse: TypeAlias = UpdateMediaBuyResponse1 | UpdateMediaBuyResponse2 | UpdateMediaBuyResponse3 - - -__all__ = [ - 'UpdateMediaBuyResponse', - 'UpdateMediaBuyResponse1', - 'UpdateMediaBuyResponse2', - 'UpdateMediaBuyResponse3', -] diff --git a/src/adcp/types/generated_poc/pricing_options/__init__.py b/src/adcp/types/generated_poc/pricing_options/__init__.py deleted file mode 100644 index 5a3cca36b..000000000 --- a/src/adcp/types/generated_poc/pricing_options/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# generated by datamodel-codegen: -# filename: .schema_temp -# timestamp: 2025-11-22T15:23:24+00:00 diff --git a/src/adcp/types/generated_poc/pricing_options/cpa_option.py b/src/adcp/types/generated_poc/pricing_options/cpa_option.py deleted file mode 100644 index 5b62dca51..000000000 --- a/src/adcp/types/generated_poc/pricing_options/cpa_option.py +++ /dev/null @@ -1,75 +0,0 @@ -# generated by datamodel-codegen: -# filename: pricing_options/cpa_option.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictFloat - -from ..enums import adjustment_kind -from ..enums import event_type as event_type_1 -from . import price_breakdown as price_breakdown_1 - - -class CpaPricingOption(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['cpa'], Field(description='Cost per acquisition (conversion event)') - ] = 'cpa' - event_type: Annotated[ - event_type_1.EventType, - Field( - description='The conversion event type that triggers billing (e.g., purchase, lead, app_install)' - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description="Name of the custom event when event_type is 'custom'. Required when event_type is 'custom', ignored otherwise." - ), - ] = None - event_source_id: Annotated[ - str | None, - Field( - description='When present, only events from this specific event source count toward billing. Allows different CPA rates for different sources (e.g., online vs in-store purchases). Must match an event source configured via sync_event_sources.' - ), - ] = None - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - StrictFloat, - Field(description='Fixed price per acquisition in the specified currency', gt=0.0), - ] - min_spend_per_package: Annotated[ - StrictFloat | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - price_breakdown_1.PriceBreakdown | None, - Field( - description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.' - ), - ] = None - eligible_adjustments: Annotated[ - list[adjustment_kind.PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/pricing_options/cpc_option.py b/src/adcp/types/generated_poc/pricing_options/cpc_option.py deleted file mode 100644 index 6ca810aa5..000000000 --- a/src/adcp/types/generated_poc/pricing_options/cpc_option.py +++ /dev/null @@ -1,76 +0,0 @@ -# generated by datamodel-codegen: -# filename: pricing_options/cpc_option.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictBool, StrictFloat - -from ..enums import adjustment_kind -from . import price_breakdown as price_breakdown_1 -from . import price_guidance as price_guidance_1 - - -class CpcPricingOption(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpc'], Field(description='Cost per click')] = 'cpc' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - StrictFloat | None, - Field( - description='Fixed price per click. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - StrictFloat | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - StrictBool | None, - Field( - deprecated=True, - description='DEPRECATED in 3.2 and removed in the next major. Legacy hint used only to normalize package bid_price to bidding.max_bid (true) or bidding.bid_amount (false/absent). New buyers express intent directly in bidding.', - ), - ] = False - price_guidance: Annotated[ - price_guidance_1.PriceGuidance | None, - Field(description='Optional pricing guidance for auction-based bidding'), - ] = None - min_spend_per_package: Annotated[ - StrictFloat | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - price_breakdown_1.PriceBreakdown | None, - Field( - description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.' - ), - ] = None - eligible_adjustments: Annotated[ - list[adjustment_kind.PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/pricing_options/cpcv_option.py b/src/adcp/types/generated_poc/pricing_options/cpcv_option.py deleted file mode 100644 index 744c699cb..000000000 --- a/src/adcp/types/generated_poc/pricing_options/cpcv_option.py +++ /dev/null @@ -1,78 +0,0 @@ -# generated by datamodel-codegen: -# filename: pricing_options/cpcv_option.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictBool, StrictFloat - -from ..enums import adjustment_kind -from . import price_breakdown as price_breakdown_1 -from . import price_guidance as price_guidance_1 - - -class CpcvPricingOption(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['cpcv'], Field(description='Cost per completed view (100% completion)') - ] = 'cpcv' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - StrictFloat | None, - Field( - description='Fixed price per completed view. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - StrictFloat | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - StrictBool | None, - Field( - deprecated=True, - description='DEPRECATED in 3.2 and removed in the next major. Legacy hint used only to normalize package bid_price to bidding.max_bid (true) or bidding.bid_amount (false/absent). New buyers express intent directly in bidding.', - ), - ] = False - price_guidance: Annotated[ - price_guidance_1.PriceGuidance | None, - Field(description='Optional pricing guidance for auction-based bidding'), - ] = None - min_spend_per_package: Annotated[ - StrictFloat | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - price_breakdown_1.PriceBreakdown | None, - Field( - description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.' - ), - ] = None - eligible_adjustments: Annotated[ - list[adjustment_kind.PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/pricing_options/cpm_option.py b/src/adcp/types/generated_poc/pricing_options/cpm_option.py deleted file mode 100644 index 63c207f99..000000000 --- a/src/adcp/types/generated_poc/pricing_options/cpm_option.py +++ /dev/null @@ -1,76 +0,0 @@ -# generated by datamodel-codegen: -# filename: pricing_options/cpm_option.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictBool, StrictFloat - -from ..enums import adjustment_kind -from . import price_breakdown as price_breakdown_1 -from . import price_guidance as price_guidance_1 - - -class CpmPricingOption(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpm'], Field(description='Cost per 1,000 impressions')] = 'cpm' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - StrictFloat | None, - Field( - description='Fixed price per unit. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - StrictFloat | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - StrictBool | None, - Field( - deprecated=True, - description='DEPRECATED in 3.2 and removed in the next major. Legacy hint used only to normalize package bid_price to bidding.max_bid (true) or bidding.bid_amount (false/absent). New buyers express intent directly in bidding.', - ), - ] = False - price_guidance: Annotated[ - price_guidance_1.PriceGuidance | None, - Field(description='Optional pricing guidance for auction-based bidding'), - ] = None - min_spend_per_package: Annotated[ - StrictFloat | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - price_breakdown_1.PriceBreakdown | None, - Field( - description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.' - ), - ] = None - eligible_adjustments: Annotated[ - list[adjustment_kind.PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/pricing_options/cpp_option.py b/src/adcp/types/generated_poc/pricing_options/cpp_option.py deleted file mode 100644 index 53e5c30de..000000000 --- a/src/adcp/types/generated_poc/pricing_options/cpp_option.py +++ /dev/null @@ -1,94 +0,0 @@ -# generated by datamodel-codegen: -# filename: pricing_options/cpp_option.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictFloat - -from ..enums import adjustment_kind -from ..enums import demographic_system as demographic_system_1 -from . import price_breakdown as price_breakdown_1 -from . import price_guidance as price_guidance_1 - - -class Parameters(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - demographic_system: Annotated[ - demographic_system_1.DemographicSystem | None, - Field( - description='Measurement system for the demographic field. Defaults to nielsen when omitted.' - ), - ] = None - demographic: Annotated[ - str, - Field( - description='Target demographic code within the specified demographic_system (e.g., P18-49 for Nielsen, ABC1 Adults for BARB)' - ), - ] - min_points: Annotated[ - StrictFloat | None, Field(description='Minimum GRPs/TRPs required', ge=0.0) - ] = None - - -class CppPricingOption(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpp'], Field(description='Cost per Gross Rating Point')] = 'cpp' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - StrictFloat | None, - Field( - description='Fixed price per rating point. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - StrictFloat | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - price_guidance: Annotated[ - price_guidance_1.PriceGuidance | None, - Field(description='Optional pricing guidance for auction-based bidding'), - ] = None - parameters: Annotated[ - Parameters, Field(description='CPP-specific parameters for demographic targeting') - ] - min_spend_per_package: Annotated[ - StrictFloat | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - price_breakdown_1.PriceBreakdown | None, - Field( - description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.' - ), - ] = None - eligible_adjustments: Annotated[ - list[adjustment_kind.PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/pricing_options/cpv_option.py b/src/adcp/types/generated_poc/pricing_options/cpv_option.py deleted file mode 100644 index 138e39a4c..000000000 --- a/src/adcp/types/generated_poc/pricing_options/cpv_option.py +++ /dev/null @@ -1,102 +0,0 @@ -# generated by datamodel-codegen: -# filename: pricing_options/cpv_option.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarFloat -from pydantic import ConfigDict, Field, StrictBool, StrictFloat - -from ..enums import adjustment_kind -from . import price_breakdown as price_breakdown_1 -from . import price_guidance as price_guidance_1 - - -class ViewThreshold(ScalarFloat): - __slots__ = () - _constraints = {'ge': 0.0, 'le': 1.0} - _json_schema_extra = { - 'description': 'Percentage completion threshold (0.0 to 1.0, e.g., 0.5 = 50%)', - } - - -class ViewThreshold1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - duration_seconds: Annotated[SchemaInt, Field(description='Seconds of viewing required', ge=1)] - - -class Parameters(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - view_threshold: ViewThreshold | ViewThreshold1 - - -class CpvPricingOption(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpv'], Field(description='Cost per view at threshold')] = 'cpv' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - StrictFloat | None, - Field( - description='Fixed price per view. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - StrictFloat | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - StrictBool | None, - Field( - deprecated=True, - description='DEPRECATED in 3.2 and removed in the next major. Legacy hint used only to normalize package bid_price to bidding.max_bid (true) or bidding.bid_amount (false/absent). New buyers express intent directly in bidding.', - ), - ] = False - price_guidance: Annotated[ - price_guidance_1.PriceGuidance | None, - Field(description='Optional pricing guidance for auction-based bidding'), - ] = None - parameters: Annotated[ - Parameters, Field(description='CPV-specific parameters defining the view threshold') - ] - min_spend_per_package: Annotated[ - StrictFloat | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - price_breakdown_1.PriceBreakdown | None, - Field( - description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.' - ), - ] = None - eligible_adjustments: Annotated[ - list[adjustment_kind.PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/pricing_options/flat_rate_option.py b/src/adcp/types/generated_poc/pricing_options/flat_rate_option.py deleted file mode 100644 index bf19ed369..000000000 --- a/src/adcp/types/generated_poc/pricing_options/flat_rate_option.py +++ /dev/null @@ -1,144 +0,0 @@ -# generated by datamodel-codegen: -# filename: pricing_options/flat_rate_option.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictFloat - -from ..enums import adjustment_kind -from . import price_breakdown as price_breakdown_1 -from . import price_guidance as price_guidance_1 - - -class Parameters(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Literal['dooh'], Field(description='Discriminator identifying this as DOOH parameters') - ] = 'dooh' - sov_percentage: Annotated[ - StrictFloat | None, - Field( - description='Guaranteed share of voice on a 0-100 percentage scale. To compare this contracted value with delivery dooh_metrics.sov_achieved, divide sov_percentage by 100. Share is time-weighted: the sum of the reserved segment durations divided by the full loop duration. On equal-duration loops this equals the slot-count ratio.', - ge=0.0, - le=100.0, - ), - ] = None - slot_span: Annotated[ - SchemaInt | None, - Field( - description='Number of consecutive loop slots reserved by this seller-declared pre-packaged offer. Contiguity is guaranteed; position is not guaranteed unless loop_position is also present. This field does not carry a buyer-requested custom span for an offer the seller has not pre-packaged.', - ge=1, - ), - ] = None - loop_position: Annotated[ - str | None, - Field( - description='Seller-defined position label for the reserved span within the loop, such as any, first, last, or adjacent_to_content_break. This is deliberately an open string because network vocabularies differ. Consumers MUST accept unrecognized values as opaque and treat them as conveying no position guarantee the consumer understands; an unrecognized value is not a protocol error.', - min_length=1, - ), - ] = None - loop_duration_seconds: Annotated[ - SchemaInt | None, - Field( - deprecated=True, - description='Deprecated compatibility copy of the placement-level dooh_placement_attributes.loop_duration_seconds, which is the canonical source. Retained for backward compatibility; new integrations read the placement-level field. When both are present they MUST agree. A product that offers different loop durations under different prices MUST expose distinct placements or products rather than vary this compatibility copy by pricing option.', - ge=1, - ), - ] = None - min_plays_per_hour: Annotated[ - SchemaInt | None, Field(description='Minimum number of plays per hour guaranteed', ge=1) - ] = None - venue_package: Annotated[ - str | None, - Field( - description='Named collection of DOOH venues or installed surfaces included in this buy' - ), - ] = None - duration_hours: Annotated[ - StrictFloat | None, - Field( - description='Duration of the DOOH slot in hours (e.g., 24 for a full-day takeover)', - ge=0.0, - ), - ] = None - daypart: Annotated[ - str | None, - Field(description='Named daypart for this slot (e.g., morning_commute, evening_rush)'), - ] = None - estimated_impressions: Annotated[ - SchemaInt | None, - Field( - description='Estimated audience impressions for this slot (informational, not a delivery guarantee)', - ge=0, - ), - ] = None - - -class FlatRatePricingOption(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['flat_rate'], Field(description='Fixed cost regardless of delivery volume') - ] = 'flat_rate' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - StrictFloat | None, - Field( - description='Flat rate cost. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - StrictFloat | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - price_guidance: Annotated[ - price_guidance_1.PriceGuidance | None, - Field(description='Optional pricing guidance for auction-based bidding'), - ] = None - parameters: Annotated[ - Parameters | None, - Field( - description='DOOH inventory allocation parameters. Sponsorship and takeover flat_rate options omit this field entirely — only include for digital out-of-home inventory.', - title='DoohParameters', - ), - ] = None - min_spend_per_package: Annotated[ - StrictFloat | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - price_breakdown_1.PriceBreakdown | None, - Field( - description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.' - ), - ] = None - eligible_adjustments: Annotated[ - list[adjustment_kind.PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/pricing_options/price_breakdown.py b/src/adcp/types/generated_poc/pricing_options/price_breakdown.py deleted file mode 100644 index 88bfc3165..000000000 --- a/src/adcp/types/generated_poc/pricing_options/price_breakdown.py +++ /dev/null @@ -1,88 +0,0 @@ -# generated by datamodel-codegen: -# filename: pricing_options/price_breakdown.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictFloat - -from ..enums import adjustment_kind - - -class Adjustment(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: adjustment_kind.PriceAdjustmentKind - name: Annotated[ - str, - Field( - description='Specific adjustment name. Use well-known values where applicable for interoperability.', - examples=[ - 'ad_serving', - 'data_targeting', - 'brand_safety', - 'volume', - 'negotiated', - 'early_booking', - 'agency', - 'intermediary', - 'cash_discount', - 'early_payment', - ], - max_length=64, - ), - ] - rate: Annotated[ - StrictFloat | None, - Field( - description='Adjustment as a decimal proportion (e.g., 0.15 for 15%). Always positive — kind determines the economic effect. Mutually exclusive with amount.', - gt=0.0, - lt=1.0, - ), - ] = None - amount: Annotated[ - StrictFloat | None, - Field( - description="Adjustment as a fixed monetary amount in the pricing option's currency. Always positive — kind determines the economic effect. Mutually exclusive with rate.", - gt=0.0, - ), - ] = None - description: Annotated[ - str | None, - Field( - description="Human-readable description of this adjustment (e.g., 'Malstaffel 12x', '2% Skonto 10 Tage')", - max_length=256, - ), - ] = None - beneficiary: Annotated[ - str | None, - Field( - description="Identifies who receives this adjustment's value. For commissions, the intermediary (e.g., a sellers.json domain, an AdCP account ID, or a human-readable party name). Optional but recommended for multi-intermediary transparency.", - max_length=256, - ), - ] = None - - -class PriceBreakdown(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - list_price: Annotated[ - StrictFloat, - Field( - description='Rate card or base price before any adjustments. The starting point from which fixed_price is derived by applying fee and discount adjustments sequentially.', - gt=0.0, - ), - ] - adjustments: Annotated[ - list[Adjustment], - Field( - description="Ordered list of price adjustments. Fee and discount adjustments walk list_price to fixed_price — fees increase the running price, discounts reduce it. Commission and settlement adjustments are disclosed for transparency but do not affect the buyer's committed price.", - max_length=20, - min_length=1, - ), - ] diff --git a/src/adcp/types/generated_poc/pricing_options/price_guidance.py b/src/adcp/types/generated_poc/pricing_options/price_guidance.py deleted file mode 100644 index 8b96a35d9..000000000 --- a/src/adcp/types/generated_poc/pricing_options/price_guidance.py +++ /dev/null @@ -1,28 +0,0 @@ -# generated by datamodel-codegen: -# filename: pricing_options/price_guidance.json -# timestamp: 2026-10-03T15:09:44+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictFloat - - -class PriceGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - p25: Annotated[ - StrictFloat | None, Field(description='25th percentile of recent winning bids', ge=0.0) - ] = None - p50: Annotated[ - StrictFloat | None, Field(description='Median of recent winning bids', ge=0.0) - ] = None - p75: Annotated[ - StrictFloat | None, Field(description='75th percentile of recent winning bids', ge=0.0) - ] = None - p90: Annotated[ - StrictFloat | None, Field(description='90th percentile of recent winning bids', ge=0.0) - ] = None diff --git a/src/adcp/types/generated_poc/pricing_options/revenue_share_option.py b/src/adcp/types/generated_poc/pricing_options/revenue_share_option.py deleted file mode 100644 index c9efe72d4..000000000 --- a/src/adcp/types/generated_poc/pricing_options/revenue_share_option.py +++ /dev/null @@ -1,72 +0,0 @@ -# generated by datamodel-codegen: -# filename: pricing_options/revenue_share_option.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictFloat - -from ..enums import event_type as event_type_1 - - -class RevenueSharePricingOption(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, - Field( - description='Unique identifier for this pricing option within the product', min_length=1 - ), - ] - pricing_model: Annotated[ - Literal['revenue_share'], - Field(description='Percentage commission on settled attributed conversion value'), - ] = 'revenue_share' - event_type: Annotated[ - event_type_1.EventType, - Field( - description='Conversion event whose attributed value may become commissionable (for example purchase or subscribe)' - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='Name of the custom event when event_type is custom. Required for custom events and absent otherwise.', - min_length=1, - ), - ] = None - event_source_id: Annotated[ - str, - Field( - description='Event source whose attribution is authoritative for billing. Must match an event source configured through sync_event_sources.', - min_length=1, - ), - ] - commission_rate: Annotated[ - StrictFloat, - Field( - description='Decimal proportion applied to commissionable_value. For example, 0.04 means 4%.', - gt=0.0, - le=1.0, - ), - ] - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code for commissionable_value and resulting spend', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - commission_basis_description: Annotated[ - str, - Field( - description='Human-readable definition of inclusions, exclusions, and return or cancellation treatment used by the billing authority to calculate commissionable_value.', - max_length=1000, - min_length=1, - ), - ] diff --git a/src/adcp/types/generated_poc/pricing_options/time_option.py b/src/adcp/types/generated_poc/pricing_options/time_option.py deleted file mode 100644 index c8d6f7b26..000000000 --- a/src/adcp/types/generated_poc/pricing_options/time_option.py +++ /dev/null @@ -1,103 +0,0 @@ -# generated by datamodel-codegen: -# filename: pricing_options/time_option.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictFloat - -from ..enums import adjustment_kind -from . import price_breakdown as price_breakdown_1 -from . import price_guidance as price_guidance_1 - - -class TimeUnit(StrEnum): - hour = 'hour' - day = 'day' - week = 'week' - month = 'month' - - -class Parameters(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - time_unit: Annotated[ - TimeUnit, - Field( - description='The time unit for pricing. Total cost = fixed_price × number of time_units in the campaign flight.' - ), - ] - min_duration: Annotated[ - SchemaInt | None, Field(description='Minimum booking duration in time_units', ge=1) - ] = None - max_duration: Annotated[ - SchemaInt | None, - Field( - description='Maximum booking duration in time_units. Must be >= min_duration when both are present.', - ge=1, - ), - ] = None - - -class TimeBasedPricingOption(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['time'], - Field(description='Cost per time unit - rate scales with campaign duration'), - ] = 'time' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - StrictFloat | None, - Field( - description='Cost per time unit. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - StrictFloat | None, - Field( - description='Minimum acceptable bid per time unit for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - price_guidance: Annotated[ - price_guidance_1.PriceGuidance | None, - Field(description='Optional pricing guidance for auction-based bidding'), - ] = None - parameters: Annotated[Parameters, Field(description='Time-based pricing parameters')] - min_spend_per_package: Annotated[ - StrictFloat | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - price_breakdown_1.PriceBreakdown | None, - Field( - description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.' - ), - ] = None - eligible_adjustments: Annotated[ - list[adjustment_kind.PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/pricing_options/vcpm_option.py b/src/adcp/types/generated_poc/pricing_options/vcpm_option.py deleted file mode 100644 index eaa2a6992..000000000 --- a/src/adcp/types/generated_poc/pricing_options/vcpm_option.py +++ /dev/null @@ -1,78 +0,0 @@ -# generated by datamodel-codegen: -# filename: pricing_options/vcpm_option.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictBool, StrictFloat - -from ..enums import adjustment_kind -from . import price_breakdown as price_breakdown_1 -from . import price_guidance as price_guidance_1 - - -class VcpmPricingOption(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['vcpm'], Field(description='Cost per 1,000 viewable impressions (MRC standard)') - ] = 'vcpm' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - StrictFloat | None, - Field( - description='Fixed price per unit. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - StrictFloat | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - StrictBool | None, - Field( - deprecated=True, - description='DEPRECATED in 3.2 and removed in the next major. Legacy hint used only to normalize package bid_price to bidding.max_bid (true) or bidding.bid_amount (false/absent). New buyers express intent directly in bidding.', - ), - ] = False - price_guidance: Annotated[ - price_guidance_1.PriceGuidance | None, - Field(description='Optional pricing guidance for auction-based bidding'), - ] = None - min_spend_per_package: Annotated[ - StrictFloat | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - price_breakdown_1.PriceBreakdown | None, - Field( - description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.' - ), - ] = None - eligible_adjustments: Annotated[ - list[adjustment_kind.PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/property/__init__.py b/src/adcp/types/generated_poc/property/__init__.py deleted file mode 100644 index 5334de51e..000000000 --- a/src/adcp/types/generated_poc/property/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# generated by datamodel-codegen: -# filename: .schema_temp -# timestamp: 2026-01-14T17:08:13+00:00 diff --git a/src/adcp/types/generated_poc/property/authorization_result.py b/src/adcp/types/generated_poc/property/authorization_result.py deleted file mode 100644 index 91ca344ee..000000000 --- a/src/adcp/types/generated_poc/property/authorization_result.py +++ /dev/null @@ -1,49 +0,0 @@ -# generated by datamodel-codegen: -# filename: property/authorization_result.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - - -class Status(StrEnum): - authorized = 'authorized' - unauthorized = 'unauthorized' - unknown = 'unknown' - - -class Violation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - code: Annotated[str, Field(description='Machine-readable violation code')] - message: Annotated[str, Field(description='Human-readable violation description')] - - -class AuthorizationResult(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - status: Annotated[ - Status, - Field( - description='Authorization status: authorized (agent in adagents.json), unauthorized (agent not in adagents.json), unknown (could not fetch or parse adagents.json)' - ), - ] - publisher_domain: Annotated[ - str | None, Field(description='The publisher domain where adagents.json was checked') - ] = None - sales_agent_url: Annotated[ - AnyUrl | None, Field(description='The sales agent URL that was validated') - ] = None - violation: Annotated[ - Violation | None, - Field( - description='Details about the authorization failure (only present for unauthorized status)' - ), - ] = None diff --git a/src/adcp/types/generated_poc/property/base_property_source.py b/src/adcp/types/generated_poc/property/base_property_source.py deleted file mode 100644 index 3b90cc6f3..000000000 --- a/src/adcp/types/generated_poc/property/base_property_source.py +++ /dev/null @@ -1,83 +0,0 @@ -# generated by datamodel-codegen: -# filename: property/base_property_source.json -# timestamp: 2026-10-02T23:18:44+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..core import identifier, property_id, property_tag - - -class BasePropertySource1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - selection_type: Annotated[ - Literal['publisher_tags'], - Field(description='Discriminator indicating selection by property tags within a publisher'), - ] = 'publisher_tags' - publisher_domain: Annotated[ - str, - Field( - description="Domain where publisher's adagents.json is hosted (e.g., 'raptive.com')", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - tags: Annotated[ - list[property_tag.PropertyTag], - Field( - description="Property tags from the publisher's adagents.json. Selects all properties with these tags.", - min_length=1, - ), - ] - - -class BasePropertySource2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - selection_type: Annotated[ - Literal['publisher_ids'], - Field( - description='Discriminator indicating selection by specific property IDs within a publisher' - ), - ] = 'publisher_ids' - publisher_domain: Annotated[ - str, - Field( - description="Domain where publisher's adagents.json is hosted (e.g., 'raptive.com')", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - property_ids: Annotated[ - list[property_id.PropertyId], - Field(description="Specific property IDs from the publisher's adagents.json", min_length=1), - ] - - -class BasePropertySource3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - selection_type: Annotated[ - Literal['identifiers'], - Field(description='Discriminator indicating selection by direct identifiers'), - ] = 'identifiers' - identifiers: Annotated[ - list[identifier.Identifier], - Field(description='Direct property identifiers (domains, app IDs, etc.)', min_length=1), - ] - - -BasePropertySource = Annotated[ - BasePropertySource1 | BasePropertySource2 | BasePropertySource3, - Field( - description='A source of properties for a property list. Supports three selection patterns: publisher with tags, publisher with property IDs, or direct identifiers.', - discriminator='selection_type', - title='Base Property Source', - ), -] diff --git a/src/adcp/types/generated_poc/property/create_property_list_request.py b/src/adcp/types/generated_poc/property/create_property_list_request.py deleted file mode 100644 index dcfedc0e9..000000000 --- a/src/adcp/types/generated_poc/property/create_property_list_request.py +++ /dev/null @@ -1,59 +0,0 @@ -# generated by datamodel-codegen: -# filename: property/create_property_list_request.json -# timestamp: 2026-10-02T23:10:32+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import ConfigDict, Field - -from ..core import account_ref, brand_ref -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.version_envelope import AdcpVersionEnvelope -from . import base_property_source, property_list_filters - - -class CreatePropertyListRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - account: Annotated[ - account_ref.AccountReference | None, - Field( - description='Account that will own the list. Pass a natural key (brand, operator, optional sandbox) or a seller-assigned account_id from list_accounts. When omitted, this task applies its task-local single-account shortcut: if exactly one account is accessible to the authenticated caller, the seller may assign the list to that account; otherwise it MUST return an account-required or ambiguous-account error. Omission MUST NOT mean an undocumented credential-local default account.' - ), - ] = None - name: Annotated[str, Field(description='Human-readable name for the list')] - description: Annotated[str | None, Field(description="Description of the list's purpose")] = ( - None - ) - base_properties: Annotated[ - list[base_property_source.BasePropertySource] | None, - Field( - description="Array of property sources to evaluate. Each entry is a discriminated union: publisher_tags (publisher_domain + tags), publisher_ids (publisher_domain + property_ids), or identifiers (direct identifiers). If omitted, queries the agent's entire property database.", - min_length=1, - ), - ] = None - filters: Annotated[ - property_list_filters.PropertyListFilters | None, - Field(description='Dynamic filters to apply when resolving the list'), - ] = None - brand: Annotated[ - brand_ref.BrandReference | None, - Field( - description='Brand reference. When provided, the agent automatically applies appropriate rules based on brand characteristics (industry, target_audience, etc.). Resolved at execution time.' - ), - ] = None - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for this request. Prevents duplicate property list creation on retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/property/create_property_list_response.py b/src/adcp/types/generated_poc/property/create_property_list_response.py deleted file mode 100644 index cc0c0d57a..000000000 --- a/src/adcp/types/generated_poc/property/create_property_list_response.py +++ /dev/null @@ -1,36 +0,0 @@ -# generated by datamodel-codegen: -# filename: property/create_property_list_response.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import ConfigDict, Field, StrictBool - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from . import property_list - - -class CreatePropertyListResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - list: Annotated[property_list.PropertyList, Field(description='The created property list')] - auth_token: Annotated[ - str, - Field( - description='Token that can be shared with sellers to authorize fetching this list. Store this - it is only returned at creation time.' - ), - ] - replayed: Annotated[ - StrictBool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. `replayed` appears only when the request actually resolved through the idempotency cache. Pure reads may ignore an optional `idempotency_key`; when a seller voluntarily caches keyed reads, those responses use the same replay indicator and full cache contract." - ), - ] = False - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/property/delete_property_list_request.py b/src/adcp/types/generated_poc/property/delete_property_list_request.py deleted file mode 100644 index a2db171c3..000000000 --- a/src/adcp/types/generated_poc/property/delete_property_list_request.py +++ /dev/null @@ -1,38 +0,0 @@ -# generated by datamodel-codegen: -# filename: property/delete_property_list_request.json -# timestamp: 2026-10-02T23:10:32+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import ConfigDict, Field - -from ..core import account_ref -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.version_envelope import AdcpVersionEnvelope - - -class DeletePropertyListRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - list_id: Annotated[str, Field(description='ID of the property list to delete')] - account: Annotated[ - account_ref.AccountReference | None, - Field( - description='Account that owns the list. Required when the authenticated agent has access to multiple accounts; optional otherwise.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for at-most-once execution. If a request with the same key has already been processed, the server returns the original response without re-processing. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] diff --git a/src/adcp/types/generated_poc/property/delete_property_list_response.py b/src/adcp/types/generated_poc/property/delete_property_list_response.py deleted file mode 100644 index 764db2fb4..000000000 --- a/src/adcp/types/generated_poc/property/delete_property_list_response.py +++ /dev/null @@ -1,30 +0,0 @@ -# generated by datamodel-codegen: -# filename: property/delete_property_list_response.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import ConfigDict, Field, StrictBool - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope - - -class DeletePropertyListResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - deleted: Annotated[StrictBool, Field(description='Whether the list was successfully deleted')] - list_id: Annotated[str, Field(description='ID of the deleted list')] - replayed: Annotated[ - StrictBool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. `replayed` appears only when the request actually resolved through the idempotency cache. Pure reads may ignore an optional `idempotency_key`; when a seller voluntarily caches keyed reads, those responses use the same replay indicator and full cache contract." - ), - ] = False - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/property/delivery_record.py b/src/adcp/types/generated_poc/property/delivery_record.py deleted file mode 100644 index d09d59e17..000000000 --- a/src/adcp/types/generated_poc/property/delivery_record.py +++ /dev/null @@ -1,39 +0,0 @@ -# generated by datamodel-codegen: -# filename: property/delivery_record.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, ConfigDict, Field - -from ..core import ext as ext_1 -from ..core import identifier as identifier_1 - - -class DeliveryRecord(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - identifier: Annotated[ - identifier_1.Identifier, - Field(description='The property identifier where impressions were delivered'), - ] - impressions: Annotated[ - SchemaInt, Field(description='Number of impressions delivered to this identifier', ge=0) - ] - record_id: Annotated[ - str | None, - Field( - description='Optional client-provided ID for correlating results back to source data' - ), - ] = None - sales_agent_url: Annotated[ - AnyUrl | None, - Field( - description="URL of the sales agent that sold this inventory. If provided, authorization is validated against the property's adagents.json." - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/property/get_property_list_request.py b/src/adcp/types/generated_poc/property/get_property_list_request.py deleted file mode 100644 index b245f3afe..000000000 --- a/src/adcp/types/generated_poc/property/get_property_list_request.py +++ /dev/null @@ -1,56 +0,0 @@ -# generated by datamodel-codegen: -# filename: property/get_property_list_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictBool - -from ..core import account_ref -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.version_envelope import AdcpVersionEnvelope - - -class Pagination(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - max_results: Annotated[ - SchemaInt | None, - Field(description='Maximum number of identifiers to return per page', ge=1, le=10000), - ] = 1000 - cursor: Annotated[ - str | None, - Field(description='Opaque cursor from a previous response to fetch the next page'), - ] = None - - -class GetPropertyListRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - list_id: Annotated[str, Field(description='ID of the property list to retrieve')] - account: Annotated[ - account_ref.AccountReference | None, - Field( - description='Account that owns the list. Required when the authenticated agent has access to multiple accounts and the list_id is not globally unique within that scope; optional otherwise.' - ), - ] = None - resolve: Annotated[ - StrictBool | None, - Field( - description='Whether to apply filters and return resolved identifiers (default: true)' - ), - ] = True - pagination: Annotated[ - Pagination | None, - Field( - description='Pagination parameters. Uses higher limits than standard pagination because property lists can contain tens of thousands of identifiers.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/property/get_property_list_response.py b/src/adcp/types/generated_poc/property/get_property_list_response.py deleted file mode 100644 index 6ddafedd2..000000000 --- a/src/adcp/types/generated_poc/property/get_property_list_response.py +++ /dev/null @@ -1,53 +0,0 @@ -# generated by datamodel-codegen: -# filename: property/get_property_list_response.json -# timestamp: 2026-05-22T13:15:12+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import AwareDatetime, ConfigDict, Field - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core import identifier, pagination_response -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from . import property_list - - -_list = list # alias to avoid shadowing by field name - - -class GetPropertyListResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - list: Annotated[ - property_list.PropertyList, - Field(description='The property list metadata (always returned)'), - ] - identifiers: Annotated[ - _list[identifier.Identifier] | None, - Field( - description='Resolved identifiers that passed filters (if resolve=true). Cache these locally for real-time use.' - ), - ] = None - pagination: pagination_response.PaginationResponse | None = None - resolved_at: Annotated[ - AwareDatetime | None, Field(description='When the list was resolved') - ] = None - cache_valid_until: Annotated[ - AwareDatetime | None, - Field( - description='Cache expiration timestamp. Re-fetch the list after this time to get updated identifiers.' - ), - ] = None - coverage_gaps: Annotated[ - dict[str, _list[identifier.Identifier]] | None, - Field( - description="Properties included in the list despite missing feature data. Only present when a feature_requirement has if_not_covered='include'. Maps feature_id to list of identifiers not covered for that feature." - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/property/list_property_lists_request.py b/src/adcp/types/generated_poc/property/list_property_lists_request.py deleted file mode 100644 index 79172da88..000000000 --- a/src/adcp/types/generated_poc/property/list_property_lists_request.py +++ /dev/null @@ -1,33 +0,0 @@ -# generated by datamodel-codegen: -# filename: property/list_property_lists_request.json -# timestamp: 2026-10-02T23:10:32+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import ConfigDict, Field - -from ..core import account_ref -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core import pagination_request -from ..core.version_envelope import AdcpVersionEnvelope - - -class ListPropertyListsRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - account: Annotated[ - account_ref.AccountReference | None, - Field( - description='Filter to lists owned by this account. When omitted, returns lists across all accounts accessible to the authenticated agent.' - ), - ] = None - name_contains: Annotated[ - str | None, Field(description='Filter to lists whose name contains this string') - ] = None - pagination: pagination_request.PaginationRequest | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/property/list_property_lists_response.py b/src/adcp/types/generated_poc/property/list_property_lists_response.py deleted file mode 100644 index a1559df93..000000000 --- a/src/adcp/types/generated_poc/property/list_property_lists_response.py +++ /dev/null @@ -1,29 +0,0 @@ -# generated by datamodel-codegen: -# filename: property/list_property_lists_response.json -# timestamp: 2026-05-22T13:15:12+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import ConfigDict, Field - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core import pagination_response -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from . import property_list - - -class ListPropertyListsResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - lists: Annotated[ - list[property_list.PropertyList], - Field(description='Array of property lists (metadata only, not resolved properties)'), - ] - pagination: pagination_response.PaginationResponse | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/property/property_error.py b/src/adcp/types/generated_poc/property/property_error.py deleted file mode 100644 index d71392538..000000000 --- a/src/adcp/types/generated_poc/property/property_error.py +++ /dev/null @@ -1,33 +0,0 @@ -# generated by datamodel-codegen: -# filename: property/property_error.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..core import property as property_1 - - -class Code(StrEnum): - PROPERTY_NOT_FOUND = 'PROPERTY_NOT_FOUND' - PROPERTY_NOT_MONITORED = 'PROPERTY_NOT_MONITORED' - LIST_NOT_FOUND = 'LIST_NOT_FOUND' - LIST_ACCESS_DENIED = 'LIST_ACCESS_DENIED' - METHODOLOGY_NOT_SUPPORTED = 'METHODOLOGY_NOT_SUPPORTED' - JURISDICTION_NOT_SUPPORTED = 'JURISDICTION_NOT_SUPPORTED' - - -class PropertyError(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - code: Annotated[Code, Field(description='Error code')] - property: Annotated[ - property_1.Property | None, Field(description='The property that caused the error') - ] = None - message: Annotated[str, Field(description='Human-readable error message')] diff --git a/src/adcp/types/generated_poc/property/property_feature.py b/src/adcp/types/generated_poc/property/property_feature.py deleted file mode 100644 index 4d7a1cbef..000000000 --- a/src/adcp/types/generated_poc/property/property_feature.py +++ /dev/null @@ -1,22 +0,0 @@ -# generated by datamodel-codegen: -# filename: property/property_feature.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - - -class PropertyFeature(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - feature_id: Annotated[str, Field(description='Identifier for the feature being assessed')] - value: Annotated[str, Field(description='The feature value')] - source: Annotated[ - str | None, - Field(description='Source of the feature data (e.g., app_store_privacy_label, tcf_string)'), - ] = None diff --git a/src/adcp/types/generated_poc/property/property_feature_definition.py b/src/adcp/types/generated_poc/property/property_feature_definition.py deleted file mode 100644 index 56cbfd1fd..000000000 --- a/src/adcp/types/generated_poc/property/property_feature_definition.py +++ /dev/null @@ -1,80 +0,0 @@ -# generated by datamodel-codegen: -# filename: property/property_feature_definition.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field, StrictFloat - -from ..core import ext as ext_1 - - -class Type(StrEnum): - binary = 'binary' - quantitative = 'quantitative' - categorical = 'categorical' - - -class Range(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - min: Annotated[StrictFloat, Field(description='Minimum value')] - max: Annotated[StrictFloat, Field(description='Maximum value')] - - -class Coverage(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_types: Annotated[ - list[str] | None, Field(description='Property types this feature applies to') - ] = None - countries: Annotated[ - list[str] | None, Field(description='Countries where this feature is available') - ] = None - - -class PropertyFeatureDefinition(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - feature_id: Annotated[ - str, - Field( - description="Unique identifier for this feature (e.g., 'consent_quality', 'carbon_score'). Features prefixed with 'registry:' reference standardized policies from the shared policy registry (e.g., 'registry:us_coppa', 'registry:uk_hfss'). Unprefixed feature IDs are agent-defined." - ), - ] - name: Annotated[str, Field(description='Human-readable name for the feature')] - description: Annotated[ - str | None, Field(description='Description of what this feature measures or represents') - ] = None - type: Annotated[ - Type, - Field( - description='The type of values this feature produces: binary (true/false), quantitative (numeric range), categorical (enumerated values)' - ), - ] - range: Annotated[ - Range | None, Field(description='For quantitative features, the valid range of values') - ] = None - allowed_values: Annotated[ - list[str] | None, Field(description='For categorical features, the set of valid values') - ] = None - coverage: Annotated[ - Coverage | None, Field(description='What this feature covers (empty arrays = all)') - ] = None - methodology_url: Annotated[ - AnyUrl, - Field( - description='URL to documentation explaining how this feature is calculated/measured' - ), - ] - methodology_version: Annotated[ - str | None, Field(description='Version identifier for the methodology (for audit trails)') - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/property/property_feature_result.py b/src/adcp/types/generated_poc/property/property_feature_result.py deleted file mode 100644 index 2fe9a72f7..000000000 --- a/src/adcp/types/generated_poc/property/property_feature_result.py +++ /dev/null @@ -1,45 +0,0 @@ -# generated by datamodel-codegen: -# filename: property/property_feature_result.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - -from ..core import ext as ext_1 -from ..core import property_id -from . import property_feature_value - - -class CoverageStatus(StrEnum): - covered = 'covered' - not_covered = 'not_covered' - pending = 'pending' - - -class PropertyFeatureResult(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property: Annotated[ - str | property_id.PropertyId, Field(description='The property these features apply to') - ] - features: Annotated[ - dict[str, property_feature_value.PropertyFeatureValue] | None, - Field(description='Map of feature_id to feature value'), - ] = None - coverage_status: Annotated[ - CoverageStatus, - Field( - description='Whether this property is covered by this governance agent: covered (has data), not_covered (not measured), pending (measurement in progress)' - ), - ] - last_evaluated: Annotated[ - AwareDatetime | None, - Field(description='When features were last evaluated for this property'), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/property/property_feature_value.py b/src/adcp/types/generated_poc/property/property_feature_value.py deleted file mode 100644 index 0b2e7b40b..000000000 --- a/src/adcp/types/generated_poc/property/property_feature_value.py +++ /dev/null @@ -1,51 +0,0 @@ -# generated by datamodel-codegen: -# filename: property/property_feature_value.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat - -from ..core import ext as ext_1 - - -class PropertyFeatureValue(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - value: Annotated[ - StrictBool | StrictFloat | str, - Field( - description='The feature value. Type depends on feature definition: boolean for binary, number for quantitative, string for categorical.' - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of measurement for quantitative values (e.g., 'gCO2e/1000_impressions', 'percentage')" - ), - ] = None - confidence: Annotated[ - StrictFloat | None, - Field(description='Confidence score for this value (0-1)', ge=0.0, le=1.0), - ] = None - measured_at: Annotated[ - AwareDatetime | None, Field(description='When this specific value was measured') - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field( - description='When this certification/value expires (for time-limited certifications)' - ), - ] = None - methodology_version: Annotated[ - str | None, Field(description='Version of the methodology used to calculate this value') - ] = None - details: Annotated[ - dict[str, Any] | None, - Field(description='Additional vendor-specific details about this measurement'), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/property/property_list.py b/src/adcp/types/generated_poc/property/property_list.py deleted file mode 100644 index d68a10c20..000000000 --- a/src/adcp/types/generated_poc/property/property_list.py +++ /dev/null @@ -1,74 +0,0 @@ -# generated by datamodel-codegen: -# filename: property/property_list.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - -from ..core import account_ref, brand_ref, vendor_pricing_option -from . import base_property_source, property_list_filters - - -class PropertyList(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - list_id: Annotated[str, Field(description='Unique identifier for this property list')] - name: Annotated[str, Field(description='Human-readable name for the list')] - description: Annotated[str | None, Field(description="Description of the list's purpose")] = ( - None - ) - account: Annotated[ - account_ref.AccountReference | None, - Field( - description='Account that owns this list. Returned as account_id form (seller-assigned identifier).' - ), - ] = None - base_properties: Annotated[ - list[base_property_source.BasePropertySource] | None, - Field( - description="Array of property sources to evaluate. Each entry is a discriminated union: publisher_tags (publisher_domain + tags), publisher_ids (publisher_domain + property_ids), or identifiers (direct identifiers). If omitted, queries the agent's entire property database." - ), - ] = None - filters: Annotated[ - property_list_filters.PropertyListFilters | None, - Field(description='Dynamic filters applied when resolving the list'), - ] = None - brand: Annotated[ - brand_ref.BrandReference | None, - Field( - description='Brand reference used to automatically apply appropriate rules. Resolved to full brand identity at execution time.' - ), - ] = None - webhook_url: Annotated[ - AnyUrl | None, - Field(description='URL to receive notifications when the resolved list changes'), - ] = None - cache_duration_hours: Annotated[ - SchemaInt | None, - Field( - description='Recommended cache duration for resolved list. Consumers should re-fetch after this period.', - ge=1, - ), - ] = 24 - created_at: Annotated[AwareDatetime | None, Field(description='When the list was created')] = ( - None - ) - updated_at: Annotated[ - AwareDatetime | None, Field(description='When the list was last modified') - ] = None - property_count: Annotated[ - SchemaInt | None, - Field(description='Number of properties in the resolved list (at time of last resolution)'), - ] = None - pricing_options: Annotated[ - list[vendor_pricing_option.VendorPricingOption] | None, - Field( - description='Pricing options for this property list. Present when the requesting account has a billing relationship with the list provider. The buyer passes the selected pricing_option_id in report_usage.', - min_length=1, - ), - ] = None diff --git a/src/adcp/types/generated_poc/property/property_list_changed_webhook.py b/src/adcp/types/generated_poc/property/property_list_changed_webhook.py deleted file mode 100644 index 307f85d72..000000000 --- a/src/adcp/types/generated_poc/property/property_list_changed_webhook.py +++ /dev/null @@ -1,61 +0,0 @@ -# generated by datamodel-codegen: -# filename: property/property_list_changed_webhook.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field - -from ..core import ext as ext_1 - - -class ChangeSummary(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - properties_added: Annotated[ - SchemaInt | None, Field(description='Number of properties added since last resolution') - ] = None - properties_removed: Annotated[ - SchemaInt | None, Field(description='Number of properties removed since last resolution') - ] = None - total_properties: Annotated[ - SchemaInt | None, Field(description='Total properties in the resolved list') - ] = None - - -class PropertyListChangedWebhook(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - idempotency_key: Annotated[ - str, - Field( - description='Sender-generated key stable across retries of the same webhook event. Governance agents MUST generate a cryptographically random value (UUID v4 recommended) per distinct list-change event and reuse the same key on every retry. Recipients MUST dedupe by this key, scoped to the authenticated sender identity established by the RFC 9421 signing key — keys from different governance agents are independent.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - event: Annotated[Literal['property_list_changed'], Field(description='The event type')] = 'property_list_changed' - list_id: Annotated[str, Field(description='ID of the property list that changed')] - list_name: Annotated[str | None, Field(description='Name of the property list')] = None - change_summary: Annotated[ - ChangeSummary | None, Field(description='Summary of changes to the resolved list') - ] = None - resolved_at: Annotated[AwareDatetime, Field(description='When the list was re-resolved')] - cache_valid_until: Annotated[ - AwareDatetime | None, - Field(description='When the consumer should refresh from the governance agent'), - ] = None - signature: Annotated[ - str, - Field( - deprecated=True, - description='Deprecated 3.x compatibility marker. Property-list webhooks previously required this field but did not define an implementable signature algorithm, signed bytes, or key-discovery contract. RFC 9421 signatures are carried in the Signature and Signature-Input HTTP headers and bind the complete payload through Content-Digest. New 3.2 senders MUST set this field to the literal value `rfc9421`; receivers MUST accept any string from an older 3.x sender and MUST ignore it when authenticating a delivery. The field remains required through 3.x for schema compatibility and is removed in 4.0.', - ), - ] - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/property/property_list_filters.py b/src/adcp/types/generated_poc/property/property_list_filters.py deleted file mode 100644 index 011cdfa59..000000000 --- a/src/adcp/types/generated_poc/property/property_list_filters.py +++ /dev/null @@ -1,54 +0,0 @@ -# generated by datamodel-codegen: -# filename: property/property_list_filters.json -# timestamp: 2026-10-02T13:37:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from adcp.types._scalar import ScalarStr -from pydantic import ConfigDict, Field - -from ..core import feature_requirement, identifier -from ..enums import channels, property_type - - -class CountriesAllItem(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{2}$'} - - -class PropertyListFilters(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - countries_all: Annotated[ - list[CountriesAllItem] | None, - Field( - description='Property must have feature data for ALL listed countries (ISO codes). When omitted, no country restriction is applied.', - min_length=1, - ), - ] = None - channels_any: Annotated[ - list[channels.MediaChannel] | None, - Field( - description='Property must support ANY of the listed channels. When omitted, no channel restriction is applied.', - min_length=1, - ), - ] = None - property_types: Annotated[ - list[property_type.PropertyType] | None, - Field(description='Filter to these property types', min_length=1), - ] = None - feature_requirements: Annotated[ - list[feature_requirement.FeatureRequirement] | None, - Field( - description='Feature-based requirements. Property must pass ALL requirements (AND logic).', - min_length=1, - ), - ] = None - exclude_identifiers: Annotated[ - list[identifier.Identifier] | None, - Field(description='Identifiers to always exclude from results', min_length=1), - ] = None diff --git a/src/adcp/types/generated_poc/property/update_property_list_request.py b/src/adcp/types/generated_poc/property/update_property_list_request.py deleted file mode 100644 index ab995849a..000000000 --- a/src/adcp/types/generated_poc/property/update_property_list_request.py +++ /dev/null @@ -1,63 +0,0 @@ -# generated by datamodel-codegen: -# filename: property/update_property_list_request.json -# timestamp: 2026-10-02T23:10:32+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import AnyUrl, ConfigDict, Field - -from ..core import account_ref, brand_ref -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.version_envelope import AdcpVersionEnvelope -from . import base_property_source, property_list_filters - - -class UpdatePropertyListRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - list_id: Annotated[str, Field(description='ID of the property list to update')] - account: Annotated[ - account_ref.AccountReference | None, - Field( - description='Account that owns the list. Required when the authenticated agent has access to multiple accounts; optional otherwise.' - ), - ] = None - name: Annotated[str | None, Field(description='New name for the list')] = None - description: Annotated[str | None, Field(description='New description')] = None - base_properties: Annotated[ - list[base_property_source.BasePropertySource] | None, - Field( - description='Complete replacement for the base properties list (not a patch). Each entry is a discriminated union: publisher_tags (publisher_domain + tags), publisher_ids (publisher_domain + property_ids), or identifiers (direct identifiers).' - ), - ] = None - filters: Annotated[ - property_list_filters.PropertyListFilters | None, - Field(description='Complete replacement for the filters (not a patch)'), - ] = None - brand: Annotated[ - brand_ref.BrandReference | None, - Field( - description='Update brand reference. Resolved to full brand identity at execution time.' - ), - ] = None - webhook_url: Annotated[ - AnyUrl | None, - Field( - description='Update the webhook URL for list change notifications (set to empty string to remove)' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for at-most-once execution. If a request with the same key has already been processed, the server returns the original response without re-processing. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] diff --git a/src/adcp/types/generated_poc/property/update_property_list_response.py b/src/adcp/types/generated_poc/property/update_property_list_response.py deleted file mode 100644 index aedc61878..000000000 --- a/src/adcp/types/generated_poc/property/update_property_list_response.py +++ /dev/null @@ -1,30 +0,0 @@ -# generated by datamodel-codegen: -# filename: property/update_property_list_response.json -# timestamp: 2026-10-03T15:09:44+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import ConfigDict, Field, StrictBool - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from . import property_list - - -class UpdatePropertyListResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - list: Annotated[property_list.PropertyList, Field(description='The updated property list')] - replayed: Annotated[ - StrictBool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. `replayed` appears only when the request actually resolved through the idempotency cache. Pure reads may ignore an optional `idempotency_key`; when a seller voluntarily caches keyed reads, those responses use the same replay indicator and full cache contract." - ), - ] = False - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/property/validate_property_delivery_request.py b/src/adcp/types/generated_poc/property/validate_property_delivery_request.py deleted file mode 100644 index e4afe9e45..000000000 --- a/src/adcp/types/generated_poc/property/validate_property_delivery_request.py +++ /dev/null @@ -1,44 +0,0 @@ -# generated by datamodel-codegen: -# filename: property/validate_property_delivery_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import ConfigDict, Field, StrictBool - -from ..core import account_ref -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.version_envelope import AdcpVersionEnvelope -from . import delivery_record - - -class ValidatePropertyDeliveryRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - list_id: Annotated[str, Field(description='ID of the property list to validate against')] - account: Annotated[ - account_ref.AccountReference | None, - Field( - description='Account that owns the list. Required when the authenticated agent has access to multiple accounts; optional otherwise.' - ), - ] = None - records: Annotated[ - list[delivery_record.DeliveryRecord], - Field( - description='Delivery records to validate. Each record represents impressions delivered to a property identifier.', - max_length=10000, - min_length=1, - ), - ] - include_compliant: Annotated[ - StrictBool | None, - Field( - description='Include compliant records in results (default: only return non_compliant, unmodeled, and unidentified)' - ), - ] = False - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/property/validate_property_delivery_response.py b/src/adcp/types/generated_poc/property/validate_property_delivery_response.py deleted file mode 100644 index ccc3fd0f0..000000000 --- a/src/adcp/types/generated_poc/property/validate_property_delivery_response.py +++ /dev/null @@ -1,149 +0,0 @@ -# generated by datamodel-codegen: -# filename: property/validate_property_delivery_response.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from . import validation_result - - -class Summary(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - total_records: Annotated[SchemaInt, Field(description='Total number of records validated')] - total_impressions: Annotated[ - SchemaInt, Field(description='Total impressions across all records') - ] - compliant_records: Annotated[ - SchemaInt, Field(description='Number of records with compliant status') - ] - compliant_impressions: Annotated[ - SchemaInt, Field(description='Impressions from compliant records') - ] - non_compliant_records: Annotated[ - SchemaInt, Field(description='Number of records with non_compliant status') - ] - non_compliant_impressions: Annotated[ - SchemaInt, Field(description='Impressions from non_compliant records') - ] - not_covered_records: Annotated[ - SchemaInt, - Field( - description='Number of records where identifier was recognized but no data available' - ), - ] - not_covered_impressions: Annotated[ - SchemaInt, Field(description='Impressions from not_covered records') - ] - unidentified_records: Annotated[ - SchemaInt, Field(description='Number of records where identifier type was not resolvable') - ] - unidentified_impressions: Annotated[ - SchemaInt, Field(description='Impressions from unidentified records') - ] - - -class Aggregate(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - score: Annotated[ - StrictFloat | None, - Field(description='Numeric score (0-100 scale typical, but agent-defined)'), - ] = None - grade: Annotated[ - str | None, - Field(description="Letter grade or category (e.g., 'A+', 'B-', 'Gold', 'Compliant')"), - ] = None - label: Annotated[ - str | None, - Field(description="Human-readable summary (e.g., '85% compliant', 'High quality')"), - ] = None - methodology_url: Annotated[ - AnyUrl | None, Field(description='URL explaining how this aggregate was calculated') - ] = None - - -class AuthorizationSummary(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - records_checked: Annotated[ - SchemaInt, Field(description='Number of records with sales_agent_url provided') - ] - impressions_checked: Annotated[ - SchemaInt, Field(description='Total impressions from records with sales_agent_url') - ] - authorized_records: Annotated[ - SchemaInt, Field(description='Number of records where sales agent was authorized') - ] - authorized_impressions: Annotated[ - SchemaInt, Field(description='Impressions from authorized records') - ] - unauthorized_records: Annotated[ - SchemaInt, Field(description='Number of records where sales agent was NOT authorized') - ] - unauthorized_impressions: Annotated[ - SchemaInt, Field(description='Impressions from unauthorized records') - ] - unknown_records: Annotated[ - SchemaInt, - Field( - description='Number of records where authorization could not be determined (adagents.json unavailable)' - ), - ] - unknown_impressions: Annotated[ - SchemaInt, - Field(description='Impressions from records where authorization could not be determined'), - ] - - -class ValidatePropertyDeliveryResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - compliant: Annotated[ - StrictBool | None, - Field( - description='Overall compliance flag for the submitted delivery — true when every record is compliant, false when any record is non_compliant. Derived from summary.non_compliant_records === 0 but surfaced at the root as a convenience signal for buyers. Agents MAY omit this field when the response represents a partial validation (e.g., when include_compliant is false and results only contains non_compliant records); consumers SHOULD fall back to summary counts if compliant is absent.' - ), - ] = None - list_id: Annotated[str, Field(description='ID of the property list validated against')] - summary: Annotated[Summary, Field(description='Aggregate validation statistics')] - aggregate: Annotated[ - Aggregate | None, - Field( - description='Optional aggregate measurements computed by the governance agent. Format and meaning are agent-specific.' - ), - ] = None - authorization_summary: Annotated[ - AuthorizationSummary | None, - Field( - description='Aggregate authorization statistics. Only present if any records included sales_agent_url.' - ), - ] = None - results: Annotated[ - list[validation_result.ValidationResult], - Field( - description='Per-record validation results. By default only includes non_compliant and unknown records. Set include_compliant=true to include all records.' - ), - ] - validated_at: Annotated[ - AwareDatetime, Field(description='Timestamp when validation was performed') - ] - list_resolved_at: Annotated[ - AwareDatetime | None, - Field(description='Timestamp of the property list resolution used for validation'), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/property/validation_result.py b/src/adcp/types/generated_poc/property/validation_result.py deleted file mode 100644 index 416fdff27..000000000 --- a/src/adcp/types/generated_poc/property/validation_result.py +++ /dev/null @@ -1,108 +0,0 @@ -# generated by datamodel-codegen: -# filename: property/validation_result.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictFloat - -from ..core import ext as ext_1 -from ..core import identifier as identifier_1 -from ..enums import feature_check_status -from . import authorization_result - - -class Status(StrEnum): - compliant = 'compliant' - non_compliant = 'non_compliant' - not_covered = 'not_covered' - unidentified = 'unidentified' - - -class Requirement(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - min_value: Annotated[ - StrictFloat | None, Field(description='Minimum value that was required') - ] = None - max_value: Annotated[ - StrictFloat | None, Field(description='Maximum value that was allowed') - ] = None - allowed_values: Annotated[ - list[Any] | None, Field(description='Values that would have been acceptable') - ] = None - - -class Feature(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - feature_id: Annotated[ - str, - Field( - description="Which feature was evaluated. Data features come from the governance agent's feature catalog (e.g., 'mfa_score', 'carbon_score'). Record-level structural checks use reserved namespaces: 'record:list_membership', 'record:excluded', 'delivery:seller_authorization', 'delivery:click_url_presence'. Reserved prefixes: 'record:', 'delivery:'." - ), - ] - status: feature_check_status.FeatureCheckStatus - policy_id: Annotated[ - str | None, - Field( - description='Optional attribution — when this feature was evaluated for the purpose of a specific policy, policy_id references the authorizing PolicyEntry. Property-list agents populate when the validation was motivated by a specific policy. See /docs/governance/policy-attribution.' - ), - ] = None - explanation: Annotated[ - str | None, Field(description='Directional human-readable explanation of the result.') - ] = None - requirement: Annotated[ - Requirement | None, - Field( - description='The feature requirement that was not met. MAY be present on failed features when the caller authored the requirement (e.g., feature_requirements on a property list). The buyer set these thresholds — echoing them back enables fix-and-retry loops without looking up the list definition.' - ), - ] = None - confidence: Annotated[ - StrictFloat | None, - Field( - description='Optional evaluator confidence in this result (0-1). Distinguishes certain verdicts from ambiguous ones.', - ge=0.0, - le=1.0, - ), - ] = None - - -class ValidationResult(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - identifier: Annotated[ - identifier_1.Identifier, Field(description='The identifier that was validated') - ] - record_id: Annotated[ - str | None, Field(description='Client-provided ID from the delivery record (if provided)') - ] = None - status: Annotated[ - Status, - Field( - description='Validation status: compliant (in list), non_compliant (not in list), not_covered (identifier recognized but no data available), unidentified (identifier type not resolvable by this governance agent)' - ), - ] - impressions: Annotated[ - SchemaInt, Field(description='Number of impressions from this record', ge=0) - ] - features: Annotated[ - list[Feature] | None, - Field( - description='Per-feature breakdown for this record. SHOULD include all failed and warning features. MAY include passed features. For property validation the buyer authored the requirement, so the `requirement` that was not met MAY be echoed back on failures — this is contract data, not evaluator IP.' - ), - ] = None - authorization: Annotated[ - authorization_result.AuthorizationResult | None, - Field( - description='Authorization validation result (only present if sales_agent_url was provided in the delivery record)' - ), - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/protocol/__init__.py b/src/adcp/types/generated_poc/protocol/__init__.py deleted file mode 100644 index 935753268..000000000 --- a/src/adcp/types/generated_poc/protocol/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# generated by datamodel-codegen: -# filename: .schema_temp -# timestamp: 2026-01-25T21:17:54+00:00 diff --git a/src/adcp/types/generated_poc/protocol/get_adcp_capabilities_request.py b/src/adcp/types/generated_poc/protocol/get_adcp_capabilities_request.py deleted file mode 100644 index a88acdfd7..000000000 --- a/src/adcp/types/generated_poc/protocol/get_adcp_capabilities_request.py +++ /dev/null @@ -1,37 +0,0 @@ -# generated by datamodel-codegen: -# filename: protocol/get_adcp_capabilities_request.json -# timestamp: 2026-05-22T13:15:12+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from pydantic import ConfigDict, Field - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.version_envelope import AdcpVersionEnvelope - - -class Protocol(StrEnum): - media_buy = 'media_buy' - signals = 'signals' - governance = 'governance' - sponsored_intelligence = 'sponsored_intelligence' - creative = 'creative' - - -class GetAdcpCapabilitiesRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - protocols: Annotated[ - list[Protocol] | None, - Field( - description='Specific protocols to query capabilities for. If omitted, returns capabilities for all supported protocols.', - min_length=1, - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/protocol/get_adcp_capabilities_response.py b/src/adcp/types/generated_poc/protocol/get_adcp_capabilities_response.py deleted file mode 100644 index f11687164..000000000 --- a/src/adcp/types/generated_poc/protocol/get_adcp_capabilities_response.py +++ /dev/null @@ -1,2745 +0,0 @@ -# generated by datamodel-codegen: -# filename: protocol/get_adcp_capabilities_response.json -# timestamp: 2026-10-04T01:57:23+00:00 - -from __future__ import annotations - -from datetime import date -from adcp.types._str_enum import StrEnum -from typing import Any, Annotated, Dict, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarInt, ScalarStr -from pydantic import ( - AnyUrl, - AwareDatetime, - ConfigDict, - Field, - RootModel, - StrictBool, - StrictFloat, -) - -from ..core import account_timezone_capability, attestation_capabilities, audience_activation_method -from ..core import context as context_1 -from ..core import creative_operation_format_declaration, duration, error, experimental_feature_id -from ..core import ext as ext_1 -from ..core import ( - geo_place_support, - geo_place_system, - geo_region_support, - locale_tag, - macro_resolution_capability, - media_buy_features, - media_buy_frequency_cap_capability, - postal_area_support, - reporting_delivery_capabilities, - reporting_verification_profile_set, - vendor_metric_id, -) -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import ( - account_currency_mode, - action_source, - age_verification_method, - billing_party, - channels, - cloud_storage_protocol, - creative_selection_strategy, -) -from ..enums import event_type as event_type_1 -from ..enums import ( - indicator_type, - match_type, - notification_type, - pricing_model, - property_type, - reach_unit, - representation_selection_strategy, - right_type, - right_use, - specialism, - transport_mode, - uid_type, - vast_version, -) -from ..governance import accepted_governance_agents as accepted_governance_agents_1 -from ..sponsored_intelligence import si_capabilities - - -class MajorVersion(ScalarInt): - __slots__ = () - _constraints = {'ge': 1} - - -class SupportedVersion(ScalarStr): - __slots__ = () - _constraints = { - 'pattern': '^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?)?$', - } - - -class Idempotency(AdCPBaseModel): - supported: Annotated[ - Literal[True], - Field( - description='Discriminator. True means the seller deduplicates replays — a repeat of the same idempotency_key within replay_ttl_seconds returns the cached response without re-executing side effects.' - ), - ] - replay_ttl_seconds: Annotated[ - SchemaInt, - Field( - description="How long the seller retains a canonical response or committed-outcome tombstone for an idempotency_key, measured from the successful mutation's durable commit time. The completed key record and canonical request hash MUST remain available until at least committed_at + this interval, independently of the lifecycle of any resource the request created; a UNIQUE idempotency_key column on the resource row alone does not satisfy this contract. Within this window, a replay with the same key + equivalent canonical payload returns the cached response, or COMMITTED_RESOURCE_PURGED when the mutation committed but the affected resource was independently deleted or purged before the canonical success response could be durably recorded; a replay with a different canonical payload returns IDEMPOTENCY_CONFLICT. An unresolved in-flight or reconciliation-required claim is retained regardless of this completed-entry clock. A replay past a completed entry's window returns IDEMPOTENCY_EXPIRED when the seller can still distinguish 'seen and evicted' from 'never seen'. Minimum 3600 (1h); recommended 86400 (24h). Maximum 604800 (7 days) — longer windows force buyers to retain secret keys at rest for extended periods and grow the seller's cache table without bounded benefit.", - ge=3600, - le=604800, - ), - ] - in_flight_max_seconds: Annotated[ - SchemaInt | None, - Field( - description="Maximum active execution lease in seconds before the seller stops or replaces the original handler and transitions the durable idempotency claim to reconciliation-required per L1/security.mdx rule 9. Expiry does not release or evict an unresolved claim and never permits reinvocation while commit state is ambiguous. Buyer SDKs use this value to cap an individual retry wait when they see `IDEMPOTENCY_IN_FLIGHT`, rather than using the much wider `replay_ttl_seconds` ceiling; it is not a deadline after which they may mint a fresh key. Optional in 3.1 (additive declaration); SDKs that don't see the field fall back to rule 9's order-of-magnitude SHOULD heuristic. Required when `supported: true` in 4.0. MUST be no greater than `replay_ttl_seconds`; validators MUST enforce this cross-field constraint at the test layer since JSON Schema cannot express field-relative bounds. A buyer that observes top-level `error.retry_after` exceeding this value MAY treat that as a seller bug for an actively executing attempt.", - ge=1, - le=604800, - ), - ] = None - account_id_is_opaque: Annotated[ - StrictBool | None, - Field( - description="When true, the seller derives `account_id` via an HKDF-based one-way transform of the buyer's natural account key rather than echoing the natural key on the wire. Buyers MUST NOT attempt to invert the opaque id and MUST treat it as a blind handle scoped to this seller. Absent or false, callers should assume `account_id` is the natural key (or a server-assigned but non-opaque id). This flag does not change the wire shape, but it DOES change buyer behavior — buyers MUST NOT cache, log, or treat `account_id` as a natural-key analog when this flag is true. Migration note for sellers already returning an opaque id without this flag: set it to true at the next capabilities refresh so buyers stop inferring natural-key semantics; until set, new-buyer replay/retry logic will misclassify these ids as natural keys." - ), - ] = False - - -class Idempotency1(AdCPBaseModel): - supported: Annotated[ - Literal[False], - Field(description='Discriminator. False means the seller does not deduplicate retries.'), - ] - - -class SupportedSection(StrEnum): - notification_configs = 'notification_configs' - reporting_destinations = 'reporting_destinations' - declarations = 'declarations' - - -class Pattern(StrEnum): - file_transfer = 'file_transfer' - warehouse_materialization = 'warehouse_materialization' - dataset_share = 'dataset_share' - - -class Transport(ScalarStr): - __slots__ = () - _constraints = {'max_length': 64, 'min_length': 1, 'pattern': '^[a-z][a-z0-9_.-]*$'} - - -class Format(StrEnum): - jsonl = 'jsonl' - csv = 'csv' - parquet = 'parquet' - avro = 'avro' - orc = 'orc' - - -class AccessMode(Transport): - pass - - -class RegistrationTask(StrEnum): - sync_agent_notification_configs = 'sync_agent_notification_configs' - sync_principal = 'sync_principal' - - -class Notifications(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supported: Annotated[ - Literal[True], - Field( - description='Discriminator. True means the seller accepts the declared registration_task for capabilities.changed subscriptions.' - ), - ] - registration_task: Annotated[ - RegistrationTask, - Field( - description='Task consumers call to manage their caller-scoped agent-level subscriber set.' - ), - ] - event_types: Annotated[ - list[Literal['capabilities.changed']], - Field( - description='Agent-level notification types this seller can emit for capability changes. Currently only `capabilities.changed` is defined.', - min_length=1, - ), - ] - coalescence_window_seconds: Annotated[ - SchemaInt | None, - Field( - description='Maximum coalescence window the seller applies before firing a `capabilities.changed` webhook after a burst of related capability edits. The default recommendation is at most 300 seconds; latency-sensitive deactivations SHOULD fire sooner.', - ge=0, - le=86400, - ), - ] = None - - -class Notifications1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supported: Annotated[ - Literal[False], - Field( - description='Discriminator. False means consumers must rely on TTL refresh, manual refresh, or registry polling for capability changes.' - ), - ] - - -class CapabilityChanges(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - capabilities_version: Annotated[ - str | None, - Field( - description='Opaque revision token for the full capability document. Required when `notifications.supported: true` because `capabilities.changed` webhooks use this as the read-after-notify fence. Sellers SHOULD change this value whenever any material field in `get_adcp_capabilities` changes. Buyers MUST treat it as opaque and compare it only for equality.', - max_length=255, - min_length=1, - ), - ] = None - last_modified: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the seller last changed the advertised capability document. This is the scoped replacement for legacy top-level `last_updated` when buyers need cache validation semantics.' - ), - ] = None - cache_ttl_seconds: Annotated[ - SchemaInt | None, - Field( - description='Maximum time a buyer or registry SHOULD reuse this capability response before refreshing when it has no fresher invalidation signal. Sellers that emit `capabilities.changed` webhooks SHOULD still set a bounded TTL so consumers can recover from missed notifications. This is a cache hint, not an authorization lifetime.', - ge=60, - le=604800, - ), - ] = None - notifications: Annotated[ - Notifications | Notifications1 | None, - Field( - description='Whether the seller supports agent-level capability-change webhooks. When supported, interested consumers register endpoint subscribers with the declared registration_task; sync_principal is preferred when the broader connection surface is available, while sync_agent_notification_configs remains the specialized compatibility task. Each capabilities.changed fire is a small invalidation payload, and consumers repair by re-reading get_adcp_capabilities.' - ), - ] = None - - -class Mode(StrEnum): - signed_context = 'signed_context' - online_execution_check = 'online_execution_check' - - -class Tasks1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - task: Literal['create_media_buy'] = 'create_media_buy' - modes: Annotated[list[Mode], Field(min_length=1)] - - -class Tasks2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - task: Literal['update_media_buy'] = 'update_media_buy' - modes: Annotated[list[Mode], Field(min_length=1)] - - -class Tasks3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - task: Literal['buy_products'] = 'buy_products' - modes: Annotated[list[Mode], Field(min_length=1)] - - -class Tasks4(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - task: Literal['accept_proposal'] = 'accept_proposal' - modes: Annotated[list[Mode], Field(min_length=1)] - - -class Tasks5(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - task: Literal['control_media_buy'] = 'control_media_buy' - modes: Annotated[list[Mode], Field(min_length=1)] - - -class Tasks6(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - task: Literal['build_creative'] = 'build_creative' - modes: Annotated[list[Literal['signed_context']], Field(max_length=1, min_length=1)] - - -class Tasks7(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - task: Literal['activate_signal'] = 'activate_signal' - modes: Annotated[list[Literal['signed_context']], Field(max_length=1, min_length=1)] - - -class Tasks8(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - task: Literal['acquire_rights'] = 'acquire_rights' - modes: Annotated[list[Literal['signed_context']], Field(max_length=1, min_length=1)] - - -class Tasks9(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - task: Literal['update_rights'] = 'update_rights' - modes: Annotated[list[Literal['signed_context']], Field(max_length=1, min_length=1)] - - -class Tasks( - RootModel[Tasks1 | Tasks2 | Tasks3 | Tasks4 | Tasks5 | Tasks6 | Tasks7 | Tasks8 | Tasks9] -): - root: Annotated[ - Tasks1 | Tasks2 | Tasks3 | Tasks4 | Tasks5 | Tasks6 | Tasks7 | Tasks8 | Tasks9, - Field(discriminator='task'), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - - - -class GovernanceEnforcement(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - tasks: Annotated[ - list[Tasks], - Field( - description='Task-scoped enforcement claims. The task field is a semantic uniqueness key: an agent MUST emit at most one entry per task and combine all supported modes in that entry. JSON Schema uniqueItems only rejects structurally identical objects, so producers and capability validators MUST enforce task-key uniqueness separately. Values correspond to request schemas annotated with x-governed-commitment. Online execution checks are currently defined only for media-buy tasks, whose prepared result has the PlannedDelivery contract; other roles can enforce signed intent authorization without inventing media-buy fields.', - min_length=1, - ), - ] - accepted_governance_agents: Annotated[ - accepted_governance_agents_1.AcceptedGovernanceAgents | None, - Field( - description='Seller-wide advisory default for governance agents this enforcing service accepts as binding counterparties. A candidate satisfying any matcher is accepted by this declaration. The per-account sync_governance response is authoritative and may apply stricter account-specific criteria. Omission means accept any, preserving legacy behavior.' - ), - ] = None - - -class SupportedProtocol(StrEnum): - media_buy = 'media_buy' - signals = 'signals' - governance = 'governance' - sponsored_intelligence = 'sponsored_intelligence' - creative = 'creative' - brand = 'brand' - measurement = 'measurement' - - -class Notifications2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supported: Annotated[ - Literal[True], - Field( - description='Discriminator. True means the seller accepts account-level `notification_configs[]` registrations for account lifecycle events.' - ), - ] - registration_task: Annotated[ - Literal['sync_accounts'], - Field(description='Task buyers call to manage account-level subscriber sets.'), - ] = 'sync_accounts' - read_task: Annotated[ - Literal['list_accounts'], - Field( - description='Authoritative repair read after receiving an account lifecycle invalidation.' - ), - ] = 'list_accounts' - event_types: Annotated[ - list[Literal['account.status_changed']], - Field( - description='Account lifecycle notification types this seller can emit. Currently only `account.status_changed` is defined.', - min_length=1, - ), - ] - supports_webhook_activity: Annotated[ - StrictBool | None, - Field( - description='Whether `list_accounts` can return recent delivery attempts in `accounts[].webhook_activity[]` when the buyer requests `include_webhook_activity: true`.' - ), - ] = False - - -class Notifications3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supported: Annotated[ - Literal[False], - Field( - description='Discriminator. False means buyers must rely on `sync_accounts` results, `push_notification_config` for one-shot provisioning callbacks, and polling `list_accounts` for later status changes.' - ), - ] - - -class ResourceType(ScalarStr): - __slots__ = () - _constraints = {'max_length': 100, 'min_length': 1, 'pattern': '^[a-z][a-z0-9_.-]{0,99}$'} - - -class ChangeFeed(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supported: Literal[True] - read_task: Literal['list_account_changes'] = 'list_account_changes' - registration_task: Literal['sync_accounts'] = 'sync_accounts' - event_type: Literal['account.change_recorded'] = 'account.change_recorded' - retention_days: Annotated[ - SchemaInt, - Field( - description='Minimum number of days committed change records remain readable.', ge=90 - ), - ] - resource_types: Annotated[ - list[ResourceType], - Field( - description='Resource families for which this seller can produce change records. Account-specific connected-source status and freshness are returned by list_account_changes.', - min_length=1, - ), - ] - - -class ChangeFeed1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supported: Literal[False] - - -class SupportedChange(StrEnum): - operator_unit_name = 'operator_unit_name' - operator_unit = 'operator_unit' - operator = 'operator' - - -class IdentityUpdates(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - supported: Annotated[ - Literal[True], - Field( - description='Discriminator. True means the seller accepts operator_identity in sync_accounts settings-update mode.' - ), - ] - supported_changes: Annotated[ - list[SupportedChange], - Field( - description='Identity changes the seller accepts. operator_unit_name covers display-name-only reconciliation within the current operator. operator_unit covers adding, removing, or changing the stable unit id within the current operator and also implies name updates. operator covers an inter-entity operator-domain handoff and encompasses the entire replacement operator identity, including any simultaneous unit addition, removal, or change; a seller need not also advertise operator_unit for that cross-operator replacement.', - min_length=1, - ), - ] - - -class IdentityUpdates1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - supported: Annotated[ - Literal[False], - Field( - description='Discriminator. False means buyers must not submit operator_identity settings updates.' - ), - ] - - -class Account(AdCPBaseModel): - require_operator_auth: Annotated[ - StrictBool | None, - Field( - description="Whether the seller requires operator-level credentials. This declares who must authenticate; it does not by itself declare whether OAuth is used, whether list_accounts is exposed, or which sync_accounts modes are supported. When true, operators authenticate independently with the seller and account-scoped calls use seller/storefront-assigned account_id values because the seller or upstream platform owns the canonical account namespace. If a credential may access more than one account, the seller MUST expose list_accounts and buyers MUST resolve an explicit account_id before the first account-scoped request. If a credential is bound to exactly one account, the seller SHOULD expose list_accounts returning that singleton; a seller MAY omit list_accounts only when it provides the same explicit account_id through another declared path or out-of-band onboarding. When false (default, buyer-declared accounts), the seller trusts the agent's identity claims and account-scoped calls use the advertiser natural key: brand + operator + optional operator_unit, fixed currency, optional buyer-selected account timezone, and sandbox. operator_unit.id is owned by the operator and is distinct from the seller's account_id. The seller normally provisions through sync_accounts, but MAY lazily provision on the first account-scoped request when billing and other required settings are unambiguous from capabilities or onboarding defaults. A lazy-provisioning seller MUST keep accepting the natural key and MUST expose list_accounts for recovery; if buyer input is needed before use, the seller MUST expose sync_accounts." - ), - ] = False - authorization_endpoint: Annotated[ - AnyUrl | None, - Field( - description='OAuth authorization endpoint for obtaining operator-level credentials. Present when the seller supports OAuth for operator authentication. The agent directs the operator to this URL to authenticate and obtain a bearer token. If absent and require_operator_auth is true, operators obtain credentials out-of-band (e.g., seller portal, API key).' - ), - ] = None - supported_billing: Annotated[ - list[billing_party.BillingParty], - Field( - description="Billing models this seller supports. operator: seller invoices the operator (agency or brand buying direct). agent: agent consolidates billing. advertiser: seller invoices the advertiser directly, even when a different operator places orders on their behalf. When the buyer calls sync_accounts, it must pass one of these values. A lazy-provisioning seller may omit sync_accounts only when billing can be resolved unambiguously from this capability or the authenticated agent's onboarding defaults.", - min_length=1, - ), - ] - supported_account_currency_modes: Annotated[ - list[account_currency_mode.AccountCurrencyMode] | None, - Field( - description='Required for sellers implementing AdCP 3.2 advertiser-account provisioning, but optional in this shared 3.x response schema so existing 3.0 and 3.1 capability responses remain valid. Declares whether advertiser accounts are bound to one immutable currency (`fixed`), select currency independently per proposal or media buy (`per_media_buy`), or support both models. When only `fixed` is advertised, buyer-declared provisioning entries MUST include `currency`. When only `per_media_buy` is advertised, they MUST omit it. When both are advertised, presence of `currency` selects a fixed-currency account and omission selects per-media-buy currency. Buyers MUST treat absence as an older seller whose currency mode is not discoverable, not as support for either mode.', - min_length=1, - ), - ] = None - timezone: Annotated[ - account_timezone_capability.AccountTimezoneCapability | None, - Field( - description='Required for sellers implementing AdCP 3.2 advertiser-account provisioning, but optional in the shared 3.x response schema for compatibility. Declares whether the account timezone is seller-wide or fixed per account and whether a buyer must select it during sync_accounts provisioning. Account timezone is the default for account-scoped calendar semantics; feature-specific capability fields explicitly declare exceptions.' - ), - ] = None - required_for_products: Annotated[ - StrictBool | None, - Field( - description='Whether an account reference is required for get_products. When true, the buyer must establish an account before browsing products. When false (default), the buyer can browse products without an account — useful for price comparison and discovery before committing to a seller.' - ), - ] = False - account_financials: Annotated[ - StrictBool | None, - Field( - description='Whether this seller exposes the `get_account_financials` task for querying account-level financial status (spend, credit, invoices). Acts as a **pre-call discriminator** — buyers MUST consult this field before issuing `get_account_financials`; when `false` (or absent), sellers MAY reject the call with an `UNSUPPORTED_FEATURE` / `OPERATION_NOT_SUPPORTED` error. Companion pattern to `creative.bills_through_adcp` (issue #2881) — both fields let buyers gate optional capability calls on a single declared boolean rather than probing for support. Only applicable to operator-billed accounts; sellers using buyer-billed flows omit or set to `false`.' - ), - ] = False - notifications: Annotated[ - Notifications2 | Notifications3 | None, - Field( - description='Whether the seller supports durable account-lifecycle webhooks through account-level `notification_configs[]`. This capability is specifically for account status changes such as approval, rejection, payment-required, suspension, recovery, and closure. When supported, buyers register subscribers with `sync_accounts.accounts[].notification_configs[]`; each `account.status_changed` fire is an invalidation payload, and buyers repair by re-reading `list_accounts` for the account_id.' - ), - ] = None - change_feed: Annotated[ - ChangeFeed | ChangeFeed1 | None, - Field( - description='Whether the seller exposes a durable, ordered feed of material changes to authoritative account-scoped state. This is distinct from webhook_activity transport diagnostics and from current-state reads. Sellers claiming support MUST retain changes for at least 90 days after recording and MUST produce records regardless of whether a mutation originated through AdCP, a seller surface, another authorized principal, seller automation, or a connected platform within declared coverage. Experimental in 3.2 (RFC #6810): sellers advertising supported: true MUST list account.change_feed in experimental_features.' - ), - ] = None - identity_updates: Annotated[ - IdentityUpdates | IdentityUpdates1 | None, - Field( - description='Whether the seller accepts buyer-desired operator identity reconciliation through sync_accounts settings-update entries. Sellers declaring support expose the exact identity transitions they implement, MUST return account revisions from sync_accounts and list_accounts, and MUST return identity_change_preview for dry-run identity updates.' - ), - ] = None - sandbox: Annotated[ - StrictBool | None, - Field( - description='Whether this seller supports sandbox accounts for testing. Buyer-declared accounts use sandbox: true in sync_accounts or, for an unambiguous lazy-provisioning seller, in the natural-key account reference. Sellers with account_id namespaces expose sandbox accounts as pre-existing test accounts through list_accounts or supply them out-of-band. Requests using a sandbox account perform no real platform calls or spend.' - ), - ] = False - - -class DefaultProfileId(ScalarStr): - __slots__ = () - _constraints = {'min_length': 1} - - -class AcceptancePolicyDiscovery(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - catalog_url: Annotated[ - AnyUrl, - Field(description='HTTPS document that validates against acceptance-policy-catalog.json.'), - ] - catalog_digest: Annotated[ - str, - Field( - description='SHA-256 digest of the exact catalog representation fetched from catalog_url.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] - default_profile_ids: Annotated[ - list[DefaultProfileId] | None, - Field( - description='Local or registry-referenced catalog profiles that apply seller-wide unless a product adds further profiles. IDs MUST resolve uniquely across profiles and registry_profiles; all referenced profiles compose restrictively.', - min_length=1, - ), - ] = None - - -class BuyingMode(StrEnum): - brief = 'brief' - wholesale = 'wholesale' - refine = 'refine' - - -class LifecycleTool(StrEnum): - list_products = 'list_products' - request_proposals = 'request_proposals' - refine_proposals = 'refine_proposals' - decline_proposals = 'decline_proposals' - buy_products = 'buy_products' - accept_proposal = 'accept_proposal' - control_media_buy = 'control_media_buy' - - -class SupportedDimension(StrEnum): - total_budget = 'total_budget' - cpm = 'cpm' - impressions = 'impressions' - flight = 'flight' - product_changes = 'product_changes' - alternatives = 'alternatives' - criteria = 'criteria' - - -class ProposalRefinement(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supported_dimensions: Annotated[ - list[SupportedDimension], - Field( - description='Typed revision dimensions the seller can parse and validate. An empty list authoritatively means ask-only refinement with no typed dimensions. total_budget, cpm, impressions, and flight cover the same-named constraints keys; product_changes covers product_changes; alternatives covers alternatives.count; criteria covers structured discovery criteria. Free-text ask interpretation is competence, not a capability declared here.' - ), - ] - max_alternatives: Annotated[ - SchemaInt | None, - Field( - description='Optional maximum alternatives.count the seller accepts, up to the protocol maximum of 10. Valid only when supported_dimensions includes alternatives. Requests above this ceiling fail at task level with VALIDATION_ERROR identifying refinements[i].alternatives.count; sellers MUST NOT silently clamp the request or return alternatives_unavailable for the declared ceiling violation.', - ge=2, - le=10, - ), - ] = None - - -class PerformanceFeedback(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - reports_application_status: Annotated[ - StrictBool | None, - Field( - description='Whether provide_performance_feedback responses include an honest application_status of accepted, applied, or not_applied. accepted is explicitly not an application claim. Sellers that cannot determine optimizer disposition omit this claim or set it false.' - ), - ] = False - - -class PropagationSurface(StrEnum): - snapshot = 'snapshot' - webhook = 'webhook' - out_of_band = 'out_of_band' - - -class CreativeApprovalMode(StrEnum): - auto_approve = 'auto_approve' - require_human = 'require_human' - - -class EventType(StrEnum): - indicators_changed = 'indicators.changed' - creative_assignment_changed = 'creative.assignment_changed' - - -class RelationshipNotifications(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - supported: Annotated[ - Literal[True], - Field( - description='Discriminator. True means the declared invalidation event types are supported.' - ), - ] - registration_task: Annotated[ - Literal['sync_accounts'], - Field(description='Task buyers call to register account-level subscribers.'), - ] = 'sync_accounts' - event_types: Annotated[ - list[EventType], - Field( - description='Relationship invalidation events supported by this seller. indicators.changed requires supported_indicator_types but is not required merely because polling readback is available. creative.assignment_changed is independently available when the seller can detect assignment or assignment-approval changes; it does not require indicator support or list_creatives.', - max_length=2, - min_length=1, - ), - ] - repair_tasks: Annotated[ - list[Literal['get_media_buys']], - Field( - description='Complete authoritative snapshot reads used after an invalidation. get_media_buys is mandatory and is the only 3.2 repair task for this contract.', - max_length=1, - min_length=1, - ), - ] - projection_tasks: Annotated[ - list[Literal['list_creatives']] | None, - Field( - description='Optional bounded reverse projections of relationship-scoped indicator and approval state. list_creatives is a discovery/read convenience for creative-library sellers, not a complete repair path; declaring it also requires creative in supported_protocols. Buyers fall back to get_media_buys whenever nested assignments are truncated or a relationship must be proven absent.', - max_length=1, - min_length=1, - ), - ] = None - supports_webhook_activity: Annotated[ - StrictBool | None, - Field( - description='Whether the declared repair tasks can return recent delivery attempts for these account-level invalidations when include_webhook_activity is true.' - ), - ] = False - - -class MraidVersion(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[0-9]+\\.[0-9]+$'} - - -class VastValidation(StrEnum): - structural = 'structural' - document = 'document' - wrapper = 'wrapper' - - -class GeoMetros(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - nielsen_dma: StrictBool | None = None - uk_itl1: StrictBool | None = None - uk_itl2: StrictBool | None = None - eurostat_nuts2: StrictBool | None = None - - -class AgeRestriction(AdCPBaseModel): - supported: Annotated[ - StrictBool | None, Field(description='Whether seller supports age restrictions') - ] = None - verification_methods: Annotated[ - list[age_verification_method.AgeVerificationMethod] | None, - Field(description='Age verification methods this seller supports'), - ] = None - - -class Demographics(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supported: Annotated[ - StrictBool, - Field( - description='Whether at least one seller product supports canonical demographic targeting.' - ), - ] - - -class SupportedRequirementMode(StrEnum): - required = 'required' - preferred = 'preferred' - - -class SupportedPresenceMode(StrEnum): - required = 'required' - when_available = 'when_available' - - -class AudienceEvidence(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supported_requirement_modes: Annotated[ - list[SupportedRequirementMode], - Field( - description='Buyer policy modes the seller evaluates. A seller MUST NOT silently ignore a mode it does not list.', - min_length=1, - ), - ] - supported_presence_modes: Annotated[ - list[SupportedPresenceMode], - Field(description='Evidence-presence semantics the seller supports.', min_length=1), - ] - supports_attestation_evaluation: Annotated[ - StrictBool, - Field( - description='Whether the seller evaluates audience-evidence attestation_refs under its core adcp.attestations policy and returns the exact reference and AttestationEvaluation in package readback.' - ), - ] - - -class Requirement(StrEnum): - optional = 'optional' - required = 'required' - - -class RightsAttestations(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - requirement: Annotated[ - Requirement, - Field( - description='required means every applicable rights constraint needs at least one current verified attestation evaluation before the creative is eligible to serve. optional permits independent legacy contractual policy, but unattested constraints remain machine-unverified and verification_url is never a downgrade path.' - ), - ] - - -class SupportedIdentifierType(StrEnum): - hashed_email = 'hashed_email' - hashed_phone = 'hashed_phone' - - -class MatchingLatencyHours(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - min: Annotated[SchemaInt | None, Field(ge=0)] = None - max: Annotated[SchemaInt | None, Field(ge=0)] = None - - -class SupportedOptimizationMetric(StrEnum): - clicks = 'clicks' - views = 'views' - completed_views = 'completed_views' - viewed_seconds = 'viewed_seconds' - viewable_rate = 'viewable_rate' - attention_seconds = 'attention_seconds' - attention_score = 'attention_score' - engagements = 'engagements' - follows = 'follows' - saves = 'saves' - profile_visits = 'profile_visits' - reach = 'reach' - - -class SupportedTarget(StrEnum): - cost_per = 'cost_per' - threshold_rate = 'threshold_rate' - - -class VendorMetricOptimization(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supported_targets: Annotated[ - list[SupportedTarget] | None, - Field( - description='Target kinds this seller can support for vendor_metric optimization goals on at least one product. Values match optimization_goals[].target.kind for kind: vendor_metric. A target-less vendor_metric goal maximizes the metric within budget and does not require a target-kind declaration.', - min_length=1, - ), - ] = None - - -class SupportedTarget1(StrEnum): - cost_per = 'cost_per' - per_ad_spend = 'per_ad_spend' - maximize_value = 'maximize_value' - - -class AttributionWindow(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - event_type: Annotated[ - event_type_1.EventType | None, - Field(description='Event type this window applies to, or omit for default window'), - ] = None - post_click: Annotated[ - list[duration.Duration], - Field( - description='Available post-click attribution windows (e.g. [{"interval": 7, "unit": "days"}])', - min_length=1, - ), - ] - post_view: Annotated[ - list[duration.Duration] | None, - Field( - description='Available post-view attribution windows (e.g. [{"interval": 1, "unit": "days"}])', - min_length=1, - ), - ] = None - - -class SupportedScope(StrEnum): - media_buy = 'media_buy' - package = 'package' - - -class ContentStandards(AdCPBaseModel): - supports_local_evaluation: Annotated[ - StrictBool | None, - Field( - description="Whether the seller runs a local evaluation model. When false, all artifacts will have local_verdict: 'unevaluated' and the failures_only filter on get_media_buy_artifacts is not useful." - ), - ] = None - supported_channels: Annotated[ - list[channels.MediaChannel] | None, - Field( - description='Channels for which the seller can provide content artifacts. Helps buyers understand which parts of a mixed-channel buy will have content standards coverage.', - min_length=1, - ), - ] = None - supports_webhook_delivery: Annotated[ - StrictBool | None, - Field( - description='Whether the seller supports push-based artifact delivery via artifact_webhook configured at buy creation time.' - ), - ] = None - - -class PublisherDomain(ScalarStr): - __slots__ = () - _constraints = { - 'pattern': '^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - } - - -class PrimaryCountry(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{2}$'} - - -class Portfolio(AdCPBaseModel): - publisher_domains: Annotated[ - list[PublisherDomain], - Field( - description="Publisher domains this seller is authorized to represent. Buyers should fetch each publisher's adagents.json for property definitions.", - min_length=1, - ), - ] - primary_channels: Annotated[ - list[channels.MediaChannel] | None, - Field( - description="Complete list of AdCP media channels for which this sales agent accepts and can meaningfully answer product-discovery briefs. When present, this is an exhaustive brief-routing allowlist: buyers MAY skip the agent when a brief's requested channels do not intersect it. Omission means channel scope is unknown and MUST NOT be interpreted as support for every channel. This is a routing pre-filter, not a promise of current product availability." - ), - ] = None - primary_countries: Annotated[ - list[PrimaryCountry] | None, - Field( - description="Complete list of ISO 3166-1 alpha-2 countries for which this sales agent accepts and can meaningfully answer product-discovery briefs. When present, this is an exhaustive brief-routing allowlist: buyers MAY skip the agent when a brief's requested countries do not intersect it. Omission means country scope is unknown and MUST NOT be interpreted as global coverage. This is a routing pre-filter, not a promise of current product availability and not an executable geo-targeting declaration; media_buy.execution.targeting geo capabilities and each product's overlay_support remain authoritative for targeting execution." - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Markdown-formatted description of the inventory portfolio', max_length=5000 - ), - ] = None - advertising_policies: Annotated[ - str | None, - Field( - description='Advertising content policies, restrictions, and guidelines', - max_length=10000, - ), - ] = None - - -class DataProviderDomain(PublisherDomain): - pass - - -class DiscoveryMode(StrEnum): - brief = 'brief' - wholesale = 'wholesale' - - -class Features(AdCPBaseModel): - __pydantic_extra__: Dict[str, StrictBool] - model_config = ConfigDict( - extra='allow', - ) - catalog_signals: Annotated[ - StrictBool | None, - Field( - deprecated=True, - description='DEPRECATED. Legacy wire flag for structured signal_ref references to provider-published signal definitions. New agents SHOULD omit this flag; callers MUST NOT require it before using signal_ref with the Signals protocol.', - ), - ] = None - - - - -class Signals(AdCPBaseModel): - anonymous_discovery: Annotated[ - StrictBool | None, - Field( - description='Whether this agent accepts get_signals discovery without caller credentials in brief or wholesale mode. true means an anonymous discovery request can produce a successful response, but the response may be a public subset and may differ from results for an authenticated principal or selected account. false means get_signals requires an authenticated principal. Absence means unspecified legacy behavior, so callers probe and handle AUTH_MISSING. A valid authenticated request is never rejected merely because credentials were supplied.' - ), - ] = None - data_provider_domains: Annotated[ - list[DataProviderDomain] | None, - Field( - description="Data provider domains this signals agent is authorized to resell. Buyers should fetch each data provider's adagents.json for published signal definitions and to verify authorization.", - min_length=1, - ), - ] = None - discovery_modes: Annotated[ - list[DiscoveryMode] | None, - Field( - description="Discovery modes this signals agent supports on get_signals. 'brief' (default — every signals agent supports this): semantic discovery driven by signal_spec or signal_refs, with deprecated signal_ids accepted for older clients. 'wholesale': raw wholesale signals feed enumeration — caller omits signal_spec, signal_refs, and signal_ids and the agent returns its full priced signals feed, paginated, scoped by filters/account/destinations/countries. Agents that do not declare 'wholesale' MAY return INVALID_REQUEST for wholesale calls. Absent declaration is treated as ['brief'].", - min_length=1, - ), - ] = [DiscoveryMode.brief] - features: Annotated[ - Features | None, Field(description='Optional signals features supported') - ] = None - - -class SignalActivation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - requirement: Annotated[ - Requirement, - Field( - description='Whether an activate_signal activation check may omit runtime_attestations. When required, missing evidence fails an activation check. Deactivation and privacy-removal checks never require signal-quality evidence and remain available without it.' - ), - ] - claim_types: Annotated[ - list[AnyUrl], - Field( - description='Signal-quality claim identifiers accepted for this governed action. Values are open absolute URIs and MUST be a subset of adcp.attestations.accepted_claim_types.', - min_length=1, - ), - ] - - -class RuntimeAttestations(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - signal_activation: SignalActivation - - -class Type(StrEnum): - binary = 'binary' - quantitative = 'quantitative' - categorical = 'categorical' - - -class Range(AdCPBaseModel): - min: Annotated[StrictFloat, Field(description='Minimum value')] - max: Annotated[StrictFloat, Field(description='Maximum value')] - - -class PropertyFeature(AdCPBaseModel): - feature_id: Annotated[ - str, - Field( - description="Unique identifier for this feature (e.g., 'consent_quality', 'coppa_certified', 'carbon_score')" - ), - ] - type: Annotated[ - Type, - Field( - description="Data type: 'binary' for yes/no, 'quantitative' for numeric scores, 'categorical' for enum values" - ), - ] - range: Annotated[ - Range | None, Field(description='For quantitative features, the valid range') - ] = None - categories: Annotated[ - list[str] | None, Field(description='For categorical features, the valid values') - ] = None - description: Annotated[ - str | None, Field(description='Human-readable description of what this feature measures') - ] = None - methodology_url: Annotated[ - AnyUrl | None, - Field( - description='URL to documentation explaining how this feature is calculated or measured. Helps buyers understand and compare methodologies across vendors.' - ), - ] = None - - -class CreativeFeature(AdCPBaseModel): - feature_id: Annotated[ - str, - Field( - description="Unique identifier for this feature (e.g., 'auto_redirect', 'brand_consistency', 'iab_casinos_gambling')" - ), - ] - type: Annotated[ - Type, - Field( - description="Data type: 'binary' for yes/no, 'quantitative' for numeric scores, 'categorical' for enum values" - ), - ] - range: Annotated[ - Range | None, Field(description='For quantitative features, the valid range') - ] = None - categories: Annotated[ - list[str] | None, Field(description='For categorical features, the valid values') - ] = None - description: Annotated[ - str | None, Field(description='Human-readable description of what this feature measures') - ] = None - methodology_url: Annotated[ - AnyUrl | None, - Field( - description='URL to documentation explaining how this feature is calculated or measured.' - ), - ] = None - - -class Governance(AdCPBaseModel): - runtime_attestations: Annotated[ - RuntimeAttestations | None, - Field( - description='Signal-activation policy for the portable attestation support declared in adcp.attestations. Presence means check_governance accepts runtime_attestations[] for purchase_type signal_activation. The claim_types list MUST be a subset of adcp.attestations.accepted_claim_types; issuer, resolver, verifier, proof-format, and delivery allowlists remain authoritative in the shared adcp.attestations block rather than being duplicated here.' - ), - ] = None - aggregation_window_days: Annotated[ - SchemaInt | None, - Field( - description='Trailing window (in days) over which this governance agent aggregates committed spend when evaluating dollar-valued thresholds (reallocation_threshold, human_review triggers, registry-policy floors). Required for fragmentation defense: without aggregation, a buyer can split a single large spend into many sub-threshold commits across plans / task surfaces / time and bypass every dollar-gated escalation. Aggregation is keyed on (buyer_agent, seller_agent, account_id) and spans all spend-commit task types. Upper bound 365 represents a one-year trailing window (fiscal-year alignment with grace); governance agents needing longer scopes negotiate via operator sign-off, not this capability. No schema default: absence of this field indicates the governance agent has not committed to any aggregation window and buyers MUST assume per-commit evaluation only (the fragmentation attack surface is open). A declared value of 30 is a common starting point but is not implied by omission. Buyers depending on a specific window for compliance MUST check this capability before relying on aggregation semantics — an agent declaring 7 days does not defend against fragmentation spread across a 30-day quarter-end push.', - ge=1, - le=365, - ), - ] = None - property_features: Annotated[ - list[PropertyFeature] | None, - Field( - description='Property features this governance agent can evaluate. Each feature describes a score, rating, or certification the agent can provide for properties.' - ), - ] = None - creative_features: Annotated[ - list[CreativeFeature] | None, - Field( - description='Creative features this governance agent can evaluate. Each feature describes a score, rating, or assessment the agent can provide for creatives (e.g., security scanning, creative quality, content categorization).' - ), - ] = None - - -class Type6(StrEnum): - mcp = 'mcp' - a2a = 'a2a' - - -class Transport1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[Type6, Field(description='Protocol transport type')] - url: Annotated[AnyUrl, Field(description='Agent endpoint URL for this transport')] - - -class Endpoint(AdCPBaseModel): - transports: Annotated[ - list[Transport1], - Field( - description='Available protocol transports. Hosts select based on their capabilities.', - min_length=1, - ), - ] - preferred: Annotated[ - Type6 | None, Field(description='Preferred transport when host supports multiple') - ] = None - - -class VariantDimension(StrEnum): - voice = 'voice' - theme = 'theme' - best_of_n = 'best_of_n' - transformer_config = 'transformer_config' - custom = 'custom' - - -class Multiplicity(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supports_catalog_fanout: Annotated[ - StrictBool | None, - Field( - description='When true, build_creative accepts max_creatives (one distinct creative per catalog item).' - ), - ] = False - max_creatives_limit: Annotated[ - SchemaInt | None, - Field(description='Ceiling on max_creatives. Omitted means no advertised ceiling.', ge=1), - ] = None - supports_signal_fanout: Annotated[ - StrictBool | None, - Field( - description='Experimental (x-status: experimental) — agents setting this true MUST also list `creative.signal_fanout` in `experimental_features`; the surface MAY change between 3.x releases with notice (see docs/reference/experimental-status). When true, build_creative accepts signal_conditions[] (one distinct creative group per signal condition, keep-all). Mirrors supports_catalog_fanout.' - ), - ] = False - max_signal_conditions_limit: Annotated[ - SchemaInt | None, - Field( - description='Ceiling on len(signal_conditions). Over-limit requests are CLAMPED (not rejected), like max_creatives_limit. Omitted means no advertised ceiling.', - ge=1, - ), - ] = None - supports_variants: Annotated[ - StrictBool | None, - Field( - description='When true, build_creative accepts max_variants > 1 / variant_axis (alternatives per creative).' - ), - ] = False - max_variants_limit: Annotated[ - SchemaInt | None, - Field(description='Ceiling on max_variants. Omitted means no advertised ceiling.', ge=1), - ] = None - variant_dimensions: Annotated[ - list[VariantDimension] | None, - Field(description='Which variant_axis.dimension values this agent supports.'), - ] = None - selection_strategies: Annotated[ - list[creative_selection_strategy.CreativeSelectionStrategy] | None, - Field( - description='Which selection_strategy values this agent supports when sampling max_creatives < items_total. Sibling to variant_dimensions. Part of the experimental signal-fanout surface (feature id `creative.signal_fanout`).' - ), - ] = None - - -class Operation(StrEnum): - build = 'build' - validate = 'validate' - preview = 'preview' - - -class RenderingOrigin(StrEnum): - platform_native = 'platform_native' - agent_approximation = 'agent_approximation' - - -class Route(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - capability_id: Annotated[ - str, - Field( - description='Agent-local creative.supported_formats[].capability_id accepted by preview_creative.', - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - rendering_origin: Annotated[ - RenderingOrigin, - Field( - description="Informational implementation origin. platform_native means the route uses the serving platform's preview machinery; agent_approximation means the agent renders an approximation. Neither value grants authority without a publisher preview_provider delegation." - ), - ] - - -class Preview(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - routes: Annotated[ - list[Route], - Field( - description='Agent-local preview routes and their implementation origin. Authority is resolved from publisher placement delegation, never from this self-description.', - min_length=1, - ), - ] - - -class Localization(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - max_target_variants: Annotated[ - SchemaInt | None, - Field( - description='Maximum target locale variants accepted on one creative. Zero advertises source-only monolingual locale topology. Omit to inherit the protocol ceiling of 50.', - ge=0, - le=50, - ), - ] = None - locale_matching: Annotated[ - Literal['rfc4647_lookup'], - Field(description='The required deterministic locale-matching algorithm.'), - ] = 'rfc4647_lookup' - - -class Oauth(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supported: Annotated[ - StrictBool, - Field( - description='Whether this agent accepts OAuth access tokens and publishes the discovery metadata needed to obtain and validate them. A true value is an explicit conformance claim: the RFC 9728 protected-resource document and every RFC 8414 authorization-server document it references MUST be reachable, internally consistent, and safe for clients to follow. False or absent means the OAuth metadata storyboard is not applicable; it does not weaken the universal requirement to implement at least one authentication mechanism.' - ), - ] - - -class CoversContentDigest(StrEnum): - required = 'required' - forbidden = 'forbidden' - either = 'either' - - -class RequiredForItem(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[a-z][a-z0-9_]*$'} - - -class WarnForItem(RequiredForItem): - pass - - -class SupportedForItem(RequiredForItem): - pass - - -class ProtocolMethodsSupportedForItem(ScalarStr): - __slots__ = () - _constraints = { - 'max_length': 256, - 'pattern': '^(?:[a-z][A-Za-z0-9_]*(?:/[A-Za-z][A-Za-z0-9_]*)+|[A-Z][A-Za-z0-9_]*)$', - } - - -class ProtocolMethodsWarnForItem(ProtocolMethodsSupportedForItem): - pass - - -class ProtocolMethodsRequiredForItem(ProtocolMethodsSupportedForItem): - pass - - -class RequestSigning(AdCPBaseModel): - supported: Annotated[ - StrictBool, - Field( - description='Whether this agent verifies RFC 9421 signatures on incoming requests. When true, signatures present on requests are validated per the AdCP request-signing profile. When false or absent, signatures are ignored (requests are bearer-authenticated only).' - ), - ] - covers_content_digest: Annotated[ - CoversContentDigest | None, - Field( - description="Policy for content-digest coverage in request signatures. In AdCP 3.2 and later, an agent with request_signing.supported=true MUST explicitly emit 'required': every accepted signature on a request with a body covers content-digest, and a body-unbound signature is rejected with request_signature_components_incomplete. Omission retains the legacy effective default of 'either' only for a 3.0/3.1 response; 3.2 responses require this field explicitly. 'either' and 'forbidden' are deprecated legacy 3.0/3.1 postures retained only for version negotiation with pre-3.2 peers; they MUST NOT be advertised as a 3.2 signing posture and are removed in 4.0. A shared endpoint MUST select the verifier policy from trusted endpoint configuration and negotiated capabilities before dispatch, never from an unbound request-body version field." - ), - ] = None - required_for: Annotated[ - list[Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')]] | None, - Field( - description="AdCP protocol operation names (e.g., 'create_media_buy') for which this agent rejects an unsigned request with request_signature_required unless an independently valid configured fallback authenticator succeeds. Not MCP tool names, A2A skill names, or any transport-specific rename — verifiers MUST NOT accept operation names that are not defined by the AdCP protocol spec. JSON-RPC protocol method names like `tasks/cancel` belong in `protocol_methods_required_for`, not here. Empty in 3.0 by default; sellers populate selectively during per-counterparty pilots. In 4.0 this list MUST include all spend-committing operations the agent supports (create_media_buy, acquire_*, etc.). Every operation listed MUST also appear in `supported_for`; see `x-adcp-validation`.", - validate_default=True, - ), - ] = [] - warn_for: Annotated[ - list[Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')]] | None, - Field( - description='AdCP protocol operation names for shadow-mode verification. The verifier records missing signatures and well-formed signatures that fail verification or body binding, but MUST NOT establish verified-signer identity from a failed signature; processing continues only when an independent bearer, API-key, or mTLS authenticator succeeds. A partial or malformed Signature / Signature-Input pair always hard-rejects. Used as a bridge between supported_for and required_for. Precedence: required_for > warn_for > supported_for. An operation MUST NOT appear in both warn_for and required_for; see x-adcp-validation.', - validate_default=True, - ), - ] = [] - supported_for: Annotated[ - list[Annotated[str, Field(pattern='^[a-z][a-z0-9_]*$')]] | None, - Field( - description='AdCP protocol operation names for which this agent verifies signatures when present but does not require them. Under the 3.2 profile, a presented signature on a body-bearing request without content-digest coverage rejects even though an unsigned request may use configured fallback authentication. Typically a superset of required_for and warn_for.' - ), - ] = None - protocol_methods_supported_for: Annotated[ - list[ProtocolMethodsSupportedForItem] | None, - Field( - description="JSON-RPC protocol method names for which this agent verifies signatures when present. Values MUST use exact, case-sensitive equality after JSON decoding: slash-path names such as 'tasks/cancel' and 'tasks/pushNotificationConfig/set' for A2A 0.3, or PascalCase names such as 'CancelTask' and 'CreateTaskPushNotificationConfig' for A2A 1.0. A dual-stack agent lists each supported wire name independently; implementations MUST NOT translate or normalize between protocol versions. The reserved MCP envelope method 'tools/call' is forbidden because its AdCP operation identity is params.name and belongs in supported_for. Under the 3.2 profile, a presented signature on a body-bearing request without content-digest coverage rejects. Disjoint from supported_for, which carries lower_snake_case AdCP operation names only.", - validate_default=True, - ), - ] = [] - protocol_methods_warn_for: Annotated[ - list[ProtocolMethodsWarnForItem] | None, - Field( - description='Exact JSON-RPC protocol method names for shadow-mode verification, mirroring warn_for in the AdCP-operation namespace. Wire-name grammar and exact, case-sensitive matching semantics are identical to protocol_methods_supported_for. Missing signatures and well-formed signatures that fail verification or body binding are recorded but MUST NOT establish verified-signer identity; processing continues only when an independent authenticator succeeds. A partial or malformed Signature / Signature-Input pair always hard-rejects. An item MUST NOT appear in both protocol_methods_warn_for and protocol_methods_required_for; see x-adcp-validation.', - validate_default=True, - ), - ] = [] - protocol_methods_required_for: Annotated[ - list[ProtocolMethodsRequiredForItem] | None, - Field( - description='Exact JSON-RPC protocol method names for which this agent rejects an unsigned request with request_signature_required unless an independently valid configured fallback authenticator succeeds. Wire-name grammar and exact, case-sensitive matching semantics are identical to protocol_methods_supported_for. Separate namespace from required_for: this bucket binds against the JSON-RPC method field, not tools/call params.name. Every listed method MUST also appear in protocol_methods_supported_for; see x-adcp-validation.', - validate_default=True, - ), - ] = [] - - -class Algorithm(StrEnum): - ed25519 = 'ed25519' - ecdsa_p256_sha256 = 'ecdsa-p256-sha256' - - -class WebhookSigning(AdCPBaseModel): - supported: Annotated[ - StrictBool, - Field( - description='Whether this agent signs outbound webhooks with the AdCP RFC 9421 webhook profile. When false or absent, webhooks are delivered with legacy Bearer or HMAC-SHA256 auth only and receivers MUST NOT expect a Signature header. When the seller advertises mutating-webhook emission (i.e., `media_buy.reporting_delivery_methods` includes `webhook`, `media_buy.content_standards.supports_webhook_delivery` is true, `media_buy.relationship_notifications.supported` is true, `wholesale_feed_webhooks.supported` is true, `adcp.capability_changes.notifications.supported` is true, or `account.notifications.supported` is true), this MUST be `true` — emitting state-changing webhooks unsigned is a downgrade vector that lets an on-path attacker forge delivery callbacks. See `x-adcp-validation`.' - ), - ] - profile: Annotated[ - Literal['adcp/webhook-signing/v1'] | None, - Field( - description='Identifier of the webhook-signing profile version the agent emits. Value MUST match the `tag=` parameter emitted in the RFC 9421 `Signature-Input` header (see docs/building/implementation/webhooks.mdx) so receivers can statically validate the declared profile against the on-wire tag. Closed enum; future profile revisions will extend this enum in a follow-up schema bump.' - ), - ] = None - algorithms: Annotated[ - list[Algorithm] | None, - Field( - description="Signature algorithms this agent uses on outbound webhooks. 3.0 profile permits 'ed25519' and 'ecdsa-p256-sha256' only; other values are reserved for future profile versions and MUST NOT be emitted under adcp/webhook-signing/v1.", - min_length=1, - ), - ] = None - legacy_hmac_fallback: Annotated[ - StrictBool | None, - Field( - deprecated=True, - description='Whether this agent will fall back to HMAC-SHA256 on the legacy push_notification_config.authentication, accounts[].notification_configs[].authentication, sync_principal.configuration.notification_configs[].authentication, or sync_agent_notification_configs.notification_configs[].authentication paths for receivers that have not adopted RFC 9421. Deprecated; removed in AdCP 4.0.', - ), - ] = False - delivery_retry_horizon_seconds: Annotated[ - SchemaInt | None, - Field( - description='Maximum elapsed time from the first delivery attempt during which this agent may retry the same webhook delivery. The publisher retains the immutable delivery-key-to-RFC-8785-JCS-payload binding and sufficient delivery state for at least this interval, and MUST NOT retry that key afterward. Receivers retain the matching payload binding and terminal publication proof for at least max(86400, this value) seconds in AdCP 3.x. Retries do not extend the horizon. A webhook-emitting AdCP 3.2 agent MUST populate this additive field; it remains schema-optional so existing 3.x capability documents stay valid. Minimum 86400 (24h), maximum 604800 (7d).', - ge=86400, - le=604800, - ), - ] = None - - -class KeyOrigins(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - governance_signing: Annotated[ - AnyUrl | None, - Field(description='Origin (scheme + host) serving the governance-signing JWKS.'), - ] = None - request_signing: Annotated[ - AnyUrl | None, Field(description='Origin (scheme + host) serving the request-signing JWKS.') - ] = None - webhook_signing: Annotated[ - AnyUrl | None, - Field( - description='Origin (scheme + host) serving the JWKS used for webhook delivery. Webhooks are signed with `adcp_use: "request-signing"` keys; the deprecated `adcp_use: "webhook-signing"` value remains accepted during the backward-compatibility window.' - ), - ] = None - tmp_signing: Annotated[ - AnyUrl | None, - Field( - description='Origin (scheme + host) serving the TMP-signing JWKS, when this operator participates in TMP.' - ), - ] = None - - -class CompromiseNotification(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - emits: Annotated[ - StrictBool | None, - Field(description='Whether this agent emits `identity.compromise_notification` events.'), - ] = False - accepts: Annotated[ - StrictBool | None, - Field( - description='Whether this agent subscribes to `identity.compromise_notification` events from counterparties it verifies signatures from.' - ), - ] = False - - -class Identity(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - brand_json_url: Annotated[ - AnyUrl | None, - Field( - description="HTTPS URL of the operator's brand.json (typically `https://{operator-domain}/.well-known/brand.json`). Trust-root pointer for this agent's signing keys. See [security.mdx §Discovering an agent's signing keys via `brand_json_url`](https://adcontextprotocol.org/docs/building/by-layer/L1/security#discovering-an-agents-signing-keys-via-brand_json_url) for the verifier algorithm and `x-adcp-validation` for structured constraints. Distinct from `sponsored_intelligence.brand_url`, which is a rendering pointer for SI agent visuals — verifiers MUST use this field for key discovery and MUST NOT fall back to `sponsored_intelligence.brand_url` as a trust-root pointer." - ), - ] = None - per_principal_key_isolation: Annotated[ - StrictBool | None, - Field( - description="When true, this multi-principal operator scopes signing keys per-principal so a single principal's key compromise does not silently re-scope across principals served by the same operator. `kid` values remain opaque to verifiers per RFC 7517; any operator-side naming convention (e.g., `{operator}:{principal}:{key_version}`) is internal bookkeeping and MUST NOT be parsed by verifiers. See docs/building/understanding/security-model.mdx." - ), - ] = False - key_origins: Annotated[ - KeyOrigins | None, - Field( - description='Map of signing-key surface/purpose → publishing origin, so counterparties can verify origin separation (e.g., governance keys served from a separate origin than transport/webhook keys) at onboarding. Absent means the operator has not declared a separation scheme; receivers SHOULD assume shared-origin. Every entry listed MUST have a corresponding signing posture declared elsewhere — `request_signing` requires non-empty `request_signing.supported_for`/`required_for`/`protocol_methods_supported_for`/`protocol_methods_required_for`; `webhook_signing` requires `webhook_signing.supported === true` and names the webhook delivery surface, not a required live `adcp_use: "webhook-signing"` key purpose — otherwise the consistency check at signature-verification time has nothing to anchor against. See `x-adcp-validation` and docs/building/implementation/security.mdx §Origin separation.' - ), - ] = None - compromise_notification: Annotated[ - CompromiseNotification | None, - Field( - description='Whether this agent emits the `identity.compromise_notification` webhook event on key revocation due to known or suspected compromise (as opposed to scheduled rotation). Subscribers use this to bound the window between compromise detected and verifiers converging on revocation. See docs/building/implementation/webhooks.mdx §identity.compromise_notification.' - ), - ] = None - - -class MeasurementGateway(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - delivery_task: Annotated[ - Literal['get_media_buy_delivery'], - Field( - description='Task providers call on the orchestrator gateway to read buyer-approved delivery.' - ), - ] = 'get_media_buy_delivery' - feedback_task: Annotated[ - Literal['provide_performance_feedback'], - Field( - description='Task providers call on the orchestrator gateway to return one compact assertion.' - ), - ] = 'provide_performance_feedback' - - -class Accreditation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - accrediting_body: Annotated[ - str, - Field( - description='Accrediting organization — open string (the global landscape includes MRC, ARF, ABC, BARB, JICWEBS, AGOF, JIC bodies in many markets). Use the canonical short name where one exists.', - examples=['MRC', 'ARF', 'ABC', 'BARB', 'JICWEBS', 'AGOF'], - ), - ] - certification_id: Annotated[ - str | None, - Field( - description="Optional identifier for the certification in the accrediting body's records (when one exists; many bodies do not issue stable IDs)." - ), - ] = None - valid_until: Annotated[ - date | None, - Field( - description="Optional ISO 8601 date when the current accreditation expires. Buyers MAY treat post-expiry data as un-accredited. Absence means the vendor does not assert an expiry — buyers SHOULD verify currency at the accrediting body's directory." - ), - ] = None - evidence_url: Annotated[ - AnyUrl | None, - Field( - description="Optional URL pointing at the accrediting body's public listing for this certification (the buyer's path to verify the claim independently)." - ), - ] = None - - -class ComplianceTesting(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scenarios: Annotated[ - list[str], - Field( - description="Compliance testing scenarios this agent supports. Must be non-empty — at least one scenario. Values SHOULD include every canonical controller scenario the agent implements, excluding list_scenarios because that value is a discovery operation rather than a test capability. Values MAY also include implementation-specific scenarios. Callers can use comply_test_controller with scenario: 'list_scenarios' to discover supported scenarios at runtime.", - min_length=1, - ), - ] - - -class ExtensionsSupportedItem(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[a-z][a-z0-9_]*$'} - _json_schema_extra = { - 'description': "Extension namespace (lowercase alphanumeric with underscores, e.g., 'iab_tcf', 'iab_gpp')", - } - - -class WholesaleFeedVersioning(AdCPBaseModel): - supported: Annotated[ - StrictBool, - Field( - description='Whether the agent returns wholesale_feed_version on responses and honors if_wholesale_feed_version on requests. When absent, treated as false; buyers MAY still probe (the field-presence detection path) but cannot pre-flight-decide.' - ), - ] - pricing_version_separate: Annotated[ - StrictBool | None, - Field( - description="Whether the agent tracks pricing_version independently of wholesale_feed_version. When true, the agent returns both tokens and honors if_pricing_version separately — useful for rate-card sweeps that don't change product and signal metadata. When false or absent, the agent collapses both into wholesale_feed_version; callers SHOULD NOT send if_pricing_version (it will be ignored and may produce INVALID_REQUEST when sent without if_wholesale_feed_version per the dependencies rule)." - ), - ] = None - cache_scope_account: Annotated[ - StrictBool | None, - Field( - description="Whether the agent ever returns cache_scope: 'account' (i.e., publishes per-account overlays distinct from the public rate card). When true, buyers MUST be prepared to maintain account-overlay caches alongside the public layer. When false or absent, all responses are cache_scope: 'public' regardless of whether account was provided — the agent's rate card is universal. Confidentiality note: declaring true advertises that the agent runs custom-pricing deals (low-grade market-posture signal); agents preferring not to disclose this MAY omit the field and let consumers detect-on-call via cache_scope on response." - ), - ] = None - - -class EventType3(StrEnum): - product_created = 'product.created' - product_updated = 'product.updated' - product_priced = 'product.priced' - product_removed = 'product.removed' - signal_created = 'signal.created' - signal_updated = 'signal.updated' - signal_priced = 'signal.priced' - signal_removed = 'signal.removed' - wholesale_feed_bulk_change = 'wholesale_feed.bulk_change' - - -class WholesaleFeedWebhooks(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supported: Annotated[ - StrictBool, - Field( - description='Whether this agent can push wholesale feed change payloads through account-level sync_accounts.accounts[].notification_configs[]. When false or absent, consumers fall back to wholesale polling, optionally with if_wholesale_feed_version probes.' - ), - ] - event_types: Annotated[ - list[EventType3] | None, - Field( - description='Wholesale feed webhook event types this agent can emit. Sales agents emit product.* events and MUST expose list_products or the deprecated 3.x wholesale get_products compatibility path. Signals agents emit signal.* events and MUST support wholesale get_signals. wholesale_feed.bulk_change requires at least one corresponding repair path.', - min_length=1, - ), - ] = None - - -class CapabilityReportingDeliveryMethod(StrEnum): - webhook = 'webhook' - offline = 'offline' - - -class BudgetCappingTimezoneBasis(StrEnum): - account = 'account' - fixed = 'fixed' - - -class ReportingDestinationOffering(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - pattern: Pattern - transports: Annotated[ - list[Transport], - Field( - description='Provider transports the seller can deliver through for this pattern.', - min_length=1, - ), - ] - formats: Annotated[ - list[Format] | None, - Field( - description='Physical file formats the seller can produce. Only meaningful for file_transfer.', - min_length=1, - ), - ] = None - access_modes: Annotated[ - list[AccessMode] | None, - Field( - description='Dataset-share access families the seller can provision. Only meaningful for dataset_share.', - min_length=1, - ), - ] = None - verification_profiles: Annotated[ - reporting_verification_profile_set.ReportingVerificationProfileSet, - Field( - description='Verification profiles the seller can produce for this pattern. A destination whose accepted_verification_profiles does not intersect this set is out of offering.' - ), - ] - - -class Principal(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - supported: Literal[True] - sync_task: Literal['sync_principal'] = 'sync_principal' - read_task: Annotated[ - Literal['get_principal'], - Field( - description="Side-effect-free task returning the caller's current connection configuration and version." - ), - ] = 'get_principal' - supported_sections: Annotated[ - list[SupportedSection], - Field( - description='Connection configuration sections this seller accepts. Unsupported sections are rejected rather than silently ignored.', - min_length=1, - ), - ] - max_reporting_destinations: Annotated[ - SchemaInt | None, - Field( - description='Maximum caller-scoped destination bindings when reporting_destinations is supported. The task schema has a portable maximum of 64; sellers may advertise a lower operational limit.', - ge=1, - le=64, - ), - ] = None - reporting_destination_offerings: Annotated[ - list[ReportingDestinationOffering] | None, - Field( - description='Objective per-pattern delivery offering, so buyers can select a compatible destination without submit-and-reject probing. A destination whose pattern, transport, access mode, format set, or verification-profile set falls outside this offering fails the sync request atomically with UNSUPPORTED_FEATURE; provider-side setup outcomes for in-offering destinations are expressed only through destination states.', - max_length=3, - min_length=1, - ), - ] = None - suspension_interval_seconds: Annotated[ - SchemaInt | None, - Field( - description='Advertised upper bound, in seconds, between a committed destination suspension or revocation — or a subscriber deactivation — and the halt of new deliveries and fires.', - ge=1, - le=86400, - ), - ] = None - caller_event_types: Annotated[ - list[notification_type.NotificationType] | None, - Field( - description='Notification types this seller can emit against caller-level subscribers, so buyers select from the offering instead of probing by rejection. Account-anchored entries fire with fire-time authorization scoping. When absent, only capabilities.changed is assumed. A caller-level subscriber requesting a type outside this set fails the sync request with UNSUPPORTED_FEATURE.', - min_length=1, - ), - ] = None - optimistic_concurrency: Annotated[ - StrictBool, - Field( - description='Whether expected_configuration_version is enforced. Sellers SHOULD support it when several services may authenticate as the same stable principal. When false, a request carrying expected_configuration_version is rejected with UNSUPPORTED_FEATURE rather than silently ignored.' - ), - ] - - -class TrustedMatch(AdCPBaseModel): - surfaces: Annotated[ - list[property_type.PropertyType] | None, - Field(description='Surface types this seller supports via TMP.'), - ] = None - - -class Language(AdCPBaseModel): - supported: Annotated[ - StrictBool | None, - Field(description='Whether seller supports language-preference targeting'), - ] = None - supported_languages: Annotated[ - list[locale_tag.LanguageTag] | None, - Field( - description='Exact canonical BCP 47 language ranges accepted in targeting.language. Omission means the seller makes no exhaustive language-list declaration.' - ), - ] = None - - -class KeywordTargets(AdCPBaseModel): - supported_match_types: Annotated[ - list[match_type.MatchType], - Field( - description='Match types this seller supports for keyword targets. Sellers must reject goals with unsupported match types.', - min_length=1, - ), - ] - - -class NegativeKeywords(AdCPBaseModel): - supported_match_types: Annotated[ - list[match_type.MatchType], - Field( - description='Match types this seller supports for negative keywords. Sellers must reject goals with unsupported match types.', - min_length=1, - ), - ] - - -class GeoProximity(AdCPBaseModel): - radius: Annotated[ - StrictBool | None, - Field( - description='Whether seller supports simple radius targeting (distance circle from a point)' - ), - ] = None - travel_time: Annotated[ - StrictBool | None, - Field( - description='Whether seller supports travel time isochrone targeting (requires a routing engine)' - ), - ] = None - geometry: Annotated[ - StrictBool | None, - Field( - description='Whether seller supports pre-computed GeoJSON geometry (buyer provides the polygon)' - ), - ] = None - transport_modes: Annotated[ - list[transport_mode.TransportMode] | None, - Field( - description='Transport modes supported for travel_time isochrones. Only relevant when travel_time is true.', - min_length=1, - ), - ] = None - - -class Targeting(AdCPBaseModel): - geo_countries: Annotated[ - StrictBool | None, - Field(description='Country-level targeting using ISO 3166-1 alpha-2 codes'), - ] = None - geo_regions: Annotated[ - StrictBool | geo_region_support.GeographicRegionSupport | None, - Field( - description='ISO 3166-2 subdivision inclusion targeting. A legacy boolean is a coarse seller-wide declaration. Structured country/value entries are individually supported within the response scope, but do not promise joint composability or availability through the same execution route or account. Only Product.overlay_support supplies the binding set of executable targeting permissions for a configured Product.' - ), - ] = None - geo_regions_exclude: Annotated[ - StrictBool | geo_region_support.GeographicRegionSupport | None, - Field( - description='ISO 3166-2 subdivision exclusion targeting, declared independently from inclusion. Structured country/value entries are individually supported within the response scope, but do not promise joint composability or availability through the same execution route or account. Only Product.overlay_support supplies the binding set of executable targeting permissions for a configured Product; absence means buyers cannot infer exclusion support from geo_regions alone.' - ), - ] = None - geo_metros: Annotated[ - GeoMetros | None, - Field( - description='Metro area targeting. Properties indicate which classification systems are supported.' - ), - ] = None - geo_postal_areas: Annotated[ - postal_area_support.PostalAreaSupport | None, - Field( - description='Postal area targeting. Prefer the native country-keyed map where each ISO 3166-1 alpha-2 country lists supported country-local postal systems. Deprecated legacy country-fused postal-system boolean aliases may be emitted alongside native country keys during migration.' - ), - ] = None - geo_places: Annotated[ - dict[ - geo_place_system.GeographicPlaceIdentifierSystem, - geo_place_support.GeographicPlaceSystemSupport, - ] - | None, - Field( - description='Place targeting support keyed by collision-safe identifier system. Each system declares exact country-to-place-type combinations, accepted catalog versions, and a machine-readable resolver. Sellers MUST reject unsupported systems, country/type pairs, versions, and identifiers rather than silently dropping them.', - min_length=1, - ), - ] = None - age_restriction: Annotated[ - AgeRestriction | None, - Field(description='Age restriction capabilities for compliance (alcohol, gambling)'), - ] = None - demographics: Annotated[ - Demographics | None, - Field( - description='Seller-wide discovery rollup for canonical demographic targeting. supported=true means at least one product implements the demographic targeting contract; it does not authorize demographic targeting on every product. Buyers MUST inspect Product.demographic_targeting for exact execution modes, bounds, intervals, and unknown-age behavior.' - ), - ] = None - language: Annotated[ - StrictBool | Language | None, - Field( - description="Language-preference targeting support. A legacy boolean is a coarse declaration. The structured form can enumerate the exact canonical BCP 47 targeting ranges the seller accepts. Buyers MUST treat supported_languages as exact selectable values: a declared 'fr' does not by itself authorize a request for 'fr-CA'. Sellers MUST reject unsupported requested values rather than silently widening or dropping them." - ), - ] = None - keyword_targets: Annotated[ - KeywordTargets | None, - Field( - description='Keyword targeting capabilities. Presence indicates support for targeting_overlay.keyword_targets and keyword_targets_add/remove in update_media_buy.' - ), - ] = None - negative_keywords: Annotated[ - NegativeKeywords | None, - Field( - description='Negative keyword capabilities. Presence indicates support for targeting_overlay.negative_keywords and negative_keywords_add/remove in update_media_buy.' - ), - ] = None - placement_selection: Annotated[ - StrictBool | None, - Field( - description='When true, seller-wide rollup indicating at least one product supports targeting_overlay.placement_selection. False or absence makes no product-level promise. Product.overlay_support and Product.placements remain authoritative for a selected product.' - ), - ] = None - property_list: Annotated[ - StrictBool | None, - Field( - description='When true, seller-wide rollup indicating at least one product supports targeting_overlay.property_list inclusion targeting. False or absence makes no product-level promise. Product.overlay_support is authoritative.' - ), - ] = None - property_list_exclude: Annotated[ - StrictBool | None, - Field( - description='When true, seller-wide rollup indicating at least one product supports targeting_overlay.property_list_exclude targeting. False or absence makes no product-level promise. Product.overlay_support is authoritative.' - ), - ] = None - collection_list: Annotated[ - StrictBool | None, - Field( - description='When true, seller-wide rollup indicating at least one product supports targeting_overlay.collection_list inclusion targeting. False or absence makes no product-level promise. Product.overlay_support is authoritative.' - ), - ] = None - collection_list_exclude: Annotated[ - StrictBool | None, - Field( - description='When true, seller-wide rollup indicating at least one product supports targeting_overlay.collection_list_exclude targeting. False or absence makes no product-level promise. Product.overlay_support is authoritative.' - ), - ] = None - geo_proximity: Annotated[ - GeoProximity | None, - Field( - description='Proximity targeting capabilities from arbitrary coordinates via targeting_overlay.geo_proximity.' - ), - ] = None - - -class ConversionTracking(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - multi_source_event_dedup: Annotated[ - StrictBool | None, - Field( - description='Whether this seller can deduplicate conversion events across multiple event sources within a single goal. When true, the seller honors the deduplication semantics in optimization_goals event_sources arrays — the same event_id from multiple sources counts once. When false or absent, buyers should use a single event source per goal; multi-source arrays will be treated as first-source-wins. Most social platforms cannot deduplicate across independently-managed pixel and CAPI sources.' - ), - ] = None - per_creative_attribution: Annotated[ - StrictBool | None, - Field( - description='Whether the seller can attribute conversions to specific creatives within a package and surface that breakdown via media_buy_deliveries[].by_package[].by_creative[].conversions in get_media_buy_delivery. Defaults to false when omitted. Sellers that report conversions only at the line / package / placement / campaign granularity (retail-media, MMP-mediated mobile, CTV performance) declare false (or omit) and the per-creative scenario grades not_applicable for them. Sellers that surface ad-level conversion attribution (most social platforms) declare true and the scenario asserts the breakdown is populated end-to-end. Defaults to false to preserve backward compatibility.' - ), - ] = None - supported_event_types: Annotated[ - list[event_type_1.EventType] | None, - Field( - description='Event types this seller can track and attribute. If omitted, all standard event types are supported.', - min_length=1, - ), - ] = None - supported_targets: Annotated[ - list[SupportedTarget1] | None, - Field( - description='Event-goal target kinds this seller can compute against. Buyers should only submit event-kind optimization goals whose target.kind is listed here — sellers MUST reject goals with unlisted target kinds. When omitted, only target-less event goals (maximize conversion count within budget) are guaranteed; sellers MAY accept specific target kinds but buyers should not rely on it. Named to parallel `metric_optimization.supported_targets` at the product level — same concept (which target kinds are supported), one at seller-capability granularity and one at product granularity.', - min_length=1, - ), - ] = None - supported_uid_types: Annotated[ - list[uid_type.UidType] | None, - Field(description='Universal ID types accepted for user matching', min_length=1), - ] = None - supported_hashed_identifiers: Annotated[ - list[SupportedIdentifierType] | None, - Field( - description='Hashed PII types accepted for user matching. Buyers must hash before sending (SHA-256, normalized).', - min_length=1, - ), - ] = None - supported_action_sources: Annotated[ - list[action_source.ActionSource] | None, - Field(description='Action sources this seller accepts events from', min_length=1), - ] = None - attribution_windows: Annotated[ - list[AttributionWindow] | None, - Field( - description='Attribution windows available from this seller. Single-element arrays indicate fixed windows; multi-element arrays indicate configurable options the buyer can choose from via attribution_window on optimization goals.' - ), - ] = None - - -class FrequencyCapping(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supported_per_units: Annotated[ - list[reach_unit.ReachUnit] | None, - Field( - description='Entity granularities the seller can enforce caps against. Values from the reach-unit enum. Omit to indicate all reach-unit values are supported.', - min_length=1, - ), - ] = None - supported_window_units: Annotated[ - list[str] | None, - Field( - description="Duration units the seller supports for frequency cap windows. Values must match the duration.json unit enum (e.g., 'hours', 'days', 'campaign'). Omit to indicate all duration units are supported.", - min_length=1, - ), - ] = None - - -class BudgetCapping(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supported_scopes: Annotated[ - list[SupportedScope], - Field( - description='Scopes where the seller enforces hard daily caps. media_buy bounds aggregate spend across all packages without allocating it. package bounds one package and remains subordinate to any aggregate cap.', - min_length=1, - ), - ] - supported_periods: Annotated[ - list[Literal['day']], - Field( - description='Cap reset periods the seller enforces. AdCP 3.2 defines day only; the array form reserves room for future periods as an additive change.', - min_length=1, - ), - ] - timezone_basis: Annotated[ - BudgetCappingTimezoneBasis, - Field( - description='Source of the default cap-day boundary. account uses the selected Account.timezone and therefore supports different boundaries for different accounts. fixed uses fixed_timezone for every media buy regardless of account timezone.' - ), - ] - fixed_timezone: Annotated[ - str | None, - Field( - description='Feature-specific default cap-day timezone used when timezone_basis is fixed. Use UTC or an IANA timezone identifier.', - min_length=1, - ), - ] = None - buyer_timezone_override: Annotated[ - StrictBool | None, - Field( - description='When true, buyers MAY override the default cap-day boundary via media-buy-level budget_cap_timezone. The accepted timezone applies to aggregate and package caps alike. When false or absent, a submitted override is rejected with UNSUPPORTED_FEATURE.' - ), - ] = None - - -class SponsoredIntelligence(AdCPBaseModel): - endpoint: Annotated[Endpoint, Field(description='SI agent endpoint configuration')] - capabilities: Annotated[ - si_capabilities.SiCapabilities, - Field(description='Modalities, components, and commerce capabilities'), - ] - brand_url: Annotated[ - AnyUrl | None, Field(description='URL to brand.json with colors, fonts, logos, tone') - ] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - rights: Annotated[ - StrictBool | None, - Field( - description='Supports get_rights and acquire_rights for rights discovery and clearance' - ), - ] = False - right_types: Annotated[ - list[right_type.RightType] | None, - Field(description='Types of rights available through this agent'), - ] = None - available_uses: Annotated[ - list[right_use.RightUse] | None, - Field(description="Rights uses available across this agent's roster"), - ] = None - generation_providers: Annotated[ - list[str] | None, - Field(description='LLM/generation providers this agent can issue credentials for'), - ] = None - description: Annotated[ - str | None, - Field( - description="Description of the agent's brand protocol capabilities", max_length=5000 - ), - ] = None - - -class RepresentationResolution(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supported: Annotated[ - Literal[True], - Field( - description='True means the agent accepts CreativeRepresentationSet input and returns `representation_selection` or per-candidate structured rejections.' - ), - ] - strategies: Annotated[ - list[representation_selection_strategy.RepresentationSelectionStrategy], - Field( - description='Deterministic selection strategies the seller implements after compatibility filtering. `representation_order` selects the first compatible source entry. `highest_compatible_vast` selects the highest exact VAST version from the already-intersected candidate set, with equal-version ties resolved by source array order.', - min_length=1, - ), - ] - - -class Metric(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - metric_id: Annotated[ - vendor_metric_id.VendorMetricId, - Field( - description="Identifier for the metric within the vendor's vocabulary. Combined with the agent's BrandRef, forms the canonical tuple `(vendor.domain, vendor.brand_id, metric_id)`. Each metric_id MUST be unique within a single agent's catalog." - ), - ] - standard_reference: Annotated[ - AnyUrl | None, - Field( - description='Optional URI pointing at the published standard this metric IMPLEMENTS (e.g., IAB Attention Measurement Guidelines, MRC Viewable Impression Measurement, GARM emissions framework). Distinct from `accreditations[]` — `standard_reference` is what the metric is built against; `accreditations[]` is third-party certification that the implementation actually conforms. Buyer agents normalizing across vendors SHOULD apply the AdCP URL canonicalization rules before comparing — vendors implementing the same standard MAY use different URL forms for the same canonical document.' - ), - ] = None - accreditations: Annotated[ - list[Accreditation] | None, - Field( - description="Third-party accreditations this metric holds (MRC, ARF, JIC, ABC, BARB, AGOF, etc.). Distinct from `standard_reference`: a metric can implement a standard without being independently accredited. Buyers asking 'is this MRC-accredited?' SHOULD check this array, not just `standard_reference`. Each entry names the accrediting body and optionally pins a certification ID, validity date, and evidence URL." - ), - ] = None - unit: Annotated[ - str | None, - Field( - description='Unit of the metric value when reported via `vendor_metric_values.value` (e.g., `score`, `seconds`, `persons`, `gCO2e`, `lift_percent`, `USD`). Buyers SHOULD render the unit alongside the value rather than computing units locally; sellers populating `vendor_metric_values.unit` MUST match this declaration when present.', - examples=['score', 'seconds', 'persons', 'gCO2e', 'lift_percent', 'index', 'USD'], - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Human-readable description of what this metric measures and any relevant methodology notes. Surfaced in buyer-agent UX when explaining the metric to humans.' - ), - ] = None - methodology_url: Annotated[ - AnyUrl | None, - Field( - description="URL to the vendor's full methodology documentation for this metric. Buyers SHOULD link or fetch this when human review of the methodology is in scope (compliance, RFP review, accreditation audit). Field name mirrors `governance.property_features[].methodology_url`." - ), - ] = None - methodology_version: Annotated[ - str | None, - Field( - description='Optional version identifier (semver, ISO date, or vendor-defined version string) for the methodology this metric currently implements. When present, buyer agents pin the contracted version via `committed_metrics[].methodology_version` (vendor scope) so silent vendor methodology changes are detectable; absence means the vendor does not version their methodology and buyers MUST treat any change as untracked.', - examples=['v2.1', '2026-Q1', '1.0'], - ), - ] = None - ext: ext_1.ExtensionObject | None = None - - -class Measurement(AdCPBaseModel): - produces_performance_feedback: Annotated[ - StrictBool | None, - Field( - description="Whether this measurement agent produces compact provide_performance_feedback assertions for a buyer-controlled orchestrator gateway. In the first experimental tier the provider reads delivery through the gateway's get_media_buy_delivery task and returns assertions through its provide_performance_feedback task. The gateway decides what to forward to each seller." - ), - ] = False - metrics: Annotated[ - list[Metric], - Field( - description="Metrics this agent computes. Each entry is identified by `metric_id` within the vendor's vocabulary; the canonical reference everywhere a measurement value appears (`committed_metrics`, `vendor_metric_values`, `missing_metrics`) is the tuple `(vendor.domain, vendor.brand_id, metric_id)`.", - min_length=1, - ), - ] - - -class CreativeSpecs(AdCPBaseModel): - vast_versions: Annotated[ - list[vast_version.VastVersion] | None, - Field( - description='Seller-wide VAST execution ceiling. Each product format option declares its binding accepted subset in `params.vast_versions`.', - min_length=1, - ), - ] = None - macro_resolution_capabilities: Annotated[ - list[macro_resolution_capability.MacroProcessingCapability] | None, - Field( - description='Seller-wide ceiling for exact macro processing tuples (dialect identity/revision, semantic mapping, operation, actor, context, and encoding). It never proves a product execution path supports the same tuple and does not claim tracker firing; inspect the selected format option and, when standardized, product tracker capabilities.', - min_length=1, - ), - ] = None - mraid_versions: Annotated[ - list[MraidVersion] | None, - Field(description='MRAID versions supported for rich media mobile creatives'), - ] = None - vpaid: Annotated[ - StrictBool | None, Field(description='VPAID support for interactive video ads') - ] = None - simid: Annotated[ - StrictBool | None, Field(description='SIMID support for interactive video ads') - ] = None - vast_validation: Annotated[ - VastValidation | None, - Field( - description="Level of VAST asset validation the seller performs at sync_creatives (including dry_run): 'structural' checks manifest shape and format requirements only and never inspects the VAST document; 'document' additionally parses the VAST document and can return VAST_PARSE_FAILED / VAST_VERSION_MISMATCH; 'wrapper' additionally resolves the wrapper chain and can return VAST_WRAPPER_DEPTH_EXCEEDED. Absent means 'structural'. See the VAST Validation section of the video channel documentation for the normative checks at each level." - ), - ] = VastValidation.structural - - -class Execution(AdCPBaseModel): - trusted_match: Annotated[ - TrustedMatch | None, - Field( - description='Trusted Match Protocol (TMP) support. Presence of this object indicates the seller has TMP infrastructure deployed. Check individual products via get_products for per-product TMP capabilities.' - ), - ] = None - axe_integrations: Annotated[ - list[AnyUrl] | None, - Field( - deprecated=True, - description='Deprecated. Legacy AXE integrations. Use trusted_match for new integrations.', - ), - ] = None - creative_specs: Annotated[ - CreativeSpecs | None, Field(description='Creative specification support') - ] = None - targeting: Annotated[ - Targeting | None, - Field( - description='Targeting capabilities. If declared true/supported, buyer can use these targeting parameters and seller MUST honor them.' - ), - ] = None - - -class SupportedFormat(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - capability_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format capability within the creative agent (e.g., 'audio_narration_30s'). The value MUST be unique across this agent's supported_formats catalog. Route the advertised operation with this ID: build_creative.target_capability_id(s) for build, preview_creative.target_capability_id for preview, or validate_input targets[] kind capability for validate. Capability IDs are agent-local routes and do not appear on portable manifests or products.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] = None - format: Annotated[ - creative_operation_format_declaration.CreativeOperationFormatDeclaration, - Field( - description='Format declaration on which this agent can perform the listed operations. Creative-agent capability self-description has no seller production authority, so tracker_execution_contract and tracker_execution_contract_digest are forbidden.' - ), - ] - operations: Annotated[ - list[Operation] | None, - Field( - description='Creative operations this capability supports. `build` means the agent can produce a conforming manifest via build_creative; `validate` means it can evaluate inputs against the declaration; `preview` means it can render a preview. New 3.2 producers MUST emit this field so buyers and registries can distinguish producers, validators, and renderers without probing tasks. Consumers interpret omission from a legacy 3.x entry as `["build"]`.', - min_length=1, - ), - ] = [Operation.build] - - -class Creative(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supports_compliance: Annotated[ - StrictBool | None, - Field( - description='When true, this creative agent can process briefs with compliance requirements (required_disclosures, prohibited_claims) and will validate that disclosures can be satisfied by the target format.' - ), - ] = None - has_creative_library: Annotated[ - StrictBool | None, - Field( - description='When true, this agent hosts a creative library and supports list_creatives and creative_id references in build_creative. Creative agents with a library should also implement the accounts protocol (sync_accounts / list_accounts) so buyers can establish access.' - ), - ] = False - supports_revisions: Annotated[ - StrictBool | None, - Field( - description='When true, this agent accepts buyer-assigned revision_id on sync_creatives, enforces immutable revision content, echoes accepted revision identity, returns it from list_creatives, and attributes delivered executions to it. Revision support does not imply revision history, rollback, or staged activation.' - ), - ] = False - supports_generation: Annotated[ - StrictBool | None, - Field( - description='When true, this agent can generate creatives from natural language briefs via build_creative. The buyer provides a message with creative direction, and the agent produces a manifest with generated assets. When false, build_creative only supports transformation or library retrieval.' - ), - ] = False - supports_transformation: Annotated[ - StrictBool | None, - Field( - description='When true, this agent can transform or resize existing canonical manifests via build_creative. The buyer supplies a creative_manifest and an advertised target_capability_id.' - ), - ] = False - representation_resolution: Annotated[ - RepresentationResolution | None, - Field( - description='Explicit opt-in for deterministic seller-bound selection from `build_creative.creative_representation_set`. Only the destination sales agent may advertise and exercise this capability because resolution requires its current product, placement/publisher narrowings, and seller-wide execution ceilings. A standalone creative agent may help a buyer select locally but MUST NOT advertise this capability or claim seller deliverability. Absence means the caller selects a representation before sending a seller-bound manifest; the agent MUST NOT guess silently.' - ), - ] = None - supports_transformers: Annotated[ - StrictBool | None, - Field( - description='When true, this agent exposes account-scoped creative transformers via list_transformers (the creative analog of media-buy products) and accepts transformer_id + config on build_creative. Buyers SHOULD call list_transformers to discover available transformers, their typed config params (and account-scoped enumerable option values via expand_params), and pricing. When false or absent, the agent does not offer the transformer surface.' - ), - ] = False - supports_refinement: Annotated[ - StrictBool | None, - Field( - description="When true, this agent retains produced build_variant leaves for an agent-defined retention window and can re-build from one via build_creative's refine_from_build_variant_id — applying a natural-language instruction in message plus an optional config delta, returning new lineage-linked variants. A build-time agent capability independent of generation/transformation. When false or absent, refine_from_build_variant_id is rejected with UNSUPPORTED_FEATURE; buyers refine instead via the transform path (creative_manifest + message)." - ), - ] = False - supports_spend_controls: Annotated[ - StrictBool | None, - Field( - description='When true, build_creative honors a per-call `max_spend` ceiling (producing partial paid results and returning budget_status:"capped" + a BUDGET_CAP_REACHED advisory rather than overspending) AND supports mode:"estimate" dry-runs (a projected cost band, producing/billing nothing). When false or absent, max_spend / mode:estimate are rejected with UNSUPPORTED_FEATURE. Out-of-band billers (bills_through_adcp:false) have no AdCP cost truth to cap against, so this is meaningful only alongside bills_through_adcp:true.' - ), - ] = False - supports_evaluator: Annotated[ - StrictBool | None, - Field( - description="Experimental (x-status: experimental) — agents setting this true MUST also list `creative.evaluator` in `experimental_features`; the surface MAY change between 3.x releases with notice (see docs/reference/experimental-status). When true, build_creative accepts an advisory `evaluator` input (exemplars / account-arranged evaluator_id / agent_url, plus an optional `feature_requirement[]` gate, a `rank_by` ordering, and an allowlisted `feature_agent` pointer). Feature discovery uses this response's governance.creative_features catalog: rank_by, feature_requirement, and eval.features[] all share the same creative-feature vocabulary as get_creative_features. evaluator_id is not discovered from this catalog; it is a pre-provisioned account preset whose emitted feature_ids still come from it. The evaluator populates a per-leaf `eval` block of creative-feature values (creative-feature-result[], the same shape get_creative_features returns) on BuildCreativeVariantSuccess leaves, which is what the recommended/rank it sets on the best_of_n axis are computed over. The agent runs a gate-then-rank pipeline over its best_of_n exploration: it evaluates each leaf, DROPS leaves failing `feature_requirement[]` from its recommended survivors, then orders survivors by `rank_by`. The gate is internal pruning of which leaves the agent recommends/returns from its own exploration — it never blocks an already-produced billable leaf: what is produced and billed is governed by max_variants/max_creatives/max_spend, not the evaluator. When the evaluator names an external agent, it MUST appear in `creative_policy.accepted_verifiers[]` (off-list → EVALUATOR_AGENT_NOT_ACCEPTED), and the producing agent authenticates the outbound evaluator call on the transport. Evaluator credentials and caller-supplied trust material MUST NOT be passed in the build_creative payload; credential- or trust-material payload keys should be rejected with CREDENTIAL_IN_ARGS. When false or absent, the `evaluator` input is ignored and no `eval` block is emitted." - ), - ] = False - refinable_retention_seconds: Annotated[ - SchemaInt | None, - Field( - description='When supports_refinement is true, the GUARANTEED-MINIMUM window (a floor, not a ceiling) during which a produced build_variant_id remains refinable via refine_from_build_variant_id: a ref within this window from production SHOULD resolve; the agent MAY retain longer. Omit when the retention window is agent-defined and not advertised — buyers then treat refinability as best-effort and handle REFERENCE_NOT_FOUND.', - ge=0, - ), - ] = None - multiplicity: Annotated[ - Multiplicity | None, - Field( - description="Pre-call discriminators for build_creative fan-out, so a buyer knows BEFORE sending max_creatives / max_variants whether this agent supports them and the ceilings. Over-limit requests are CLAMPED to these ceilings (the agent produces up to the limit and signals the shortfall via items_returned < items_total on BuildCreativeVariantSuccess), not rejected — consistent with item_limit's 'use the lesser' rule. Absent means no fan-out: build_creative produces a single creative and max_creatives/max_variants>1 are not supported." - ), - ] = None - supported_formats: Annotated[ - list[SupportedFormat] | None, - Field( - description='Canonical-format capability catalog for this creative agent. This is the 3.2 source of truth for discovering which format contracts the agent can build, validate, or preview; it replaces the deprecated `list_creative_formats` task. Each entry uses the authority-free `CreativeOperationFormatDeclaration` projection of a product format declaration: canonical shape and creative-route macro processing are preserved, while seller production commitments are excluded. New 3.2 producers MUST publish a stable agent-local `capability_id` and explicit `operations` for task routing. Every emitted `capability_id` MUST be unique within this catalog so a route selects exactly one entry. During the 3.x compatibility window, consumers MUST also accept legacy entries that omit either field; absent `operations` means `["build"]`, while an absent `capability_id` means the entry is discoverable by canonical contract but cannot be selected through a capability-ID route.\n\n**Publisher-specific support.** To claim exact support for a publisher declaration, `format` carries the declaration\'s `{publisher_domain, format_option_id}` pair plus its canonical `format_kind` and narrowed `params`. Generic creative agents MAY instead advertise a canonical parameter envelope without publisher identity. A generic capability matches a target declaration only when the capability can satisfy every target constraint; matching canonical names alone is insufficient. Registries MAY reverse-index these entries by `format.format_kind`, `format.publisher_domain`, and `format.format_option_id`.\n\nThis catalog describes creative operations, not sales-agent inventory deliverability. Sales agents publish the purchasable closed set on each `Product.format_options[]`; publisher acceptance lives in `adagents.json.formats[]`.' - ), - ] = None - preview: Annotated[ - Preview | None, - Field( - description='Per-route preview_creative capability metadata. New 3.2 producers whose supported_formats[] explicitly advertises a routable preview operation MUST emit this block. rendering_origin describes how each route is implemented but is informational and never grants presentation authority: only a matching publisher-origin placement preview_provider delegation can do that. routes[].capability_id MUST equal the set of capability IDs on supported_formats[] entries whose operations contains preview.' - ), - ] = None - localization: Annotated[ - Localization | None, - Field( - description='Materialized creative-localization support for sync_creatives/list_creatives, including source-only monolingual topology. Presence opts the agent into exact locale-variant round-trip, strict RFC 4647 Lookup, optional buyer-declared language-family fallback rules, explicit final default/unmatched behavior, creative-wide review, transactional replacement, seller product-format locale-policy enforcement, and delivery attribution. This is a coarse structural capability, not a promise that every locale/format/account combination is accepted; sellers publish accepted ranges on product format declarations and validate each write before mutation. Omit this object when localization is unsupported.' - ), - ] = None - bills_through_adcp: Annotated[ - StrictBool | None, - Field( - description='When true, this creative agent bills through the AdCP rate-card surface: list_creatives returns pricing_options when include_pricing=true with an authenticated account, build_creative populates pricing_option_id and vendor_cost on the response, and report_usage accepts records against the rate card. When false or absent, the agent bills out of band (flat license, SaaS contract, bundled enterprise agreement) and buyers should skip pricing fields and tolerate report_usage returning accepted: 0 with errors carrying BILLING_OUT_OF_BAND. A pre-call discriminator so buyer agents can route across many creative agents without first establishing an account to probe pricing.' - ), - ] = False - canonical_catalog_version: Annotated[ - str | None, - Field( - description="Optional. The AdCP canonical-formats catalog version this agent's runtime is built against (e.g., `3.1`, `3.2.0`). Lets buyer SDKs detect canonical-catalog skew between their generated types and the seller's actual support. SDKs MAY declare the version they were generated against (typically the AdCP version they ship for); when seller and SDK versions disagree, SDKs SHOULD soft-warn rather than fail (the open-enum semantics on `canonical-format-kind.json` make unknown canonicals safe to retain, so skew is not a hard error — it just means the older side might not understand newer canonical values). Omitted by sellers who haven't yet generated against a versioned catalog; absence is interpreted as the AdCP version advertised by the broader capabilities response.", - pattern='^\\d+\\.\\d+(\\.\\d+)?$', - ), - ] = None - - -class AudienceTargeting(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supported_identifier_types: Annotated[ - list[SupportedIdentifierType], - Field( - description='PII-derived identifier types accepted for audience matching. Buyers should only send identifiers the seller supports.', - min_length=1, - ), - ] - supports_platform_customer_id: Annotated[ - StrictBool | None, - Field( - description="Whether the seller accepts the buyer's CRM/loyalty ID as a matchable identifier. Only applicable when the seller operates a closed ecosystem with a shared ID namespace (e.g., a retailer matching against their loyalty program). When true, buyers can include platform_customer_id values in AudienceMember.identifiers for matching against the seller's identity graph. Reporting on matched platform_customer_ids typically requires a clean room or the seller's own reporting surface." - ), - ] = None - supported_uid_types: Annotated[ - list[uid_type.UidType] | None, - Field( - description='Universal ID types accepted for audience matching (MAIDs, RampID, UID2, etc.). MAID support varies significantly by platform — check this field before sending uids with type: maid.', - min_length=1, - ), - ] = None - minimum_audience_size: Annotated[ - SchemaInt, - Field( - description='Minimum matched audience size required for targeting. Audiences below this threshold will have status: too_small. Varies by platform (100–1000 is typical).', - ge=1, - ), - ] - supported_activation_methods: Annotated[ - list[audience_activation_method.AudienceActivationMethod] | None, - Field( - description="Union of audience_activation.methods across the seller's products. Fast-fail discovery: a buyer reads this once and skips the catalog walk when nothing overlaps its pipeline. Per-product declarations are the source of truth; sellers MUST keep this consistent with the catalog. Operational coordinates are account-scoped: the union MAY omit consumer_identities and destination_ref until bilateral account setup establishes them, and MUST NOT expose another account's coordinates. Absence of this field with media_buy.audience_activation listed in experimental_features means walk the catalog; only a present, non-overlapping union is a fast-fail signal. Experimental (x-status: experimental): sellers implementing audience activation declarations MUST list media_buy.audience_activation in experimental_features. Per docs/reference/experimental-status, this surface MAY change between 3.x releases with notice.", - min_length=1, - ), - ] = None - matching_latency_hours: Annotated[ - MatchingLatencyHours | None, - Field( - description='Expected matching latency range in hours after upload. Use to calibrate polling cadence and set appropriate expectations before configuring push_notification_config.' - ), - ] = None - - -class MediaBuy(AdCPBaseModel): - anonymous_discovery: Annotated[ - StrictBool | None, - Field( - description='Whether this seller accepts product discovery without caller credentials. This applies to list_products and to get_products in brief or wholesale mode; it does not apply to proposal refinement or finalization, purchasing, account-scoped reads, or mutations. true means an anonymous discovery request can produce a successful response, but the response may be a public subset and may differ from results for an authenticated principal or selected account. false means these discovery calls require an authenticated principal. Absence means unspecified legacy behavior, so callers probe and handle AUTH_MISSING. A valid authenticated request is never rejected merely because credentials were supplied. true is inconsistent with account.required_for_products=true because an anonymous caller cannot select protected account context.' - ), - ] = None - acceptance_policy_discovery: Annotated[ - AcceptancePolicyDiscovery | None, - Field( - description='Registry-backed seller acceptance-policy discovery. Presence means the seller publishes a versioned catalog; it does not claim that the seller evaluates acceptance_context during discovery. Discovery is advisory, exact task responses remain authoritative, and absent capability means support is unknown rather than unrestricted acceptance.' - ), - ] = None - supported_pricing_models: Annotated[ - list[pricing_model.PricingModel] | None, - Field( - description='Pricing models this seller supports across its product portfolio. Buyers can use this for pre-flight filtering before querying individual products. Individual products may support a subset of these models.', - min_length=1, - ), - ] = None - buying_modes: Annotated[ - list[BuyingMode] | None, - Field( - description="Buying modes this seller supports on get_products. 'brief' (semantic discovery driven by the brief) is universally supported and implicit. 'wholesale' (raw wholesale product feed enumeration — caller omits brief and the seller returns the full priced product feed, paginated) is opt-in and SHOULD be declared explicitly so buyers can probe before issuing wholesale calls. 'refine' lets buyers iterate on prior products/proposals and is also the vehicle for finalizing draft proposals when the seller returns them. Sellers MAY declare ['brief', 'wholesale'] to signal wholesale support; absent declaration is treated as ['brief'] for wholesale-feed probing purposes and sellers MAY return INVALID_REQUEST for wholesale calls they do not support. Symmetric with signals.discovery_modes.", - min_length=1, - ), - ] = [BuyingMode.brief] - measurement_terms_acceptance: Annotated[ - StrictBool | None, - Field( - description="Whether this seller can accept the default measurement_terms it advertises on a product. A value of true means the seller can return a product carrying measurement_terms for a measurement-specific brief and accept those terms unchanged on a package for that product. This opts the seller into conformance scenarios that discover and replay the product's own terms. False or absent means acceptance is outside the seller's advertised scope, but the seller must still reject unsupported terms with TERMS_REJECTED rather than being graded on an acceptance path it did not claim." - ), - ] = False - availability_horizon: Annotated[ - StrictBool | None, - Field( - description='Whether this seller supports flexible-window availability discovery: parsing offer_filters.availability_horizon and answering with time-dimensioned forecast points that carry availability_status. Sellers declaring true MUST apply the full window contract — half-open non-overlapping windows that partition the requested horizon (or signal gaps via incomplete[]), with availability_status computed from all booking eligibility constraints, not only competing holds. false or absent means flexible-window support is unknown: buyers SHOULD use exact start_date/end_date filtering, and sellers MAY ignore the field or reject it. Conformance storyboards gate flexible-window checks on this declaration.' - ), - ] = False - lifecycle_tools: Annotated[ - list[LifecycleTool] | None, - Field( - description='Compact product and MediaBuy lifecycle operation names this seller supports. Added in AdCP 3.2 as task-specific contracts that form the 4.0 lifecycle foundation. Sellers may advertise any supported subset while retaining the deprecated get_products/create_media_buy/update_media_buy facades throughout 3.x. Each stateful split task has its own idempotency identity; callers MUST retry with the same tool name.', - min_length=1, - ), - ] = None - proposal_refinement: Annotated[ - ProposalRefinement | None, - Field( - description='Pre-flight support for typed refine_proposals revision dimensions. These declarations mean the seller can parse and mechanically validate a dimension; they never promise that the seller will commercially concede it. Absence means typed-dimension support is unknown and buyers must handle per-result partial or unable outcomes. A dimension omitted from an explicit supported_dimensions list MUST be rejected at task level with UNSUPPORTED_FEATURE before any proposal is created.' - ), - ] = None - reporting_delivery_methods: Annotated[ - list[CapabilityReportingDeliveryMethod] | None, - Field( - description="How this seller delivers reporting data to buyers. Polling via get_media_buy_delivery is always available as a baseline regardless of this field. This array declares additional push-based delivery methods the seller supports. 'webhook': seller pushes to buyer-provided URL (configured per buy via reporting_webhook). 'offline': seller pushes batch files to a cloud storage bucket (seller-provisioned per account via reporting_bucket on the account object). When absent, only polling is available.", - min_length=1, - ), - ] = None - performance_feedback: Annotated[ - PerformanceFeedback | None, - Field( - description='Structured seller performance-feedback support beyond the legacy scalar contract. Presence means the seller accepts compact baseline/metric/provenance fields from a buyer orchestrator and returns a feedback_id.' - ), - ] = None - offline_delivery_protocols: Annotated[ - list[cloud_storage_protocol.CloudStorageProtocol] | None, - Field( - description="Cloud storage protocols this seller supports for offline file delivery. Only meaningful when reporting_delivery_methods includes 'offline'. Buyers express a protocol preference in sync_accounts; the seller provisions the account's reporting_bucket using a supported protocol.", - min_length=1, - ), - ] = None - reporting_delivery: Annotated[ - reporting_delivery_capabilities.ReportingDeliveryCapabilities | None, - Field( - description='AdCP 3.2 Reliable Reporting capability. The affirmative machine answer to ‘Do you support Reliable Reporting?’ requires this block with supported: true and reliable_reporting_version: 1.0 plus media_buy.reporting_delivery in experimental_features. Core exposes seller get_reporting_status; during the published migration window, consumer_status_task separately advertises opt-in buyer-to-seller sync_reporting_status and becomes required Core only in the next eligible minor. managed_delivery and reconciled_billing identify optional tiers. This generalizes, but does not remove, the legacy reporting_delivery_methods/offline_delivery_protocols surface; those legacy fields and declarations from other protocols do not imply Reliable Reporting support.' - ), - ] = None - supports_proposals: Annotated[ - StrictBool | None, - Field( - description='Conformance declaration that this seller supports proposals through either the compact request/refine/finalize lifecycle or the legacy get_products facade. accept_proposal, or the create_media_buy compatibility facade, consumes a finalized committed proposal_id before expires_at.' - ), - ] = False - outcome_target: Annotated[ - StrictBool | None, - Field( - description="Whether this seller supports reverse-forecast planning: parsing criteria.outcome_target (a compact goal — a forecastable-metric delivery metric or an event-type conversion event — plus a desired volume, a cost_per target, or both) and planning against it, answering with total_budget_guidance and forecasts whose points carry the goal's key in metrics; see outcome-target.json for cost_per answers and rejections. This flag does not distinguish sellers that plan cost_per; buyers rely on the negotiated adcp_version together with features.bidding_policy. false or absent means support is unknown: buyers SHOULD express outcome goals in brief prose instead, and sellers reject a structured outcome_target on proposal requests with UNSUPPORTED_FEATURE rather than silently ignoring it (list_products ignores it for every seller)." - ), - ] = False - governance_aware: Annotated[ - StrictBool | None, - Field( - description='Compatibility claim used by existing media-buy conformance runners. A value of true corresponds only to online governance consultation for create_media_buy, the historically graded surface. Agents use adcp.governance_enforcement for explicit task-scoped claims, including update_media_buy and cross-role signed-context enforcement.' - ), - ] = False - propagation_surfaces: Annotated[ - list[PropagationSurface] | None, - Field( - description='Where this seller surfaces dependency-resource impairments (creative suspended/rejected post-approval, audience suspended, catalog item withdrawn, event source insufficient, property depublished) to buyers. Non-exclusive: a seller mirroring impairments on both the buy snapshot AND firing webhooks declares `["snapshot", "webhook"]` (the common case for premium guaranteed sellers). Each value names one surface where buyers can observe an impairment:\n\n- **`snapshot`** — seller propagates resource transitions into `media_buy.health` and `media_buy.impairments[]` on the next `get_media_buys` read. The `impairment.coherence` compliance assertion grades this surface; storyboards that exercise it (`media_buy_seller/dependency_impairment`, `media_buy_seller/dependency_impairment_cardinality`) require `"snapshot"` to be declared, else they grade `not_applicable`.\n- **`webhook`** — seller fires `notification-type: impairment` webhooks (configured via `push_notification_config`). Sellers declaring `"webhook"` MUST satisfy the persistent-channel webhook contract for the impairment event type. A seller declaring `["webhook"]` without `"snapshot"` is webhook-only — buyers reconcile state from the push channel alone, and snapshot-coherence storyboards grade `not_applicable`.\n- **`out_of_band`** — seller propagates via channels outside the AdCP protocol surface entirely (email to trafficker, separate dashboard, partner-specific notification feed). Long-tail and enterprise-bundled platforms commonly use this when impairment workflows are managed in human channels. Sellers declaring only `["out_of_band"]` are not graded by snapshot or webhook compliance — their bar is the offline agreement, not a protocol assertion. If a seller has impairment data in their API under a non-AdCP field name (a mapping gap, not truly out-of-band), they SHOULD document the mapping rather than declare `out_of_band` — the spec\'s gap, not the seller\'s posture, is what `out_of_band` legitimately covers.\n\nDefault: `["snapshot"]` when absent (preserves the existing snapshot-coherence contract for sellers that don\'t declare). Empty array `[]` is invalid (`minItems: 1`) — omit the field to inherit the default rather than declaring no surfaces. Pick the surfaces that honestly describe where buyers will see impairments on this agent. Mixing is normative — `["snapshot", "webhook"]` is the documented common case; `["snapshot", "webhook", "out_of_band"]` is valid for sellers that ship all three surfaces (rare but legal). See lifecycle.mdx § Compliance for the per-surface contract.', - min_length=1, - ), - ] = [PropagationSurface.snapshot] - creative_approval_mode: Annotated[ - CreativeApprovalMode | None, - Field( - description="Tenant-wide applicability signal for media-buy creative approval behavior. This is not a notification or new approval workflow. `auto_approve` means human review does not block serving eligibility after creatives are assigned and automated validation passes. `require_human` means one or more products/accounts may require manual review before creatives become eligible to serve; buyers and compliance runners MUST treat this as a worst-case ceiling across this seller's portfolio unless a future product-level override says otherwise. Compliance runners use this mainly to decide whether auto-approval-dependent storyboards apply. When absent, approval behavior is legacy-unspecified; runners SHOULD NOT treat omission as an affirmative auto-approval claim. `ai_assisted` is intentionally not part of the enum until a behavioral contract is defined." - ), - ] = None - supported_indicator_types: Annotated[ - list[indicator_type.IndicatorType] | None, - Field( - description="Indicator types this seller can expose on get_media_buys media-buy/package/creative-assignment snapshots. Each type's meaning is defined by the negotiated AdCP release; indicator types do not carry independent sub-versions. This is availability, not complete upstream coverage. Poll-only sellers may declare this field without relationship_notifications. If relationship_notifications includes indicators.changed, this field is required so receivers know which durable indicator types can be repaired.", - min_length=1, - ), - ] = None - relationship_notifications: Annotated[ - RelationshipNotifications | None, - Field( - description='Optional durable account-level invalidations for indicator, creative-assignment, and assignment-approval changes. A seller may expose indicators only through polling and omit this block. A seller without an indicator catalog may declare creative.assignment_changed alone. get_media_buys is the complete authoritative repair read. creative.assignment_changed is independent of the optional bounded list_creatives reverse projection, so inline-only sellers can advertise approval and assignment invalidations. Presence means the seller accepts the declared subscriptions through sync_accounts notification_configs. Timestamp-only reevaluation does not fire. Poll-based upstream integrations fire when they detect a change; this declaration does not promise upstream detection latency.' - ), - ] = None - features: media_buy_features.MediaBuyFeatures | None = None - execution: Annotated[ - Execution | None, Field(description='Technical execution capabilities for media buying') - ] = None - audience_evidence: Annotated[ - AudienceEvidence | None, - Field( - description='Support for structured product audience evidence and buyer-authored evidence policy. Presence means the seller can publish Product.audience_evidence, preserve immutable snapshots through package readback, and evaluate the declared policy modes. It does not declare audience-targeting capability.' - ), - ] = None - rights_attestations: Annotated[ - RightsAttestations | None, - Field( - description='Seller evaluation policy for portable rights-grant attestations carried on creative rights constraints. Presence requires adcp.attestations and the rights-grant claim URI in accepted_claim_types. The seller remains verifier-of-record and never treats verification_url or a buyer-authored evaluation as authorization.' - ), - ] = None - audience_targeting: Annotated[ - AudienceTargeting | None, - Field( - description='Audience targeting capabilities. Presence of this object indicates the seller supports audience targeting, including sync_audiences and audience_include/audience_exclude in targeting overlays.' - ), - ] = None - supported_optimization_metrics: Annotated[ - list[SupportedOptimizationMetric] | None, - Field( - description='Optimization metrics this seller can support on at least one of their products. Seller-level rollup of product-level metric_optimization.supported_metrics declarations (core/product.json). Buyers SHOULD filter their requested optimization goals against this list before submitting briefs. Sellers MUST keep this in sync with their product catalog — if no products support a metric, it must not appear here. Omitting this field means the seller declares no specific guarantees about which metrics they support; buyers should fall back to per-product inspection of metric_optimization.supported_metrics.', - min_length=1, - ), - ] = None - vendor_metric_optimization: Annotated[ - VendorMetricOptimization | None, - Field( - description='Seller-level rollup of vendor-metric optimization capabilities supported by at least one product. Product-level vendor_metric_optimization.supported_metrics[] remains authoritative for the specific (vendor, metric_id) pairs and target kinds a buyer may bind on a package; this seller-level object exists so buyers and compliance runners can discover whether vendor_metric goals are in scope before walking the catalog. Sellers MUST keep this in sync with product-level vendor_metric_optimization declarations.' - ), - ] = None - conversion_tracking: Annotated[ - ConversionTracking | None, - Field( - description='Seller-level conversion tracking capabilities. Presence of this object indicates the seller supports sync_event_sources and log_event for conversion event tracking.' - ), - ] = None - frequency_capping: Annotated[ - FrequencyCapping | None, - Field( - description='Seller-wide package frequency-capping infrastructure. Presence means the seller honors targeting_overlay.frequency_cap on packages, with an independent counter per package, and MUST reject caps it cannot enforce rather than silently dropping them. Product.overlay_support is the binding per-product declaration. A cap whose single counter spans every package in a MediaBuy is advertised separately through aggregate_frequency_capping.' - ), - ] = None - aggregate_frequency_capping: Annotated[ - media_buy_frequency_cap_capability.MediaBuyFrequencyCapCapability | None, - Field( - description='Seller-wide support for a maximum-impression cap whose single counter is shared across every package in a MediaBuy. Presence is required before a buyer sends a root frequency_cap. Products additionally declare media_buy_support.frequency_cap and any narrower constraints.' - ), - ] = None - budget_capping: Annotated[ - BudgetCapping | None, - Field( - description='Hard daily budget-cap capabilities. Presence declares only the scopes listed in supported_scopes; sellers MUST reject a daily_budget_cap at an undeclared scope with UNSUPPORTED_FEATURE before mutation and MUST NOT silently drop or soften it. A cap is always a hard ceiling. Sellers that offer a best-effort pacing target must expose that under a separately named feature rather than interpreting daily_budget_cap as soft. Daily caps are orthogonal to pacing.' - ), - ] = None - content_standards: Annotated[ - ContentStandards | None, - Field( - description='Content standards implementation details. Presence of this object indicates the seller supports content_standards configuration including sampling rates and category filtering. Gives buyers pre-buy visibility into local evaluation and artifact delivery capabilities. This is a seller-side media-buy capability; governance agents providing content standards services declare `specialisms: ["content-standards"]` instead.' - ), - ] = None - portfolio: Annotated[ - Portfolio | None, - Field( - description="Information about the seller's media inventory portfolio. Media-buy sellers SHOULD publish primary_channels and primary_countries as their complete brief-routing scope. Buyers use publisher_domains to verify authorization via adagents.json. Omitted routing arrays mean unknown scope and MUST NOT be interpreted as global coverage." - ), - ] = None - - -class Adcp(AdCPBaseModel): - major_versions: Annotated[ - list[MajorVersion], - Field( - deprecated=True, - description='DEPRECATED in favor of `supported_versions` (release-precision strings). Servers MUST continue to emit this field through 3.x for backwards compatibility. Removed in 4.0. Original semantics: AdCP major versions supported by this seller. Major versions indicate breaking changes.', - min_length=1, - ), - ] - supported_versions: Annotated[ - list[SupportedVersion] | None, - Field( - description='Release-precision (VERSION.RELEASE) AdCP versions this seller speaks. Authoritative for buyer-side release pinning — buyers SHOULD declare `adcp_version` (release-precision string) on each request. Sellers downshift to the highest supported release ≤ the buyer\'s pin within the same major; cross-major mismatch returns VERSION_UNSUPPORTED. Pre-release tags (e.g. `"3.1-beta"`) hang off release.', - examples=[['3.0'], ['3.0', '3.1'], ['3.0', '3.1-beta']], - min_length=1, - ), - ] = None - build_version: Annotated[ - str | None, - Field( - description="Optional advisory metadata: full semver build identifier of the seller's deployment — MAJOR.MINOR.PATCH plus optional pre-release and build-metadata segments per semver §9–§10. Patches are not part of the wire contract — semver patch by definition introduces no contract change — but surfacing the build helps buyers triage incidents and bug reports against a specific seller deployment lineage. Buyers MUST NOT use this field for negotiation; use `supported_versions` (release-precision) instead.", - examples=[ - '3.0.1', - '3.1.2', - '3.1.0-beta.3', - '3.1.2+scope3.deploy.4821', - '3.1.0-beta.3+sha.a1b2c3d', - ], - pattern='^\\d+\\.\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?(\\+[a-zA-Z0-9.-]+)?$', - ), - ] = None - idempotency: Annotated[ - Idempotency | Idempotency1, - Field( - description='Idempotency semantics for mutating requests. Sellers MUST declare whether they honor idempotency_key replay protection so buyers can reason about safe retry behavior. Modeled as a discriminated union on the supported boolean so that code generators produce two named types (IdempotencySupported, IdempotencyUnsupported) with the replay_ttl_seconds invariant enforced at the type level — draft-07 if/then would be dropped by most generators (openapi-typescript, zod-to-json-schema, datamodel-code-generator pre-0.25, quicktype). Clients MUST NOT assume a default — a seller without this declaration is non-compliant and should be treated as unsafe for retry-sensitive operations.' - ), - ] - principal: Annotated[ - Principal | None, - Field( - description='Caller-scoped durable connection configuration accepted by this agent. This is the buyer-to-seller configuration half of negotiation, not a second seller capability document: the seller advertises its objective offering here, while each authenticated caller submits its desired webhooks and reusable destinations through sync_principal and reads them back through get_principal. Per-account authority and feed selection remain in account/reporting configuration. Sellers exposing this block MUST list protocol.principal in experimental_features.' - ), - ] = None - capability_changes: Annotated[ - CapabilityChanges | None, - Field( - description='Freshness metadata and optional invalidation webhooks for this `get_adcp_capabilities` document. Buyers and registries MAY cache capabilities for up to `cache_ttl_seconds` when present, SHOULD compare `capabilities_version` across refreshes when present, and SHOULD re-run `get_adcp_capabilities` after receiving a `capabilities.changed` webhook. This block describes the agent-wide capability document, not per-caller authorization or account-scoped settings. A material capability change is any externally advertised contract change that can affect routing, validation, conformance coverage, task availability, auth/account handling, sandbox support, billing support, reporting delivery methods, creative-library support, targeting support, protocol versions, or other buyer-visible feature gates. Non-contract operational changes that do not alter the response body do not require a revision or webhook fire.' - ), - ] = None - governance_enforcement: Annotated[ - GovernanceEnforcement | None, - Field( - description='Cross-role declaration that this agent enforces buyer-provided governance authorization when performing consequential tasks. This is distinct from the top-level `governance` capability block, which describes an agent that provides governance services. An agent can enforce governance without implementing the governance protocol itself. Absence means the agent makes no governance-enforcement conformance claim.' - ), - ] = None - attestations: Annotated[ - attestation_capabilities.AttestationCapabilities | None, - Field( - description='Portable-attestation trust and delivery capabilities for this evaluator. Present only when the agent accepts AttestationReference inputs on one or more domain task surfaces. This block is an allowlist: presenters cannot expand accepted issuers, resolver endpoints, verifier agents, claim types, or proof formats by supplying values in a request.' - ), - ] = None - - -class GetAdcpCapabilitiesResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - adcp: Annotated[Adcp, Field(description='Core AdCP protocol information')] - supported_protocols: Annotated[ - list[SupportedProtocol], - Field( - description='AdCP protocols this agent supports. Stable values both (a) declare which tools the agent implements and (b) commit the agent to pass the baseline compliance storyboard at /compliance/{version}/protocols/{protocol}/ (with snake_case → kebab-case path mapping, e.g. media_buy → /compliance/.../protocols/media-buy/). The `measurement` protocol is experimental and currently covers provider catalog/output declaration (`measurement.core`) and buyer-orchestrator interchange gateways (`measurement.gateway`). Measurement agents exchange delivery and feedback with the gateway rather than receiving direct seller access. Additional provider tasks and a baseline storyboard land only when concrete workflows require them. Compliance testing support is declared separately via the `compliance_testing` capability block (below), not as a protocol claim.', - min_length=1, - ), - ] - account: Annotated[ - Account | None, - Field( - description='Account management capabilities. Describes how accounts are established, what billing models are supported, and whether an account is required before browsing products.' - ), - ] = None - media_buy: Annotated[ - MediaBuy | None, - Field( - description='Media-buy protocol capabilities. Expected when media_buy is in supported_protocols. Sellers declaring media_buy should also include account with supported_billing.' - ), - ] = None - signals: Annotated[ - Signals | None, - Field( - description='Signals protocol capabilities. Only present if signals is in supported_protocols.' - ), - ] = None - governance: Annotated[ - Governance | None, - Field( - description='Governance protocol capabilities. Only present if governance is in supported_protocols. Governance agents provide property and creative data like compliance scores, brand safety ratings, sustainability metrics, and creative quality assessments.' - ), - ] = None - sponsored_intelligence: Annotated[ - SponsoredIntelligence | None, - Field( - description='Sponsored Intelligence protocol capabilities. Only present if sponsored_intelligence is in supported_protocols. SI agents handle conversational brand experiences.' - ), - ] = None - brand: Annotated[ - Brand | None, - Field( - description='Brand protocol capabilities. Only present if brand is in supported_protocols. Brand agents provide identity data (logos, colors, tone, assets) and optionally rights clearance for licensable content (talent, music, stock media).' - ), - ] = None - creative: Annotated[ - Creative | None, - Field( - description='Creative protocol capabilities. Only present if creative is in supported_protocols.' - ), - ] = None - oauth: Annotated[ - Oauth | None, - Field( - description='Introduced in AdCP 3.2. OAuth 2.0 protected-resource support for inbound transport authentication. This is a capability claim, not a requirement that every caller use OAuth: `supported: true` means the agent publishes RFC 9728 protected-resource metadata for its advertised endpoint and RFC 8414 metadata for every referenced authorization server, and opts into the universal `oauth_setup` conformance storyboard. Agents that use only static Bearer, Basic, mTLS, or RFC 9421 authentication omit this block or declare `supported: false`. Operator credential acquisition remains separately described by `account.authorization_endpoint` when applicable.' - ), - ] = None - request_signing: Annotated[ - RequestSigning | None, - Field( - description='RFC 9421 HTTP Signatures support for incoming requests. Signing remains optional through 3.2, but every accepted 3.2 signature on a request with a body MUST cover content-digest. Request signing becomes required for spend-committing operations in 4.0. The full profile is defined in docs/building/by-layer/L1/security.mdx (Signed Requests (Transport Layer)).' - ), - ] = None - webhook_signing: Annotated[ - WebhookSigning | None, - Field( - description='RFC 9421 webhook-signature and delivery-retry support for outbound webhook callbacks (top-level peer of request_signing). Declares which AdCP webhook-signing profile version and algorithms this agent produces on delivery, whether it supports the legacy HMAC-SHA256 fallback for receivers that have not yet adopted RFC 9421, and the maximum retry horizon receivers use to retain immutable delivery evidence. See docs/building/by-layer/L3/webhooks.mdx.' - ), - ] = None - identity: Annotated[ - Identity | None, - Field( - description='Operator identity posture — trust-root pointer (`brand_json_url`) plus key-scoping and compromise-response controls the agent operates. `brand_json_url` is **load-bearing** for signature verification: when the agent declares any signing posture (`request_signing.supported_for`/`required_for` non-empty, `webhook_signing.supported === true`, or any `key_origins` subfield), `brand_json_url` MUST be present (storyboard-enforced in 3.x; schema-required in 4.0). Verifiers use it to bootstrap from the agent URL to the operator\'s brand.json (and from there to signing keys); see [security.mdx §Discovering an agent\'s signing keys](https://adcontextprotocol.org/docs/building/by-layer/L1/security#discovering-an-agents-signing-keys-via-brand_json_url). The remaining fields (`per_principal_key_isolation`, `key_origins`, `compromise_notification`) are advisory and receivers use them to reason about blast radius and revocation latency at onboarding. Empty-object semantics: `identity: {}` means "posture block present but no posture claimed" — schema-valid but advisory-neutral and receivers MUST treat it as equivalent to omitting the block, **except** that an agent declaring a signing posture elsewhere in the response with an empty `identity` MUST be rejected by storyboard runners as missing `brand_json_url`.' - ), - ] = None - measurement_gateway: Annotated[ - MeasurementGateway | None, - Field( - description='Buyer-controlled task gateway between an orchestrator and measurement providers. In the first experimental tier, providers read buyer-approved cross-seller delivery through get_media_buy_delivery and return compact assertions through provide_performance_feedback, without receiving seller credentials. Orchestrators implementing this block MUST include measurement in supported_protocols and measurement.gateway in experimental_features; this gateway role does not claim the media_buy seller protocol.' - ), - ] = None - measurement: Annotated[ - Measurement | None, - Field( - description="Experimental measurement capability block. Presence indicates this agent computes one or more quantitative metrics about ad delivery, exposure, or effect, and is willing to be discovered as a measurement vendor. Agents implementing this block MUST list `measurement.core` in experimental_features. Returns metric definitions and whether the provider produces compact performance feedback, not pricing/coverage (negotiated via `measurement_terms` on `create_media_buy`) or raw/live datasets. Per-buy vendor values remain on delivery reports; optimizer-ready projections use provide_performance_feedback. AgenticAdvertising.org crawls each measurement agent's `metrics[]` on a TTL to populate the federated cross-vendor index." - ), - ] = None - compliance_testing: Annotated[ - ComplianceTesting | None, - Field( - description="Compliance testing capabilities. The presence of this block declares that the agent supports deterministic testing via comply_test_controller for lifecycle state machine validation. Omit the block entirely if the agent does not support compliance testing. Sellers SHOULD list every canonical controller scenario they implement so buyers and runners can distinguish full deterministic coverage from partial coverage without probing each scenario one by one; the runtime source of truth remains comply_test_controller with scenario: 'list_scenarios'." - ), - ] = None - specialisms: Annotated[ - list[specialism.AdcpSpecialism] | None, - Field( - description="Optional — specialized compliance claims this agent supports. Values MUST be kebab-case enum IDs (e.g., 'creative-generative', 'sales-non-guaranteed'). An agent that implements a specialism's tools but omits its ID from this array will receive 'No applicable tracks found' from the compliance runner — tracks for that specialism are not evaluated even if every tool works. Omitting the field means the agent declares no specialism claims (it still passes the universal + domain-baseline storyboards implied by supported_protocols). Each specialism maps to a storyboard bundle at /compliance/{version}/specialisms/{id}/ that the AAO compliance runner executes to verify the claim. Each specialism rolls up to one of the protocols in supported_protocols — the runner rejects a specialism claim whose parent protocol is missing. Only list specialisms your agent actually implements — the AAO Verified badge enumerates which specialisms were demonstrably passed." - ), - ] = None - extensions_supported: Annotated[ - list[ExtensionsSupportedItem] | None, - Field( - description='Extension namespaces this agent supports. Buyers can expect meaningful data in ext.{namespace} fields on responses from this agent. Extension schemas are published in the AdCP extension registry.' - ), - ] = None - experimental_features: Annotated[ - list[experimental_feature_id.ExperimentalFeatureId] | None, - Field( - description='Experimental AdCP surfaces this agent implements. A surface is experimental when its schema carries x-status: experimental and the working group has not yet frozen it. Sellers that implement any experimental surface MUST list its feature id here. Buyers inspect this array before relying on experimental surfaces — a seller that does not list a surface is asserting it does not implement it. Experimental surfaces MAY break between any two 3.x releases with at least 6 weeks notice; the full contract is in docs/reference/experimental-status.' - ), - ] = None - wholesale_feed_versioning: Annotated[ - WholesaleFeedVersioning | None, - Field( - description="Conditional-fetch token capabilities for get_products and get_signals. Independent of wholesale feed webhooks: an agent MAY support cheap version probes via if_wholesale_feed_version without pushing change payloads (and vice versa). When supported is true, the agent returns wholesale_feed_version on every get_products / get_signals response and honors if_wholesale_feed_version on subsequent requests. When absent or supported is false, callers MAY still send if_wholesale_feed_version — pre-3.1 agents that ignore it just return the full payload (correct, just inefficient). Pre-flight declaration here lets buyers fast-path which agents to bother caching versions for. See get_products / get_signals 'Wholesale feed versioning' sections." - ), - ] = None - last_updated: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp of when capabilities were last updated. Buyers can use this for cache invalidation.' - ), - ] = None - errors: Annotated[ - list[error.Error] | None, Field(description='Task-specific errors and warnings') - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - wholesale_feed_webhooks: Annotated[ - WholesaleFeedWebhooks | None, - Field( - description='Per-agent wholesale product-feed and wholesale signals-feed webhook capabilities. Consumers register durable sync_accounts notification subscribers and receive actual product.*, signal.*, or wholesale_feed.bulk_change payloads without polling. Product mirrors bootstrap and repair through list_products(if_feed_version); signal mirrors use get_signals(if_wholesale_feed_version). Deprecated wholesale get_products remains the 3.x product compatibility path. Webhook emission MUST apply the same caller/account authorization and cache-scope predicate as the corresponding read.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/protocol/get_principal_request.py b/src/adcp/types/generated_poc/protocol/get_principal_request.py deleted file mode 100644 index 4868edabe..000000000 --- a/src/adcp/types/generated_poc/protocol/get_principal_request.py +++ /dev/null @@ -1,19 +0,0 @@ -# generated by datamodel-codegen: -# filename: protocol/get_principal_request.json -# timestamp: 2026-09-01T19:37:00+00:00 - -from __future__ import annotations - -from pydantic import ConfigDict - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.version_envelope import AdcpVersionEnvelope - - -class GetPrincipalRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/protocol/get_principal_response.py b/src/adcp/types/generated_poc/protocol/get_principal_response.py deleted file mode 100644 index c312e747f..000000000 --- a/src/adcp/types/generated_poc/protocol/get_principal_response.py +++ /dev/null @@ -1,100 +0,0 @@ -# generated by datamodel-codegen: -# filename: protocol/get_principal_response.json -# timestamp: 2026-10-04T01:57:23+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 -from ..core import principal_state -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import principal_kind as principal_kind_1 - - -class Result(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['unconfigured'] = 'unconfigured' - - -class Result9(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['failed'] = 'failed' - errors: Annotated[list[error.Error], Field(max_length=16, min_length=1)] - - -class Result7(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['recognized'] = 'recognized' - principal_id: Annotated[ - str, - Field( - description='Seller-issued opaque identifier for the existing durable principal record. The read returns the same identifier after credential renewal or rotation when the new credential maps to this principal. It is not a credential and does not grant authority over an advertiser account.', - max_length=255, - min_length=1, - ), - ] - principal_kind: Annotated[ - principal_kind_1.PrincipalKind, - Field( - description='Seller-resolved party kind of the authenticated principal. This is resolved from authenticated transport and authorization state, never request content.' - ), - ] - - -class Result6(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['current'] = 'current' - principal_id: Annotated[ - str, - Field( - description='Seller-issued opaque identifier for this authenticated principal record. It is response-only, not a credential, not caller identity, and not advertiser-account authority.', - max_length=255, - min_length=1, - ), - ] - principal_kind: Annotated[ - principal_kind_1.PrincipalKind, - Field( - description="Seller-resolved party kind of the authenticated principal: a buyer-agent workload, or an operator-side identity such as a person at the operator. Resolved solely from the seller's authorization system, never from request content, so per-party policy such as billing gates can rely on it." - ), - ] - configuration_version: Annotated[ - str, - Field( - description='Opaque version of the persisted configuration. Compare only for equality and pass it as expected_configuration_version on a later guarded sync_principal replacement.', - max_length=255, - min_length=1, - ), - ] - configuration: principal_state.PrincipalState - - -class GetPrincipalResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - result: Result6 | Result7 | Result | Result9 - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -# Stable aliases for anonymous result arms (selected by discriminator). -PrincipalUnconfiguredResult = Result -PrincipalCurrentResult = Result6 -PrincipalRecognizedResult = Result7 -PrincipalReadFailedResult = Result9 diff --git a/src/adcp/types/generated_poc/protocol/get_task_status_request.py b/src/adcp/types/generated_poc/protocol/get_task_status_request.py deleted file mode 100644 index 539cfa004..000000000 --- a/src/adcp/types/generated_poc/protocol/get_task_status_request.py +++ /dev/null @@ -1,41 +0,0 @@ -# generated by datamodel-codegen: -# filename: protocol/get_task_status_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import ConfigDict, Field, StrictBool - -from ..core import account_ref -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.version_envelope import AdcpVersionEnvelope - - -class GetTaskStatusRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - task_id: Annotated[str, Field(description='Unique identifier of the task to retrieve')] - account: Annotated[ - account_ref.AccountReference | None, - Field( - description='Account scope for the task lookup. Sellers MUST return REFERENCE_NOT_FOUND for a task_id that exists only under a different account or principal. When omitted, the seller MAY use the credential-bound singleton account, but multi-account credentials SHOULD require an explicit account.' - ), - ] = None - include_history: Annotated[ - StrictBool | None, - Field( - description='Include full conversation history for this task (may increase response size)' - ), - ] = False - include_result: Annotated[ - StrictBool | None, - Field( - description="Include the task's canonical terminal result payload when one exists. Defaults to false for lightweight status-only polls. When true, sellers MUST include result for completed, failed, or rejected terminal tasks when that task produced a terminal artifact; canceled tasks may have no result. The legacy singular error field remains a convenience for failed tasks but does not replace the canonical terminal result." - ), - ] = False - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/protocol/get_task_status_response.py b/src/adcp/types/generated_poc/protocol/get_task_status_response.py deleted file mode 100644 index 1b56b53c9..000000000 --- a/src/adcp/types/generated_poc/protocol/get_task_status_response.py +++ /dev/null @@ -1,122 +0,0 @@ -# generated by datamodel-codegen: -# filename: protocol/get_task_status_response.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field, StrictBool, StrictFloat - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import adcp_protocol, task_status -from ..enums import task_type as task_type_1 - - -class Progress(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - percentage: Annotated[ - StrictFloat | None, Field(description='Completion percentage (0-100)', ge=0.0, le=100.0) - ] = None - current_step: Annotated[ - str | None, Field(description='Current step or phase of the operation') - ] = None - total_steps: Annotated[ - SchemaInt | None, Field(description='Total number of steps in the operation', ge=1) - ] = None - step_number: Annotated[SchemaInt | None, Field(description='Current step number', ge=1)] = None - - -class Details(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - protocol: Annotated[ - adcp_protocol.AdcpProtocol | None, Field(description='AdCP protocol where error occurred') - ] = None - operation: Annotated[str | None, Field(description='Specific operation that failed')] = None - specific_context: Annotated[ - dict[str, Any] | None, Field(description='Domain-specific error context') - ] = None - - -class Error(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[str, Field(description='Error code for programmatic handling')] - message: Annotated[str, Field(description='Detailed error message')] - details: Annotated[Details | None, Field(description='Additional error context')] = None - - -class Type(StrEnum): - request = 'request' - response = 'response' - - -class HistoryItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - timestamp: Annotated[AwareDatetime, Field(description='When this exchange occurred (ISO 8601)')] - type: Annotated[ - Type, Field(description='Whether this was a request from client or response from server') - ] - data: Annotated[dict[str, Any], Field(description='The full request or response payload')] - - -class GetTaskStatusResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - task_id: Annotated[str, Field(description='Unique identifier for this task')] - task_type: Annotated[task_type_1.TaskType, Field(description='Type of AdCP operation')] - protocol: Annotated[ - adcp_protocol.AdcpProtocol, Field(description='AdCP protocol this task belongs to') - ] - status: Annotated[task_status.TaskStatus, Field(description='Current task status')] - created_at: Annotated[ - AwareDatetime, Field(description='When the task was initially created (ISO 8601)') - ] - updated_at: Annotated[ - AwareDatetime, Field(description='When the task was last updated (ISO 8601)') - ] - completed_at: Annotated[ - AwareDatetime | None, - Field( - description='When the task completed (ISO 8601, only for completed/failed/canceled tasks)' - ), - ] = None - has_webhook: Annotated[ - StrictBool | None, Field(description='Whether this task has webhook configuration') - ] = None - progress: Annotated[ - Progress | None, Field(description='Progress information for long-running tasks') - ] = None - error: Annotated[ - Error | None, - Field( - description='Convenience summary for failed tasks. When include_result was true and the canonical terminal result is also present, this error MUST agree with the canonical fatal error in result. A legacy poll carrying only this singular summary proves failure status but not equivalence to a richer terminal webhook artifact.' - ), - ] = None - history: Annotated[ - list[HistoryItem] | None, - Field( - description='Complete conversation history for this task (only included if include_history was true in request)' - ), - ] = None - result: Annotated[ - dict[str, Any] | None, - Field( - description='Canonical task-specific terminal payload. Present when include_result was true and a completed, failed, or rejected task produced a terminal artifact; canceled tasks may omit it. For failed tasks, the singular error field is a convenience summary and MUST agree with the canonical fatal error represented here. Consumers and sellers MUST resolve and validate the exact schema through manifest.task_result_resolution: use terminal_schema_overrides[task_type] when present, otherwise tools[task_type].response_schema. The polling envelope keeps this field generic so get_task_status does not embed every task response schema.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/protocol/list_tasks_request.py b/src/adcp/types/generated_poc/protocol/list_tasks_request.py deleted file mode 100644 index dd90d2504..000000000 --- a/src/adcp/types/generated_poc/protocol/list_tasks_request.py +++ /dev/null @@ -1,117 +0,0 @@ -# generated by datamodel-codegen: -# filename: protocol/list_tasks_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field, StrictBool - -from ..core import account_ref -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core import pagination_request -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import adcp_protocol, sort_direction, task_status -from ..enums import task_type as task_type_1 - - -class Field1(StrEnum): - created_at = 'created_at' - updated_at = 'updated_at' - status = 'status' - task_type = 'task_type' - protocol = 'protocol' - - -class Filters(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - protocol: Annotated[ - adcp_protocol.AdcpProtocol | None, Field(description='Filter by single AdCP protocol') - ] = None - protocols: Annotated[ - list[adcp_protocol.AdcpProtocol] | None, - Field(description='Filter by multiple AdCP protocols', min_length=1), - ] = None - status: Annotated[ - task_status.TaskStatus | None, Field(description='Filter by single task status') - ] = None - statuses: Annotated[ - list[task_status.TaskStatus] | None, - Field(description='Filter by multiple task statuses', min_length=1), - ] = None - task_type: Annotated[ - task_type_1.TaskType | None, Field(description='Filter by single task type') - ] = None - task_types: Annotated[ - list[task_type_1.TaskType] | None, - Field(description='Filter by multiple task types', min_length=1), - ] = None - created_after: Annotated[ - AwareDatetime | None, Field(description='Filter tasks created after this date (ISO 8601)') - ] = None - created_before: Annotated[ - AwareDatetime | None, Field(description='Filter tasks created before this date (ISO 8601)') - ] = None - updated_after: Annotated[ - AwareDatetime | None, - Field(description='Filter tasks last updated after this date (ISO 8601)'), - ] = None - updated_before: Annotated[ - AwareDatetime | None, - Field(description='Filter tasks last updated before this date (ISO 8601)'), - ] = None - task_ids: Annotated[ - list[str] | None, - Field(description='Filter by specific task IDs', max_length=100, min_length=1), - ] = None - context_contains: Annotated[ - str | None, - Field( - description='Filter tasks where context contains this text (searches media_buy_id, signal_id, etc.)' - ), - ] = None - has_webhook: Annotated[ - StrictBool | None, - Field(description='Filter tasks that have webhook configuration when true'), - ] = None - - -class Sort(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - field: Annotated[Field1 | None, Field(description='Field to sort by')] = Field1.created_at - direction: Annotated[ - sort_direction.SortDirection | None, Field(description='Sort direction') - ] = sort_direction.SortDirection.desc - - -class ListTasksRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - account: Annotated[ - account_ref.AccountReference | None, - Field( - description="Account scope for task reconciliation. Sellers MUST only return tasks created for the caller's authenticated account + principal pair. When omitted, the seller MAY use the credential-bound singleton account, but multi-account credentials SHOULD require an explicit account." - ), - ] = None - filters: Annotated[Filters | None, Field(description='Filter criteria for querying tasks')] = ( - None - ) - sort: Annotated[Sort | None, Field(description='Sorting parameters')] = None - pagination: pagination_request.PaginationRequest | None = None - include_history: Annotated[ - StrictBool | None, - Field( - description='Include full conversation history for each task (may significantly increase response size)' - ), - ] = False - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/protocol/list_tasks_response.py b/src/adcp/types/generated_poc/protocol/list_tasks_response.py deleted file mode 100644 index ef7518df8..000000000 --- a/src/adcp/types/generated_poc/protocol/list_tasks_response.py +++ /dev/null @@ -1,117 +0,0 @@ -# generated by datamodel-codegen: -# filename: protocol/list_tasks_response.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AwareDatetime, ConfigDict, Field, StrictBool - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core import pagination_response -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import task_status -from ..enums import task_type as task_type_1 - - -class DomainBreakdown(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_buy: Annotated[ - SchemaInt | None, - Field(alias='media-buy', description='Number of media-buy tasks in results', ge=0), - ] = None - signals: Annotated[ - SchemaInt | None, Field(description='Number of signals tasks in results', ge=0) - ] = None - creative: Annotated[ - SchemaInt | None, Field(description='Number of creative tasks in results', ge=0) - ] = None - - -class Direction(StrEnum): - asc = 'asc' - desc = 'desc' - - -class SortApplied(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - field: str - direction: Direction - - -class QuerySummary(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - total_matching: Annotated[ - SchemaInt | None, - Field(description='Total number of tasks matching filters (across all pages)', ge=0), - ] = None - returned: Annotated[ - SchemaInt | None, Field(description='Number of tasks returned in this response', ge=0) - ] = None - domain_breakdown: Annotated[ - DomainBreakdown | None, Field(description='Count of tasks by domain') - ] = None - status_breakdown: Annotated[ - dict[str, SchemaInt] | None, Field(description='Count of tasks by status') - ] = None - filters_applied: Annotated[ - list[str] | None, Field(description='List of filters that were applied to the query') - ] = None - sort_applied: Annotated[ - SortApplied | None, Field(description='Sort order that was applied') - ] = None - - -class Domain(StrEnum): - media_buy = 'media-buy' - signals = 'signals' - creative = 'creative' - - -class Task(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - task_id: Annotated[str, Field(description='Unique identifier for this task')] - task_type: Annotated[task_type_1.TaskType, Field(description='Type of AdCP operation')] - domain: Annotated[Domain, Field(description='AdCP domain this task belongs to')] - status: Annotated[task_status.TaskStatus, Field(description='Current task status')] - created_at: Annotated[ - AwareDatetime, Field(description='When the task was initially created (ISO 8601)') - ] - updated_at: Annotated[ - AwareDatetime, Field(description='When the task was last updated (ISO 8601)') - ] - completed_at: Annotated[ - AwareDatetime | None, - Field( - description='When the task completed (ISO 8601, only for completed/failed/canceled tasks)' - ), - ] = None - has_webhook: Annotated[ - StrictBool | None, Field(description='Whether this task has webhook configuration') - ] = None - - -class ListTasksResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - query_summary: Annotated[ - QuerySummary, Field(description='Summary of the query that was executed') - ] - tasks: Annotated[list[Task], Field(description='Array of tasks matching the query criteria')] - pagination: pagination_response.PaginationResponse - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/protocol/sync_agent_notification_configs_request.py b/src/adcp/types/generated_poc/protocol/sync_agent_notification_configs_request.py deleted file mode 100644 index f31eb6f84..000000000 --- a/src/adcp/types/generated_poc/protocol/sync_agent_notification_configs_request.py +++ /dev/null @@ -1,44 +0,0 @@ -# generated by datamodel-codegen: -# filename: protocol/sync_agent_notification_configs_request.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import ConfigDict, Field, StrictBool - -from ..core import agent_notification_config -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.version_envelope import AdcpVersionEnvelope - - -class SyncAgentNotificationConfigsRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - idempotency_key: Annotated[ - str, - Field( - description="Client-generated unique key for at-most-once execution. The task mutates the authenticated caller's persistent agent-level subscriber set and may trigger endpoint proof-of-control challenges, so retries MUST reuse the same key and same payload. Use a fresh UUID v4 for each logical replacement.", - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - notification_configs: Annotated[ - list[agent_notification_config.AgentNotificationConfig], - Field( - description="Complete desired set of agent-level notification subscribers for the authenticated caller or registry identity. Omit is invalid; send an empty array to remove every subscriber owned by this caller. Each entry registers a URL, the agent-level event types the subscriber wants, and optional legacy auth. Duplicate `subscriber_id` values are rejected within this caller-scoped array. If any entry fails validation or activation proof, the seller rejects the replacement and leaves this caller's previous set unchanged.", - max_length=16, - ), - ] - dry_run: Annotated[ - StrictBool | None, - Field( - description='When true, validate the replacement and report what would be persisted without applying it or sending endpoint proof-of-control challenges.' - ), - ] = False - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/protocol/sync_agent_notification_configs_response.py b/src/adcp/types/generated_poc/protocol/sync_agent_notification_configs_response.py deleted file mode 100644 index 147b321a5..000000000 --- a/src/adcp/types/generated_poc/protocol/sync_agent_notification_configs_response.py +++ /dev/null @@ -1,55 +0,0 @@ -# generated by datamodel-codegen: -# filename: protocol/sync_agent_notification_configs_response.json -# timestamp: 2026-10-03T15:09:44+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from pydantic import ConfigDict, Field, StrictBool - -from ..core import agent_notification_config -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope - - -class Action(StrEnum): - updated = 'updated' - unchanged = 'unchanged' - cleared = 'cleared' - failed = 'failed' - - -class SyncAgentNotificationConfigsResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - dry_run: Annotated[ - StrictBool | None, - Field(description='Whether this was a dry run and no persisted subscriber set changed.'), - ] = None - action: Annotated[ - Action, - Field( - description="Outcome for the caller-scoped replacement. `updated`: a new subscriber set was persisted for this caller. `unchanged`: the submitted set matched this caller's current set. `cleared`: the submitted empty array removed all subscribers owned by this caller. `failed`: validation, authorization, or endpoint proof failed; the prior caller-scoped set remains unchanged and `errors[]` explains why." - ), - ] - notification_configs: Annotated[ - list[agent_notification_config.AgentNotificationConfig] | None, - Field( - description='Current persisted agent-level notification subscribers owned by the authenticated caller after the request. Entries are keyed by `subscriber_id`; `authentication.credentials` is omitted on every entry because credentials are write-only. Present on successful actions and MAY be present on `failed` responses to show the unchanged prior caller-scoped set.', - max_length=16, - ), - ] = None - errors: Annotated[ - list[error.Error] | None, - Field( - description='Operation-level errors and warnings. Present when action is `failed`, and MAY include non-fatal warnings on successful responses.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/protocol/sync_principal_request.py b/src/adcp/types/generated_poc/protocol/sync_principal_request.py deleted file mode 100644 index 9ddddb085..000000000 --- a/src/adcp/types/generated_poc/protocol/sync_principal_request.py +++ /dev/null @@ -1,86 +0,0 @@ -# generated by datamodel-codegen: -# filename: protocol/sync_principal_request.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictBool - -from ..core import agent_notification_config, agent_reporting_destination -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core import principal_declarations -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import principal_kind - - -class Configuration(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - notification_configs: Annotated[ - list[agent_notification_config.AgentNotificationConfig] | None, - Field( - description='Complete desired agent-level subscriber set. The same caller-scoping, proof-of-control, secret handling, and replacement rules as sync_agent_notification_configs apply.', - max_length=16, - ), - ] = None - reporting_destinations: Annotated[ - list[agent_reporting_destination.AgentReportingDestination] | None, - Field( - description='Complete desired reusable reporting destination set. Omitting a previously present destination_id revokes it, and [] revokes every destination: the seller halts new deliveries to all of its generations within the advertised suspension_interval_seconds and retains it as a retired generation for reporting history. Revocation does not delete caller-owned data already delivered. destination_id values MUST be unique.', - max_length=64, - ), - ] = None - declarations: Annotated[ - principal_declarations.AgentDeclarations | None, - Field( - description='Complete declared consumption facts for this principal record. A present object replaces the declared set wholesale; {} clears it; omission leaves it unchanged. The seller computes and returns the accepted intersection in state.' - ), - ] = None - - -class SyncPrincipalRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - idempotency_key: Annotated[ - str, - Field( - description='Client-generated key for at-most-once execution, at least 16 characters; a fresh UUID v4 per logical operation is recommended. Retries MUST reuse the same key with the same body.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - expected_configuration_version: Annotated[ - str | None, - Field( - description='Optional optimistic-concurrency fence returned by a previous successful sync. When present and stale, the seller rejects the whole request without mutation. Compare only for equality.', - max_length=255, - min_length=1, - ), - ] = None - expected_principal_kind: Annotated[ - principal_kind.PrincipalKind | None, - Field( - description='Optional assertion fence, not identity input: the caller states which party kind it believes it is authenticating as. When present and different from the seller-resolved principal_kind, the seller rejects the whole request with CONFLICT before mutation. The field never influences resolution.' - ), - ] = None - configuration: Annotated[ - Configuration, - Field( - description='Sections to replace atomically. At least one section is required. A present array is complete desired state for that section; [] clears it; omission leaves it unchanged.' - ), - ] - dry_run: Annotated[ - StrictBool | None, - Field( - description='Validate the proposed replacements and report the would-be action without persisting them, issuing durable identifiers or grants, or sending endpoint proof challenges.' - ), - ] = False - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/protocol/sync_principal_response.py b/src/adcp/types/generated_poc/protocol/sync_principal_response.py deleted file mode 100644 index afdae9250..000000000 --- a/src/adcp/types/generated_poc/protocol/sync_principal_response.py +++ /dev/null @@ -1,102 +0,0 @@ -# generated by datamodel-codegen: -# filename: protocol/sync_principal_response.json -# timestamp: 2026-10-04T01:57:23+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 -from ..core import principal_state -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import principal_kind as principal_kind_1 - - -class Action(StrEnum): - updated = 'updated' - unchanged = 'unchanged' - cleared = 'cleared' - - -class Action33(StrEnum): - would_update = 'would_update' - would_be_unchanged = 'would_be_unchanged' - would_clear = 'would_clear' - - -class Result(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['validated'] = 'validated' - action: Action33 - dry_run: Literal[True] - warnings: Annotated[list[error.Error] | None, Field(max_length=16)] = None - - -class Result19(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['failed'] = 'failed' - errors: Annotated[list[error.Error], Field(max_length=16, min_length=1)] - - -class Result17(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Literal['applied'] = 'applied' - action: Annotated[ - Action, - Field( - description="Persisted outcome for the submitted sections, computed solely against the caller's own prior state. cleared applies only when every submitted section was []; any other change is updated; unchanged means no submitted section differed." - ), - ] - dry_run: Literal[False] - principal_id: Annotated[ - str, - Field( - description='Seller-issued opaque identifier for this authenticated principal record. It is response-only, not a credential, not caller identity, and not advertiser-account authority.', - max_length=255, - min_length=1, - ), - ] - principal_kind: Annotated[ - principal_kind_1.PrincipalKind, - Field( - description="Seller-resolved party kind of the authenticated principal: a buyer-agent workload, or an operator-side identity such as a person at the operator. Resolved solely from the seller's authorization system, never from request content, so per-party policy such as billing gates can rely on it." - ), - ] - configuration_version: Annotated[ - str, - Field( - description='Opaque version of the persisted configuration. Compare only for equality and return it as expected_configuration_version on a later guarded replacement.', - max_length=255, - min_length=1, - ), - ] - configuration: principal_state.PrincipalState - warnings: Annotated[list[error.Error] | None, Field(max_length=16)] = None - - -class SyncPrincipalResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - result: Result17 | Result | Result19 - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -# Stable aliases for anonymous result arms (selected by discriminator). -PrincipalValidatedResult = Result -PrincipalAppliedResult = Result17 -PrincipalSyncFailedResult = Result19 diff --git a/src/adcp/types/generated_poc/registries/__init__.py b/src/adcp/types/generated_poc/registries/__init__.py deleted file mode 100644 index 8930ceaf3..000000000 --- a/src/adcp/types/generated_poc/registries/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# generated by datamodel-codegen: -# filename: .schema_temp -# timestamp: 2026-05-22T13:15:12+00:00 diff --git a/src/adcp/types/generated_poc/registries/v1_canonical_mapping.py b/src/adcp/types/generated_poc/registries/v1_canonical_mapping.py deleted file mode 100644 index 0b1c47611..000000000 --- a/src/adcp/types/generated_poc/registries/v1_canonical_mapping.py +++ /dev/null @@ -1,131 +0,0 @@ -# generated by datamodel-codegen: -# filename: registries/v1_canonical_mapping.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from datetime import date -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictBool - -from ..core import canonical_format_kind - - -class V1Pattern(AdCPBaseModel): - format_id_glob: Annotated[ - str, - Field( - description="Glob pattern matched against v1 format_id.id. Examples: 'iab_mrec_300x250', 'iab_leaderboard_*', 'meta_*_reels'." - ), - ] - - -class Dimensions(AdCPBaseModel): - width: SchemaInt | None = None - height: SchemaInt | None = None - - -class Structural(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_types: Annotated[ - list[str] | None, - Field( - description="Set of asset_type values that must appear in the format's slots (in any order, any count)." - ), - ] = None - vast_versions: Annotated[ - list[str] | None, - Field(description="VAST version constraints. Strings like '>=4.0', '4.x', '4.2'."), - ] = None - daast_versions: list[str] | None = None - dimensions: Dimensions | None = None - - -class V1Pattern1(AdCPBaseModel): - structural: Annotated[ - Structural, - Field( - description="Structural match against the format's slot shape, asset types, and version constraints." - ), - ] - - -class Transform(StrEnum): - identity = 'identity' - singleton_array = 'singleton_array' - - -class ParameterMapping(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - source_field: Annotated[ - str, Field(description='Field read from the matched v1 format_id object.', min_length=1) - ] - target_parameter: Annotated[ - str, Field(description='Field written into the projected v2 params object.', min_length=1) - ] - transform: Annotated[ - Transform | None, - Field( - description='Value transform applied during forwarding. identity copies the value; singleton_array wraps it in a one-element array.' - ), - ] = Transform.identity - - -class V2(AdCPBaseModel): - canonical: Annotated[ - canonical_format_kind.CanonicalFormatKind, - Field(description='v2 canonical format the v1 pattern projects to.'), - ] - parameters: Annotated[ - dict[str, Any] | None, - Field( - description='Optional parameters that narrow the canonical (e.g., width/height, vast_version). When present, become the params on the projected v2 ProductFormatDeclaration. The shape MUST be valid params for the named canonical.' - ), - ] = None - parameter_mappings: Annotated[ - list[ParameterMapping] | None, - Field( - description='Machine-readable forwarding rules for parameters carried by a structured v1 format_id. SDKs copy each present source_field to the target_parameter after applying transform; absent source fields leave the target parameter omitted. Static v2.parameters are applied before these forwarded values.', - min_length=1, - ), - ] = None - - -class Mapping(AdCPBaseModel): - v1_pattern: Annotated[ - V1Pattern | V1Pattern1, - Field(description='Match pattern. Carries either format_id_glob OR structural, not both.'), - ] - v2: V2 - deprecated: Annotated[ - StrictBool | None, - Field( - description='When true, this mapping is retained for backward-compatibility but should not be used for new mappings. SDKs SHOULD emit lint warnings when matching a deprecated entry.' - ), - ] = False - notes: Annotated[ - str | None, - Field(description='Optional human-readable explanation, examples, or rationale.'), - ] = None - - -class V1V2CanonicalFormatMappingRegistry(AdCPBaseModel): - version: Annotated[ - str, Field(description='Semver of this registry. Bumped on every published change.') - ] - last_updated: Annotated[ - date | None, Field(description='ISO date of the last published change.') - ] = None - mappings: Annotated[ - list[Mapping], - Field( - description='Ordered list of v1 → v2 mappings. SDKs apply mappings in order and use the first match.' - ), - ] diff --git a/src/adcp/types/generated_poc/signals/__init__.py b/src/adcp/types/generated_poc/signals/__init__.py deleted file mode 100644 index 5a3cca36b..000000000 --- a/src/adcp/types/generated_poc/signals/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# generated by datamodel-codegen: -# filename: .schema_temp -# timestamp: 2025-11-22T15:23:24+00:00 diff --git a/src/adcp/types/generated_poc/signals/activate_signal_request.py b/src/adcp/types/generated_poc/signals/activate_signal_request.py deleted file mode 100644 index 837279df0..000000000 --- a/src/adcp/types/generated_poc/signals/activate_signal_request.py +++ /dev/null @@ -1,78 +0,0 @@ -# generated by datamodel-codegen: -# filename: signals/activate_signal_request.json -# timestamp: 2026-10-02T23:10:32+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from pydantic import ConfigDict, Field - -from ..core import account_ref -from ..core import context as context_1 -from ..core import destination -from ..core import ext as ext_1 -from ..core.version_envelope import AdcpVersionEnvelope - - -class Action(StrEnum): - activate = 'activate' - deactivate = 'deactivate' - - -class ActivateSignalRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - action: Annotated[ - Action | None, - Field( - description="Whether to activate or deactivate the signal. Deactivating removes the segment from downstream platforms, required when campaigns end to comply with data governance policies (GDPR, CCPA). Defaults to 'activate' when omitted." - ), - ] = Action.activate - signal_agent_segment_id: Annotated[ - str, - Field( - description='Opaque activation handle returned in the signal_agent_segment_id field of each get_signals response entry. Pass this string verbatim — do not pass the signal_id object.' - ), - ] - destinations: Annotated[ - list[destination.Destination], - Field( - description='Target destination(s) for activation. If the authenticated caller matches one of these destinations, activation keys will be included in the response.', - min_length=1, - ), - ] - pricing_option_id: Annotated[ - str | None, - Field( - description="The pricing option selected from the signal's pricing_options in the get_signals response. Required when the signal has pricing options. Records the buyer's pricing commitment at activation time; pass this same value in report_usage for billing verification." - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description='Opaque authorization context returned by an approved check_governance decision for this signal activation. Required when the account has a registered governance agent; signal agents MUST reject governed activations that omit a valid context. Conditions and denied decisions never produce this value.', - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - account: Annotated[ - account_ref.AccountReference | None, - Field( - description='Account for this activation. Associates with a commercial relationship established via sync_accounts.' - ), - ] = None - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for this request. Prevents duplicate activations on retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/signals/activate_signal_response.py b/src/adcp/types/generated_poc/signals/activate_signal_response.py deleted file mode 100644 index 4fc188c45..000000000 --- a/src/adcp/types/generated_poc/signals/activate_signal_response.py +++ /dev/null @@ -1,41 +0,0 @@ -# generated by datamodel-codegen: -# filename: signals/activate_signal_response.json -# timestamp: preserved-by-post-generate-fixes - -from __future__ import annotations - -from typing import Annotated, TypeAlias - -from pydantic import ConfigDict, Field - -from ..core.version_envelope import AdcpVersionEnvelope -from ..core import context as context_1 -from ..core import deployment as deployment_1 -from ..core import error as error_1 -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope - - -class ActivateSignalResponse1(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - deployments: list[deployment_1.Deployment] - sandbox: bool | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -class ActivateSignalResponse2(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict(extra='allow') - errors: Annotated[list[error_1.Error], Field(min_length=1)] - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - -ActivateSignalResponse: TypeAlias = ActivateSignalResponse1 | ActivateSignalResponse2 - - -__all__ = [ - 'ActivateSignalResponse', - 'ActivateSignalResponse1', - 'ActivateSignalResponse2', -] diff --git a/src/adcp/types/generated_poc/signals/get_signals_async_response_submitted.py b/src/adcp/types/generated_poc/signals/get_signals_async_response_submitted.py deleted file mode 100644 index 22488e68a..000000000 --- a/src/adcp/types/generated_poc/signals/get_signals_async_response_submitted.py +++ /dev/null @@ -1,51 +0,0 @@ -# generated by datamodel-codegen: -# filename: signals/get_signals_async_response_submitted.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 - - -class GetSignalsSubmitted(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Literal['submitted'], - Field( - description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, whose signals array is issued in-line. See task-status.json for the full task-status enum.' - ), - ] = 'submitted' - task_id: Annotated[ - str, - Field( - description='Task handle the caller uses with get_task_status (or the legacy AdCP tasks/get alias), and that the agent references on push-notification callbacks. The signals array is issued on the completion artifact, not here. This AdCP application-layer handle remains the snake_case task_id in every transport payload and is distinct from any transport-native A2A Task id.' - ), - ] - message: Annotated[ - str | None, - Field( - description="Optional human-readable explanation of why the task is submitted — e.g., 'Provider discovery queued; typical turnaround 10-30 minutes.' Plain text only. Callers MUST treat this as untrusted agent input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile agent may inject prompt-injection payloads aimed at the caller's agent.", - max_length=2000, - ), - ] = None - estimated_completion: Annotated[ - AwareDatetime | None, - Field(description='Estimated completion time for the signal discovery task.'), - ] = None - errors: Annotated[ - list[error.Error] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories or partial provider unavailability). Terminal failures belong in the error branch, not here.' - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/signals/get_signals_async_response_working.py b/src/adcp/types/generated_poc/signals/get_signals_async_response_working.py deleted file mode 100644 index 54a4a68da..000000000 --- a/src/adcp/types/generated_poc/signals/get_signals_async_response_working.py +++ /dev/null @@ -1,40 +0,0 @@ -# generated by datamodel-codegen: -# filename: signals/get_signals_async_response_working.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictFloat - -from ..core import context as context_1 -from ..core import ext as ext_1 - - -class GetSignalsWorking(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - percentage: Annotated[ - StrictFloat | None, - Field( - description='Progress percentage of the signal discovery operation.', ge=0.0, le=100.0 - ), - ] = None - current_step: Annotated[ - str | None, - Field( - description='Current step in the signal discovery process, such as `querying_providers`, `ranking_signals`, or `checking_deployments`.' - ), - ] = None - total_steps: Annotated[ - SchemaInt | None, - Field(description='Total number of steps in the signal discovery process.'), - ] = None - step_number: Annotated[ - SchemaInt | None, Field(description='Current step number (1-indexed).') - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/signals/get_signals_request.py b/src/adcp/types/generated_poc/signals/get_signals_request.py deleted file mode 100644 index 144f39533..000000000 --- a/src/adcp/types/generated_poc/signals/get_signals_request.py +++ /dev/null @@ -1,164 +0,0 @@ -# generated by datamodel-codegen: -# filename: signals/get_signals_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._scalar import ScalarStr - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import SchemaInt -from pydantic import ConfigDict, Field - -from ..core import account_ref -from ..core import context as context_1 -from ..core import destination -from ..core import ext as ext_1 -from ..core import pagination_request -from ..core import push_notification_config as push_notification_config_1 -from ..core import signal_filters -from ..core import signal_id as signal_id_1 -from ..core import signal_ref as signal_ref_1 -from ..core.version_envelope import AdcpVersionEnvelope - - -class DiscoveryMode(StrEnum): - brief = 'brief' - wholesale = 'wholesale' - - -class Country(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{2}$'} - - -class Field1(StrEnum): - signal_ref = 'signal_ref' - signal_id = 'signal_id' - signal_agent_segment_id = 'signal_agent_segment_id' - name = 'name' - description = 'description' - value_type = 'value_type' - categories = 'categories' - range = 'range' - demographic_predicate = 'demographic_predicate' - signal_type = 'signal_type' - data_provider = 'data_provider' - coverage_percentage = 'coverage_percentage' - deployments = 'deployments' - pricing_options = 'pricing_options' - taxonomy = 'taxonomy' - data_sources = 'data_sources' - methodology = 'methodology' - segmentation_criteria = 'segmentation_criteria' - criteria_url = 'criteria_url' - refresh_cadence = 'refresh_cadence' - lookback_window = 'lookback_window' - onboarder = 'onboarder' - modeling = 'modeling' - audience_expansion = 'audience_expansion' - device_expansion = 'device_expansion' - countries = 'countries' - consent_basis = 'consent_basis' - restricted_attributes = 'restricted_attributes' - policy_categories = 'policy_categories' - art9_basis = 'art9_basis' - data_subject_rights = 'data_subject_rights' - last_updated = 'last_updated' - - -class GetSignalsRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - discovery_mode: Annotated[ - DiscoveryMode | None, - Field( - description="Declares caller intent for this request. 'brief' (default): semantic discovery — signal_spec, signal_refs, or legacy signal_ids is required and the agent performs inference/RAG. 'wholesale': raw wholesale signals feed enumeration — signal_spec, signal_refs, and signal_ids MUST NOT be provided and the agent returns its full priced signals feed, paginated, scoped by filters/account/destinations/countries when present. Sellers receiving requests from pre-v3.1 clients without discovery_mode MUST default to 'brief'. Timing semantics: 'wholesale' is a wholesale signals feed read — agents SHOULD respond synchronously and MUST NOT route a 'wholesale' request through the async/Submitted arm; partial completion is signalled via the response's incomplete[] field, not via a task-handoff envelope. Agents that do not implement wholesale enumeration MAY return INVALID_REQUEST for wholesale calls; callers SHOULD probe via get_adcp_capabilities (signals.discovery_modes) first." - ), - ] = DiscoveryMode.brief - account: Annotated[ - account_ref.AccountReference | None, - Field( - description="Account for this request. When provided, the signals agent returns per-account pricing options if configured. In 'wholesale' mode, this is the rate-card scope: when omitted in wholesale mode, agents return their default rate-card pricing or omit pricing_options entirely." - ), - ] = None - signal_spec: Annotated[ - str | None, - Field( - description="Natural language description of the desired signals. When used alone, enables semantic discovery. When combined with signal_refs, provides context for the agent but signal_ref matches are returned first. MUST NOT be provided when discovery_mode is 'wholesale'." - ), - ] = None - signal_refs: Annotated[ - list[signal_ref_1.SignalRef] | None, - Field( - description="Specific signals to look up by reference. Returns exact matches for the requested SignalRef values. When combined with signal_spec, these signals anchor the starting set and signal_spec guides adjustments. MUST NOT be provided when discovery_mode is 'wholesale'.", - min_length=1, - ), - ] = None - signal_ids: Annotated[ - list[signal_id_1.SignalId] | None, - Field( - deprecated=True, - description="DEPRECATED. Use signal_refs instead. Legacy exact lookup field using SignalId objects. MUST NOT be provided when discovery_mode is 'wholesale'.", - min_length=1, - ), - ] = None - destinations: Annotated[ - list[destination.Destination] | None, - Field( - description='Filter signals to those activatable on specific agents/platforms. When omitted, returns all signals available on the current agent. If the authenticated caller matches one of these destinations, activation keys will be included in the response.', - min_length=1, - ), - ] = None - countries: Annotated[ - list[Country] | None, - Field( - description='Countries where signals will be used (ISO 3166-1 alpha-2 codes). When omitted, no geographic filter is applied.', - min_length=1, - ), - ] = None - filters: signal_filters.SignalFilters | None = None - fields: Annotated[ - list[Field1] | None, - Field( - description="Specific signal fields to include in the response, aligned with get_products.fields. Required identity and activation fields such as signal_ref or signal_id, signal_agent_segment_id, name, description, signal_type, coverage_percentage, and deployments are always included when required by the response schema. Use for progressive disclosure of rich signal-definition metadata: request fields such as demographic_predicate, taxonomy, data_sources, methodology, segmentation_criteria, criteria_url, refresh_cadence, lookback_window, onboarder, modeling, audience_expansion, device_expansion, countries, consent_basis, restricted_attributes, policy_categories, art9_basis, data_subject_rights, and last_updated when the buyer needs them inline. Omit for the agent's default discovery projection. Agents SHOULD honor requested fields for exact lookup, refinement, small custom-signal result sets, and private/source-native signals when available. fields is a projection request, not an entitlement grant; agents MAY redact requested definition fields unless the caller is authorized for the underlying lineage, methodology, and rights-routing metadata. When demographic_predicate, consent_basis, or art9_basis is projected for another provider's signal, the value remains provider-declared signal-definition posture; sellers and federating agents MUST NOT substitute their own semantics or processing basis. For broad discovery and wholesale pages, agents MAY return compact pointers instead of inlining large resources, especially when provider-published definitions can be resolved from signal_ref, taxonomy.ref, criteria_url, disclosure_url, and validators such as resolved URL plus catalog_etag, HTTP ETag/Last-Modified, or taxonomy.etag.", - min_length=1, - ), - ] = None - max_results: Annotated[ - SchemaInt | None, - Field( - deprecated=True, - description='DEPRECATED: Use pagination.max_results instead. When both fields are present, agents MUST honor pagination.max_results. When only this field is present without a pagination envelope, agents SHOULD treat it as the page size subject to a maximum of 100 results. This field will be removed in AdCP 4.0.', - ge=1, - ), - ] = None - pagination: Annotated[ - pagination_request.PaginationRequest | None, - Field( - description='Pagination parameters. Use pagination.max_results (max: 100, default: 50) and pagination.cursor for cursor-based page walks. When the deprecated top-level max_results field is also present, pagination.max_results takes precedence.' - ), - ] = None - push_notification_config: Annotated[ - push_notification_config_1.PushNotificationConfig | None, - Field( - description='Optional webhook configuration for async terminal completion/failure notifications on semantic signal discovery. Meaningful only for `discovery_mode: "brief"` requests that enter the async lifecycle. Submitted envelopes with `task_id` remain pollable through `get_task_status` (legacy `tasks/get`) whether or not this field is present. If a brief request includes this field and the agent returns a Submitted envelope, the agent MUST deliver at least the terminal completion/failure notification to the configured URL; intermediate progress notifications are MAY. If the agent cannot honor the webhook channel, it MUST reject the request with a structured error instead of silently accepting. This field does not change wholesale timing semantics: agents MUST NOT route `discovery_mode: "wholesale"` requests through the async/Submitted arm or emit async delivery solely because `push_notification_config` is present; partial wholesale completion is reported via `incomplete[]`.' - ), - ] = None - if_wholesale_feed_version: Annotated[ - str | None, - Field( - description="Opaque wholesale_feed_version token returned by a prior wholesale-mode get_signals response from this agent. Only valid when discovery_mode is wholesale. When provided, the agent compares against its current wholesale signals feed version for the caller's cache_scope and MAY return an unchanged: true response (with signals omitted) if nothing has changed. The token is scope-keyed: callers cache `(cache_scope, wholesale_feed_version)` pairs. Scoping dimensions: (agent, discovery_mode, filters, destinations, countries) for cache_scope: 'public'; that tuple plus account_id for cache_scope: 'account'. pagination.cursor is NOT part of the scoping tuple. See specs/wholesale-feed-webhooks.md for the full sync pattern." - ), - ] = None - if_pricing_version: Annotated[ - str | None, - Field( - description="Opaque pricing_version token from a prior get_signals response. MUST only be sent together with if_wholesale_feed_version — pricing version has no structural baseline to compare against on its own. Evaluation order: (1) if_wholesale_feed_version mismatch → agent returns the full payload; (2) if_wholesale_feed_version matches but if_pricing_version mismatches → agent returns the full payload so the caller sees updated pricing_options; (3) both match → agent MAY return unchanged: true. Agents that don't track pricing separately ignore this and fall back to if_wholesale_feed_version semantics." - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/signals/get_signals_response.py b/src/adcp/types/generated_poc/signals/get_signals_response.py deleted file mode 100644 index cd9a8b8e7..000000000 --- a/src/adcp/types/generated_poc/signals/get_signals_response.py +++ /dev/null @@ -1,473 +0,0 @@ -# generated by datamodel-codegen: -# filename: signals/get_signals_response.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from collections.abc import Sequence -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarStr -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, StrictBool, StrictFloat - -from ..core import context as context_1 -from ..core import demographic_predicate as demographic_predicate_1 -from ..core import deployment, duration, error -from ..core import ext as ext_1 -from ..core import pagination_response, signal_coverage_forecast -from ..core import signal_id as signal_id_1 -from ..core import signal_modeling_disclosure -from ..core import signal_ref as signal_ref_1 -from ..core import vendor_pricing_option -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import consent_basis as consent_basis_1 -from ..enums import restricted_attribute, signal_catalog_type, signal_value_type - - -class Range(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - min: Annotated[StrictFloat, Field(description='Minimum value, inclusive.')] - max: Annotated[StrictFloat, Field(description='Maximum value, inclusive.')] - - -class Value(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - id: Annotated[str, Field(min_length=1)] - path: str | None = None - modifiers: list[str] | None = None - - -class ValueMapping(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - value: str - taxonomy_value_id: str - path: str | None = None - modifiers: list[str] | None = None - - -class ParentMatchBehavior(StrEnum): - exact_only = 'exact_only' - descendants_supported = 'descendants_supported' - unknown = 'unknown' - - -class Taxonomy(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - ref: AnyUrl - version: str | None = None - segtax: Annotated[SchemaInt | None, Field(ge=1)] = None - etag: str | None = None - values: Annotated[list[Value], Field(min_length=1)] - value_mappings: Annotated[list[ValueMapping] | None, Field(min_length=1)] = None - parent_match_behavior: ParentMatchBehavior | None = None - - -class DataSource(StrEnum): - app_behavior = 'app_behavior' - app_usage = 'app_usage' - web_usage = 'web_usage' - geo_location = 'geo_location' - email = 'email' - tv_ott_or_stb_device = 'tv_ott_or_stb_device' - panel = 'panel' - online_ecommerce = 'online_ecommerce' - credit_data = 'credit_data' - loyalty_card = 'loyalty_card' - transaction = 'transaction' - online_survey = 'online_survey' - offline_survey = 'offline_survey' - public_record_census = 'public_record_census' - public_record_voter_file = 'public_record_voter_file' - public_record_other = 'public_record_other' - offline_transaction = 'offline_transaction' - - -class Methodology(StrEnum): - observed = 'observed' - declared = 'declared' - derived = 'derived' - inferred = 'inferred' - modeled = 'modeled' - - -class RefreshCadence(StrEnum): - intra_day = 'intra_day' - daily = 'daily' - weekly = 'weekly' - monthly = 'monthly' - bi_monthly = 'bi_monthly' - quarterly = 'quarterly' - bi_annually = 'bi_annually' - annually = 'annually' - - -class MatchKey(StrEnum): - name = 'name' - address = 'address' - email = 'email' - postal = 'postal' - lat_long = 'lat_long' - mobile_id = 'mobile_id' - cookie_id = 'cookie_id' - ip = 'ip' - customer_id = 'customer_id' - phone = 'phone' - - -class PreOnboardingPrecisionLevel(StrEnum): - individual = 'individual' - household = 'household' - business = 'business' - geography = 'geography' - - -class Onboarder(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - match_keys: Annotated[list[MatchKey], Field(min_length=1)] - pre_onboarding_audience_expansion: StrictBool | None = None - pre_onboarding_device_expansion: StrictBool | None = None - pre_onboarding_precision_level: PreOnboardingPrecisionLevel | None = None - - -class Country(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{2}$'} - - -class Art9Basis(StrEnum): - explicit_consent = 'explicit_consent' - manifestly_made_public = 'manifestly_made_public' - substantial_public_interest = 'substantial_public_interest' - vital_interests = 'vital_interests' - - -class Method(StrEnum): - lookalike = 'lookalike' - supervised = 'supervised' - embedding = 'embedding' - rules = 'rules' - - -class Type(StrEnum): - first_party_crm = 'first_party_crm' - panel = 'panel' - declared_survey = 'declared_survey' - transactional = 'transactional' - behavioral = 'behavioral' - - -class SeedSource(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Type - provider_signed: Annotated[ - StrictBool, - Field( - description='Provider assertion that the seed source carries a signed attestation. Consumers MUST NOT treat this boolean alone as cryptographic proof.' - ), - ] - - -class TrainingDataJurisdiction(Country): - pass - - -class AiActRiskClass(StrEnum): - minimal = 'minimal' - limited = 'limited' - high_risk = 'high_risk' - - -class Right(StrEnum): - access = 'access' - rectification = 'rectification' - erasure = 'erasure' - portability = 'portability' - objection = 'objection' - - -class Channel(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - rights: Annotated[list[Right], Field(min_length=1)] - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - countries: list[Country] | None = None - - -class DataSubjectRights(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - upstream_source_domain: Annotated[ - str | None, - Field( - max_length=253, - pattern='^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]{0,61}[A-Za-z0-9])$', - ), - ] = None - channels: Annotated[list[Channel], Field(min_length=1)] - response_sla_days: Annotated[SchemaInt | None, Field(ge=1, le=90)] = None - ccpa_opt_out_url: AnyUrl | None = None - - -class Scope(StrEnum): - signals = 'signals' - pricing = 'pricing' - wholesale_feed = 'wholesale_feed' - - -class IncompleteItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Scope, - Field( - description="'signals': not all matching signals were returned. 'pricing': signals returned but pricing is absent or unconfirmed. 'wholesale_feed': in wholesale mode, full feed enumeration could not complete in the time budget." - ), - ] - description: Annotated[ - str, Field(description='Human-readable explanation of what is missing and why.') - ] - estimated_wait: Annotated[ - duration.Duration | None, - Field( - description='How much additional time would resolve this scope. Allows the caller to decide whether to retry with a larger time_budget.' - ), - ] = None - - -class CacheScope(StrEnum): - public = 'public' - account = 'account' - - -class Modeling(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - method: Method - seed_source: SeedSource - training_data_jurisdictions: Annotated[list[TrainingDataJurisdiction], Field(min_length=1)] - ai_act_risk_class: AiActRiskClass - disclosure: signal_modeling_disclosure.SignalModelingDisclosure | None = None - - -class Signal(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_id: Annotated[ - signal_id_1.SignalId | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older Signals Protocol clients.', - ), - ] = None - signal_ref: Annotated[ - signal_ref_1.SignalRef | None, - Field( - description="Canonical signal reference. Use scope 'product' for a product-local signal defined by this listing; use scope 'data_provider' with data_provider_domain for a signal defined in a data provider's published adagents.json signals[]; use scope 'signal_source' with signal_source_url for a source-native signal." - ), - ] = None - signal_agent_segment_id: Annotated[ - str, - Field( - description='Opaque resolved-segment handle issued by this signal source. Pass this string verbatim to activate_signal.signal_agent_segment_id, and echo it in package signal targeting when the selected product option exposes the same handle. Treat the value as provider-scoped and opaque: providers MAY namespace it so two providers can expose similarly named signals without relying on a shared taxonomy. Do not pass the signal_id object as this handle, and do not reconstruct a segment handle from categorical values when get_signals returned a resolved segment.' - ), - ] - name: Annotated[ - str, - Field( - description="Human-readable signal name. Required when signal_ref_1.scope is 'product'. For data_provider and signal_source refs, this is optional contextual display text; the referenced definition or source remains authoritative." - ), - ] - description: Annotated[ - str, - Field( - description='Detailed signal description. For data_provider and signal_source refs, this is optional contextual display text and MUST NOT replace the referenced definition.' - ), - ] - value_type: Annotated[ - signal_value_type.SignalValueType | None, - Field( - description="The data type of this signal's values. Required when signal_ref_1.scope is 'product'." - ), - ] = None - categories: Annotated[ - list[str] | None, - Field( - description="Valid values for categorical signals. Present when value_type is 'categorical'.", - min_length=1, - ), - ] = None - range: Annotated[ - Range | None, - Field(description="Valid range for numeric signals. Present when value_type is 'numeric'."), - ] = None - signal_type: Annotated[ - signal_catalog_type.SignalAvailabilityType, - Field(description='Commercial/provenance type of signal (marketplace, custom, owned)'), - ] - data_provider: Annotated[ - str | None, - Field( - description='Human-readable source name for the signal, when applicable. For data_provider-scoped signals this is the data provider name; for signal_source-scoped signals it may identify the signal source or proprietary origin.' - ), - ] = None - coverage_percentage: Annotated[ - StrictFloat | None, - Field( - deprecated=True, - description='DEPRECATED for detailed planning. Optional legacy scalar percentage of audience coverage retained only as a fallback for clients that do not consume coverage_forecast. When coverage_forecast is present, coverage_forecast is authoritative for signal-level discovery and coverage_percentage is fallback-only. If coverage_forecast includes an absent bucket over the same denominator, coverage_percentage SHOULD align with 100 * (1 - absent coverage_rate.mid).', - ge=0.0, - le=100.0, - ), - ] = None - coverage_forecast: Annotated[ - signal_coverage_forecast.SignalCoverageForecast | None, - Field( - description='Optional forecast-shaped signal availability guidance. When present, this is authoritative for signal-level discovery coverage. Use this to disclose the denominator, bucket semantics, not-present bucket, aggregate present bucket, and per-value coverage distribution for the signal.' - ), - ] = None - deployments: Annotated[ - Sequence[deployment.Deployment], Field(description='Array of deployment targets') - ] - pricing_options: Annotated[ - list[vendor_pricing_option.VendorPricingOption] | None, - Field( - description='Pricing options available for this signal when it has an incremental price. The buyer selects one and passes its pricing_option_id in report_usage or package-level signal_targeting_groups for billing verification. Omit when pricing is unavailable to the caller, bundled into the destination product, or has no incremental cost.', - min_length=1, - ), - ] = None - methodology_url: Annotated[ - AnyUrl | None, - Field( - description='Optional link to published methodology, media-kit, or data documentation. For data_provider and signal_source refs, this SHOULD match or supplement the referenced definition.' - ), - ] = None - last_updated: Annotated[ - AwareDatetime | None, - Field( - description='When this definition record was last updated. This indicates freshness of the definition record, not an attestation that the underlying data or model was refreshed at that time.' - ), - ] = None - restricted_attributes: Annotated[ - list[restricted_attribute.RestrictedAttribute] | None, - Field(description='Restricted attribute categories this signal touches.', min_length=1), - ] = None - demographic_predicate: Annotated[ - demographic_predicate_1.DemographicPredicate | None, - Field( - description="Projected authoritative demographic meaning for the signal. When projected from another provider, this MUST match the provider's definition exactly. Signal names alone never establish demographic semantics." - ), - ] = None - policy_categories: Annotated[ - list[str] | None, - Field(description='Policy categories this signal is sensitive for.', min_length=1), - ] = None - taxonomy: Annotated[ - Taxonomy | None, - Field( - description='Optional taxonomy metadata describing what this signal means in an external audience, content, retail-media, or provider-owned taxonomy.' - ), - ] = None - segmentation_criteria: Annotated[str | None, Field(max_length=500)] = None - criteria_url: AnyUrl | None = None - data_sources: Annotated[list[DataSource] | None, Field(min_length=1)] = None - methodology: Methodology | None = None - audience_expansion: StrictBool | None = None - device_expansion: StrictBool | None = None - refresh_cadence: RefreshCadence | None = None - lookback_window: RefreshCadence | None = None - onboarder: Onboarder | None = None - countries: Annotated[list[Country] | None, Field(min_length=1)] = None - consent_basis: Annotated[ - list[consent_basis_1.ConsentBasis] | None, - Field( - description="Data provider's declared GDPR Article 6 lawful basis or consent basis for the underlying signal definition, projected into this get_signals response row when requested. Sellers and federating agents that pass through another provider's signal MUST NOT substitute their own processing basis for the provider-declared basis.", - min_length=1, - ), - ] = None - art9_basis: Annotated[ - Art9Basis | None, - Field( - description="Data provider's declared GDPR Article 9 basis for the underlying signal definition when special-category data is involved and Article 9 applies, projected into this get_signals response row when requested. Sellers and federating agents that pass through another provider's signal MUST NOT substitute their own Article 9 basis for the provider-declared basis." - ), - ] = None - modeling: Modeling | None = None - data_subject_rights: Annotated[ - DataSubjectRights | None, - Field( - description='Per-signal data-subject-rights routing. This is a contact/routing reference, not a machine-callable AdCP API.' - ), - ] = None - dts_compliant_version: str | None = None - - -class GetSignalsResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - signals: Annotated[Sequence[Signal] | None, Field(description='Array of matching signals')] = None - errors: Annotated[ - list[error.Error] | None, - Field( - description='Task-specific errors and warnings (e.g., signal discovery or pricing issues)' - ), - ] = None - incomplete: Annotated[ - list[IncompleteItem] | None, - Field( - description="Declares what the agent could not finish within the caller's time_budget or due to internal limits. Each entry identifies a scope that is missing or partial. Absent when the response is fully complete.", - min_length=1, - ), - ] = None - wholesale_feed_version: Annotated[ - str | None, - Field( - description="Opaque token representing the version of the wholesale signals feed state used to compose this response. Agents that implement conditional-fetch (if_wholesale_feed_version) MUST return this on every wholesale-mode response so callers can cache and probe later. Callers MUST treat the value as opaque — no format, no ordering, no inspection. The token is scope-keyed: it describes a version for the cache_scope declared on this response, NOT a global agent version. A caller caches `(cache_scope, wholesale_feed_version)` pairs and presents the matching token on the next request. Scoping dimensions: (agent, discovery_mode, filters, destinations, countries) for cache_scope: 'public'; that tuple plus account_id for cache_scope: 'account'. pagination.cursor is NOT part of the scoping tuple. See specs/wholesale-feed-webhooks.md for the full cache layering model." - ), - ] = None - pricing_version: Annotated[ - str | None, - Field( - description='Opaque token representing the version of the pricing layer. When the agent supports independent pricing versioning, pricing_version changes when prices move but wholesale_feed_version changes only when structure/metadata moves. Same cache_scope keying as wholesale_feed_version. Agents not separating these MAY omit pricing_version and use wholesale_feed_version for both.' - ), - ] = None - cache_scope: Annotated[ - CacheScope | None, - Field( - description="Declares whether the wholesale_feed_version and pricing_version on this response describe a universal layer or an account-specific overlay. REQUIRED on every 3.1+ response (the 3.1 schema enforces this — the safety property of the two-layer cache model depends on it). 'public': this response describes the agent's published rate card; the caller MAY dedupe under (agent, discovery_mode, filters, destinations, countries) without scoping by account. 'account': this response includes account-specific overrides; the caller MUST cache the version under that tuple plus account_id. When the request did NOT include `account`, the agent MUST return `cache_scope: 'public'`. When the request included `account`, the agent MUST return either 'public' (this account prices off the public rate card — caller dedupes) or 'account' (account-specific overrides exist — caller caches under the account key). Agents MAY return 'public' on an account-scoped request that previously had overrides — callers SHOULD interpret this as a downgrade. Without schema-required cache_scope, an agent silently omitting the field on an account-scoped response would cause callers to mis-key the cache and serve account-overlay payloads to other accounts — the canonical safety invariant of the entire cache layering model. **Backward-compatibility note for 3.1 validators:** SDKs validating strictly against the 3.1 schema MUST select the validator based on the server-declared `adcp_version`. For responses with `adcp_version` starting `3.0`, the 3.1 cache_scope-required constraint MUST be relaxed — pre-3.1 agents correctly emit no cache_scope and remain conformant to their declared version. This is a tightening within 3.1, not a 3.0 break." - ), - ] = CacheScope.public - unchanged: Annotated[ - Literal[True] | None, - Field( - description="Present and `true` ONLY on wholesale-mode responses when the request carried if_wholesale_feed_version (and/or if_pricing_version) matching the agent's current version for the caller's cache_scope, in which case signals[] MUST be omitted; wholesale_feed_version (echoed), cache_scope (echoed), and pricing_version (echoed when used) MUST still be present. Callers receiving unchanged: true MUST NOT mutate their local wholesale signals mirror. **One shape per state:** agents MUST NOT emit `unchanged: false` — the absence of the field IS the signal that the response carries signals. **Cross-scope isolation:** the comparator that decides `unchanged` MUST be keyed on `(cache_scope, wholesale_feed_version)`, not on the token value alone. An agent MUST NOT emit `unchanged: true` when it resolves the request to a different `cache_scope` than the one whose token the caller echoed in `if_wholesale_feed_version` (and/or `if_pricing_version`): because the token is scope-keyed, a value minted for `cache_scope: 'public'` cannot match the agent's current token for `cache_scope: 'account'` (or vice-versa), so such a request MUST return the full feed for the resolved scope with that scope's own token." - ), - ] = None - pagination: pagination_response.PaginationResponse | None = None - sandbox: Annotated[ - StrictBool | None, - Field(description='When true, this response contains simulated data from sandbox mode.'), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/sponsored_intelligence/__init__.py b/src/adcp/types/generated_poc/sponsored_intelligence/__init__.py deleted file mode 100644 index a01e99719..000000000 --- a/src/adcp/types/generated_poc/sponsored_intelligence/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# generated by datamodel-codegen: -# filename: .schema_temp -# timestamp: 2026-06-01T00:32:59+00:00 diff --git a/src/adcp/types/generated_poc/sponsored_intelligence/si_capabilities.py b/src/adcp/types/generated_poc/sponsored_intelligence/si_capabilities.py deleted file mode 100644 index bf86e4c49..000000000 --- a/src/adcp/types/generated_poc/sponsored_intelligence/si_capabilities.py +++ /dev/null @@ -1,125 +0,0 @@ -# generated by datamodel-codegen: -# filename: sponsored_intelligence/si_capabilities.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StrictBool - - -class Voice(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - provider: Annotated[ - str | None, Field(description='TTS provider (elevenlabs, openai, etc.)') - ] = None - voice_id: Annotated[str | None, Field(description='Brand voice identifier')] = None - - -class Video(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - formats: Annotated[ - list[str] | None, Field(description='Supported video formats (mp4, webm, etc.)') - ] = None - max_duration_seconds: Annotated[ - SchemaInt | None, Field(description='Maximum video duration') - ] = None - - -class Avatar(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - provider: Annotated[ - str | None, Field(description='Avatar provider (d-id, heygen, synthesia, etc.)') - ] = None - avatar_id: Annotated[str | None, Field(description='Brand avatar identifier')] = None - - -class Modalities(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - conversational: Annotated[ - StrictBool | None, Field(description='Pure text exchange - the baseline modality') - ] = True - voice: Annotated[ - StrictBool | Voice | None, Field(description='Audio-based interaction using brand voice') - ] = None - video: Annotated[ - StrictBool | Video | None, Field(description='Brand video content playback') - ] = None - avatar: Annotated[ - StrictBool | Avatar | None, Field(description='Animated video presence with brand avatar') - ] = None - - -class StandardEnum(StrEnum): - text = 'text' - link = 'link' - image = 'image' - product_card = 'product_card' - carousel = 'carousel' - action_button = 'action_button' - - -class Components(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - standard: Annotated[ - list[StandardEnum] | None, - Field(description='Standard components that all SI hosts must render'), - ] = None - extensions: Annotated[ - dict[str, Any] | None, - Field(description='Platform-specific extensions (chatgpt_apps_sdk, maps, forms, etc.)'), - ] = None - - -class Commerce(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - acp_checkout: Annotated[ - StrictBool | None, - Field(description='Supports ACP (Agentic Commerce Protocol) checkout handoff'), - ] = None - - -class A2ui(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supported: Annotated[ - StrictBool | None, Field(description='Supports A2UI surface rendering') - ] = False - catalogs: Annotated[ - list[str] | None, - Field(description="Supported A2UI component catalogs (e.g., 'si-standard', 'standard')"), - ] = None - - -class SiCapabilities(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - modalities: Annotated[ - Modalities | None, Field(description='Interaction modalities supported') - ] = None - components: Annotated[Components | None, Field(description='Visual components supported')] = ( - None - ) - commerce: Annotated[Commerce | None, Field(description='Commerce capabilities')] = None - a2ui: Annotated[A2ui | None, Field(description='A2UI (Agent-to-UI) capabilities')] = None - mcp_apps: Annotated[ - StrictBool | None, - Field(description='Supports MCP Apps for rendering A2UI surfaces in iframes'), - ] = False diff --git a/src/adcp/types/generated_poc/sponsored_intelligence/si_context_use.py b/src/adcp/types/generated_poc/sponsored_intelligence/si_context_use.py deleted file mode 100644 index 0bdccd1fe..000000000 --- a/src/adcp/types/generated_poc/sponsored_intelligence/si_context_use.py +++ /dev/null @@ -1,13 +0,0 @@ -# generated by datamodel-codegen: -# filename: sponsored_intelligence/si_context_use.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum - - -class SiContextUse(StrEnum): - presentation_only = 'presentation_only' - comparison_set = 'comparison_set' - reasoning_context = 'reasoning_context' diff --git a/src/adcp/types/generated_poc/sponsored_intelligence/si_get_offering_request.py b/src/adcp/types/generated_poc/sponsored_intelligence/si_get_offering_request.py deleted file mode 100644 index 653d65242..000000000 --- a/src/adcp/types/generated_poc/sponsored_intelligence/si_get_offering_request.py +++ /dev/null @@ -1,38 +0,0 @@ -# generated by datamodel-codegen: -# filename: sponsored_intelligence/si_get_offering_request.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import SchemaInt -from pydantic import ConfigDict, Field, StrictBool - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.version_envelope import AdcpVersionEnvelope - - -class SiGetOfferingRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - offering_id: Annotated[ - str, Field(description='Offering identifier from the catalog to get details for') - ] - intent: Annotated[ - str | None, - Field( - description="Optional natural language description of user intent for personalized results (e.g., 'mens size 14 near Cincinnati'). Must be anonymous - no PII." - ), - ] = None - context: context_1.ContextObject | None = None - include_products: Annotated[ - StrictBool | None, Field(description='Whether to include matching products in the response') - ] = False - product_limit: Annotated[ - SchemaInt | None, - Field(description='Maximum number of matching products to return', ge=1, le=50), - ] = 5 - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/sponsored_intelligence/si_get_offering_response.py b/src/adcp/types/generated_poc/sponsored_intelligence/si_get_offering_response.py deleted file mode 100644 index 3ff20fd16..000000000 --- a/src/adcp/types/generated_poc/sponsored_intelligence/si_get_offering_response.py +++ /dev/null @@ -1,126 +0,0 @@ -# generated by datamodel-codegen: -# filename: sponsored_intelligence/si_get_offering_response.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictBool - -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import offering_availability_status -from . import si_sponsored_context - - -class Offering(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - offering_id: Annotated[str | None, Field(description='Offering identifier')] = None - title: Annotated[str | None, Field(description='Offering title')] = None - summary: Annotated[str | None, Field(description='Brief summary of the offering')] = None - tagline: Annotated[str | None, Field(description='Short promotional tagline')] = None - expires_at: Annotated[AwareDatetime | None, Field(description='When this offering expires')] = ( - None - ) - availability_status: Annotated[ - offering_availability_status.OfferingAvailabilityStatus | None, - Field( - description="Machine-readable availability state for the offering. Optional; when omitted, derive availability from the top-level 'available' boolean and 'unavailable_reason'." - ), - ] = None - price_hint: Annotated[ - str | None, Field(description="Price indication (e.g., 'from $199', '50% off')") - ] = None - image_url: Annotated[AnyUrl | None, Field(description='Hero image for the offering')] = None - landing_url: Annotated[AnyUrl | None, Field(description='Landing page URL')] = None - - -class MatchingProduct(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - product_id: Annotated[str, Field(description='Product identifier')] - name: Annotated[str, Field(description='Product name')] - price: Annotated[str | None, Field(description="Display price (e.g., '$129', '$89.99')")] = None - original_price: Annotated[str | None, Field(description='Original price if on sale')] = None - image_url: Annotated[AnyUrl | None, Field(description='Product image')] = None - availability_summary: Annotated[ - str | None, - Field( - description="Brief availability info (e.g., 'In stock', 'Size 14 available', '3 left')" - ), - ] = None - availability_status: Annotated[ - offering_availability_status.OfferingAvailabilityStatus | None, - Field( - description='Machine-readable availability state for this product. Structured counterpart to availability_summary.' - ), - ] = None - url: Annotated[AnyUrl | None, Field(description='Product detail page URL')] = None - - -class SiGetOfferingResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - available: Annotated[ - StrictBool, Field(description='Whether the offering is currently available') - ] - offering_token: Annotated[ - str | None, - Field( - description="Token to pass to si_initiate_session for session continuity. Brand stores the full query context server-side (products shown, order, context) so they can resolve references like 'the second one' when the session starts." - ), - ] = None - ttl_seconds: Annotated[ - SchemaInt | None, - Field( - description='How long this offering information is valid (seconds). Host should re-fetch after TTL expires.', - ge=0, - ), - ] = None - checked_at: Annotated[ - AwareDatetime | None, Field(description='When this offering information was retrieved') - ] = None - offering: Annotated[Offering | None, Field(description='Offering details')] = None - matching_products: Annotated[ - list[MatchingProduct] | None, - Field( - description='Products matching the request context. Only included if include_products was true.' - ), - ] = None - sponsored_context: Annotated[ - si_sponsored_context.SiSponsoredContext | None, - Field( - description='Declaration for the sponsored context carried by this offering response. When present, it applies to the returned offering and matching_products package as a whole unless a future extension narrows the declaration to individual items. Hosts MUST either honor the declared context_use and disclosure_obligation or reject the context before using it.' - ), - ] = None - total_matching: Annotated[ - SchemaInt | None, - Field( - description='Total number of products matching the context (may be more than returned in matching_products)', - ge=0, - ), - ] = None - unavailable_reason: Annotated[ - str | None, - Field( - description="If not available, why (e.g., 'expired', 'sold_out', 'region_restricted')" - ), - ] = None - alternative_offering_ids: Annotated[ - list[str] | None, - Field(description='Alternative offerings to consider if this one is unavailable'), - ] = None - errors: Annotated[ - list[error.Error] | None, Field(description='Errors during offering lookup') - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/sponsored_intelligence/si_identity.py b/src/adcp/types/generated_poc/sponsored_intelligence/si_identity.py deleted file mode 100644 index 0dc750eac..000000000 --- a/src/adcp/types/generated_poc/sponsored_intelligence/si_identity.py +++ /dev/null @@ -1,80 +0,0 @@ -# generated by datamodel-codegen: -# filename: sponsored_intelligence/si_identity.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, StrictBool - - -class ConsentScopeEnum(StrEnum): - name = 'name' - email = 'email' - shipping_address = 'shipping_address' - phone = 'phone' - locale = 'locale' - - -class PrivacyPolicyAcknowledged(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - brand_policy_url: Annotated[ - AnyUrl | None, Field(description="URL to brand's privacy policy") - ] = None - brand_policy_version: Annotated[ - str | None, Field(description='Version of policy acknowledged') - ] = None - - -class ShippingAddress(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - street: str | None = None - city: str | None = None - state: str | None = None - postal_code: str | None = None - country: str | None = None - - -class User(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - email: Annotated[EmailStr | None, Field(description="User's email address")] = None - name: Annotated[str | None, Field(description="User's display name")] = None - locale: Annotated[str | None, Field(description="User's locale (e.g., en-US)")] = None - phone: Annotated[str | None, Field(description="User's phone number")] = None - shipping_address: Annotated[ - ShippingAddress | None, Field(description="User's shipping address for accurate pricing") - ] = None - - -class SiIdentity(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - consent_granted: Annotated[ - StrictBool, Field(description='Whether user consented to share identity') - ] - consent_timestamp: Annotated[ - AwareDatetime | None, Field(description='When consent was granted (ISO 8601)') - ] = None - consent_scope: Annotated[ - list[ConsentScopeEnum] | None, Field(description='What data was consented to share') - ] = None - privacy_policy_acknowledged: Annotated[ - PrivacyPolicyAcknowledged | None, Field(description='Brand privacy policy acknowledgment') - ] = None - user: Annotated[ - User | None, Field(description='User data (only present if consent_granted is true)') - ] = None - anonymous_session_id: Annotated[ - str | None, - Field(description='Session ID for anonymous users (when consent_granted is false)'), - ] = None diff --git a/src/adcp/types/generated_poc/sponsored_intelligence/si_initiate_session_request.py b/src/adcp/types/generated_poc/sponsored_intelligence/si_initiate_session_request.py deleted file mode 100644 index a2bce468a..000000000 --- a/src/adcp/types/generated_poc/sponsored_intelligence/si_initiate_session_request.py +++ /dev/null @@ -1,66 +0,0 @@ -# generated by datamodel-codegen: -# filename: sponsored_intelligence/si_initiate_session_request.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from pydantic import ConfigDict, Field - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.version_envelope import AdcpVersionEnvelope -from . import si_capabilities, si_identity, si_sponsored_context_receipt - - -class SiInitiateSessionRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - intent: Annotated[ - str, - Field( - description='Natural language description of user intent — the conversation handoff from the host describing what the user needs from the brand agent' - ), - ] - context: context_1.ContextObject | None = None - identity: si_identity.SiIdentity - media_buy_id: Annotated[ - str | None, Field(description='AdCP media buy ID if session was triggered by advertising') - ] = None - placement: Annotated[ - str | None, - Field( - description="Where this session was triggered (e.g., 'chatgpt_search', 'claude_chat')" - ), - ] = None - offering_id: Annotated[ - str | None, Field(description='Brand-specific offering identifier to apply') - ] = None - supported_capabilities: Annotated[ - si_capabilities.SiCapabilities | None, - Field(description='What capabilities the host supports'), - ] = None - offering_token: Annotated[ - str | None, - Field( - description="Token from si_get_offering response for session continuity. Brand uses this to recall what products were shown to the user, enabling natural references like 'the second one' or 'that blue shoe'." - ), - ] = None - sponsored_context_receipt: Annotated[ - si_sponsored_context_receipt.SiSponsoredContextReceipt | None, - Field( - description='Host receipt for sponsored context accepted from a prior si_get_offering response or other pre-session context package. This records the accepted context_use, disclosure commitment, paying_principal, and host receipt for audit.' - ), - ] = None - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for this request. Prevents duplicate session creation on retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/sponsored_intelligence/si_initiate_session_response.py b/src/adcp/types/generated_poc/sponsored_intelligence/si_initiate_session_response.py deleted file mode 100644 index 86a3b6d17..000000000 --- a/src/adcp/types/generated_poc/sponsored_intelligence/si_initiate_session_response.py +++ /dev/null @@ -1,68 +0,0 @@ -# generated by datamodel-codegen: -# filename: sponsored_intelligence/si_initiate_session_response.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field - -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import si_session_status -from . import si_capabilities, si_sponsored_context, si_ui_element - - -class Response(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - message: Annotated[str | None, Field(description='Conversational message from brand agent')] = ( - None - ) - ui_elements: Annotated[ - list[si_ui_element.SiUiElement] | None, Field(description='Visual components to render') - ] = None - - -class SiInitiateSessionResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - session_id: Annotated[ - str, Field(description='Unique session identifier for subsequent messages') - ] - response: Annotated[Response | None, Field(description="Brand agent's initial response")] = None - negotiated_capabilities: Annotated[ - si_capabilities.SiCapabilities | None, - Field(description='Intersection of brand and host capabilities for this session'), - ] = None - sponsored_context: Annotated[ - si_sponsored_context.SiSponsoredContext | None, - Field( - description='Declaration for sponsored context carried by the initial brand-agent response. Hosts MUST either honor the declared context_use and disclosure_obligation or reject the context before presenting, comparing, or otherwise using it.' - ), - ] = None - session_status: Annotated[ - si_session_status.SiSessionStatus, - Field( - description='Current session lifecycle state. Returned in initiation, message, and termination responses.' - ), - ] - session_ttl_seconds: Annotated[ - SchemaInt | None, - Field( - description='Session inactivity timeout in seconds. After this duration without a message, the brand agent may terminate the session. Hosts SHOULD warn users before timeout when possible.', - ge=1, - ), - ] = None - errors: Annotated[ - list[error.Error] | None, Field(description='Errors during session initiation') - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/sponsored_intelligence/si_send_message_request.py b/src/adcp/types/generated_poc/sponsored_intelligence/si_send_message_request.py deleted file mode 100644 index f100cafd1..000000000 --- a/src/adcp/types/generated_poc/sponsored_intelligence/si_send_message_request.py +++ /dev/null @@ -1,67 +0,0 @@ -# generated by datamodel-codegen: -# filename: sponsored_intelligence/si_send_message_request.json -# timestamp: 2026-10-04T01:14:11+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, model_validator - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.version_envelope import AdcpVersionEnvelope -from . import si_sponsored_context_receipt - - -class ActionResponse(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - action: Annotated[str | None, Field(description='The action that was triggered')] = None - payload: Annotated[ - dict[str, Any] | None, Field(description='Action-specific response data') - ] = None - - -class SiSendMessageRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for at-most-once execution. Each conversational turn is a distinct mutation of session transcript — without this key, a timeout-and-retry produces a duplicate turn and a duplicate model response. MUST be unique per (seller, request) pair. Use a fresh UUID v4 for each user turn.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - session_id: Annotated[str, Field(description='Active session identifier')] - message: Annotated[str | None, Field(description="User's message to the brand agent")] = None - action_response: Annotated[ - ActionResponse | None, - Field(description='Response to a previous action_button (e.g., user clicked checkout)'), - ] = None - sponsored_context_receipt: Annotated[ - si_sponsored_context_receipt.SiSponsoredContextReceipt | None, - Field( - description="Host receipt for sponsored context accepted from a prior SI response in this session. This gives the brand/seller an audit-visible record of the host's accepted use mode and disclosure commitment for that context." - ), - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None - - @model_validator(mode='after') - def _require_schema_required_group(self) -> SiSendMessageRequest: - # ``required`` asks whether the caller supplied the field, which is what - # model_fields_set answers. An explicit null is a supplied value — on a - # mutation input it is the command to clear — and a default the caller - # never sent is not. - for group in (('message',), ('action_response',),): - if all(name in self.model_fields_set for name in group): - return self - raise ValueError( - 'SiSendMessageRequest requires at least one of these field groups: message | action_response' - ) diff --git a/src/adcp/types/generated_poc/sponsored_intelligence/si_send_message_response.py b/src/adcp/types/generated_poc/sponsored_intelligence/si_send_message_response.py deleted file mode 100644 index e035daeb4..000000000 --- a/src/adcp/types/generated_poc/sponsored_intelligence/si_send_message_response.py +++ /dev/null @@ -1,125 +0,0 @@ -# generated by datamodel-codegen: -# filename: sponsored_intelligence/si_send_message_response.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictFloat - -from ..a2ui import surface as surface_1 -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import si_session_status -from . import si_sponsored_context, si_ui_element - - -class Type(StrEnum): - transaction = 'transaction' - complete = 'complete' - - -class Price(AdCPBaseModel): - amount: StrictFloat | None = None - currency: str | None = None - - -class Intent(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - action: Annotated[str | None, Field(description="The commerce action (e.g., 'purchase')")] = ( - None - ) - product: Annotated[dict[str, Any] | None, Field(description='Product details for checkout')] = ( - None - ) - price: Annotated[Price | None, Field(description='Price information')] = None - - -class ContextForCheckout(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - conversation_summary: Annotated[ - str | None, Field(description='Summary of the conversation leading to purchase') - ] = None - applied_offers: Annotated[ - list[str] | None, Field(description='Offer IDs that were applied during the conversation') - ] = None - - -class Handoff(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Type | None, - Field( - description='Type of handoff: transaction (ready for ACP checkout) or complete (conversation done)' - ), - ] = None - intent: Annotated[ - Intent | None, - Field(description='For transaction handoffs: what the user wants to purchase'), - ] = None - context_for_checkout: Annotated[ - ContextForCheckout | None, Field(description='Context to pass to ACP for seamless checkout') - ] = None - - -class Response(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - message: Annotated[str | None, Field(description='Conversational message from brand agent')] = ( - None - ) - surface: Annotated[ - surface_1.A2UiSurface | None, Field(description='A2UI surface with interactive components') - ] = None - ui_elements: Annotated[ - list[si_ui_element.SiUiElement] | None, - Field( - deprecated=True, - description='Visual components to render (DEPRECATED: use surface instead)', - ), - ] = None - - -class SiSendMessageResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - session_id: Annotated[str, Field(description='Session identifier')] - response: Annotated[Response | None, Field(description="Brand agent's response")] = None - mcp_resource_uri: Annotated[ - str | None, - Field( - description='MCP resource URI for hosts with MCP Apps support (e.g., ui://si/session-abc123)' - ), - ] = None - sponsored_context: Annotated[ - si_sponsored_context.SiSponsoredContext | None, - Field( - description='Declaration for sponsored context carried by this brand-agent response. Hosts MUST either honor the declared context_use and disclosure_obligation or reject the context before presenting, comparing, or otherwise using it.' - ), - ] = None - session_status: Annotated[ - si_session_status.SiSessionStatus, - Field( - description='Current session status. On a successful response, one of: active, pending_handoff, or complete. Terminated sessions return error codes (SESSION_NOT_FOUND or SESSION_TERMINATED) instead of a success response.' - ), - ] - handoff: Annotated[ - Handoff | None, Field(description='Handoff request when session_status is pending_handoff') - ] = None - errors: list[error.Error] | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/sponsored_intelligence/si_sponsored_context.py b/src/adcp/types/generated_poc/sponsored_intelligence/si_sponsored_context.py deleted file mode 100644 index 7264f9df4..000000000 --- a/src/adcp/types/generated_poc/sponsored_intelligence/si_sponsored_context.py +++ /dev/null @@ -1,155 +0,0 @@ -# generated by datamodel-codegen: -# filename: sponsored_intelligence/si_sponsored_context.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictBool - -from ..core import brand_ref -from ..core import ext as ext_1 -from . import si_context_use - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, Field(description='Seller-assigned account identifier for the paying principal.') - ] - - -class Timing(StrEnum): - before_use = 'before_use' - at_first_influenced_output = 'at_first_influenced_output' - near_each_influenced_output = 'near_each_influenced_output' - - -class Proximity(StrEnum): - session_level = 'session_level' - near_rendered_unit = 'near_rendered_unit' - near_influenced_output = 'near_influenced_output' - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[str, Field(description='ISO 3166-1 alpha-2 country code.')] - region: Annotated[str | None, Field(description='Optional sub-national region code.')] = None - regulation: Annotated[str, Field(description='Regulation or policy identifier.')] - - -class DisclosureObligation(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - StrictBool, - Field( - description='Whether the declaring party requires disclosure for this sponsored context.' - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Disclosure label text the host should render when disclosure is required.' - ), - ] = None - timing: Annotated[ - Timing | None, - Field( - description="When the disclosure must be presented relative to the sponsored context's influence." - ), - ] = None - proximity: Annotated[ - Proximity | None, - Field(description='Where the disclosure should appear relative to the affected output.'), - ] = None - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field( - description='Jurisdictions where this declared disclosure obligation applies.', - min_length=1, - ), - ] = None - - -class Role(StrEnum): - brand_agent = 'brand_agent' - seller = 'seller' - network = 'network' - platform = 'platform' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, Field(description='HTTPS URL of the declaring agent or service.') - ] = None - role: Annotated[Role, Field(description='Role of the declaring party.')] - - -class PayingPrincipal(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - brand: Annotated[ - brand_ref.BrandReference, - Field(description='Brand economically accountable for the sponsored context.'), - ] - account: Annotated[ - Account | None, - Field( - description='Optional seller-assigned account context for the paying principal. This intentionally carries only an account_id so the canonical economic principal remains paying_principal.brand.' - ), - ] = None - operator: Annotated[ - str | None, - Field( - description='Domain of the operator acting for the paying principal, when different from the brand domain.', - 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='Human-readable label for disclosure rendering. The canonical identity remains the brand/account reference.' - ), - ] = None - - -class SiSponsoredContext(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - paying_principal: Annotated[ - PayingPrincipal, - Field( - description="Economic accountability fact: the brand that funded or sponsored this context, with optional account/operator context. This identifies who paid for the sponsored context; it is distinct from the host's later receipt and use commitment." - ), - ] - context_use: Annotated[ - si_context_use.SiContextUse, - Field(description='Declared host-side use mode for this sponsored context.'), - ] - disclosure_obligation: Annotated[ - DisclosureObligation, - Field( - description='Disclosure obligation the receiving host must either accept and satisfy or reject before using this sponsored context. This is a declared obligation and audit input, not a protocol-level legal determination.' - ), - ] - declared_at: Annotated[ - AwareDatetime | None, Field(description='When this sponsored-context declaration was made.') - ] = None - declared_by: Annotated[ - DeclaredBy | None, Field(description='Agent or service that attached the declaration.') - ] = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/sponsored_intelligence/si_sponsored_context_receipt.py b/src/adcp/types/generated_poc/sponsored_intelligence/si_sponsored_context_receipt.py deleted file mode 100644 index 4246a0e5f..000000000 --- a/src/adcp/types/generated_poc/sponsored_intelligence/si_sponsored_context_receipt.py +++ /dev/null @@ -1,101 +0,0 @@ -# generated by datamodel-codegen: -# filename: sponsored_intelligence/si_sponsored_context_receipt.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - -from ..core import ext as ext_1 -from . import si_context_use, si_sponsored_context - - -class Status(StrEnum): - accepted = 'accepted' - rejected = 'rejected' - - -class Status46(StrEnum): - accepted = 'accepted' - not_required = 'not_required' - - -class DisclosureCommitment(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Status46, - Field( - description="Host commitment status for the disclosure obligation. Use accepted when the declaration requires disclosure and the host will satisfy it; use not_required only when the declaration's disclosure_obligation.required is false. A host that will not satisfy a required disclosure rejects the sponsored context." - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Disclosure label text the host committed to render, if different from or copied from the declaration.' - ), - ] = None - notes: Annotated[ - str | None, Field(description='Optional host explanation for the disclosure commitment.') - ] = None - - -class HostReceipt(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Status, - Field(description='Whether the host accepted the declared sponsored context for use.'), - ] - accepted_context_use: Annotated[ - si_context_use.SiContextUse | None, - Field( - description="Use mode the host accepted. When host_receipt.status is accepted, this MUST match the declaration's context_use; a host that cannot honor the declared use mode rejects the sponsored context instead of down-scoping it." - ), - ] = None - received_at: Annotated[ - AwareDatetime, Field(description='When the host received the sponsored context.') - ] - host_surface: Annotated[ - str | None, - Field( - description='Host-defined surface or placement where the context was accepted, such as an assistant session, search result page, or comparison module.' - ), - ] = None - disclosure_commitment: Annotated[ - DisclosureCommitment | None, - Field( - description='How the host committed to handle the declared disclosure obligation. Required when host_receipt.status is accepted.' - ), - ] = None - rejection_reason: Annotated[ - str | None, - Field( - description='Optional explanation when status is rejected, for example unsupported context_use or inability to satisfy the disclosure obligation.' - ), - ] = None - - -class SiSponsoredContextReceipt(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - sponsored_context: Annotated[ - si_sponsored_context.SiSponsoredContext, - Field( - description='The sponsored-context declaration the host received and is accepting or rejecting.' - ), - ] - host_receipt: Annotated[ - HostReceipt, - Field( - description='Receiving-surface accountability fact: the use mode the host accepted and committed to honor for this sponsored context.' - ), - ] - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/sponsored_intelligence/si_terminate_session_request.py b/src/adcp/types/generated_poc/sponsored_intelligence/si_terminate_session_request.py deleted file mode 100644 index 8e96b0565..000000000 --- a/src/adcp/types/generated_poc/sponsored_intelligence/si_terminate_session_request.py +++ /dev/null @@ -1,63 +0,0 @@ -# generated by datamodel-codegen: -# filename: sponsored_intelligence/si_terminate_session_request.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..core import context as context_1 -from ..core import ext as ext_1 -from ..core.version_envelope import AdcpVersionEnvelope - - -class Reason(StrEnum): - handoff_transaction = 'handoff_transaction' - handoff_complete = 'handoff_complete' - user_exit = 'user_exit' - session_timeout = 'session_timeout' - host_terminated = 'host_terminated' - - -class Action(StrEnum): - purchase = 'purchase' - subscribe = 'subscribe' - - -class TransactionIntent(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - action: Action | None = None - product: Annotated[dict[str, Any] | None, Field(description='Product/service details')] = None - - -class TerminationContext(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - summary: Annotated[str | None, Field(description='Summary of the conversation')] = None - transaction_intent: Annotated[ - TransactionIntent | None, - Field(description='For handoff_transaction - what user wants to buy'), - ] = None - cause: Annotated[ - str | None, Field(description='For host_terminated - why host ended session') - ] = None - - -class SiTerminateSessionRequest(AdcpVersionEnvelope): - model_config = ConfigDict( - extra='allow', - ) - session_id: Annotated[str, Field(description='Session identifier to terminate')] - reason: Annotated[Reason, Field(description='Reason for termination')] - termination_context: Annotated[ - TerminationContext | None, Field(description='Context for the termination') - ] = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/sponsored_intelligence/si_terminate_session_response.py b/src/adcp/types/generated_poc/sponsored_intelligence/si_terminate_session_response.py deleted file mode 100644 index 0f493dc78..000000000 --- a/src/adcp/types/generated_poc/sponsored_intelligence/si_terminate_session_response.py +++ /dev/null @@ -1,87 +0,0 @@ -# generated by datamodel-codegen: -# filename: sponsored_intelligence/si_terminate_session_response.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictBool - -from ..core import context as context_1 -from ..core import error -from ..core import ext as ext_1 -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from ..enums import si_session_status - - -class AcpHandoff(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - checkout_url: Annotated[ - AnyUrl | None, - Field( - description="Brand's ACP checkout endpoint. Hosts MUST validate this is HTTPS before opening." - ), - ] = None - checkout_token: Annotated[ - str | None, - Field( - description='Opaque token for the checkout flow. The host passes this to the checkout endpoint to correlate the SI session with the transaction.' - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Rich checkout context to pass to the ACP endpoint (product details, applied offers, pricing). Alternative to checkout_token for integrations that need structured data.' - ), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field( - description='When this handoff data expires. Hosts should initiate checkout before this time.' - ), - ] = None - - -class Action(StrEnum): - save_for_later = 'save_for_later' - set_reminder = 'set_reminder' - subscribe_updates = 'subscribe_updates' - none = 'none' - - -class FollowUp(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - action: Action | None = None - data: Annotated[dict[str, Any] | None, Field(description='Data for follow-up action')] = None - - -class SiTerminateSessionResponse(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - session_id: Annotated[str, Field(description='Terminated session identifier')] - terminated: Annotated[ - StrictBool, Field(description='Whether session was successfully terminated') - ] - session_status: Annotated[ - si_session_status.SiSessionStatus | None, - Field( - description="Resulting session state. 'complete' for handoff_transaction/handoff_complete, 'terminated' for user_exit/session_timeout/host_terminated." - ), - ] = None - acp_handoff: Annotated[ - AcpHandoff | None, - Field(description='ACP checkout handoff data. Present when reason is handoff_transaction.'), - ] = None - follow_up: Annotated[FollowUp | None, Field(description='Suggested follow-up actions')] = None - errors: list[error.Error] | None = None - context: context_1.ContextObject | None = None - ext: ext_1.ExtensionObject | None = None diff --git a/src/adcp/types/generated_poc/sponsored_intelligence/si_ui_element.py b/src/adcp/types/generated_poc/sponsored_intelligence/si_ui_element.py deleted file mode 100644 index 92556de6d..000000000 --- a/src/adcp/types/generated_poc/sponsored_intelligence/si_ui_element.py +++ /dev/null @@ -1,30 +0,0 @@ -# generated by datamodel-codegen: -# filename: sponsored_intelligence/si_ui_element.json -# timestamp: 2026-05-02T19:36:29+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - - -class Type(StrEnum): - text = 'text' - link = 'link' - image = 'image' - product_card = 'product_card' - carousel = 'carousel' - action_button = 'action_button' - app_handoff = 'app_handoff' - integration_actions = 'integration_actions' - - -class SiUiElement(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[Type, Field(description='Component type')] - data: Annotated[dict[str, Any] | None, Field(description='Component-specific data')] = None diff --git a/src/adcp/types/generated_poc/trusted_match/__init__.py b/src/adcp/types/generated_poc/trusted_match/__init__.py deleted file mode 100644 index a20dd31c4..000000000 --- a/src/adcp/types/generated_poc/trusted_match/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# generated by datamodel-codegen: -# filename: .schema_temp -# timestamp: 2026-07-01T04:30:58+00:00 diff --git a/src/adcp/types/generated_poc/trusted_match/available_package.py b/src/adcp/types/generated_poc/trusted_match/available_package.py deleted file mode 100644 index a7c3c7ab9..000000000 --- a/src/adcp/types/generated_poc/trusted_match/available_package.py +++ /dev/null @@ -1,46 +0,0 @@ -# generated by datamodel-codegen: -# filename: trusted_match/available_package.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..core import catalog, format_id, package_format_snapshot, seller_agent_ref - - -class AvailablePackage(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - package_id: Annotated[str, Field(description='Unique identifier for the package')] - media_buy_id: Annotated[str, Field(description='Media buy that this package belongs to')] - seller_agent: Annotated[ - seller_agent_ref.SellerAgentReference, - Field( - description="The seller agent that owns this package. `agent_url` MUST match one of `authorized_agents[].url` in the publisher's adagents.json authoritative for every property this package may serve. Providers SHOULD validate at sync time and reject mismatches with `seller_not_authorized`. Cached alongside the package and used for offer attribution, per-seller observability, and dispute resolution — not for request-time filtering." - ), - ] - format_ids: Annotated[ - list[format_id.FormatReferenceStructuredObject] | None, - Field( - deprecated=True, - description='Deprecated in AdCP 3.2; removed in AdCP 4.0. Legacy named-format identifiers eligible for this package. Use canonical `format_options`.', - ), - ] = None - format_options: Annotated[ - list[package_format_snapshot.PackageFormatSnapshot] | None, - Field( - description='Immutable seller-resolved PackageFormatSnapshots eligible for this package. A sender projects this field only when the Trusted Match transport profile supports the highest AdCP release version of every field in every snapshot. It MUST NOT strip an unsupported selector or digest field from a snapshot; for an older or version-unknown provider, omit the entire contract-bearing snapshot and do not claim production-path evidence from a legacy projection.', - min_length=1, - ), - ] = None - catalogs: Annotated[ - list[catalog.Catalog] | None, - Field( - description="The buyer's catalogs attached to this package, with selectors (ids, gtins, tags, category, query) scoping which items are in play. References synced catalogs by catalog_id. The provider resolves items from its cached copy." - ), - ] = None diff --git a/src/adcp/types/generated_poc/trusted_match/context_match_request.py b/src/adcp/types/generated_poc/trusted_match/context_match_request.py deleted file mode 100644 index 51eb72cc0..000000000 --- a/src/adcp/types/generated_poc/trusted_match/context_match_request.py +++ /dev/null @@ -1,278 +0,0 @@ -# generated by datamodel-codegen: -# filename: trusted_match/context_match_request.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal -from uuid import UUID - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarStr -from pydantic import AnyUrl, ConfigDict, Field - -from ..content_standards import artifact as artifact_1 -from ..core import property_id as property_id_1 -from ..enums import metro_system -from ..enums import property_type as property_type_1 - - -class Type(StrEnum): - url = 'url' - url_hash = 'url_hash' - eidr = 'eidr' - gracenote = 'gracenote' - isrc = 'isrc' - gtin = 'gtin' - rss_guid = 'rss_guid' - isbn = 'isbn' - custom = 'custom' - - -class ArtifactRef(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Annotated[ - Type, - Field( - description="Identifier type. 'url' for web pages, 'url_hash' for URL-addressable content the publisher prefers not to share directly (buyer matches against pre-crawled index), 'eidr' for film/TV (EIDR DOI), 'gracenote' for music/TV (Gracenote TMS ID), 'isrc' for music recordings (International Standard Recording Code), 'gtin' for products (Global Trade Item Number — UPC, EAN, ISBN-13), 'rss_guid' for podcast episodes (RSS GUID), 'isbn' for books, 'custom' for publisher-defined identifiers." - ), - ] - value: Annotated[ - str, - Field( - description="The identifier value. For 'url': the canonical content URL — MUST NOT contain user-specific path segments, query parameters, or fragments; use 'url_hash' when the publisher prefers not to reveal the URL. For 'url_hash': Blake3 hash of the canonicalized URL, base64-encoded (canonicalization: strip scheme, strip www./m./amp. prefixes, lowercase, strip trailing slash, strip query params and fragments). For 'eidr': the EIDR DOI (e.g., '10.5240/xxxx'). For 'gracenote': the Gracenote TMS ID (e.g., 'SH032541890000'). For 'isrc': the ISRC code (e.g., 'USRC17607839'). For 'gtin': the GTIN (e.g., '00012345678905'). For 'rss_guid': the episode GUID from the RSS feed. For 'isbn': the ISBN (e.g., '978-0-123456-78-9'). For 'custom': a publisher-defined identifier." - ), - ] - - -class Sentiment(StrEnum): - positive = 'positive' - negative = 'negative' - neutral = 'neutral' - mixed = 'mixed' - - -class Keyword(ScalarStr): - __slots__ = () - _constraints = {'max_length': 100} - - -class ContextSignals(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - topics: Annotated[ - list[str] | None, - Field( - description="Content topic identifiers. Use IAB Content Taxonomy 3.0 IDs (e.g., '632' for Food & Drink) when taxonomy_id is 7, or bounded human-readable category labels (e.g., 'cooking.pasta') for custom taxonomies. For non-public content attributable to a single user or session, publishers MUST use standardized taxonomy identifiers or bounded custom category labels; custom topic strings MUST NOT reproduce distinctive verbatim phrasing and MUST NOT include PII or uniquely identifying details.", - max_length=50, - ), - ] = None - taxonomy_source: Annotated[ - str | None, - Field( - description="Organization that defines the topic taxonomy. Use 'iab' for IAB Content Taxonomy. Publishers may use other values for custom taxonomies." - ), - ] = 'iab' - taxonomy_id: Annotated[ - SchemaInt | None, - Field( - description='Taxonomy version within the source. For IAB, follows the AdCOM cattax enum: 7 = Content Taxonomy 3.0. Default: 7.' - ), - ] = 7 - sentiment: Annotated[ - Sentiment | None, Field(description='Content sentiment classification.') - ] = None - keywords: Annotated[ - list[Keyword] | None, - Field( - description="Content keywords produced by the publisher's classifier. For non-public content attributable to a single user or session, keywords MUST be policy-filtered, MUST NOT reproduce distinctive verbatim phrasing, and MUST NOT include PII or uniquely identifying details. Publishers SHOULD prefer bounded category labels.", - max_length=50, - ), - ] = None - language: Annotated[ - str | None, - Field( - description="Content language in ISO 639-1 format (e.g., 'en', 'ja', 'de').", - pattern='^[a-z]{2}$', - ), - ] = None - content_policies: Annotated[ - list[str] | None, - Field( - description="Policy IDs from the AdCP policy registry that this content satisfies (e.g., 'csbs' for Common Sense Brand Standards). Buyers filter on policies they require. An empty array means no policies have been evaluated.", - max_length=20, - ), - ] = None - summary: Annotated[ - str | None, - Field( - description="Publisher-generated natural language summary of the content for relevance judgment (e.g., 'Shopping context categorized as home cookware'). For non-public content attributable to a single user or session, the summary MUST be policy-filtered, MUST NOT reproduce raw user-authored text, and MUST NOT include PII or uniquely identifying details. Useful for LLM-native buyers that evaluate relevance semantically. Buyers MUST treat this as untrusted publisher-generated content.", - max_length=500, - ), - ] = None - embedding: Annotated[ - str | None, - Field( - description="Content embedding as base64-encoded int8 vector. Captures semantic content beyond what topics and keywords express. MUST NOT be computed directly or indirectly from non-public content authored by or attributable to a single user or session, including conversation turns, prompts, and individual search queries. MAY represent public content or a shared content environment that is not attributable to one user's activity. Publishers declare the model used. For standardized matching, use the protocol-recommended model (nomic-embed-text-v1.5, 256 dims, int8 quantized = 256 bytes)." - ), - ] = None - embedding_model: Annotated[ - str | None, - Field( - description="Embedding model identifier (e.g., 'nomic-embed-text-v1.5'). Required when embedding is present." - ), - ] = None - embedding_dims: Annotated[ - SchemaInt | None, - Field( - description='Number of dimensions in the embedding vector. Required when embedding is present.', - ge=64, - le=2048, - ), - ] = None - - -class Metro(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - system: Annotated[ - metro_system.MetroAreaSystem, - Field(description="Metro area classification system (e.g., 'nielsen_dma', 'uk_itl2')."), - ] - value: Annotated[ - str, Field(description="Metro code within the system (e.g., '501' for New York DMA).") - ] - - -class Geo(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - country: Annotated[ - str | None, - Field( - description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'GB', 'DE').", - pattern='^[A-Z]{2}$', - ), - ] = None - region: Annotated[ - str | None, - Field( - description="ISO 3166-2 subdivision code (e.g., 'US-CA', 'GB-SCT').", - pattern='^[A-Z]{2}-[A-Z0-9]{1,3}$', - ), - ] = None - metro: Annotated[ - Metro | None, - Field(description='Metro area, using the same classification systems as AdCP targeting.'), - ] = None - - -class ContextMatchRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - field_schema: Annotated[ - AnyUrl | None, - Field( - alias='$schema', description='Optional schema URI for validation. Ignored at runtime.' - ), - ] = None - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin. Inlined here (rather than via core/version-envelope.json allOf) so this schema can keep `additionalProperties: false` — the privacy boundary on this endpoint is contract-bearing.', - pattern='^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?)?$', - ), - ] = None - adcp_major_version: Annotated[ - SchemaInt | None, - Field( - deprecated=True, - description='DEPRECATED in favor of adcp_version. Removed in 4.0. Inlined alongside adcp_version to preserve strict-mode on this endpoint.', - ge=1, - le=99, - ), - ] = None - type: Annotated[ - Literal['context_match_request'], - Field(description='Message type discriminator for deserialization.'), - ] = 'context_match_request' - protocol_version: Annotated[ - str | None, - Field( - description='TMP protocol version. Allows receivers to handle semantic differences across versions.' - ), - ] = '1.0' - request_id: Annotated[ - str, - Field( - description='Unique request identifier. MUST NOT correlate with any identity match request_id.' - ), - ] - property_rid: Annotated[ - UUID, - Field( - description='Property catalog UUID (UUID v7). Globally unique, stable identifier assigned by the property catalog. The primary key for TMP matching and property list targeting.' - ), - ] - property_id: Annotated[ - property_id_1.PropertyId | None, - Field( - description="Publisher's human-readable property slug (e.g., 'cnn_homepage'). Optional when property_rid is present. Useful for logging and debugging." - ), - ] = None - property_type: Annotated[ - property_type_1.PropertyType, Field(description='Type of the publisher property') - ] - placement_id: Annotated[ - str, - Field( - description="Placement identifier from the publisher's placement registry in adagents.json. Identifies where on the property this ad opportunity exists. One placement per request." - ), - ] - seller_agent_url: Annotated[ - AnyUrl, - Field( - description="API endpoint URL of the seller agent issuing this request. The provider uses this to resolve the active package set it has synced for this seller; when `package_ids` is omitted, evaluation occurs against that full set. If `seller_agent_url` does not match any seller the provider has synced packages for, the provider MUST return an empty offer set — it MUST NOT fall back to another seller's active set. The value identifies the asking seller, is identical for every user on a given placement, and carries no user identity, so it neither varies the request per user nor weakens the context/identity decorrelation boundary. Compared using the AdCP URL canonicalization rules, not byte-equality — see docs/reference/url-canonicalization. Consistent with `seller_agent_url` on the identity match request, `seller_agent.agent_url` on `AvailablePackage`, and `agent_url` in `adagents.json`." - ), - ] - artifact: Annotated[ - artifact_1.Artifact | None, - Field( - description='Full content artifact adjacent to this ad opportunity. Same schema used for content standards evaluation. The publisher sends the artifact when they want the buyer to evaluate the full content. Contractual protections govern buyer use. TEE deployment upgrades contractual trust to cryptographic verification. Because the router fans out to multiple buyer agents, publishers MUST NOT include bearer tokens, service-account credentials, or signed URLs in this artifact. Routers MUST remove every asset `access` object and remove or replace every credential-bearing asset `url` before forwarding; only public asset URLs that recipients can resolve independently may remain.' - ), - ] = None - artifact_refs: Annotated[ - list[ArtifactRef] | None, - Field( - description='Public content references adjacent to this ad opportunity. Each artifact identifies content via a public identifier the buyer can resolve independently — no private registry sync required.', - max_length=20, - min_length=1, - ), - ] = None - geo: Annotated[ - Geo | None, - Field( - description='Coarse geographic location of the viewer. Publisher controls granularity — country is sufficient for regulatory compliance and volume filtering, region or metro helps with campaign targeting and valuation. Coarsened to prevent user identification: no postcode, no coordinates. All fields optional.' - ), - ] = None - context_signals: Annotated[ - ContextSignals | None, - Field( - description="Pre-computed classifier outputs for the content environment. Use when the publisher wants to provide privacy-reduced context without sharing content or public references. Can supplement artifact_refs or replace them entirely. Ephemeral content that many users encounter (a trending query, a syndicated segment) is shared content; one user's turn or query is not. For non-public content attributable to a single user or session, only the field-specific privacy-reduced outputs permitted below may be sent. Raw content MUST NOT be included. The publisher is the classifier and privacy boundary." - ), - ] = None - package_ids: Annotated[ - list[str] | None, - Field( - description='Restrict evaluation to specific packages. When omitted, the provider evaluates all eligible packages for this placement (the common case). MUST NOT vary by user — the same package_ids must be sent for every user on a given placement. User-dependent filtering leaks identity into the context path.', - max_length=500, - min_length=1, - ), - ] = None diff --git a/src/adcp/types/generated_poc/trusted_match/context_match_response.py b/src/adcp/types/generated_poc/trusted_match/context_match_response.py deleted file mode 100644 index f9e878915..000000000 --- a/src/adcp/types/generated_poc/trusted_match/context_match_response.py +++ /dev/null @@ -1,86 +0,0 @@ -# generated by datamodel-codegen: -# filename: trusted_match/context_match_response.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, RootModel, StringConstraints - -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from . import offer - - -class Signals(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - segments: Annotated[ - list[str] | None, - Field(description='Contextual segment identifiers combined across provider responses.'), - ] = None - - -class TargetingKv(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - key: Annotated[ - str, - Field( - description='Provider-local targeting key. Publishers resolve this together with provider_id; it is not a globally unique or publisher-owned ad-server key.' - ), - ] - value: Annotated[str, Field(description='Targeting value preserved unchanged by the router.')] - - -class TargetingKvs(RootModel[list[TargetingKv]]): - root: Annotated[ - list[TargetingKv], - Field( - description='Provider-local key-value pairs for ad-server targeting. The router preserves each pair unchanged and attributes it by provider_id; the publisher maps the tuple (provider_id, key) to a local ad-server destination.' - ), - ] - - -class SignalsByProvider(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - targeting_kvs: TargetingKvs - - -class ContextMatchResponseRouterPublisher(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Literal['context_match_response'], - Field(description='Message type discriminator for deserialization.'), - ] = 'context_match_response' - request_id: Annotated[ - str, Field(description='Echoed request identifier from the context match request.') - ] - offers: Annotated[ - list[offer.Offer], - Field( - description='Offers collected across the provider fan-out, one per activated package. An empty array means no packages matched. For simple activation, each offer has just package_id. For richer responses, offers include brand, price, summary, and creative manifest.' - ), - ] - signals: Annotated[ - Signals | None, - Field( - description='Merged non-keyed response-level signals. Provider-local targeting pairs do not pass through this object; the router emits them only in signals_by_provider.' - ), - ] = None - signals_by_provider: Annotated[ - dict[Annotated[str, StringConstraints(pattern=r'^[A-Za-z0-9_]+$', min_length=1, max_length=64)], SignalsByProvider] - | None, - Field( - description="Router-authored map of provider targeting pairs, keyed by the publisher-assigned provider_id from provider registration. For every provider response containing a non-empty signals.targeting_kvs list, the router copies the complete list unchanged into that provider's bucket. The router derives the map key from its registration and MUST ignore or reject provider-supplied signals_by_provider data. A provider with no targeting pairs is omitted. Publishers resolve each (provider_id, key) tuple to a local ad-server destination and drop tuples that have no local mapping.", - min_length=1, - ), - ] = None diff --git a/src/adcp/types/generated_poc/trusted_match/error.py b/src/adcp/types/generated_poc/trusted_match/error.py deleted file mode 100644 index 987255417..000000000 --- a/src/adcp/types/generated_poc/trusted_match/error.py +++ /dev/null @@ -1,42 +0,0 @@ -# generated by datamodel-codegen: -# filename: trusted_match/error.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - - -class Code(StrEnum): - invalid_request = 'invalid_request' - unknown_package = 'unknown_package' - seller_not_authorized = 'seller_not_authorized' - rate_limited = 'rate_limited' - timeout = 'timeout' - internal_error = 'internal_error' - provider_unavailable = 'provider_unavailable' - - -class TmpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Literal['error'], Field(description='Message type discriminator for deserialization.') - ] = 'error' - request_id: Annotated[ - str, Field(description='Echoed request identifier from the original request') - ] - code: Annotated[ - Code, - Field( - description="Machine-readable error code. `seller_not_authorized` is returned by providers at sync time when an AvailablePackage declares a `seller_agent.agent_url` that is not present in the `authorized_agents` list of the publisher's adagents.json for a property the package claims to serve." - ), - ] - message: Annotated[ - str | None, Field(description='Human-readable error description for debugging') - ] = None diff --git a/src/adcp/types/generated_poc/trusted_match/identity_match_request.py b/src/adcp/types/generated_poc/trusted_match/identity_match_request.py deleted file mode 100644 index c57b46583..000000000 --- a/src/adcp/types/generated_poc/trusted_match/identity_match_request.py +++ /dev/null @@ -1,230 +0,0 @@ -# generated by datamodel-codegen: -# filename: trusted_match/identity_match_request.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, StrictBool - -from ..core import brand_ref -from ..enums import attestation_claim -from ..enums import uid_type as uid_type_1 - - -class VerificationLevel(StrEnum): - orb = 'orb' - device = 'device' - document = 'document' - - -class Consent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - gdpr: Annotated[ - StrictBool | None, Field(description='Whether GDPR applies to this request.') - ] = None - tcf_consent: Annotated[ - str | None, Field(description='IAB TCF v2.2 consent string. Present when gdpr is true.') - ] = None - gpp: Annotated[str | None, Field(description='IAB Global Privacy Platform string.')] = None - us_privacy: Annotated[ - str | None, - Field( - deprecated=True, - description='US Privacy string (CCPA). Deprecated in favor of GPP but still widely used.', - ), - ] = None - - -class SealedCredential(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - audience_kid: Annotated[ - str, - Field( - description='Key id identifying the recipient (network / relying party) whose HPKE private key opens `payload`.', - max_length=128, - ), - ] - payload: Annotated[ - str, - Field( - description='HPKE-sealed attestation in the TMPX envelope format: `kid.base64url_nopad(ciphertext)` — unpadded base64url per RFC 4648 §5. Opaque pass-through for the publisher.', - max_length=8192, - ), - ] - - -class Attestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - issuer: Annotated[ - brand_ref.BrandReference, - Field( - description='Attestation authority / identity issuer, referenced as a vendor BrandRef (e.g. {"domain": "world.org"}) — the same vendor-reference shape AdCP uses for measurement and signals vendors. The issuer\'s canonical domain is the anchor; brand.json hosting is optional. `scheme` selects the verifier version within the issuer.' - ), - ] - scheme: Annotated[ - str, - Field( - description='Proof scheme and version, e.g. "world_id_v4". Selects how `proof` is verified.' - ), - ] - relying_party_id: Annotated[ - str | None, - Field( - description="Relying-party id the proof was minted for. The receiver checks this against the relying_party_id's published owner (brand.json `identity_relying_parties[]`) so a forwarded proof cannot be replayed under a different owner." - ), - ] = None - action: Annotated[ - str | None, - Field( - description='Issuer action/scope the proof was bound to (e.g. "humanity-check-for-ads").' - ), - ] = None - claims: Annotated[ - list[attestation_claim.AttestationClaim], - Field( - description='Claims this attestation establishes. Closed, issuer-agnostic set.', - max_length=16, - min_length=1, - ), - ] - verification_level: Annotated[ - VerificationLevel | None, - Field( - description='Credential strength (e.g. World ID Orb = biometric unique-human; Device = weaker; Document = passport/NFC).' - ), - ] = None - signal_binding: Annotated[ - str | None, - Field( - description='Hash of the signal the proof commits to. The receiver MUST verify it matches the context the receiver expects (e.g. a buyer-issued nonce or the request_id) and that the attestation is within its freshness window; this, plus nullifier-reuse tracking, is the replay defense.' - ), - ] = None - proof: Annotated[ - dict[str, Any], - Field( - description="Scheme-specific verifiable proof material. Opaque to this schema; validated by the scheme's verifier. MUST carry only scheme-defined verification material — never page context or additional user identifiers." - ), - ] - expires_at: Annotated[ - AwareDatetime | None, - Field(description='Attestation validity horizon. Receivers MUST reject when past.'), - ] = None - - -class Identity(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - user_token: Annotated[ - str, - Field( - description='Opaque token from an identity provider (ID5, LiveRamp, UID2) or publisher-generated. Buyer may map to internal identity graph but cannot reverse to PII.' - ), - ] - uid_type: Annotated[ - uid_type_1.UidType, - Field( - description='Type of the user identifier. Tells the buyer which identity graph to resolve against, avoiding trial-and-error matching.' - ), - ] - attestation: Annotated[ - Attestation | None, - Field( - description="Optional verifiable proof ABOUT this identity (e.g. World ID proof-of-personhood and/or age). The receiver MUST verify it (see conformance) and MUST treat an absent-or-unverifiable attestation as 'no attestation' — never as an asserted-true claim. Issuer-agnostic: World ID is the first scheme; mDL / VC-style issuers use the same shape. Receivers MUST bound attestation size to prevent DoS." - ), - ] = None - - -class IdentityMatchRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - field_schema: Annotated[ - AnyUrl | None, - Field( - alias='$schema', description='Optional schema URI for validation. Ignored at runtime.' - ), - ] = None - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin. Inlined here (rather than via core/version-envelope.json allOf) so this schema can keep `additionalProperties: false` — the privacy boundary on this endpoint is contract-bearing.', - pattern='^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?)?$', - ), - ] = None - adcp_major_version: Annotated[ - SchemaInt | None, - Field( - deprecated=True, - description='DEPRECATED in favor of adcp_version. Removed in 4.0. Inlined alongside adcp_version to preserve strict-mode on this endpoint.', - ge=1, - le=99, - ), - ] = None - type: Annotated[ - Literal['identity_match_request'], - Field(description='Message type discriminator for deserialization.'), - ] = 'identity_match_request' - protocol_version: Annotated[ - str | None, - Field( - description='TMP protocol version. Allows receivers to handle semantic differences across versions.' - ), - ] = '1.0' - request_id: Annotated[ - str, - Field( - description='Unique request identifier. MUST NOT correlate with any context match request_id.' - ), - ] - seller_agent_url: Annotated[ - AnyUrl, - Field( - description="API endpoint URL of the seller agent issuing this request. The buyer's identity-match service uses this to resolve the active package set it has registered for this seller; when `package_ids` is omitted, evaluation occurs against that full set. If `seller_agent_url` does not match any seller for which the buyer has registered active packages, the buyer MUST return an empty `eligible_package_ids` set — it MUST NOT fall back to evaluating against another seller's active set. Compared using the AdCP URL canonicalization rules, not byte-equality — see docs/reference/url-canonicalization. Consistent with `seller_agent.agent_url` on `AvailablePackage` and `agent_url` in `adagents.json`." - ), - ] - identities: Annotated[ - list[Identity], - Field( - description='Identity tokens for the user, each tagged with its type. Publishers SHOULD include every token they have available — the buyer resolves on whichever graph matches. Entry order is not semantically significant; buyers use their own preference order when multiple entries resolve. Duplicate `(uid_type, user_token)` pairs MUST NOT appear; routers MAY reject or dedupe. `maxItems: 3` matches the TMPX plaintext budget (~120 bytes after HPKE overhead fits three 32-byte tokens); exceeding it forces buyer-side truncation.', - max_length=3, - min_length=1, - ), - ] - consent: Annotated[ - Consent | None, - Field( - description='Privacy consent signals. Buyers in regulated jurisdictions MUST NOT process the user token without consent information.' - ), - ] = None - package_ids: Annotated[ - list[str] | None, - Field( - description="Optional. When omitted, the buyer evaluates eligibility against the full set of active packages it has registered for `seller_agent_url`. When provided, the composition of `package_ids` MUST be statistically independent of the current placement — sending only the page-specific subset would let the buyer correlate Identity Match with Context Match by comparing package sets. Two acceptable modes: (a) **all-active** — include every active package this buyer has at this publisher; (b) **fuzzed** — include a random sample of active packages, optionally padded with synthetic non-existent IDs, drawn from a distribution that does not depend on the current placement. The buyer's silent-drop behavior on unknown IDs (specified below) is what makes synthetic-ID padding safe — they do not affect the response shape and cannot leak registry membership. When both `seller_agent_url` and `package_ids` are present, the buyer evaluates against the intersection of its registered active set and `package_ids`; IDs in `package_ids` that the buyer has not registered for this seller MUST be silently ignored (not surfaced as errors) to avoid leaking registry membership back to the publisher.", - min_length=1, - ), - ] = None - country: Annotated[ - str | None, - Field( - description='ISO 3166-1 alpha-2 country code. Routing directive for the TMP Router — used to select the correct regional provider. The router MUST strip this field before forwarding the request to the buyer agent. Not an identity signal.', - pattern='^[A-Z]{2}$', - ), - ] = None - sealed_credentials: Annotated[ - list[SealedCredential] | None, - Field( - description='Optional HPKE-sealed credentials addressed to specific audiences — the network-as-RP ("issuer-as-RP"/Mechanism B) carrier. Each payload is opaque to the publisher, who relays it untouched; the inner plaintext is an `attestation` (see identities[].attestation) scoped to the audience\'s relying party. Reuses the TMPX envelope format. Router handling (normative — see docs/trusted-match/specification.mdx): the router forwards each entry only to the provider that owns its `audience_kid` (not broadcast), folds `sealed_credentials` into the per-provider re-signature canonical bytes so an injected/swapped blob breaks the signature, and includes a `sealed_credentials_hash` in the dedup cache key. Receivers decrypt only entries whose `audience_kid` they hold a key for and ignore the rest. Receivers MUST bound count and size to prevent DoS amplification.', - max_length=8, - ), - ] = None diff --git a/src/adcp/types/generated_poc/trusted_match/identity_match_response.py b/src/adcp/types/generated_poc/trusted_match/identity_match_response.py deleted file mode 100644 index bd5eb5e1a..000000000 --- a/src/adcp/types/generated_poc/trusted_match/identity_match_response.py +++ /dev/null @@ -1,102 +0,0 @@ -# generated by datamodel-codegen: -# filename: trusted_match/identity_match_response.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -import re - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, StringConstraints, model_validator - -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope - -_PROVIDER_ID_PATTERN = re.compile(r'^[A-Za-z0-9_]{1,64}$') -from . import tmpx_chunk - - - -class TmpxMacro(AdCPBaseModel): - """Deprecated 3.1.8 TMPX macro/value compatibility model.""" - - model_config = ConfigDict( - extra='forbid', - ) - name: Annotated[ - str, - Field(max_length=64, min_length=1, pattern='^[A-Z][A-Z0-9_]*$'), - ] - value: Annotated[str, Field(max_length=1024, min_length=1)] - - -class TmpxProviders(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - chunks: Annotated[ - list[tmpx_chunk.TmpxChunk], - Field( - description="Ordered TMPX chunks for this provider. Each entry is a `{slot_id, value}` pair copied verbatim from the provider's `tmpx_chunks`. Ordered-prefix invariant: the sequence of `slot_id`s MUST equal an ordered prefix of the provider's registered `tmpx_slots` — publishers MAY reject responses whose slot_ids or ordering diverge. Cap of 2 chunks in v1; the cap MAY rise without a shape change.", - max_length=2, - min_length=1, - ), - ] - - -class IdentityMatchResponseRouterPublisher(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Literal['identity_match_response'], - Field(description='Message type discriminator for deserialization.'), - ] = 'identity_match_response' - request_id: Annotated[ - str, Field(description='Echoed request identifier from the identity match request') - ] - eligible_package_ids: Annotated[ - list[str], - Field( - description='Package IDs the user is eligible for. Packages not listed are ineligible.' - ), - ] - serve_window_sec: Annotated[ - SchemaInt, - Field( - description="Per-package single-shot fcap window, in seconds. After serving the user one impression on each eligible package within this window, the publisher MUST re-query Identity Match before serving from those packages again. This is NOT a router response cache TTL — it is a buyer-asserted serve throttle. Multi-impression frequency caps are handled separately by the buyer's impression tracker, which writes cap-fire events to the IdentityMatch cap-state store at the boundary regardless of this window. Maximum 300 — longer windows reduce IdentityMatch load but coarsen fcap granularity below what most campaigns require.", - ge=1, - le=300, - ), - ] - tmpx: Annotated[ - str | None, - Field( - deprecated=True, - description='DEPRECATED in favor of tmpx_providers. Routers MAY continue to populate this field for back-compat with consumers that only know the single-token shape; when both fields are present, tmpx_providers is authoritative. Single HPKE-encrypted exposure token containing the resolved user identity tokens. Wire format: kid.base64url_nopad(ciphertext) — unpadded base64url per RFC 4648 section 5 (no = characters). Publishers MUST treat this value as opaque pass-through data. Removed in 4.0.', - ), - ] = None - tmpx_providers: Annotated[ - dict[Annotated[str, StringConstraints(pattern=r'^[A-Za-z0-9_]+$', min_length=1, max_length=64)], TmpxProviders] | None, - Field( - description="Router-populated: ordered TMPX chunk/value pairs grouped by the originating identity provider's `provider_id`. Each entry's `chunks[]` is a copy of the provider's emitted `tmpx_chunks` list, in the same order. Each chunk carries a provider-local `slot_id` (from the provider's registered `tmpx_slots`) and an opaque URL-safe `value`; the publisher's deployment configuration (see publisher-tmpx-config.json) resolves each `(provider_id, slot_id)` pair to the ad-server macro name, targeting key, VAST substitution, or play-log field for that surface. The protocol carries values and attribution only. Required by router conformance when any identity provider emitted TMPX in this request; collapsing per-provider tokens into a single string loses attribution and breaks per-provider impression accounting. Map keys MUST match the provider_id charset registered in provider-registration.json (enforced by `propertyNames`). Publishers MUST NOT parse, decode, or transform any chunk's `value` — each is an opaque URL-safe wire string substituted verbatim into the mapped destination." - ), - ] = None - - @model_validator(mode='after') - def _validate_tmpx_provider_ids(self) -> IdentityMatchResponseRouterPublisher: - if self.tmpx_providers is None: - return self - invalid = [ - provider_id - for provider_id in self.tmpx_providers - if not _PROVIDER_ID_PATTERN.fullmatch(provider_id) - ] - if invalid: - raise ValueError('tmpx_providers keys must be valid provider_id values') - return self - - -IdentityMatchResponse = IdentityMatchResponseRouterPublisher diff --git a/src/adcp/types/generated_poc/trusted_match/offer.py b/src/adcp/types/generated_poc/trusted_match/offer.py deleted file mode 100644 index 0cd6257cb..000000000 --- a/src/adcp/types/generated_poc/trusted_match/offer.py +++ /dev/null @@ -1,58 +0,0 @@ -# generated by datamodel-codegen: -# filename: trusted_match/offer.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - -from ..core import brand_ref -from ..core import creative_manifest as creative_manifest_1 -from ..core import seller_agent_ref -from . import offer_price - - -class Offer(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - package_id: Annotated[str, Field(description='Package identifier from the media buy.')] - seller_agent: Annotated[ - seller_agent_ref.SellerAgentReference | None, - Field( - description="Optional echo of the package's seller agent from sync-time metadata. Provided for publisher-side observability so log pipelines can attribute offers to sellers without round-tripping to the media-buy store. Non-authoritative: the binding on the cached AvailablePackage is source of truth. When omitted, the router MAY stamp this field from its cached package→seller map." - ), - ] = None - brand: Annotated[ - brand_ref.BrandReference | None, - Field( - description='Brand for this offer. Required when the product allows dynamic brands (brand selected at match time rather than fixed on the package). For single-brand packages, the brand is already known from the media buy.' - ), - ] = None - price: Annotated[ - offer_price.OfferPrice | None, - Field( - description='Price for this offer. Only present when the product supports variable pricing. For fixed-price packages, price is already set on the media buy.' - ), - ] = None - summary: Annotated[ - str | None, - Field( - description="Buyer-generated description of the offer, for the publisher to judge relevance. E.g., '50% off Goldenfield mayo — recipe integration'. The publisher (or their AI assistant) uses this to decide whether the offer fits the context." - ), - ] = None - creative_manifest: Annotated[ - creative_manifest_1.CreativeManifest | None, - Field( - description='Full creative details, inline. When present, the publisher has everything needed to render. Inline for small creatives (markdown, product card). For large creatives (VAST, video), the manifest references external assets via URLs.' - ), - ] = None - creative_data: Annotated[ - dict[str, str] | None, - Field( - description="Optional free-form string enhancements for dynamic creative rendering, such as sponsor labels and promotion codes. The map has no protocol-level key registry or required keys: meanings are package-local, receivers MUST ignore unknown keys, and a missing key MUST NOT make an otherwise renderable offer fail. Required renderable content belongs in creative_manifest assets, so creative_data may complement but never replace a manifest's required assets. This is not ad-server macro substitution or attribution tracking; tracker URLs belong in creative_manifest assets and per-user exposure tracking uses TMPX." - ), - ] = None diff --git a/src/adcp/types/generated_poc/trusted_match/offer_price.py b/src/adcp/types/generated_poc/trusted_match/offer_price.py deleted file mode 100644 index 174fc0e17..000000000 --- a/src/adcp/types/generated_poc/trusted_match/offer_price.py +++ /dev/null @@ -1,32 +0,0 @@ -# generated by datamodel-codegen: -# filename: trusted_match/offer_price.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StrictFloat - - -class Model(StrEnum): - cpm = 'cpm' - cpc = 'cpc' - cpcv = 'cpcv' - cpa = 'cpa' - flat = 'flat' - - -class OfferPrice(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - amount: Annotated[ - StrictFloat, Field(description='Price amount in the specified currency', ge=0.0) - ] - currency: Annotated[ - str | None, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$') - ] = 'USD' - model: Annotated[Model, Field(description='Pricing model for this offer')] diff --git a/src/adcp/types/generated_poc/trusted_match/provider_context_match_response.py b/src/adcp/types/generated_poc/trusted_match/provider_context_match_response.py deleted file mode 100644 index b120d6b25..000000000 --- a/src/adcp/types/generated_poc/trusted_match/provider_context_match_response.py +++ /dev/null @@ -1,81 +0,0 @@ -# generated by datamodel-codegen: -# filename: trusted_match/provider_context_match_response.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import AdCPBaseModel, SchemaInt -from pydantic import ConfigDict, Field, RootModel - -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from . import offer - - -class TargetingKv(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - key: Annotated[ - str, - Field( - description='Provider-local targeting key. It is not a globally unique or publisher-owned ad-server destination.' - ), - ] - value: Annotated[str, Field(description='Targeting value that the router preserves unchanged.')] - - -class TargetingKvs(RootModel[list[TargetingKv]]): - root: Annotated[ - list[TargetingKv], - Field( - description="Provider-local key-value pairs for ad-server targeting. The router preserves each pair unchanged under the emitting provider's registered provider_id." - ), - ] - - -class Signals(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - segments: Annotated[list[str] | None, Field(description='Contextual segment identifiers.')] = ( - None - ) - targeting_kvs: TargetingKvs | None = None - - -class ContextMatchResponseProviderRouter(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Literal['context_match_response'], - Field( - description='Message type discriminator for deserialization. Same const as the router→publisher variant so decoders can key off type before dispatching on hop shape.' - ), - ] = 'context_match_response' - request_id: Annotated[ - str, Field(description='Echoed request identifier from the context match request.') - ] - offers: Annotated[ - list[offer.Offer], - Field( - description='Offers from this provider, one per activated package. An empty array means no packages matched. For simple activation, each offer has just package_id. For richer responses, offers include brand, price, summary, and creative manifest.' - ), - ] - cache_ttl: Annotated[ - SchemaInt | None, - Field( - description="Optional override for the router's default 5-minute response cache TTL, in seconds. When present, the router MUST use this value instead of its default. Set to 0 to disable caching.", - ge=0, - le=86400, - ), - ] = None - signals: Annotated[ - Signals | None, - Field( - description="Provider-origin response-level signals. Targeting pairs use the provider's local key vocabulary; the router preserves and attributes them rather than passing them through as flattened publisher targeting." - ), - ] = None diff --git a/src/adcp/types/generated_poc/trusted_match/provider_identity_match_response.py b/src/adcp/types/generated_poc/trusted_match/provider_identity_match_response.py deleted file mode 100644 index 91d88bb28..000000000 --- a/src/adcp/types/generated_poc/trusted_match/provider_identity_match_response.py +++ /dev/null @@ -1,51 +0,0 @@ -# generated by datamodel-codegen: -# filename: trusted_match/provider_identity_match_response.json -# timestamp: 2026-10-03T16:00:16+00:00 - -from __future__ import annotations - -from typing import Annotated, Literal - -from adcp.types.base import SchemaInt -from pydantic import ConfigDict, Field - -from ..core.protocol_envelope import ProtocolEnvelope -from ..core.version_envelope import AdcpVersionEnvelope -from . import tmpx_chunk - - -class IdentityMatchResponseProviderRouter(AdcpVersionEnvelope, ProtocolEnvelope): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Literal['identity_match_response'], - Field( - description='Message type discriminator for deserialization. Same const as the router→publisher variant so publisher-facing decoders can key off type before dispatching on shape.' - ), - ] = 'identity_match_response' - request_id: Annotated[ - str, Field(description='Echoed request identifier from the identity match request.') - ] - eligible_package_ids: Annotated[ - list[str], - Field( - description='Package IDs the user is eligible for. Packages not listed are ineligible.' - ), - ] - serve_window_sec: Annotated[ - SchemaInt, - Field( - description="Per-package single-shot fcap window, in seconds. After serving the user one impression on each eligible package within this window, the publisher MUST re-query Identity Match before serving from those packages again. Not a router response cache TTL — it is a buyer-asserted serve throttle. Multi-impression frequency caps are handled separately by the buyer's impression tracker. Maximum 300.", - ge=1, - le=300, - ), - ] - tmpx_chunks: Annotated[ - list[tmpx_chunk.TmpxChunk] | None, - Field( - description="Ordered TMPX chunk/value pairs this identity agent mints. Each entry names the provider-local `slot_id` the value fills (from the provider's registered `tmpx_slots` in provider-registration.json). Ordered-prefix invariant: a response that carries fewer chunks than the provider registered MUST emit an ordered prefix of the registered `tmpx_slots` — routers MUST NOT reorder or fill gaps. The router MUST validate this contract before forwarding: if the provider has no `tmpx_slots` registration, or if the chunk `slot_id` sequence is not an exact non-empty ordered prefix of the registered list (duplicate, reordered, sparse, or unregistered slot IDs), the router MUST drop that provider's chunks atomically and MUST NOT forward them into `tmpx_providers`. Cap of 2 chunks in v1 aligned with the GAM macro-slot budget; the cap MAY rise without a shape change. Omitted when this provider mints no TMPX (e.g. no eligible packages).", - max_length=2, - min_length=1, - ), - ] = None diff --git a/src/adcp/types/generated_poc/trusted_match/provider_registration.py b/src/adcp/types/generated_poc/trusted_match/provider_registration.py deleted file mode 100644 index 67eafbe5f..000000000 --- a/src/adcp/types/generated_poc/trusted_match/provider_registration.py +++ /dev/null @@ -1,239 +0,0 @@ -# generated by datamodel-codegen: -# filename: trusted_match/provider_registration.json -# timestamp: 2026-10-04T00:59:59+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Literal -from uuid import UUID - -from adcp.types.base import AdCPBaseModel, SchemaInt -from adcp.types._scalar import ScalarStr -from pydantic import AnyUrl, ConfigDict, Field, StrictBool, field_validator, model_validator - -from ..enums import uid_type - - -class Country(ScalarStr): - __slots__ = () - _constraints = {'pattern': '^[A-Z]{2}$'} - - - -class TmpxMacro(ScalarStr): - """Deprecated 3.1.8 registered macro-name compatibility model.""" - - __slots__ = () - _constraints = {'max_length': 64, 'min_length': 1, 'pattern': '^[A-Z][A-Z0-9_]*$'} - - -class TmpxSlot(ScalarStr): - __slots__ = () - _constraints = {'max_length': 64, 'min_length': 1, 'pattern': '^[a-zA-Z][a-zA-Z0-9_]*$'} - - -class Status(StrEnum): - active = 'active' - inactive = 'inactive' - draining = 'draining' - - -class TmpProviderRegistration1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - provider_id: Annotated[ - str, - Field( - description="Stable identifier for this provider registration. Used in logs, metrics, cache keys, as the key in `signals_by_provider` on Context Match responses and `targeting_kv_mapping` in publisher configuration, and as the key in `tmpx_providers` on Identity Match responses and `tmpx_macro_mapping` in publisher configuration. Publishers assign this — it is not the provider's agent_url. Charset is constrained to a safe alphanumeric/underscore set so the value can appear in operational surfaces (logs, metrics, dashboards) without quoting. See publisher-targeting-kv-config.json and publisher-tmpx-config.json.", - max_length=64, - min_length=1, - pattern='^[A-Za-z0-9_]+$', - ), - ] - endpoint: Annotated[ - AnyUrl, - Field( - description='Base URL the router calls. The router appends /context for Context Match and /identity for Identity Match. MUST be HTTPS in production, validated against the canonical reserved IPv4 and IPv6 ranges, with the TCP connection pinned to the validated IP (DNS re-resolution alone is insufficient against rebinding). Publishers comparing two provider registrations for the same `endpoint` MUST canonicalize both per the AdCP URL canonicalization rules; two registrations differing only in case, default port, or path-slash collapsing are the same provider. See docs/trusted-match/specification#provider-registration-security, docs/building/implementation/security#webhook-url-validation-ssrf, and docs/reference/url-canonicalization.' - ), - ] - context_match: Annotated[ - Literal[True], Field(description='Provider handles Context Match requests (POST /context).') - ] - identity_match: Annotated[ - StrictBool | None, - Field(description='Provider handles Identity Match requests (POST /identity).'), - ] = None - countries: Annotated[ - list[Country] | None, - Field( - description="ISO 3166-1 alpha-2 country codes this provider serves. The router filters Identity Match providers by the request's country field. MUST be present and non-empty when identity_match is true.", - min_length=1, - ), - ] = None - uid_types: Annotated[ - list[uid_type.UidType] | None, - Field( - description="Identity types this provider can resolve. The router selects Identity Match providers whose uid_types overlaps with any uid_type in the request's identities array. MUST be present and non-empty when identity_match is true.", - min_length=1, - ), - ] = None - properties: Annotated[ - list[UUID] | None, - Field( - description='Property RIDs (UUID v7) this provider serves. When present, the router only sends requests from these properties to this provider. When absent, the provider serves all properties.', - min_length=1, - ), - ] = None - timeout_ms: Annotated[ - SchemaInt | None, - Field( - description="Per-provider timeout in milliseconds. The router skips this provider if it does not respond within this budget. Must be less than or equal to the router's overall latency_budget_ms. The router may further reduce this based on adaptive timeout allocation.", - ge=5, - le=5000, - ), - ] = 50 - priority: Annotated[ - SchemaInt | None, - Field( - description='Provider ordering for Context Match offer conflict resolution. Lower values have higher priority. When two providers return offers for the same package_id (a configuration error), the router keeps the offer from the higher-priority provider; equal priorities are broken by first response received. Identity Match eligibility remains a responder-scoped union because silent omission is not a negative vote. Also used for adaptive timeout allocation — higher-priority providers receive a larger share of the latency budget.', - ge=0, - ), - ] = 0 - tmpx_slots: Annotated[ - list[TmpxSlot] | None, - Field( - description='Stable provider-local slot identifiers for the ordered TMPX chunks this provider mints. Slot IDs are opaque provider-namespaced tokens (e.g. `["primary","secondary"]`), NOT ad-server macro names — publishers map `(provider_id, slot_id)` → local destination via `tmpx_macro_mapping` in publisher-tmpx-config.json, so the destination namespace stays publisher-owned and the router never accepts a destination name from an untrusted provider. Distinct providers MAY reuse the same slot_id without collision because publisher lookup is keyed on `(provider_id, slot_id)`. Publishers use this list at startup to validate `tmpx_macro_mapping` covers every slot the provider mints and to detect config drift when the provider\'s slot contract changes. Ordering carries the ordered-prefix invariant: a provider that emits fewer chunks than it registered MUST emit an ordered prefix of this list — chunks map to slots in registration order and MUST NOT be shifted, sparse, or reordered. Cap of 2 slots in v1 aligned with the GAM macro-slot budget; the cap MAY rise without a shape change. A provider that emits TMPX (populates `tmpx_chunks` on its identity-match response) MUST register this list; a provider that does not emit TMPX omits it. Schema cannot enforce that predicate because "emits TMPX" is not schema-visible.', - max_length=2, - min_length=1, - ), - ] = None - status: Annotated[ - Status | None, - Field( - description='Provider lifecycle status. Active providers receive requests. Inactive providers are skipped entirely. Draining providers stop receiving new requests but in-flight requests complete normally.' - ), - ] = Status.active - - - @field_validator('endpoint') - @classmethod - def _require_https_endpoint(cls, value: AnyUrl) -> AnyUrl: - if value.scheme != 'https': - raise ValueError('endpoint must use https') - return value - - @model_validator(mode='after') - def _require_identity_match_dimensions(self) -> TmpProviderRegistration1: - if self.identity_match is True: - if not self.countries: - raise ValueError('countries is required when identity_match is true') - if not self.uid_types: - raise ValueError('uid_types is required when identity_match is true') - return self - - -class TmpProviderRegistration2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - provider_id: Annotated[ - str, - Field( - description="Stable identifier for this provider registration. Used in logs, metrics, cache keys, as the key in `signals_by_provider` on Context Match responses and `targeting_kv_mapping` in publisher configuration, and as the key in `tmpx_providers` on Identity Match responses and `tmpx_macro_mapping` in publisher configuration. Publishers assign this — it is not the provider's agent_url. Charset is constrained to a safe alphanumeric/underscore set so the value can appear in operational surfaces (logs, metrics, dashboards) without quoting. See publisher-targeting-kv-config.json and publisher-tmpx-config.json.", - max_length=64, - min_length=1, - pattern='^[A-Za-z0-9_]+$', - ), - ] - endpoint: Annotated[ - AnyUrl, - Field( - description='Base URL the router calls. The router appends /context for Context Match and /identity for Identity Match. MUST be HTTPS in production, validated against the canonical reserved IPv4 and IPv6 ranges, with the TCP connection pinned to the validated IP (DNS re-resolution alone is insufficient against rebinding). Publishers comparing two provider registrations for the same `endpoint` MUST canonicalize both per the AdCP URL canonicalization rules; two registrations differing only in case, default port, or path-slash collapsing are the same provider. See docs/trusted-match/specification#provider-registration-security, docs/building/implementation/security#webhook-url-validation-ssrf, and docs/reference/url-canonicalization.' - ), - ] - context_match: Annotated[ - StrictBool | None, - Field(description='Provider handles Context Match requests (POST /context).'), - ] = None - identity_match: Annotated[ - Literal[True], - Field(description='Provider handles Identity Match requests (POST /identity).'), - ] - countries: Annotated[ - list[Country] | None, - Field( - description="ISO 3166-1 alpha-2 country codes this provider serves. The router filters Identity Match providers by the request's country field. MUST be present and non-empty when identity_match is true.", - min_length=1, - ), - ] = None - uid_types: Annotated[ - list[uid_type.UidType] | None, - Field( - description="Identity types this provider can resolve. The router selects Identity Match providers whose uid_types overlaps with any uid_type in the request's identities array. MUST be present and non-empty when identity_match is true.", - min_length=1, - ), - ] = None - properties: Annotated[ - list[UUID] | None, - Field( - description='Property RIDs (UUID v7) this provider serves. When present, the router only sends requests from these properties to this provider. When absent, the provider serves all properties.', - min_length=1, - ), - ] = None - timeout_ms: Annotated[ - SchemaInt | None, - Field( - description="Per-provider timeout in milliseconds. The router skips this provider if it does not respond within this budget. Must be less than or equal to the router's overall latency_budget_ms. The router may further reduce this based on adaptive timeout allocation.", - ge=5, - le=5000, - ), - ] = 50 - priority: Annotated[ - SchemaInt | None, - Field( - description='Provider ordering for Context Match offer conflict resolution. Lower values have higher priority. When two providers return offers for the same package_id (a configuration error), the router keeps the offer from the higher-priority provider; equal priorities are broken by first response received. Identity Match eligibility remains a responder-scoped union because silent omission is not a negative vote. Also used for adaptive timeout allocation — higher-priority providers receive a larger share of the latency budget.', - ge=0, - ), - ] = 0 - tmpx_slots: Annotated[ - list[TmpxSlot] | None, - Field( - description='Stable provider-local slot identifiers for the ordered TMPX chunks this provider mints. Slot IDs are opaque provider-namespaced tokens (e.g. `["primary","secondary"]`), NOT ad-server macro names — publishers map `(provider_id, slot_id)` → local destination via `tmpx_macro_mapping` in publisher-tmpx-config.json, so the destination namespace stays publisher-owned and the router never accepts a destination name from an untrusted provider. Distinct providers MAY reuse the same slot_id without collision because publisher lookup is keyed on `(provider_id, slot_id)`. Publishers use this list at startup to validate `tmpx_macro_mapping` covers every slot the provider mints and to detect config drift when the provider\'s slot contract changes. Ordering carries the ordered-prefix invariant: a provider that emits fewer chunks than it registered MUST emit an ordered prefix of this list — chunks map to slots in registration order and MUST NOT be shifted, sparse, or reordered. Cap of 2 slots in v1 aligned with the GAM macro-slot budget; the cap MAY rise without a shape change. A provider that emits TMPX (populates `tmpx_chunks` on its identity-match response) MUST register this list; a provider that does not emit TMPX omits it. Schema cannot enforce that predicate because "emits TMPX" is not schema-visible.', - max_length=2, - min_length=1, - ), - ] = None - status: Annotated[ - Status | None, - Field( - description='Provider lifecycle status. Active providers receive requests. Inactive providers are skipped entirely. Draining providers stop receiving new requests but in-flight requests complete normally.' - ), - ] = Status.active - - - @field_validator('endpoint') - @classmethod - def _require_https_endpoint(cls, value: AnyUrl) -> AnyUrl: - if value.scheme != 'https': - raise ValueError('endpoint must use https') - return value - - @model_validator(mode='after') - def _require_identity_match_dimensions(self) -> TmpProviderRegistration2: - if self.identity_match is True: - if not self.countries: - raise ValueError('countries is required when identity_match is true') - if not self.uid_types: - raise ValueError('uid_types is required when identity_match is true') - return self - - -TmpProviderRegistration = Annotated[ - TmpProviderRegistration1 | TmpProviderRegistration2, - Field( - description="Declares a TMP provider's endpoint, capabilities, and operational parameters. Used in router configuration (static YAML or dynamic API) and referenced by product-level provider entries. The publisher controls which providers participate in their ad decisioning. Endpoint URLs MUST be validated against SSRF, and dynamic registration endpoints MUST authenticate callers — see docs/trusted-match/specification#provider-registration-security.", - title='TMP Provider Registration', - ), -] diff --git a/src/adcp/types/generated_poc/trusted_match/publisher_targeting_kv_config.py b/src/adcp/types/generated_poc/trusted_match/publisher_targeting_kv_config.py deleted file mode 100644 index 50b801336..000000000 --- a/src/adcp/types/generated_poc/trusted_match/publisher_targeting_kv_config.py +++ /dev/null @@ -1,22 +0,0 @@ -# generated by datamodel-codegen: -# filename: trusted_match/publisher_targeting_kv_config.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StringConstraints - - -class PublisherTargetingKvMapping(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - targeting_kv_mapping: Annotated[ - dict[Annotated[str, StringConstraints(pattern=r'^[A-Za-z0-9_]+$', min_length=1, max_length=64)], dict[str, str]], - Field( - description="Map from the publisher-assigned `provider_id` to that provider's key-to-destination mapping for this surface. Outer keys use the `provider_id` charset from provider-registration.json. Inner keys are exact provider-local targeting keys from `signals_by_provider[provider_id].targeting_kvs`; no separator, prefix, or case normalization is applied. Values are publisher-local ad-server targeting destinations. At serve time, the publisher applies a mapping only when both the provider and key are own properties of their respective maps. Missing providers and missing keys are dropped independently, without falling back to the provider-local key. Multiple tuples may intentionally map to one destination; each mapped tuple contributes its value and MUST NOT overwrite another value. Publishers SHOULD warn at startup when this map names a provider that is not registered." - ), - ] diff --git a/src/adcp/types/generated_poc/trusted_match/publisher_tmpx_config.py b/src/adcp/types/generated_poc/trusted_match/publisher_tmpx_config.py deleted file mode 100644 index c20c39517..000000000 --- a/src/adcp/types/generated_poc/trusted_match/publisher_tmpx_config.py +++ /dev/null @@ -1,25 +0,0 @@ -# generated by datamodel-codegen: -# filename: trusted_match/publisher_tmpx_config.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field, StringConstraints - - -class PublisherTmpxMacroMapping(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - tmpx_macro_mapping: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[A-Za-z0-9_]+$', min_length=1, max_length=64)], - dict[Annotated[str, StringConstraints(pattern=r'^[a-zA-Z][a-zA-Z0-9_]*$', min_length=1, max_length=64)], str], - ], - Field( - description="Map from `provider_id` to a slot-keyed map of ad-server destinations (macro names, GAM key-values, VAST URL macro tokens, DOOH play-log field identifiers) for that provider on this surface. The inner map's keys are the provider-local `slot_id` values the provider registered in `tmpx_slots` (see provider-registration.json). At serve time, the publisher's adapter reads `tmpx_providers[provider_id].chunks[]` from the identity-match response and, for each chunk, substitutes the chunk's `value` into `tmpx_macro_mapping[provider_id][chunk.slot_id]`. Publishers use the outer map's keys plus the inner map's keys to validate the mapping at startup — every `slot_id` a provider registered in `tmpx_slots` SHOULD have a corresponding entry in that provider's inner map, and publishers SHOULD surface any missing entry as a startup warning so the operator can add it before responses begin carrying the unmapped slot. The exact destination string is publisher-local: GAM adopters typically use uppercase snake-case macro names, VAST surfaces use URL macro tokens, DOOH surfaces use play-log field identifiers. Outer-map key charset matches `provider_id` in provider-registration.json (enforced by `propertyNames`). Inner-map key charset matches `slot_id` in tmpx-chunk.json. When a response carries a `slot_id` for a provider that this mapping has no entry for — the mapping is missing the whole provider, OR the provider is present but the specific `slot_id` is unknown to this surface — the publisher's adapter MUST fail closed for that provider on that impression: none of that provider's chunks are fired into the ad-serving path and the adapter logs a configuration error. Other providers on the same response are unaffected. A `provider_id` absent from this mapping entirely (e.g. a newly onboarded provider not yet trafficked on this surface) is the same case — the adapter drops that provider's chunks on this surface rather than firing them into an unconfigured destination. Startup validation and serve-time fail-closed are two stages of a single rule: the warning at startup is the operator's chance to fix the mapping before the serve-time rule catches the missing slot." - ), - ] diff --git a/src/adcp/types/generated_poc/trusted_match/tmpx_chunk.py b/src/adcp/types/generated_poc/trusted_match/tmpx_chunk.py deleted file mode 100644 index 0100d1b92..000000000 --- a/src/adcp/types/generated_poc/trusted_match/tmpx_chunk.py +++ /dev/null @@ -1,33 +0,0 @@ -# generated by datamodel-codegen: -# filename: trusted_match/tmpx_chunk.json -# timestamp: 2026-10-03T15:15:43+00:00 - -from __future__ import annotations - -from typing import Annotated - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - - -class TmpxChunk(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - slot_id: Annotated[ - str, - Field( - description="Provider-local slot identifier from the emitting provider's registered `tmpx_slots` (provider-registration.json). Opaque provider-namespaced token; publishers map `(provider_id, slot_id)` → local destination via `tmpx_macro_mapping`. NOT an ad-server macro name.", - max_length=64, - min_length=1, - pattern='^[a-zA-Z][a-zA-Z0-9_]*$', - ), - ] - value: Annotated[ - str, - Field( - description="Opaque, URL-safe wire string the publisher substitutes verbatim into the destination the publisher's mapping resolves for this `(provider_id, slot_id)` pair. Publishers MUST NOT parse, decode, or transform this value.", - max_length=1024, - min_length=1, - ), - ] diff --git a/src/adcp/types/legacy.py b/src/adcp/types/legacy.py index ecda36762..222bca752 100644 --- a/src/adcp/types/legacy.py +++ b/src/adcp/types/legacy.py @@ -6,182 +6,186 @@ and AdCP 3.0/3.1 wire adapters. """ -from typing import Annotated, Any - -from pydantic import AnyUrl, ConfigDict, Field, StrictFloat, StrictInt, TypeAdapter, field_validator - -from adcp._deferred_adapters import deferred_adapter from adcp.types._legacy_assets import ( coerce_legacy_asset, coerce_legacy_assets, infer_asset_type, ) -from adcp.types.generated_poc.core.creative_asset import CreativeAsset as LegacyCreativeAsset -from adcp.types.generated_poc.core.creative_filters import CreativeFilters as LegacyCreativeFilters -from adcp.types.generated_poc.core.format import Format as LegacyFormat -from adcp.types.generated_poc.core.format_id import FormatReferenceStructuredObject -from adcp.types.generated_poc.core.package import Package as LegacyPackage -from adcp.types.generated_poc.core.placement import Placement as LegacyPlacement -from adcp.types.generated_poc.core.product import Product as LegacyProduct -from adcp.types.generated_poc.core.product_filters import ProductFilters as LegacyProductFilters -from adcp.types.generated_poc.core.product_format_declaration import ( +from adcp.types.domains.core.creative_asset import CreativeAsset as LegacyCreativeAsset +from adcp.types.domains.core.creative_filters import CreativeFilters as LegacyCreativeFilters +from adcp.types.domains.core.format import Format as LegacyFormat +from adcp.types.domains.core.format_id import FormatReferenceStructuredObject +from adcp.types.domains.core.package import Package as LegacyPackage +from adcp.types.domains.core.placement import Placement as LegacyPlacement +from adcp.types.domains.core.product import Product as LegacyProduct +from adcp.types.domains.core.product_filters import ProductFilters as LegacyProductFilters +from adcp.types.domains.core.product_format_declaration import ( ProductFormatDeclaration as LegacyGeneratedProductFormatDeclaration, ) -from adcp.types.generated_poc.creative.get_creative_delivery_response import ( +from adcp.types.domains.creative.get_creative_delivery_response import ( GetCreativeDeliveryResponse as LegacyGetCreativeDeliveryResponse, ) -from adcp.types.generated_poc.creative.list_creatives_request import ( +from adcp.types.domains.creative.list_creatives_request import ( ListCreativesRequest as LegacyListCreativesRequest, ) -from adcp.types.generated_poc.creative.list_creatives_response import ( +from adcp.types.domains.creative.list_creatives_response import ( ListCreativesResponse as LegacyListCreativesResponse, ) -from adcp.types.generated_poc.creative.preview_creative_request import ( +from adcp.types.domains.creative.preview_creative_request import ( PreviewCreativeRequest as LegacyPreviewCreativeRequest, ) -from adcp.types.generated_poc.creative.preview_creative_response import ( +from adcp.types.domains.creative.preview_creative_response import ( PreviewCreativeResponse as LegacyPreviewCreativeResponse, ) -from adcp.types.generated_poc.creative.preview_creative_response import ( +from adcp.types.domains.creative.preview_creative_response import ( PreviewCreativeResponse1 as LegacyPreviewCreativeResponse1, ) -from adcp.types.generated_poc.creative.preview_creative_response import ( +from adcp.types.domains.creative.preview_creative_response import ( PreviewCreativeResponse2 as LegacyPreviewCreativeResponse2, ) -from adcp.types.generated_poc.creative.preview_creative_response import ( +from adcp.types.domains.creative.preview_creative_response import ( PreviewCreativeResponse3 as LegacyPreviewCreativeResponse3, ) -from adcp.types.generated_poc.creative.sync_creatives_request import ( +from adcp.types.domains.creative.preview_creative_response import ( + PreviewCreativeResponse4 as LegacyPreviewCreativeResponse4, +) +from adcp.types.domains.creative.sync_creatives_request import ( SyncCreativesRequest as LegacySyncCreativesRequest, ) -from adcp.types.generated_poc.creative.sync_creatives_response import ( +from adcp.types.domains.creative.sync_creatives_response import ( SyncCreativesResponse as LegacySyncCreativesResponse, ) -from adcp.types.generated_poc.media_buy.build_creative_request import ( +from adcp.types.domains.media_buy.build_creative_request import ( BuildCreativeRequest as LegacyBuildCreativeRequest, ) -from adcp.types.generated_poc.media_buy.build_creative_response import ( +from adcp.types.domains.media_buy.build_creative_response import ( BuildCreativeResponse as LegacyBuildCreativeResponse, ) -from adcp.types.generated_poc.media_buy.build_creative_response import ( +from adcp.types.domains.media_buy.build_creative_response import ( BuildCreativeResponse1 as LegacyBuildCreativeResponse1, ) -from adcp.types.generated_poc.media_buy.build_creative_response import ( +from adcp.types.domains.media_buy.build_creative_response import ( BuildCreativeResponse2 as LegacyBuildCreativeResponse2, ) -from adcp.types.generated_poc.media_buy.build_creative_response import ( +from adcp.types.domains.media_buy.build_creative_response import ( BuildCreativeResponse3 as LegacyBuildCreativeResponse3, ) -from adcp.types.generated_poc.media_buy.build_creative_response import ( +from adcp.types.domains.media_buy.build_creative_response import ( BuildCreativeResponse4 as LegacyBuildCreativeResponse4, ) -from adcp.types.generated_poc.media_buy.build_creative_response import ( +from adcp.types.domains.media_buy.build_creative_response import ( BuildCreativeResponse5 as LegacyBuildCreativeResponse5, ) -from adcp.types.generated_poc.media_buy.build_creative_response import ( +from adcp.types.domains.media_buy.build_creative_response import ( BuildCreativeResponse6 as LegacyBuildCreativeResponse6, ) -from adcp.types.generated_poc.media_buy.create_media_buy_request import ( +from adcp.types.domains.media_buy.create_media_buy_request import ( CreateMediaBuyRequest as LegacyCreateMediaBuyRequest, ) -from adcp.types.generated_poc.media_buy.create_media_buy_response import ( +from adcp.types.domains.media_buy.create_media_buy_response import ( CreateMediaBuyResponse as LegacyCreateMediaBuyResponse, ) -from adcp.types.generated_poc.media_buy.create_media_buy_response import ( +from adcp.types.domains.media_buy.create_media_buy_response import ( CreateMediaBuyResponse1 as LegacyCreateMediaBuyResponse1, ) -from adcp.types.generated_poc.media_buy.create_media_buy_response import ( +from adcp.types.domains.media_buy.create_media_buy_response import ( CreateMediaBuyResponse2 as LegacyCreateMediaBuyResponse2, ) -from adcp.types.generated_poc.media_buy.create_media_buy_response import ( +from adcp.types.domains.media_buy.create_media_buy_response import ( CreateMediaBuyResponse3 as LegacyCreateMediaBuyResponse3, ) -from adcp.types.generated_poc.media_buy.get_media_buy_delivery_response import ( +from adcp.types.domains.media_buy.get_media_buy_delivery_response import ( GetMediaBuyDeliveryResponse as LegacyGetMediaBuyDeliveryResponse, ) -from adcp.types.generated_poc.media_buy.get_media_buys_response import ( +from adcp.types.domains.media_buy.get_media_buys_response import ( GetMediaBuysResponse as LegacyGetMediaBuysResponse, ) -from adcp.types.generated_poc.media_buy.get_products_request import ( +from adcp.types.domains.media_buy.get_products_request import ( GetProductsRequest as LegacyGetProductsRequest, ) -from adcp.types.generated_poc.media_buy.get_products_response import ( +from adcp.types.domains.media_buy.get_products_response import ( GetProductsResponse as LegacyGetProductsResponse, ) -from adcp.types.generated_poc.media_buy.list_creative_formats_request import ( +from adcp.types.domains.media_buy.list_creative_formats_request import ( ListCreativeFormatsRequest as LegacyListCreativeFormatsRequest, ) -from adcp.types.generated_poc.media_buy.list_creative_formats_response import ( +from adcp.types.domains.media_buy.list_creative_formats_response import ( ListCreativeFormatsResponse as LegacyListCreativeFormatsResponse, ) -from adcp.types.generated_poc.media_buy.package_request import ( +from adcp.types.domains.media_buy.package_request import ( PackageRequest as LegacyPackageRequest, ) -from adcp.types.generated_poc.media_buy.package_update import PackageUpdate as LegacyPackageUpdate -from adcp.types.generated_poc.media_buy.update_media_buy_request import ( +from adcp.types.domains.media_buy.package_update import PackageUpdate as LegacyPackageUpdate +from adcp.types.domains.media_buy.update_media_buy_request import ( UpdateMediaBuyRequest as LegacyUpdateMediaBuyRequest, ) -from adcp.types.generated_poc.media_buy.update_media_buy_response import ( +from adcp.types.domains.media_buy.update_media_buy_response import ( UpdateMediaBuyResponse as LegacyUpdateMediaBuyResponse, ) -from adcp.types.generated_poc.media_buy.update_media_buy_response import ( +from adcp.types.domains.media_buy.update_media_buy_response import ( UpdateMediaBuyResponse1 as LegacyUpdateMediaBuyResponse1, ) -from adcp.types.generated_poc.media_buy.update_media_buy_response import ( +from adcp.types.domains.media_buy.update_media_buy_response import ( UpdateMediaBuyResponse2 as LegacyUpdateMediaBuyResponse2, ) -from adcp.types.generated_poc.media_buy.update_media_buy_response import ( +from adcp.types.domains.media_buy.update_media_buy_response import ( UpdateMediaBuyResponse3 as LegacyUpdateMediaBuyResponse3, ) - -@deferred_adapter -def _url_adapter() -> TypeAdapter[AnyUrl]: - return TypeAdapter(AnyUrl) - - -class LegacyFormatId(FormatReferenceStructuredObject): - """Legacy tuple that validates a URL without rewriting its wire spelling.""" - - model_config = ConfigDict(extra="allow") - - # The generated parent already carries ``agent_url`` as a URL-validated - # wire string (``WireUrl``, #1384); the override keeps this model's own - # validator and the plain ``str`` spelling the legacy tuple documents. - agent_url: str - id: Annotated[str, Field(pattern=r"^[a-zA-Z0-9_-]+$")] - width: Annotated[StrictInt | None, Field(ge=1)] = None - height: Annotated[StrictInt | None, Field(ge=1)] = None - duration_ms: Annotated[StrictInt | StrictFloat | None, Field(ge=1)] = None - - @field_validator("agent_url") - @classmethod - def _validate_agent_url(cls, value: str) -> str: - _url_adapter().validate_python(value) - return value - - def model_dump(self, **kwargs: Any) -> dict[str, Any]: - """Preserve the original agent_url bytes in explicit legacy output.""" - - return super().model_dump(**kwargs) - - -LegacyFormatReferenceStructuredObject = LegacyFormatId +# The generated class itself, under the ``Legacy*`` spelling every other name +# in this module uses. It was a SUBCLASS until #1398, written when the generated +# ``agent_url`` was an ``AnyUrl`` that normalized the wire bytes — it carried a +# plain ``str`` annotation, its own ``TypeAdapter(AnyUrl)`` validator, and +# ``StrictInt`` dimensions. #1384 moved ``agent_url`` to ``WireUrl`` +# (``StrictStr`` + ``_require_absolute_url`` + ``WithJsonSchema``), which is +# that subclass's whole purpose, and left every redeclaration a WEAKENING of +# the parent it narrowed: ``agent_url: str`` dropped the strictness, the +# absolute-URL validator and the ``format: uri`` schema; ``StrictInt`` +# dimensions dropped ``SchemaInt``'s JSON-schema-integer coercion, so +# ``width: 300.0`` validated against the generated class the SDK's own model +# fields declare and was REFUSED by the public name for it; and +# ``duration_ms: StrictInt | StrictFloat`` widened a ``StrictFloat``. The +# ``model_dump`` override was a bare ``super()`` call. The parent already +# carries ``extra='allow'`` and the same ``id`` pattern, so there is nothing +# left for a subclass to hold. +LegacyFormatId = FormatReferenceStructuredObject +LegacyFormatReferenceStructuredObject = FormatReferenceStructuredObject LegacyProductFormatDeclaration = LegacyGeneratedProductFormatDeclaration + +# One semantic name per ARM of every response union whose tool reaches this +# surface. Not a curated subset of them (#1399): where the bare public name for +# an arm is the canonical SUBCLASS, the generated arm itself has no public +# spelling at all without one of these, and a seller reading an off-the-wire +# response can then only name it through ``adcp.types.domains..*`` — +# which is the private tree this release exists to retire. +# +# The set used to be curated, and it had five holes at once: +# ``LegacyUpdateMediaBuySubmittedResponse`` and all three +# ``LegacyCreateMediaBuy*Response`` arms were absent while +# ``LegacyUpdateMediaBuy{Success,Error}Response`` shipped, and the generator had +# already opened a fifth by adding a fourth ``preview_creative`` arm that nobody +# named. ``tests/test_legacy_surface_names_every_arm.py`` derives the required +# set from the generated response unions, so a sixth fails the build instead of +# reaching an adopter. LegacyBuildCreativeSuccessResponse = LegacyBuildCreativeResponse1 LegacyBuildCreativeErrorResponse = LegacyBuildCreativeResponse2 LegacyBuildCreativeSubmittedResponse = LegacyBuildCreativeResponse6 +LegacyCreateMediaBuySuccessResponse = LegacyCreateMediaBuyResponse1 +LegacyCreateMediaBuyErrorResponse = LegacyCreateMediaBuyResponse2 +LegacyCreateMediaBuySubmittedResponse = LegacyCreateMediaBuyResponse3 LegacyPreviewCreativeSingleResponse = LegacyPreviewCreativeResponse1 LegacyPreviewCreativeBatchResponse = LegacyPreviewCreativeResponse2 LegacyPreviewCreativeVariantResponse = LegacyPreviewCreativeResponse3 +LegacyPreviewCreativeSubmittedResponse = LegacyPreviewCreativeResponse4 __all__ = [ "infer_asset_type", "coerce_legacy_asset", "coerce_legacy_assets", - # The class generated model fields are typed with, and the parent - # ``LegacyFormatId`` narrows. Exported here and deliberately not on - # ``adcp.types``, which carries the canonical format model instead. + # The class generated model fields are typed with, under its own name as + # well as under ``LegacyFormatId``. ``adcp.types`` reaches it through the + # two ``Legacy*`` spellings below and carries the canonical format model + # under the bare ``Format`` / ``FormatId`` names instead. "FormatReferenceStructuredObject", "LegacyBuildCreativeRequest", "LegacyBuildCreativeResponse", @@ -199,6 +203,9 @@ def model_dump(self, **kwargs: Any) -> dict[str, Any]: "LegacyCreateMediaBuyResponse1", "LegacyCreateMediaBuyResponse2", "LegacyCreateMediaBuyResponse3", + "LegacyCreateMediaBuyErrorResponse", + "LegacyCreateMediaBuySubmittedResponse", + "LegacyCreateMediaBuySuccessResponse", "LegacyCreativeAsset", "LegacyCreativeFilters", "LegacyFormat", @@ -222,8 +229,10 @@ def model_dump(self, **kwargs: Any) -> dict[str, Any]: "LegacyPreviewCreativeResponse1", "LegacyPreviewCreativeResponse2", "LegacyPreviewCreativeResponse3", + "LegacyPreviewCreativeResponse4", "LegacyPreviewCreativeBatchResponse", "LegacyPreviewCreativeSingleResponse", + "LegacyPreviewCreativeSubmittedResponse", "LegacyPreviewCreativeVariantResponse", "LegacyProduct", "LegacyProductFilters", diff --git a/src/adcp/types/protocol.py b/src/adcp/types/protocol.py index 130a1afaa..5245b9b7b 100644 --- a/src/adcp/types/protocol.py +++ b/src/adcp/types/protocol.py @@ -25,6 +25,7 @@ "Request", "Response", "ProtocolEnvelope", + "AdcpVersionEnvelope", "ProtocolResponse", "AdcpProtocol", "Protocol", @@ -104,6 +105,7 @@ # lazily through ``__getattr__`` at runtime. from adcp.types import ( # noqa: F401 AdcpProtocol, + AdcpVersionEnvelope, AgentDeclarations, AgentNotificationConfig, AgentNotificationConfigState, diff --git a/src/adcp/utils/format_assets.py b/src/adcp/utils/format_assets.py index 0694e7138..728306a71 100644 --- a/src/adcp/utils/format_assets.py +++ b/src/adcp/utils/format_assets.py @@ -21,7 +21,7 @@ from collections.abc import Mapping, Sequence from typing import Any, Protocol -from adcp.types.generated_poc.core.format import Assets as AssetsModel +from adcp.types.domains.core.format import Assets as AssetsModel class LegacyFormatAssetsInput(Protocol): diff --git a/src/adcp/utils/preview_cache.py b/src/adcp/utils/preview_cache.py index 31822190e..8be9d7224 100644 --- a/src/adcp/utils/preview_cache.py +++ b/src/adcp/utils/preview_cache.py @@ -468,7 +468,7 @@ async def add_preview_urls_to_products( if preview_data: if product.product_id not in product_previews: product_previews[product.product_id] = {} - key = declaration.format_option_id or declaration.format_kind.value + key = declaration.format_option_id or declaration.format_kind product_previews[product.product_id][key] = preview_data # Add preview data to products @@ -495,14 +495,14 @@ async def process_format(declaration: Format) -> tuple[str, dict[str, Any] | Non preview_data = await generator.get_preview_data_for_manifest( declaration, sample_manifest ) - key = declaration.format_option_id or declaration.format_kind.value + key = declaration.format_option_id or declaration.format_kind return (key, preview_data) except Exception as e: logger.warning( f"Failed to generate preview for product {product.product_id}, " f"format {declaration}: {e}" ) - key = declaration.format_option_id or declaration.format_kind.value + key = declaration.format_option_id or declaration.format_kind return (key, None) format_tasks = [process_format(item) for item in product.format_options] diff --git a/src/adcp/webhook_receiver.py b/src/adcp/webhook_receiver.py index 89861fb10..f7e8b4360 100644 --- a/src/adcp/webhook_receiver.py +++ b/src/adcp/webhook_receiver.py @@ -89,15 +89,15 @@ async def hook(request): WebhookVerifyOptions, verify_webhook_signature, ) -from adcp.types.generated_poc.brand.revocation_notification import RevocationNotification -from adcp.types.generated_poc.collection.collection_list_changed_webhook import ( +from adcp.types.domains.brand.revocation_notification import RevocationNotification +from adcp.types.domains.collection.collection_list_changed_webhook import ( CollectionListChangedWebhook, ) -from adcp.types.generated_poc.content_standards.artifact_webhook_payload import ( +from adcp.types.domains.content_standards.artifact_webhook_payload import ( ArtifactWebhookPayload, ) -from adcp.types.generated_poc.core.mcp_webhook_payload import McpWebhookPayload -from adcp.types.generated_poc.property.property_list_changed_webhook import ( +from adcp.types.domains.core.mcp_webhook_payload import McpWebhookPayload +from adcp.types.domains.property.property_list_changed_webhook import ( PropertyListChangedWebhook, ) diff --git a/src/adcp/webhook_sender.py b/src/adcp/webhook_sender.py index 7573dd122..26ef60e7f 100644 --- a/src/adcp/webhook_sender.py +++ b/src/adcp/webhook_sender.py @@ -61,7 +61,7 @@ WholesaleFeedEvent, WholesaleFeedWebhook, ) -from adcp.types.generated_poc.core.async_response_data import AdcpAsyncResponseData +from adcp.types.domains.core.async_response_data import AdcpAsyncResponseData from adcp.webhook_auth import ( AdcpLegacyHmacStrategy, BearerTokenStrategy, diff --git a/tests/canonical_stub_field_gap.json b/tests/canonical_stub_field_gap.json deleted file mode 100644 index 5cbf1aeff..000000000 --- a/tests/canonical_stub_field_gap.json +++ /dev/null @@ -1,786 +0,0 @@ -{ - "_comment": "Runtime fields that src/adcp/types/canonical_creative.pyi does not declare, so a type checker rejects reading them and rejects passing them to the constructor. Shrink-only: declare the field in the stub, then run `python scripts/update_canonical_stub_ledger.py`. Graded by tests/test_protocol_envelope_inheritance.py::test_canonical_stub_declares_every_runtime_field.", - "gap": { - "CreateMediaBuyRequest": [ - "adcp_major_version", - "adcp_version", - "advertiser_industry", - "agency_estimate_number", - "artifact_webhook", - "bidding", - "brand", - "budget_allocation", - "budget_cap_timezone", - "context", - "daily_budget_cap", - "end_time", - "ext", - "frequency_cap", - "governance_context", - "idempotency_key", - "invoice_recipient", - "io_acceptance", - "name", - "opportunity", - "pacing", - "paused", - "plan_id", - "po_number", - "proposal_id", - "push_notification_config", - "reporting_webhook", - "start_time", - "total_budget" - ], - "CreateMediaBuyResponse1": [ - "account", - "adcp_error", - "adcp_major_version", - "adcp_version", - "available_actions", - "bidding", - "budget_allocation", - "budget_cap_timezone", - "context", - "context_id", - "creative_deadline", - "currency", - "daily_budget_cap", - "ext", - "frequency_cap", - "governance_context", - "invoice_recipient", - "media_buy_status", - "message", - "name", - "pacing", - "payload", - "planned_delivery", - "proposal_id", - "push_notification_config", - "replayed", - "revision", - "sandbox", - "task_id", - "timestamp", - "total_budget", - "valid_actions", - "warnings" - ], - "CreateMediaBuyResponse2": [ - "adcp_error", - "adcp_major_version", - "adcp_version", - "context", - "context_id", - "errors", - "ext", - "governance_context", - "message", - "payload", - "push_notification_config", - "replayed", - "task_id", - "timestamp" - ], - "CreateMediaBuyResponse3": [ - "adcp_error", - "adcp_major_version", - "adcp_version", - "context", - "context_id", - "errors", - "ext", - "governance_context", - "message", - "payload", - "push_notification_config", - "replayed", - "task_id", - "timestamp" - ], - "Creative": [ - "account", - "assets", - "assignments", - "component_assets", - "concept_id", - "concept_name", - "created_date", - "items", - "localization", - "localization_unavailable", - "name", - "pricing_options", - "purge", - "representation_selection", - "revision_id", - "rights", - "rights_attestation_evaluations", - "snapshot", - "snapshot_unavailable_reason", - "status", - "tags", - "updated_date", - "variables", - "webhook_activity" - ], - "CreativeAsset": [ - "assets", - "component_assets", - "industry_identifiers", - "inputs", - "name", - "placement_ids", - "placement_refs", - "provenance", - "representation_selection", - "rights", - "status", - "tags", - "weight" - ], - "CreativeFilters": [ - "accounts", - "asset_types", - "assigned_to_packages", - "concept_ids", - "created_after", - "created_before", - "creative_ids", - "ext", - "format_kinds", - "has_served", - "has_variables", - "indicator_types", - "media_buy_ids", - "name_contains", - "statuses", - "tags", - "tags_any", - "unassigned", - "updated_after", - "updated_before" - ], - "CreativeManifest": [ - "brand", - "component_assets", - "ext", - "format_option_ref", - "industry_identifiers", - "provenance", - "representation_selection", - "rights" - ], - "CreativeVariant": [ - "brand_lift", - "brand_search_lift", - "by_action_source", - "by_event_type", - "clicks", - "commissionable_value", - "completed_views", - "completion_rate", - "conversion_lift", - "conversion_value", - "conversions", - "cost_per_acquisition", - "cost_per_click", - "cost_per_completed_view", - "cpm", - "ctr", - "dooh_metrics", - "downloads", - "engagement_rate", - "engagements", - "follows", - "foot_traffic", - "frequency", - "generation_context", - "grps", - "impressions", - "incremental_sales_lift", - "leads", - "locale_variant_id", - "measurement_source", - "new_to_brand_rate", - "new_to_brand_units", - "ooh_metrics", - "plays", - "profile_visits", - "quartile_data", - "reach", - "reach_unit", - "reach_window", - "revision_id", - "roas", - "saves", - "spend", - "time_based_views", - "units_sold", - "variant_id", - "vendor_metric_values", - "viewability", - "views" - ], - "DeliveryCreative": [ - "format_option_ref", - "media_buy_id", - "totals", - "variant_count" - ], - "Format": [ - "applies_to_channels", - "display_name", - "experimental", - "format_schema", - "format_shape", - "seller_preference" - ], - "GetCreativeDeliveryResponse": [ - "account_id", - "adcp_error", - "adcp_major_version", - "adcp_version", - "context", - "context_id", - "currency", - "errors", - "ext", - "governance_context", - "media_buy_id", - "message", - "pagination", - "payload", - "push_notification_config", - "replayed", - "reporting_period", - "task_id", - "timestamp" - ], - "GetMediaBuyDeliveryResponse": [ - "adcp_error", - "adcp_major_version", - "adcp_version", - "aggregated_totals", - "attribution_window", - "context", - "context_id", - "currency", - "errors", - "ext", - "governance_context", - "media_buy_deliveries", - "message", - "next_expected_at", - "notification_type", - "pagination", - "partial_data", - "payload", - "push_notification_config", - "replayed", - "reporting_period", - "reporting_revision", - "reporting_revision_binding", - "reporting_rows", - "sandbox", - "sequence_number", - "task_id", - "timestamp", - "unavailable_count" - ], - "GetMediaBuysResponse": [ - "adcp_error", - "adcp_major_version", - "adcp_version", - "context", - "context_id", - "errors", - "ext", - "governance_context", - "message", - "pagination", - "payload", - "push_notification_config", - "replayed", - "sandbox", - "task_id", - "timestamp" - ], - "GetProductsRequest": [ - "acceptance_context", - "adcp_major_version", - "adcp_version", - "brand", - "brief", - "buying_mode", - "catalog", - "context", - "ext", - "idempotency_key", - "if_pricing_version", - "if_wholesale_feed_version", - "media_buy_frequency_cap", - "preferred_delivery_types", - "property_list", - "push_notification_config", - "required_media_buy_support", - "required_overlay_support", - "required_policies", - "targeting_overlay" - ], - "GetProductsResponse": [ - "adcp_error", - "adcp_major_version", - "adcp_version", - "cache_scope", - "catalog_applied", - "context", - "context_id", - "errors", - "ext", - "extensions", - "filter_diagnostics", - "governance_context", - "incomplete", - "message", - "pagination", - "payload", - "pricing_version", - "property_list_applied", - "push_notification_config", - "reason", - "replayed", - "sandbox", - "suggestions", - "targeting_resolution", - "task_id", - "timestamp", - "unchanged", - "wholesale_feed_version" - ], - "ListCreativesRequest": [ - "adcp_major_version", - "adcp_version", - "assignment_limit", - "assignment_projection", - "context", - "ext", - "include_assignments", - "include_items", - "include_pricing", - "include_purged", - "include_snapshot", - "include_variables", - "include_webhook_activity", - "pagination", - "sort", - "webhook_activity_limit" - ], - "ListCreativesResponse": [ - "adcp_error", - "adcp_major_version", - "adcp_version", - "context", - "context_id", - "errors", - "ext", - "format_summary", - "governance_context", - "message", - "pagination", - "payload", - "push_notification_config", - "query_summary", - "replayed", - "sandbox", - "status_summary", - "task_id", - "timestamp" - ], - "MediaBuy": [ - "accepted_proposal", - "accepted_proposal_id", - "accepted_proposal_terms_digest", - "account", - "available_actions", - "bidding", - "budget_allocation", - "budget_cap_timezone", - "cancellation", - "confirmed_at", - "context", - "created_at", - "creative_deadline", - "currency", - "daily_budget_cap", - "end_time", - "ext", - "frequency_cap", - "health", - "history", - "impairments", - "indicator_types_evaluated", - "indicators", - "indicators_as_of", - "indicators_evaluated_scope", - "invoice_recipient", - "media_buy_id", - "name", - "pacing", - "rejection_reason", - "revision", - "start_time", - "status", - "status_as_of", - "total_budget", - "updated_at", - "valid_actions", - "webhook_activity" - ], - "MediaBuyPackage": [ - "bid_price", - "bidding", - "budget", - "canceled", - "cancellation", - "context", - "creative_approvals", - "creative_deadline", - "currency", - "daily_budget_cap", - "end_time", - "ext", - "format_kind", - "format_option_refs", - "formats_pending", - "formats_to_provide", - "impressions", - "indicator_types_evaluated", - "indicators", - "indicators_as_of", - "indicators_evaluated_scope", - "min_spend_target", - "optimization_goals", - "pacing", - "package_id", - "params", - "paused", - "product_id", - "snapshot", - "snapshot_unavailable_reason", - "start_time", - "targeting_overlay", - "targeting_resolution" - ], - "Package": [ - "agency_estimate_number", - "audience_evidence_selections", - "bid_price", - "bidding", - "budget", - "canceled", - "cancellation", - "catalogs", - "committed_metrics", - "context", - "creative_assignments", - "creative_deadline", - "daily_budget_cap", - "end_time", - "ext", - "format_kind", - "formats_pending", - "formats_to_provide", - "impressions", - "measurement_terms", - "min_spend_target", - "optimization_goals", - "pacing", - "params", - "paused", - "performance_standards", - "price_breakdown", - "pricing_option_id", - "start_time", - "targeting_overlay", - "targeting_resolution" - ], - "PackageRequest": [ - "adcp_major_version", - "adcp_version", - "agency_estimate_number", - "audience_evidence_pins", - "audience_evidence_requirements", - "bid_price", - "bidding", - "budget", - "catalogs", - "committed_metrics", - "context", - "creative_assignments", - "daily_budget_cap", - "end_time", - "ext", - "format_kind", - "impressions", - "measurement_terms", - "min_spend_target", - "optimization_goals", - "pacing", - "params", - "paused", - "performance_standards", - "pricing_option_id", - "start_time" - ], - "PackageUpdate": [ - "bid_price", - "bidding", - "budget", - "canceled", - "cancellation_reason", - "catalogs", - "context", - "creative_assignments", - "daily_budget_cap", - "end_time", - "ext", - "impressions", - "keyword_targets_add", - "keyword_targets_remove", - "min_spend_target", - "negative_keywords_add", - "negative_keywords_remove", - "optimization_goals", - "pacing", - "paused", - "start_time" - ], - "Placement": [ - "audio_distribution_types", - "description", - "dooh_placement_attributes", - "identifiers", - "kind", - "mode", - "name", - "placement_id", - "publisher_domain", - "seller_agent", - "social_placement_surfaces", - "sponsored_placement_types", - "tags", - "video_placement_types" - ], - "PlatformExtensionReference": [ - "digest", - "uri" - ], - "Product": [ - "acceptance_policy_profile_ids", - "allowed_actions", - "audience_activation", - "audience_evidence", - "audience_evidence_selections", - "audio_distribution_types", - "brief_relevance", - "cancellation_policy", - "catalog_match", - "catalog_types", - "channels", - "collection_targeting_allowed", - "collections", - "conversion_tracking", - "creative_policy", - "data_provider_signals", - "delivery_measurement", - "delivery_type", - "demographic_targeting", - "enforced_policies", - "exclusivity", - "execution_requirements", - "expires_at", - "ext", - "forecast", - "identity", - "included_signals", - "installments", - "is_custom", - "list_applications", - "material_submission", - "max_optimization_goals", - "measurement_readiness", - "measurement_terms", - "media_buy_support", - "metric_optimization", - "outcome_measurement", - "overlay_support", - "performance_standards", - "product_card", - "product_card_detailed", - "property_targeting_allowed", - "publisher_properties", - "reporting_capabilities", - "signal_targeting_allowed", - "signal_targeting_options", - "signal_targeting_rules", - "social_placement_surfaces", - "sponsored_placement_types", - "targeting_resolution", - "trusted_match", - "vendor_metric_optimization", - "video_placement_types" - ], - "ProductFilters": [ - "audience_activation_methods", - "audience_evidence_requirements", - "audio_distribution_types", - "budget_range", - "channels", - "countries", - "delivery_type", - "end_date", - "exclusivity", - "ext", - "format_kinds", - "format_option_refs", - "geo_proximity", - "is_fixed_price", - "keywords", - "metros", - "min_exposures", - "postal_areas", - "pricing_currencies", - "pricing_structures", - "regions", - "required_axe_integrations", - "required_features", - "required_geo_targeting", - "required_metrics", - "required_performance_standards", - "required_vendor_metrics", - "signal_targeting", - "social_placement_surfaces", - "sponsored_placement_types", - "standard_formats_only", - "start_date", - "trusted_match", - "video_placement_types" - ], - "ProductFormatDeclaration": [ - "applies_to_channels", - "canonical_formats_only", - "display_name", - "experimental", - "format_kind", - "format_option_id", - "format_schema", - "format_shape", - "params", - "publisher_domain", - "seller_preference" - ], - "SyncCreativesRequest": [ - "adcp_major_version", - "adcp_version", - "assignment_operations", - "assignments", - "context", - "creative_ids", - "delete_missing", - "dry_run", - "ext", - "idempotency_key", - "push_notification_config", - "validation_mode" - ], - "UpdateMediaBuyRequest": [ - "adcp_major_version", - "adcp_version", - "bidding", - "budget_allocation", - "budget_cap_timezone", - "canceled", - "cancellation_reason", - "context", - "daily_budget_cap", - "end_time", - "ext", - "frequency_cap", - "governance_context", - "idempotency_key", - "invoice_recipient", - "name", - "pacing", - "paused", - "push_notification_config", - "reporting_webhook", - "revision", - "start_time", - "total_budget" - ], - "UpdateMediaBuyResponse1": [ - "adcp_error", - "adcp_major_version", - "adcp_version", - "available_actions", - "bidding", - "budget_allocation", - "budget_cap_timezone", - "context", - "context_id", - "currency", - "daily_budget_cap", - "ext", - "frequency_cap", - "governance_context", - "implementation_date", - "invoice_recipient", - "message", - "name", - "pacing", - "payload", - "push_notification_config", - "replayed", - "sandbox", - "task_id", - "timestamp", - "total_budget", - "valid_actions", - "warnings" - ], - "UpdateMediaBuyResponse2": [ - "adcp_error", - "adcp_major_version", - "adcp_version", - "context", - "context_id", - "errors", - "ext", - "governance_context", - "message", - "payload", - "push_notification_config", - "replayed", - "task_id", - "timestamp" - ], - "UpdateMediaBuyResponse3": [ - "adcp_error", - "adcp_major_version", - "adcp_version", - "context", - "context_id", - "errors", - "ext", - "governance_context", - "message", - "payload", - "push_notification_config", - "replayed", - "task_id", - "timestamp" - ] - } -} diff --git a/tests/canonical_stub_gap.py b/tests/canonical_stub_gap.py deleted file mode 100644 index ce53a4aba..000000000 --- a/tests/canonical_stub_gap.py +++ /dev/null @@ -1,78 +0,0 @@ -"""Which runtime fields ``canonical_creative.pyi`` does not declare. - -``canonical_creative.py`` builds its public models with ``create_model``, so a -type checker cannot see their fields and reads the hand-written -``canonical_creative.pyi`` instead. A field the stub omits does not exist as far -as mypy is concerned: reading it is an attribute error, and passing it to the -constructor the pydantic plugin synthesizes is an unexpected keyword argument. - -This module computes the gap. ``test_protocol_envelope_inheritance`` holds it to -a ledger that may only shrink, and ``scripts/update_canonical_stub_ledger.py`` -rewrites the ledger from the current gap. -""" - -from __future__ import annotations - -import ast -from pathlib import Path - -from pydantic import BaseModel - -from adcp.types import canonical_creative - -LEDGER_FILE = Path(__file__).parent / "canonical_stub_field_gap.json" - - -def _stub_path() -> Path: - return Path(canonical_creative.__file__).with_suffix(".pyi") - - -def _public_models() -> dict[str, type[BaseModel]]: - return { - name: obj - for name, obj in vars(canonical_creative).items() - if isinstance(obj, type) and issubclass(obj, BaseModel) and not name.startswith("_") - } - - -def stub_field_gap() -> dict[str, list[str]]: - """Runtime fields each public model carries that the stub never declares. - - Classes the stub resolves by import are excluded: their fields are declared - wherever they are defined, not here. - """ - stub = ast.parse(_stub_path().read_text(encoding="utf-8")) - - imported: set[str] = { - alias.asname or alias.name - for node in ast.walk(stub) - if isinstance(node, ast.ImportFrom | ast.Import) - for alias in node.names - } - declared: dict[str, set[str]] = {} - bases: dict[str, list[str]] = {} - for node in stub.body: - if not isinstance(node, ast.ClassDef): - continue - declared[node.name] = { - item.target.id - for item in node.body - if isinstance(item, ast.AnnAssign) and isinstance(item.target, ast.Name) - } - bases[node.name] = [ast.unparse(base) for base in node.bases] - - def chain(name: str, seen: frozenset[str] = frozenset()) -> set[str]: - fields = set(declared.get(name, ())) - for base in bases.get(name, []): - if base in declared and base not in seen: - fields |= chain(base, seen | {name}) - return fields - - gap: dict[str, list[str]] = {} - for name, model in _public_models().items(): - if name in imported: - continue - undeclared = set(model.model_fields) - chain(name) - if undeclared: - gap[name] = sorted(undeclared) - return dict(sorted(gap.items())) diff --git a/tests/conformance/_schema_parity.py b/tests/conformance/_schema_parity.py index 00e4e2261..02f142c26 100644 --- a/tests/conformance/_schema_parity.py +++ b/tests/conformance/_schema_parity.py @@ -24,6 +24,7 @@ import sys from collections.abc import Callable from dataclasses import dataclass +from functools import cache from typing import Any from adcp.validation.schema_loader import _ensure_state, _make_ref_resolver @@ -476,6 +477,75 @@ def describe(self) -> str: return f"{self.tool}{where} = {self.mutation!r}: {self.direction} ({reason})" +# --------------------------------------------------------------------------- +# The one schema this SDK deliberately does not reproduce. +# --------------------------------------------------------------------------- + +#: ``core/canonical-format-kind.json`` declares a closed 16-member ``enum`` and, +#: in the same file, requires a consumer to retain an unknown value and not fail +#: the payload. A closed enum cannot do both, so every reference to it generates +#: ``str`` and the vocabulary is governed by a validator instead — strict on a +#: request model, absent on a read-back. See +#: :mod:`adcp.types.canonical_creative`'s module docstring and +#: ``OPEN_VOCABULARY_SCHEMAS`` in ``scripts/generate_types.py``. +_OPEN_VOCABULARY_SCHEMA = "core/canonical-format-kind.json" + + +@cache +def open_vocabulary_properties() -> frozenset[str]: + """Property names whose declaration references the open-vocabulary schema. + + Derived by walking the whole pinned bundle, not listed: a schema that + starts referencing the same enum under a further name is then covered + without an edit, which is what keeps the exemption below one entry rather + than a growing list of field names. The WHOLE bundle and not only the tool + roots, because the declaration is often a file away — ``calibrate_content`` + reaches ``format_kind`` through ``$ref`` to + ``content-standards/artifact.json``. At pin 3.2.1 this finds + ``format_kind``, ``format_kinds`` and ``input_format_kinds``. + """ + state = _ensure_state(None) + names: set[str] = set() + + def walk(node: Any, enclosing: str | None) -> None: + if isinstance(node, dict): + ref = node.get("$ref") + if isinstance(ref, str) and ref.endswith(_OPEN_VOCABULARY_SCHEMA) and enclosing: + names.add(enclosing) + for key, value in node.items(): + if key == "properties" and isinstance(value, dict): + for prop, declaration in value.items(): + walk(declaration, prop) + else: + walk(value, enclosing) + elif isinstance(node, list): + for item in node: + walk(item, enclosing) + + for file in sorted(state.root.root.rglob("*.json")): + if file.name == "index.json": + continue + walk(json.loads(file.read_text()), None) + assert names, f"no property references {_OPEN_VOCABULARY_SCHEMA} — the schema moved" + return frozenset(names) + + +def refused_property(schema_path: str) -> str: + """The property name whose keyword refused, read off a schema pointer. + + ``#/properties/artifact/properties/format_kind/enum`` and + ``#/properties/campaign/properties/format_kinds/items/enum`` both name the + property two or three segments up, so the trailing keyword and ``items`` + are dropped before reading it. + """ + parts = [part for part in schema_path.split("/") if part not in ("", "#")] + if parts and parts[-1] == "enum": + parts.pop() + if parts and parts[-1] == "items": + parts.pop() + return parts[-1] if parts else "" + + def _is_number(value: Any) -> bool: return isinstance(value, (int, float)) and not isinstance(value, bool) @@ -547,6 +617,28 @@ def _is_numeric_string(value: Any) -> bool: "canonical_model_refuses_legacy_creative_identity": lambda d: ( d.direction == SCHEMA_ACCEPTS and d.error_types == frozenset({"value_error"}) ), + # ``core/canonical-format-kind.json`` is the one schema this SDK + # deliberately does not reproduce. It declares a closed 16-member ``enum`` + # and, in the same file, requires a consumer to retain an unknown + # ``format_kind`` and not fail the payload — "the producer-side enum stays + # closed; the consumer-side enum stays open". The schema knows the rule is + # directional and then encodes it as one closed enum, which cannot carry + # that, so every reference generates ``str`` and no model refuses a value: + # a pinned SDK cannot tell a kind a seller invented from a kind defined + # after its pin, and refusing the second to prevent the first would make + # this library's version a ceiling on what the protocol permits. Checking + # the vocabulary is the caller's, through + # ``adcp.types.is_canonical_format_kind``, whose vocabulary is a parameter + # because only the caller knows which spec version its counterpart speaks. + # Every model therefore accepts a value the bundled enum refuses. The + # property names are derived from the bundle (``open_vocabulary_properties``) + # so this stays ONE entry covering ONE schema rather than a list of fields. + # Closing it needs the upstream fix: adcontextprotocol/adcp#7929. + "format_kind_is_an_open_vocabulary_by_decision": lambda d: ( + d.direction == MODEL_ACCEPTS + and d.keyword == "enum" + and refused_property(d.schema_path) in open_vocabulary_properties() + ), # The model marks a field required that the schema leaves optional. "model_requires_a_field_the_schema_does_not": lambda d: ( d.direction == SCHEMA_ACCEPTS and d.error_types == frozenset({"missing"}) diff --git a/tests/conformance/reporting/_production_packaging.py b/tests/conformance/reporting/_production_packaging.py index 2396f5836..a9991b106 100644 --- a/tests/conformance/reporting/_production_packaging.py +++ b/tests/conformance/reporting/_production_packaging.py @@ -101,10 +101,10 @@ def production_modules(): "types/base.py", "types/v32.py", "types/versioned.py", - "types/generated_poc/core/reporting_delivery_capabilities.py", - "types/generated_poc/core/reporting_delivery_config.py", - "types/generated_poc/media_buy/get_media_buy_delivery_request.py", - "types/generated_poc/bundled/protocol/get_adcp_capabilities_response.py", + "types/domains/core/reporting_delivery_capabilities.py", + "types/domains/core/reporting_delivery_config.py", + "types/domains/media_buy/get_media_buy_delivery_request.py", + "types/domains/bundled/protocol/get_adcp_capabilities_response.py", ) ] return { diff --git a/tests/conformance/reporting/test_reporting_production_harness.py b/tests/conformance/reporting/test_reporting_production_harness.py index cdc9a5b42..abefa31c1 100644 --- a/tests/conformance/reporting/test_reporting_production_harness.py +++ b/tests/conformance/reporting/test_reporting_production_harness.py @@ -39,9 +39,7 @@ def test_installed_progress_preserves_json_protocol_and_real_pytest_phase_timing "@pytest.mark.parametrize('value', ['private-parameter-canary'])\n" "def test_example(value):\n assert value\n" ) - body = ( - BOOT - + """ + body = BOOT + """ import contextlib, io, pytest progress.start('collection') with contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO()): @@ -53,7 +51,6 @@ def test_installed_progress_preserves_json_protocol_and_real_pytest_phase_timing progress.close() print(json.dumps({'pytest_exit': int(code)})) """ - ) value = settings(tmp_path) result = run_step( [sys.executable, "-I", "-c", body], @@ -113,9 +110,7 @@ def test_installed_phase_deadline_reports_active_case_and_kills_descendant( tmp_path, monkeypatch, capsys, new_session ): monkeypatch.delenv("ADCP_PG_TEST_URL", raising=False) - body = ( - BOOT - + """ + body = BOOT + """ import subprocess, threading child_code = ( 'import signal,time;' @@ -129,7 +124,6 @@ def test_installed_phase_deadline_reports_active_case_and_kills_descendant( print('private-provider-canary', file=sys.stderr, flush=True) threading.Event().wait() """ - ) value = {**settings(tmp_path), "new_session": new_session} with pytest.raises(AssertionError, match="deadline_kind=phase phase=call"): run_step( @@ -158,15 +152,12 @@ def test_installed_phase_deadline_reports_active_case_and_kills_descendant( def test_installed_aggregate_deadline_is_not_reset_by_continued_progress(tmp_path, monkeypatch): monkeypatch.delenv("ADCP_PG_TEST_URL", raising=False) value = settings(tmp_path) - body = ( - BOOT - + """ + body = BOOT + """ import time while True: progress.start('call', nodeid='tests/test_busy.py::test_busy') time.sleep(0.02) """ - ) with pytest.raises(AssertionError, match="deadline_kind=aggregate"): run_step( [sys.executable, "-I", "-c", body], @@ -206,9 +197,7 @@ def test_installed_cleanup_owns_escaped_descendant_after_parent_exits( ): monkeypatch.delenv("ADCP_PG_TEST_URL", raising=False) value = {**settings(tmp_path), "inherit_pipes": inherit_pipes} - body = ( - BOOT - + """ + body = BOOT + """ import os, subprocess, time code = ( 'import json,os,signal,time;from pathlib import Path;' @@ -228,7 +217,6 @@ def test_installed_cleanup_owns_escaped_descendant_after_parent_exits( progress.close() os._exit(0) """ - ) # A different session must remain alive while the owned orphan is killed. unrelated = subprocess.Popen( [sys.executable, "-I", "-c", "import time;time.sleep(60)"], start_new_session=True @@ -312,9 +300,7 @@ def test_installed_phase_deadline_captures_database_wait_and_cleans_owned_sessio pytest.skip("ADCP_PG_TEST_URL supplies the isolated test database") key = secrets.randbits(63) value = {**settings(tmp_path), "lock_key": key, "sessions": 4} - body = ( - BOOT - + """ + body = BOOT + """ import os, psycopg, threading, time connections = [psycopg.connect(os.environ['ADCP_PG_TEST_URL'], autocommit=True) for _ in range(settings['sessions'])] @@ -335,7 +321,6 @@ def test_installed_phase_deadline_captures_database_wait_and_cleans_owned_sessio raise AssertionError('database wait barrier') threading.Event().wait() """ - ) with psycopg.connect(url, autocommit=True) as holder: holder.execute("SELECT pg_advisory_lock(%s)", (key,)) with pytest.raises(AssertionError, match="deadline_kind=phase phase=call"): diff --git a/tests/conformance/signing/test_autosign.py b/tests/conformance/signing/test_autosign.py index 1bd6aa12b..174193e7f 100644 --- a/tests/conformance/signing/test_autosign.py +++ b/tests/conformance/signing/test_autosign.py @@ -14,7 +14,7 @@ from adcp.signing import SigningConfig, operation_needs_signing from adcp.signing.autosign import signing_profile_for_adcp_version from adcp.signing.crypto import ALG_ED25519, ALG_ES256 -from adcp.types.generated_poc.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.protocol.get_adcp_capabilities_response import ( CoversContentDigest, RequestSigning, ) diff --git a/tests/conformance/signing/test_autosign_e2e.py b/tests/conformance/signing/test_autosign_e2e.py index 149d63635..7d1575926 100644 --- a/tests/conformance/signing/test_autosign_e2e.py +++ b/tests/conformance/signing/test_autosign_e2e.py @@ -41,7 +41,7 @@ ) from adcp.signing.autosign import current_operation from adcp.types.core import AgentConfig, Protocol -from adcp.types.generated_poc.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.protocol.get_adcp_capabilities_response import ( Adcp, CoversContentDigest, GetAdcpCapabilitiesResponse, diff --git a/tests/conformance/signing/test_autosign_hook.py b/tests/conformance/signing/test_autosign_hook.py index ea24f2960..10a8995ff 100644 --- a/tests/conformance/signing/test_autosign_hook.py +++ b/tests/conformance/signing/test_autosign_hook.py @@ -30,7 +30,7 @@ ) from adcp.signing.autosign import current_operation from adcp.types.core import AgentConfig, Protocol -from adcp.types.generated_poc.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.protocol.get_adcp_capabilities_response import ( Adcp, CoversContentDigest, GetAdcpCapabilitiesResponse, diff --git a/tests/conformance/signing/test_install_signing_event_hook.py b/tests/conformance/signing/test_install_signing_event_hook.py index 25ae3422f..7326f75cf 100644 --- a/tests/conformance/signing/test_install_signing_event_hook.py +++ b/tests/conformance/signing/test_install_signing_event_hook.py @@ -27,7 +27,7 @@ signing_operation, verify_request_signature, ) -from adcp.types.generated_poc.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.protocol.get_adcp_capabilities_response import ( CoversContentDigest, RequestSigning, ) diff --git a/tests/conformance/signing/test_webhook_receiver.py b/tests/conformance/signing/test_webhook_receiver.py index 709feb1a4..e57b293c6 100644 --- a/tests/conformance/signing/test_webhook_receiver.py +++ b/tests/conformance/signing/test_webhook_receiver.py @@ -16,7 +16,7 @@ from adcp.server.idempotency import MemoryBackend, WebhookDedupStore from adcp.signing import StaticJwksResolver, private_key_from_jwk -from adcp.types.generated_poc.core.mcp_webhook_payload import McpWebhookPayload +from adcp.types.domains.core.mcp_webhook_payload import McpWebhookPayload from adcp.webhooks import ( LegacyHmacFallback, LegacyWebhookHmacOptions, diff --git a/tests/fixtures/public_api_snapshot.json b/tests/fixtures/public_api_snapshot.json index 392254b83..794f0af72 100644 --- a/tests/fixtures/public_api_snapshot.json +++ b/tests/fixtures/public_api_snapshot.json @@ -317,8 +317,8 @@ "LegacyCreativeAsset": "core.creative_asset:CreativeAsset", "LegacyCreativeFilters": "core.creative_filters:CreativeFilters", "LegacyFormat": "core.format:Format", - "LegacyFormatId": "adcp.types.legacy", - "LegacyFormatReferenceStructuredObject": "adcp.types.legacy:LegacyFormatId", + "LegacyFormatId": "core.format_id:FormatReferenceStructuredObject", + "LegacyFormatReferenceStructuredObject": "core.format_id:FormatReferenceStructuredObject", "LegacyGetCreativeDeliveryResponse": "creative.get_creative_delivery_response:GetCreativeDeliveryResponse", "LegacyGetMediaBuyDeliveryResponse": "media_buy.get_media_buy_delivery_response:GetMediaBuyDeliveryResponse", "LegacyGetMediaBuysResponse": "media_buy.get_media_buys_response:GetMediaBuysResponse", @@ -1145,12 +1145,15 @@ "LegacyBuildCreativeResponse6": "media_buy.build_creative_response:BuildCreativeResponse6", "LegacyBuildCreativeSubmittedResponse": "media_buy.build_creative_response:BuildCreativeResponse6", "LegacyBuildCreativeSuccessResponse": "media_buy.build_creative_response:BuildCreativeResponse1", + "LegacyCreateMediaBuyErrorResponse": "media_buy.create_media_buy_response:CreateMediaBuyResponse2", "LegacyCreateMediaBuyRequest": "media_buy.create_media_buy_request:CreateMediaBuyRequest", + "LegacyCreateMediaBuySubmittedResponse": "media_buy.create_media_buy_response:CreateMediaBuyResponse3", + "LegacyCreateMediaBuySuccessResponse": "media_buy.create_media_buy_response:CreateMediaBuyResponse1", "LegacyCreativeAsset": "core.creative_asset:CreativeAsset", "LegacyCreativeFilters": "core.creative_filters:CreativeFilters", "LegacyFormat": "core.format:Format", - "LegacyFormatId": "adcp.types.legacy", - "LegacyFormatReferenceStructuredObject": "adcp.types.legacy:LegacyFormatId", + "LegacyFormatId": "core.format_id:FormatReferenceStructuredObject", + "LegacyFormatReferenceStructuredObject": "core.format_id:FormatReferenceStructuredObject", "LegacyGetCreativeDeliveryResponse": "creative.get_creative_delivery_response:GetCreativeDeliveryResponse", "LegacyGetMediaBuyDeliveryResponse": "media_buy.get_media_buy_delivery_response:GetMediaBuyDeliveryResponse", "LegacyGetMediaBuysResponse": "media_buy.get_media_buys_response:GetMediaBuysResponse", @@ -1170,7 +1173,9 @@ "LegacyPreviewCreativeResponse1": "creative.preview_creative_response:PreviewCreativeResponse1", "LegacyPreviewCreativeResponse2": "creative.preview_creative_response:PreviewCreativeResponse2", "LegacyPreviewCreativeResponse3": "creative.preview_creative_response:PreviewCreativeResponse3", + "LegacyPreviewCreativeResponse4": "creative.preview_creative_response:PreviewCreativeResponse4", "LegacyPreviewCreativeSingleResponse": "creative.preview_creative_response:PreviewCreativeResponse1", + "LegacyPreviewCreativeSubmittedResponse": "creative.preview_creative_response:PreviewCreativeResponse4", "LegacyPreviewCreativeVariantResponse": "creative.preview_creative_response:PreviewCreativeResponse3", "LegacyProduct": "core.product:Product", "LegacyProductFilters": "core.product_filters:ProductFilters", @@ -1179,6 +1184,7 @@ "LegacySyncCreativesRequest": "creative.sync_creatives_request:SyncCreativesRequest", "LegacyUpdateMediaBuyErrorResponse": "media_buy.update_media_buy_response:UpdateMediaBuyResponse2", "LegacyUpdateMediaBuyRequest": "media_buy.update_media_buy_request:UpdateMediaBuyRequest", + "LegacyUpdateMediaBuySubmittedResponse": "media_buy.update_media_buy_response:UpdateMediaBuyResponse3", "LegacyUpdateMediaBuySuccessResponse": "media_buy.update_media_buy_response:UpdateMediaBuyResponse1", "ListAccountChangesRequest": "account.list_account_changes_request", "ListAccountChangesResponse": "account.list_account_changes_response", @@ -1654,6 +1660,7 @@ "buyer": "module:adcp.types.buyer", "creative": "module:adcp.types.creative", "generated": "module:adcp.types._generated", + "is_canonical_format_kind": "adcp.types.canonical_creative", "media_buy": "module:adcp.types.media_buy", "project_geo_postal_areas": "adcp.types.projections", "protocol": "module:adcp.types.protocol", diff --git a/tests/test_account_projection.py b/tests/test_account_projection.py index 0ad89bfe0..1fa00a527 100644 --- a/tests/test_account_projection.py +++ b/tests/test_account_projection.py @@ -23,7 +23,7 @@ Account, BusinessEntity, ) -from adcp.types.generated_poc.core.business_entity import Address, Bank +from adcp.types.domains.core.business_entity import Address, Bank def _bank_fixture() -> Bank: diff --git a/tests/test_account_projections.py b/tests/test_account_projections.py index 4deb8c256..f30e903a6 100644 --- a/tests/test_account_projections.py +++ b/tests/test_account_projections.py @@ -18,7 +18,7 @@ from pydantic import ValidationError from adcp.types import Account, BusinessEntity -from adcp.types.generated_poc.core.business_entity import Address, Bank +from adcp.types.domains.core.business_entity import Address, Bank from adcp.types.projections import ( AccountResponse, BusinessEntityResponse, diff --git a/tests/test_account_v3_wire.py b/tests/test_account_v3_wire.py index 305fda7dc..36b8e6e2f 100644 --- a/tests/test_account_v3_wire.py +++ b/tests/test_account_v3_wire.py @@ -72,15 +72,15 @@ # `GovernanceAgent`) and the response-side variant (no authentication # field). Importing the response-side type explicitly so the # schema-shape regression test can assert the input/output split. -from adcp.types.generated_poc.core.account import ( +from adcp.types.domains.core.account import ( GovernanceAgent as ResponseGovernanceAgent, ) # Address and Bank are sub-models of BusinessEntity. The codegen places -# them on the BusinessEntity-owning module; pulling from generated_poc +# them on the BusinessEntity-owning module; pulling from a domain module # is acceptable in tests (the type-import layering rule applies to # src/, not tests/). -from adcp.types.generated_poc.core.business_entity import Address, Bank +from adcp.types.domains.core.business_entity import Address, Bank # --------------------------------------------------------------------------- # Fixtures @@ -939,7 +939,7 @@ def test_sync_governance_typed_request_agent_has_authentication() -> None: The publicly-exported ``GovernanceAgent`` from ``adcp.types`` is the request-side variant (carries ``authentication``); the response-side variant lives at - ``adcp.types.generated_poc.core.account.GovernanceAgent`` and has + ``adcp.types.domains.core.account.GovernanceAgent`` and has no ``authentication`` field. The framework's wire-emit projection strips ``authentication`` regardless of which shape the adopter constructs.""" diff --git a/tests/test_adagents.py b/tests/test_adagents.py index 4a5c72502..dea4dbf7b 100644 --- a/tests/test_adagents.py +++ b/tests/test_adagents.py @@ -4036,7 +4036,7 @@ def test_fetch_parser_enforces_reference_renderer_origin(self): assert parsed == data def test_generated_publisher_catalog_does_not_invent_registry_role(self): - from adcp.types.generated_poc.adagents import AdcpAgentsAuthorization + from adcp.types.domains.adagents import AdcpAgentsAuthorization model = TypeAdapter(AdcpAgentsAuthorization).validate_python( { @@ -4054,7 +4054,7 @@ def test_generated_publisher_catalog_does_not_invent_registry_role(self): assert "catalog_role" not in model.model_dump(mode="json", exclude_none=True) def test_generated_catalog_validates_nested_reference_renderer(self): - from adcp.types.generated_poc.adagents import AdcpAgentsAuthorization + from adcp.types.domains.adagents import AdcpAgentsAuthorization with pytest.raises(ValueError, match="Field required"): TypeAdapter(AdcpAgentsAuthorization).validate_python( @@ -4308,7 +4308,7 @@ def test_validate_accepts_pydantic_model_instance(self): # model now requires `publisher_domain`. The dict-layer helper still # implements the SDK-side compact-form / XOR contract (PR #750, #759) # for adopters consuming raw adagents.json bytes. - from adcp.types.generated_poc.core.publisher_property_selector import ( + from adcp.types.domains.core.publisher_property_selector import ( PublisherPropertySelector1, ) from adcp.validation import ( diff --git a/tests/test_adcp_3_1_14_schema_backport.py b/tests/test_adcp_3_1_14_schema_backport.py index 39002e44a..68d3aadbf 100644 --- a/tests/test_adcp_3_1_14_schema_backport.py +++ b/tests/test_adcp_3_1_14_schema_backport.py @@ -1,11 +1,11 @@ """Regression coverage for enum-reference fixes in AdCP 3.1.14.""" from adcp.types import AdcpProtocol, CatalogType, PropertyType -from adcp.types.generated_poc.core.registry_event import BadgeRole -from adcp.types.generated_poc.formats.canonical.sponsored_placement import ( +from adcp.types.domains.core.registry_event import BadgeRole +from adcp.types.domains.formats.canonical.sponsored_placement import ( CanonicalFormatSponsoredPlacementRetailMediaCatalogDriven, ) -from adcp.types.generated_poc.protocol.get_adcp_capabilities_response import TrustedMatch +from adcp.types.domains.protocol.get_adcp_capabilities_response import TrustedMatch def test_registry_badge_role_uses_canonical_protocol_enum() -> None: diff --git a/tests/test_adopter_public_paths.py b/tests/test_adopter_public_paths.py index 09f35d85d..edd22bc3c 100644 --- a/tests/test_adopter_public_paths.py +++ b/tests/test_adopter_public_paths.py @@ -1,6 +1,6 @@ """Every generated type a production adopter imports has a public path. -The names below are the complete set of ``adcp.types.generated_poc`` imports in +The names below are the complete set of ``adcp.types.domains`` imports in one production AdCP seller — 110 ``(module, type)`` pairs over 105 distinct names across 39 files. They are in this suite because they are evidence rather than a hypothesis: a real application reached into a private namespace 110 times @@ -28,9 +28,9 @@ from scripts.consolidate_exports import schema_domain -#: ``(generated module, type name)``, every ``generated_poc`` import in the +#: ``(generated module, type name)``, every deep generated import in the #: adopter's ``src/`` tree. Regenerate by grepping -#: ``from adcp.types.generated_poc`` there; do not curate it, the point is that +#: ``from adcp.types.domains`` there; do not curate it, the point is that #: it is unfiltered. ADOPTER_IMPORTS: tuple[tuple[str, str], ...] = ( ("account.sync_accounts_request", "Accounts"), @@ -151,7 +151,7 @@ def _public_paths(module_name: str, type_name: str) -> list[str]: import adcp.types import adcp.types.domains - target = getattr(importlib.import_module(f"adcp.types.generated_poc.{module_name}"), type_name) + target = getattr(importlib.import_module(f"adcp.types.domains.{module_name}"), type_name) paths = [] if getattr(adcp.types, type_name, None) is target: paths.append(f"adcp.types.{type_name}") @@ -189,7 +189,7 @@ def test_every_adopter_import_has_a_public_path_to_the_same_class( paths = _public_paths(module_name, type_name) assert paths, ( f"{module_name}.{type_name} has no public path — an adopter has to reach into " - "adcp.types.generated_poc for it, which the migration guide forbids" + "adcp.types.domains for it, which the migration guide forbids" ) diff --git a/tests/test_asset_aliases_stable.py b/tests/test_asset_aliases_stable.py index 1a4948c50..9be3da7a3 100644 --- a/tests/test_asset_aliases_stable.py +++ b/tests/test_asset_aliases_stable.py @@ -134,7 +134,7 @@ def test_format_category_module_raises_migration_pointer() -> None: # schemas in AdCP 3.0. Importing the old module path now raises a # guided ImportError instead of ModuleNotFoundError. with pytest.raises(ImportError, match="MIGRATION_v3_to_v4"): - from adcp.types.generated_poc.enums.format_category import ( # noqa: F401 + from adcp.types.domains.enums.format_category import ( # noqa: F401 FormatCategory, ) @@ -184,9 +184,9 @@ def test_content_type_resolves_to_generated_payload_class( content_name: str, expected_module_suffix: str ) -> None: # Each public `Content` must resolve to the corresponding - # generated payload class under ``generated_poc/core/assets/*``. + # generated payload class under ``domains/core/assets/*``. cls = getattr(adcp_types, content_name) - expected_module = f"adcp.types.generated_poc.core.assets.{expected_module_suffix}" + expected_module = f"adcp.types.domains.core.assets.{expected_module_suffix}" assert cls.__module__ == expected_module, ( f"{content_name} resolved to {cls.__module__}.{cls.__name__}; " f"expected a class from {expected_module}." diff --git a/tests/test_authorized_agents_public_surface.py b/tests/test_authorized_agents_public_surface.py index 67beb508f..318ee4a8a 100644 --- a/tests/test_authorized_agents_public_surface.py +++ b/tests/test_authorized_agents_public_surface.py @@ -9,7 +9,7 @@ type-checks as its neighbour. Both halves of the contract are pinned here. The seven public symbols must be -the raw ``generated_poc.adagents`` variant classes at runtime (this module), +the raw ``domains.adagents`` variant classes at runtime (this module), and must infer as those same classes under ``mypy --strict`` (``tests/type_checks/authorized_agents_variants.py``). @@ -25,9 +25,9 @@ import pytest -from adcp.types import generated_poc +from adcp.types import domains -# Public symbol -> the ``generated_poc.adagents`` class it must be. +# Public symbol -> the ``domains.adagents`` class it must be. EXPECTED_VARIANTS = { "AuthorizedAgents": "AuthorizedAgents1", "AuthorizedAgentsByPropertyId": "AuthorizedAgents1", @@ -58,7 +58,7 @@ def test_public_symbol_is_the_raw_generated_variant(public_name: str, variant_na """Each public symbol is the raw variant class, not a shifted rebinding.""" import adcp.types - assert getattr(adcp.types, public_name) is getattr(generated_poc.adagents, variant_name) + assert getattr(adcp.types, public_name) is getattr(domains.adagents, variant_name) @pytest.mark.parametrize(("public_name", "discriminator"), sorted(EXPECTED_DISCRIMINATORS.items())) @@ -89,7 +89,7 @@ def test_authorized_agent_union_covers_every_variant_in_order() -> None: """The public union is exactly the six raw variants, variant 1 through 6.""" import adcp.types - expected = tuple(getattr(generated_poc.adagents, f"AuthorizedAgents{n}") for n in range(1, 7)) + expected = tuple(getattr(domains.adagents, f"AuthorizedAgents{n}") for n in range(1, 7)) assert adcp.types.AuthorizedAgent.__args__ == expected @@ -119,7 +119,7 @@ def test_property_ids_variant_validates_its_wire_fragment() -> None: "package_first": "import adcp", "eager_first": "import adcp.types._eager", "generated_first": "import adcp.types._generated", - "generated_poc_first": "from adcp.types.generated_poc import adagents", + "domain_module_first": "from adcp.types.domains import adagents", "partial_module_first": "import adcp.types.seller", } @@ -128,7 +128,7 @@ def test_property_ids_variant_validates_its_wire_fragment() -> None: import adcp import adcp.types import adcp.types.aliases -from adcp.types.generated_poc import adagents +from adcp.types.domains import adagents names = {names!r} for name in names: @@ -156,6 +156,6 @@ def test_bindings_are_stable_across_import_orders(order: str) -> None: resolved = dict(line.split(" ", 1) for line in result.stdout.strip().splitlines()) assert resolved == { - name: f"adcp.types.generated_poc.adagents.{variant}" + name: f"adcp.types.domains.adagents.{variant}" for name, variant in EXPECTED_VARIANTS.items() } diff --git a/tests/test_backward_compat.py b/tests/test_backward_compat.py index f8fca3167..9ed5fe5e9 100644 --- a/tests/test_backward_compat.py +++ b/tests/test_backward_compat.py @@ -130,7 +130,7 @@ def test_brand_identity_is_public_everywhere(self): from adcp import BrandIdentity as RootBrandIdentity from adcp.types import BrandIdentity from adcp.types.buyer import BrandIdentity as BuyerBrandIdentity - from adcp.types.generated_poc.brand_discovery import Brand + from adcp.types.domains.brand_discovery import Brand assert RootBrandIdentity is BrandIdentity is BuyerBrandIdentity is Brand @@ -144,7 +144,7 @@ def test_brand_identity_preserves_brand_json_shape(self): def test_brand_identity_field_contract_and_collision_separation(self): from adcp.types import BrandIdentity - from adcp.types.generated_poc.protocol.get_adcp_capabilities_response import ( + from adcp.types.domains.protocol.get_adcp_capabilities_response import ( Brand as CapabilitiesBrand, ) diff --git a/tests/test_beta3_url_constraints.py b/tests/test_beta3_url_constraints.py index cec952185..4be0ca8d3 100644 --- a/tests/test_beta3_url_constraints.py +++ b/tests/test_beta3_url_constraints.py @@ -5,7 +5,7 @@ from adcp import PackageRequest, PlacementPresentationReference, PublisherDesignatedPreviewProvider from adcp.types import ReferenceRendererProvenance -from adcp.types.generated_poc.core.placement_presentation import ImageRef +from adcp.types.domains.core.placement_presentation import ImageRef _DIGEST = "sha256:" + "a" * 64 diff --git a/tests/test_canceled_literal_default.py b/tests/test_canceled_literal_default.py index 85db2ba7c..b1b851d3d 100644 --- a/tests/test_canceled_literal_default.py +++ b/tests/test_canceled_literal_default.py @@ -20,7 +20,7 @@ class TestUpdateMediaBuyRequestCanceledDefault: } def test_canceled_defaults_to_none_when_omitted(self) -> None: - from adcp.types.generated_poc.media_buy.update_media_buy_request import ( + from adcp.types.domains.media_buy.update_media_buy_request import ( UpdateMediaBuyRequest, ) @@ -28,7 +28,7 @@ def test_canceled_defaults_to_none_when_omitted(self) -> None: assert req.canceled is None def test_canceled_true_still_accepted(self) -> None: - from adcp.types.generated_poc.media_buy.update_media_buy_request import ( + from adcp.types.domains.media_buy.update_media_buy_request import ( UpdateMediaBuyRequest, ) @@ -37,7 +37,7 @@ def test_canceled_true_still_accepted(self) -> None: def test_canceled_none_excluded_from_wire_payload(self) -> None: """When canceled is None, model_dump(exclude_none=True) omits it — no cancellation on wire.""" - from adcp.types.generated_poc.media_buy.update_media_buy_request import ( + from adcp.types.domains.media_buy.update_media_buy_request import ( UpdateMediaBuyRequest, ) @@ -47,7 +47,7 @@ def test_canceled_none_excluded_from_wire_payload(self) -> None: def test_canceled_true_present_in_wire_payload(self) -> None: """Explicit canceled=True must appear in the wire payload to trigger cancellation.""" - from adcp.types.generated_poc.media_buy.update_media_buy_request import ( + from adcp.types.domains.media_buy.update_media_buy_request import ( UpdateMediaBuyRequest, ) @@ -59,7 +59,7 @@ def test_canceled_false_rejected(self) -> None: """Literal[True] still rejects False — the field is a one-way commit signal.""" from pydantic import ValidationError - from adcp.types.generated_poc.media_buy.update_media_buy_request import ( + from adcp.types.domains.media_buy.update_media_buy_request import ( UpdateMediaBuyRequest, ) @@ -71,26 +71,26 @@ class TestPackageUpdateCanceledDefault: """PackageUpdate.canceled must default to None (non-destructive).""" def test_canceled_defaults_to_none_when_omitted(self) -> None: - from adcp.types.generated_poc.media_buy.package_update import PackageUpdate + from adcp.types.domains.media_buy.package_update import PackageUpdate pkg = PackageUpdate(package_id="pkg-1") assert pkg.canceled is None def test_canceled_true_still_accepted(self) -> None: - from adcp.types.generated_poc.media_buy.package_update import PackageUpdate + from adcp.types.domains.media_buy.package_update import PackageUpdate pkg = PackageUpdate(package_id="pkg-1", canceled=True) assert pkg.canceled is True def test_canceled_none_excluded_from_wire_payload(self) -> None: - from adcp.types.generated_poc.media_buy.package_update import PackageUpdate + from adcp.types.domains.media_buy.package_update import PackageUpdate pkg = PackageUpdate(package_id="pkg-1") payload = pkg.model_dump(mode="json", exclude_none=True) assert "canceled" not in payload def test_canceled_true_present_in_wire_payload(self) -> None: - from adcp.types.generated_poc.media_buy.package_update import PackageUpdate + from adcp.types.domains.media_buy.package_update import PackageUpdate pkg = PackageUpdate(package_id="pkg-1", canceled=True) payload = pkg.model_dump(mode="json", exclude_none=True) diff --git a/tests/test_canonical_catalog_precedence.py b/tests/test_canonical_catalog_precedence.py index b80e8c466..0337b9ca5 100644 --- a/tests/test_canonical_catalog_precedence.py +++ b/tests/test_canonical_catalog_precedence.py @@ -83,4 +83,4 @@ def test_bundled_aao_catalog_keeps_its_unique_bare_id_fallback() -> None: ) assert result.declaration is not None - assert result.declaration.format_kind.value == "image" + assert result.declaration.format_kind == "image" diff --git a/tests/test_canonical_creatives_rc3.py b/tests/test_canonical_creatives_rc3.py index 5d2e7a9f4..a68518dee 100644 --- a/tests/test_canonical_creatives_rc3.py +++ b/tests/test_canonical_creatives_rc3.py @@ -27,7 +27,7 @@ from adcp.decisioning.account_projection import strip_credentials_from_wire_result from adcp.server.responses import list_creatives_response from adcp.types.canonical_creative import PRIMARY_CANONICAL_MODELS -from adcp.types.generated_poc.core.media_buy_features import MediaBuyFeatures +from adcp.types.domains.core.media_buy_features import MediaBuyFeatures from adcp.utils import get_format_assets _GOLDEN = Path(__file__).parent / "fixtures/canonical/typescript-13.0.0-rc.3-option-ids.json" @@ -68,7 +68,12 @@ def test_root_surface_is_canonical_and_legacy_is_explicit() -> None: assert not hasattr(adcp, "FormatId") assert not hasattr(adcp.types.aliases, "FormatId") assert adcp.Format is adcp.ProductFormatDeclaration - assert adcp.LegacyFormatId.__name__ == "LegacyFormatId" + # #1398: the explicit legacy name IS the generated format reference. It used + # to be a subclass of it, asserted here BY NAME — and the subclass had drifted + # into weakening three of its parent's validators, so the public name and the + # class the SDK's own model fields declare disagreed about the same payload. + assert adcp.LegacyFormatId is adcp.types.legacy.FormatReferenceStructuredObject + assert adcp.LegacyFormatId.__module__ == "adcp.types.domains.core.format_id" assert "format_kind" in adcp.Format.model_fields assert "format_id" not in adcp.Format.model_fields assert "format_ids" not in adcp.Product.model_fields @@ -252,7 +257,7 @@ def test_exact_owner_and_id_wins_and_bare_id_collision_fails_closed() -> None: catalog=catalog, ) assert exact.declaration is not None - assert exact.declaration.format_kind.value == "image" + assert exact.declaration.format_kind == "image" ambiguous = project_legacy_format_id( {"agent_url": "https://seller.example/formats", "id": "shared"}, @@ -278,7 +283,7 @@ def test_converter_overrides_unique_bare_id_compatibility_inference() -> None: }, ) assert result.declaration is not None - assert result.declaration.format_kind.value == "display_tag" + assert result.declaration.format_kind == "display_tag" assert [ref.model_dump(mode="json") for ref in result.declaration.legacy_format_refs] == [ source ] diff --git a/tests/test_canonical_formats_public_api.py b/tests/test_canonical_formats_public_api.py index ca2d411b7..f816e3875 100644 --- a/tests/test_canonical_formats_public_api.py +++ b/tests/test_canonical_formats_public_api.py @@ -1,7 +1,7 @@ """Public-API surface for canonical-formats types (AdCP 3.1). Guards that the canonical-formats types are reachable from -:mod:`adcp.types` (rather than only from ``generated_poc``). Failures +:mod:`adcp.types` (rather than only from a domain path). Failures here mean adopter code that does ``from adcp.types import …`` will break — the public surface is part of the contract. """ diff --git a/tests/test_canonical_formats_roundtrip.py b/tests/test_canonical_formats_roundtrip.py index 8c7ec5d5b..68b4fbf4d 100644 --- a/tests/test_canonical_formats_roundtrip.py +++ b/tests/test_canonical_formats_roundtrip.py @@ -35,6 +35,7 @@ ) from adcp.types import ( CanonicalFormatKind, + is_canonical_format_kind, ProductFormatDeclaration, ) from adcp.types.legacy import LegacyFormatId as FormatId @@ -161,7 +162,7 @@ def test_v2_product_declarations_are_constructable(fixture_name: str) -> None: assert declarations # Every declaration carries a valid kind from the canonical enum. for d in declarations: - assert isinstance(d.format_kind, CanonicalFormatKind) + assert is_canonical_format_kind(d.format_kind) # --------------------------------------------------------------------------- diff --git a/tests/test_canonical_formats_v1_to_v2.py b/tests/test_canonical_formats_v1_to_v2.py index ec3426d39..7c2fd2a3f 100644 --- a/tests/test_canonical_formats_v1_to_v2.py +++ b/tests/test_canonical_formats_v1_to_v2.py @@ -38,7 +38,7 @@ def test_explicit_canonical_annotation_wins() -> None: } result = project_v1_format_to_declaration(v1) assert result.declaration is not None - assert result.declaration.format_kind is CanonicalFormatKind.image + assert result.declaration.format_kind == CanonicalFormatKind.image assert result.advisories == [] # asset_source from canonical threads into params: assert result.declaration.params.get("asset_source") == "buyer_uploaded" @@ -58,7 +58,7 @@ def test_explicit_canonical_bypasses_registry_with_no_advisory() -> None: } result = project_v1_format_to_declaration(v1) assert result.declaration is not None - assert result.declaration.format_kind is CanonicalFormatKind.html5 + assert result.declaration.format_kind == CanonicalFormatKind.html5 assert result.advisories == [] @@ -128,7 +128,7 @@ def test_seller_kind_does_not_inherit_registry_retina_contract() -> None: result = project_v1_format_to_declaration(v1) assert result.declaration is not None - assert result.declaration.format_kind is CanonicalFormatKind.html5 + assert result.declaration.format_kind == CanonicalFormatKind.html5 assert result.declaration.params == {"width": 300, "height": 250} assert result.advisories == [] @@ -166,7 +166,7 @@ def test_registry_literal_mappings_project_canonical_params( ) assert result.declaration is not None - assert result.declaration.format_kind is kind + assert result.declaration.format_kind == kind assert result.declaration.params == params assert result.declaration.legacy_format_refs[0].id == format_id assert result.advisories == [] @@ -197,7 +197,7 @@ def test_retina_only_registry_mappings_preserve_pixel_ratio( ) assert result.declaration is not None - assert result.declaration.format_kind is CanonicalFormatKind.image + assert result.declaration.format_kind == CanonicalFormatKind.image assert result.declaration.params == { "width": width, "height": height, @@ -220,7 +220,7 @@ def test_retina_rendition_set_registry_mapping_preserves_slot_contract( ) assert result.declaration is not None - assert result.declaration.format_kind is CanonicalFormatKind.image + assert result.declaration.format_kind == CanonicalFormatKind.image assert result.declaration.params == { "width": width, "height": height, @@ -253,7 +253,7 @@ def test_structural_match_emits_ambiguous_advisory() -> None: } result = project_v1_format_to_declaration(v1) assert result.declaration is not None - assert result.declaration.format_kind is CanonicalFormatKind.video_vast + assert result.declaration.format_kind == CanonicalFormatKind.video_vast # Step 4: family-level match always emits AMBIGUOUS assert len(result.advisories) == 1 a = result.advisories[0] @@ -356,7 +356,7 @@ def test_step1_threads_registry_params_when_seller_annotates_kind_only() -> None } result = project_v1_format_to_declaration(v1) assert result.declaration is not None - assert result.declaration.format_kind is CanonicalFormatKind.video_vast + assert result.declaration.format_kind == CanonicalFormatKind.video_vast # No registry glob → no params, no advisory (step 1 stops here). assert result.advisories == [] @@ -413,8 +413,8 @@ def test_group_declarations_by_product_buckets_by_first_v1_ref() -> None: }, ) assert set(grouped) == {"product_alpha", "product_beta"} - assert {d.format_kind.value for d in grouped["product_alpha"]} == {"image", "html5"} - assert {d.format_kind.value for d in grouped["product_beta"]} == {"display_tag"} + assert {d.format_kind for d in grouped["product_alpha"]} == {"image", "html5"} + assert {d.format_kind for d in grouped["product_beta"]} == {"display_tag"} def test_group_declarations_by_product_omits_unmapped_declarations() -> None: @@ -464,7 +464,7 @@ def test_group_declarations_by_product_preserves_order_within_a_product() -> Non mapping={"product_alpha": ["fmt_second", "fmt_first"]}, ) # Declaration order from the projection wins, not mapping order. - assert [d.format_kind.value for d in grouped["product_alpha"]] == ["image", "html5"] + assert [d.format_kind for d in grouped["product_alpha"]] == ["image", "html5"] def test_full_v1_reference_catalog_projects_via_seller_canonical() -> None: diff --git a/tests/test_canonical_formats_v2_to_v1.py b/tests/test_canonical_formats_v2_to_v1.py index 0b6117ac4..8421f159b 100644 --- a/tests/test_canonical_formats_v2_to_v1.py +++ b/tests/test_canonical_formats_v2_to_v1.py @@ -174,8 +174,8 @@ def test_non_translatable_canonicals_are_silent_with_no_ref(kind: CanonicalForma decl = ProductFormatDeclaration( format_kind=kind, params={}, - format_shape="test_custom" if kind is CanonicalFormatKind.custom else None, - format_schema=_format_schema() if kind is CanonicalFormatKind.custom else None, + format_shape="test_custom" if kind == CanonicalFormatKind.custom else None, + format_schema=_format_schema() if kind == CanonicalFormatKind.custom else None, ) result = project_declaration_to_v1(decl) @@ -223,7 +223,7 @@ def test_translatable_canonical_without_v1_ref_emits_ambiguous( assert advisory.code == "FORMAT_DECLARATION_V1_AMBIGUOUS" assert advisory.source.value == "sdk" assert advisory.sdk_id == SDK_ID - assert advisory.details["format_kind"] == kind.value + assert advisory.details["format_kind"] == kind assert advisory.details["product_id"] == "prod_xyz" assert advisory.details["reason"] == "no_v1_format_ref" diff --git a/tests/test_canonical_models_are_subclasses.py b/tests/test_canonical_models_are_subclasses.py new file mode 100644 index 000000000..d9d3c9cc3 --- /dev/null +++ b/tests/test_canonical_models_are_subclasses.py @@ -0,0 +1,172 @@ +"""A canonical public model is a SUBCLASS of the generated wire model (#1368). + +The canonical layer used to build its 32 public classes with ``create_model``, +copying ``model_fields`` off the generated model and inheriting nothing from it. +That is why PR #1368's injected ``enforce_root_required_groups`` validator +reached the generated class and never the public one — the blocking review +finding on that PR. ARCH-ONE-SURFACE §6 refuses a second injection mechanism +for the copy layer ("it fixes the finding by doubling the thing that caused +it"), so the copy is gone instead. + +These tests grade the replacement as a property rather than as a diff: + +* every public canonical model descends from a generated wire model; +* every ``model_validator`` the generated model declares is present on the + public model — which is the #1368 contract, stated for ALL validators rather + than for one; +* the two concerns the copy had to re-attach per class (the boundary's + ``extra="allow"`` and the legacy-identity strip) still hold. + +Reverting any canonical model to a copy fails the first test, because +``create_model`` stamps the calling module onto what it builds and the copy has +no generated ancestor at all. +""" + +from __future__ import annotations + +import pytest +from pydantic import BaseModel + +from adcp.types import canonical_creative +from adcp.types.canonical_creative import ( + PRIMARY_CANONICAL_MODELS, + CanonicalBoundaryModel, + is_legacy_creative_identity_key, +) + +_GENERATED_PREFIX = "adcp.types.domains." + +#: ``Format`` is the one canonical model with no generated counterpart: it is +#: hand-written from the canonical format declaration and declares its own +#: fields. Everything else refines a generated wire model. +_HAND_WRITTEN = {"Format"} + + +def _generated_ancestors(model: type[BaseModel]) -> list[type[BaseModel]]: + return [ + base + for base in model.__mro__ + if base.__module__.startswith(_GENERATED_PREFIX) and issubclass(base, BaseModel) + ] + + +def _ids(models: tuple[type[BaseModel], ...]) -> list[str]: + return [model.__name__ for model in models] + + +def test_the_canonical_model_list_is_not_empty() -> None: + """A later refactor must not satisfy these tests by emptying the list.""" + assert len(PRIMARY_CANONICAL_MODELS) == 31 + + +@pytest.mark.parametrize("model", PRIMARY_CANONICAL_MODELS, ids=_ids(PRIMARY_CANONICAL_MODELS)) +def test_every_canonical_model_descends_from_a_generated_model(model: type[BaseModel]) -> None: + """The anti-copy property, stated structurally.""" + if model.__name__ in _HAND_WRITTEN: + pytest.skip(f"{model.__name__} has no generated counterpart") + ancestors = _generated_ancestors(model) + assert ancestors, ( + f"{model.__name__} has no generated ancestor — it is a copy, not a " + f"refinement. MRO: {[base.__name__ for base in model.__mro__]}" + ) + + +@pytest.mark.parametrize("model", PRIMARY_CANONICAL_MODELS, ids=_ids(PRIMARY_CANONICAL_MODELS)) +def test_every_generated_validator_reaches_the_public_model(model: type[BaseModel]) -> None: + """#1368's contract, for every validator rather than for one. + + A validator injected into a generated class by a generation pass — which is + what ``enforce_root_required_groups`` is — must be live on the public model + an adopter imports. Pydantic carries inherited decorators on the subclass's + ``__pydantic_decorators__``, so the subclass's validator set is a superset + of each generated ancestor's. A copy's is disjoint. + """ + if model.__name__ in _HAND_WRITTEN: + pytest.skip(f"{model.__name__} has no generated counterpart") + public = set(model.__pydantic_decorators__.model_validators) + for ancestor in _generated_ancestors(model): + inherited = set(ancestor.__pydantic_decorators__.model_validators) + assert inherited <= public, ( + f"{model.__name__} lost {sorted(inherited - public)} declared on its " + f"generated ancestor {ancestor.__name__}" + ) + + +@pytest.mark.parametrize("model", PRIMARY_CANONICAL_MODELS, ids=_ids(PRIMARY_CANONICAL_MODELS)) +def test_every_canonical_model_declares_no_legacy_identity(model: type[BaseModel]) -> None: + """Inheritance brings the legacy fields along; the one declared rule removes them.""" + leaked = [name for name in model.model_fields if is_legacy_creative_identity_key(name)] + assert leaked == [], f"{model.__name__} declares legacy creative identity: {leaked}" + + +@pytest.mark.parametrize("model", PRIMARY_CANONICAL_MODELS, ids=_ids(PRIMARY_CANONICAL_MODELS)) +def test_every_canonical_model_keeps_the_boundary_policy(model: type[BaseModel]) -> None: + """``CanonicalBoundaryModel`` is the LAST base, so its config wins the merge.""" + assert issubclass(model, CanonicalBoundaryModel), model.__name__ + assert model.model_config["extra"] == "allow", model.__name__ + + +@pytest.mark.parametrize("model", PRIMARY_CANONICAL_MODELS, ids=_ids(PRIMARY_CANONICAL_MODELS)) +def test_every_canonical_model_runs_the_wrap_serializer(model: type[BaseModel]) -> None: + """The strip serializer is declared once on the boundary and inherited. + + It used to be re-attached per class through ``create_model``'s + ``__validators__``; a class the copy helper missed would serialize legacy + identity from a nested ``TypeAdapter`` path. + """ + serializers = model.__pydantic_decorators__.model_serializers + assert "_serialize_canonical" in serializers, model.__name__ + + +def test_the_removal_rule_is_declared_once_and_derived() -> None: + """The strip predicate has one owner, and the rule reads it. + + Four behaviors strip legacy identity — input rejection, JSON-schema + sanitation, serialization, and now the declared-field removal — and all + four call ``is_legacy_creative_identity_key``. A second spelling of the + key list is the drift channel this asserts shut. + """ + + class Probe(canonical_creative.Package): + pass + + assert "format_ids" not in Probe.model_fields + assert "format_ids" in _generated_ancestors(Probe)[0].model_fields + + +def test_an_inherited_validator_that_reads_a_removed_field_still_works() -> None: + """The one interaction between inheritance and the removal rule. + + ``list-creatives-response.json`` carries an injected + ``_validate_format_reference_xor`` that reads ``self.format_id``. Inheritance + is the point of this layer, so that validator now runs on the canonical + model — and with the field merely deleted it raised ``AttributeError`` + instead of validating. The removal binds the name to ``None``, which is the + removal's own truth, and the XOR then reduces to the invariant the canonical + model should hold: ``format_kind`` must be set. + + Both directions are graded, because a reduced invariant that accepts + everything is not an invariant. + """ + import pytest as _pytest + from pydantic import ValidationError + + from adcp.types import CanonicalFormatKind + + row = { + "creative_id": "creative-1", + "name": "Image", + "format_kind": "image", + "status": "approved", + "created_date": "2026-09-01T00:00:00Z", + "updated_date": "2026-09-01T00:00:00Z", + } + accepted = canonical_creative.Creative.model_validate(row) + assert accepted.format_kind == CanonicalFormatKind.image + assert "format_id" not in canonical_creative.Creative.model_fields + assert accepted.format_id is None + + with _pytest.raises(ValidationError): + canonical_creative.Creative.model_validate( + {k: v for k, v in row.items() if k != "format_kind"} + ) diff --git a/tests/test_canonical_reference_resolver.py b/tests/test_canonical_reference_resolver.py index 2f618d061..84a527d6c 100644 --- a/tests/test_canonical_reference_resolver.py +++ b/tests/test_canonical_reference_resolver.py @@ -13,7 +13,7 @@ CanonicalReferenceStatus, parse_canonical_reference, ) -from adcp.types.generated_poc.core.platform_extension_ref import PlatformExtensionReference +from adcp.types.domains.core.platform_extension_ref import PlatformExtensionReference PUBLIC_IP = "93.184.216.34" BASE_URI = "https://formats.adcontextprotocol.org/schemas/custom.json" diff --git a/tests/test_canonical_removed_fields.py b/tests/test_canonical_removed_fields.py new file mode 100644 index 000000000..d0ad661b8 --- /dev/null +++ b/tests/test_canonical_removed_fields.py @@ -0,0 +1,389 @@ +"""The removed legacy-identity fields, graded on every surface that shows them. + +A canonical model is a real subclass of the generated wire model, so it +INHERITS that model's legacy creative identity (``format_id``, ``format_ids``, +``format_ids_pending``, ``format_ids_to_provide``). +``CanonicalBoundaryModel.__pydantic_init_subclass__`` removes those inherited +declarations, and it does so through pydantic internals: it deletes from +``cls.model_fields``, pops ``__annotations__``, binds the name to ``None`` and +forces ``model_rebuild``. None of that is a supported pydantic API. It works on +2.13; a minor release could change any of it without saying so. + +This module is the alarm for that. For every canonical model that inherits a +legacy-identity field it grades the removal on all four runtime surfaces an +adopter can observe — + +* ``model_fields``, +* ``model_json_schema()``, +* ``model_dump()`` / ``model_dump_json()``, +* ``model_validate`` of a document carrying the name, + +— and on the two static ones, because a type checker that still sees the +field disagrees with a runtime that refuses it: + +* the ``TYPE_CHECKING`` redeclaration exists, says ``init=False``, and restates + the generated parent's annotation EXACTLY; +* mypy and pyright both refuse the constructor keyword. + +Nothing here is a hand-written table. The model list is +``PRIMARY_CANONICAL_MODELS``; the removed names per model are every field name +on a generated ancestor that satisfies ``is_legacy_creative_identity_key`` — +the one predicate that already owns input rejection, schema sanitation, +serialization and the removal rule itself. In particular the names are derived +from the ANCESTORS rather than by subtracting the canonical model's own +``model_fields``: a subtraction would silently go empty the day the removal +stops working, and every test here would then pass with nothing to grade. +""" + +from __future__ import annotations + +import ast +import json +import os +import subprocess +import sys +from collections.abc import Iterator, Sequence +from pathlib import Path +from typing import Any + +import pytest +from pydantic import BaseModel, ValidationError + +from adcp.types import canonical_creative +from adcp.types.canonical_creative import ( + PRIMARY_CANONICAL_MODELS, + is_legacy_creative_identity_key, +) +from adcp.types.domains.core.format_id import FormatReferenceStructuredObject + +_GENERATED_PREFIX = "adcp.types.domains." +_SOURCE = Path(canonical_creative.__file__) + +#: The boundary validator's refusal text. Asserting on it keeps the +#: ``model_validate`` test from passing because of a missing required field. +_REFUSAL = "contains legacy creative identity" + + +def _generated_ancestors(model: type[BaseModel]) -> list[type[BaseModel]]: + return [ + base + for base in model.__mro__ + if base.__module__.startswith(_GENERATED_PREFIX) and issubclass(base, BaseModel) + ] + + +def _removed_names(model: type[BaseModel]) -> list[str]: + """Every legacy-identity field *model* inherits from a generated ancestor.""" + + inherited: set[str] = set() + for ancestor in _generated_ancestors(model): + inherited |= { + name for name in ancestor.model_fields if is_legacy_creative_identity_key(name) + } + return sorted(inherited) + + +def _declaring_ancestor(model: type[BaseModel], name: str) -> type[BaseModel]: + for ancestor in _generated_ancestors(model): + if name in ancestor.model_fields: + return ancestor + raise AssertionError(f"{name} is on no generated ancestor of {model.__name__}") + + +def _pairs() -> list[tuple[type[BaseModel], str]]: + return [(model, name) for model in PRIMARY_CANONICAL_MODELS for name in _removed_names(model)] + + +REMOVED = _pairs() +REMOVED_IDS = [f"{model.__name__}.{name}" for model, name in REMOVED] +MODELS_WITH_REMOVALS = sorted({model.__name__ for model, _ in REMOVED}) + + +def test_the_removed_field_surface_is_not_empty() -> None: + """A later refactor must not satisfy this module by emptying the surface. + + Eleven canonical models inherit fifteen legacy-identity declarations. The + numbers are pinned exactly, the way + ``test_the_canonical_model_list_is_not_empty`` pins the model count: a drop + means a generated parent stopped declaring the field (then this number + moves deliberately), and a rise means a new one needs hiding too. + """ + assert len(MODELS_WITH_REMOVALS) == 11, MODELS_WITH_REMOVALS + assert len(REMOVED) == 15, REMOVED_IDS + + +@pytest.mark.parametrize(("model", "name"), REMOVED, ids=REMOVED_IDS) +def test_the_removed_field_is_absent_from_model_fields(model: type[BaseModel], name: str) -> None: + """``__pydantic_init_subclass__``'s own surface.""" + assert name in _declaring_ancestor(model, name).model_fields + assert name not in model.model_fields + + +def _schema_keys(node: Any) -> Iterator[str]: + """Every property name and ``required`` entry anywhere under *node*.""" + + if isinstance(node, list): + for item in node: + yield from _schema_keys(item) + return + if not isinstance(node, dict): + return + properties = node.get("properties") + if isinstance(properties, dict): + yield from (key for key in properties if isinstance(key, str)) + required = node.get("required") + if isinstance(required, list): + yield from (key for key in required if isinstance(key, str)) + for value in node.values(): + yield from _schema_keys(value) + + +@pytest.mark.parametrize(("model", "name"), REMOVED, ids=REMOVED_IDS) +def test_the_removed_field_is_absent_from_the_json_schema( + model: type[BaseModel], name: str +) -> None: + """At every nesting depth, not only at the root. + + ``sanitize_canonical_schema`` walks the whole document, so a nested + ``$defs`` entry re-introducing the name is as much a leak as a root + property. + """ + keys = list(_schema_keys(model.model_json_schema())) + assert keys, model.__name__ + assert name not in keys, f"{model.__name__} publishes {name} in its JSON schema" + + +@pytest.mark.parametrize(("model", "name"), REMOVED, ids=REMOVED_IDS) +def test_the_removed_field_is_absent_from_model_dump(model: type[BaseModel], name: str) -> None: + """Including when an instance really is carrying the key. + + A canonical model is ``extra="allow"``, so the name does land in + ``__pydantic_extra__`` when it bypasses validation — which is exactly the + case the strip serializer exists for. Asserting the extra arrived first is + what keeps this from passing merely because nothing was ever set. + """ + carrier = model.model_construct(**{name: "legacy-value"}) + assert name in (carrier.__pydantic_extra__ or {}), ( + f"{model.__name__}.{name} did not land in __pydantic_extra__ — either the " + f"boundary's extra policy changed or the field is a declared field again" + ) + + assert name not in carrier.model_dump() + assert name not in carrier.model_dump(mode="json") + assert name not in json.loads(carrier.model_dump_json()) + assert name not in model.model_construct().model_dump() + + +@pytest.mark.parametrize(("model", "name"), REMOVED, ids=REMOVED_IDS) +def test_model_validate_rejects_a_document_carrying_the_removed_field( + model: type[BaseModel], name: str +) -> None: + """Refused, and refused for the boundary's reason. + + A document holding only the removed name would also fail for missing + required fields on most of these models, so the refusal text is asserted: + without it this test would stay green if the boundary validator were + deleted outright. + """ + with pytest.raises(ValidationError) as caught: + model.model_validate({name: "legacy-value"}) + assert _REFUSAL in str(caught.value), str(caught.value) + + +# --------------------------------------------------------------------------- +# The static half: a type checker must not offer a keyword the runtime refuses. +# --------------------------------------------------------------------------- + + +def _type_checking_redeclarations() -> dict[tuple[str, str], ast.AnnAssign]: + """Map ``(class name, field name)`` to its ``TYPE_CHECKING`` redeclaration.""" + + tree = ast.parse(_SOURCE.read_text()) + found: dict[tuple[str, str], ast.AnnAssign] = {} + for node in ast.walk(tree): + if not isinstance(node, ast.ClassDef): + continue + for statement in node.body: + if not ( + isinstance(statement, ast.If) + and isinstance(statement.test, ast.Name) + and statement.test.id == "TYPE_CHECKING" + ): + continue + for declaration in statement.body: + if isinstance(declaration, ast.AnnAssign) and isinstance( + declaration.target, ast.Name + ): + found[node.name, declaration.target.id] = declaration + return found + + +def _alias_namespace() -> dict[str, Any]: + """The module's globals plus the aliases its ``TYPE_CHECKING`` block defines. + + Those aliases never execute, so they are read out of the source and + evaluated here. Evaluating them rather than restating them is the point: + the comparison below then has no second copy of the annotation to drift + from. + """ + + namespace: dict[str, Any] = dict(vars(canonical_creative)) + namespace["FormatReferenceStructuredObject"] = FormatReferenceStructuredObject + namespace["Sequence"] = Sequence + tree = ast.parse(_SOURCE.read_text()) + for node in tree.body: + if not ( + isinstance(node, ast.If) + and isinstance(node.test, ast.Name) + and node.test.id == "TYPE_CHECKING" + ): + continue + for statement in node.body: + if isinstance(statement, ast.Assign) and isinstance(statement.targets[0], ast.Name): + namespace[statement.targets[0].id] = eval(ast.unparse(statement.value), namespace) + return namespace + + +@pytest.mark.parametrize(("model", "name"), REMOVED, ids=REMOVED_IDS) +def test_the_removed_field_is_hidden_from_the_type_checker( + model: type[BaseModel], name: str +) -> None: + """Every removal has a ``TYPE_CHECKING`` redeclaration, and it is honest. + + ``init=False`` is what removes the keyword from the ``__init__`` mypy and + pyright synthesize through ``dataclass_transform``. The annotation must be + the generated parent's exact field type: a narrowed one is an incompatible + override (pyright says so by name), and a widened one hands the caller a + read type the runtime never produces. + """ + declaration = _type_checking_redeclarations().get((model.__name__, name)) + assert declaration is not None, ( + f"{model.__name__}.{name} is removed at runtime but still visible to a " + f"type checker — add a TYPE_CHECKING redeclaration with init=False" + ) + assert isinstance(declaration.value, ast.Call), ast.unparse(declaration) + keywords = { + keyword.arg: ast.literal_eval(keyword.value) + for keyword in declaration.value.keywords + if keyword.arg is not None + } + assert keywords.get("init") is False, ast.unparse(declaration) + + declared = eval(ast.unparse(declaration.annotation), _alias_namespace()) + assert declared == _declaring_ancestor(model, name).model_fields[name].annotation + + +def _construction(model: type[BaseModel], names: list[str]) -> str: + """``Model(=, ...)`` for a checker to refuse.""" + + arguments = [] + for name in names: + annotation = str(_declaring_ancestor(model, name).model_fields[name].annotation) + arguments.append( + f"{name}=[_REF]" if annotation.startswith(("list[", "collections")) else f"{name}=_REF" + ) + return f"({', '.join(arguments)})" + + +def _refusal_snippet() -> tuple[str, dict[int, tuple[str, str]], dict[int, tuple[str, str]]]: + """Build a snippet, plus the line → (class, field) maps for both halves. + + The control half constructs the GENERATED parent with the same keyword. A + checker must accept it there — otherwise a misspelled keyword would make + the canonical half refuse for the wrong reason and this test would pass + while the hiding did nothing. + """ + + lines = [ + "from adcp.types.domains.core.format_id import FormatReferenceStructuredObject", + '_REF = FormatReferenceStructuredObject(agent_url="https://a.example", id="b")', + ] + canonical_lines: dict[int, tuple[str, str]] = {} + control_lines: dict[int, tuple[str, str]] = {} + + for index, class_name in enumerate(MODELS_WITH_REMOVALS): + model = next(m for m, _ in REMOVED if m.__name__ == class_name) + names = _removed_names(model) + lines.append(f"from {model.__module__} import {class_name} as _Canonical{index}") + ancestor = _declaring_ancestor(model, names[0]) + lines.append(f"from {ancestor.__module__} import {ancestor.__name__} as _Wire{index}") + for name in names: + canonical_lines[len(lines) + 1] = (class_name, name) + lines.append(f"_Canonical{index}{_construction(model, [name])}") + control_lines[len(lines) + 1] = (ancestor.__name__, name) + lines.append(f"_Wire{index}{_construction(model, [name])}") + + return "\n".join(lines) + "\n", canonical_lines, control_lines + + +_UNKNOWN_KEYWORD = { + "mypy": 'Unexpected keyword argument "{name}"', + "pyright": 'No parameter named "{name}"', +} + + +def _diagnostics(checker: str, snippet: Path, root: Path) -> list[tuple[int, str]]: + if checker == "mypy": + command = [sys.executable, "-m", "mypy", "--strict", str(snippet)] + else: + command = [ + sys.executable, + "-m", + "pyright", + "--outputjson", + "--pythonpath", + sys.executable, + str(snippet), + ] + environment = dict(os.environ, MYPYPATH=str(root / "src")) + result = subprocess.run( + command, cwd=root, env=environment, capture_output=True, text=True, timeout=600 + ) + if checker == "pyright": + report = json.loads(result.stdout) + return [ + (entry["range"]["start"]["line"] + 1, entry["message"]) + for entry in report["generalDiagnostics"] + ] + output: list[tuple[int, str]] = [] + for line in result.stdout.splitlines(): + parts = line.split(":", 3) + if len(parts) == 4 and parts[1].isdigit(): + output.append((int(parts[1]), parts[3].strip())) + assert output, result.stdout + result.stderr + return output + + +@pytest.mark.parametrize("checker", ["mypy", "pyright"]) +def test_both_checkers_refuse_the_removed_constructor_keyword(checker: str, tmp_path: Path) -> None: + """The disagreement in review item 3, pinned shut on both checkers. + + Before the ``TYPE_CHECKING`` redeclarations, every one of these fifteen + constructions type-checked clean on mypy AND pyright and then raised + ``ValidationError`` at runtime. The keyword is now unavailable statically, + with no suppression anywhere: ``init=False`` is read by both checkers + through pydantic's ``dataclass_transform``. + """ + root = Path(__file__).resolve().parents[1] + snippet_text, canonical_lines, control_lines = _refusal_snippet() + snippet = tmp_path / "removed_field_keywords.py" + snippet.write_text(snippet_text) + + diagnostics = _diagnostics(checker, snippet, root) + phrase = _UNKNOWN_KEYWORD[checker] + + def on(line: int) -> list[str]: + return [message for number, message in diagnostics if number == line] + + for line, (class_name, name) in canonical_lines.items(): + wanted = phrase.format(name=name) + assert any( + wanted in message for message in on(line) + ), f"{checker} accepted {class_name}({name}=...) — it reported {on(line)}" + + for line, (class_name, name) in control_lines.items(): + wanted = phrase.format(name=name) + assert not any(wanted in message for message in on(line)), ( + f"{checker} does not know {name} on the wire model {class_name} either, " + f"so the refusal above proves nothing: {on(line)}" + ) diff --git a/tests/test_canonical_validator_parity.py b/tests/test_canonical_validator_parity.py index 737cf1cc2..35b63c3b8 100644 --- a/tests/test_canonical_validator_parity.py +++ b/tests/test_canonical_validator_parity.py @@ -1,12 +1,21 @@ -"""A canonical clone enforces every validator its generated source declares. - -``canonical_creative.py`` builds the public canonical models with ``create_model`` -over the source's bases and a copy of its fields, so the generated class is not in -the clone's MRO. Validators declared on the generated class body — the root-level -required groups ``enforce_root_required_groups`` emits (#1361), the uniqueness and -pairing checks other post-generation fixes inject — reach the clone only because -``_canonical_clone`` carries them across. These tests hold that: a document the -source refuses, the public name refuses too. +"""A public canonical model enforces every validator its generated source declares. + +The canonical models are real subclasses of the generated classes they refine, so a +validator declared on the generated class body — the root-level required groups +``enforce_root_required_groups`` emits (#1361), the uniqueness and pairing checks other +post-generation fixes inject — reaches the public name by inheritance. These tests hold +the consequence: a document the generated class refuses, the public name refuses too. + +They used to hold the same obligation over a different mechanism. The canonical models +were built with ``create_model`` over a copy of the source's fields, which left the +generated class out of the MRO, so each validator had to be re-decorated onto the copy +and any validator touching a dropped field was skipped and recorded. Both the carrying +and the skip list are gone: there is one mechanism, and nothing to forget. The tests +below are the same obligations re-pointed at it, and two of them are strictly stronger +for it — ``test_no_generated_validator_is_skipped`` admits no exceptions where the skip +list was an allowed set, and +``test_canonical_manifest_enforces_its_required_group_through_format_kind`` asserts a +refusal where the clone accepted the document. """ from __future__ import annotations @@ -22,17 +31,38 @@ _REQUIRED_GROUP_VALIDATOR = "_require_schema_required_group" -def _canonical_clones() -> dict[str, type[BaseModel]]: +def _generated_ancestors(model: type[BaseModel]) -> list[type[BaseModel]]: + """The generated classes ``model`` descends from, nearest first.""" + return [ + base + for base in model.__mro__[1:] + if isinstance(base, type) + and issubclass(base, BaseModel) + and base.__module__.startswith("adcp.types.domains") + ] + + +def _canonical_models() -> dict[str, type[BaseModel]]: + """Every model in ``canonical_creative`` that refines a generated class. + + Broader than ``PRIMARY_CANONICAL_MODELS`` in + ``tests/test_canonical_models_are_subclasses.py``, which is the 31 publicly bound + models: this population also carries the private ``_Legacy*`` and ``_Delivery*`` + refinements, because a validator dropped on one of those is just as silent. + """ return { name: obj for name, obj in vars(canonical_creative).items() - if isinstance(obj, type) - and issubclass(obj, BaseModel) - and getattr(obj, "__adcp_canonical_source__", None) is not None + if isinstance(obj, type) and issubclass(obj, BaseModel) and _generated_ancestors(obj) } -def _own_validator_names(model: type[BaseModel]) -> set[str]: +def _own_validators(model: type[BaseModel]) -> set[str]: + """Validators ``model`` declares on its own body, both decorator kinds. + + Only own-body validators are interesting: an inherited one reaches the public model + through the same MRO this file is about. + """ decorators = model.__pydantic_decorators__ own = vars(model) return { @@ -40,89 +70,99 @@ def _own_validator_names(model: type[BaseModel]) -> set[str]: } -def test_every_canonical_clone_carries_its_source_model_validators() -> None: - clones = _canonical_clones() - assert "CreateMediaBuyRequest" in clones - missing: dict[str, set[str]] = {} - for name, clone in clones.items(): - source = clone.__adcp_canonical_source__ - assert source is not None - carried = set(clone.__pydantic_decorators__.model_validators) - left_behind = clone.__adcp_canonical_validators_left_behind__ - wanted = { - validator - for validator in source.__pydantic_decorators__.model_validators - if validator in vars(source) and validator not in left_behind - } - if wanted - carried: - missing[name] = wanted - carried - assert not missing, f"canonical clones dropped source model validators: {missing}" +def _all_validators(model: type[BaseModel]) -> set[str]: + decorators = model.__pydantic_decorators__ + return {*decorators.model_validators, *decorators.field_validators} + +def test_the_canonical_model_population_is_not_empty() -> None: + """Anti-vacuity floor: every loop below is over this mapping.""" + models = _canonical_models() + assert len(models) >= 31, len(models) + assert "CreateMediaBuyRequest" in models + assert "CreativeManifest" in models -def test_every_canonical_clone_carries_its_source_field_validators() -> None: + +def test_every_generated_validator_reaches_the_public_model() -> None: + """Both decorator kinds, over every refinement, with no exceptions permitted. + + Field validators are included deliberately even though no generated ancestor + declares one today: the clone mechanism would have had to carry them too, and an + assertion that only covers the kind that currently exists stops grading the moment + a post-generation fix emits the other. + """ missing: dict[str, set[str]] = {} - for name, clone in _canonical_clones().items(): - source = clone.__adcp_canonical_source__ - assert source is not None - carried = set(clone.__pydantic_decorators__.field_validators) - left_behind = clone.__adcp_canonical_validators_left_behind__ - wanted = { - validator - for validator in source.__pydantic_decorators__.field_validators - if validator in vars(source) and validator not in left_behind - } - if wanted - carried: - missing[name] = wanted - carried - assert not missing, f"canonical clones dropped source field validators: {missing}" - - -def test_a_validator_is_left_behind_only_for_a_field_the_clone_drops() -> None: - """The clone drops legacy creative identity on purpose; nothing else is skipped.""" - for name, clone in _canonical_clones().items(): - source = clone.__adcp_canonical_source__ - assert source is not None - for validator, touched in clone.__adcp_canonical_validators_left_behind__.items(): - assert touched, (name, validator) - assert touched <= set(source.model_fields) - set(clone.model_fields), ( - name, - validator, - touched, - ) - - -def test_canonical_manifest_keeps_its_optional_kind_without_legacy_identity() -> None: - """``format_id | format_kind`` names a legacy field; the canonical boundary has no - ``format_id`` to satisfy it, so the group stays on the generated class only.""" + for name, model in _canonical_models().items(): + wanted: set[str] = set() + for ancestor in _generated_ancestors(model): + wanted |= _own_validators(ancestor) + absent = wanted - _all_validators(model) + if absent: + missing[name] = absent + assert not missing, f"public canonical models do not carry generated validators: {missing}" + + +def test_no_generated_validator_is_skipped() -> None: + """There is no skip list, and a validator reading a removed field is why there was one. + + A canonical model drops the legacy creative identity fields, so a rule written + against ``format_id`` inherits down onto a class that does not declare it. The + removal rule binds each removed name to ``None`` after class creation, so the read + resolves and the rule reduces rather than being skipped — which is what lets this + test admit no exceptions at all. + """ + for name, model in _canonical_models().items(): + for ancestor in _generated_ancestors(model): + for validator in _own_validators(ancestor): + assert validator in _all_validators(model), (name, ancestor.__name__, validator) + + +def test_canonical_manifest_enforces_its_required_group_through_format_kind() -> None: + """``format_id | format_kind`` reaches the public manifest, satisfied by the surviving arm. + + ``core/creative-manifest.json`` declares the group, and the canonical boundary has + no ``format_id`` to satisfy it with. Under the clone this validator was skipped and + ``{"assets": {}}`` validated; it is now inherited, so the public name refuses the + document its generated source refuses and accepts it through ``format_kind``. + """ manifest = canonical_creative.CreativeManifest - assert _REQUIRED_GROUP_VALIDATOR in manifest.__adcp_canonical_validators_left_behind__ - assert manifest.model_validate({"assets": {}}).format_kind is None + assert _REQUIRED_GROUP_VALIDATOR in manifest.__pydantic_decorators__.model_validators + assert "format_id" not in manifest.model_fields + + with pytest.raises(ValidationError, match="at least one of these field groups"): + manifest.model_validate({"assets": {}}) + + accepted = manifest.model_validate({"assets": {}, "format_kind": "image"}) + assert accepted.format_kind is not None -def test_before_validators_carry_their_classmethod_binding() -> None: - """A ``before`` validator is stored as a classmethod; re-decorating the bare - function hands it ``ValidationInfo`` as ``data`` and Product refused every payload.""" +def test_before_validators_keep_their_classmethod_binding() -> None: + """A ``before`` validator is stored as a classmethod and must stay one. + + The clone re-decorated the class namespace rather than ``Decorator.func`` because + the unwrapped function has already lost its ``cls`` binding; inheritance has no + such hazard, and this holds that the binding survives on both classes. + """ from adcp.types import Product - from adcp.types.generated_poc.core.product import Product as Generated + from adcp.types.domains.core.product import Product as Generated assert "_coerce_publisher_property_models" in Product.__pydantic_decorators__.model_validators assert "_coerce_publisher_property_models" in Generated.__pydantic_decorators__.model_validators def test_required_group_validators_reach_the_canonical_names() -> None: - """The gap #1368 documented: the source rejected, the clone accepted.""" - sources_with_groups = { - name: clone - for name, clone in _canonical_clones().items() - if _REQUIRED_GROUP_VALIDATOR - in clone.__adcp_canonical_source__.__pydantic_decorators__.model_validators + """The gap #1368 documented: the generated class rejected, the public name accepted.""" + with_groups = { + name: model + for name, model in _canonical_models().items() + if any( + _REQUIRED_GROUP_VALIDATOR in _own_validators(ancestor) + for ancestor in _generated_ancestors(model) + ) } - assert "CreateMediaBuyRequest" in sources_with_groups - for name, clone in sources_with_groups.items(): - if _REQUIRED_GROUP_VALIDATOR in clone.__adcp_canonical_validators_left_behind__: - # The group names a legacy identity field the clone drops; see - # test_a_validator_is_left_behind_only_for_a_field_the_clone_drops. - continue - assert _REQUIRED_GROUP_VALIDATOR in clone.__pydantic_decorators__.model_validators, name + assert "CreateMediaBuyRequest" in with_groups + for name, model in with_groups.items(): + assert _REQUIRED_GROUP_VALIDATOR in model.__pydantic_decorators__.model_validators, name _MEDIA_BUY_UNCONDITIONAL_FIELDS: dict[str, Any] = { @@ -144,12 +184,13 @@ def test_canonical_create_media_buy_request_requires_one_root_anyof_group( ) -> None: """A media buy with no packages, no budget and no proposal is refused on every path. - ``create-media-buy-request.json`` declares the three ways to be a media buy as - a root ``anyOf``. The generated class gained the rule in #1368; this is the - controlled probe that showed the public canonical clone still accepted the - document, held green. + ``create-media-buy-request.json`` declares the three ways to be a media buy as a + root ``anyOf``. The generated class gained the rule in #1368; this is the controlled + probe that showed the public canonical name still accepted the document, held green. """ - source = model.__adcp_canonical_source__ # type: ignore[attr-defined] + ancestors = _generated_ancestors(model) + assert ancestors, model.__name__ + source = ancestors[0] with pytest.raises(ValidationError, match="at least one of these field groups"): source.model_validate(dict(_MEDIA_BUY_UNCONDITIONAL_FIELDS)) with pytest.raises(ValidationError, match="at least one of these field groups"): @@ -166,14 +207,14 @@ def test_canonical_create_media_buy_request_requires_one_root_anyof_group( assert committed.packages is None # type: ignore[attr-defined] -def test_canonical_clone_keeps_the_boundary_validator_first() -> None: - """Carrying source validators must not displace the boundary's own.""" - clone = canonical_creative.CreateMediaBuyRequest - validators = clone.__pydantic_decorators__.model_validators +def test_canonical_model_keeps_the_boundary_validator_alongside_the_inherited_ones() -> None: + """Inheriting source validators must not displace the boundary's own.""" + model = canonical_creative.CreateMediaBuyRequest + validators = model.__pydantic_decorators__.model_validators assert "_reject_legacy_creative_identity" in validators assert _REQUIRED_GROUP_VALIDATOR in validators with pytest.raises(ValidationError, match="legacy creative identity"): - clone.model_validate( + model.model_validate( { **_MEDIA_BUY_UNCONDITIONAL_FIELDS, "packages": [ diff --git a/tests/test_capabilities.py b/tests/test_capabilities.py index b0b05a63f..9a78a517c 100644 --- a/tests/test_capabilities.py +++ b/tests/test_capabilities.py @@ -12,11 +12,11 @@ from adcp.exceptions import ADCPError, ADCPFeatureUnsupportedError from adcp.server.base import ADCPHandler from adcp.types.core import AgentConfig, Protocol, TaskResult, TaskStatus -from adcp.types.generated_poc.core.media_buy_features import MediaBuyFeatures -from adcp.types.generated_poc.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.core.media_buy_features import MediaBuyFeatures +from adcp.types.domains.protocol.get_adcp_capabilities_response import ( Adcp as AdcpInfo, ) -from adcp.types.generated_poc.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.protocol.get_adcp_capabilities_response import ( Execution, ExtensionsSupportedItem, GetAdcpCapabilitiesResponse, @@ -27,7 +27,7 @@ SupportedProtocol, Targeting, ) -from adcp.types.generated_poc.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.protocol.get_adcp_capabilities_response import ( Features as SignalsFeatures, ) diff --git a/tests/test_catalog_types.py b/tests/test_catalog_types.py index d6f42f490..72eb28332 100644 --- a/tests/test_catalog_types.py +++ b/tests/test_catalog_types.py @@ -286,7 +286,7 @@ def test_signal_catalog_type_unaffected(): def test_signal_catalog_type_deep_import_alias(): """The historical generated-module import remains available.""" - from adcp.types.generated_poc.enums.signal_catalog_type import ( + from adcp.types.domains.enums.signal_catalog_type import ( SignalAvailabilityType, SignalCatalogType, ) diff --git a/tests/test_client.py b/tests/test_client.py index bdfe9ef86..b4959b3b2 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -282,7 +282,7 @@ def test_get_products_preserves_canonical_format_options(): assert result.data.products is not None assert len(result.data.products) == 1 declaration = result.data.products[0].format_options[0] - assert declaration.format_kind.value == "image" + assert declaration.format_kind == "image" assert declaration.params == {"width": 300, "height": 250} assert result.metadata == {"projection": {"diagnostics": []}} @@ -322,7 +322,7 @@ def test_get_products_still_projects_legacy_format_ids(): assert isinstance(result.data, GetProductsResponse) assert result.data.products is not None assert len(result.data.products) == 1 - assert result.data.products[0].format_options[0].format_kind.value == "image" + assert result.data.products[0].format_options[0].format_kind == "image" @pytest.mark.asyncio diff --git a/tests/test_code_generation.py b/tests/test_code_generation.py index 6b2a10b19..641f5a31c 100644 --- a/tests/test_code_generation.py +++ b/tests/test_code_generation.py @@ -206,8 +206,12 @@ def test_post_generation_restores_codegen_contract_compatibility(tmp_path, monke assert "Canonical signal reference." in product_source representation_source = (core_dir / "creative_representation.py").read_text() - assert "from .canonical_format_kind import CanonicalFormatKind" in representation_source - assert "format_kind: Annotated[\n CanonicalFormatKind," in representation_source + # ``str``, not the enum. The fix repairs a flattening artifact (the + # generator emits ``format_kind: Any``); it must not re-close the + # vocabulary, which is open at every reference by decision — see + # ``OPEN_VOCABULARY_SCHEMAS`` in ``scripts/generate_types.py``. + assert "from .canonical_format_kind import CanonicalFormatKind" not in representation_source + assert "format_kind: Annotated[\n str," in representation_source assert "Canonical 3.2 path." in representation_source assert "'representation_selection'" in representation_source assert "@model_validator(mode='before')" in representation_source @@ -512,7 +516,7 @@ def test_nested_format_discriminator_drops_only_codegen_ambiguous_outer_hint(): def test_audience_evidence_attestation_subject_uses_narrowed_resource_arm(): """The allOf-constrained evidence subject must build a valid Pydantic schema.""" - from adcp.types.generated_poc.core.audience_evidence import AttestationRef + from adcp.types.domains.core.audience_evidence import AttestationRef schema = AttestationRef.model_json_schema() @@ -526,7 +530,7 @@ def test_post_generate_legacy_purchase_losses_are_always_an_array(tmp_path, monk """The schema's negated empty array must not become an empty object arm.""" from scripts import post_generate_fixes - generated_dir = tmp_path / "generated_poc" + generated_dir = tmp_path / "domains" target = generated_dir / "media_buy" / "legacy_purchase_continuation_input.py" target.parent.mkdir(parents=True) target.write_text( @@ -549,7 +553,7 @@ def test_post_generate_legacy_purchase_losses_restore_array_constraints(tmp_path """Runtime and emitted schemas retain constraints codegen drops.""" from scripts import post_generate_fixes - generated_dir = tmp_path / "generated_poc" + generated_dir = tmp_path / "domains" target = generated_dir / "media_buy" / "legacy_purchase_continuation_input.py" target.parent.mkdir(parents=True) target.write_text( @@ -589,7 +593,7 @@ def test_post_generate_change_term_constraint_import_is_idempotent(tmp_path, mon """Repeated post-generation fixes never duplicate model_validator imports.""" from scripts import post_generate_fixes - generated_dir = tmp_path / "generated_poc" + generated_dir = tmp_path / "domains" target = generated_dir / "media_buy" / "change_term_constraints.py" target.parent.mkdir(parents=True) target.write_text( @@ -617,7 +621,7 @@ def test_post_generate_preserves_request_signing_operation_strings(tmp_path, mon """Constrained operation names remain plain strings after validation.""" from scripts import post_generate_fixes - generated_dir = tmp_path / "generated_poc" + generated_dir = tmp_path / "domains" targets = ( generated_dir / "protocol" / "get_adcp_capabilities_response.py", generated_dir / "bundled" / "protocol" / "get_adcp_capabilities_response.py", @@ -651,7 +655,7 @@ def test_post_generate_preserves_request_signing_operation_strings(tmp_path, mon def test_allof_merge_selects_literal_base_independent_of_order(tmp_path, monkeypatch): from scripts import post_generate_fixes - generated_dir = tmp_path / "generated_poc" + generated_dir = tmp_path / "domains" target = generated_dir / "core" / "postal_area.py" target.parent.mkdir(parents=True) target.write_text( @@ -681,7 +685,7 @@ def test_allof_merge_selects_literal_base_independent_of_order(tmp_path, monkeyp def test_allof_merge_leaves_named_disjoint_bases_untouched(tmp_path, monkeypatch): from scripts import post_generate_fixes - generated_dir = tmp_path / "generated_poc" + generated_dir = tmp_path / "domains" target = generated_dir / "core" / "disjoint.py" target.parent.mkdir(parents=True) source = ( @@ -709,7 +713,7 @@ def test_allof_merge_leaves_named_disjoint_bases_untouched(tmp_path, monkeypatch def test_allof_merge_preserves_non_type_literal_discriminator_branch(tmp_path, monkeypatch): from scripts import post_generate_fixes - generated_dir = tmp_path / "generated_poc" + generated_dir = tmp_path / "domains" target = generated_dir / "core" / "budget_allocation.py" target.parent.mkdir(parents=True) target.write_text( @@ -737,7 +741,7 @@ def test_allof_merge_fails_when_narrow_base_is_ambiguous(tmp_path, monkeypatch): from scripts import post_generate_fixes - generated_dir = tmp_path / "generated_poc" + generated_dir = tmp_path / "domains" target = generated_dir / "core" / "ambiguous.py" target.parent.mkdir(parents=True) target.write_text( @@ -757,7 +761,7 @@ def test_allof_merge_fails_when_narrow_base_is_ambiguous(tmp_path, monkeypatch): def test_list_creatives_merged_model_restores_xor_and_legacy_aliases(tmp_path, monkeypatch): from scripts import post_generate_fixes - generated_dir = tmp_path / "generated_poc" + generated_dir = tmp_path / "domains" target = generated_dir / "creative" / "list_creatives_response.py" target.parent.mkdir(parents=True) target.write_text( @@ -791,7 +795,7 @@ def test_allof_merge_preserves_concrete_type_constraints_and_requiredness(tmp_pa from scripts import post_generate_fixes - generated_dir = tmp_path / "generated_poc" + generated_dir = tmp_path / "domains" target = generated_dir / "concrete.py" generated_dir.mkdir(parents=True) target.write_text( @@ -830,7 +834,7 @@ def test_generated_adagents_requires_authorization_or_non_empty_catalog(): import pytest from pydantic import TypeAdapter, ValidationError - from adcp.types.generated_poc.adagents import AdcpAgentsAuthorization + from adcp.types.domains.adagents import AdcpAgentsAuthorization adapter = TypeAdapter(AdcpAgentsAuthorization) with pytest.raises(ValidationError): @@ -845,7 +849,7 @@ def test_generated_adagents_requires_authorization_or_non_empty_catalog(): def test_post_generate_restores_product_fields_item_reference(tmp_path, monkeypatch): from scripts import post_generate_fixes - generated_dir = tmp_path / "generated_poc" + generated_dir = tmp_path / "domains" target = generated_dir / "media_buy" / "get_products_request.py" target.parent.mkdir(parents=True) target.write_text("fields: list[product_fields.Items] | None = None\n") @@ -863,7 +867,7 @@ def test_post_generate_restores_combined_get_products_field_enum(tmp_path, monke """The public projection enum remains the union after beta.9's schema split.""" from scripts import post_generate_fixes - generated_dir = tmp_path / "generated_poc" + generated_dir = tmp_path / "domains" media_buy_dir = generated_dir / "media_buy" media_buy_dir.mkdir(parents=True) target = media_buy_dir / "get_products_request.py" @@ -892,7 +896,7 @@ def test_post_generate_restores_combined_get_products_field_enum(tmp_path, monke def test_generated_adagents_rejects_null_required_catalog_arm(catalog_field): from pydantic import TypeAdapter, ValidationError - from adcp.types.generated_poc.adagents import AdcpAgentsAuthorization + from adcp.types.domains.adagents import AdcpAgentsAuthorization with pytest.raises(ValidationError): TypeAdapter(AdcpAgentsAuthorization).validate_python( @@ -903,7 +907,7 @@ def test_generated_adagents_rejects_null_required_catalog_arm(catalog_field): def test_post_generate_injects_postal_pairing_validator_idempotently(tmp_path, monkeypatch): from scripts import post_generate_fixes - generated_dir = tmp_path / "generated_poc" + generated_dir = tmp_path / "domains" target = generated_dir / "core" / "postal_area.py" target.parent.mkdir(parents=True) target.write_text( @@ -928,7 +932,7 @@ def test_post_generate_injects_postal_pairing_validator_idempotently(tmp_path, m def test_post_generate_prefers_legacy_postal_union_arm_idempotently(tmp_path, monkeypatch): from scripts import post_generate_fixes - generated_dir = tmp_path / "generated_poc" + generated_dir = tmp_path / "domains" target = generated_dir / "core" / "postal_area.py" target.parent.mkdir(parents=True) target.write_text( @@ -951,7 +955,7 @@ def test_unwrap_rootmodel_unions_keeps_field_metadata_and_discriminator(tmp_path """A union root becomes an annotated alias; the Field survives verbatim.""" from scripts import post_generate_fixes - generated_dir = tmp_path / "generated_poc" + generated_dir = tmp_path / "domains" generated_dir.mkdir() target = generated_dir / "deployment.py" target.write_text( @@ -991,7 +995,7 @@ def test_unwrap_rootmodel_unions_keeps_value_roots_wrapped(tmp_path, monkeypatch """A value root carries its constraints on the wrapper, so it stays one.""" from scripts import post_generate_fixes - generated_dir = tmp_path / "generated_poc" + generated_dir = tmp_path / "domains" generated_dir.mkdir() target = generated_dir / "values.py" source = ( @@ -1020,7 +1024,7 @@ def test_unwrap_rootmodel_unions_keeps_arms_of_a_discriminated_union(tmp_path, m """ from scripts import post_generate_fixes - generated_dir = tmp_path / "generated_poc" + generated_dir = tmp_path / "domains" generated_dir.mkdir() target = generated_dir / "asset_union.py" target.write_text( @@ -1059,7 +1063,7 @@ def test_unwrap_rootmodel_unions_keeps_an_arm_of_another_modules_field_union(tmp """ from scripts import post_generate_fixes - generated_dir = tmp_path / "generated_poc" + generated_dir = tmp_path / "domains" (generated_dir / "core").mkdir(parents=True) (generated_dir / "core" / "localized_creative_asset.py").write_text( "from typing import Annotated\n\n" @@ -1108,7 +1112,7 @@ def test_unwrap_rootmodel_unions_subclasses_a_single_model_root(tmp_path, monkey """A root naming one model becomes a subclass carrying its description.""" from scripts import post_generate_fixes - generated_dir = tmp_path / "generated_poc" + generated_dir = tmp_path / "domains" generated_dir.mkdir() target = generated_dir / "check_governance_request.py" target.write_text( @@ -1136,7 +1140,7 @@ def test_unwrap_rootmodel_unions_keeps_a_root_with_a_validator(tmp_path, monkeyp """The wrapper is the only place a cross-arm validator can live.""" from scripts import post_generate_fixes - generated_dir = tmp_path / "generated_poc" + generated_dir = tmp_path / "domains" generated_dir.mkdir() target = generated_dir / "postal_area.py" source = ( @@ -1167,7 +1171,7 @@ def test_discriminated_union_roots_report_one_tag_error(): from pydantic import TypeAdapter, ValidationError from adcp.types import ListProductsResponse - from adcp.types.generated_poc.core.deployment import Deployment + from adcp.types.domains.core.deployment import Deployment for union_type, payload in ( (Deployment, {"type": "nope", "is_live": True}), @@ -1191,7 +1195,7 @@ def test_account_reference_arms_are_reachable_through_the_alias(): def test_product_change_map_uses_valid_constrained_string_key_type(): """Constrained mapping keys must be valid for Pydantic and static type checkers.""" - from adcp.types.generated_poc.core.product_change_map import ProductChangeMap + from adcp.types.domains.core.product_change_map import ProductChangeMap schema = ProductChangeMap.model_json_schema() @@ -1249,11 +1253,11 @@ def test_consolidated_exports_include_annotated_type_aliases(tmp_path): def test_consolidation_filters_aggregate_schema_helpers(): """Aggregate/reference modules do not shadow canonical public models.""" - from scripts.consolidate_exports import GENERATED_POC_DIR, exports_for_public_consolidation + from scripts.consolidate_exports import DOMAINS_DIR, exports_for_public_consolidation - asset_union = GENERATED_POC_DIR / "core/assets/asset_union.py" + asset_union = DOMAINS_DIR / "core/assets/asset_union.py" async_ref = ( - GENERATED_POC_DIR + DOMAINS_DIR / "core/async_response_refs/media_buy/accept_proposal_async_response_submitted.py" ) @@ -1283,32 +1287,32 @@ def test_semantic_response_aliases_resolve_to_concrete_generated_arms(): expected_aliases = [ ( "ActivateSignalSuccessResponse", - "adcp.types.generated_poc.signals.activate_signal_response", + "adcp.types.domains.signals.activate_signal_response", "ActivateSignalResponse1", ), ( "AcquireRightsAcquiredResponse", - "adcp.types.generated_poc.brand.acquire_rights_response", + "adcp.types.domains.brand.acquire_rights_response", "AcquireRightsResponse1", ), ( "GetBrandIdentitySuccessResponse", - "adcp.types.generated_poc.brand.get_brand_identity_response", + "adcp.types.domains.brand.get_brand_identity_response", "GetBrandIdentityResponse1", ), ( "GetContentStandardsSuccessResponse", - "adcp.types.generated_poc.content_standards.get_content_standards_response", + "adcp.types.domains.content_standards.get_content_standards_response", "GetContentStandardsResponse1", ), ( "GetCreativeFeaturesSuccessResponse", - "adcp.types.generated_poc.creative.get_creative_features_response", + "adcp.types.domains.creative.get_creative_features_response", "GetCreativeFeaturesResponse1", ), ( "GetMediaBuyArtifactsSuccessResponse", - "adcp.types.generated_poc.content_standards.get_media_buy_artifacts_response", + "adcp.types.domains.content_standards.get_media_buy_artifacts_response", "GetMediaBuyArtifactsResponse1", ), ( @@ -1335,7 +1339,7 @@ def test_sync_creatives_response_arm_matches_schema_creative_fields(): from pydantic import ValidationError from adcp._version import _read_packaged_version - from adcp.types.generated_poc.creative.sync_creatives_response import Creative + from adcp.types.domains.creative.sync_creatives_response import Creative from adcp.validation.version import resolve_bundle_key bundle_key = resolve_bundle_key(_read_packaged_version()) @@ -1366,7 +1370,7 @@ def test_sync_creatives_response_arm_accepts_submitted_response(): """sync_creatives schema includes a submitted async response branch.""" from pydantic import TypeAdapter - from adcp.types.generated_poc.creative.sync_creatives_response import ( + from adcp.types.domains.creative.sync_creatives_response import ( SyncCreativesResponse, SyncCreativesResponse3, ) @@ -1391,16 +1395,16 @@ def test_schema_derived_response_arms_preserve_nested_validation(): import pytest from pydantic import ValidationError - from adcp.types.generated_poc.account.get_account_financials_response import Invoice - from adcp.types.generated_poc.brand.get_brand_identity_response import ( + from adcp.types.domains.account.get_account_financials_response import Invoice + from adcp.types.domains.brand.get_brand_identity_response import ( File, Fonts, GetBrandIdentityResponse1, ) - from adcp.types.generated_poc.content_standards.validate_content_delivery_response import ( + from adcp.types.domains.content_standards.validate_content_delivery_response import ( ValidateContentDeliveryResponse1, ) - from adcp.types.generated_poc.creative.preview_creative_response import Input + from adcp.types.domains.creative.preview_creative_response import Input with pytest.raises(ValidationError): GetBrandIdentityResponse1.model_validate( @@ -1449,33 +1453,22 @@ def test_schema_derived_response_arms_preserve_nested_validation(): ) -def test_post_generate_preserves_open_delivery_format_kind(tmp_path, monkeypatch) -> None: - """The public delivery alias must stay open after clean code generation.""" - from scripts import post_generate_fixes - - generated_dir = tmp_path / "generated_poc" - target = generated_dir / "creative" / "get_creative_delivery_response.py" - target.parent.mkdir(parents=True) - target.write_text( - "from typing import Annotated\n" - "from pydantic import Field\n" - "from ..core import canonical_format_kind\n\n" - "class Creative:\n" - " format_kind: Annotated[\n" - " canonical_format_kind.CanonicalFormatKind | None,\n" - " Field(description='Canonical format kind delivered for this creative.'),\n" - " ] = None\n" - ) - monkeypatch.setattr(post_generate_fixes, "OUTPUT_DIR", generated_dir) - - post_generate_fixes.preserve_open_delivery_format_kind() - generated_source = target.read_text() - post_generate_fixes.preserve_open_delivery_format_kind() - - assert target.read_text() == generated_source - assert "CanonicalFormatKind | str | None" in generated_source - assert "union_mode='left_to_right'" in generated_source - compile(generated_source, str(target), "exec") +# ``test_post_generate_preserves_open_delivery_format_kind`` stood here. It +# graded ``preserve_open_delivery_format_kind``, a post-generation fix that +# hand-patched ONE generated field to ``CanonicalFormatKind | str | None`` so a +# delivery response could retain an unknown kind. The fix is deleted: every +# reference to ``core/canonical-format-kind.json`` now generates ``str`` +# (``OPEN_VOCABULARY_SCHEMAS`` in ``scripts/generate_types.py``), so there is +# nothing to patch and no per-site widening to keep in step. +# +# Its obligation — a delivery response retains a kind this SDK does not know — +# is graded by +# ``tests/test_open_format_kind_vocabulary.py``'s +# ``test_every_kind_bearing_model_types_the_kind_as_a_string`` +# and +# ``tests/test_forward_compat_format_kind.py``'s +# ``test_unknown_format_kind_is_retained_on_a_response``, +# both of which read the public model rather than a string in a generated file. def test_post_generate_sync_creatives_response_arms_match_schema_creative_fields( @@ -1490,7 +1483,7 @@ def test_post_generate_sync_creatives_response_arms_match_schema_creative_fields from adcp.validation.version import resolve_bundle_key from scripts import post_generate_fixes - generated_dir = tmp_path / "generated_poc" + generated_dir = tmp_path / "domains" target = generated_dir / "creative" / "sync_creatives_response.py" target.parent.mkdir(parents=True) target.write_text( @@ -1554,12 +1547,25 @@ def test_generated_types_can_import(): assert hasattr(generated, "Property") -def test_generated_poc_types_can_import(): - """Test that generated_poc types can be imported.""" - from adcp.types import _generated as generated_poc +def _unpatched_targeting_model(name: str): + """A model declaring the generated, PRE-widening ``targeting_overlay``. - # The generated_poc package should exist - assert generated_poc is not None + ``_forward_compat`` widens ``targeting_overlay`` at runtime to admit beta.14 + ``TargetingOverlay`` objects. The generated source declares only + ``TargetingOverlayInput | None``, so a model that carries that annotation + and nothing else is exactly the regression the public-surface guard exists + to catch. + """ + from pydantic import Field, create_model + + from adcp.types.canonical_creative import CanonicalBoundaryModel + from adcp.types.domains.core.targeting_input import TargetingOverlayInput + + return create_model( + name, + __base__=CanonicalBoundaryModel, + targeting_overlay=(TargetingOverlayInput | None, Field(default=None)), + ) def _public_targeting_mutation_fields(): @@ -1656,10 +1662,10 @@ def test_targeting_overlay_input_is_generated_and_public(): from pydantic import BaseModel, RootModel, TypeAdapter, ValidationError from adcp.types import TargetingOverlay, _generated - from adcp.types.generated_poc.core.targeting_input import TargetingOverlayInput + from adcp.types.domains.core.targeting_input import TargetingOverlayInput from scripts.consolidate_exports import exports_for_public_consolidation - source = Path(__file__).parent.parent / "src/adcp/types/generated_poc/core/targeting_input.py" + source = Path(__file__).parent.parent / "src/adcp/types/domains/core/targeting_input.py" assert "TargetingOverlayInput" in exports_for_public_consolidation(source) assert _generated.TargetingOverlayInput is TargetingOverlayInput assert issubclass(TargetingOverlayInput, BaseModel) @@ -1716,11 +1722,14 @@ def test_targeting_public_surface_guard_rejects_new_public_clone(monkeypatch, su from pydantic import Field, create_model - from adcp.types.canonical_creative import _canonical_clone, _PackageRequestBase - - # This private base still has the generated, pre-patch FieldInfo. Merely - # existing privately is fine; exporting a fresh copy is the regression. - clone = _canonical_clone("UnpatchedTargetingPackage", _PackageRequestBase) + # The regression is a public model that declares the generated, pre-patch + # annotation. It used to be reproduced by cloning the private canonical + # base, which held the pre-patch FieldInfo because ``_forward_compat`` + # widened only the public clone; now that the canonical models are real + # subclasses there is no unpatched copy to clone, so the test declares the + # pre-patch annotation itself. Same subject, no dependence on a layer the + # SDK no longer has. + clone = _unpatched_targeting_model("UnpatchedTargetingPackage") value = clone if nested: request = create_model("FutureRequest", packages=(list[clone], ...)) @@ -1741,9 +1750,8 @@ def test_targeting_public_surface_guard_resolves_uncached_lazy_exports(monkeypat from types import ModuleType from adcp.types import _eager - from adcp.types.canonical_creative import _canonical_clone, _PackageRequestBase - clone = _canonical_clone("UnpatchedTargetingPackage", _PackageRequestBase) + clone = _unpatched_targeting_model("UnpatchedTargetingPackage") module = import_module(surface) name = "FutureLazyTargetingRequest" assert name not in vars(module) @@ -1763,18 +1771,27 @@ def test_targeting_public_surface_guard_resolves_uncached_lazy_exports(monkeypat vars(module).pop(name, None) -def test_targeting_public_surface_guard_accepts_a_compatible_new_clone(monkeypatch): - """Discover a seventh model by export, while leaving private bases alone.""" +def test_targeting_public_surface_guard_accepts_a_compatible_new_subclass(monkeypatch): + """Discover a seventh model by export, while leaving private models alone.""" from adcp.types import canonical_creative - clone = canonical_creative._canonical_clone("FuturePackage", canonical_creative.PackageRequest) - monkeypatch.setattr(canonical_creative, "FuturePackage", clone, raising=False) + # A real subclass of the patched public model inherits the widened + # FieldInfo, so it is compatible — this is now literally how every + # canonical model is built. + subclass = type("FuturePackage", (canonical_creative.PackageRequest,), {}) + monkeypatch.setattr(canonical_creative, "FuturePackage", subclass, raising=False) + # An unexported private model is not a composition boundary, even when it + # declares the pre-patch annotation. (The former subject of this assertion + # was ``_PackageRequestBase``, a private clone that no longer exists; the + # property under test is export-reachability, not that one class.) + private = _unpatched_targeting_model("_PrivateUnreachablePackage") + monkeypatch.setattr(canonical_creative, "_PrivateUnreachablePackage", private, raising=False) monkeypatch.setattr( canonical_creative, "__all__", [*canonical_creative.__all__, "FuturePackage"] ) discovered = _public_targeting_mutation_fields() - assert clone in discovered - assert canonical_creative._PackageRequestBase not in discovered + assert subclass in discovered + assert private not in discovered test_targeting_overlay_input_is_generated_and_public() @@ -1822,7 +1839,7 @@ def references(node, path=()): (f"media-buy/{stem}.json", ("properties", "targeting_overlay")) for stem in expected } - generated_root = root / "src/adcp/types/generated_poc" + generated_root = root / "src/adcp/types/domains" generated_sites = set() for source in generated_root.rglob("*.py"): text = source.read_text() @@ -2101,7 +2118,7 @@ def test_post_generate_required_nullable_field_stays_required_and_nullable(tmp_p from adcp.validation.version import resolve_bundle_key from scripts import post_generate_fixes - generated_dir = tmp_path / "generated_poc" + generated_dir = tmp_path / "domains" target = generated_dir / "media_buy" / "create_media_buy_response.py" target.parent.mkdir(parents=True) target.write_text( @@ -2163,7 +2180,7 @@ def test_generated_create_media_buy_success_matches_schema_nullability(): import ast from pathlib import Path - source = Path("src/adcp/types/generated_poc/media_buy/create_media_buy_response.py").read_text() + source = Path("src/adcp/types/domains/media_buy/create_media_buy_response.py").read_text() module = ast.parse(source) success_class = next( node @@ -2197,7 +2214,7 @@ def _write_response_arm_fixture( from scripts import post_generate_fixes - generated_dir = tmp_path / "generated_poc" + generated_dir = tmp_path / "domains" target = generated_dir / "account" / "sync_accounts_response.py" target.parent.mkdir(parents=True) target.write_text( @@ -2619,7 +2636,7 @@ def _write_required_group_fixture(tmp_path, monkeypatch, schema, module_source): from scripts import post_generate_fixes schema_dir = tmp_path / "schemas" - generated_dir = tmp_path / "generated_poc" + generated_dir = tmp_path / "domains" (schema_dir / "media-buy").mkdir(parents=True) (schema_dir / "media-buy" / "budget-plan.json").write_text(json.dumps(schema)) (generated_dir / "media_buy").mkdir(parents=True) @@ -2722,7 +2739,7 @@ def test_root_required_group_resolves_the_generator_acronym_casing(tmp_path, mon ) schema_dir = tmp_path / "schemas" - generated_dir = tmp_path / "generated_poc" + generated_dir = tmp_path / "domains" (schema_dir / "sponsored-intelligence").mkdir(parents=True) (schema_dir / "sponsored-intelligence" / "si-send-message-request.json").write_text( json.dumps(schema) diff --git a/tests/test_codegen_contract_compatibility.py b/tests/test_codegen_contract_compatibility.py index 599ed47f0..4ff4ee2eb 100644 --- a/tests/test_codegen_contract_compatibility.py +++ b/tests/test_codegen_contract_compatibility.py @@ -47,7 +47,7 @@ def test_product_signal_targeting_option_keeps_discriminated_signal_ref() -> Non from typing import get_args from adcp import ProductSignalTargetingOption - from adcp.types.generated_poc.core.signal_ref import SignalRef + from adcp.types.domains.core.signal_ref import SignalRef # SignalRef is Annotated[SignalRef1 | SignalRef2 | SignalRef3, Field(...)]; # Pydantic lifts the metadata into FieldInfo and keeps the union. @@ -77,9 +77,13 @@ def test_product_signal_targeting_option_keeps_discriminated_signal_ref() -> Non def test_creative_representation_keeps_canonical_format_contract() -> None: from adcp import LegacyBuildCreativeRequest from adcp.types import CanonicalFormatKind - from adcp.types.generated_poc.core.creative_representation import CreativeRepresentation + from adcp.types.domains.core.creative_representation import CreativeRepresentation - assert CreativeRepresentation.model_fields["format_kind"].annotation is CanonicalFormatKind + # ``str``, not the enum: the vocabulary is open at every reference and + # governed by a validator on the request models. Asserting membership of + # the generated enum here keeps the vocabulary itself in the test. + assert CreativeRepresentation.model_fields["format_kind"].annotation is str + assert "image" in {kind.value for kind in CanonicalFormatKind} schema = CreativeRepresentation.model_json_schema() assert schema["properties"]["format_kind"]["description"].startswith("Canonical 3.2 path.") assert schema["not"] == { @@ -97,10 +101,14 @@ def test_creative_representation_keeps_canonical_format_contract() -> None: "assets": {}, } parsed_representation = CreativeRepresentation.model_validate(representation) - assert parsed_representation.format_kind is CanonicalFormatKind.image + assert parsed_representation.format_kind == CanonicalFormatKind.image - with pytest.raises(ValidationError): - CreativeRepresentation.model_validate({**representation, "format_kind": "unknown"}) + # ``CreativeRepresentation`` carries the open vocabulary, like every other + # model in this SDK: an unknown kind is retained rather than refused, in + # either direction. Checking the vocabulary is the caller's, through + # ``adcp.types.is_canonical_format_kind``. + unknown = CreativeRepresentation.model_validate({**representation, "format_kind": "unknown"}) + assert unknown.format_kind == "unknown" for seller_bound_field in ("format_id", "format_option_ref", "representation_selection"): with pytest.raises(ValidationError): @@ -114,18 +122,27 @@ def test_creative_representation_keeps_canonical_format_contract() -> None: "name": "Test creative", "representations": [{**representation, "format_kind": "unknown"}], } - with pytest.raises(ValidationError): - LegacyBuildCreativeRequest.model_validate( - { - "idempotency_key": "idem-123456789012", - "creative_representation_set": representation_set, - } - ) + # ``LegacyBuildCreativeRequest`` is a request model and it too retains the + # unknown kind rather than refusing it: no model in this SDK refuses a + # format kind, because a pinned library cannot tell a kind a seller + # invented from a kind defined after its pin. That is the one cost of the + # open vocabulary, and it is declared + # by name in ``tests/conformance/_schema_parity.py``'s + # ``format_kind_is_an_open_vocabulary_by_decision`` — the bundled schema + # still refuses this document, and the model no longer agrees. Retention is + # asserted at the nested position so the claim is graded rather than implied. + built = LegacyBuildCreativeRequest.model_validate( + { + "idempotency_key": "idem-123456789012", + "creative_representation_set": representation_set, + } + ) + assert built.creative_representation_set.representations[0].format_kind == "unknown" def test_transformer_requires_a_canonical_or_legacy_output_declaration() -> None: from adcp.types import ListTransformersResponse - from adcp.types.generated_poc.core.transformer import Transformer + from adcp.types.domains.core.transformer import Transformer base_transformer = {"transformer_id": "transformer_1", "name": "Test transformer"} with pytest.raises(ValidationError): @@ -162,17 +179,17 @@ def test_public_response_bases_remain_constructible_and_arms_remain_specific() - ListContentStandardsSuccessResponse, UpdateContentStandardsSuccessResponse, ) - from adcp.types.generated_poc.account.sync_governance_response import SyncGovernanceResponse1 - from adcp.types.generated_poc.compliance.comply_test_controller_response import ( + from adcp.types.domains.account.sync_governance_response import SyncGovernanceResponse1 + from adcp.types.domains.compliance.comply_test_controller_response import ( ComplyTestControllerResponse1, ) - from adcp.types.generated_poc.content_standards.create_content_standards_response import ( + from adcp.types.domains.content_standards.create_content_standards_response import ( CreateContentStandardsResponse1, ) - from adcp.types.generated_poc.content_standards.list_content_standards_response import ( + from adcp.types.domains.content_standards.list_content_standards_response import ( ListContentStandardsResponse1, ) - from adcp.types.generated_poc.content_standards.update_content_standards_response import ( + from adcp.types.domains.content_standards.update_content_standards_response import ( UpdateContentStandardsResponse1, ) from adcp.utils.response_parser import parse_json_or_text @@ -275,7 +292,7 @@ def test_create_media_buy_request_requires_one_root_anyof_group() -> None: the five and none of the three is a media buy with no packages, no budget and no proposal. """ - from adcp.types.generated_poc.media_buy.create_media_buy_request import CreateMediaBuyRequest + from adcp.types.domains.media_buy.create_media_buy_request import CreateMediaBuyRequest with pytest.raises(ValidationError, match="at least one of these field groups"): CreateMediaBuyRequest.model_validate(dict(_MEDIA_BUY_UNCONDITIONAL_FIELDS)) @@ -298,7 +315,7 @@ def test_sync_creatives_request_takes_assignment_operations_without_creatives() ``assignments`` and ``assignment_operations``, so a request that traffics existing creative IDs carries no ``creatives`` at all. """ - from adcp.types.generated_poc.creative.sync_creatives_request import SyncCreativesRequest + from adcp.types.domains.creative.sync_creatives_request import SyncCreativesRequest base = {"idempotency_key": "idem-key-0123456789", "account": {"account_id": "acct_1"}} diff --git a/tests/test_codegen_invariants.py b/tests/test_codegen_invariants.py index 5e54dd603..36fe98aee 100644 --- a/tests/test_codegen_invariants.py +++ b/tests/test_codegen_invariants.py @@ -19,7 +19,7 @@ RefineProposalsRequest, ) -_GENERATED_DIR = Path(__file__).resolve().parents[1] / "src/adcp/types/generated_poc" +_GENERATED_DIR = Path(__file__).resolve().parents[1] / "src/adcp/types/domains" _POSTAL_FLAGS = ( "us_zip", "us_zip_plus_four", diff --git a/tests/test_collision_aliases.py b/tests/test_collision_aliases.py index 191d3badf..22f07c199 100644 --- a/tests/test_collision_aliases.py +++ b/tests/test_collision_aliases.py @@ -1,6 +1,6 @@ """Tests for cross-module name collision aliases (#911, Step 2). -Several bare type names are defined in more than one generated_poc module. +Several bare type names are defined in more than one generated domain module. When adopters write ``from adcp.types import Creative`` they silently get whichever module wins the consolidate sort order. ``adcp.types.disambiguated`` carries every variant under a module-derived qualified name; aliases.py gives @@ -18,7 +18,7 @@ import pytest from pydantic import BaseModel, TypeAdapter, ValidationError -# (alias name, source module under generated_poc, base class name in that module) +# (alias name, source module under adcp.types.domains, base class name in that module) COLLISION_ALIASES: list[tuple[str, str, str]] = [ # Remaining concrete SDK 8 migration inventory (#911 comment) ("AccountIdReference", "core.account_ref", "AccountReference1"), @@ -151,16 +151,16 @@ def _source_class(module_suffix: str, base_name: str) -> type: - module = importlib.import_module(f"adcp.types.generated_poc.{module_suffix}") + module = importlib.import_module(f"adcp.types.domains.{module_suffix}") return getattr(module, base_name) def _duration_unit_source_class() -> type: try: - module = importlib.import_module("adcp.types.generated_poc.enums.duration_unit") + module = importlib.import_module("adcp.types.domains.enums.duration_unit") return module.DurationUnit except ModuleNotFoundError as exc: - if exc.name != "adcp.types.generated_poc.enums.duration_unit": + if exc.name != "adcp.types.domains.enums.duration_unit": raise return _source_class("core.duration", "Unit") @@ -302,7 +302,7 @@ def test_listing_creative_is_the_rich_shape() -> None: """ import adcp.types as types_module from adcp.types import aliases as a - from adcp.types.generated_poc.creative.list_creatives_response import ( + from adcp.types.domains.creative.list_creatives_response import ( Creatives, Creatives1, ) @@ -370,7 +370,7 @@ def _minimal_list_creatives_response(creative: dict[str, object]) -> dict[str, o def test_list_creatives_response_enforces_format_reference_xor() -> None: """Generated response rows must keep the schema oneOf exactness.""" from adcp.types import aliases as a - from adcp.types.generated_poc.creative.list_creatives_response import ListCreativesResponse + from adcp.types.domains.creative.list_creatives_response import ListCreativesResponse legacy = _minimal_list_creative() canonical = _minimal_list_creative(format_id=None, format_kind="image") diff --git a/tests/test_collision_guard.py b/tests/test_collision_guard.py index 6f258f673..553aa8366 100644 --- a/tests/test_collision_guard.py +++ b/tests/test_collision_guard.py @@ -1,26 +1,25 @@ """Tests for the consolidate-step reachability guard (issues #911, #1080). -`consolidate_exports.py` flattens every `generated_poc/` module into a single +`consolidate_exports.py` flattens every generated domain module into a single namespace. When the same bare type name is declared in more than one module, one class wins that name in `_generated` and the others would be unreachable. -The public mirror under `adcp.types.domains` answers that: one module per -schema, re-exporting what that schema declares, so every variant has a path and -nothing is renamed. - -These tests assert the mirror covers every generated class, that the domain -roots bind exactly the unambiguous names, and that the guard raises when a -class would be left unreachable. +The generated tree itself answers that: `adcp.types.domains` is where codegen +DEFINES each class, one module per schema, so every variant already has a +public path and nothing is renamed. + +These tests assert the domain roots bind exactly the unambiguous names and +that the guard raises when a class would be left unreachable. That every +generated class really is importable at its own definition path is graded +against the live import graph in ``tests/test_export_surface_is_derived.py``, +which is where the reachability charter lives. """ from __future__ import annotations -import collections - import pytest from scripts.consolidate_exports import ( _enforce_every_class_is_reachable, - _mirror_relative_paths, colliding_names, exports_for_public_consolidation, extract_exports_from_module, @@ -44,43 +43,19 @@ def test_a_domain_namespace_cannot_split_a_name_its_own_domain_declares_twice(): else: unsolvable += 1 # A domain root handles the names no single domain declares twice; the rest - # need the schema's own module, which is why the mirror exists. + # need the schema's own module, which is why the tree is public to that depth. assert solvable > 0 and unsolvable > 0, (solvable, unsolvable) assert solvable + unsolvable == len(collisions) def test_core_declares_nine_units_and_no_domain_namespace_could_hold_them(): - """The concrete case, pinned so the mirror's depth is not mistaken for noise.""" + """The concrete case, pinned so the tree's depth is not mistaken for noise.""" declared = scan_declared_names() units = {m for m in declared["Unit"] if schema_domain(m) == "core"} assert len(units) == 9, sorted(units) assert "Unit" not in unambiguous_domain_bindings(declared)["core"] -def test_the_mirror_covers_every_declared_pair_exactly_once(): - """One public module per schema, re-exporting what that schema declares.""" - declared = scan_declared_names() - mirrored = collections.Counter() - for rel in _mirror_relative_paths(): - module_name = ".".join([*rel.parts[:-1], rel.stem]) - for name in extract_exports_from_module(_mirror_path(rel)): - mirrored[(module_name, name)] += 1 - - expected = { - (module_name, type_name) - for type_name, modules in declared.items() - for module_name in modules - } - assert set(mirrored) == expected - assert all(count == 1 for count in mirrored.values()) - - -def _mirror_path(rel): - from scripts.consolidate_exports import GENERATED_POC_DIR - - return GENERATED_POC_DIR / rel - - def test_a_domain_root_binds_the_unambiguous_names_and_renames_nothing(): """No invented name anywhere: a root key is the name codegen gave the class.""" declared = scan_declared_names() @@ -102,9 +77,9 @@ def test_the_unfiltered_scan_is_what_reaches_brand_discovery(): own root; ``brand_discovery`` keeps nothing, so reading the domains layer off the filtered scan left its own types unreachable. """ - from scripts.consolidate_exports import GENERATED_POC_DIR + from scripts.consolidate_exports import DOMAINS_DIR - path = GENERATED_POC_DIR / "brand_discovery.py" + path = DOMAINS_DIR / "brand_discovery.py" assert exports_for_public_consolidation(path) == set() assert "Brand" in extract_exports_from_module(path) assert "brand_discovery" in scan_declared_names()["Brand"] @@ -116,8 +91,8 @@ def test_current_tree_leaves_no_class_unreachable(): _enforce_every_class_is_reachable(scan_declared_names()) -def test_a_declared_class_the_mirror_does_not_carry_fails_the_build(): - """A pair with no mirror export fails the consolidate step.""" +def test_a_declared_class_with_no_public_path_fails_the_build(): + """A pair the tree cannot serve fails the consolidate step.""" declared = scan_declared_names() declared["WidgetGuardSentinel"] = {"core.widget_a"} @@ -270,7 +245,7 @@ def test_aggregate_modules_export_only_their_root( module_path = tmp_path / relative_path module_path.parent.mkdir(parents=True, exist_ok=True) module_path.write_text(source) - monkeypatch.setattr("scripts.consolidate_exports.GENERATED_POC_DIR", tmp_path) + monkeypatch.setattr("scripts.consolidate_exports.DOMAINS_DIR", tmp_path) exports = exports_for_public_consolidation(module_path) diff --git a/tests/test_composability_invariant.py b/tests/test_composability_invariant.py index 829497309..5a908935b 100644 --- a/tests/test_composability_invariant.py +++ b/tests/test_composability_invariant.py @@ -12,13 +12,13 @@ import adcp.types.capabilities as capability_types from adcp.decisioning.capabilities import MediaBuy, Portfolio from adcp.types import AcceptancePolicyDiscovery, PrimaryCountry, PublisherDomain -from adcp.types.generated_poc.bundled.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.bundled.protocol.get_adcp_capabilities_response import ( AcceptancePolicyDiscovery as BundledAcceptancePolicyDiscovery, ) -from adcp.types.generated_poc.bundled.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.bundled.protocol.get_adcp_capabilities_response import ( PrimaryCountry as BundledPrimaryCountry, ) -from adcp.types.generated_poc.bundled.protocol.get_adcp_capabilities_response import ( +from adcp.types.domains.bundled.protocol.get_adcp_capabilities_response import ( PublisherDomain as BundledPublisherDomain, ) diff --git a/tests/test_decisioning_async_discovery.py b/tests/test_decisioning_async_discovery.py index a64ef442c..4aa28a69b 100644 --- a/tests/test_decisioning_async_discovery.py +++ b/tests/test_decisioning_async_discovery.py @@ -663,7 +663,7 @@ async def get_signals(self, req, ctx): def test_task_type_enum_includes_discovery_verbs() -> None: """The generated TaskType enum (which validates tasks_get / list_tasks / webhook task_type) carries both discovery verbs.""" - from adcp.types.generated_poc.enums.task_type import TaskType + from adcp.types.domains.enums.task_type import TaskType values = {t.value for t in TaskType} assert "get_products" in values diff --git a/tests/test_decisioning_capabilities_submodule.py b/tests/test_decisioning_capabilities_submodule.py index e3d13686d..fef026425 100644 --- a/tests/test_decisioning_capabilities_submodule.py +++ b/tests/test_decisioning_capabilities_submodule.py @@ -395,7 +395,7 @@ def test_signals_features_and_content_standards_re_exported() -> None: collides with the unrelated wire ``adcp.types.ContentStandards``) are surfaced through :mod:`adcp.decisioning.capabilities` so adopters declaring deep Signals / MediaBuy blocks don't have to dig into - ``generated_poc``. + the generated tree. """ from adcp.decisioning.capabilities import ( ContentStandards, @@ -800,7 +800,7 @@ async def test_projection_si_block_constructs_with_submodule_imports(make_handle ``SiCapabilities`` blocks. Adopters constructing SI declarations pull these from :mod:`adcp.decisioning.capabilities` — the re-export coverage test for the submodule's ``__all__`` is the - only thing keeping deeply-nested SI declarations off ``generated_poc`` + only thing keeping deeply-nested SI declarations off the generated tree direct imports. """ from adcp.decisioning import DecisioningCapabilities diff --git a/tests/test_delivery_manifest_readback.py b/tests/test_delivery_manifest_readback.py index c032a980a..6a3c2625e 100644 --- a/tests/test_delivery_manifest_readback.py +++ b/tests/test_delivery_manifest_readback.py @@ -18,10 +18,10 @@ LegacyGetCreativeDeliveryResponse, ) from adcp.types.core import AgentConfig, Protocol, TaskResult, TaskStatus -from adcp.types.generated_poc.core.creative_manifest import ( +from adcp.types.domains.core.creative_manifest import ( CreativeManifest as WireCreativeManifest, ) -from adcp.types.generated_poc.core.creative_variant import ( +from adcp.types.domains.core.creative_variant import ( CreativeVariant as WireCreativeVariant, ) @@ -73,7 +73,7 @@ def test_delivery_response_round_trips_unknown_nested_manifest(model, json_input ) creative = response.creatives[0] assert creative.format_kind == FUTURE_KIND - assert creative.variants[0].manifest.format_kind is CanonicalFormatKind.image + assert creative.variants[0].manifest.format_kind == CanonicalFormatKind.image variant = creative.variants[1] assert variant.manifest.format_kind == FUTURE_KIND assert variant.impressions == 7 @@ -91,25 +91,74 @@ def test_delivery_response_round_trips_unknown_nested_manifest(model, json_input @pytest.mark.parametrize("input_model", [CreativeManifest, WireCreativeManifest]) @pytest.mark.parametrize("reuse_instance", [False, True], ids=["dump", "instance"]) def test_delivery_manifest_cannot_bypass_strict_input(model, input_model, reuse_instance): + """What a read-back manifest does when handed to an input model. + + #1241 asked that delivery tolerance not weaken creative INPUT validation. + That question had an answer while the vocabulary was a type: delivery was + tolerant, input was strict, and the two had to stay apart. The + open-vocabulary decision removes the premise — ``format_kind`` is ``str`` + on every model and NO model refuses a value, because a pinned SDK cannot + tell a kind invented by a seller from a kind defined after its pin (see + :mod:`adcp.types.canonical_creative`). So there is no longer a strict side + to weaken, and what this test grades is what remains true: + + * a dump round-trips into either input model and RETAINS the unknown kind; + * an instance is accepted or refused by ordinary pydantic class rules, not + by anything about the vocabulary — the canonical response nests a + subclass of the generated manifest, so the instance revalidates and + passes; the legacy response nests a ``_forward_compat`` clone that + inherits from nothing, so it is refused as ``model_type``. + + Deciding which kinds to accept is the caller's, through + ``adcp.types.is_canonical_format_kind``, and + ``tests/test_open_format_kind_vocabulary.py`` grades that. + """ response = model.model_validate(delivery_payload()) manifest = response.creatives[0].variants[1].manifest - # A tolerant subclass could bypass Pydantic's default instance revalidation. value = manifest if reuse_instance else manifest.model_dump(mode="json") - with pytest.raises(ValidationError): - input_model.model_validate(value) - assert not isinstance(manifest, input_model) + + if not reuse_instance: + assert input_model.model_validate(value).format_kind == FUTURE_KIND + elif input_model is CreativeManifest or model is not GetCreativeDeliveryResponse: + # Not an instance of the field's class: refused structurally. + with pytest.raises(ValidationError) as error: + input_model.model_validate(value) + assert error.value.errors()[0]["type"] == "model_type" + else: + assert input_model.model_validate(value).format_kind == FUTURE_KIND + # ``assert not isinstance(manifest, input_model)`` used to stand here and is now + # false by construction: phase 2 (feat/no-clones-and-arm-names) made every + # canonical model a real subclass of the generated wire model it refines, so the + # tolerant delivery manifest IS an instance of the generated CreativeManifest. The + # ``pytest.raises`` above is the obligation this test exists for, and it is what + # grades the instance-revalidation bypass that the subclassing opened. @pytest.mark.parametrize("model", RESPONSE_MODELS, ids=["canonical", "legacy"]) @pytest.mark.parametrize("input_model", [CreativeVariant, WireCreativeVariant]) def test_delivery_variant_cannot_bypass_strict_input(model, input_model): + """The variant level of the same obligation, split the same way. + + The variant level of the same thing. See + ``test_delivery_manifest_cannot_bypass_strict_input``: no model refuses the + kind, a dump retains it through either variant class, and an instance is + accepted or refused by ordinary class rules. + """ response = model.model_validate(delivery_payload()) variant = response.creatives[0].variants[1] - with pytest.raises(ValidationError): - input_model.model_validate(variant) - with pytest.raises(ValidationError): - input_model.model_validate(variant.model_dump(mode="json")) - assert not isinstance(variant, input_model) + dumped = variant.model_dump(mode="json") + + assert input_model.model_validate(dumped).manifest.format_kind == FUTURE_KIND + + if input_model is not CreativeVariant and model is GetCreativeDeliveryResponse: + assert input_model.model_validate(variant).manifest.format_kind == FUTURE_KIND + else: + with pytest.raises(ValidationError) as error: + input_model.model_validate(variant) + assert error.value.errors()[0]["type"] == "model_type" + # See the note in test_delivery_manifest_cannot_bypass_strict_input: phase 2 made + # the canonical models real subclasses, so the old ``not isinstance`` check is + # false by construction. The two ``pytest.raises`` above carry the obligation. @pytest.mark.parametrize("model", RESPONSE_MODELS, ids=["canonical", "legacy"]) @@ -162,7 +211,7 @@ def test_delivery_accepts_known_input_models(model, input_model, as_variant): payload["creatives"][0]["variants"][0] = variant response = model.model_validate(payload) served = response.creatives[0].variants[0] - assert served.manifest.format_kind is CanonicalFormatKind.image + assert served.manifest.format_kind == CanonicalFormatKind.image assert served.impressions == 3 @@ -193,7 +242,13 @@ def test_delivery_keeps_optional_manifest_and_kind_defaults(model): payload = delivery_payload() variants = payload["creatives"][0]["variants"] variants[0].pop("manifest") - variants[1]["manifest"].pop("format_kind") + # Was ``variants[1]["manifest"].pop("format_kind")``. Dropping the key leaves a + # manifest with neither ``format_id`` nor ``format_kind``, which + # core/creative-manifest.json's root oneOf forbids and #1368's + # enforce_root_required_groups now holds at runtime. An explicit null is the + # schema-valid way to state the same thing: the group is keyed on the field being + # present, not on it being non-null. + variants[1]["manifest"]["format_kind"] = None response = model.model_validate(payload) assert response.creatives[0].variants[0].manifest is None assert response.creatives[0].variants[1].manifest.format_kind is None diff --git a/tests/test_derive_packages.py b/tests/test_derive_packages.py index 9d413e0ee..aea0a2ddc 100644 --- a/tests/test_derive_packages.py +++ b/tests/test_derive_packages.py @@ -90,7 +90,7 @@ def test_derive_packages_propagates_allocation_flight_times() -> None: def test_derive_packages_accepts_typed_total_budget() -> None: """Typed ``TotalBudget`` from the wire model works the same as a dict.""" from adcp.types import PackageRequest - from adcp.types.generated_poc.media_buy.create_media_buy_request import TotalBudget + from adcp.types.domains.media_buy.create_media_buy_request import TotalBudget payload = { "allocations": [ diff --git a/tests/test_discriminated_unions.py b/tests/test_discriminated_unions.py index 60b73b9e5..8c304ea58 100644 --- a/tests/test_discriminated_unions.py +++ b/tests/test_discriminated_unions.py @@ -705,14 +705,14 @@ def test_public_property_tag_is_from_shared_schema(self): from adcp import PropertyTag # Should come from the shared schema in core/, not embedded in another schema - assert PropertyTag.__module__ == "adcp.types.generated_poc.core.property_tag" + assert PropertyTag.__module__ == "adcp.types.domains.core.property_tag" def test_property_id_is_from_shared_schema(self): """Public PropertyId should be from the shared property_id schema.""" from adcp import PropertyId # Should come from the shared schema in core/ - assert PropertyId.__module__ == "adcp.types.generated_poc.core.property_id" + assert PropertyId.__module__ == "adcp.types.domains.core.property_id" def test_property_tag_works_with_publisher_properties_by_tag(self): """PropertyTag should work correctly with PublisherPropertiesByTag.""" @@ -732,7 +732,7 @@ def test_property_tag_works_with_publisher_properties_by_tag(self): def test_shared_schema_prevents_collision(self): """Verify that both adagents and publisher_property_selector import from shared schema.""" from adcp import PropertyTag - from adcp.types.generated_poc.core import property_tag + from adcp.types.domains.core import property_tag # The shared schema is the canonical definition in core/ assert PropertyTag is property_tag.PropertyTag @@ -741,8 +741,8 @@ def test_shared_schema_prevents_collision(self): # core.property_tag module (not define their own) import inspect - import adcp.types.generated_poc.adagents as adagents_module - import adcp.types.generated_poc.core.publisher_property_selector as selector_module + import adcp.types.domains.adagents as adagents_module + import adcp.types.domains.core.publisher_property_selector as selector_module # Check that they import from property_tag, not define their own adagents_source = inspect.getsource(adagents_module) diff --git a/tests/test_error_code_conformance.py b/tests/test_error_code_conformance.py index f79deb43e..a9a5d81fa 100644 --- a/tests/test_error_code_conformance.py +++ b/tests/test_error_code_conformance.py @@ -4,7 +4,7 @@ sites and asserts every string-literal first-positional code is either: * in the canonical AdCP error-code enum (bundled at - :file:`src/adcp/types/generated_poc/enums/error_code.py`, generated + :file:`src/adcp/types/domains/enums/error_code.py`, generated from :file:`schemas/cache/enums/error-code.json`); * prefixed with ``X_`` per the AdCP vendor-extension convention; or * explicitly listed in :data:`KNOWN_NON_SPEC_CODES` below — a small, @@ -44,7 +44,7 @@ import pytest -from adcp.types.generated_poc.enums.error_code import ErrorCode +from adcp.types.domains.enums.error_code import ErrorCode # --------------------------------------------------------------------------- # Allowlist — codes used intentionally by the SDK that are not in the diff --git a/tests/test_error_info_extraction.py b/tests/test_error_info_extraction.py index 88e410455..684d8b381 100644 --- a/tests/test_error_info_extraction.py +++ b/tests/test_error_info_extraction.py @@ -63,7 +63,7 @@ class Duck: def test_from_pydantic_error_model(self) -> None: from adcp.types._generated import BuyerReason as WireBuyerReason from adcp.types._generated import Recovery - from adcp.types.generated_poc.core.error import Error as WireError + from adcp.types.domains.core.error import Error as WireError wire = WireError( code="BUDGET_TOO_LOW", @@ -153,7 +153,7 @@ def test_retry_after_boundaries_accepted(self) -> None: def test_retry_after_and_details_from_pydantic_model(self) -> None: from adcp.types._generated import Recovery - from adcp.types.generated_poc.core.error import Error as WireError + from adcp.types.domains.core.error import Error as WireError wire = WireError( code="RATE_LIMITED", @@ -187,7 +187,7 @@ def test_error_info_from_pydantic_models_end_to_end(self) -> None: # `extract_adcp_error_info` in isolation. from adcp.types._generated import BuyerReason as WireBuyerReason from adcp.types._generated import Recovery - from adcp.types.generated_poc.core.error import Error as WireError + from adcp.types.domains.core.error import Error as WireError wire = WireError( code="BUDGET_TOO_LOW", diff --git a/tests/test_export_surface_is_derived.py b/tests/test_export_surface_is_derived.py index d182880dc..a98951005 100644 --- a/tests/test_export_surface_is_derived.py +++ b/tests/test_export_surface_is_derived.py @@ -7,8 +7,8 @@ ``# type: ignore[assignment]``, which left mypy holding the pre-reassignment declaration while the runtime held its neighbour (#1141). The static half of that contract is ``tests/type_checks/authorized_agents_variants.py``; -* every public class in a non-bundled generated module is importable from the - public mirror of the schema that declares it (#911); +* every public class in a non-bundled generated module is importable at the + address of the schema that declares it, and is DEFINED there (#911); * ``adcp.types.error_details`` carries each ``error-details/*.json`` model together with the field types its annotations reference, so a seller constructs the payload with typed values (#1080). @@ -20,6 +20,8 @@ import importlib import inspect import pkgutil +import types +import typing from pathlib import Path import pytest @@ -85,21 +87,56 @@ def test_generated_module_rebinds_no_imported_name() -> None: # --------------------------------------------------------------------------- +def _is_defined_class(obj: object) -> bool: + """True for a real class, False for a union or a parameterized alias. + + ``isinstance(list[int], type)`` is True on 3.10+, and an ``Annotated`` + alias reports ``__module__ == "typing"``, so the origin check has to come + first or every alias reads as a class defined in the wrong place. + """ + if isinstance(obj, types.UnionType) or typing.get_origin(obj) is not None: + return False + return inspect.isclass(obj) + + def test_no_generated_type_is_reachable_under_zero_names() -> None: """A model an adopter cannot import is a model an adopter cannot construct. - Every ``(module, type)`` pair the tree declares is bound by the public - mirror of the module that declares it. Checked against the built package, - name by name, because a generated module binds several names to one class - and not every export is a class at all. + Every ``(module, type)`` pair the tree declares must import from + ``adcp.types.domains.``, and a real class must be DEFINED there. + The second clause is the one a re-export tree cannot satisfy, and it is + what the previous version of this file could not ask: it compared the + public mirror's exports to the private tree's exports, two reads of the + same declaration, so it held whatever the public layout was. + + Checked against the built package, name by name, because a generated + module binds several names to one class and not every export is a class + at all — a schema whose root composes other schemas generates an + ``Annotated[...]`` alias, for which being bound here is the whole claim. """ unreachable: list[str] = [] + misplaced: list[str] = [] + classes = 0 + sentinel = object() for type_name, modules in sorted(scan_declared_names().items()): for module_name in sorted(modules): - mirror = importlib.import_module(f"adcp.types.domains.{module_name}") - if type_name not in mirror.__all__ or not hasattr(mirror, type_name): + qualified = f"adcp.types.domains.{module_name}" + module = importlib.import_module(qualified) + bound = getattr(module, type_name, sentinel) + if bound is sentinel: unreachable.append(f"{module_name}.{type_name}") + elif _is_defined_class(bound): + classes += 1 + if bound.__module__ != qualified: + misplaced.append( + f"{qualified}.{type_name}: defined in {bound.__module__}, not here" + ) assert unreachable == [] + assert misplaced == [] + # The ``__module__`` clause carries this test, so a drop to zero graded + # classes would make it pass vacuously. The floor also replaces the volume + # check the deleted mirror test carried. + assert classes > 4000, f"only {classes} definitions graded — the tree shrank" def test_every_domain_root_binds_what_its_domain_declares_once() -> None: @@ -117,25 +154,13 @@ def test_every_domain_root_binds_what_its_domain_declares_once() -> None: for domain, rows in sorted(expected.items()): module = importlib.import_module(f"adcp.types.domains.{domain}") for type_name, module_name in sorted(rows.items()): - source = importlib.import_module(f"adcp.types.generated_poc.{module_name}") + source = importlib.import_module(f"adcp.types.domains.{module_name}") assert getattr(module, type_name) is getattr(source, type_name), f"{domain}.{type_name}" assert schema_domain(module_name) == domain # Every name a domain root binds keeps the name codegen gave it. assert all(name in rows or name in module.__all__ for name in rows) -def test_a_mirror_exports_its_schema_verbatim() -> None: - """A mirror renames nothing and adds nothing: it is its module's own surface.""" - checked = 0 - for module_name in sorted({m for mods in scan_declared_names().values() for m in mods}): - mirror = importlib.import_module(f"adcp.types.domains.{module_name}") - source = importlib.import_module(f"adcp.types.generated_poc.{module_name}") - for name in mirror.__all__: - assert getattr(mirror, name) is getattr(source, name), f"{module_name}.{name}" - checked += 1 - assert checked > 4000, f"only {checked} names checked — the mirror shrank" - - def test_a_domain_module_names_the_variant_the_flat_namespace_cannot() -> None: """The defect the domain modules answer, pinned as the behaviour it is. @@ -148,7 +173,7 @@ def test_a_domain_module_names_the_variant_the_flat_namespace_cannot() -> None: import adcp.types.domains.core as core_domain import adcp.types.domains.creative as creative_domain import adcp.types.domains.protocol as protocol_domain - from adcp.types.generated_poc.creative.list_creatives_response import ListCreativesResponse + from adcp.types.domains.creative.list_creatives_response import ListCreativesResponse assert ListCreativesResponse.model_fields["query_summary"].annotation is ( creative_domain.QuerySummary @@ -174,7 +199,7 @@ def test_a_domain_module_names_the_variant_the_flat_namespace_cannot() -> None: def test_a_name_its_own_domain_declares_twice_is_reached_through_its_schema() -> None: """``creative`` declares ``Creative`` four times, so the domain cannot bind it.""" import adcp.types.domains.creative as creative_domain - from adcp.types.generated_poc.creative import list_creatives_response as lcr + from adcp.types.domains.creative import list_creatives_response as lcr assert "Creative" not in creative_domain.__all__ declaring = { @@ -249,7 +274,7 @@ def test_generated_modules_define_and_build_no_class(module: object) -> None: def test_every_exported_class_is_the_class_its_module_defines() -> None: """Identity, not shape: the exported name IS the generated class object. - ``__module__`` must point into ``generated_poc`` and the class must be the + ``__module__`` must point into ``adcp.types.domains`` and the class must be the object that module holds under its own name. Both halves matter: ``create_model`` stamps the calling module onto the class it builds, so a copy would satisfy an identity check that trusted ``__module__``. @@ -260,7 +285,7 @@ def test_every_exported_class_is_the_class_its_module_defines() -> None: bound = getattr(module, name) if not inspect.isclass(bound): continue - assert bound.__module__.startswith("adcp.types.generated_poc."), ( + assert bound.__module__.startswith("adcp.types.domains."), ( f"{name} resolves to {bound.__module__}.{bound.__name__}, which codegen " "did not define — a derived export re-exports, it does not rebuild" ) @@ -283,7 +308,7 @@ def _error_details_models() -> dict[str, object]: adopter imports like any other. The ``isclass`` filter dropped two of them the moment codegen started emitting that shape. """ - package = importlib.import_module("adcp.types.generated_poc.error_details") + package = importlib.import_module("adcp.types.domains.error_details") package_dir = Path(package.__path__[0]) models: dict[str, object] = {} for info in pkgutil.iter_modules(package.__path__): diff --git a/tests/test_extra_policy.py b/tests/test_extra_policy.py index db13e829c..5e8465ffd 100644 --- a/tests/test_extra_policy.py +++ b/tests/test_extra_policy.py @@ -20,8 +20,8 @@ from adcp._version import _read_packaged_version from adcp.types.base import AdCPBaseModel -from adcp.types.generated_poc.core.media_buy_features import MediaBuyFeatures -from adcp.types.generated_poc.governance.check_governance_request import CheckGovernanceRequest +from adcp.types.domains.core.media_buy_features import MediaBuyFeatures +from adcp.types.domains.governance.check_governance_request import CheckGovernanceRequest from adcp.validation.version import resolve_bundle_key from scripts.post_generate_fixes import ( _ensure_configdict_import, @@ -33,7 +33,7 @@ _BUNDLE_KEY = resolve_bundle_key(_read_packaged_version()) SCHEMAS_DIR = Path(__file__).parent.parent / "schemas" / "cache" / _BUNDLE_KEY -GENERATED_DIR = Path(__file__).parent.parent / "src" / "adcp" / "types" / "generated_poc" +GENERATED_DIR = Path(__file__).parent.parent / "src" / "adcp" / "types" / "domains" def test_base_model_config_is_ignore() -> None: diff --git a/tests/test_format_reference_wire_url.py b/tests/test_format_reference_wire_url.py index 063fbfce5..c83e258e1 100644 --- a/tests/test_format_reference_wire_url.py +++ b/tests/test_format_reference_wire_url.py @@ -15,7 +15,7 @@ from adcp.canonical_formats.projection import migrated_format_option_id from adcp.types.base import WireUrl -from adcp.types.generated_poc.core.format_id import FormatReferenceStructuredObject +from adcp.types.domains.core.format_id import FormatReferenceStructuredObject from adcp.types.legacy import LegacyFormatId _WIRE = {"agent_url": "https://creative.adcontextprotocol.org", "id": "display_300x250_image"} diff --git a/tests/test_forward_compat_assets.py b/tests/test_forward_compat_assets.py index 5b08a5a13..320e7e3a0 100644 --- a/tests/test_forward_compat_assets.py +++ b/tests/test_forward_compat_assets.py @@ -18,7 +18,7 @@ UnknownFormatAsset, UnknownGroupAsset, ) -from adcp.types.generated_poc.core.format import Assets94, Format +from adcp.types.domains.core.format import Assets94, Format def _make_format_id(name: str) -> dict: diff --git a/tests/test_forward_compat_format_kind.py b/tests/test_forward_compat_format_kind.py index 108b982e1..328b7e637 100644 --- a/tests/test_forward_compat_format_kind.py +++ b/tests/test_forward_compat_format_kind.py @@ -1,4 +1,28 @@ -"""Closed creative format kinds and tolerant delivery readback (issues #1241/#1140).""" +"""An open ``format_kind`` vocabulary and tolerant readback (#1241/#1140). + +``core/canonical-format-kind.json`` declares a closed 16-member ``enum`` and, +in the same file, requires a consumer to retain an unknown value and not fail +the payload — "the producer-side enum stays closed; the consumer-side enum +stays open for forward compatibility." The schema knows the rule is +DIRECTIONAL and then encodes it as one closed enum, which cannot carry that. + +So this SDK does not reproduce the enum: ``format_kind`` is ``str`` at every +reference (``OPEN_VOCABULARY_SCHEMAS`` in ``scripts/generate_types.py``) and +**no model refuses a value, in either direction**. A pinned SDK cannot tell a +kind a seller invented from a kind defined after its pin, so refusing the +second to prevent the first would make this library's version a ceiling on +what the protocol permits. Checking the vocabulary is the caller's, through +``adcp.types.is_canonical_format_kind``. Upstream ask: +adcontextprotocol/adcp#7929. + +What this module grades is therefore one thing rather than a pair: a value +this SDK does not recognise survives construction, validation, serialization +and re-parsing, on every model that carries the field, and nothing in the +published JSON schema says otherwise. The #1241 obligation this module was +written for — that delivery tolerance must not weaken creative input +validation — had its premise removed with the strict side; what is left of it +is graded in ``tests/test_delivery_manifest_readback.py``. +""" from __future__ import annotations @@ -19,15 +43,16 @@ Format, GetCreativeDeliveryResponse, SyncCreativesRequest, + is_canonical_format_kind, ) from adcp.types.aliases import DeliveryCreative as AliasDeliveryCreative from adcp.types.creative import Creative as PartialCreative from adcp.types.creative import CreativeAsset as PartialCreativeAsset from adcp.types.creative import CreativeManifest as PartialCreativeManifest -from adcp.types.generated_poc.core.creative_manifest import ( +from adcp.types.domains.core.creative_manifest import ( CreativeManifest as GeneratedCreativeManifest, ) -from adcp.types.generated_poc.creative.get_creative_delivery_response import ( +from adcp.types.domains.creative.get_creative_delivery_response import ( GetCreativeDeliveryResponse as GeneratedGetCreativeDeliveryResponse, ) @@ -67,47 +92,75 @@ def _creative_manifest(format_kind: str) -> CreativeManifest: return CreativeManifest(format_kind=format_kind, assets={}) -@pytest.mark.parametrize( - "factory", - [_creative_asset, _creative, _creative_manifest], -) -@pytest.mark.parametrize("value", [FUTURE_FORMAT_KIND, "totally_bogus", "IMAGE", ""]) -def test_unknown_format_kind_is_rejected(factory, value) -> None: - with pytest.raises(ValidationError) as error: - factory(value) - - assert [(detail["loc"], detail["type"]) for detail in error.value.errors()] == [ - (("format_kind",), "enum") - ] +#: Every model that carries a ``format_kind``, in both directions. There is no +#: request/response split: the SDK refuses nothing, so the same expectations +#: hold on a model a buyer sends and on a row a seller returns. +FACTORIES = [_creative_asset, _creative, _creative_manifest, _delivery_creative] -@pytest.mark.parametrize("factory", [_creative_asset, _creative, _creative_manifest]) +@pytest.mark.parametrize("factory", FACTORIES) +@pytest.mark.parametrize("value", [FUTURE_FORMAT_KIND, "totally_bogus", "IMAGE", ""]) +def test_an_unrecognised_format_kind_is_retained_everywhere(factory, value) -> None: + """The SDK does not discriminate, in either direction. + + ``""`` is in the list deliberately, and it is the sharpest case: the closed + enum refused it, and ``core/canonical-format-kind.json`` is + ``{"type": "string", "enum": [...]}`` with no ``minLength`` and no + ``pattern``, so an open ``str`` has nothing to refuse it with. A seller + sending an empty kind is a seller bug, and saying so is the CALLER's job + through ``is_canonical_format_kind`` — this library carries the value. + + ``core/canonical-format-kind.json`` requires a consumer to retain an + unknown value, and a pinned SDK cannot tell a kind a seller invented from a + kind defined after its pin — so refusing on the way out would make this + library's version a ceiling on what the protocol permits. Retention is + asserted on the attribute AND on the wire, because dropping it at + serialization would satisfy a weaker check. + + Whether to accept a kind is the caller's decision, through + ``adcp.types.is_canonical_format_kind``, graded by + ``tests/test_open_format_kind_vocabulary.py``. + """ + model = factory(value) + assert model.format_kind == value + assert model.model_dump(mode="json")["format_kind"] == value + assert json.loads(model.model_dump_json())["format_kind"] == value + assert not is_canonical_format_kind(value) + + +@pytest.mark.parametrize("factory", FACTORIES) @pytest.mark.parametrize("json_input", [False, True], ids=["python", "json"]) -def test_raw_creative_validation_rejects_unknown_format_kind(factory, json_input) -> None: +def test_raw_validation_retains_an_unrecognised_format_kind(factory, json_input) -> None: + """Round-tripping a document through the model keeps the value it carried.""" model = factory("image") payload = model.model_dump(mode="json", exclude_unset=True) payload["format_kind"] = FUTURE_FORMAT_KIND - with pytest.raises(ValidationError) as error: - if json_input: - type(model).model_validate_json(json.dumps(payload)) - else: - type(model).model_validate(payload) + reparsed = ( + type(model).model_validate_json(json.dumps(payload)) + if json_input + else type(model).model_validate(payload) + ) + assert reparsed.format_kind == FUTURE_FORMAT_KIND - assert error.value.errors()[0]["loc"] == ("format_kind",) +@pytest.mark.parametrize("factory", FACTORIES) +def test_no_model_publishes_a_vocabulary_constraint(factory) -> None: + """The published JSON schema agrees with the runtime: ``type: string``. -@pytest.mark.parametrize("factory", [_creative_asset, _creative, _creative_manifest]) -def test_creative_validation_schema_rejects_unknown_format_kind(factory) -> None: + A model whose schema said ``enum`` while its validator accepted anything + would be the same disagreement in the other direction. Graded through the + bundled validator rather than by reading the node, so it is the behaviour + being checked. + """ model = factory("image") validator = Draft202012Validator(type(model).model_json_schema()) payload = model.model_dump(mode="json", exclude_unset=True) validator.validate(payload) payload["format_kind"] = FUTURE_FORMAT_KIND - errors = list(validator.iter_errors(payload)) - assert errors - assert all(list(error.path) == ["format_kind"] for error in errors) + assert list(validator.iter_errors(payload)) == [] + assert type(model).model_validate(payload).format_kind == FUTURE_FORMAT_KIND @pytest.mark.parametrize( @@ -139,16 +192,42 @@ def test_creative_format_kind_remains_required_and_non_nullable(factory) -> None def test_manifest_format_kind_keeps_its_optional_default() -> None: - omitted = CreativeManifest(assets={}) + """``format_kind`` is a declared-optional field, and omitting it entirely is refused. + + This used to construct ``CreativeManifest(assets={})`` to show the field defaults + to ``None``. That document is schema-invalid: core/creative-manifest.json's root + ``oneOf`` takes one of ``format_id`` / ``format_kind``, and #1368's + enforce_root_required_groups now holds it at runtime. The canonical manifest cannot + use the ``format_id`` arm either -- it strips legacy creative identity -- so on this + surface ``format_kind`` is the only satisfiable arm. + + The obligation the old construction stood for is that codegen must not mark the + field required, which is read off the declaration directly rather than inferred + from a construction that the document rule now forbids. + """ + field = CreativeManifest.model_fields["format_kind"] + assert not field.is_required() + assert field.default is None + + with pytest.raises(ValidationError): + CreativeManifest(assets={}) + + # An explicit ``None`` satisfies the document rule -- the group is keyed on the + # field being present, not on it being non-null -- and round-trips as null. explicit = CreativeManifest(assets={}, format_kind=None) - assert omitted.format_kind is None assert explicit.format_kind is None - assert "format_kind" not in omitted.model_dump(exclude_unset=True) assert explicit.model_dump(exclude_unset=True, exclude_none=False)["format_kind"] is None @pytest.mark.parametrize("json_input", [False, True], ids=["python", "json"]) -def test_sync_request_rejects_unknown_creative_format_kind(json_input) -> None: +def test_sync_request_carries_an_unrecognised_creative_format_kind(json_input) -> None: + """A whole request nests the value unchanged, not only a bare asset. + + This used to assert a refusal. A seller on this SDK may legitimately need + to send a kind defined after its pin, and the library has no way to tell + that apart from an invented one, so it carries the value and the seller + decides — see ``adcp.types.is_canonical_format_kind``. + """ payload = { "account": {"account_id": "account-1"}, "idempotency_key": "creative-sync-idempotency-1", @@ -157,26 +236,30 @@ def test_sync_request_rejects_unknown_creative_format_kind(json_input) -> None: SyncCreativesRequest.model_validate(payload) payload["creatives"][0]["format_kind"] = FUTURE_FORMAT_KIND - with pytest.raises(ValidationError) as error: - if json_input: - SyncCreativesRequest.model_validate_json(json.dumps(payload)) - else: - SyncCreativesRequest.model_validate(payload) - assert error.value.errors()[0]["loc"] == ("creatives", 0, "format_kind") + request = ( + SyncCreativesRequest.model_validate_json(json.dumps(payload)) + if json_input + else SyncCreativesRequest.model_validate(payload) + ) + assert request.creatives[0].format_kind == FUTURE_FORMAT_KIND + assert json.loads(request.model_dump_json())["creatives"][0]["format_kind"] == ( + FUTURE_FORMAT_KIND + ) -def test_public_variant_rejects_unknown_manifest_format_kind() -> None: +def test_public_variant_carries_an_unrecognised_manifest_format_kind() -> None: + """Nested one level deeper, the value still survives.""" variant = {"variant_id": "variant-1", "manifest": {"assets": {}, "format_kind": "image"}} CreativeVariant.model_validate(variant) variant["manifest"]["format_kind"] = FUTURE_FORMAT_KIND - with pytest.raises(ValidationError) as error: - CreativeVariant.model_validate(variant) - assert error.value.errors()[0]["loc"][-2:] == ("manifest", "format_kind") + parsed = CreativeVariant.model_validate(variant) + assert parsed.manifest is not None + assert parsed.manifest.format_kind == FUTURE_FORMAT_KIND @pytest.mark.parametrize( - ("response_type", "strict_manifest_type"), + ("response_type", "input_manifest_type"), [ (GetCreativeDeliveryResponse, CreativeManifest), (GeneratedGetCreativeDeliveryResponse, GeneratedCreativeManifest), @@ -184,7 +267,7 @@ def test_public_variant_rejects_unknown_manifest_format_kind() -> None: ids=["canonical", "generated"], ) def test_unknown_nested_manifest_kind_round_trips_in_delivery_readback( - response_type, strict_manifest_type + response_type, input_manifest_type ) -> None: payload = { "currency": "USD", @@ -208,7 +291,7 @@ def test_unknown_nested_manifest_kind_round_trips_in_delivery_readback( delivery = response_type.model_validate(payload) manifest = delivery.creatives[0].variants[0].manifest assert manifest is not None - assert type(manifest) is not strict_manifest_type + assert type(manifest) is not input_manifest_type assert type(delivery.creatives[0].variants[0]) is not CreativeVariant assert manifest.format_kind == FUTURE_FORMAT_KIND @@ -217,9 +300,14 @@ def test_unknown_nested_manifest_kind_round_trips_in_delivery_readback( delivery.model_dump(mode="json") ) - with pytest.raises(ValidationError) as error: - strict_manifest_type.model_validate(payload["creatives"][0]["variants"][0]["manifest"]) - assert error.value.errors()[0]["loc"] == ("format_kind",) + # Both the canonical and the generated manifest accept the nested document + # and retain the kind. One class serves both directions — the same + # ``core/creative-manifest.json`` is referenced by + # ``sync-creatives-request.json`` and by + # ``get-creative-delivery-response.json`` — and there is no second, stricter + # class for it to be measured against any more. + nested = payload["creatives"][0]["variants"][0]["manifest"] + assert input_manifest_type.model_validate(nested).format_kind == FUTURE_FORMAT_KIND @pytest.mark.parametrize( @@ -227,12 +315,23 @@ def test_unknown_nested_manifest_kind_round_trips_in_delivery_readback( [_creative_asset, _creative, _delivery_creative, _creative_manifest], ) @pytest.mark.parametrize("kind", list(CanonicalFormatKind)) -def test_known_format_kind_still_coerces_to_enum(factory, kind) -> None: +def test_a_canonical_format_kind_round_trips_as_the_string_it_is(factory, kind) -> None: + """Every one of the sixteen, on every model, python and JSON. + + ``format_kind`` is ``str`` by decision (see the module docstring), so a + canonical value is carried as itself rather than coerced to an enum member. + The value is asserted against ``CanonicalFormatKind`` rather than a literal + so the vocabulary stays the subject, and the ``str``-ness is asserted too — + a reintroduced enum would satisfy ``==`` and fail ``type() is str``. + """ model = factory(kind.value) - assert model.format_kind is kind + assert model.format_kind == kind + assert type(model.format_kind) is str assert model.model_dump(mode="json")["format_kind"] == kind.value - assert type(model).model_validate_json(model.model_dump_json()).format_kind is kind + reparsed = type(model).model_validate_json(model.model_dump_json()) + assert reparsed.format_kind == kind + assert type(reparsed.format_kind) is str def test_delivery_creative_alias_is_open_and_keeps_its_identity() -> None: @@ -271,7 +370,7 @@ def test_custom_format_accepts_shape_and_schema() -> None: format_schema=_format_schema(), ) - assert model.format_kind is CanonicalFormatKind.custom + assert model.format_kind == CanonicalFormatKind.custom assert model.format_shape == "new_shape" assert model.format_schema is not None diff --git a/tests/test_generate_types_safety.py b/tests/test_generate_types_safety.py index f1d9d9131..63d75ec46 100644 --- a/tests/test_generate_types_safety.py +++ b/tests/test_generate_types_safety.py @@ -65,7 +65,7 @@ def _configure_fake_pipeline( fail_ergonomic: bool, ) -> tuple[Path, Path, Path, Path]: repository = tmp_path / "repository" - output = repository / "src" / "adcp" / "types" / "generated_poc" + output = repository / "src" / "adcp" / "types" / "domains" consolidated = output.parent / "_generated.py" ergonomic = output.parent / "_ergonomic.py" deltas = repository / "SCHEMA_DELTAS.md" @@ -97,7 +97,10 @@ def fake_generate(_schemas: Path, destination: Path) -> bool: monkeypatch.setattr(generate_types, "prune_unused_bundled_modules", lambda *_args: None) monkeypatch.setattr(generate_types, "restore_unchanged_files", lambda *_args: None) - def fake_package(staging_root: Path, _generated: Path) -> Path: + def fake_staged_source(staging_root: Path) -> Path: + # Mirrors the real helper: the package copy, with the committed + # generated tree dropped so generation creates it. The tree lives + # INSIDE this copy now, which is what makes it one artifact. source = staging_root / "source" types = source / "adcp" / "types" types.mkdir(parents=True) @@ -105,7 +108,7 @@ def fake_package(staging_root: Path, _generated: Path) -> Path: (types / "_ergonomic.py").write_text("stale ergonomic module\n") return source - monkeypatch.setattr(generate_types, "_copy_package_for_introspection", fake_package) + monkeypatch.setattr(generate_types, "_staged_source_tree", fake_staged_source) def fake_subprocess(args: list[str], **_kwargs: object) -> subprocess.CompletedProcess[str]: if "consolidate_exports.py" in args[1]: diff --git a/tests/test_generated_enum_strenum.py b/tests/test_generated_enum_strenum.py index 9a77ec3c4..0457b8b47 100644 --- a/tests/test_generated_enum_strenum.py +++ b/tests/test_generated_enum_strenum.py @@ -3,10 +3,10 @@ from pathlib import Path from adcp.types import CreativeAction, GetProductsRequest, MediaBuyStatus -from adcp.types.generated_poc.media_buy.get_products_request import BuyingMode +from adcp.types.domains.media_buy.get_products_request import BuyingMode from tests.conftest import validate_union -GENERATED_TYPES_DIR = Path(__file__).parents[1] / "src" / "adcp" / "types" / "generated_poc" +GENERATED_TYPES_DIR = Path(__file__).parents[1] / "src" / "adcp" / "types" / "domains" def test_generated_enums_are_string_comparable() -> None: diff --git a/tests/test_generated_hierarchy.py b/tests/test_generated_hierarchy.py index 819a726d7..eb47cecc3 100644 --- a/tests/test_generated_hierarchy.py +++ b/tests/test_generated_hierarchy.py @@ -41,7 +41,7 @@ from scripts.post_generate_fixes import _resolve_schema_ref, _schema_title_to_class_name _REPO_ROOT = Path(__file__).resolve().parent.parent -_GENERATED_DIR = _REPO_ROOT / "src" / "adcp" / "types" / "generated_poc" +_GENERATED_DIR = _REPO_ROOT / "src" / "adcp" / "types" / "domains" _BUNDLE_KEY = resolve_bundle_key((_REPO_ROOT / "src" / "adcp" / "ADCP_VERSION").read_text().strip()) _SCHEMA_DIR = _REPO_ROOT / "schemas" / "cache" / _BUNDLE_KEY @@ -143,7 +143,7 @@ def _module_path(rel: Path) -> Path: def _module_name(rel: Path) -> str: relative = _module_path(rel).relative_to(_GENERATED_DIR).with_suffix("") - return "adcp.types.generated_poc." + ".".join(relative.parts) + return "adcp.types.domains." + ".".join(relative.parts) @functools.cache @@ -409,9 +409,9 @@ def test_envelope_composition_is_rendered_as_a_base_class() -> None: if composition.target not in counted: continue for cls in composing: - assert issubclass(cls, parent), ( - f"{composition.schema}::{cls.__name__} does not inherit {parent.__name__}" - ) + assert issubclass( + cls, parent + ), f"{composition.schema}::{cls.__name__} does not inherit {parent.__name__}" counted[composition.target] += 1 short = { @@ -429,7 +429,7 @@ def test_envelope_composition_is_rendered_as_a_base_class() -> None: def test_public_surface_keeps_the_composed_ancestry() -> None: """A name exported from ``adcp.types`` keeps the ancestry its schema declares. - Inheritance inside ``generated_poc`` is worth nothing to a consumer if the + Inheritance inside the generated tree is worth nothing to a consumer if the layer that re-exports the name drops the base. ``canonical_creative`` rebuilds some boundary models with ``create_model``, which is exactly where a base can go missing without any schema changing. @@ -474,7 +474,7 @@ def test_response_arm_aliases_keep_the_protocol_envelope() -> None: for every arm of every task response, so naming it once is auditable. """ from adcp.types import aliases - from adcp.types.generated_poc.core.protocol_envelope import ProtocolEnvelope + from adcp.types.domains.core.protocol_envelope import ProtocolEnvelope arms = sorted( name for name in aliases.__all__ if name.endswith(("SuccessResponse", "ErrorResponse")) diff --git a/tests/test_generated_poc_alias.py b/tests/test_generated_poc_alias.py new file mode 100644 index 000000000..6a18e7274 --- /dev/null +++ b/tests/test_generated_poc_alias.py @@ -0,0 +1,654 @@ +"""The pre-9.0 ``adcp.types.generated_poc`` path resolves, as the same objects. + +#1360 measured 110 deep ``generated_poc`` imports in ONE production seller, so +9.0 keeps the old path working and warns rather than breaking all of them at +once. The danger in doing that is the one the review named: redirecting a +package's ``__path__`` makes the import machinery LOAD each module a second +time under the old name, so there are two module objects and two class objects +per class, and an ``isinstance`` against the wrong one fails for no visible +reason. ``tests/test_export_surface_is_derived.py`` exists to refuse that, by +asserting every generated class is DEFINED at the address it is imported from. + +``adcp.types._generated_poc_alias`` is not that. Its loader executes nothing: +it imports the canonical module and hands that object back, so ``sys.modules`` +holds ONE object under both keys. This module grades that claim, and the four +others the alias has to keep: + +1. the leaf class is the SAME object, ``is`` not ``==``; +2. ``sys.modules`` holds one module object under both keys; +3. a ``DeprecationWarning`` naming the new path, once per module; +4. the shared object carries no trace of having been reached through the old + name — which is the property ``test_export_surface_is_derived`` rests on; +5. the alias does not outlive the major that removes it. + +The module list is derived — the root, every entry in +``adcp.types.domains.DOMAINS``, and the first few leaf modules each domain +package actually contains — rather than hand-listed, because a hand-listed +sample stops covering the tree the moment the tree changes. + +**A root discovery schema is the one name for which (2) cannot hold, and #1402 +is what happens when nothing says so.** ``generated_poc.brand`` resolved to +``domains.brand`` and ``from ... import Brand`` raised, because ``brand.json`` +now generates to ``domains/brand_discovery.py`` and ``domains/brand/__init__.py`` +is the domain aggregator. The suite above could not see it: it asserted over the +names the CANONICAL module binds, never over the names the PRE-9.0 module +exported. The split family below asserts the second direction, over every public +name of both halves, and takes its split set from the generator's own +``ROOT_DISCOVERY_SCHEMAS`` so the next split is graded without anyone extending +a list here. +""" + +from __future__ import annotations + +import importlib +import importlib.util +import pkgutil +import re +import subprocess +import sys +import warnings +from pathlib import Path +from types import ModuleType + +import pytest + +import adcp.types # noqa: F401 — importing it is what installs the finder +import adcp.types.domains as domains +from adcp.types._generated_poc_alias import ( + CANONICAL_ROOT, + DEPRECATED_ROOT, + REMOVED_IN_MAJOR, + ROOT_DISCOVERY_SUFFIX, + _AliasFinder, + _AliasLoader, + _SplitAliasModule, + canonical_name, + discovery_half, + install, +) +from scripts.generate_types import ROOT_DISCOVERY_SCHEMAS + +_REPO_ROOT = Path(__file__).resolve().parents[1] + +#: Leaf modules sampled per domain. Two is enough to grade the mechanism — +#: which is per-name, not per-module — without importing 1088 modules. +_LEAVES_PER_DOMAIN = 2 + + +def _canonical_module_names() -> list[str]: + """The canonical modules this suite grades, derived from the tree.""" + + names = [CANONICAL_ROOT] + for domain in domains.DOMAINS: + package_name = f"{CANONICAL_ROOT}.{domain}" + names.append(package_name) + package = importlib.import_module(package_name) + # A domain whose schemas are few enough generates a module, not a + # package, and has no submodules to sample. + search_path = getattr(package, "__path__", None) + if search_path is None: + continue + leaves = sorted(info.name for info in pkgutil.iter_modules(search_path)) + names.extend(f"{package_name}.{leaf}" for leaf in leaves[:_LEAVES_PER_DOMAIN]) + return names + + +CANONICAL_MODULES = _canonical_module_names() +DEPRECATED_MODULES = [DEPRECATED_ROOT + name[len(CANONICAL_ROOT) :] for name in CANONICAL_MODULES] +PAIRS = list(zip(DEPRECATED_MODULES, CANONICAL_MODULES, strict=True)) +PAIR_IDS = [deprecated[len(DEPRECATED_ROOT) + 1 :] or "" for deprecated, _ in PAIRS] + +#: The schemas whose pre-9.0 module and 9.0 module are not the same address, +#: read from the generator's own declaration rather than named here. Each entry +#: is ``(deprecated module, discovery half, aggregator half)``: ``brand.json`` +#: generated into ``generated_poc/brand/__init__.py`` and now generates into +#: ``domains/brand_discovery.py``, leaving ``domains/brand/__init__.py`` to the +#: domain aggregator, so one deprecated name has two canonical halves. +SPLITS = [ + ( + f"{DEPRECATED_ROOT}.{schema.stem}", + f"{CANONICAL_ROOT}.{output.stem}", + f"{CANONICAL_ROOT}.{schema.stem}", + ) + for schema, output in sorted(ROOT_DISCOVERY_SCHEMAS.items()) +] +SPLIT_IDS = [schema.stem for schema in sorted(ROOT_DISCOVERY_SCHEMAS)] +SPLIT_DEPRECATED = frozenset(deprecated for deprecated, _, _ in SPLITS) + +#: The pairs for which one deprecated name has exactly one canonical module. +UNSPLIT_PAIRS = [pair for pair in PAIRS if pair[0] not in SPLIT_DEPRECATED] +UNSPLIT_PAIR_IDS = [ + deprecated[len(DEPRECATED_ROOT) + 1 :] or "" for deprecated, _ in UNSPLIT_PAIRS +] + + +def _import_deprecated(name: str) -> ModuleType: + """Import a deprecated path with the warning suppressed. + + Suppressed because every import here is deliberate; the warning itself is + graded by ``test_the_warning_fires_once_per_module_and_names_the_new_path`` + and, on a real import, by the subprocess test below. + """ + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + return importlib.import_module(name) + + +def test_the_graded_module_list_is_not_empty() -> None: + """A tree reorganization must not empty this suite silently.""" + assert len(domains.DOMAINS) == 26, domains.DOMAINS + assert len(PAIRS) > 60, len(PAIRS) + assert CANONICAL_MODULES[0] == CANONICAL_ROOT + + +# --------------------------------------------------------------------------- +# Properties 1 and 2 — one module object, one class object +# --------------------------------------------------------------------------- + + +@pytest.mark.parametrize(("deprecated", "canonical"), UNSPLIT_PAIRS, ids=UNSPLIT_PAIR_IDS) +def test_sys_modules_holds_one_object_under_both_keys(deprecated: str, canonical: str) -> None: + """The property a ``__path__`` redirect cannot keep. + + Stated over every name that did not split. A split name has two canonical + halves and so cannot be one object with either; what it keeps instead — + the shim defines no class, so every class reached through it is still the + canonical class, which is the property this one exists to protect — is + graded by ``test_a_split_name_defines_nothing_and_serves_canonical_classes``. + """ + old = _import_deprecated(deprecated) + new = importlib.import_module(canonical) + + assert old is new + assert sys.modules[deprecated] is sys.modules[canonical] + assert len({id(sys.modules[deprecated]), id(sys.modules[canonical])}) == 1 + + +@pytest.mark.parametrize(("deprecated", "canonical"), UNSPLIT_PAIRS, ids=UNSPLIT_PAIR_IDS) +def test_every_class_reached_through_the_old_path_is_the_canonical_class( + deprecated: str, canonical: str +) -> None: + """Stated over every public class the module binds, not over a sample. + + ``is`` rather than ``==``: two pydantic models built from one schema compare + unequal as classes but would both satisfy a structural check, and it is + object identity that makes ``isinstance`` work. + + A split name is graded by the split tests instead. Not because the identity + is weaker there — it is not, the shim defines no class — but because the + canonical module this compares against is only HALF of what the pre-9.0 + module bound, and the two halves share 12 names (``Logo``, ``Colors``, + ``Fonts``, ...) that the pre-9.0 import resolved to the discovery class. + """ + old = _import_deprecated(deprecated) + new = importlib.import_module(canonical) + + public = [name for name in dir(new) if not name.startswith("_")] + assert public, f"{canonical} binds nothing public" + for name in public: + assert getattr(old, name) is getattr(new, name), f"{deprecated}.{name}" + + +def test_the_identity_sweep_grades_thousands_of_classes() -> None: + """The floor the per-module test cannot carry. + + A module that binds only aliases still passes the sweep above, and a tree + reorganization could leave every graded module in that state. This counts + the classes the sweep actually compared, so the suite cannot go quiet. + """ + modules = [_import_deprecated(name) for name in DEPRECATED_MODULES] + + classes = sum( + 1 + for module in modules + for name in dir(module) + if not name.startswith("_") and isinstance(getattr(module, name), type) + ) + assert classes > 1000, f"only {classes} classes graded — the sample collapsed" + + +def test_every_spelling_an_adopter_may_have_written_resolves() -> None: + """Each of these reaches the import system by a different route. + + A dotted ``from ... import `` resolves the leaf through + ``_find_and_load``; ``from import `` goes through + ``_handle_fromlist``; and reaching ``adcp.types.generated_poc`` as an + attribute goes through ``adcp.types.__getattr__``'s submodule passthrough, + which probes with ``find_spec`` before importing. The finder has to answer + for all three. + + ``importlib.reload`` of a deprecated name is graded in the subprocess test + below, not here: reloading a generated module rebuilds its classes, so the + domain root and ``_generated`` keep the pre-reload objects and + ``test_export_surface_is_derived`` fails for the rest of the session. It + caught exactly that when the assertion lived here. + """ + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + from adcp.types.generated_poc import core as old_core + from adcp.types.generated_poc.core import format_id as old_format_id + from adcp.types.generated_poc.media_buy.package_request import ( + PackageRequest as OldPackageRequest, + ) + + from adcp.types import generated_poc as old_root + + import adcp.types.domains as new_root + from adcp.types.domains import core as new_core + from adcp.types.domains.core import format_id as new_format_id + from adcp.types.domains.media_buy.package_request import PackageRequest + + assert old_root is new_root + assert old_core is new_core + assert old_format_id is new_format_id + assert OldPackageRequest is PackageRequest + assert adcp.types.generated_poc.core.format_id is new_format_id + + +# --------------------------------------------------------------------------- +# A root discovery schema — the one case a prefix rename gets wrong (#1402) +# --------------------------------------------------------------------------- + + +def test_the_split_set_is_read_from_the_generator_not_named_here() -> None: + """A split this suite does not know about is a hole it cannot see. + + ``ROOT_DISCOVERY_SCHEMAS`` is the generator's own declaration of which + schema's module is not at its own stem, so a schema added there is graded + by the tests below without anyone remembering to extend a list. This + asserts the derivation is wired and non-empty — a renamed or emptied + declaration would otherwise leave every test below vacuously green. + """ + assert SPLITS, "ROOT_DISCOVERY_SCHEMAS is empty — the split tests grade nothing" + assert ( + f"{DEPRECATED_ROOT}.brand", + f"{CANONICAL_ROOT}.brand_discovery", + f"{CANONICAL_ROOT}.brand", + ) in SPLITS + + +@pytest.mark.parametrize(("deprecated", "discovery", "aggregator"), SPLITS, ids=SPLIT_IDS) +def test_a_root_discovery_module_is_named_the_way_the_alias_derives_it( + deprecated: str, discovery: str, aggregator: str +) -> None: + """The alias derives the second half by suffix; the generator must spell it so. + + ``discovery_half`` appends ``ROOT_DISCOVERY_SUFFIX`` to the domain package's + name rather than carrying a second copy of ``ROOT_DISCOVERY_SCHEMAS``. That + is a derivation, not a guess, only while the generator keeps naming these + modules ``_discovery`` — so this is the assertion that a differently + named entry fails the build instead of silently reopening #1402. + """ + assert discovery == aggregator + ROOT_DISCOVERY_SUFFIX + assert discovery_half(aggregator) == discovery + # And the derivation fires for nothing else: a leaf module, and a domain + # with no discovery sibling, are ordinary prefix renames. + assert discovery_half(f"{CANONICAL_ROOT}.core.format_id") is None + assert discovery_half(f"{CANONICAL_ROOT}.media_buy") is None + + +@pytest.mark.parametrize(("deprecated", "discovery", "aggregator"), SPLITS, ids=SPLIT_IDS) +def test_every_name_a_split_pre_9_0_module_exported_resolves_through_the_alias( + deprecated: str, discovery: str, aggregator: str +) -> None: + """#1402: the module resolved and its contents did not. + + ``generated_poc/brand/__init__.py`` WAS ``brand.json`` — 140 classes, among + them ``Brand``, ``BrandDiscovery3`` and ``LocalizedName``. Those classes are + now in ``domains/brand_discovery.py``, and a prefix rename sent the + deprecated name to ``domains/brand``, where they are not. An adopter got + ``ImportError: cannot import name 'Brand' from 'adcp.types.domains.brand'`` + — an error naming a module they never wrote, which is worse than no alias + at all. + + Stated over every public name of the discovery half, not a sample, and over + the aggregator half too: a deprecated name has to serve everything both of + its canonical halves bind. + """ + old = _import_deprecated(deprecated) + discovery_module = importlib.import_module(discovery) + aggregator_module = importlib.import_module(aggregator) + + graded = 0 + for half in (discovery_module, aggregator_module): + public = [name for name in dir(half) if not name.startswith("_")] + assert public, f"{half.__name__} binds nothing public" + for name in public: + expected = getattr(half, name) + if half is aggregator_module and hasattr(discovery_module, name): + # A name both halves bind resolved to the discovery class + # pre-9.0, because the pre-9.0 module was the discovery schema + # and nothing else. That ordering is the fidelity claim. + expected = getattr(discovery_module, name) + assert getattr(old, name) is expected, f"{deprecated}.{name}" + graded += 1 + assert graded > 250, f"only {graded} names graded — the sweep collapsed" + + +@pytest.mark.parametrize(("deprecated", "discovery", "aggregator"), SPLITS, ids=SPLIT_IDS) +def test_a_split_name_defines_nothing_and_serves_canonical_classes( + deprecated: str, discovery: str, aggregator: str +) -> None: + """The property the one-module-object rule was protecting, kept without it. + + Two canonical modules cannot be one object, so a split name gets its own. + What must not follow is a second LOAD of either half: that is what makes two + class objects per class and an ``isinstance`` fail for no visible reason. + The shim executes nothing and binds no class — every class it hands back + reports a canonical ``__module__``. + """ + old = _import_deprecated(deprecated) + + assert isinstance(old, _SplitAliasModule) + assert sys.modules[deprecated] is old + assert old is not importlib.import_module(discovery) + assert old is not importlib.import_module(aggregator) + + discovery_module = importlib.import_module(discovery) + classes = [ + getattr(old, name) + for name in dir(discovery_module) + if not name.startswith("_") and isinstance(getattr(old, name), type) + ] + assert len(classes) > 150, f"only {len(classes)} classes reached — the sweep collapsed" + # Every class the discovery half DEFINES reports that half, never the + # deprecated name. The handful it merely imports (``AdCPBaseModel``, + # ``Annotated``, the pydantic bases) report where they are defined, which is + # the same answer: a second load under the deprecated name is what would + # show up here, and it would show up on all of them. + defined_here = [cls for cls in classes if cls.__module__ == discovery] + assert len(defined_here) > 150, f"only {len(defined_here)} classes defined in {discovery}" + for cls in classes: + assert DEPRECATED_ROOT not in cls.__module__, cls + + +@pytest.mark.parametrize(("deprecated", "discovery", "aggregator"), SPLITS, ids=SPLIT_IDS) +def test_a_split_name_warns_about_both_halves_not_the_prefix_rename( + deprecated: str, discovery: str, aggregator: str +) -> None: + """The advice an adopter reads has to be the advice that works for them. + + "import adcp.types.domains.brand instead" is the prefix rename, and for this + one name it sends the reader to a module that does not bind what they + imported — the same wrong answer #1402 was about, delivered as guidance + rather than as an ``ImportError``. Graded on a fresh finder, because the + installed one has already warned for this name. + """ + finder = _AliasFinder() + + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter("always") + assert finder.find_spec(deprecated) is not None + + assert len(caught) == 1, [str(entry.message) for entry in caught] + message = str(caught[0].message) + assert discovery in message, message + assert aggregator in message, message + assert "split in two" in message, message + assert "prefix rename" in message and "not a prefix rename" in message, message + assert f"v{REMOVED_IN_MAJOR}" in message, message + + +@pytest.mark.parametrize(("deprecated", "discovery", "aggregator"), SPLITS, ids=SPLIT_IDS) +def test_a_split_deprecated_package_still_resolves_its_leaf_modules( + deprecated: str, discovery: str, aggregator: str +) -> None: + """``_find_and_load`` reads the parent's ``__path__`` before any finder runs. + + So a shim without one answers "is not a package" for every leaf under it — + 20 modules in ``brand``'s case, broken to serve 193 names. It borrows the + aggregator package's own ``__path__``, and the finder ahead of + ``PathFinder`` still serves each leaf as the canonical module object. + """ + old = _import_deprecated(deprecated) + aggregator_module = importlib.import_module(aggregator) + + assert old.__path__ is aggregator_module.__path__ + + leaves = sorted(info.name for info in pkgutil.iter_modules(aggregator_module.__path__)) + assert len(leaves) > 5, leaves + for leaf in leaves: + through_alias = _import_deprecated(f"{deprecated}.{leaf}") + assert through_alias is importlib.import_module(f"{aggregator}.{leaf}") + + +# --------------------------------------------------------------------------- +# Property 4 — the shared object carries no trace of the old name +# --------------------------------------------------------------------------- + + +@pytest.mark.parametrize(("deprecated", "canonical"), UNSPLIT_PAIRS, ids=UNSPLIT_PAIR_IDS) +def test_the_shared_module_carries_no_trace_of_the_deprecated_name( + deprecated: str, canonical: str +) -> None: + """``test_export_surface_is_derived`` reads these, so the alias must not move them. + + The import machinery writes exactly one of them when a loader hands back an + existing module: ``_init_module_attrs`` always replaces ``__spec__``, and + sets the rest only when absent. ``exec_module`` puts the canonical spec + back; this is the assertion that says so. + + A split name is its own module and says so, which is the honest answer when + there are two canonical halves — what + ``test_export_surface_is_derived`` actually reads is each CLASS's + ``__module__``, and the shim creates no class to stamp. + """ + module = _import_deprecated(deprecated) + + assert module.__name__ == canonical + assert module.__spec__ is not None + assert module.__spec__.name == canonical + # ``__loader__`` is the real source loader, never the alias's. If + # ``_init_module_attrs`` ever started overriding it, a reload would re-run + # the alias instead of the file. + assert not isinstance(module.__loader__, _AliasLoader) + assert module.__loader__ is module.__spec__.loader + + traces = { + "__name__": module.__name__, + "__package__": module.__package__ or "", + "__file__": module.__file__ or "", + "__spec__.name": module.__spec__.name, + "__spec__.origin": module.__spec__.origin or "", + } + leaked = {key: value for key, value in traces.items() if DEPRECATED_ROOT in value} + assert leaked == {}, leaked + + +def test_a_class_reached_through_the_old_path_reports_the_canonical_module() -> None: + """``__module__`` is what grades #911, over 4000 classes. + + A second load under the deprecated name would stamp the deprecated path + onto every class it created, and + ``test_export_surface_is_derived::test_no_generated_type_is_reachable_under_zero_names`` + would fail. Here is the same invariant, asserted on a class reached the + deprecated way. + """ + old = _import_deprecated(f"{DEPRECATED_ROOT}.core.format_id") + + assert old.FormatReferenceStructuredObject.__module__ == f"{CANONICAL_ROOT}.core.format_id" + + +# --------------------------------------------------------------------------- +# Property 3 — a DeprecationWarning, once per module +# --------------------------------------------------------------------------- + + +def test_the_warning_fires_once_per_module_and_names_the_new_path() -> None: + """Graded on a fresh finder, because the installed one has already warned. + + ``find_spec`` is called again for a name already in ``sys.modules`` + whenever something probes it — ``adcp.types.__getattr__`` does, for its + submodule passthrough — so the once-per-module rule is the finder's own + bookkeeping rather than a side effect of ``sys.modules``. + """ + finder = _AliasFinder() + name = f"{DEPRECATED_ROOT}.core.format_id" + + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter("always") + assert finder.find_spec(name) is not None + assert finder.find_spec(name) is not None + assert finder.find_spec(f"{DEPRECATED_ROOT}.core.product") is not None + + messages = [str(entry.message) for entry in caught] + assert all(entry.category is DeprecationWarning for entry in caught) + assert len(messages) == 2, messages + assert sum(name in message for message in messages) == 1, messages + + first = messages[0] + assert canonical_name(name) in first + assert f"v{REMOVED_IN_MAJOR}" in first + + +def test_the_finder_declines_every_name_outside_the_deprecated_root() -> None: + """A finder at the front of ``sys.meta_path`` must not answer for anything else.""" + finder = _AliasFinder() + for name in ( + "adcp", + "adcp.types", + CANONICAL_ROOT, + f"{CANONICAL_ROOT}.core", + "adcp.types.generated_pocket", + "json", + ): + assert finder.find_spec(name) is None, name + + +def test_a_name_that_exists_under_neither_root_is_reported_as_itself() -> None: + """The error names what the caller asked for, not the rewritten target.""" + with pytest.raises(ModuleNotFoundError) as caught: + importlib.import_module(f"{DEPRECATED_ROOT}.core.not_a_schema") + assert f"{DEPRECATED_ROOT}.core.not_a_schema" in str(caught.value) + assert CANONICAL_ROOT not in str(caught.value) + + +def test_installing_twice_leaves_one_finder_ahead_of_the_path_finder() -> None: + """``adcp.types`` installs it; a second call must not stack a second copy.""" + install() + install() + ours = [index for index, f in enumerate(sys.meta_path) if isinstance(f, _AliasFinder)] + assert len(ours) == 1, sys.meta_path + + path_finder = [ + index for index, f in enumerate(sys.meta_path) if getattr(f, "__name__", "") == "PathFinder" + ] + assert path_finder, sys.meta_path + assert ours[0] < path_finder[0], ( + "PathFinder would find the real files on the canonical package's __path__ " + "and load a SECOND copy of every module under the deprecated name" + ) + + +def test_a_real_import_in_a_clean_process_warns_and_keeps_identity() -> None: + """The adopter-facing behaviour, measured where ``sys.modules`` is empty. + + In-process tests cannot show this: whichever test imports the deprecated + path first consumes the warning for the whole session. Two subprocesses — + one that turns the warning into an error, one that ignores it and checks + identity — pin both halves. + """ + deep = f"{DEPRECATED_ROOT}.core.format_id" + + refused = subprocess.run( + [sys.executable, "-W", "error::DeprecationWarning", "-c", f"import {deep}"], + cwd=_REPO_ROOT, + capture_output=True, + text=True, + timeout=180, + ) + assert refused.returncode != 0, refused.stdout + assert "DeprecationWarning" in refused.stderr + assert canonical_name(DEPRECATED_ROOT) in refused.stderr + + accepted = subprocess.run( + [ + sys.executable, + "-W", + "ignore::DeprecationWarning", + "-c", + f"import {deep} as old, {canonical_name(deep)} as new; " + "print(old is new, old.FormatReferenceStructuredObject " + "is new.FormatReferenceStructuredObject, old.__name__)", + ], + cwd=_REPO_ROOT, + capture_output=True, + text=True, + timeout=180, + ) + assert accepted.returncode == 0, accepted.stderr + assert accepted.stdout.strip() == f"True True {canonical_name(deep)}" + + +def test_reload_of_a_deprecated_name_re_runs_the_canonical_module() -> None: + """``reload`` must not re-enter the alias — it reads the restored ``__spec__``. + + In its own process, because reloading a generated module rebuilds its + classes and every other module in the tree keeps the old ones. + """ + deep = f"{DEPRECATED_ROOT}.core.format_id" + result = subprocess.run( + [ + sys.executable, + "-W", + "ignore::DeprecationWarning", + "-c", + f"import importlib, {deep} as m; " + "again = importlib.reload(m); " + "print(again is m, again.__name__, type(again.__loader__).__name__)", + ], + cwd=_REPO_ROOT, + capture_output=True, + text=True, + timeout=180, + ) + assert result.returncode == 0, result.stderr + # ``reload`` returns the same object, re-executed from the canonical file by + # the canonical loader. + assert result.stdout.strip() == f"True {canonical_name(deep)} SourceFileLoader" + + +# --------------------------------------------------------------------------- +# Property 5 — the alias does not outlive its removal major +# --------------------------------------------------------------------------- + + +def _declared_major() -> int: + """The major version ``pyproject.toml`` declares, which release-please bumps.""" + pyproject = (_REPO_ROOT / "pyproject.toml").read_text() + match = re.search(r'^version = "(\d+)\.', pyproject, re.MULTILINE) + assert match, "pyproject.toml declares no [project] version" + return int(match.group(1)) + + +def _installed_major() -> int: + """The major version of the built distribution, which may run ahead of the tree.""" + import adcp + + match = re.match(r"(\d+)\.", adcp.__version__) + assert match, adcp.__version__ + return int(match.group(1)) + + +def test_the_alias_does_not_outlive_its_removal_major() -> None: + """A deprecation with no expiry is a permanent second surface. + + Read from both places a major can appear, because they diverge: a dev tree + carries ``pyproject.toml``'s version while the venv's metadata still holds + whatever was last installed. Either reaching v10 means this module, its + ``install()`` call in ``adcp/types/__init__.py``, and this suite go. + """ + reached = max(_declared_major(), _installed_major()) + assert reached < REMOVED_IN_MAJOR, ( + f"adcp is at v{reached} and {DEPRECATED_ROOT} was to be removed in " + f"v{REMOVED_IN_MAJOR}. Delete src/adcp/types/_generated_poc_alias.py, its " + f"install() call in src/adcp/types/__init__.py, and this test module." + ) + + +def test_the_removal_major_is_stated_where_an_adopter_reads_it() -> None: + """The expiry is in the warning and in the migration guide, not only in code.""" + assert REMOVED_IN_MAJOR == 10 + guide = (_REPO_ROOT / "docs" / "types-9-migration.md").read_text() + assert f"v{REMOVED_IN_MAJOR}" in guide + assert DEPRECATED_ROOT in guide diff --git a/tests/test_import_layering.py b/tests/test_import_layering.py index 709b96998..754533ec0 100644 --- a/tests/test_import_layering.py +++ b/tests/test_import_layering.py @@ -1,30 +1,35 @@ """Enforce the type-import layering rule documented in CLAUDE.md. -Only type facade/override modules such as ``aliases.py``, ``legacy.py``, -``canonical_creative.py``, and the public ``adcp.types/__init__.py`` composer -may import from the auto-generated layer -(``adcp.types._generated`` / ``adcp.types.generated_poc``). Every other module -under ``src/adcp/`` should import types via the public surface (``adcp.types`` -or ``adcp``). - -The rule exists because schema-regen rewrites generated class names. Public -imports are forwarded through ``aliases.py`` and survive regen; direct -``generated_poc`` imports break silently when datamodel-code-generator picks -a different name. +``adcp.types._generated`` is the one internal namespace left: it binds a bare +type name that several generated modules define to a single winner, chosen by +module sort order. Only the type facade/override modules that build the public +surface — ``aliases.py``, ``capabilities.py``, ``_eager.py``, and the public +``adcp.types/__init__.py`` composer — may import from it. Every other module +under ``src/adcp/`` imports types via ``adcp.types``, or from the domain module +that declares the name when the flat namespace cannot bind it. + +The rule exists because that single winner is not the schema's choice: a schema +addition can repoint a bare name, and the only trace is a line in a regenerated +file. + +The generated tree is NOT on this list. ``adcp.types.domains.`` and +``adcp.types.domains..`` are where the generator defines every +class, and they are a public address — there is no private tree to reach into, +which is why the eight files that used to be listed below as violations now +import legally. This test enforces a **frozen baseline**: existing violations are listed in ``_KNOWN_VIOLATIONS`` so refactor-them-away can be tackled separately. Any *new* file or *new* import that bypasses the public surface fails the test. To shrink the baseline: -- Re-route the import through a public namespace. Only ``adcp.types._generated`` - and ``adcp.types.generated_poc`` are forbidden; ``adcp``, ``adcp.types``, - ``adcp.types.domains.[.]``, ``adcp.types.legacy``, - ``adcp.types.aliases``, ``adcp.types.error_details`` and - ``adcp.types.capabilities`` are public and all pass. +- Re-route the import. Only ``adcp.types._generated`` is forbidden; ``adcp``, + ``adcp.types``, ``adcp.types.domains.[.]``, + ``adcp.types.legacy``, ``adcp.types.aliases``, ``adcp.types.error_details`` + and ``adcp.types.capabilities`` are public and all pass. - Which one depends on WHY the import is deep, and the two answers differ. If - the name is merely absent from the public surface, the surface is incomplete: - every generated type is exported from the module for the schema that declares + the name is merely absent from the flat surface, the surface is incomplete: + every generated type is declared by the module for the schema that declares it, so ``adcp.types.domains...`` serves it. If the name is QUARANTINED — the legacy v1 ``format_id`` world is reachable only under explicitly named ``Legacy*`` paths, which ``tests/test_canonical_creatives_rc3.py`` asserts — @@ -41,51 +46,27 @@ SRC_ROOT = Path(__file__).parent.parent / "src" / "adcp" +#: The generated tree. Skipped wholesale: it is a thousand modules the +#: generator writes, and it imports its own siblings relatively. +GENERATED_DIR = SRC_ROOT / "types" / "domains" + ALLOWED_FILES = { SRC_ROOT / "types" / "aliases.py", - SRC_ROOT / "types" / "_ergonomic.py", SRC_ROOT / "types" / "_generated.py", SRC_ROOT / "types" / "__init__.py", # ``_eager.py`` holds the eager realization of the public type surface # (the former ``__init__.py`` body): it binds every exported name from - # ``_generated`` / ``generated_poc`` and runs the import-time patchers. - # ``__init__.py`` is now a thin lazy facade that imports ``_eager`` on - # first attribute access, so the same direct generated-layer access applies. + # ``_generated`` and runs the import-time patchers. ``__init__.py`` is now + # a thin lazy facade that imports ``_eager`` on first attribute access, so + # the same direct ``_generated`` access applies. SRC_ROOT / "types" / "_eager.py", # ``capabilities.py`` is a re-export layer for the bundled # ``get_adcp_capabilities_response`` sub-models — it disambiguates # the ``Account`` / ``MediaBuy`` / ``Creative`` name collisions # before adopters import them via :mod:`adcp.decisioning.capabilities`. # Same architectural role as ``aliases.py`` (re-exports + renames), - # so the same direct ``generated_poc`` import access applies. + # so the same direct ``_generated`` access applies. SRC_ROOT / "types" / "capabilities.py", - # ``error_details.py`` and every module under ``domains/`` are generated - # re-export layers written by ``scripts/consolidate_exports.py`` alongside - # ``_generated.py``: one carries the error-details models with the field - # types they reference, the others re-export each schema domain faithfully - # so a type name several domains declare is unambiguous by module path. - # Same architectural role as ``_generated.py``, so the same direct - # generated-layer access applies. - SRC_ROOT / "types" / "error_details.py", - *(SRC_ROOT / "types" / "domains").rglob("*.py"), - # ``_forward_compat.py`` patches Format.assets and Assets94.assets at - # import time with open union types (issue #742). It must import the - # generated classes in-place to call model_rebuild() on them, giving it - # the same architectural role as ``_ergonomic.py``. - SRC_ROOT / "types" / "_forward_compat.py", - # ``canonical_decl.py`` (issue #741) is the hand-rolled - # ``ProductFormatDeclaration`` that replaces the codegen output — - # ``datamodel-code-generator`` flattens the upstream discriminated - # ``oneOf`` and drops the ``format_kind`` + ``params`` fields, so the - # public class is hand-rolled here. Same role as ``aliases.py`` / - # ``capabilities.py``: re-exports + overrides of generated types. - SRC_ROOT / "types" / "canonical_decl.py", - # The 7.0 creative migration exposes generated wire models only through - # an explicitly legacy facade and replaces primary creative lifecycle - # models with canonical-first boundary models. Both modules therefore - # have the same generated-type adapter role as ``aliases.py``. - SRC_ROOT / "types" / "legacy.py", - SRC_ROOT / "types" / "canonical_creative.py", } # Frozen baseline of pre-existing violations — paths relative to repo root. @@ -94,21 +75,11 @@ # if you forget to update the list, which is the desired behavior). _KNOWN_VIOLATIONS = frozenset( { - "src/adcp/capabilities.py", - "src/adcp/client.py", - "src/adcp/signing/autosign.py", - "src/adcp/signing/client.py", - "src/adcp/utils/format_assets.py", "src/adcp/utils/preview_cache.py", - "src/adcp/webhook_receiver.py", - "src/adcp/webhook_sender.py", } ) -_FORBIDDEN_PREFIXES = ( - "adcp.types._generated", - "adcp.types.generated_poc", -) +_FORBIDDEN_PREFIXES = ("adcp.types._generated",) def _module_imports_forbidden(path: Path) -> list[str]: @@ -134,7 +105,7 @@ def test_no_new_layering_violations() -> None: repo_root = SRC_ROOT.parent.parent violators: dict[str, list[str]] = {} for path in SRC_ROOT.rglob("*.py"): - if path in ALLOWED_FILES or "generated_poc" in path.parts: + if path in ALLOWED_FILES or path.is_relative_to(GENERATED_DIR): continue rel = str(path.relative_to(repo_root)) bad = _module_imports_forbidden(path) @@ -161,12 +132,27 @@ def test_no_new_layering_violations() -> None: raise AssertionError("\n".join(msgs)) +def test_generated_tree_is_not_forbidden() -> None: + """The generated tree's own address must stay off the forbidden list. + + The generator writes ``adcp.types.domains`` and defines every public class + there, so forbidding that prefix would forbid the definition site. This + assertion is what makes the shrink above deliberate rather than a lapse: + re-adding the prefix to buy back the old allowlist fails here. + """ + assert not any(p.startswith("adcp.types.domains") for p in _FORBIDDEN_PREFIXES), ( + "adcp.types.domains is where the generator defines the public classes — " + "see docs/type-surface.md. Forbid only namespaces that resolve a name by " + "something other than the schema." + ) + + def test_claude_md_documents_the_rule() -> None: """The architectural rule this test enforces must be documented in CLAUDE.md.""" claude_md = SRC_ROOT.parent.parent / "CLAUDE.md" text = claude_md.read_text(encoding="utf-8") # Loose match — we care that someone reading the test can find the rationale. - assert re.search(r"generated_poc/.*may import|import.*generated_poc", text), ( - "CLAUDE.md doesn't reference the generated_poc import layering rule. " + assert re.search(r"may import from `_generated\.py`|import.*_generated\.py", text), ( + "CLAUDE.md doesn't reference the generated-layer import layering rule. " "If the rule moved, update this test's docstring with the new pointer." ) diff --git a/tests/test_lazy_types.py b/tests/test_lazy_types.py index 40fe84ff9..fb9913ed3 100644 --- a/tests/test_lazy_types.py +++ b/tests/test_lazy_types.py @@ -315,7 +315,7 @@ def test_partial_modules_never_import_generated_layer(mod_name: str) -> None: for node in ast.walk(tree): if isinstance(node, ast.ImportFrom) and node.module: assert not node.module.startswith( - ("adcp.types._generated", "adcp.types.generated_poc") + ("adcp.types._generated", "adcp.types.domains") ), f"{mod_name}.py imports the internal generated layer: {node.module}" diff --git a/tests/test_legacy_surface_names_every_arm.py b/tests/test_legacy_surface_names_every_arm.py new file mode 100644 index 000000000..c710cf0c1 --- /dev/null +++ b/tests/test_legacy_surface_names_every_arm.py @@ -0,0 +1,235 @@ +"""A ``Legacy*`` name is the generated class, and there is one per response arm. + +Two defects, one cause: the ``Legacy*`` set was curated by hand, so what it +named drifted from what the generator emits. + +#1398 — ``adcp.types.LegacyFormatId`` was a SUBCLASS of the generated +``FormatReferenceStructuredObject``, and no public name on ``adcp.types`` +resolved to the generated class at all. The subclass redeclared four fields and +every redeclaration had become a weakening of the parent it was written to +narrow: ``width: 300.0`` validated against the class the SDK's own model fields +declare and was refused by the public name for it. A public name more permissive +(or less) than the library's own contract fails late and for no visible reason. + +#1399 — ``LegacyUpdateMediaBuy{Success,Error}Response`` shipped while +``LegacyUpdateMediaBuySubmittedResponse`` and all three +``LegacyCreateMediaBuy*Response`` arms did not, so four response arms could only +be named through ``adcp.types.domains.media_buy.*_response`` — the private tree +this release exists to retire. Adding four names would have fixed four +instances: the generator had ALREADY opened a fifth, by adding a fourth +``preview_creative`` arm that nobody named. + +So neither test reads a list. Both derive what the surface must carry from the +generated tree: + +* the arms of a response union come from ``typing.get_args`` of the union the + generated module itself declares — not from a name pattern, and not from + ``__module__`` (which names the canonical spec even for a duplicate, and so + cannot tell a copy from the original); +* a response is "on the ``Legacy*`` surface" when its tool has any ``Legacy*`` + name at all, which is what an adopter migrating through those names has; +* and the obligation is stated as object identity against ``adcp.types``, which + is the only thing an adopter's ``isinstance`` cares about. +""" + +from __future__ import annotations + +import importlib +import pathlib +import typing + +import pytest +from pydantic import BaseModel + +import adcp.types as types + +_DOMAINS_DIR = pathlib.Path(importlib.import_module("adcp.types.domains").__file__).parent +_CANONICAL_ROOT = "adcp.types.domains" + +LEGACY_NAMES = sorted(name for name in types.__all__ if name.startswith("Legacy")) +PLAIN_NAMES = sorted(name for name in types.__all__ if not name.startswith("Legacy")) + +#: The ``Legacy*`` names bound to a model class. The rest are union aliases +#: (``LegacyBuildCreativeResponse``, ``LegacyProductFormatDeclaration``, ...), +#: which have no field set to weaken — filtered out rather than skipped, so a +#: class that stops being one is a parametrization change and not a quiet skip. +LEGACY_CLASSES = sorted( + name + for name in LEGACY_NAMES + if isinstance(getattr(types, name, None), type) and issubclass(getattr(types, name), BaseModel) +) + + +def _camel(stem: str) -> str: + return "".join(part.title() for part in stem.split("_")) + + +def _response_arms() -> list[tuple[str, str, type[BaseModel]]]: + """Every arm of every response union whose tool is on the ``Legacy*`` surface. + + ``(tool, arm name, arm class)``. The arms come from the generated module's + own ``Response`` union, so a schema that gains or loses an arm changes + this list with no edit here. A response that is one class rather than a + union contributes that class as its only arm. + """ + + arms: list[tuple[str, str, type[BaseModel]]] = [] + for path in sorted(_DOMAINS_DIR.rglob("*_response.py")): + relative = path.relative_to(_DOMAINS_DIR) + if relative.parts[0] == "bundled": + # Compiled transport bundles inline a per-message copy of the source + # models. They are not the tool's response. + continue + tool = _camel(relative.stem.removesuffix("_response")) + if not any(name.startswith(f"Legacy{tool}") for name in LEGACY_NAMES): + continue + module = importlib.import_module( + ".".join([_CANONICAL_ROOT, *relative.parts[:-1], relative.stem]) + ) + union = getattr(module, f"{tool}Response", None) + if union is None: + continue + members = typing.get_args(union) or (union,) + for member in members: + if isinstance(member, type) and issubclass(member, BaseModel): + arms.append((tool, member.__name__, member)) + return arms + + +ARMS = _response_arms() +ARM_IDS = [f"{tool}:{name}" for tool, name, _ in ARMS] + + +def test_the_derived_sets_are_not_empty() -> None: + """Neither obligation below may go quiet when the tree is reorganized. + + A derivation that silently resolves to nothing is worse than a hand-written + list, because a list at least fails loudly when it stops matching. + """ + assert len(LEGACY_NAMES) > 40, LEGACY_NAMES + assert len(LEGACY_CLASSES) > 35, LEGACY_CLASSES + assert len(ARMS) > 12, ARM_IDS + assert {tool for tool, _, _ in ARMS} >= { + "BuildCreative", + "CreateMediaBuy", + "PreviewCreative", + "UpdateMediaBuy", + }, sorted({tool for tool, _, _ in ARMS}) + + +# --------------------------------------------------------------------------- +# #1398 — a Legacy name is the generated class, never a shim over it +# --------------------------------------------------------------------------- + + +@pytest.mark.parametrize("name", LEGACY_CLASSES) +def test_a_legacy_name_is_the_generated_class_not_a_subclass_of_it(name: str) -> None: + """``Legacy`` means "the class codegen emitted", with nothing in between. + + The load-bearing assertion is the ROUND TRIP: the class must be the + attribute the module it claims actually binds under its own qualname. + Neither half of it is sufficient alone, and a fire probe proved it — + + * ``__module__.startswith(domains)`` alone is defeated by one line + (``Clone.__module__ = "adcp.types.domains.core.format_id"``), and + ``__module__`` is a weak witness by construction here: ``module_from_spec`` + stamps the canonical spec's name onto a duplicate too, so it cannot tell a + copy from the original; + * the MRO check alone misses the shape this repo actually shipped. #1398's + clone was ``class LegacyFormatId(FormatReferenceStructuredObject)`` — a + different class NAME, so a "shadows a same-named base" rule reports + nothing. That rule is kept below for the subclass that does reuse the + name, but it is not what catches a renamed shim. + + Together they do: a shim declared anywhere but the generated tree fails the + first, and a shim that lies about where it was declared fails the second, + because the module it names binds no such attribute. + + A ``Legacy*`` name bound to a union alias rather than a class is out of the + parametrization: there is no field set to weaken. + """ + value = getattr(types, name) + + assert value.__module__.startswith(_CANONICAL_ROOT), ( + f"adcp.types.{name} is {value.__module__}.{value.__qualname__}, declared " + f"outside the generated tree. A Legacy name is the generated class " + f"itself, so a class in between reshapes the contract the SDK's own " + f"model fields carry (#1398)" + ) + declaring = importlib.import_module(value.__module__) + assert getattr(declaring, value.__qualname__, None) is value, ( + f"adcp.types.{name} reports {value.__module__} but that module binds no " + f"{value.__qualname__} — it is a local class claiming a generated address" + ) + + shadowed = [ + base + for base in value.__mro__[1:] + if isinstance(base, type) + and issubclass(base, BaseModel) + and base.__name__ == value.__name__ + ] + assert shadowed == [], f"adcp.types.{name} shadows the same-named {shadowed[0]!r}" + + +def test_the_format_reference_is_reachable_under_a_public_name() -> None: + """#1398's first half, on the one class it was measured on. + + ``[n for n in adcp.types.__all__ if getattr(adcp.types, n) is + FormatReferenceStructuredObject]`` was empty: generated model fields are + annotated with this class, and nothing on the flat surface named it. Both + ``Legacy*`` spellings now do. + """ + generated = importlib.import_module(f"{_CANONICAL_ROOT}.core.format_id") + reference = generated.FormatReferenceStructuredObject + + public = [name for name in types.__all__ if getattr(types, name, None) is reference] + assert public == [ + "LegacyFormatId", + "LegacyFormatReferenceStructuredObject", + ], public + + +def test_the_format_reference_accepts_what_the_sdk_s_own_model_fields_accept() -> None: + """The measured consequence of the dropped validators, stated as behaviour. + + ``width``/``height`` are ``SchemaInt`` on the generated class, which carries + a ``BeforeValidator`` for the JSON-schema spelling of an integer. The + subclass declared ``StrictInt`` and refused ``300.0`` — a payload every + model that declares the real type accepts. + """ + wire = {"agent_url": "https://creative.adcontextprotocol.org", "id": "display_300x250"} + + reference = types.LegacyFormatId.model_validate({**wire, "width": 300.0, "height": 250.0}) + assert (reference.width, reference.height) == (300, 250) + + assert reference.model_json_schema()["properties"]["agent_url"]["format"] == "uri" + + +# --------------------------------------------------------------------------- +# #1399 — one Legacy name per arm, derived from the generated unions +# --------------------------------------------------------------------------- + + +@pytest.mark.parametrize(("tool", "arm_name", "arm"), ARMS, ids=ARM_IDS) +def test_every_arm_of_a_response_on_the_legacy_surface_is_publicly_nameable( + tool: str, arm_name: str, arm: type[BaseModel] +) -> None: + """The generated arm itself, not only the canonical subclass that shadows it. + + A seller reading a response off the wire holds an instance of the GENERATED + arm. ``adcp.types.CreateMediaBuySuccessResponse`` is the canonical subclass, + so an ``isinstance`` against it is False for that instance and the only + public spelling for the thing in hand was a ``domains`` path. The + ``Legacy*`` name is how the flat surface reaches it, and every arm of a + response whose tool is on that surface needs one. + """ + direct = [name for name in PLAIN_NAMES if getattr(types, name, None) is arm] + through_legacy = [name for name in LEGACY_NAMES if getattr(types, name, None) is arm] + + assert direct or through_legacy, ( + f"{arm.__module__}.{arm_name} is an arm of {tool}Response and no public " + f"name on adcp.types resolves to it, so it can only be named through " + f"adcp.types.domains — which is the tree 9.0 retires. Add " + f"Legacy{tool}Response in src/adcp/types/legacy.py (#1399)" + ) diff --git a/tests/test_manifest_response_readback.py b/tests/test_manifest_response_readback.py index 8e52a636e..8947987dd 100644 --- a/tests/test_manifest_response_readback.py +++ b/tests/test_manifest_response_readback.py @@ -25,15 +25,15 @@ TmpOffer, ) from adcp.types.core import AgentConfig, Protocol, TaskResult, TaskStatus -from adcp.types.generated_poc.core.async_response_data import AdcpAsyncResponseData -from adcp.types.generated_poc.core.creative_manifest import ( +from adcp.types.domains.core.async_response_data import AdcpAsyncResponseData +from adcp.types.domains.core.creative_manifest import ( CreativeManifest as InputManifest, ) -from adcp.types.generated_poc.core.version_envelope import AdcpVersionEnvelope -from adcp.types.generated_poc.media_buy.build_creative_response import ( +from adcp.types.domains.core.version_envelope import AdcpVersionEnvelope +from adcp.types.domains.media_buy.build_creative_response import ( Creative as InputBuildCreative, ) -from adcp.types.generated_poc.trusted_match.provider_context_match_response import ( +from adcp.types.domains.trusted_match.provider_context_match_response import ( ContextMatchResponseProviderRouter, ) @@ -119,7 +119,7 @@ def test_manifest_response_round_trip(case, kind, json_input): ) manifest = read_manifest(response, case) if kind == "image": - assert manifest.format_kind is CanonicalFormatKind.image + assert manifest.format_kind == CanonicalFormatKind.image else: assert manifest.format_kind == kind assert manifest.model_dump()["vendor_annotation"] == {"notes": ["preserved"]} @@ -218,11 +218,31 @@ async def test_public_completed_task_parser_keeps_manifest_readback(case): @pytest.mark.parametrize("input_model", [CreativeManifest, InputManifest]) @pytest.mark.parametrize("reuse_instance", [False, True], ids=["dump", "instance"]) def test_manifest_readback_cannot_bypass_input_validation(case, input_model, reuse_instance): + """What a read-back manifest does when handed to an input model. + + The vocabulary half of this test is gone with the open-vocabulary decision: + ``format_kind`` is ``str`` on every model and none of them refuses a value, + because a pinned SDK cannot tell a kind a seller invented from a kind + defined after its pin (see :mod:`adcp.types.canonical_creative`). Choosing + which kinds to accept is the caller's, through + ``adcp.types.is_canonical_format_kind``. + + What remains, and is graded here, is the STRUCTURAL half, which never + depended on the vocabulary: a readback node is an instance of neither + input model, so an instance is refused as ``model_type``, while its dump + round-trips and retains the unknown kind. + """ model, payload = response_case(case) manifest = read_manifest(model.model_validate(payload), case) value = manifest if reuse_instance else manifest.model_dump(mode="json") - with pytest.raises(ValidationError): - input_model.model_validate(value) + + if reuse_instance: + with pytest.raises(ValidationError) as error: + input_model.model_validate(value) + assert error.value.errors()[0]["type"] == "model_type" + else: + assert input_model.model_validate(value).format_kind == FUTURE_KIND + assert not isinstance(manifest, input_model) @@ -233,7 +253,7 @@ def test_nested_readback_accepts_known_source_models(case): source = InputBuildCreative if case == "build-variants" else TmpOffer payload[collection][0] = source.model_validate(payload[collection][0]) response = model.model_validate(payload) - assert read_manifest(response, case).format_kind is CanonicalFormatKind.image + assert read_manifest(response, case).format_kind == CanonicalFormatKind.image node = getattr(response, collection)[0] assert type(node).__name__ not in public_types.__all__ assert not hasattr(public_types, type(node).__name__) @@ -284,7 +304,21 @@ def test_build_readback_preserves_empty_and_error_branch_rules(): assert response.creatives[0].errors[0].code == "BUILD_FAILED" -def test_direct_listed_creative_kind_stays_strict(): +def test_a_directly_listed_creative_retains_an_unknown_kind(): + """A listed creative retains the ``format_kind`` the seller sent. + + This test used to assert the opposite — that a listed creative's + ``format_kind`` stays inside the closed enum — because the enum was the + type. ``format_kind`` is now ``str`` on every model and none of them + refuses a value (see :mod:`adcp.types.canonical_creative`), so retaining is + the required behaviour and refusing would be the defect: a buyer on an + older SDK would fail the whole page because one row named a kind promoted + after their release. + + There is no strict counterpart to pair this with. Deciding which kinds to + accept is the caller's, through ``adcp.types.is_canonical_format_kind``, + graded by ``tests/test_open_format_kind_vocabulary.py``. + """ payload = { "query_summary": {"total_matching": 1, "returned": 1}, "pagination": {"has_more": False}, @@ -299,11 +333,11 @@ def test_direct_listed_creative_kind_stays_strict(): } ], } - assert ListCreativesResponse.model_validate(payload).creatives[0].format_kind is ( + assert ListCreativesResponse.model_validate(payload).creatives[0].format_kind == ( CanonicalFormatKind.image ) payload["creatives"][0]["format_kind"] = FUTURE_KIND - with pytest.raises(ValidationError) as error: - ListCreativesResponse.model_validate(payload) - assert error.value.errors()[0]["loc"] == ("creatives", 0, "format_kind") - assert error.value.errors()[0]["type"] == "enum" + listed = ListCreativesResponse.model_validate(payload).creatives[0] + assert listed.format_kind == FUTURE_KIND + # Retained on the wire too, so a buyer can route it and hand it back. + assert listed.model_dump(mode="json")["format_kind"] == FUTURE_KIND diff --git a/tests/test_migrate_v3_to_v4.py b/tests/test_migrate_v3_to_v4.py index 0bde06a55..4e73c684d 100644 --- a/tests/test_migrate_v3_to_v4.py +++ b/tests/test_migrate_v3_to_v4.py @@ -259,7 +259,10 @@ def test_generated_poc_symbol_map_covers_publicly_exported_names() -> None: assert hasattr( types_module, public_name ), f"source-scoped migration target {replacement!r} is not public" - source_module = importlib.import_module(f"adcp.types.generated_poc.{_module}") + # The map's keys are the v3 spelling the codemod detects; the classes + # live at adcp.types.domains now, same module stems. Same asymmetry as + # v3_to_v4._replacement_is_identical, for the same reason. + source_module = importlib.import_module(f"adcp.types.domains.{_module}") assert getattr(types_module, public_name) is getattr(source_module, _symbol) diff --git a/tests/test_normalize_pyproject_prerelease.py b/tests/test_normalize_pyproject_prerelease.py index 14dd2e0e3..64329e7c2 100644 --- a/tests/test_normalize_pyproject_prerelease.py +++ b/tests/test_normalize_pyproject_prerelease.py @@ -33,9 +33,7 @@ def test_normalize_pyproject_text_changes_only_project_version_line() -> None: version = "1.0.0-beta.1" """ - assert ( - normalize_pyproject_text(text) - == """\ + assert normalize_pyproject_text(text) == """\ [project] name = "adcp" version = "7.0.0b1" @@ -43,7 +41,6 @@ def test_normalize_pyproject_text_changes_only_project_version_line() -> None: [tool.example] version = "1.0.0-beta.1" """ - ) def test_normalize_pyproject_reports_whether_file_changed(tmp_path: Path) -> None: diff --git a/tests/test_open_format_kind_vocabulary.py b/tests/test_open_format_kind_vocabulary.py new file mode 100644 index 000000000..73b1cc6a9 --- /dev/null +++ b/tests/test_open_format_kind_vocabulary.py @@ -0,0 +1,259 @@ +"""``format_kind`` is one open ``str``; the vocabulary is exported, not enforced. + +``core/canonical-format-kind.json`` declares a closed 16-member ``enum`` and, +in the same file, requires a consumer to retain an unknown value and not fail +the payload — "the producer-side enum stays closed; the consumer-side enum +stays open for forward compatibility." The schema knows the rule is directional +and then encodes it as one closed enum, which cannot carry that. + +So every reference generates ``str`` and **no model refuses a value**: a pinned +SDK cannot tell a kind a seller invented from a kind defined after its pin, and +refusing the second to prevent the first would make this library's version a +ceiling on what the protocol permits. The vocabulary is not discarded, it is +relocated — :class:`CanonicalFormatKind` stays a first-class export for +comparison, and :func:`is_canonical_format_kind` answers membership against a +vocabulary the CALLER supplies. Upstream ask: adcontextprotocol/adcp#7929. + +Where the behaviour is graded: + +* ``tests/test_forward_compat_format_kind.py`` — an unrecognised value + survives construction, validation, serialization and re-parsing on every + model that carries the field; +* ``tests/test_delivery_manifest_readback.py`` and + ``tests/test_manifest_response_readback.py`` — what is left of #1241 once + there is no strict side: the structural rules about instances; +* **this module** — the properties of the OVERRIDE itself: it is applied in one + place, nothing re-closes the vocabulary, the enum stays exported, and the + predicate takes the caller's vocabulary. +""" + +from __future__ import annotations + +import json +import pathlib +import sys + +import pytest +from pydantic import BaseModel + +import adcp.types +from adcp.types.canonical_creative import PRIMARY_CANONICAL_MODELS, is_canonical_format_kind + +# From the DEFINITION module, deliberately: ``test_the_vocabulary_enum_stays_exported`` +# asserts that ``adcp.types`` re-exports it, and that assertion can only run if +# importing this module does not itself depend on the export being there. +from adcp.types.domains.core.canonical_format_kind import CanonicalFormatKind +from adcp.validation.schema_loader import _ensure_state + +_REPO_ROOT = pathlib.Path(__file__).resolve().parents[1] +_GENERATED = _REPO_ROOT / "src" / "adcp" / "types" / "domains" + +sys.path.insert(0, str(_REPO_ROOT)) +from scripts.generate_types import OPEN_VOCABULARY_SCHEMAS # noqa: E402 + +_VOCABULARY_SCHEMA = pathlib.Path("core/canonical-format-kind.json") +#: The two field names a model can carry the vocabulary under. +_KIND_FIELDS = ("format_kind", "format_kinds") + + +# --------------------------------------------------------------------------- +# The override lives in one place. +# --------------------------------------------------------------------------- + + +def test_the_override_is_declared_once_and_names_the_schema_it_overrides() -> None: + """One entry in one set, in the generator — not a widening per call site.""" + assert OPEN_VOCABULARY_SCHEMAS == {_VOCABULARY_SCHEMA} + + +def test_no_generated_model_types_a_field_with_the_closed_enum() -> None: + """The property the single transform buys: nothing re-closes the vocabulary. + + A per-site widening leaves every site it missed closed, which is what the + scaffolding this replaced was for. Asserted over the whole generated tree: + the enum may be IMPORTED — it is the vocabulary, and ``core/__init__`` + re-exports it — but may not annotate a field. + """ + offenders: list[str] = [] + for path in sorted(_GENERATED.rglob("*.py")): + for number, line in enumerate(path.read_text().splitlines(), 1): + stripped = line.strip() + if "CanonicalFormatKind" not in stripped: + continue + if ( + stripped.startswith(("import ", "from ", "#", '"')) + or '"CanonicalFormatKind"' in stripped + # the vocabulary's own declaration, which is the point + or stripped == "class CanonicalFormatKind(StrEnum):" + ): + continue + offenders.append(f"{path.relative_to(_GENERATED)}:{number} {stripped}") + assert offenders == [], "\n".join( + ["these generated declarations re-close the open vocabulary:", *offenders] + ) + + +def _referencing_property_names() -> set[str]: + """Property names whose declaration references the vocabulary schema.""" + state = _ensure_state(None) + names: set[str] = set() + + def walk(node: object, enclosing: str | None) -> None: + if isinstance(node, dict): + ref = node.get("$ref") + if isinstance(ref, str) and ref.endswith(_VOCABULARY_SCHEMA.as_posix()) and enclosing: + names.add(enclosing) + for key, value in node.items(): + if key == "properties" and isinstance(value, dict): + for name, declaration in value.items(): + walk(declaration, name) + else: + walk(value, enclosing) + elif isinstance(node, list): + for item in node: + walk(item, enclosing) + + for file in sorted(state.root.root.rglob("*.json")): + if file.name != "index.json": + walk(json.loads(file.read_text()), None) + return names + + +def test_every_reference_to_the_schema_generated_a_string() -> None: + """Read from the bundle, not from a list: every ``$ref`` is accounted for.""" + referencing = _referencing_property_names() + assert referencing, "no property references the schema — it moved" + + graded = 0 + for path in sorted(_GENERATED.rglob("*.py")): + source = path.read_text() + for name in referencing: + for marker in (f" {name}: Annotated[", f" {name}: "): + index = source.find(marker) + if index == -1: + continue + declaration = source[index : index + 400] + assert ( + "CanonicalFormatKind" not in declaration.split("Field(")[0] + ), f"{path.relative_to(_GENERATED)} declares {name} with the closed enum" + graded += 1 + break + assert graded > 15, f"only {graded} declarations examined" + + +# --------------------------------------------------------------------------- +# The vocabulary is relocated, not discarded. +# --------------------------------------------------------------------------- + + +def test_the_vocabulary_enum_stays_exported() -> None: + """The one thing the override must not take away. + + No field references ``CanonicalFormatKind`` any more, so nothing in the + generated tree forces it to keep existing — a later regeneration could drop + it as unused, and then a string field has no constants and every consumer + writes literals. This is the test that keeps that honest. It is also what + makes ``creative.format_kind == CanonicalFormatKind.image`` the sanctioned + comparison: the enum is a ``StrEnum``, so the member equals the plain + string a model carries. + """ + assert "CanonicalFormatKind" in adcp.types.__all__ + assert adcp.types.CanonicalFormatKind is CanonicalFormatKind + assert issubclass(CanonicalFormatKind, str) + assert len(list(CanonicalFormatKind)) == 16 + assert "image" == CanonicalFormatKind.image + + +def test_the_exported_vocabulary_is_the_pinned_bundle_s() -> None: + """Derived from the schema, so it cannot drift from the pin.""" + bundle = json.loads((_ensure_state(None).root.core / "canonical-format-kind.json").read_text()) + assert {kind.value for kind in CanonicalFormatKind} == set(bundle["enum"]) + + +# --------------------------------------------------------------------------- +# One helper, whose vocabulary is the caller's. +# --------------------------------------------------------------------------- + + +def test_the_predicate_is_exported_and_defaults_to_the_pinned_vocabulary() -> None: + assert "is_canonical_format_kind" in adcp.types.__all__ + assert adcp.types.is_canonical_format_kind is is_canonical_format_kind + assert is_canonical_format_kind("image") + assert is_canonical_format_kind(CanonicalFormatKind.image) + assert not is_canonical_format_kind("holographic_banner") + + +@pytest.mark.parametrize("value", [None, 7, b"image", ["image"]]) +def test_the_predicate_answers_false_for_a_non_string(value: object) -> None: + """A yes/no question answers yes or no — it does not raise. + + ``CanonicalFormatKind(value)`` raising ``ValueError`` is a working test and + an awkward API; a consumer should not write ``try``/``except`` to ask + whether a value is canonical. + """ + assert is_canonical_format_kind(value) is False + + +def test_the_vocabulary_is_the_callers() -> None: + """The parameter is the point: a seller's set is not this SDK's. + + It can be LARGER — the seller speaks a newer spec and handles a kind this + pin has never heard of — or SMALLER — the seller implements four of the + sixteen. Neither is expressible by anything the library knows, so the + vocabulary is supplied rather than assumed. + """ + newer_spec = {*(kind.value for kind in CanonicalFormatKind), "holographic_banner"} + assert is_canonical_format_kind("holographic_banner", newer_spec) + assert is_canonical_format_kind("image", newer_spec) + + seller_subset = {"image", "html5"} + assert is_canonical_format_kind("image", seller_subset) + assert not is_canonical_format_kind("video_vast", seller_subset) + + +# --------------------------------------------------------------------------- +# One type, no per-direction model. +# --------------------------------------------------------------------------- + + +def _carries_a_kind(model: type[BaseModel]) -> bool: + return any(name in model.model_fields for name in _KIND_FIELDS) + + +KIND_BEARING = [model for model in PRIMARY_CANONICAL_MODELS if _carries_a_kind(model)] +KIND_BEARING_IDS = [model.__name__ for model in KIND_BEARING] + + +def test_the_kind_bearing_model_set_is_not_empty() -> None: + assert len(KIND_BEARING) == 10, KIND_BEARING_IDS + + +@pytest.mark.parametrize("model", KIND_BEARING, ids=KIND_BEARING_IDS) +def test_every_kind_bearing_model_types_the_kind_as_a_string( + model: type[BaseModel], +) -> None: + """One type on every model. That is the whole override.""" + for name in _KIND_FIELDS: + field = model.model_fields.get(name) + if field is None: + continue + rendered = str(field.annotation) + assert "CanonicalFormatKind" not in rendered, f"{model.__name__}.{name}: {rendered}" + assert "str" in rendered, f"{model.__name__}.{name}: {rendered}" + + +@pytest.mark.parametrize("model", KIND_BEARING, ids=KIND_BEARING_IDS) +def test_no_model_carries_a_vocabulary_validator(model: type[BaseModel]) -> None: + """No per-direction strictness anywhere — the SDK does not discriminate. + + Stated structurally rather than by probing values, so a validator + reintroduced under any name on any of these models fails here. A field + validator bound to ``format_kind`` or ``format_kinds`` is what a + reintroduced ``_StrictFormatKind`` would look like. + """ + bound = { + name + for name, decorator in model.__pydantic_decorators__.field_validators.items() + if set(decorator.info.fields) & set(_KIND_FIELDS) + } + assert bound == set(), f"{model.__name__} validates the vocabulary: {sorted(bound)}" diff --git a/tests/test_projection_catalog_adapters_rc3.py b/tests/test_projection_catalog_adapters_rc3.py index 6f5c2ad25..05f22166a 100644 --- a/tests/test_projection_catalog_adapters_rc3.py +++ b/tests/test_projection_catalog_adapters_rc3.py @@ -219,7 +219,7 @@ def test_publisher_snapshot_precedes_exact_bundled_aao_route() -> None: ) assert projected.declaration is not None - assert projected.declaration.format_kind.value == "display_tag" + assert projected.declaration.format_kind == "display_tag" assert projected.declaration.format_option_id == "publisher_mrec_override" assert projected.declaration.publisher_domain == "publisher.example" @@ -245,7 +245,7 @@ def ordinary_converter(context): ) assert projected.declaration is not None - assert projected.declaration.format_kind.value == "image" + assert projected.declaration.format_kind == "image" assert projected.declaration.format_option_id != "ordinary_override" diff --git a/tests/test_protocol_envelope_inheritance.py b/tests/test_protocol_envelope_inheritance.py index f4fc6aa17..65f1e1dfb 100644 --- a/tests/test_protocol_envelope_inheritance.py +++ b/tests/test_protocol_envelope_inheritance.py @@ -15,7 +15,7 @@ import json from pathlib import Path -from typing import Literal, get_args, get_origin +from typing import Literal import pytest from pydantic import BaseModel @@ -23,9 +23,8 @@ from adcp._version import _read_packaged_version from adcp.types import ProtocolEnvelope, canonical_creative from adcp.types import aliases as aliases_module -from adcp.types.generated_poc.enums.task_status import TaskStatus +from adcp.types.domains.enums.task_status import TaskStatus from adcp.validation.version import resolve_bundle_key -from tests import canonical_stub_gap _ENVELOPE_FIELDS = frozenset(ProtocolEnvelope.model_fields) @@ -99,7 +98,7 @@ def test_generated_arms_match_their_root_schema_composition(relative: str) -> No import importlib import re - module_name = "adcp.types.generated_poc." + relative.removesuffix(".py").replace("/", ".") + module_name = "adcp.types.domains." + relative.removesuffix(".py").replace("/", ".") module = importlib.import_module(module_name) schema_path = _schema_dir() / Path(relative).with_suffix(".json").as_posix().replace("_", "-") @@ -175,10 +174,10 @@ def test_envelope_state_used_to_land_in_extra() -> None: assert response.context_id == "ctx_1" -# ---- canonical_creative dynamic clones ---- +# ---- canonical_creative public models ---- -_CANONICAL_RESPONSE_CLONES = ( +_CANONICAL_RESPONSES = ( "GetProductsResponse", "CreateMediaBuyResponse1", "CreateMediaBuyResponse2", @@ -193,27 +192,35 @@ def test_envelope_state_used_to_land_in_extra() -> None: ) -@pytest.mark.parametrize("name", _CANONICAL_RESPONSE_CLONES) -def test_canonical_clone_preserves_envelope_ancestry(name: str) -> None: - """``_canonical_clone`` rebuilds fields; it must keep the ancestry too.""" +@pytest.mark.parametrize("name", _CANONICAL_RESPONSES) +def test_canonical_response_preserves_envelope_ancestry(name: str) -> None: + """A canonical response inherits the generated model, envelopes included. + + The ancestry used to be re-declared: ``_canonical_clone`` copied fields and + had to list the envelopes as extra bases to keep ``issubclass`` true. The + models are real subclasses now, so the ancestry is transitive and nothing + re-declares it — which is the thing this test must keep proving. + """ model = getattr(canonical_creative, name) assert issubclass(model, ProtocolEnvelope), name assert _ENVELOPE_FIELDS <= set(model.model_fields), name -@pytest.mark.parametrize("name", [*_CANONICAL_RESPONSE_CLONES, "GetProductsRequest", "Product"]) -def test_canonical_clone_keeps_the_boundary_extra_policy(name: str) -> None: - """The envelope bases must not override ``CanonicalBoundaryModel``'s config. +@pytest.mark.parametrize("name", [*_CANONICAL_RESPONSES, "GetProductsRequest", "Product"]) +def test_canonical_model_keeps_the_boundary_extra_policy(name: str) -> None: + """The generated base must not override ``CanonicalBoundaryModel``'s config. - Pydantic merges ``model_config`` left to right across bases, so an envelope - listed after the boundary model would reinstate :class:`AdCPBaseModel`'s - ``extra="ignore"`` and silently drop caller-supplied extension keys. + Pydantic merges ``model_config`` left to right across bases, so a generated + model listed AFTER the boundary model would reinstate + :class:`AdCPBaseModel`'s ``extra="ignore"`` and silently drop + caller-supplied extension keys. ``CanonicalBoundaryModel`` is therefore + always the last base. """ model = getattr(canonical_creative, name) assert model.model_config["extra"] == "allow", name -def test_canonical_clone_still_round_trips_extension_keys() -> None: +def test_canonical_model_still_round_trips_extension_keys() -> None: """The config regression above is observable through an unknown key.""" request = canonical_creative.GetProductsRequest.model_validate( {"promoted_offering": "test", "buying_mode": "brief"} @@ -253,114 +260,53 @@ def test_canonical_responses_construct_without_status() -> None: assert accepted.status == TaskStatus.submitted -def _stub_status_annotation(runtime_annotation: object) -> str: - """Spell a runtime ``status`` annotation the way the stub must declare it.""" - if runtime_annotation is TaskStatus: - return "TaskStatus" - if get_origin(runtime_annotation) is Literal: - (member,) = get_args(runtime_annotation) - if isinstance(member, TaskStatus): - return f"Literal[TaskStatus.{member.name}]" - return f"Literal['{member}']" - raise AssertionError(f"unhandled runtime status annotation: {runtime_annotation!r}") - +def test_canonical_response_status_is_the_generated_one() -> None: + """Every concrete canonical response carries its generated ``status`` exactly. -def test_canonical_response_stub_status_matches_runtime() -> None: - """Every concrete canonical response re-declares its exact ``status``. + This replaces ``test_canonical_response_stub_status_matches_runtime``, whose + subject was ``canonical_creative.pyi``: the stub declared a private + ``_CanonicalResponseEnvelope`` bridge relaxing ``status`` to ``Any`` so the + arms' ``Literal`` pins would not trip mypy's Liskov check, and this test + pinned every concrete response's hand-written re-declaration against the + runtime so the bridge could not leak ``Any`` to adopters. - ``_CanonicalResponseEnvelope`` relaxes ``status`` to ``Any`` so the arms' - ``Literal`` pins do not trip the Liskov check. That relaxation is a bridge, - not a public type: a concrete response that forgets to re-declare ``status`` - silently hands adopters ``Any``, which type-checks against anything and - hides the very narrowing this module is about. Pin stub and runtime - together so the bridge cannot leak. + The stub is deleted and the canonical responses are real subclasses of the + generated wire models, so there is no second declaration of ``status`` to + drift from the first — the arm's pinned ``Literal`` is inherited. The + property worth grading is therefore the one that now carries the contract: + a canonical response's ``status`` annotation must be IDENTICAL to the + annotation of the generated class it refines. A redeclaration that widened + or relaxed it would fail here, which is the leak the old test was watching. """ - import ast - - stub_path = Path(canonical_creative.__file__).with_suffix(".pyi") - stub = ast.parse(stub_path.read_text()) - - declared: dict[str, str] = {} - for node in stub.body: - if not isinstance(node, ast.ClassDef): - continue - if not any( - isinstance(base, ast.Name) and base.id == "_CanonicalResponseEnvelope" - for base in node.bases - ): - continue - status = next( - ( - item - for item in node.body - if isinstance(item, ast.AnnAssign) - and isinstance(item.target, ast.Name) - and item.target.id == "status" - ), - None, - ) - assert status is not None, ( - f"{node.name} inherits the relaxed ``status: Any`` bridge without " - f"re-declaring its own — adopters would read ``Any``" - ) - declared[node.name] = ast.unparse(status.annotation) - - # Guard the other direction too: a newly added canonical response must show - # up in the stub rather than quietly skipping this check. runtime_responses = { - name + name: model for name in dir(canonical_creative) if not name.startswith("_") - and isinstance(getattr(canonical_creative, name), type) - and issubclass(getattr(canonical_creative, name), ProtocolEnvelope) + and isinstance(model := getattr(canonical_creative, name), type) + and issubclass(model, ProtocolEnvelope) # ``ProtocolEnvelope`` itself is imported into this namespace as a base; - # only the canonical clones built on it are in scope here. - and issubclass(getattr(canonical_creative, name), canonical_creative.CanonicalBoundaryModel) - } - assert declared.keys() == runtime_responses - - for name, stub_annotation in sorted(declared.items()): - runtime = getattr(canonical_creative, name).model_fields["status"] - assert stub_annotation == _stub_status_annotation(runtime.annotation), name - # The stub marks the field defaulted (``= ...``); the runtime agrees. - assert not runtime.is_required(), name - - -def test_canonical_stub_declares_every_runtime_field() -> None: - """``canonical_creative.pyi`` must not fall behind the models it types. - - The models are built with ``create_model``, so the stub is the only thing a - type checker reads. A field the stub omits does not exist to mypy: the - synthesized ``__init__`` rejects it as an unexpected keyword argument and - reading it is an attribute error, both on fields that are required at - runtime. The stub currently omits several hundred, ledgered per class in - ``canonical_stub_field_gap.json``; the ledger may only shrink, so a schema - bump that adds a field the stub does not declare fails here instead of - reaching adopters. - """ - recorded: dict[str, list[str]] = json.loads( - canonical_stub_gap.LEDGER_FILE.read_text(encoding="utf-8") - )["gap"] - measured = canonical_stub_gap.stub_field_gap() - - grown = { - name: sorted(set(fields) - set(recorded.get(name, ()))) - for name, fields in measured.items() - if set(fields) - set(recorded.get(name, ())) + # only the canonical models built on it are in scope here. + and issubclass(model, canonical_creative.CanonicalBoundaryModel) } - assert not grown, ( - "canonical_creative.pyi does not declare these runtime fields, and the " - f"ledger does not record them: {grown}. Declare them in the stub. If a " - "field genuinely left the stub, run " - "`python scripts/update_canonical_stub_ledger.py` — it refuses to record a loss." - ) + assert runtime_responses, "no canonical responses discovered" - closed = { - name: sorted(set(fields) - set(measured.get(name, ()))) - for name, fields in recorded.items() - if set(fields) - set(measured.get(name, ())) - } - assert not closed, ( - f"The stub now declares these ledgered fields: {closed}. " - "Run `python scripts/update_canonical_stub_ledger.py` to shrink the ledger." - ) + for name, model in runtime_responses.items(): + generated = next( + base + for base in model.__bases__ + if base is not canonical_creative.CanonicalBoundaryModel + ) + assert generated.__module__.startswith("adcp.types.domains."), ( + f"{name} does not refine a generated wire model (base {generated!r}); " + f"a canonical response must be a subclass, never a copy" + ) + assert ( + model.model_fields["status"].annotation == generated.model_fields["status"].annotation + ), ( + f"{name}.status is {model.model_fields['status'].annotation!r} but the " + f"generated {generated.__name__}.status is " + f"{generated.model_fields['status'].annotation!r}" + ) + # The other half of the old stub contract: ``status`` is defaulted, so a + # plain construction never has to supply it. + assert not model.model_fields["status"].is_required(), name diff --git a/tests/test_publisher_selector_xor_autoenforce.py b/tests/test_publisher_selector_xor_autoenforce.py index a5e4b8e25..0c36b6ec2 100644 --- a/tests/test_publisher_selector_xor_autoenforce.py +++ b/tests/test_publisher_selector_xor_autoenforce.py @@ -39,7 +39,7 @@ class TestSelectorXorAutoEnforce: @_COMPACT_FORM_DROPPED def test_selector1_rejects_bare_construct(self): # selection_type='all' with neither publisher_domain nor publisher_domains - from adcp.types.generated_poc.core.publisher_property_selector import ( + from adcp.types.domains.core.publisher_property_selector import ( PublisherPropertySelector1, ) @@ -47,7 +47,7 @@ def test_selector1_rejects_bare_construct(self): PublisherPropertySelector1(selection_type="all") def test_selector1_rejects_both_publisher_fields(self): - from adcp.types.generated_poc.core.publisher_property_selector import ( + from adcp.types.domains.core.publisher_property_selector import ( PublisherPropertySelector1, ) @@ -59,7 +59,7 @@ def test_selector1_rejects_both_publisher_fields(self): ) def test_selector1_accepts_singular_form(self): - from adcp.types.generated_poc.core.publisher_property_selector import ( + from adcp.types.domains.core.publisher_property_selector import ( PublisherPropertySelector1, ) @@ -68,7 +68,7 @@ def test_selector1_accepts_singular_form(self): @_COMPACT_FORM_DROPPED def test_selector1_accepts_compact_form(self): - from adcp.types.generated_poc.core.publisher_property_selector import ( + from adcp.types.domains.core.publisher_property_selector import ( PublisherPropertySelector1, ) @@ -80,7 +80,7 @@ def test_selector1_accepts_compact_form(self): @_COMPACT_FORM_DROPPED def test_selector3_rejects_bare_construct(self): - from adcp.types.generated_poc.core.publisher_property_selector import ( + from adcp.types.domains.core.publisher_property_selector import ( PublisherPropertySelector3, ) @@ -89,7 +89,7 @@ def test_selector3_rejects_bare_construct(self): @_COMPACT_FORM_DROPPED def test_selector3_accepts_compact_form_with_required_tags(self): - from adcp.types.generated_poc.core.publisher_property_selector import ( + from adcp.types.domains.core.publisher_property_selector import ( PublisherPropertySelector3, ) @@ -105,7 +105,7 @@ def test_selector2_unpatched_passes_valid_input(self): # by_id selector has no XOR — only publisher_domain is allowed, # publisher_domains is rejected at the JSON-schema level. The # auto-enforce patch correctly leaves this class alone. - from adcp.types.generated_poc.core.publisher_property_selector import ( + from adcp.types.domains.core.publisher_property_selector import ( PublisherPropertySelector2, ) @@ -136,7 +136,7 @@ def test_model_validator_decorator_info_present(self): def test_selector1_has_registered_validator(self): # The patch lands at module-import time. If the registration # shape changes and the patch silently no-ops, this catches it. - from adcp.types.generated_poc.core.publisher_property_selector import ( + from adcp.types.domains.core.publisher_property_selector import ( PublisherPropertySelector1, ) diff --git a/tests/test_purchase_continuation.py b/tests/test_purchase_continuation.py index 2b92d6106..30a0c3ece 100644 --- a/tests/test_purchase_continuation.py +++ b/tests/test_purchase_continuation.py @@ -2277,8 +2277,7 @@ async def test_sqlite_migrates_pre_release_tombstone_schema_before_purge( for trigger_name in trigger_names: conn.execute(f'DROP TRIGGER "{trigger_name}"') conn.execute("DROP TABLE adcp_compat_issuance_tombstones") - conn.execute( - """ + conn.execute(""" CREATE TABLE adcp_compat_issuance_tombstones ( token_hash TEXT PRIMARY KEY, principal_id TEXT NOT NULL, @@ -2287,8 +2286,7 @@ async def test_sqlite_migrates_pre_release_tombstone_schema_before_purge( legacy_equivalence_hash TEXT NOT NULL, retired_at TEXT NOT NULL ) - """ - ) + """) conn.execute( "CREATE UNIQUE INDEX adcp_compat_issuance_tombstones_issuance_idx " "ON adcp_compat_issuance_tombstones (principal_id, issuance_fingerprint) " diff --git a/tests/test_reporting_capability_models.py b/tests/test_reporting_capability_models.py index d8ae20449..6410070af 100644 --- a/tests/test_reporting_capability_models.py +++ b/tests/test_reporting_capability_models.py @@ -50,7 +50,7 @@ def graph(request): return ReportingDeliveryCapabilities, GetAdcpCapabilitiesResponse # Codegen's self-contained clone is deliberately tested as a separate graph. module = importlib.import_module( - "adcp.types.generated_poc.bundled.protocol.get_adcp_capabilities_response" + "adcp.types.domains.bundled.protocol.get_adcp_capabilities_response" ) return module.ReportingDelivery, module.GetAdcpCapabilitiesResponse @@ -159,7 +159,7 @@ class OrdinaryParent(BaseModel): def test_post_generation_repair_is_idempotent_for_both_actual_model_layouts(tmp_path, monkeypatch): from scripts import post_generate_fixes - root = Path(__file__).parents[1] / "src/adcp/types/generated_poc" + root = Path(__file__).parents[1] / "src/adcp/types/domains" targets = ( "core/reporting_delivery_capabilities.py", "bundled/protocol/get_adcp_capabilities_response.py", diff --git a/tests/test_reporting_interop_corpus.py b/tests/test_reporting_interop_corpus.py index d91de866d..1eaaa64a7 100644 --- a/tests/test_reporting_interop_corpus.py +++ b/tests/test_reporting_interop_corpus.py @@ -1161,9 +1161,7 @@ class FakePool { } process.stdout.write('managed-resource-phase-fakes-complete\n'); })().catch(error => { console.error(error); process.exitCode = 1; }); - """.replace( - "__MANAGED__", json.dumps(str(DATA / "ts_managed_reporting_server.cjs")) - ) + """.replace("__MANAGED__", json.dumps(str(DATA / "ts_managed_reporting_server.cjs"))) completed = subprocess.run( ["node", "-e", script], check=True, diff --git a/tests/test_reporting_reconciliation.py b/tests/test_reporting_reconciliation.py index ba2cf47f7..d5429ae9f 100644 --- a/tests/test_reporting_reconciliation.py +++ b/tests/test_reporting_reconciliation.py @@ -43,20 +43,20 @@ ReportingRevision, ) from adcp.types.core import TaskResult, TaskStatus -from adcp.types.generated_poc.core.reporting_canonical_content_digest import ( +from adcp.types.domains.core.reporting_canonical_content_digest import ( ReportingCanonicalContentDigest, ) -from adcp.types.generated_poc.core.reporting_control_total import ReportingControlTotal -from adcp.types.generated_poc.media_buy.get_reporting_status_request import ( +from adcp.types.domains.core.reporting_control_total import ReportingControlTotal +from adcp.types.domains.media_buy.get_reporting_status_request import ( GetReportingStatusRequest, ) -from adcp.types.generated_poc.media_buy.get_reporting_status_response import ( +from adcp.types.domains.media_buy.get_reporting_status_response import ( GetReportingStatusResponse, ) -from adcp.types.generated_poc.media_buy.sync_reporting_receipts_request import ( +from adcp.types.domains.media_buy.sync_reporting_receipts_request import ( SyncReportingReceiptsRequest, ) -from adcp.types.generated_poc.media_buy.sync_reporting_receipts_response import ( +from adcp.types.domains.media_buy.sync_reporting_receipts_response import ( SyncReportingReceiptsResponse, ) diff --git a/tests/test_reporting_selector_generation.py b/tests/test_reporting_selector_generation.py index 11077bf12..dd188da21 100644 --- a/tests/test_reporting_selector_generation.py +++ b/tests/test_reporting_selector_generation.py @@ -12,13 +12,13 @@ from scripts import post_generate_fixes -ROOT = Path(__file__).parents[1] / "src/adcp/types/generated_poc" +ROOT = Path(__file__).parents[1] / "src/adcp/types/domains" SOURCES = ( - ("core/reporting_delivery_config.py", "Scope", "adcp.types.generated_poc.core"), + ("core/reporting_delivery_config.py", "Scope", "adcp.types.domains.core"), ( "media_buy/get_media_buy_delivery_request.py", "GetMediaBuyDeliveryRequest", - "adcp.types.generated_poc.media_buy", + "adcp.types.domains.media_buy", ), ) METHODS = { diff --git a/tests/test_rootmodel_proxy.py b/tests/test_rootmodel_proxy.py index 12f1ac482..cb38c9417 100644 --- a/tests/test_rootmodel_proxy.py +++ b/tests/test_rootmodel_proxy.py @@ -127,7 +127,7 @@ def test_retained_rootmodel_unions_have_getattr(): shapes carry ``asset_type='vast'``, so the wrapper is the one choice that tag maps to. """ - from adcp.types.generated_poc.core.assets.asset_union import VastAsset, VastAsset1 + from adcp.types.domains.core.assets.asset_union import VastAsset, VastAsset1 inner = VastAsset1( asset_type="vast", delivery_type="url", asset_id="a-1", url="https://cdn.test/v.xml" diff --git a/tests/test_scalar_roots.py b/tests/test_scalar_roots.py index 50069cb0f..1e1da4003 100644 --- a/tests/test_scalar_roots.py +++ b/tests/test_scalar_roots.py @@ -30,7 +30,7 @@ from adcp.validation.version import resolve_bundle_key _REPO_ROOT = Path(__file__).resolve().parent.parent -_GENERATED_DIR = _REPO_ROOT / "src" / "adcp" / "types" / "generated_poc" +_GENERATED_DIR = _REPO_ROOT / "src" / "adcp" / "types" / "domains" _BUNDLE_KEY = resolve_bundle_key((_REPO_ROOT / "src" / "adcp" / "ADCP_VERSION").read_text().strip()) _SCHEMA_DIR = _REPO_ROOT / "schemas" / "cache" / _BUNDLE_KEY @@ -100,9 +100,9 @@ def test_every_scalar_root_has_exactly_one_class_behind_it() -> None: The trap flagged in #1277 is fixing this in ``aliases.py`` instead of in the generator: generated modules import these names directly from - ``generated_poc``, so a redefinition one layer up leaves two classes with + the generated tree, so a redefinition one layer up leaves two classes with one name and `isinstance` fails across the seam. The rewrite happens in - ``generated_poc`` itself, and this holds the line. + the generated tree itself, and this holds the line. """ import importlib @@ -116,8 +116,8 @@ def test_every_scalar_root_has_exactly_one_class_behind_it() -> None: failures: list[str] = [] for name, cls in sorted(roots.items()): - if not cls.__module__.startswith("adcp.types.generated_poc."): - failures.append(f"{name}: defined in {cls.__module__}, not under generated_poc") + if not cls.__module__.startswith("adcp.types.domains."): + failures.append(f"{name}: defined in {cls.__module__}, not under adcp.types.domains") continue owner = importlib.import_module(cls.__module__) if getattr(owner, cls.__name__, None) is not cls: diff --git a/tests/test_schema_validation_server.py b/tests/test_schema_validation_server.py index 23b8b839e..b78c8d5d1 100644 --- a/tests/test_schema_validation_server.py +++ b/tests/test_schema_validation_server.py @@ -16,7 +16,7 @@ from adcp.server.base import ADCPHandler, ToolContext from adcp.server.mcp_tools import create_tool_caller from adcp.server.responses import media_buy_response, products_response -from adcp.types.generated_poc.enums.media_buy_status import MediaBuyStatus +from adcp.types.domains.enums.media_buy_status import MediaBuyStatus from adcp.validation import ValidationHookConfig diff --git a/tests/test_schemas_version_pin.py b/tests/test_schemas_version_pin.py index a2802c501..b2931708e 100644 --- a/tests/test_schemas_version_pin.py +++ b/tests/test_schemas_version_pin.py @@ -81,7 +81,7 @@ def test_adcp_version_matches_schemas_cache() -> None: (or vice versa). Fix: run ``make regenerate-schemas`` at the repo root, then commit - the updated ``schemas/cache/`` + ``src/adcp/types/generated_poc/``. + the updated ``schemas/cache/`` + ``src/adcp/types/domains/``. """ pinned = _ADCP_VERSION_FILE.read_text().strip() cache_data = json.loads(_CACHE_INDEX.read_text()) diff --git a/tests/test_server_framework.py b/tests/test_server_framework.py index df6332592..828ce6622 100644 --- a/tests/test_server_framework.py +++ b/tests/test_server_framework.py @@ -341,7 +341,7 @@ async def handle_create_property_list(self, request, context=None): return CreatePropertyListResponse.model_construct() async def handle_get_property_list(self, request, context=None): - from adcp.types.generated_poc.property.property_list import PropertyList + from adcp.types.domains.property.property_list import PropertyList return GetPropertyListResponse( list=PropertyList(list_id="pl-1", name="test"), diff --git a/tests/test_targeting_overlay_compat.py b/tests/test_targeting_overlay_compat.py index 4bfc27cf8..2e6e668c2 100644 --- a/tests/test_targeting_overlay_compat.py +++ b/tests/test_targeting_overlay_compat.py @@ -29,15 +29,15 @@ TargetingOverlay, UpdateMediaBuyRequest, ) -from adcp.types.generated_poc.core.targeting_input import TargetingOverlayInput as GeneratedInput -from adcp.types.generated_poc.media_buy.package_control import PackageControl -from adcp.types.generated_poc.media_buy.package_request import ( +from adcp.types.domains.core.targeting_input import TargetingOverlayInput as GeneratedInput +from adcp.types.domains.media_buy.package_control import PackageControl +from adcp.types.domains.media_buy.package_request import ( PackageRequest as GeneratedPackageRequest, ) -from adcp.types.generated_poc.media_buy.package_update import ( +from adcp.types.domains.media_buy.package_update import ( PackageUpdate as GeneratedPackageUpdate, ) -from adcp.types.generated_poc.media_buy.product_purchase_input import ProductPurchaseInput +from adcp.types.domains.media_buy.product_purchase_input import ProductPurchaseInput from adcp.validation import validate_request _NEW_PACKAGE = {"product_id": "product-1", "pricing_option_id": "price-1", "budget": 1000} diff --git a/tests/test_targeting_overlay_errors.py b/tests/test_targeting_overlay_errors.py index 39083df8b..13825c562 100644 --- a/tests/test_targeting_overlay_errors.py +++ b/tests/test_targeting_overlay_errors.py @@ -24,8 +24,8 @@ CreateMediaBuyRequest, UpdateMediaBuyRequest, ) +from adcp.types.domains.core.targeting_input import TargetingOverlayInput from adcp.types.error_narrowing import narrow_union_errors -from adcp.types.generated_poc.core.targeting_input import TargetingOverlayInput _SECRET = "never-echo-this-buyer-value" _PATHS = [ diff --git a/tests/test_targeting_overlay_schema.py b/tests/test_targeting_overlay_schema.py index 8c1c56522..d3495a3b9 100644 --- a/tests/test_targeting_overlay_schema.py +++ b/tests/test_targeting_overlay_schema.py @@ -98,9 +98,9 @@ def test_advertised_targeting_fields_carry_the_description_the_schema_declares() overlay = next(iter(dict(_targeting_nodes(_inline_refs(advertised))).values())) fields = next(arm for arm in overlay["anyOf"] if "properties" in arm)["properties"] - assert set(fields) == set(TargetingOverlayInput.model_json_schema()["properties"]), ( - "advertised fields drifted from the model" - ) + assert set(fields) == set( + TargetingOverlayInput.model_json_schema()["properties"] + ), "advertised fields drifted from the model" assert sorted(name for name, field in fields.items() if "description" not in field) == [ "age_restriction", "collection_list", diff --git a/tests/test_task_message_lattice.py b/tests/test_task_message_lattice.py new file mode 100644 index 000000000..292b77e4c --- /dev/null +++ b/tests/test_task_message_lattice.py @@ -0,0 +1,561 @@ +"""The request/response lattice: every registered task message is a typed AdCP message. + +A caller that holds a request should be able to resolve its account, decide at-most-once, +echo its context and negotiate version before knowing which tool it is; a caller that +holds a response should be able to route on task state and split envelope from payload the +same way. That needs the task messages to share a TYPE, which is what ``AdcpRequest`` and +``AdcpResponse`` are -- and ``issubclass(model, AdcpRequest)`` is then the +registration-time proof that a model is spec-derived rather than a hand-written parallel +(a field test passes for a forged model; descent does not). + +The ratchet below is what keeps that true across a schema bump. Each envelope residue is +pinned to the exact set of schemas measured to lack the ancestry, with the upstream issue +that owns it, and the assertions are equality: a schema that regains the ancestry fails +until its entry is REMOVED, and a schema that loses it fails with its own name. Residues +shrink; they never grow. +""" + +from __future__ import annotations + +import importlib +import json +from pathlib import Path +from typing import Any, cast + +import pytest +from pydantic import BaseModel + +from adcp.types.base import AdcpRequest, AdcpResponse, _AdcpMessage +from adcp.types.domains.core.protocol_envelope import ProtocolEnvelope +from adcp.types.domains.core.version_envelope import AdcpVersionEnvelope +from scripts.generate_types import normalize_version_envelope_composition +from scripts.post_generate_fixes import OUTPUT_DIR, SCHEMA_DIR +from scripts.task_message_lattice import task_message_roots + +# ``creative/validate-input-request.json`` declares no version fields at all, so there is +# nothing for the normalization in ``generate_types`` to lift into a composition -- the +# SDK will not invent a field the schema does not have. Upstream ask: declare the envelope +# like every sibling task does (one ``allOf`` line). +_REQUEST_VERSION_RESIDUE = frozenset({"creative/validate-input-request.json"}) + +# The five compact media-buy responses compose ``media-buy/media-buy-commitment-response`` +# instead, which is itself envelope-free. The two proposals responses declare the version +# via pointer refs, and the normalization in ``generate_types`` deliberately REFUSES them: +# their submitted arm is a bare ``$ref`` to ``core/compact-task-submitted.json``, so moving +# the stubs into a root ``allOf`` would delete the field from that arm rather than relocate +# it (see ``_has_bare_ref_root_arm``). Normalizing them would not have changed this residue +# anyway -- the submitted arm gains no ancestry either way, so the all-arms answer is 70/77 +# with or without. Upstream: adcp#7892 plus the commitment / compact-task-submitted +# components, which is upstream ask four. +_RESPONSE_VERSION_RESIDUE = frozenset( + { + "media-buy/accept-proposal-response.json", + "media-buy/buy-products-response.json", + "media-buy/control-media-buy-response.json", + "media-buy/decline-proposals-response.json", + "media-buy/list-products-response.json", + "media-buy/refine-proposals-response.json", + "media-buy/request-proposals-response.json", + } +) + +# These ten declare NO protocol-envelope fields of their own, against the envelope's own +# "The ``status`` field is REQUIRED on every task response envelope". There is nothing +# equivalent to lift, so unlike the version envelope this one cannot be normalized by +# equivalence: composing it would change what validates. Upstream's to fix. +_RESPONSE_PROTOCOL_RESIDUE = frozenset( + { + "governance/check-governance-response.json", + "governance/report-plan-adjustment-response.json", + "governance/report-plan-outcome-response.json", + "media-buy/accept-proposal-response.json", + "media-buy/buy-products-response.json", + "media-buy/control-media-buy-response.json", + "media-buy/decline-proposals-response.json", + "media-buy/list-products-response.json", + "media-buy/refine-proposals-response.json", + "media-buy/request-proposals-response.json", + } +) + +#: (task, kind, schema path, the root classes the message validates into) +_TaskMessage = tuple[str, str, str, tuple[type[BaseModel], ...]] + +_VERSION_ENVELOPE_REF = "https://adcontextprotocol.org/schemas/3.2.1/core/version-envelope.json" + + +def _module_name(path: Path) -> str: + return "adcp.types.domains." + ".".join(path.relative_to(OUTPUT_DIR).with_suffix("").parts) + + +def _task_message_classes() -> list[_TaskMessage]: + """(task, kind, schema path, root classes) for all 77 registered tasks, both directions. + + Resolved exactly the way the marker-insertion pass resolves it -- same registry, same + provenance header, same root-class rule -- so this grades the generated tree rather + than re-deriving a second answer that could agree with the pass while both are wrong. + """ + rows: list[_TaskMessage] = [] + for task, kind, schema_rel, module, names in task_message_roots(SCHEMA_DIR, OUTPUT_DIR): + imported = importlib.import_module(_module_name(module)) + rows.append((task, kind, schema_rel.as_posix(), tuple(getattr(imported, n) for n in names))) + return rows + + +@pytest.fixture(scope="module") +def task_messages() -> list[_TaskMessage]: + return _task_message_classes() + + +def _residue(rows: list[_TaskMessage], kind: str, ancestor: type[BaseModel]) -> frozenset[str]: + """The schemas whose root classes do not ALL descend from ``ancestor``. + + All arms, not any arm: a union whose submitted arm lacks the ancestry cannot answer + the envelope question for every value the task can return, so counting it as composed + would overstate what a caller may rely on by 2 schemas. + """ + return frozenset( + schema + for _, row_kind, schema, classes in rows + if row_kind == kind and not all(issubclass(c, ancestor) for c in classes) + ) + + +def test_registry_covers_every_task_in_both_directions(task_messages: list[_TaskMessage]) -> None: + """77 tasks, each with one request and one response, all resolving to real classes.""" + assert len(task_messages) == 154, "the pinned bundle registers 77 tasks" + assert sum(1 for _, kind, _, _ in task_messages if kind == "request") == 77 + assert all(classes for *_, classes in task_messages) + + +def test_every_task_request_is_an_adcp_request(task_messages: list[_TaskMessage]) -> None: + missing = sorted( + schema + for _, kind, schema, classes in task_messages + if kind == "request" and not all(issubclass(c, AdcpRequest) for c in classes) + ) + assert missing == [], "task requests not marked AdcpRequest: " + ", ".join(missing) + + +def test_every_task_response_is_an_adcp_response(task_messages: list[_TaskMessage]) -> None: + missing = sorted( + schema + for _, kind, schema, classes in task_messages + if kind == "response" and not all(issubclass(c, AdcpResponse) for c in classes) + ) + assert missing == [], "task responses not marked AdcpResponse: " + ", ".join(missing) + + +def test_a_request_is_never_also_a_response(task_messages: list[_TaskMessage]) -> None: + """The two markers partition the task messages; nothing carries both.""" + confused = sorted( + {c.__name__ for *_, classes in task_messages for c in classes} + & { + c.__name__ + for _, kind, _, classes in task_messages + for c in classes + if issubclass(c, AdcpRequest) and issubclass(c, AdcpResponse) + } + ) + assert confused == [] + + +def test_markers_are_plain_classes_with_no_fields() -> None: + """A field on the marker would invent a spec field on the one tool that declares none. + + ``ValidateInputRequest`` declares no version fields; an ``adcp_version`` inherited from + the marker would put one on its wire type. So the marker is not a model: it carries + accessors that read the instance dict, and answers ``None`` for a tool that declares + nothing. + """ + for marker in (AdcpRequest, AdcpResponse): + assert not issubclass(marker, BaseModel), f"{marker.__name__} must not be a model" + assert "model_fields" not in vars(marker) + assert not hasattr(marker, "__pydantic_fields__") + + +def test_task_request_version_envelope_residue_is_pinned(task_messages: list[_TaskMessage]) -> None: + """76 of 77 task requests descend from AdcpVersionEnvelope; the residue is one schema.""" + residue = _residue(task_messages, "request", AdcpVersionEnvelope) + assert residue == _REQUEST_VERSION_RESIDUE, ( + "version-envelope residue moved. Newly missing: " + f"{sorted(residue - _REQUEST_VERSION_RESIDUE)}; now composing (remove the pin): " + f"{sorted(_REQUEST_VERSION_RESIDUE - residue)}" + ) + assert len(residue) == 1 + + +def test_task_response_version_envelope_residue_is_pinned( + task_messages: list[_TaskMessage], +) -> None: + residue = _residue(task_messages, "response", AdcpVersionEnvelope) + assert residue == _RESPONSE_VERSION_RESIDUE, ( + "version-envelope residue moved. Newly missing: " + f"{sorted(residue - _RESPONSE_VERSION_RESIDUE)}; now composing (remove the pin): " + f"{sorted(_RESPONSE_VERSION_RESIDUE - residue)}" + ) + + +def test_task_response_protocol_envelope_residue_is_pinned( + task_messages: list[_TaskMessage], +) -> None: + residue = _residue(task_messages, "response", ProtocolEnvelope) + assert residue == _RESPONSE_PROTOCOL_RESIDUE, ( + "protocol-envelope residue moved. Newly missing: " + f"{sorted(residue - _RESPONSE_PROTOCOL_RESIDUE)}; now composing (remove the pin): " + f"{sorted(_RESPONSE_PROTOCOL_RESIDUE - residue)}" + ) + + +def test_task_requests_do_not_compose_the_protocol_envelope( + task_messages: list[_TaskMessage], +) -> None: + """The protocol envelope wraps responses. A request carrying it would be a leak.""" + leaked = sorted( + schema + for _, kind, schema, classes in task_messages + if kind == "request" and any(issubclass(c, ProtocolEnvelope) for c in classes) + ) + assert leaked == [] + + +def test_field_classifiers_partition_the_declared_fields(task_messages: list[_TaskMessage]) -> None: + """version / protocol / payload are disjoint and together are exactly ``model_fields``. + + Which is what makes "split the envelope from the payload" one expression instead of a + hand list of envelope field names per transport edge -- the protocol envelope grew + ``governance_context`` and ``replayed`` inside one minor, and every hand list is a list + someone forgot to extend. + """ + for task, kind, schema, classes in task_messages: + for cls in classes: + # A task message is a pydantic model AND a marker. Python has no intersection + # type, so the cast states the half this block needs -- the same answer + # BuyerRequest's own validator gives to the same problem. + message = cast("type[_AdcpMessage]", cls) + version = message.version_fields() + protocol = message.protocol_fields() + payload = message.payload_fields() + where = f"{task} {kind} ({schema}) {cls.__name__}" + assert not version & payload, where + assert not protocol & payload, where + assert version | protocol | payload == frozenset(cls.model_fields), where + + +def test_a_class_that_inlines_the_version_fields_reports_no_version_stratum() -> None: + """The residue degrades honestly: empty classifier, working accessor. + + ``validate-input`` declares no version fields, so there is nothing to classify. The + point of the pair is the general case: a schema that INLINES the fields without + composing shares the names but not the stratum, and ``version_fields()`` must not + report a composition that is not there -- while ``get_adcp_version()`` still answers, + because it reads the instance dict rather than the MRO. + """ + from adcp.types.domains.creative.validate_input_request import ValidateInputRequest + + assert issubclass(ValidateInputRequest, AdcpRequest) + assert not issubclass(ValidateInputRequest, AdcpVersionEnvelope) + assert ValidateInputRequest.version_fields() == frozenset() + assert ValidateInputRequest.payload_fields() == frozenset(ValidateInputRequest.model_fields) + + +def test_request_accessors_answer_for_a_tool_that_declares_the_fields() -> None: + from adcp.types.domains.media_buy.buy_products_request import BuyProductsRequest + + request = BuyProductsRequest.model_validate( + { + "adcp_version": "3.2", + "adcp_major_version": 3, + "account": {"account_id": "acct-1"}, + "brand": {"domain": "brand.example"}, + "idempotency_key": "0123456789abcdef", + "feed_version": "feed-1", + "purchases": [{"product_id": "p1", "pricing_option_id": "po1"}], + "start_time": "asap", + "end_time": "2026-12-01T00:00:00Z", + "context": {"trace": "t-1"}, + } + ) + assert request.get_adcp_version() == "3.2" + assert request.get_adcp_major_version() == 3 + assert request.get_idempotency_key() == "0123456789abcdef" + assert request.get_account() is not None + assert request.get_context() is not None + assert request.get_push_notification_config() is None + + +def test_request_accessors_answer_none_for_fields_a_tool_does_not_declare() -> None: + """The whole reason the accessors are on a field-less marker rather than a model. + + ``validate-input`` declares an ``account`` and no version pins, no idempotency key and + no webhook config. A marker carrying fields would have given it all four; the accessors + answer from what the schema actually declared. + """ + from adcp.types.domains.creative.validate_input_request import ValidateInputRequest + + # The manifest carries a ``format_kind``: ``core/creative-manifest.json`` declares a + # root required group of ``format_id | format_kind``, which + # ``enforce_root_required_groups`` now enforces on the generated class. The accessors + # under test are unaffected by which arm satisfies it. + request = ValidateInputRequest.model_validate( + { + "account": {"account_id": "acct-1"}, + "manifest": {"assets": {}, "format_kind": "image"}, + } + ) + assert request.get_account() is not None + assert request.get_adcp_version() is None + assert request.get_adcp_major_version() is None + assert request.get_idempotency_key() is None + assert request.get_push_notification_config() is None + assert request.get_context() is None + + +def test_response_accessors_read_the_protocol_stratum() -> None: + from adcp.types.domains.media_buy.create_media_buy_response import ( + CreateMediaBuyResponse3, + ) + + submitted = CreateMediaBuyResponse3.model_validate( + {"status": "submitted", "task_id": "task-1", "message": "queued"} + ) + assert submitted.get_status() == "submitted" + assert submitted.get_task_id() == "task-1" + assert submitted.get_message() == "queued" + assert submitted.get_adcp_error() is None + assert submitted.get_replayed() is False + + +def _normalize(schema: dict[str, Any], rel: str) -> dict[str, Any]: + return normalize_version_envelope_composition(json.loads(json.dumps(schema)), Path(rel)) + + +def test_version_normalization_rewrites_a_pointer_ref_declaration() -> None: + """Predicate A: the authored pointer form gains a real root composition.""" + schema = { + "type": "object", + "properties": { + "adcp_version": {"$ref": f"{_VERSION_ENVELOPE_REF}#/properties/adcp_version"}, + "brief": {"type": "string"}, + }, + } + result = _normalize(schema, "media-buy/example-request.json") + assert result["allOf"] == [{"$ref": _VERSION_ENVELOPE_REF}] + assert "adcp_version" not in result["properties"] + assert result["properties"]["brief"] == {"type": "string"} + + +def test_version_normalization_is_a_no_op_once_upstream_composes() -> None: + """adcp#7892 landing must make this pass inert, in either form it can land in. + + A clean root ``allOf`` matches neither predicate, so the schema is returned untouched + rather than gaining a second ``allOf`` arm. The stub-retaining form -- which draft-07 + may force on #7892 -- is also untouched, and that shape is not hypothetical: it ships + today in ``media-buy/sync-reporting-receipts-request.json``, which already renders as + ``SyncReportingReceiptsRequest(AdcpVersionEnvelope)``. + """ + composed = { + "type": "object", + "allOf": [{"$ref": _VERSION_ENVELOPE_REF}], + "properties": {"brief": {"type": "string"}}, + } + assert _normalize(composed, "media-buy/example-request.json") == composed + + stub_retaining = { + "type": "object", + "allOf": [{"$ref": _VERSION_ENVELOPE_REF}], + "properties": { + "adcp_version": {"$ref": f"{_VERSION_ENVELOPE_REF}#/properties/adcp_version"}, + "brief": {"type": "string"}, + }, + } + assert _normalize(stub_retaining, "media-buy/example-request.json") == stub_retaining + + from adcp.types.domains.media_buy.sync_reporting_receipts_request import ( + SyncReportingReceiptsRequest, + ) + + assert issubclass(SyncReportingReceiptsRequest, AdcpVersionEnvelope) + + +def test_version_normalization_is_idempotent() -> None: + """Applying the pass to its own output changes nothing.""" + schema = { + "type": "object", + "properties": { + "adcp_version": {"$ref": f"{_VERSION_ENVELOPE_REF}#/properties/adcp_version"} + }, + } + once = _normalize(schema, "media-buy/example-request.json") + assert _normalize(once, "media-buy/example-request.json") == once + + +def test_version_normalization_refuses_a_schema_whose_arm_is_a_bare_ref() -> None: + """Relocating the stubs must never DELETE the field from a union arm. + + datamodel-code-generator merges a root ``properties`` into every arm it builds, but a + root ``allOf`` ``$ref`` becomes a base only of the arm built FROM the root object. An + arm that is a bare ``$ref`` elsewhere is generated from the referenced schema, so the + composition never reaches it and dropping the stub strips the field outright. + + Measured when the rewrite was applied anyway: ``RefineProposalsResponse2`` went from 10 + fields to 9 and ``RequestProposalsResponse4`` from 16 to 15, both losing + ``adcp_version`` -- and both carry ``extra: forbid``, so a seller echoing the version on + the submitted arm would have been REJECTED where it previously validated. The two + schemas are refused instead, and the all-arms ancestry is 70/77 either way because that + submitted arm gains no ancestry from the rewrite. + """ + with_ref_arm = { + "type": "object", + "anyOf": [ + {"required": ["results"]}, + { + "$ref": "https://adcontextprotocol.org/schemas/3.2.1/core/compact-task-submitted.json" + }, + ], + "properties": { + "adcp_version": {"$ref": f"{_VERSION_ENVELOPE_REF}#/properties/adcp_version"} + }, + } + assert _normalize(with_ref_arm, "media-buy/example-response.json") == with_ref_arm + + # The same schema with inline arms IS normalized: nothing is generated from elsewhere, + # so every arm inherits the composition. + inline_arms = { + "type": "object", + "anyOf": [{"required": ["results"]}, {"required": ["task_id"]}], + "properties": { + "adcp_version": {"$ref": f"{_VERSION_ENVELOPE_REF}#/properties/adcp_version"} + }, + } + assert _normalize(inline_arms, "media-buy/example-response.json") != inline_arms + + +def test_the_two_pointer_ref_responses_keep_the_field_on_every_arm() -> None: + """The live consequence of the refusal above, on the generated tree. + + Both arms of each proposals response still declare ``adcp_version``. If the refusal is + removed, the submitted arm loses it and this goes red. + """ + from adcp.types.domains.media_buy.refine_proposals_response import ( + RefineProposalsResponse1, + RefineProposalsResponse2, + ) + from adcp.types.domains.media_buy.request_proposals_response import ( + RequestProposalsResponse4, + ) + + for arm in (RefineProposalsResponse1, RefineProposalsResponse2, RequestProposalsResponse4): + assert "adcp_version" in arm.model_fields, arm.__name__ + + +def test_version_normalization_refuses_a_declaration_it_cannot_prove_equivalent() -> None: + """A third convention is left alone, so it surfaces as residue with its name attached. + + ``manifest.schema.json`` declares ``adcp_version`` as a full semver, and requires it; + ``core/verification-token-claims.json`` declares its own shape. Lifting either into the + envelope would change what validates. + """ + foreign = { + "type": "object", + "required": ["adcp_version"], + "properties": {"adcp_version": {"type": "string", "pattern": r"^\d+\.\d+\.\d+$"}}, + } + assert _normalize(foreign, "manifest.schema.json") == foreign + + equivalent_but_required = { + "type": "object", + "required": ["adcp_version"], + "properties": { + "adcp_version": {"$ref": f"{_VERSION_ENVELOPE_REF}#/properties/adcp_version"} + }, + } + assert ( + _normalize(equivalent_but_required, "media-buy/example-request.json") + == equivalent_but_required + ) + + +def test_descent_refuses_a_forged_model_that_merely_declares_the_fields() -> None: + """The forgeability argument the markers exist to keep true (ARCH-HIERARCHY §1). + + A field-presence test accepts any hand-written model that declares ``adcp_version`` and + ``adcp_major_version``. That is not hypothetical: the Prebid Sales Agent once carried + ``CompleteTaskRequest`` and ``CompleteTaskRequestLocal``, neither a subclass of the + other, both parallel to the SDK -- and its registration refusal + (``src/core/main.py:286``) tests descent precisely because a field test would have + passed them both. + + Both markers must refuse that model. They do, and this test is what keeps them + refusing: a marker that grew the two fields would make the forgery pass. + """ + + class ForgedByFields(BaseModel): + adcp_version: str | None = None + adcp_major_version: int | None = None + + assert {"adcp_version", "adcp_major_version"} <= set(ForgedByFields.model_fields) + assert not issubclass(ForgedByFields, AdcpRequest) + assert not issubclass(ForgedByFields, AdcpVersionEnvelope) + + +def test_the_version_envelope_has_a_public_name() -> None: + """The adoption gate's guard conjunction must be spellable without a deep import. + + ``ProtocolEnvelope`` was already public and ``AdcpVersionEnvelope`` was not, so a + consumer writing + ``issubclass(model, AdcpRequest) and issubclass(model, AdcpVersionEnvelope)`` + had to reach into ``adcp.types.domains.core.version_envelope`` — which is + exactly the import the one-surface work exists to retire, and which the Prebid Sales + Agent carries today at ``src/core/main.py:230``. Re-export, never a copy: the public + name must be the SAME object, or the conjunction would refuse every real request. + """ + import adcp.types + import adcp.types.protocol + + assert "AdcpVersionEnvelope" in adcp.types.__all__ + assert "AdcpVersionEnvelope" in adcp.types.protocol.__all__ + assert adcp.types.AdcpVersionEnvelope is AdcpVersionEnvelope + assert adcp.types.protocol.AdcpVersionEnvelope is AdcpVersionEnvelope + # Its already-public sibling, as the parity this mirrors. + assert "ProtocolEnvelope" in adcp.types.__all__ + + # The conjunction, written in the public spelling only, on a real task request. + from adcp.types import GetProductsRequest + from adcp.types.base import AdcpRequest as PublicAdcpRequest + + assert issubclass(GetProductsRequest, PublicAdcpRequest) + assert issubclass(GetProductsRequest, adcp.types.AdcpVersionEnvelope) + + +def test_the_two_descent_predicates_refuse_different_forgeries() -> None: + """Why a consumer's guard wants BOTH, not one in place of the other. + + ``AdcpRequest`` is field-less on purpose, so inheriting it supplies nothing and a forger + can inherit it and invent fields -- it answers "someone put this class in the request + position", not "this class carries the spec's version contract". + ``AdcpVersionEnvelope`` answers the second and not the first: inheriting it is what + supplies the two fields, and a model that does so is still not any task's request. + + Measured, so the asymmetry is recorded rather than assumed. Until the generated task + table (ARCH-HIERARCHY §7.6) exists, the conjunction is the strongest available form of + "is a registered task message", and a consumer swapping one predicate for the other + loses a refusal. + """ + + class ForgedByMarker(AdcpRequest, BaseModel): + adcp_version: str | None = None + whatever: str | None = None + + class ForgedByEnvelope(AdcpVersionEnvelope): + whatever: str | None = None + + assert issubclass(ForgedByMarker, AdcpRequest) + assert not issubclass(ForgedByMarker, AdcpVersionEnvelope) + + assert issubclass(ForgedByEnvelope, AdcpVersionEnvelope) + assert not issubclass(ForgedByEnvelope, AdcpRequest) + + # The conjunction refuses both; neither predicate alone does. + for forged in (ForgedByMarker, ForgedByEnvelope): + assert not (issubclass(forged, AdcpRequest) and issubclass(forged, AdcpVersionEnvelope)) diff --git a/tests/test_triage_authorization.py b/tests/test_triage_authorization.py index 8df26f343..ab4244ca0 100644 --- a/tests/test_triage_authorization.py +++ b/tests/test_triage_authorization.py @@ -26,8 +26,7 @@ def authorize(tmp_path: Path): if step.get("name") == "Authorize mutation-capable trigger" ) stub = tmp_path / "gh" - stub.write_text( - """#!/usr/bin/env bash + stub.write_text("""#!/usr/bin/env bash set -euo pipefail printf '%s\\n' "$*" >> "$API_CALLS" [[ "$GH_TOKEN" == fixture-token ]] @@ -40,8 +39,7 @@ def authorize(tmp_path: Path): exit 1 fi printf '%s' "$API_RESPONSE" | jq -r "$4" -""" - ) +""") stub.chmod(0o755) calls = tmp_path / "calls" diff --git a/tests/test_trusted_match_validation.py b/tests/test_trusted_match_validation.py index fa94e6a0a..aaebb2304 100644 --- a/tests/test_trusted_match_validation.py +++ b/tests/test_trusted_match_validation.py @@ -1,16 +1,16 @@ import pytest from pydantic import TypeAdapter, ValidationError -from adcp.types.generated_poc.trusted_match.identity_match_response import ( +from adcp.types.domains.trusted_match.identity_match_response import ( IdentityMatchResponse, ) -from adcp.types.generated_poc.trusted_match.provider_identity_match_response import ( +from adcp.types.domains.trusted_match.provider_identity_match_response import ( IdentityMatchResponseProviderRouter, ) -from adcp.types.generated_poc.trusted_match.provider_registration import ( +from adcp.types.domains.trusted_match.provider_registration import ( TmpProviderRegistration, ) -from adcp.types.generated_poc.trusted_match.publisher_tmpx_config import ( +from adcp.types.domains.trusted_match.publisher_tmpx_config import ( PublisherTmpxMacroMapping, ) diff --git a/tests/test_type_aliases.py b/tests/test_type_aliases.py index 6c744c542..7fe3f5a04 100644 --- a/tests/test_type_aliases.py +++ b/tests/test_type_aliases.py @@ -227,7 +227,7 @@ def test_discriminated_union_aliases_point_to_correct_types(): PreviewRender2, PreviewRender3, ) - from adcp.types.generated_poc.core.assets.asset_union import ( + from adcp.types.domains.core.assets.asset_union import ( DaastAsset1, DaastAsset2, VastAsset1, @@ -286,8 +286,13 @@ def test_stable_package_export_is_full_package(): """Test that types/__init__.py exports the Package as Package.""" from adcp.types import Package as StablePackage - # Stable Package should be the full package - stable_fields = set(StablePackage.__annotations__.keys()) + # Stable Package should be the full package. Read ``model_fields``, not + # ``__annotations__``: a class's ``__annotations__`` holds only its OWN + # annotations, so it answered this question only while the canonical + # Package was a copy that re-declared every inherited field. It is a real + # subclass of the generated wire model now, declares nothing of its own, + # and ``model_fields`` is the pydantic truth that includes what it inherits. + stable_fields = set(StablePackage.model_fields) assert ( len(stable_fields) >= 13 ), f"Stable Package should have at least 13 fields, got {len(stable_fields)}" @@ -441,8 +446,8 @@ def test_property_id_and_tag_are_constrained_strings(): from pydantic import RootModel, ValidationError # Import directly from the core modules to avoid collision - from adcp.types.generated_poc.core.property_id import PropertyId as CorePropertyId - from adcp.types.generated_poc.core.property_tag import PropertyTag + from adcp.types.domains.core.property_id import PropertyId as CorePropertyId + from adcp.types.domains.core.property_tag import PropertyTag # Create valid PropertyId and PropertyTag prop_id = CorePropertyId("my_property_id") diff --git a/tests/test_type_coercion.py b/tests/test_type_coercion.py index 8ce4644d9..8b2e79c39 100644 --- a/tests/test_type_coercion.py +++ b/tests/test_type_coercion.py @@ -16,15 +16,15 @@ ListCreativesRequest, PackageRequest, ) -from adcp.types.generated_poc.core.context import ContextObject -from adcp.types.generated_poc.core.ext import ExtensionObject -from adcp.types.generated_poc.creative.list_creative_formats_request import ( +from adcp.types.domains.core.context import ContextObject +from adcp.types.domains.core.ext import ExtensionObject +from adcp.types.domains.creative.list_creative_formats_request import ( ListCreativeFormatsRequestCreativeAgent, ) -from adcp.types.generated_poc.creative.list_creatives_request import Field1 as FieldModel -from adcp.types.generated_poc.creative.list_creatives_request import Sort -from adcp.types.generated_poc.enums.creative_sort_field import CreativeSortField -from adcp.types.generated_poc.enums.sort_direction import SortDirection +from adcp.types.domains.creative.list_creatives_request import Field1 as FieldModel +from adcp.types.domains.creative.list_creatives_request import Sort +from adcp.types.domains.enums.creative_sort_field import CreativeSortField +from adcp.types.domains.enums.sort_direction import SortDirection from adcp.types.legacy import LegacyListCreativeFormatsRequest as ListCreativeFormatsRequest from tests.conftest import validate_union @@ -591,7 +591,7 @@ def test_get_products_response_accepts_product_subclass(self): GetProductsResponse, Product, ) - from adcp.types.generated_poc.core.product import DeliveryMeasurement + from adcp.types.domains.core.product import DeliveryMeasurement class ExtendedProduct(Product): """Extended with internal tracking fields.""" @@ -643,7 +643,7 @@ def test_response_errors_accepts_error_subclass(self): # Import Error from core.error - the type used in ergonomic coercion # (adcp.types.Error exports a different Error from content_standards) - from adcp.types.generated_poc.core.error import Error as CoreError + from adcp.types.domains.core.error import Error as CoreError class ExtendedError(CoreError): """Extended with internal tracking fields.""" diff --git a/tests/test_update_media_buy_decompose.py b/tests/test_update_media_buy_decompose.py index 24351a114..cef78fcb8 100644 --- a/tests/test_update_media_buy_decompose.py +++ b/tests/test_update_media_buy_decompose.py @@ -294,7 +294,7 @@ def test_disallowed_update_media_buy_mutations_match_candidate_actions() -> None def test_allowed_action_helpers_accept_wire_available_actions() -> None: - from adcp.types.generated_poc.core.media_buy_available_action import ( + from adcp.types.domains.core.media_buy_available_action import ( MediaBuyAvailableAction, ) diff --git a/tests/test_verify_from_agent_url.py b/tests/test_verify_from_agent_url.py index 3cc20bf0d..6b5f8f469 100644 --- a/tests/test_verify_from_agent_url.py +++ b/tests/test_verify_from_agent_url.py @@ -194,9 +194,7 @@ def test_request_signature_code_covers_every_resolver_code() -> None: from adcp.signing.agent_resolver import AgentResolverErrorCode - assert set(get_args(AgentResolverErrorCode)) == { - row[0] for row in _RESOLVER_CODE_TO_SPEC_CODE - } + assert set(get_args(AgentResolverErrorCode)) == {row[0] for row in _RESOLVER_CODE_TO_SPEC_CODE} #: Every brand.json hop outcome and the code the table assigns it. ``jwks_origin_mismatch`` @@ -244,7 +242,7 @@ def test_request_signature_code_only_emits_codes_the_pinned_enum_defines() -> No ``request-signing-error-code`` enum, including the explicit ``signature_code`` values the brand.json hop attaches.""" from adcp.signing.agent_resolver import _brand_resolution_error - from adcp.types.generated_poc.enums.request_signing_error_code import ( + from adcp.types.domains.enums.request_signing_error_code import ( RequestSigningErrorCode, ) diff --git a/tests/test_webhooks_deliver.py b/tests/test_webhooks_deliver.py index 685f68bf1..78bedef7d 100644 --- a/tests/test_webhooks_deliver.py +++ b/tests/test_webhooks_deliver.py @@ -24,15 +24,15 @@ from a2a.types import TaskState # TaskState is the proto enum; still exported from adcp.signing import SSRFValidationError -from adcp.types.generated_poc.core.mcp_webhook_payload import McpWebhookPayload -from adcp.types.generated_poc.core.push_notification_config import ( +from adcp.types.domains.core.mcp_webhook_payload import McpWebhookPayload +from adcp.types.domains.core.push_notification_config import ( Authentication as PNAuthentication, ) -from adcp.types.generated_poc.core.push_notification_config import ( +from adcp.types.domains.core.push_notification_config import ( PushNotificationConfig, ) -from adcp.types.generated_poc.core.reporting_webhook import Authentication as RWAuth -from adcp.types.generated_poc.core.reporting_webhook import ( +from adcp.types.domains.core.reporting_webhook import Authentication as RWAuth +from adcp.types.domains.core.reporting_webhook import ( ReportingFrequency, ReportingWebhook, ) diff --git a/tests/test_webhooks_to_wire_dict.py b/tests/test_webhooks_to_wire_dict.py index 51383d1ea..b72399738 100644 --- a/tests/test_webhooks_to_wire_dict.py +++ b/tests/test_webhooks_to_wire_dict.py @@ -21,7 +21,7 @@ import pytest from adcp.types import GeneratedTaskStatus -from adcp.types.generated_poc.core.mcp_webhook_payload import McpWebhookPayload +from adcp.types.domains.core.mcp_webhook_payload import McpWebhookPayload from adcp.webhooks import ( create_a2a_webhook_payload, create_mcp_webhook_payload, diff --git a/tests/type_checks/creative_asset_binding.py b/tests/type_checks/creative_asset_binding.py index c20b81802..d10c0419a 100644 --- a/tests/type_checks/creative_asset_binding.py +++ b/tests/type_checks/creative_asset_binding.py @@ -1,16 +1,29 @@ -"""Public canonical model identity and closed format-kind types (#1141/#1241).""" +"""Public canonical model identity and the open ``format_kind`` type (#1141/#1241). + +``format_kind`` is a ``str`` at every reference and no model refuses a value, +because the schema requires a consumer to retain an unknown one and a pinned +SDK cannot tell a kind a seller invented from a kind defined after its pin. +Checking the vocabulary is the caller's, through +``adcp.types.is_canonical_format_kind``. See +:mod:`adcp.types.canonical_creative`'s module docstring and adcp#7929. + +One consequence is visible below: a field that had to be asserted ``Any`` +because no annotation could express a widening union now has a real static +type, since there is no widening left. +""" + +from collections.abc import Sequence from typing_extensions import assert_type from adcp.types import ( - CanonicalFormatKind, Creative, CreativeAsset, CreativeManifest, DeliveryCreative, GetCreativeDeliveryResponse, ) -from adcp.types.canonical_creative import CanonicalBoundaryModel +from adcp.types.canonical_creative import CanonicalBoundaryModel, _DeliveryCreativeVariant def accepts_canonical_class(model: type[CanonicalBoundaryModel]) -> None: @@ -27,27 +40,41 @@ def accepts_canonical_class(model: type[CanonicalBoundaryModel]) -> None: "assets": {}, } ) -assert_type(asset.format_kind, CanonicalFormatKind) +assert_type(asset.format_kind, str) -def listed_kind(creative: Creative) -> CanonicalFormatKind: - assert_type(creative.format_kind, CanonicalFormatKind) +def listed_kind(creative: Creative) -> str: + assert_type(creative.format_kind, str) return creative.format_kind manifest = CreativeManifest(assets={}) -assert_type(manifest.format_kind, CanonicalFormatKind | None) +assert_type(manifest.format_kind, str | None) delivery = DeliveryCreative( creative_id="creative-1", format_kind="future_canonical_format", variants=[], ) -assert_type(delivery.format_kind, CanonicalFormatKind | str | None) +assert_type(delivery.format_kind, str | None) def check_served_manifest(response: GetCreativeDeliveryResponse) -> None: for creative in response.creatives: - for variant in creative.variants: + # ``variants`` narrows the generated ``list[CreativeVariant]`` to the + # tolerant delivery row, which pydantic accepts and mypy's invariant + # list rule does not — so the field is declared ``SchemaVariant`` and + # reads as ``Any``. Name the element type to keep the assertion below + # grading something: the row is module-private because it is a readback + # shape no adopter constructs, which is why the type is spelled here + # rather than imported from ``adcp.types``. + variants: Sequence[_DeliveryCreativeVariant] = creative.variants + for variant in variants: if variant.manifest is not None: - assert_type(variant.manifest.format_kind, CanonicalFormatKind | str | None) + # This used to read ``Any``: the tolerant manifest widened the + # generated ``CanonicalFormatKind | None`` to admit an unknown + # kind, no annotation expresses a widening override, so the + # field had to be ``SchemaVariant``. With one open type there is + # no widening, the readback manifest redeclares nothing, and the + # inherited annotation is visible and assertable. + assert_type(variant.manifest.format_kind, str | None) diff --git a/tests/type_checks/domain_variants.py b/tests/type_checks/domain_variants.py index 36972532d..c5b9db46a 100644 --- a/tests/type_checks/domain_variants.py +++ b/tests/type_checks/domain_variants.py @@ -60,6 +60,4 @@ billing = BillingNotSupportedDetails(scope=ScopeFromBillingNotSupported.account) assert_type(billing.scope, ScopeFromBillingNotSupported | None) -rate_limit_scopes: list[ScopeFromRateLimited] = [ - ScopeFromRateLimited.account -] +rate_limit_scopes: list[ScopeFromRateLimited] = [ScopeFromRateLimited.account] diff --git a/tests/type_checks/extend_targeting_input.py b/tests/type_checks/extend_targeting_input.py index b8fa10894..118c7dda6 100644 --- a/tests/type_checks/extend_targeting_input.py +++ b/tests/type_checks/extend_targeting_input.py @@ -1,5 +1,7 @@ """The exact public mutation variant supports adopter subclasses and all facades.""" +from typing import cast + from pydantic import Field from typing_extensions import assert_type @@ -23,13 +25,23 @@ class InternalTargetingInput(TargetingOverlayInput): ) updated = PackageUpdate.model_validate({"package_id": "package-1", "targeting_overlay": overlay}) -# The canonical facade stubs expose the actual runtime readback union. Keep the -# extended object reference to access adopter-only attributes without a cast. -assert_type(created.targeting_overlay, TargetingOverlayInput | TargetingOverlay | None) -assert_type(updated.targeting_overlay, TargetingOverlayInput | TargetingOverlay | None) +# The STATIC type is the generated declaration, ``TargetingOverlayInput | None``. +# The readback union is wider at runtime: ``_forward_compat`` widens the field +# to admit beta.14 ``TargetingOverlay`` objects, carrying the ``SkipJsonSchema`` +# arm, the left-to-right union mode and a per-model core schema with it — a +# runtime mutation no source annotation expresses, so no type checker sees it. +# These assertions previously read the wider union out of +# ``canonical_creative.pyi``, which hand-transcribed the patch result; the stub +# is deleted -- it declared 35 classes while leaving 759 of their runtime fields +# undeclared across 33 of them, 40 of those required, and declared one field the +# runtime does not have -- so the static claim is now the generated one, and the +# wider runtime union is graded by the identity assertions below and by +# tests/test_targeting_overlay_compat.py. +assert_type(created.targeting_overlay, TargetingOverlayInput | None) +assert_type(updated.targeting_overlay, TargetingOverlayInput | None) +assert_type(overlay.workflow_id, str) assert created.targeting_overlay is overlay assert updated.targeting_overlay is overlay -assert_type(overlay.workflow_id, str) class LegacyTargeting(TargetingOverlay): @@ -40,5 +52,7 @@ class LegacyTargeting(TargetingOverlay): legacy_update = PackageUpdate.model_validate( {"package_id": "package-1", "targeting_overlay": legacy} ) -assert_type(legacy_update.targeting_overlay, TargetingOverlayInput | TargetingOverlay | None) -assert legacy_update.targeting_overlay is legacy +assert_type(legacy_update.targeting_overlay, TargetingOverlayInput | None) +# The beta.14 arm is the half the static annotation does not carry, so the +# identity check has to say so explicitly rather than read as non-overlapping. +assert cast(object, legacy_update.targeting_overlay) is legacy diff --git a/tests/type_checks/response_envelope_fields.py b/tests/type_checks/response_envelope_fields.py index c05b25a03..e836f2878 100644 --- a/tests/type_checks/response_envelope_fields.py +++ b/tests/type_checks/response_envelope_fields.py @@ -31,6 +31,8 @@ SyncCreativesResponse1, UpdateMediaBuyResponse3, ) +from adcp.types.domains.core.pagination_response import PaginationResponse +from adcp.types.domains.creative.list_creatives_response import QuerySummary def echo_envelope(response: ProtocolEnvelope) -> str | None: @@ -99,15 +101,19 @@ def read_envelope_status(response: ProtocolEnvelope) -> GeneratedTaskStatus: # --- ``status`` is defaulted, so construction must not demand it --- # -# ``status`` has a default on every runtime response. If the stub declared it -# without ``= ...`` the synthesized ``__init__`` would require it, and these -# three plain constructions would fail to type-check even though the runtime -# model defaults the field. (``ListCreativesResponse`` also has required -# ``query_summary``/``pagination`` fields the stub does not enumerate, so only -# the ``status`` half of its signature is asserted here; the runtime -# construction is covered in tests/test_protocol_envelope_inheritance.py.) - -listed = ListCreativesResponse(creatives=[]) +# ``status`` has a default on every runtime response, so a plain construction +# must type-check without it. ``ListCreativesResponse`` also REQUIRES +# ``query_summary`` and ``pagination`` at runtime — the deleted canonical stub +# declared neither, so this construction type-checked while failing at runtime +# (#1366's hidden-required-fields defect). Now that the canonical models are +# real subclasses of the generated wire models, mypy reads the true required +# set and the construction below is the one the runtime actually accepts. + +listed = ListCreativesResponse( + creatives=[], + query_summary=QuerySummary(total_matching=0, returned=0), + pagination=PaginationResponse(has_more=False), +) assert_type(listed.status, GeneratedTaskStatus) buys = GetMediaBuysResponse(media_buys=[])